Compare commits

..
Author SHA1 Message Date
Omar Basem 6221b42f2a fix: android bottom padding 2023-05-16 10:44:03 +04:00
105 changed files with 908 additions and 1846 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.5'
library 'status-jenkins-lib@v1.7.3'
/* 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.5'
library 'status-jenkins-lib@v1.7.3'
pipeline {
agent { label 'linux' }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.5'
library 'status-jenkins-lib@v1.7.3'
/* 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.5'
library 'status-jenkins-lib@v1.7.3'
pipeline {
agent { label params.AGENT_LABEL }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.5'
library 'status-jenkins-lib@v1.7.3'
/* 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.5'
library 'status-jenkins-lib@v1.7.3'
pipeline {
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.5'
library 'status-jenkins-lib@v1.7.3'
pipeline {
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.5'
library 'status-jenkins-lib@v1.7.3'
pipeline {
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.5'
library 'status-jenkins-lib@v1.7.3'
pipeline {
agent { label 'macos' }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.5'
library 'status-jenkins-lib@v1.7.3'
pipeline {
agent { label 'linux' }
+1 -1
View File
@@ -1,4 +1,4 @@
library 'status-jenkins-lib@v1.7.5'
library 'status-jenkins-lib@v1.7.3'
pipeline {
agent {
-37
View File
@@ -365,43 +365,6 @@ dispatch.
(str "Hello " username)]])
```
### Registering effects
When registering re-frame effects (`reg-fx`), prefer to expose a data-only
interface because that will allow event handlers to stay pure.
For instance, if an effect needs a `on-success` callback, allow it to receive a
*re-frame event vector*. This approach is used by us in the [json-rpc/call
effect](src/status_im2/common/json_rpc/events.cljs), but also by third-party
effects, such as https://github.com/Day8/re-frame-http-fx. For the complete
rationale, see [PR #15936](https://github.com/status-im/status-mobile/pull/15936).
### Using the effect `:json-rpc/call`
Prefer the pure version of `:json-rpc/call` (no callbacks).
```clojure
;; not as good
(rf/defn accept-contact-request
{:events [:activity-center.contact-requests/accept]}
[_ contact-id]
{:json-rpc/call
[{:method "wakuext_acceptContactRequest"
:params [{:id contact-id}]
:on-success #(rf/dispatch [:sanitize-messages-and-process-response %])
:on-error #(rf/dispatch [:activity-center.contact-requests/accept-error contact-id %])}]})
;; better
(rf/defn accept-contact-request
{:events [:activity-center.contact-requests/accept]}
[_ contact-id]
{:json-rpc/call
[{:method "wakuext_acceptContactRequest"
:params [{:id contact-id}]
:on-success [:sanitize-messages-and-process-response]
:on-error [:activity-center.contact-requests/accept-error contact-id]}]})
```
### Registering event handlers
Events must always be declared with the `utils.fx/defn` macro. Also, don't use
Binary file not shown.

Before

Width:  |  Height:  |  Size: 838 B

After

Width:  |  Height:  |  Size: 944 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.7 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.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 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-opa-70 override-theme)
:password-icon (colors/theme-colors colors/neutral-100 colors/white 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
@@ -1,20 +0,0 @@
(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
@@ -1,42 +0,0 @@
(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
@@ -1,38 +0,0 @@
(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)}]]))
@@ -6,7 +6,6 @@
{:title "Some title"
:description "Some description"
:link "status.im"
:logo "data:image/png,logo-x"
:thumbnail "data:image/png,whatever"})
(h/describe "Links - Link Preview"
@@ -20,17 +19,12 @@
(h/is-truthy (h/query-by-text (:title props)))
(h/is-truthy (h/query-by-text (:description props)))
(h/is-truthy (h/query-by-text (:link props)))
(h/is-truthy (h/query-by-label-text :logo))
(h/is-truthy (h/query-by-label-text :thumbnail)))
(h/test "does not render thumbnail if prop is not present"
(h/render [view/view (dissoc props :thumbnail)])
(h/is-null (h/query-by-label-text :thumbnail)))
(h/test "does not render logo if prop is not present"
(h/render [view/view (dissoc props :logo)])
(h/is-null (h/query-by-label-text :logo)))
(h/test "shows button to enable preview when preview is disabled"
(h/render [view/view
(assoc props
@@ -54,9 +54,7 @@
[logo]
[rn/image
{:accessibility-label :logo
:source (if (string? logo)
{:uri logo}
logo)
:source logo
:style style/logo}])
(defn view
@@ -70,11 +68,9 @@
(if enabled?
[:<>
[rn/view {:style style/header-container}
(when logo
[logo-comp logo])
[logo-comp logo]
[title-comp title]]
(when description
[description-comp description])
[description-comp description]
[link-comp link]
(when thumbnail
[thumbnail-comp thumbnail thumbnail-size])]
@@ -7,14 +7,10 @@
(h/test "default render"
(h/render [view/view])
(h/is-truthy (h/query-by-label-text :title))
(h/is-truthy (h/query-by-label-text :logo))
(h/is-truthy (h/query-by-label-text :button-clear-preview))
(h/is-null (h/query-by-label-text :logo))
(h/is-null (h/query-by-label-text :url-preview-loading)))
(h/test "renders logo when prop is present"
(h/render [view/view {:logo "data:image/png,logo"}])
(h/is-truthy (h/query-by-label-text :logo)))
(h/test "on-clear event"
(let [on-clear (h/mock-fn)]
(h/render [view/view {:on-clear on-clear}])
@@ -6,13 +6,11 @@
[quo2.foundations.colors :as colors]
[react-native.core :as rn]))
(defn- logo-comp
(defn- logo-component
[{:keys [logo]}]
[rn/image
{:accessibility-label :logo
:source (if (string? logo)
{:uri logo}
logo)
:source logo
:style style/logo}])
(defn- content
@@ -59,7 +57,6 @@
[rn/view
{:accessibility-label :url-preview
:style (merge (style/container) container-style)}
(when logo
[logo-comp {:logo logo}])
[logo-component {:logo logo}]
[content {:title title :body body}]
[clear-button {:on-press on-clear}]]))
@@ -1,9 +1,10 @@
(ns quo2.components.links.url-preview-list.view
(:require
[oops.core :as oops]
[quo2.components.links.url-preview-list.style :as style]
[quo2.components.links.url-preview.view :as url-preview]
[react-native.core :as rn]
[react-native.gesture :as gesture]))
[reagent.core :as reagent]))
(defn- use-scroll-to-last-item
[flat-list-ref item-count item-width]
@@ -43,33 +44,39 @@
:on-clear on-clear
:container-style (merge container-style {:width width})}])
(defn- calculate-width
[preview-width horizontal-spacing ^js e]
(reset! preview-width
(- (oops/oget e "nativeEvent.layout.width")
(* 2 horizontal-spacing))))
(defn- f-view
[]
(let [flat-list-ref (atom nil)]
(let [preview-width (reagent/atom 0)
flat-list-ref (atom nil)]
(fn [{:keys [data key-fn horizontal-spacing on-clear loading-message
container-style container-style-item
preview-width]}]
(use-scroll-to-last-item flat-list-ref (count data) preview-width)
container-style container-style-item]}]
(use-scroll-to-last-item flat-list-ref (count data) @preview-width)
;; We need to use a wrapping view expanded to 100% instead of "flex 1",
;; otherwise `on-layout` will be triggered multiple times as the flat list
;; renders its children.
[rn/view
{:style container-style
{:style (merge container-style {:width "100%"})
:accessibility-label :url-preview-list}
[gesture/flat-list
[rn/flat-list
{:ref #(reset! flat-list-ref %)
:keyboard-should-persist-taps :always
:key-fn key-fn
:on-layout #(calculate-width preview-width horizontal-spacing %)
:horizontal true
:deceleration-rate :fast
:on-scroll-to-index-failed identity
:content-container-style {:padding-horizontal horizontal-spacing}
:separator [separator]
:snap-to-interval (+ preview-width style/url-preview-gap)
:snap-to-interval (+ @preview-width style/url-preview-gap)
:shows-horizontal-scroll-indicator false
:data data
:render-fn item-component
:render-data {:width preview-width
:render-data {:width @preview-width
:on-clear on-clear
:loading-message loading-message
:container-style container-style-item}}]])))
+2 -3
View File
@@ -1,12 +1,11 @@
(ns quo2.components.share.qr-code.view
(:require [quo2.components.share.qr-code.style :as style]
[react-native.core :as rn]
[react-native.fast-image :as fast-image]))
[react-native.core :as rn]))
(defn qr-code
[{:keys [source width height]}]
[rn/view
{:style style/container}
[fast-image/fast-image
[rn/image
{:source source
:style (style/image width height)}]])
+9 -15
View File
@@ -4,7 +4,6 @@
[quo2.components.icon :as icons]
[quo2.components.markdown.text :as text]
[quo2.components.tags.context-tag.style :as style]
[quo2.components.avatars.user-avatar.style :as user-avatar-style]
[react-native.core :as rn]))
(defn trim-public-key
@@ -40,21 +39,16 @@
(trim-public-key public-key)]])
(defn context-tag
[{:keys [text-style blur? no-avatar-placeholder?] :as params} photo name channel-name]
(let [text-params {:weight :medium
:size :paragraph-2
:style (assoc text-style :justify-content :center)}
empty-photo? (empty? photo)
avatar-size :xxs
avatar-outer-size (get-in user-avatar-style/sizes [avatar-size :outer])]
[{:keys [text-style blur?] :as params} photo name channel-name]
(let [text-params {:weight :medium
:size :paragraph-2
:style (assoc text-style :justify-content :center)}]
[base-tag (assoc-in params [:style :padding-left] 3)
(if (and empty-photo? no-avatar-placeholder?)
[rn/view {:style {:width avatar-outer-size}}]
[user-avatar/user-avatar
{:full-name name
:profile-picture photo
:size avatar-size
:status-indicator? false}])
[user-avatar/user-avatar
{:full-name name
:profile-picture photo
:size :xxs
:status-indicator? false}]
[rn/view {:style style/context-tag-text-container}
[text/text text-params (str " " name)]
(when channel-name
-2
View File
@@ -38,7 +38,6 @@
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
@@ -127,7 +126,6 @@
;;;; 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
@@ -1,11 +0,0 @@
(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))
+10 -16
View File
@@ -6,12 +6,11 @@
[status-im.chat.models.mentions :as mentions]
[status-im.chat.models.message :as chat.message]
[status-im.chat.models.message-content :as message-content]
[status-im.utils.utils :as utils]
[status-im2.constants :as constants]
[status-im2.contexts.chat.composer.link-preview.events :as link-preview]
[taoensso.timbre :as log]
[utils.re-frame :as rf]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
[status-im.utils.utils :as utils]
[taoensso.timbre :as log]))
(defn text->emoji
"Replaces emojis in a specified `text`"
@@ -145,15 +144,13 @@
preferred-name (get-in db [:multiaccount :preferred-name])
emoji? (message-content/emoji-only-content? {:text input-text
:response-to message-id})]
{:chat-id current-chat-id
:content-type (if emoji?
constants/content-type-emoji
constants/content-type-text)
:text input-text
:response-to message-id
:ens-name preferred-name
:link-previews (map #(select-keys % [:url :title :description :thumbnail])
(get-in db [:chat/link-previews :unfurled]))})))
{:chat-id current-chat-id
:content-type (if emoji?
constants/content-type-emoji
constants/content-type-text)
:text input-text
:response-to message-id
:ens-name preferred-name})))
(defn build-image-messages
[{db :db} chat-id input-text]
@@ -188,7 +185,6 @@
(let [current-chat-id (:current-chat-id db)]
(rf/merge cofx
(clean-input current-chat-id)
(link-preview/reset-unfurled)
(mentions/clear-mentions))))
(rf/defn send-messages
@@ -200,7 +196,6 @@
(when (seq messages)
(rf/merge cofx
(clean-input (:current-chat-id db))
(link-preview/reset-unfurled)
(chat.message/send-messages messages)))))
(rf/defn send-audio-message
@@ -246,7 +241,6 @@
:on-success (fn [result]
(re-frame/dispatch [:sanitize-messages-and-process-response
result]))}]}
(link-preview/reset-unfurled)
(cancel-message-edit)))
(rf/defn send-current-message
@@ -31,7 +31,6 @@
(= {:last-message {:quoted-message nil
:outgoing-status nil
:command-parameters nil
:link-previews []
:content {:sticker nil
:rtl? nil
:ens-name nil
@@ -47,7 +46,6 @@
:reply-message {:quoted-message nil
:outgoing-status nil
:command-parameters nil
:link-previews []
:content {:sticker nil
:rtl? nil
:ens-name nil
+2 -10
View File
@@ -15,13 +15,6 @@
:community-id :communityId
:clock-value :clock})))
(defn- <-link-preview-rpc
[preview]
(update preview
:thumbnail
(fn [thumbnail]
(set/rename-keys thumbnail {:dataUri :data-uri}))))
(defn <-rpc
[message]
(-> message
@@ -50,9 +43,8 @@
:imageHeight :image-height
:new :new?
:albumImagesCount :album-images-count
:displayName :display-name
:linkPreviews :link-previews})
(update :link-previews #(map <-link-preview-rpc %))
:displayName :display-name})
(update :quoted-message
set/rename-keys
{:parsedText :parsed-text
+3 -7
View File
@@ -8,7 +8,7 @@
"0x0424a68f89ba5fcd5e0640c1e1f591d561fa4125ca4e2a43592bc4123eca10ce064e522c254bb83079ba404327f6eafc01ec90a1444331fe769d3f3a7f90b0dde1")
(deftest message<-rpc
(testing "message from RPC"
(testing "message to rpc"
(let [expected {:message-id message-id
:content {:chat-id chat-id
:sticker {:hash "hash" :pack 1}
@@ -34,9 +34,7 @@
:text "reply"}
:content-type 1
:compressed-key "c"
:timestamp 3
:link-previews [{:thumbnail {:url "http://localhost"
:data-uri "data:image/png"}}]}
:timestamp 3}
message {:id message-id
:whisperTimestamp 1
:parsedText "parsed-text"
@@ -58,7 +56,5 @@
:quotedMessage {:from "from"
:text "reply"}
:timestamp 3
:outgoingStatus "sending"
:linkPreviews [{:thumbnail {:url "http://localhost"
:dataUri "data:image/png"}}]}]
:outgoingStatus "sending"}]
(is (= expected (m/<-rpc message))))))
+29 -29
View File
@@ -1,35 +1,35 @@
(ns ^{:doc "Protocol API and protocol utils"} status-im.transport.message.protocol
(:require [clojure.set :as set]
[re-frame.core :as re-frame]
[taoensso.timbre :as log]
[utils.re-frame :as rf]))
(defn- link-preview->rpc
[preview]
(update preview
:thumbnail
(fn [thumbnail]
(set/rename-keys thumbnail {:data-uri :dataUri}))))
(:require [re-frame.core :as re-frame]
[utils.re-frame :as rf]
[taoensso.timbre :as log]))
(defn build-message
[msg]
(-> msg
(update :link-previews #(map link-preview->rpc %))
(set/rename-keys
{:album-id :albumId
:audio-duration-ms :audioDurationMs
:audio-path :audioPath
:chat-id :chatId
:community-id :communityId
:content-type :contentType
:ens-name :ensName
:image-height :imageHeight
:image-path :imagePath
:image-width :imageWidth
:link-previews :linkPreviews
:response-to :responseTo
:sticker :sticker
:text :text})))
[{:keys [chat-id
album-id
image-width
image-height
text
response-to
ens-name
community-id
image-path
audio-path
audio-duration-ms
sticker
content-type]}]
{:chatId chat-id
:albumId album-id
:imageWidth image-width
:imageHeight image-height
:text text
:responseTo response-to
:ensName ens-name
:imagePath image-path
:audioPath audio-path
:audioDurationMs audio-duration-ms
:communityId community-id
:sticker sticker
:contentType content-type})
(rf/defn send-chat-messages
[_ messages]
@@ -17,7 +17,7 @@
:style {:width 40
:height 40}}]
(seq thumbnail-image)
[photos/photo {:uri thumbnail-image} {:size 40}]
[photos/photo thumbnail-image {:size 40}]
:else
[chat-icon.screen/chat-icon-view-chat-list
@@ -13,8 +13,7 @@
[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]
[react-native.platform :as platform]))
[native-module.core :as native-module]))
;; TODO(yenda) investigate why `handle-universal-link` event is
;; dispatched 7 times for the same link
@@ -176,15 +175,10 @@
(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 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
"
on login, otherwise just handle it"
{:events [:universal-links/handle-url]}
[{:keys [db] :as cofx} url]
(if (and (multiaccounts.model/logged-in? db)
(or platform/android? (= (:app-state db) "active")))
(if (and (multiaccounts.model/logged-in? db) (= (:app-state db) "active"))
(route-url cofx url)
(store-url-for-later cofx url)))
+1 -3
View File
@@ -95,9 +95,7 @@
(assoc button-common-props :accessibility-label :open-scanner-button)
:i/scan]
[quo/button
(merge button-common-props
{:accessibility-label :show-qr-button
:on-press #(rf/dispatch [:open-modal :share-shell])})
(assoc button-common-props :accessibility-label :show-qr-button)
:i/qr-code]
[rn/view
[unread-indicator]
+11 -47
View File
@@ -1,13 +1,12 @@
(ns status-im2.common.json-rpc.events
(:require [clojure.string :as string]
[native-module.core :as native-module]
[re-frame.core :as re-frame]
[react-native.background-timer :as background-timer]
[native-module.core :as native-module]
[taoensso.timbre :as log]
[utils.re-frame :as rf]
[utils.transforms :as transforms]))
(defn- on-error-retry
(defn on-error-retry
[call-method {:keys [method number-of-retries delay on-error] :as arg}]
(if (pos? number-of-retries)
(fn [error]
@@ -25,31 +24,6 @@
on-error))
(defn call
"Call private RPC endpoint.
method: string - The name of an endpoint function in status-go, with the first
character lowercased and prefixed by wakuext_. For example, the BackupData
function should be represented as the string wakuext_backupData.
params: sequence - A positional sequence of zero or more arguments.
on-success/on-error: function/vector (optional) - When a function, it will be
called with the transformed response as the sole argument. When a vector, it
is expected to be a valid re-frame event vector, and the event will be
dispatched with the transformed response conj'ed at the end.
js-response: boolean - When non-nil, the successful response will not be
recursively converted to Clojure data structures. Default: nil.
number-of-retries: integer - The maximum number of retries in case of failure.
Default: nil.
delay: integer - The number of milliseconds to wait between retries. Default:
nil.
Note that on-error is optional, but if not provided, a default implementation
will be used.
"
[{:keys [method params on-success on-error js-response] :as arg}]
(let [params (or params [])
on-error (or on-error
@@ -60,25 +34,15 @@
:id 1
:method method
:params params})
(fn [raw-response]
(if (string/blank? raw-response)
(let [error {:message "Blank response"}]
(if (vector? on-error)
(rf/dispatch (conj on-error error))
(on-error error)))
(let [^js response-js (transforms/json->js raw-response)]
(if-let [error (.-error response-js)]
(let [error (transforms/js->clj error)]
(if (vector? on-error)
(rf/dispatch (conj on-error error))
(on-error error)))
(when on-success
(let [result (if js-response
(.-result response-js)
(transforms/js->clj (.-result response-js)))]
(if (vector? on-success)
(rf/dispatch (conj on-success result))
(on-success result)))))))))))
(fn [response]
(if (string/blank? response)
(on-error {:message "Blank response"})
(let [response-js (transforms/json->js response)]
(if (.-error response-js)
(on-error (transforms/js->clj (.-error response-js)))
(on-success (if js-response
(.-result response-js)
(transforms/js->clj (.-result response-js)))))))))))
(re-frame/reg-fx
:json-rpc/call
+1 -4
View File
@@ -15,10 +15,7 @@
: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")
@@ -112,9 +112,12 @@
(when-let [notification (get-notification db notification-id)]
{:json-rpc/call [{:method "wakuext_markActivityCenterNotificationsRead"
:params [[notification-id]]
:on-success [:activity-center.notifications/mark-as-read-success notification]
:on-error [:activity-center/process-notification-failure notification-id
:notification/mark-as-read]}]}))
:on-success #(rf/dispatch [:activity-center.notifications/mark-as-read-success
notification])
:on-error #(rf/dispatch [:activity-center/process-notification-failure
notification-id
:notification/mark-as-read
%])}]}))
(rf/defn mark-as-read-success
{:events [:activity-center.notifications/mark-as-read-success]}
@@ -127,9 +130,12 @@
(when-let [notification (get-notification db notification-id)]
{:json-rpc/call [{:method "wakuext_markActivityCenterNotificationsUnread"
:params [[notification-id]]
:on-success [:activity-center.notifications/mark-as-unread-success notification]
:on-error [:activity-center/process-notification-failure notification-id
:notification/mark-as-unread]}]}))
:on-success #(rf/dispatch [:activity-center.notifications/mark-as-unread-success
notification])
:on-error #(rf/dispatch [:activity-center/process-notification-failure
notification-id
:notification/mark-as-unread
%])}]}))
(rf/defn mark-as-unread-success
{:events [:activity-center.notifications/mark-as-unread-success]}
@@ -143,9 +149,12 @@
(when (>= now undoable-till)
{:json-rpc/call [{:method "wakuext_markAllActivityCenterNotificationsRead"
:params []
:on-success [:activity-center.notifications/mark-all-as-read-success]
:on-error [:activity-center/process-notification-failure nil
:notification/mark-all-as-read]}]})))
:on-success #(rf/dispatch
[:activity-center.notifications/mark-all-as-read-success])
:on-error #(rf/dispatch [:activity-center/process-notification-failure
nil
:notification/mark-all-as-read
%])}]})))
(rf/defn mark-all-as-read-success
{:events [:activity-center.notifications/mark-all-as-read-success]}
@@ -201,14 +210,17 @@
[{:keys [db]} notification-id]
{:json-rpc/call [{:method "wakuext_acceptActivityCenterNotifications"
:params [[notification-id]]
:on-success [:activity-center.notifications/accept-success notification-id]
:on-error [:activity-center/process-notification-failure notification-id
:notification/accept]}]})
:on-success #(rf/dispatch [:activity-center.notifications/accept-success
notification-id %])
:on-error #(rf/dispatch [:activity-center/process-notification-failure
notification-id
:notification/accept
%])}]})
(rf/defn accept-notification-success
{:events [:activity-center.notifications/accept-success]}
[{:keys [db] :as cofx} notification-id {:keys [chats]}]
(when-let [notification (get-notification db notification-id)]
(let [notification (get-notification db notification-id)]
(rf/merge cofx
(chat.events/ensure-chats (map data-store.chats/<-rpc chats))
(notifications-reconcile [(assoc notification :read true :accepted true)]))))
@@ -218,14 +230,17 @@
[{:keys [db]} notification-id]
{:json-rpc/call [{:method "wakuext_dismissActivityCenterNotifications"
:params [[notification-id]]
:on-success [:activity-center.notifications/dismiss-success notification-id]
:on-error [:activity-center/process-notification-failure notification-id
:notification/dismiss]}]})
:on-success #(rf/dispatch [:activity-center.notifications/dismiss-success
notification-id])
:on-error #(rf/dispatch [:activity-center/process-notification-failure
notification-id
:notification/dismiss
%])}]})
(rf/defn dismiss-notification-success
{:events [:activity-center.notifications/dismiss-success]}
[{:keys [db] :as cofx} notification-id]
(when-let [notification (get-notification db notification-id)]
(let [notification (get-notification db notification-id)]
(notifications-reconcile cofx [(assoc notification :read true :dismissed true)])))
(rf/defn delete-notification
@@ -233,9 +248,12 @@
[{:keys [db]} notification-id]
{:json-rpc/call [{:method "wakuext_deleteActivityCenterNotifications"
:params [[notification-id]]
:on-success [:activity-center.notifications/delete-success notification-id]
:on-error [:activity-center/process-notification-failure notification-id
:notification/delete]}]})
:on-success #(rf/dispatch [:activity-center.notifications/delete-success
notification-id])
:on-error #(rf/dispatch [:activity-center/process-notification-failure
notification-id
:notification/delete
%])}]})
(rf/defn delete-notification-success
{:events [:activity-center.notifications/delete-success]}
@@ -250,36 +268,48 @@
[_ notification-id]
{:json-rpc/call [{:method "wakuext_declineContactVerificationRequest"
:params [notification-id]
:on-success [:activity-center/reconcile-notifications-from-response]
:on-error [:activity-center/process-notification-failure notification-id
:contact-verification/decline]}]})
:on-success #(rf/dispatch [:activity-center/reconcile-notifications-from-response
%])
:on-error #(rf/dispatch [:activity-center/process-notification-failure
notification-id
:contact-verification/decline
%])}]})
(rf/defn contact-verification-reply
{:events [:activity-center.contact-verification/reply]}
[_ notification-id reply]
{:json-rpc/call [{:method "wakuext_acceptContactVerificationRequest"
:params [notification-id reply]
:on-success [:activity-center/reconcile-notifications-from-response]
:on-error [:activity-center/process-notification-failure notification-id
:contact-verification/reply]}]})
:on-success #(rf/dispatch [:activity-center/reconcile-notifications-from-response
%])
:on-error #(rf/dispatch [:activity-center/process-notification-failure
notification-id
:contact-verification/reply
%])}]})
(rf/defn contact-verification-mark-as-trusted
{:events [:activity-center.contact-verification/mark-as-trusted]}
[_ notification-id]
{:json-rpc/call [{:method "wakuext_verifiedTrusted"
:params [{:id notification-id}]
:on-success [:activity-center/reconcile-notifications-from-response]
:on-error [:activity-center/process-notification-failure notification-id
:contact-verification/mark-as-trusted]}]})
:on-success #(rf/dispatch [:activity-center/reconcile-notifications-from-response
%])
:on-error #(rf/dispatch [:activity-center/process-notification-failure
notification-id
:contact-verification/mark-as-trusted
%])}]})
(rf/defn contact-verification-mark-as-untrustworthy
{:events [:activity-center.contact-verification/mark-as-untrustworthy]}
[_ notification-id]
{:json-rpc/call [{:method "wakuext_verifiedUntrustworthy"
:params [{:id notification-id}]
:on-success [:activity-center/reconcile-notifications-from-response]
:on-error [:activity-center/process-notification-failure notification-id
:contact-verification/mark-as-untrustworthy]}]})
:on-success #(rf/dispatch [:activity-center/reconcile-notifications-from-response
%])
:on-error #(rf/dispatch [:activity-center/process-notification-failure
notification-id
:contact-verification/mark-as-untrustworthy
%])}]})
;;;; Notifications fetching and pagination
@@ -338,9 +368,10 @@
:limit per-page
:activityTypes (filter-type->rpc-param filter-type)
:readType (->rpc-read-type filter-status)}]
:on-success [:activity-center.notifications/fetch-success reset-data?]
:on-error [:activity-center.notifications/fetch-error filter-type
filter-status]}]})))
:on-success #(rf/dispatch [:activity-center.notifications/fetch-success
reset-data? %])
:on-error #(rf/dispatch [:activity-center.notifications/fetch-error
filter-type filter-status %])}]})))
(rf/defn notifications-fetch-first-page
{:events [:activity-center.notifications/fetch-first-page]}
@@ -403,8 +434,9 @@
:limit 20
:activityTypes [types/contact-request]
:readType (->rpc-read-type :unread)}]
:on-success [:activity-center.notifications/fetch-pending-contact-requests-success]
:on-error [:activity-center.notifications/fetch-error types/contact-request :unread]}]})
:on-success #(rf/dispatch [:activity-center.notifications/fetch-pending-contact-requests-success %])
:on-error #(rf/dispatch [:activity-center.notifications/fetch-error types/contact-request :unread
%])}]})
(rf/defn notifications-fetch-pending-contact-requests-success
{:events [:activity-center.notifications/fetch-pending-contact-requests-success]}
@@ -432,8 +464,8 @@
{:json-rpc/call
[{:method "wakuext_hasUnseenActivityCenterNotifications"
:params []
:on-success [:activity-center/update-seen-state-success]
:on-error [:activity-center/update-seen-state-error]}]})
:on-success #(rf/dispatch [:activity-center/update-seen-state-success %])
:on-error #(rf/dispatch [:activity-center/update-seen-state-error %])}]})
(rf/defn update-seen-state-success
{:events [:activity-center/update-seen-state-success]}
@@ -453,8 +485,8 @@
{:json-rpc/call
[{:method "wakuext_markAsSeenActivityCenterNotifications"
:params []
:on-success [:activity-center/mark-as-seen-success]
:on-error [:activity-center/mark-as-seen-error]}]})
:on-success #(rf/dispatch [:activity-center/mark-as-seen-success %])
:on-error #(rf/dispatch [:activity-center/mark-as-seen-error %])}]})
(rf/defn mark-as-seen-success
{:events [:activity-center/mark-as-seen-success]}
@@ -477,8 +509,8 @@
[{:method "wakuext_activityCenterNotificationsCount"
:params [{:activityTypes types/all-supported
:readType (->rpc-read-type :unread)}]
:on-success [:activity-center.notifications/fetch-unread-count-success]
:on-error [:activity-center.notifications/fetch-unread-count-error]}]})
:on-success #(rf/dispatch [:activity-center.notifications/fetch-unread-count-success %])
:on-error #(rf/dispatch [:activity-center.notifications/fetch-unread-count-error %])}]})
(rf/defn notifications-fetch-unread-count-success
{:events [:activity-center.notifications/fetch-unread-count-success]}
@@ -1,169 +1,163 @@
(ns status-im2.contexts.activity-center.events-test
(:require [cljs.test :refer [deftest is testing]]
[status-im2.constants :as constants]
[status-im2.contexts.activity-center.events :as events]
status-im.events
[test-helpers.unit :as h]
[status-im2.contexts.activity-center.notification-types :as types]
[test-helpers.unit :as h]))
[utils.re-frame :as rf]))
(h/use-log-fixture)
(def notification-id "0x1")
(defn setup
[]
(h/register-helper-events)
(rf/dispatch [:setup/app-started]))
(defn test-log-on-failure
[{:keys [before-test notification-id event action]}]
(h/run-test-sync
(setup)
(when before-test
(before-test))
(h/stub-fx-with-callbacks :json-rpc/call :on-error (constantly :fake-error))
(rf/dispatch event)
(is (= {:args [(str "Failed to " action)
{:notification-id notification-id
:error :fake-error}]
:level :warn}
(last @h/logs)))))
;;;; Misc
(deftest open-activity-center-test
(testing "opens the activity center with default filters"
(is (= {:db {}
:dispatch [:open-modal :activity-center {}]
:dispatch-later [{:ms 1000 :dispatch [:activity-center/mark-as-seen]}]}
(events/open-activity-center {:db {}} nil))))
(testing "opens the activity center with filters enabled"
(is (= {:db {:activity-center {:filter {:status :unread :type types/contact-request}}}
:dispatch [:open-modal :activity-center {}]
:dispatch-later [{:ms 1000 :dispatch [:activity-center/mark-as-seen]}]}
(events/open-activity-center {:db {}}
{:filter-type types/contact-request
:filter-status :unread})))))
(h/run-test-sync
(setup)
(rf/dispatch [:activity-center/open
{:filter-type types/contact-request
:filter-status :unread}])
(deftest process-notification-failure-test
(testing "logs and returns nil"
(is (nil? (events/process-notification-failure
{:db {}}
notification-id
:some-action-name
:some-error)))
(is (= {:args ["Failed to :some-action-name"
{:notification-id notification-id
:error :some-error}]
:level :warn}
(last @h/logs)))))
(is (= {:status :unread
:type types/contact-request}
(get-in (h/db) [:activity-center :filter])))))
;;;; Mark as read/unread
(testing "opens the activity center with default filters"
(h/run-test-sync
(setup)
(rf/dispatch [:activity-center/open])
(is (= {:status :unread :type types/no-type}
(get-in (h/db) [:activity-center :filter]))))))
(deftest mark-as-read-test
(testing "does nothing if the notification ID cannot be found in the app db"
(let [cofx {:db {:activity-center
{:notifications [{:id "0x1"
:read false
:type types/one-to-one-chat}]}}}]
(is (nil? (events/mark-as-read cofx "0x99")))))
(h/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/spy-fx spy-queue :json-rpc/call)
(let [notifications [{:id notification-id
:read false
:type types/one-to-one-chat}]]
(rf/dispatch [:test/assoc-in [:activity-center :notifications] notifications])
(testing "dispatches RPC call"
(let [notif {:id "0x1" :read false :type types/one-to-one-chat}
cofx {:db {:activity-center {:notifications [notif]}}}]
(is (= {:json-rpc/call
[{:method "wakuext_markActivityCenterNotificationsRead"
:params [[(:id notif)]]
:on-success [:activity-center.notifications/mark-as-read-success notif]
:on-error [:activity-center/process-notification-failure (:id notif)
:notification/mark-as-read]}]}
(events/mark-as-read cofx (:id notif)))))))
(rf/dispatch [:activity-center.notifications/mark-as-read "0x666"])
(deftest mark-as-read-success-test
(let [f-args (atom [])
cofx {:db {}}
notif {:id "0x1" :read false :type types/one-to-one-chat}]
(with-redefs [events/notifications-reconcile
(fn [& args]
(reset! f-args args)
:result)]
(is (= :result (events/mark-as-read-success cofx notif)))
(is (= [cofx [(assoc notif :read true)]]
@f-args)))))
(is (= [] @spy-queue))
(is (= notifications (get-in (h/db) [:activity-center :notifications])))))))
(deftest mark-as-unread-test
(testing "does nothing if the notification ID cannot be found in the app db"
(let [cofx {:db {:activity-center
{:notifications [{:id "0x1"
:read true
:type types/one-to-one-chat}]}}}]
(is (nil? (events/mark-as-unread cofx "0x99")))))
(testing "marks notifications as read and updates app db"
(h/run-test-sync
(setup)
(let [notif-1 {:id "0x1" :read true :type types/one-to-one-chat}
notif-2 {:id "0x2" :read false :type types/one-to-one-chat}
notif-3 {:id "0x3" :read false :type types/one-to-one-chat}
new-notif-3 (assoc notif-3 :read true)
new-notif-2 (assoc notif-2 :read true)]
(h/stub-fx-with-callbacks :json-rpc/call :on-success (constantly nil))
(rf/dispatch [:test/assoc-in [:activity-center]
{:filter {:status :all :type types/no-type}
:notifications [notif-3 notif-2 notif-1]}])
(testing "dispatches RPC call"
(let [notif {:id "0x1" :read true :type types/one-to-one-chat}
cofx {:db {:activity-center {:notifications [notif]}}}]
(is (= {:json-rpc/call
[{:method "wakuext_markActivityCenterNotificationsUnread"
:params [[(:id notif)]]
:on-success [:activity-center.notifications/mark-as-unread-success notif]
:on-error [:activity-center/process-notification-failure (:id notif)
:notification/mark-as-unread]}]}
(events/mark-as-unread cofx (:id notif)))))))
(rf/dispatch [:activity-center.notifications/mark-as-read (:id notif-2)])
(is (= [notif-3 new-notif-2 notif-1]
(get-in (h/db) [:activity-center :notifications])))
(deftest mark-as-unread-success-test
(let [f-args (atom [])
cofx {:db {}}
notif {:id "0x1" :read true :type types/one-to-one-chat}]
(with-redefs [events/notifications-reconcile
(fn [& args]
(reset! f-args args)
:reconciliation-result)]
(is (= :reconciliation-result (events/mark-as-unread-success cofx notif)))
(is (= [cofx [(assoc notif :read false)]]
@f-args)))))
(rf/dispatch [:activity-center.notifications/mark-as-read (:id notif-3)])
(is (= [new-notif-3 new-notif-2 notif-1]
(get-in (h/db) [:activity-center :notifications]))))))
(testing "logs on failure"
(test-log-on-failure
{:notification-id notification-id
:event [:activity-center.notifications/mark-as-read notification-id]
:action :notification/mark-as-read
:before-test (fn []
(rf/dispatch
[:test/assoc-in [:activity-center :notifications]
[{:id notification-id
:read false
:type types/one-to-one-chat}]]))})))
;;;; Acceptance/dismissal
(deftest accept-notification-test
(is (= {:json-rpc/call
[{:method "wakuext_acceptActivityCenterNotifications"
:params [[notification-id]]
:on-success [:activity-center.notifications/accept-success notification-id]
:on-error [:activity-center/process-notification-failure notification-id
:notification/accept]}]}
(events/accept-notification {:db {}} notification-id))))
(deftest accept-notification-success-test
(testing "does nothing if the notification ID cannot be found in the app db"
(let [cofx {:db {:activity-center
{:notifications [{:id "0x1"
:read false
:type types/one-to-one-chat}]}}}]
(is (nil? (events/accept-notification-success cofx "0x99" nil)))))
(deftest notification-acceptance-test
(testing "marks notification as accepted and read, then reconciles"
(let [notif-1 {:id "0x1" :type types/private-group-chat}
notif-2 {:id "0x2" :type types/private-group-chat}
notif-2-accepted (assoc notif-2 :accepted true :read true)
cofx {:db {:activity-center {:filter {:type types/no-type :status :all}
:notifications [notif-2 notif-1]}}}]
(is (= {:db {:activity-center {:filter {:type 0 :status :all}
:notifications [notif-2-accepted notif-1]}
:chats {}
:chats-home-list nil}
:dispatch-n [[:activity-center.notifications/fetch-unread-count]
[:activity-center.notifications/fetch-pending-contact-requests]]}
(events/accept-notification-success cofx (:id notif-2) nil))))))
(h/run-test-sync
(setup)
(let [notif-1 {:id "0x1" :type types/private-group-chat}
notif-2 {:id "0x2" :type types/private-group-chat}
notif-2-accepted (assoc notif-2 :accepted true :read true)]
(h/stub-fx-with-callbacks :json-rpc/call :on-success (constantly notif-2))
(rf/dispatch [:test/assoc-in [:activity-center]
{:filter {:type types/no-type :status :all}
:notifications [notif-2 notif-1]}])
(deftest dismiss-notification-test
(is (= {:json-rpc/call
[{:method "wakuext_dismissActivityCenterNotifications"
:params [[notification-id]]
:on-success [:activity-center.notifications/dismiss-success notification-id]
:on-error [:activity-center/process-notification-failure notification-id
:notification/dismiss]}]}
(events/dismiss-notification {:db {}} notification-id))))
(rf/dispatch [:activity-center.notifications/accept (:id notif-2)])
(deftest dismiss-notification-success-test
(testing "does nothing if the notification ID cannot be found in the app db"
(let [cofx {:db {:activity-center
{:notifications [{:id "0x1"
:read false
:type types/one-to-one-chat}]}}}]
(is (nil? (events/dismiss-notification-success cofx "0x99")))))
(is (= [notif-2-accepted notif-1]
(get-in (h/db) [:activity-center :notifications])))
(testing "marks notification as dismissed and read, then reconciles"
(let [notif-1 {:id "0x1" :type types/private-group-chat}
notif-2 {:id "0x2" :type types/private-group-chat}
notif-2-dismissed (assoc notif-2 :dismissed true :read true)
cofx {:db {:activity-center {:filter {:type types/no-type :status :all}
:notifications [notif-2 notif-1]}}}]
(is (= {:db {:activity-center {:filter {:type 0 :status :all}
:notifications [notif-2-dismissed notif-1]}}
:dispatch-n [[:activity-center.notifications/fetch-unread-count]
[:activity-center.notifications/fetch-pending-contact-requests]]}
(events/dismiss-notification-success cofx (:id notif-2)))))))
;; Ignores accepted notification if the Unread filter is enabled because
;; accepted notifications are also marked as read in status-go.
(rf/dispatch [:test/assoc-in [:activity-center :filter]
{:filter {:type types/no-type :status :unread}}])
(rf/dispatch [:activity-center.notifications/accept (:id notif-2)])
(is (= [notif-1]
(get-in (h/db) [:activity-center :notifications]))))))
(testing "logs on failure"
(test-log-on-failure
{:notification-id notification-id
:event [:activity-center.notifications/accept notification-id]
:action :notification/accept})))
(deftest notification-dismissal-test
(testing "dismisses & mark notification as read, and keep it in the app db"
(h/run-test-sync
(setup)
(let [notif-1 {:id "0x1" :type types/private-group-chat}
notif-2 {:id "0x2" :type types/admin}
dismissed-notif-1 (assoc notif-1 :dismissed true :read true)]
(h/stub-fx-with-callbacks :json-rpc/call :on-success (constantly notif-2))
(rf/dispatch [:test/assoc-in [:activity-center]
{:filter {:type types/no-type :status :all}
:notifications [notif-2 notif-1]}])
(rf/dispatch [:activity-center.notifications/dismiss (:id notif-1)])
(is (= [notif-2 dismissed-notif-1]
(get-in (h/db) [:activity-center :notifications]))))))
(testing "logs on failure"
(test-log-on-failure
{:notification-id notification-id
:event [:activity-center.notifications/dismiss notification-id]
:action :notification/dismiss})))
;;;; Contact verification
@@ -202,7 +196,6 @@
:text nil}
:outgoing false
:outgoing-status nil
:link-previews []
:quoted-message nil}
:name "0x04d03f"
:read true
@@ -211,228 +204,339 @@
:type types/contact-verification})
(deftest contact-verification-decline-test
(is (= {:json-rpc/call
[{:method "wakuext_declineContactVerificationRequest"
:params [notification-id]
:on-success [:activity-center/reconcile-notifications-from-response]
:on-error [:activity-center/process-notification-failure notification-id
:contact-verification/decline]}]}
(events/contact-verification-decline {:db {}} notification-id))))
(testing "declines notification and reconciles"
(h/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/stub-fx-with-callbacks :json-rpc/call
:on-success
(constantly contact-verification-rpc-response))
(h/spy-fx spy-queue :json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center]
{:filter {:type types/contact-verification :status :all}}])
(rf/dispatch [:activity-center.contact-verification/decline notification-id])
(is (= [contact-verification-expected-notification]
(get-in (h/db) [:activity-center :notifications]))))))
(testing "logs on failure"
(test-log-on-failure
{:notification-id notification-id
:event [:activity-center.contact-verification/decline notification-id]
:action :contact-verification/decline})))
(deftest contact-verification-reply-test
(let [reply "The answer is 42"]
(is (= {:json-rpc/call
[{:method "wakuext_acceptContactVerificationRequest"
:params [notification-id reply]
:on-success [:activity-center/reconcile-notifications-from-response]
:on-error [:activity-center/process-notification-failure notification-id
:contact-verification/reply]}]}
(events/contact-verification-reply {:db {}} notification-id reply)))))
(testing "sends reply and reconciles"
(let [reply "any answer"]
(h/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/stub-fx-with-callbacks :json-rpc/call
:on-success
(constantly contact-verification-rpc-response))
(h/spy-fx spy-queue :json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center]
{:filter {:type types/contact-verification :status :all}}])
(rf/dispatch [:activity-center.contact-verification/reply notification-id reply])
(is (= [contact-verification-expected-notification]
(get-in (h/db) [:activity-center :notifications])))))))
(testing "logs on failure"
(test-log-on-failure
{:notification-id notification-id
:event [:activity-center.contact-verification/reply notification-id "any answer"]
:action :contact-verification/reply})))
(deftest contact-verification-mark-as-trusted-test
(is (= {:json-rpc/call
[{:method "wakuext_verifiedTrusted"
:params [{:id notification-id}]
:on-success [:activity-center/reconcile-notifications-from-response]
:on-error [:activity-center/process-notification-failure notification-id
:contact-verification/mark-as-trusted]}]}
(events/contact-verification-mark-as-trusted {:db {}} notification-id))))
(testing "app db reconciliation"
(h/run-test-sync
(setup)
(h/stub-fx-with-callbacks :json-rpc/call
:on-success
(constantly contact-verification-rpc-response))
;; With "Unread" filter disabled
(rf/dispatch [:test/assoc-in [:activity-center]
{:filter {:type types/no-type :status :all}}])
(rf/dispatch [:activity-center.contact-verification/mark-as-trusted notification-id])
(is (= [contact-verification-expected-notification]
(get-in (h/db) [:activity-center :notifications])))
;; With "Unread" filter enabled
(rf/dispatch [:test/assoc-in [:activity-center :filter :status] :unread])
(rf/dispatch [:activity-center.contact-verification/mark-as-trusted notification-id])
(is (= [] (get-in (h/db) [:activity-center :notifications])))))
(testing "logs on failure"
(test-log-on-failure
{:notification-id notification-id
:event [:activity-center.contact-verification/mark-as-trusted notification-id]
:action :contact-verification/mark-as-trusted})))
(deftest contact-verification-mark-as-untrustworthy-test
(is (= {:json-rpc/call
[{:method "wakuext_verifiedUntrustworthy"
:params [{:id notification-id}]
:on-success [:activity-center/reconcile-notifications-from-response]
:on-error [:activity-center/process-notification-failure notification-id
:contact-verification/mark-as-untrustworthy]}]}
(events/contact-verification-mark-as-untrustworthy {:db {}} notification-id))))
(testing "app db reconciliation"
(h/run-test-sync
(setup)
(h/stub-fx-with-callbacks
:json-rpc/call
:on-success
(constantly contact-verification-rpc-response))
;; With "Unread" filter disabled
(rf/dispatch [:test/assoc-in [:activity-center]
{:filter {:type types/no-type :status :all}}])
(rf/dispatch [:activity-center.contact-verification/mark-as-untrustworthy notification-id])
(is (= [contact-verification-expected-notification]
(get-in (h/db) [:activity-center :notifications])))
;; With "Unread" filter enabled
(rf/dispatch [:test/assoc-in [:activity-center :filter :status] :unread])
(rf/dispatch [:activity-center.contact-verification/mark-as-untrustworthy notification-id])
(is (= [] (get-in (h/db) [:activity-center :notifications])))))
(testing "logs on failure"
(test-log-on-failure
{:notification-id notification-id
:event [:activity-center.contact-verification/mark-as-untrustworthy notification-id]
:action :contact-verification/mark-as-untrustworthy})))
;;;; Notification reconciliation
(deftest notifications-reconcile-test
(testing "All tab + All filter"
(let [notif-1 {:id "0x1" :read true :type types/one-to-one-chat}
notif-2 {:id "0x2" :read false :type types/system}
new-notif-3 {:id "0x3" :read false :type types/system}
new-notif-4 {:id "0x4" :read true :type types/system}
new-notif-2 (assoc notif-2 :read true)
cofx {:db {:activity-center
{:filter {:type types/no-type :status :all}
:notifications [notif-2 notif-1]}}}]
(is (= {:db {:activity-center
{:filter {:type types/no-type :status :all}
:notifications [new-notif-4 new-notif-3 new-notif-2]}}
:dispatch-n [[:activity-center.notifications/fetch-unread-count]
[:activity-center.notifications/fetch-pending-contact-requests]]}
(events/notifications-reconcile
cofx
[(assoc notif-1 :deleted true) ; will be removed
new-notif-2
new-notif-3
new-notif-4])))))
(h/run-test-sync
(setup)
(let [notif-1 {:id "0x1" :read true :type types/one-to-one-chat}
notif-2 {:id "0x2" :read false :type types/system}
new-notif-3 {:id "0x3" :read false :type types/system}
new-notif-4 {:id "0x4" :read true :type types/system}
new-notif-2 (assoc notif-2 :read true)]
(rf/dispatch [:test/assoc-in [:activity-center]
{:filter {:type types/no-type :status :all}
:notifications [notif-2 notif-1]}])
(rf/dispatch
[:activity-center.notifications/reconcile
[(assoc notif-1 :deleted true) ; will be removed
new-notif-2
new-notif-3
new-notif-4]])
(is (= [new-notif-4 new-notif-3 new-notif-2]
(get-in (h/db) [:activity-center :notifications]))))))
(testing "All tab + Unread filter"
(let [notif-1 {:id "0x1" :read false :type types/one-to-one-chat}
notif-2 {:id "0x2" :read false :type types/system}
new-notif-2 (assoc notif-2 :read true)
new-notif-3 {:id "0x3" :read false :type types/system}
new-notif-4 {:id "0x4" :read true :type types/system}
notif-5 {:id "0x5" :type types/system}
cofx {:db {:activity-center
{:filter {:type types/no-type :status :unread}
:notifications [notif-5 notif-2 notif-1]}}}]
(is (= {:db {:activity-center
{:filter {:type types/no-type :status :unread}
:notifications [new-notif-3 notif-1]}}
:dispatch-n [[:activity-center.notifications/fetch-unread-count]
[:activity-center.notifications/fetch-pending-contact-requests]]}
(events/notifications-reconcile
cofx
[new-notif-2 ; will be removed because it's read
new-notif-3 ; will be inserted
new-notif-4 ; will be ignored because it's read
(assoc notif-5 :deleted true) ; will be removed
])))))
(h/run-test-sync
(setup)
(let [notif-1 {:id "0x1" :read false :type types/one-to-one-chat}
notif-2 {:id "0x2" :read false :type types/system}
new-notif-2 (assoc notif-2 :read true)
new-notif-3 {:id "0x3" :read false :type types/system}
new-notif-4 {:id "0x4" :read true :type types/system}
notif-5 {:id "0x5" :type types/system}]
(rf/dispatch [:test/assoc-in [:activity-center]
{:filter {:type types/no-type :status :unread}
:notifications [notif-5 notif-2 notif-1]}])
(rf/dispatch
[:activity-center.notifications/reconcile
[new-notif-2 ; will be removed because it's read
new-notif-3 ; will be inserted
new-notif-4 ; will be ignored because it's read
(assoc notif-5 :deleted true) ; will be removed
]])
(is (= [new-notif-3 notif-1]
(get-in (h/db) [:activity-center :notifications]))))))
(testing "Contact request tab + All filter"
(let [notif-1 {:id "0x1" :read true :type types/contact-request}
notif-2 {:id "0x2" :read false :type types/contact-request}
new-notif-2 (assoc notif-2 :read true)
new-notif-3 {:id "0x3" :read false :type types/contact-request}
new-notif-4 {:id "0x4" :read true :type types/system}
notif-5 {:id "0x5" :read false :type types/contact-request}
cofx {:db {:activity-center
{:filter {:type types/contact-request :status :all}
:notifications [notif-5 notif-2 notif-1]}}}]
(is (= {:db {:activity-center
{:filter {:type types/contact-request :status :all}
:notifications [new-notif-3 new-notif-2 notif-1]}}
:dispatch-n [[:activity-center.notifications/fetch-unread-count]
[:activity-center.notifications/fetch-pending-contact-requests]]}
(events/notifications-reconcile
cofx
[new-notif-2 ; will be updated
new-notif-3 ; will be inserted
new-notif-4 ; will be ignored because it's not a contact request
(assoc notif-5 :deleted true) ; will be removed
])))))
(h/run-test-sync
(setup)
(let [notif-1 {:id "0x1" :read true :type types/contact-request}
notif-2 {:id "0x2" :read false :type types/contact-request}
new-notif-2 (assoc notif-2 :read true)
new-notif-3 {:id "0x3" :read false :type types/contact-request}
new-notif-4 {:id "0x4" :read true :type types/system}
notif-5 {:id "0x5" :read false :type types/contact-request}]
(rf/dispatch [:test/assoc-in [:activity-center]
{:filter {:type types/contact-request :status :all}
:notifications [notif-5 notif-2 notif-1]}])
(rf/dispatch
[:activity-center.notifications/reconcile
[new-notif-2 ; will be updated
new-notif-3 ; will be inserted
new-notif-4 ; will be ignored because it's not a contact request
(assoc notif-5 :deleted true) ; will be removed
]])
(is (= [new-notif-3 new-notif-2 notif-1]
(get-in (h/db) [:activity-center :notifications]))))))
(testing "Contact request tab + Unread filter"
(let [notif-1 {:id "0x1" :read false :type types/contact-request}
notif-2 {:id "0x2" :read false :type types/contact-request}
new-notif-2 (assoc notif-2 :read true)
new-notif-3 {:id "0x3" :read false :type types/contact-request}
new-notif-4 {:id "0x4" :read true :type types/contact-request}
new-notif-5 {:id "0x5" :read true :type types/system}
notif-6 {:id "0x6" :read false :type types/contact-request}
cofx {:db {:activity-center
{:filter {:type types/contact-request
:status :unread}
:notifications [notif-6 notif-2 notif-1]}}}]
(is (= {:db {:activity-center
{:filter {:type types/contact-request
:status :unread}
:notifications [new-notif-3 notif-1]}}
:dispatch-n [[:activity-center.notifications/fetch-unread-count]
[:activity-center.notifications/fetch-pending-contact-requests]]}
(events/notifications-reconcile
cofx
[new-notif-2 ; will be removed because it's read
new-notif-3 ; will be inserted
new-notif-4 ; will be ignored because it's read
new-notif-5 ; will be ignored because it's not a contact request
(assoc notif-6 :deleted true) ; will be removed
])))))
(h/run-test-sync
(setup)
(let [notif-1 {:id "0x1" :read false :type types/contact-request}
notif-2 {:id "0x2" :read false :type types/contact-request}
new-notif-2 (assoc notif-2 :read true)
new-notif-3 {:id "0x3" :read false :type types/contact-request}
new-notif-4 {:id "0x4" :read true :type types/contact-request}
new-notif-5 {:id "0x5" :read true :type types/system}
notif-6 {:id "0x6" :read false :type types/contact-request}]
(rf/dispatch [:test/assoc-in [:activity-center]
{:filter {:type types/contact-request :status :unread}
:notifications [notif-6 notif-2 notif-1]}])
(rf/dispatch
[:activity-center.notifications/reconcile
[new-notif-2 ; will be removed because it's read
new-notif-3 ; will be inserted
new-notif-4 ; will be ignored because it's read
new-notif-5 ; will be ignored because it's not a contact request
(assoc notif-6 :deleted true) ; will be removed
]])
(is (= [new-notif-3 notif-1]
(get-in (h/db) [:activity-center :notifications]))))))
;; Sorting by timestamp and ID is compatible with what the backend does when
;; returning paginated results.
(testing "sorts notifications by timestamp and id in descending order"
(let [notif-1 {:id "0x1" :timestamp 1}
notif-2 {:id "0x2" :timestamp 1}
notif-3 {:id "0x3" :timestamp 50}
notif-4 {:id "0x4" :timestamp 100}
notif-5 {:id "0x5" :timestamp 100}
new-notif-1 (assoc notif-1 :last-message {})
new-notif-4 (assoc notif-4 :last-message {})
cofx {:db {:activity-center
{:notifications [notif-1 notif-3 notif-4 notif-2
notif-5]}}}]
(is (= {:db {:activity-center
{:notifications [notif-5
new-notif-4
notif-3
notif-2
new-notif-1]}}
:dispatch-n [[:activity-center.notifications/fetch-unread-count]
[:activity-center.notifications/fetch-pending-contact-requests]]}
(events/notifications-reconcile cofx [new-notif-1 new-notif-4]))))))
(h/run-test-sync
(setup)
(let [notif-1 {:id "0x1" :timestamp 1}
notif-2 {:id "0x2" :timestamp 1}
notif-3 {:id "0x3" :timestamp 50}
notif-4 {:id "0x4" :timestamp 100}
notif-5 {:id "0x5" :timestamp 100}
new-notif-1 (assoc notif-1 :last-message {})
new-notif-4 (assoc notif-4 :last-message {})]
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
[notif-1 notif-3 notif-4 notif-2 notif-5]])
(rf/dispatch [:activity-center.notifications/reconcile [new-notif-1 new-notif-4]])
(is (= [notif-5 new-notif-4 notif-3 notif-2 new-notif-1]
(get-in (h/db) [:activity-center :notifications])))))))
(deftest remove-pending-contact-request-test
(testing "removes notification from all related filter types and status"
(let [author "0x99"
notif-1 {:id "0x1" :read true :type types/contact-request}
notif-2 {:id "0x2" :read false :type types/contact-request :author author}
notif-3 {:id "0x3" :read false :type types/private-group-chat :author author}
cofx {:db {:activity-center
{:notifications
[notif-3 ; will be ignored because it's not a contact
; request
notif-2 ; will be removed
notif-1 ; will be ignored because it's not from the
; same author
]}}}]
(is (= {:db {:activity-center {:notifications [notif-3 notif-1]}}}
(events/notifications-remove-pending-contact-request cofx author))))))
(h/run-test-sync
(setup)
(let [author "0x99"
notif-1 {:id "0x1" :read true :type types/contact-request}
notif-2 {:id "0x2" :read false :type types/contact-request :author author}
notif-3 {:id "0x3" :read false :type types/private-group-chat :author author}]
(rf/dispatch [:test/assoc-in [:activity-center :notifications]
[notif-3 ; will be ignored because it's not a contact request
notif-2 ; will be removed
notif-1 ; will be ignored because it's not from the same author
]])
(rf/dispatch [:activity-center/remove-pending-contact-request author])
(is (= [notif-3 notif-1]
(get-in (h/db) [:activity-center :notifications])))))))
;;;; Notifications fetching and pagination
(deftest notifications-fetch-first-page-test
(deftest notifications-fetch-test
(testing "fetches first page"
(let [cofx {:db {}}]
(is (= {:db {:activity-center {:filter {:type types/one-to-one-chat
:status :unread}
:loading? true}}
:json-rpc/call [{:method "wakuext_activityCenterNotifications"
:params [{:cursor ""
:limit (:notifications-per-page events/defaults)
:activityTypes [types/one-to-one-chat]
:readType events/read-type-unread}]
:on-success [:activity-center.notifications/fetch-success true]
:on-error [:activity-center.notifications/fetch-error
types/one-to-one-chat :unread]}]}
(events/notifications-fetch-first-page cofx {:filter-type types/one-to-one-chat}))))))
(h/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/stub-fx-with-callbacks
:json-rpc/call
:on-success
(constantly {:cursor "10"
:notifications [{:id "0x1"
:type types/one-to-one-chat
:read false
:chatId "0x9"}]}))
(h/spy-fx spy-queue :json-rpc/call)
(rf/dispatch [:activity-center.notifications/fetch-first-page
{:filter-type types/one-to-one-chat}])
(is (= :unread (get-in (h/db) [:activity-center :filter :status])))
(is (= "" (get-in @spy-queue [0 :args 0 :params 0 :cursor]))
"Should be called with empty cursor when fetching first page")
(is (= "10" (get-in (h/db) [:activity-center :cursor])))
(is (= [{:chat-id "0x9"
:chat-name nil
:chat-type types/one-to-one-chat
:group-chat false
:id "0x1"
:public? false
:last-message nil
:message nil
:read false
:reply-message nil
:type types/one-to-one-chat}]
(get-in (h/db) [:activity-center :notifications]))))))
(deftest notifications-fetch-next-next-page-test
(testing "does not fetch next page when pagination cursor reached the end"
(is (nil? (events/notifications-fetch-next-page
{:db {:activity-center {:cursor events/start-or-end-cursor}}}))))
(h/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/spy-fx spy-queue :json-rpc/call)
(testing "fetches the next page"
(let [f-args (atom [])
cursor "abc"
cofx {:db {:activity-center {:cursor cursor
:filter {:type types/one-to-one-chat
:status :unread}}}}]
(with-redefs [events/notifications-fetch
(fn [& args]
(reset! f-args args)
:result)]
(is (= :result (events/notifications-fetch-next-page cofx)))
(is (= [cofx
{:cursor cursor
:filter-type types/one-to-one-chat
:filter-status :unread
:reset-data? false}]
@f-args))))))
(rf/dispatch [:test/assoc-in [:activity-center :cursor] ""])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
(is (= [] @spy-queue)))))
(deftest notifications-fetch-error-test
(testing "resets loading state"
(let [cofx {:db {:activity-center
{:loading? true
:filter {:status :unread
:type types/one-to-one-chat}
:cursor ""}}}]
(is (= {:db {:activity-center {:filter {:status :unread
:type types/one-to-one-chat}
:cursor ""}}}
(events/notifications-fetch-error cofx :dummy-error))))))
(testing "fetches next page when pagination cursor is not empty"
(h/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/stub-fx-with-callbacks
:json-rpc/call
:on-success
(constantly {:cursor ""
:notifications [{:id "0x1"
:type types/mention
:read false
:chatId "0x9"}]}))
(h/spy-fx spy-queue :json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center]
{:filter {:status :unread :type types/mention}
:cursor "10"}])
(rf/dispatch [:activity-center.notifications/fetch-next-page])
(is (= "10" (get-in @spy-queue [0 :args 0 :params 0 :cursor]))
"Should be called with current cursor")
(is (= "" (get-in (h/db) [:activity-center :cursor])))
(is (= [{:chat-id "0x9"
:chat-name nil
:chat-type 3
:id "0x1"
:last-message nil
:message nil
:read false
:reply-message nil
:type types/mention}]
(get-in (h/db) [:activity-center :notifications]))))))
(testing "resets loading state after error"
(h/run-test-sync
(setup)
(let [spy-queue (atom [])]
(h/stub-fx-with-callbacks :json-rpc/call :on-error (constantly :fake-error))
(h/spy-event-fx spy-queue :activity-center.notifications/fetch-error)
(h/spy-fx spy-queue :json-rpc/call)
(rf/dispatch [:test/assoc-in [:activity-center]
{:filter {:status :unread :type types/one-to-one-chat}
:cursor ""}])
(rf/dispatch [:activity-center.notifications/fetch-first-page])
(is (nil? (get-in (h/db) [:activity-center :loading?])))
(is (= [:activity-center.notifications/fetch-error
types/one-to-one-chat
:unread
:fake-error]
(:args (last @spy-queue))))))))
@@ -1,5 +1,6 @@
(ns status-im2.contexts.activity-center.notification.admin.view
(:require [quo2.core :as quo]
[quo2.foundations.colors :as colors]
[status-im2.constants :as constants]
[status-im2.contexts.activity-center.notification.common.style :as common-style]
[status-im2.contexts.activity-center.notification.common.view :as common]
@@ -68,8 +69,12 @@
:context [[common/user-avatar-tag author]
(i18n/label :t/wants-to-join)
[quo/context-tag
common/tag-params
community-image community-name]]
{:size :small
:override-theme :dark
:color colors/primary-50
:style common-style/user-avatar-tag
:text-style common-style/user-avatar-tag-text}
{:uri community-image} community-name]]
:items (case membership-status
constants/activity-center-membership-status-accepted
[{:type :status
@@ -9,12 +9,11 @@
[utils.re-frame :as rf]))
(def tag-params
{:size :small
:override-theme :dark
:color colors/primary-50
:style style/user-avatar-tag
:text-style style/user-avatar-tag-text
:no-avatar-placeholder? true})
{:size :small
:override-theme :dark
:color colors/primary-50
:style style/user-avatar-tag
:text-style style/user-avatar-tag-text})
(defn user-avatar-tag
[user-id]
@@ -33,5 +33,5 @@
:unread? (not read)
:context [[quo/text {:style common-style/user-avatar-tag-text}
(i18n/label :t/community-kicked-body)]
[quo/context-tag common/tag-params community-image
[quo/context-tag common/tag-params {:uri community-image}
community-name]]}]]))
@@ -22,7 +22,7 @@
[community membership-status]
(let [community-name (:name community)
community-image (get-in community [:images :thumbnail :uri])
community-context-tag [quo/context-tag common/tag-params community-image
community-context-tag [quo/context-tag common/tag-params {:uri community-image}
community-name]]
(cond
(= membership-status constants/activity-center-membership-status-idle)
@@ -1,6 +1,7 @@
(ns status-im2.contexts.activity-center.notification.mentions.view
(:require [clojure.string :as string]
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[react-native.gesture :as gesture]
[status-im2.contexts.activity-center.notification.common.view :as common]
[status-im2.contexts.activity-center.notification.mentions.style :as style]
@@ -8,6 +9,13 @@
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(def ^:private tag-params
{:size :small
:override-theme :dark
:color colors/primary-50
:style style/tag
:text-style style/tag-text})
(defn- message-body
[message]
(let [parsed-text (get-in message [:content :parsed-text])
@@ -60,6 +68,6 @@
:context [[common/user-avatar-tag author]
[quo/text {:style style/tag-text} (string/lower-case (i18n/label :t/on))]
(if community-chat?
[quo/context-tag common/tag-params community-image community-name chat-name]
[quo/group-avatar-tag chat-name common/tag-params])]
[quo/context-tag tag-params {:uri community-image} community-name chat-name]
[quo/group-avatar-tag chat-name tag-params])]
:message {:body (message-body message)}}]]]))
@@ -1,6 +1,7 @@
(ns status-im2.contexts.activity-center.notification.reply.view
(:require [clojure.string :as string]
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[react-native.gesture :as gesture]
[status-im.ui2.screens.chat.messages.message :as old-message]
[status-im2.common.not-implemented :as not-implemented]
@@ -11,6 +12,13 @@
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(def ^:private tag-params
{:size :small
:override-theme :dark
:color colors/primary-50
:style style/tag
:text-style style/tag-text})
;; NOTE: Replies support text, image and stickers only.
(defn- get-message-content
[{:keys [content-type] :as message}]
@@ -68,7 +76,7 @@
:context [[common/user-avatar-tag author]
[quo/text {:style style/lowercase-text} (i18n/label :t/on)]
(if community-chat?
[quo/context-tag common/tag-params community-image community-name chat-name]
[quo/group-avatar-tag chat-name common/tag-params])]
[quo/context-tag tag-params {:uri community-image} community-name chat-name]
[quo/group-avatar-tag chat-name tag-params])]
:message {:body-number-of-lines 1
:body (get-message-content message)}}]]]))
@@ -31,4 +31,4 @@
:left -20
:right -20
:bottom 0
:height constants/composer-default-height})
:height (- constants/composer-default-height constants/bottom-padding)})
@@ -64,7 +64,7 @@
[:f> f-send-button state animations window-height images? btn-opacity z-index]))
(defn audio-button
[{:keys [record-reset-fn input-ref]}
[{:keys [record-reset-fn]}
{:keys [record-permission? recording? gesture-enabled? focused?]}
{:keys [container-opacity]}]
(let [audio (rf/sub [:chats/sending-audio])]
@@ -86,22 +86,17 @@
(reset! recording? false)
(reset! gesture-enabled? true)
(rf/dispatch [:chat/send-audio file-path duration])
(if-not @focused?
(when-not @focused?
(reanimated/animate container-opacity
constants/empty-opacity)
(js/setTimeout #(when @input-ref (.focus ^js @input-ref))
300))
constants/empty-opacity))
(rf/dispatch [:chat.ui/set-input-audio nil]))
:on-cancel (fn []
(when @recording?
(reset! recording? false)
(reset! gesture-enabled? true)
(if-not @focused?
(when-not @focused?
(reanimated/animate container-opacity
constants/empty-opacity)
(js/setTimeout #(when @input-ref
(.focus ^js @input-ref))
300))
constants/empty-opacity))
(rf/dispatch [:chat.ui/set-input-audio nil])))
:on-check-audio-permissions (fn []
(permissions/permission-granted?
@@ -9,7 +9,9 @@
(def ^:const actions-container-height 56)
(def ^:const composer-default-height (+ bar-container-height input-height actions-container-height))
(def ^:const bottom-padding (if platform/android? 15 0))
(def ^:const composer-default-height (+ bar-container-height input-height actions-container-height bottom-padding))
(def ^:const line-height (:line-height typography/paragraph-1))
@@ -19,8 +21,6 @@
(def ^:const images-container-height 76)
(def ^:const links-container-height 76)
(def ^:const reply-container-height 32)
(def ^:const edit-container-height 32)
@@ -38,7 +38,3 @@
(def ^:const background-threshold 0.75)
(def ^:const max-text-size 4096)
(def ^:const unfurl-debounce-ms
"Use a high threshold to prevent unnecessary rendering overhead."
400)
@@ -1,16 +1,14 @@
(ns status-im2.contexts.chat.composer.effects
(:require
[clojure.string :as string]
[oops.core :as oops]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[status-im.async-storage.core :as async-storage]
[react-native.core :as rn]
[react-native.reanimated :as reanimated]
[status-im2.contexts.chat.composer.constants :as constants]
[status-im2.contexts.chat.composer.keyboard :as kb]
[utils.debounce :as debounce]
[utils.number :as utils.number]
[utils.re-frame :as rf]))
[oops.core :as oops]
[utils.debounce :as debounce]))
(defn reenter-screen-effect
[{:keys [text-value saved-cursor-position maximized?]}
@@ -55,12 +53,6 @@
(reanimated/set-shared-value background-y 0)
(reanimated/animate opacity 1)))
(defn link-preview-effect
[{:keys [text-value]}]
(let [text @text-value]
(when-not (string/blank? text)
(rf/dispatch [:link-preview/unfurl-urls text]))))
(defn images-effect
[{:keys [sending-images? input-ref]}
{:keys [container-opacity]}
@@ -86,17 +78,10 @@
[{:keys [text-value maximized? focused?]}
{:keys [container-opacity]}
images?
link-previews?
reply?
audio]
(when
(and (empty? @text-value)
(not images?)
(not link-previews?)
(not reply?)
(not @maximized?)
(not @focused?)
(not audio))
(and (empty? @text-value) (not images?) (not reply?) (not @maximized?) (not @focused?) (not audio))
(reanimated/animate-delay container-opacity constants/empty-opacity 200)))
(defn component-will-unmount
@@ -106,8 +91,7 @@
(.remove ^js @keyboard-frame-listener))
(defn initialize
[props state animations {:keys [max-height] :as dimensions}
{:keys [chat-input images link-previews? reply audio]}]
[props state animations {:keys [max-height] :as dimensions} {:keys [chat-input images reply audio]}]
(rn/use-effect
(fn []
(maximized-effect state animations dimensions chat-input)
@@ -116,9 +100,8 @@
(kb-default-height-effect state)
(background-effect state animations dimensions chat-input)
(images-effect props animations images)
(link-preview-effect state)
(audio-effect state animations audio)
(empty-effect state animations images link-previews? reply audio)
(empty-effect state animations images reply audio)
(kb/add-kb-listeners props state animations dimensions)
#(component-will-unmount props))
[max-height]))
@@ -1,15 +1,15 @@
(ns status-im2.contexts.chat.composer.handlers
(:require
[oops.core :as oops]
[react-native.core :as rn]
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]
[oops.core :as oops]
[status-im2.contexts.chat.composer.constants :as constants]
[status-im2.contexts.chat.composer.keyboard :as kb]
[status-im2.contexts.chat.composer.selection :as selection]
[status-im2.contexts.chat.composer.utils :as utils]
[utils.debounce :as debounce]
[utils.re-frame :as rf]))
[status-im2.contexts.chat.composer.selection :as selection]
[utils.re-frame :as rf]
[utils.debounce :as debounce]))
(defn focus
[{:keys [input-ref] :as props}
@@ -36,30 +36,29 @@
(defn blur
[{:keys [text-value focused? lock-selection? cursor-position saved-cursor-position gradient-z-index
maximized? recording?]}
maximized?]}
{:keys [height saved-height last-height gradient-opacity container-opacity opacity background-y]}
{:keys [content-height max-height window-height]}
{:keys [images link-previews? reply]}]
(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 link-previews? 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])))))
{: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]))))
(defn content-size-change
[event
@@ -111,9 +110,6 @@
[text
{:keys [input-ref record-reset-fn]}
{:keys [text-value cursor-position recording?]}]
(debounce/debounce-and-dispatch [:link-preview/unfurl-urls text]
constants/unfurl-debounce-ms)
(reset! text-value text)
(reagent/next-tick #(when @input-ref
(.setNativeProps ^js @input-ref
@@ -1,132 +0,0 @@
(ns status-im2.contexts.chat.composer.link-preview.events
(:require
[clojure.set :as set]
[clojure.string :as string]
[status-im.data-store.messages :as data-store.messages]
[taoensso.timbre :as log]
[utils.collection]
[utils.re-frame :as rf]))
(rf/defn unfurl-urls
{:events [:link-preview/unfurl-urls]}
[{:keys [db]} text]
(if (string/blank? text)
{:db (update db :chat/link-previews dissoc :unfurled :request-id)}
{:json-rpc/call
[{:method "wakuext_getTextURLs"
:params [text]
:on-success #(rf/dispatch [:link-preview/unfurl-parsed-urls %])
:on-error (fn [error]
(log/error "Failed to parse text and extract URLs"
{:error error
:event :link-preview/unfurl-urls}))}]}))
(defn- urls->previews
[preview-cache urls]
(->> urls
(map #(get preview-cache % {:url % :loading? true}))
(remove :failed?)))
(def new-request-id (comp str random-uuid))
(rf/defn unfurl-parsed-urls
{:events [:link-preview/unfurl-parsed-urls]}
[{:keys [db]} urls]
(let [cleared (set (get-in db [:chat/link-previews :cleared]))]
(when (or (empty? urls)
(not= (set urls) cleared))
(let [cache (get-in db [:chat/link-previews :cache])
previews (urls->previews cache urls)
new-urls (->> previews
(filter :loading?)
(map :url))
;; `request-id` is a must because we need to process only the last
;; unfurling event, as well as avoid needlessly updating the app db
;; if the user changes the URLs in the input text when there are
;; in-flight RPC requests.
request-id (new-request-id)]
(merge {:db (-> db
(assoc-in [:chat/link-previews :unfurled] previews)
(assoc-in [:chat/link-previews :request-id] request-id)
(update :chat/link-previews dissoc :cleared))}
(when (seq new-urls)
(log/debug "Unfurling URLs" {:urls new-urls :request-id request-id})
{:json-rpc/call
[{:method "wakuext_unfurlURLs"
:params [new-urls]
:on-success #(rf/dispatch [:link-preview/unfurl-parsed-urls-success request-id %])
:on-error #(rf/dispatch [:link-preview/unfurl-parsed-urls-error request-id
%])}]}))))))
(defn- failed-previews
[curr-previews new-previews]
(let [curr-urls (set (->> curr-previews
(filter :loading?)
(map :url)))
new-urls (set (map :url new-previews))]
(map (fn [url]
{:url url :failed? true})
(set/difference curr-urls new-urls))))
(defn- reconcile-unfurled
[curr-previews indexed-new-previews]
(reduce (fn [acc preview]
(if (:loading? preview)
(if-let [loaded-preview (get indexed-new-previews
(:url preview))]
(conj acc loaded-preview)
acc)
(conj acc preview)))
[]
curr-previews))
(rf/defn unfurl-parsed-urls-success
{:events [:link-preview/unfurl-parsed-urls-success]}
[{:keys [db]} request-id new-previews]
(when (= request-id (get-in db [:chat/link-previews :request-id]))
(let [new-previews (map data-store.messages/<-link-preview-rpc new-previews)
curr-previews (get-in db [:chat/link-previews :unfurled])
indexed-new-previews (utils.collection/index-by :url new-previews)]
(log/debug "URLs unfurled"
{:event :link-preview/unfurl-parsed-urls-success
:previews (map #(update % :thumbnail dissoc :data-uri) new-previews)
:request-id request-id})
{:db (-> db
(update-in [:chat/link-previews :unfurled] reconcile-unfurled indexed-new-previews)
(update-in [:chat/link-previews :cache]
merge
indexed-new-previews
(utils.collection/index-by :url
(failed-previews curr-previews new-previews))))})))
(rf/defn unfurl-parsed-urls-error
{:events [:link-preview/unfurl-parsed-urls-error]}
[{:keys [db]} request-id error]
(log/error "Failed to unfurl URLs"
{:request-id request-id
:error error
:event :link-preview/unfurl-parsed-urls}))
(rf/defn reset-unfurled
"Reset preview state, but keep the cache. Use this event after a message is
sent."
{:events [:link-preview/reset-unfurled]}
[{:keys [db]}]
{:db (update db :chat/link-previews dissoc :unfurled :request-id :cleared)})
(rf/defn reset-all
"Reset all preview state. It is especially important to delete any cached
URLs, as failing to do so results in its unbounded growth."
{:events [:link-preview/reset-all]}
[{:keys [db]}]
{:db (dissoc db :chat/link-previews)})
(rf/defn clear-link-previews
"Mark current unfurled URLs as `cleared`, meaning the user won't see previews
until they insert/remove non-cleared URL(s)."
{:events [:link-preview/clear]}
[{:keys [db]}]
(let [unfurled-urls (set (map :url (get-in db [:chat/link-previews :unfurled])))]
{:db (-> db
(update :chat/link-previews dissoc :unfurled :request-id)
(assoc-in [:chat/link-previews :cleared] unfurled-urls))}))
@@ -1,204 +0,0 @@
(ns status-im2.contexts.chat.composer.link-preview.events-test
(:require [status-im2.contexts.chat.composer.link-preview.events :as events]
[cljs.test :refer [is deftest testing]]))
(def url-github "https://github.com")
(def url-gitlab "https://gitlab.com")
(def preview-github {:url url-github :thumbnail nil})
(def preview-gitlab {:url url-gitlab :thumbnail nil})
(def request-id "abc123")
(defn remove-rpc-callbacks
[effects]
(if (get-in effects [:json-rpc/call 0])
(update-in effects [:json-rpc/call 0] dissoc :on-success :on-error)
effects))
(deftest unfurl-urls
(testing "clear up state when text is empty"
(let [cofx {:db {:chat/link-previews {:unfurled {}
:cache {}
:request-id "123"}}}]
(is (= {:db {:chat/link-previews {:cache {}}}}
(events/unfurl-urls cofx " ")))))
(testing "fetches parsed URLs"
(let [cofx {:db {:chat/link-previews {:unfurled {}
:cache {}
:request-id "123"}}}]
(is (= {:json-rpc/call [{:method "wakuext_getTextURLs"
:params [url-github]}]}
(remove-rpc-callbacks
(events/unfurl-urls cofx url-github)))))))
(deftest unfurl-parsed-urls-test
(with-redefs [events/new-request-id (constantly request-id)]
(testing "empty state with 2 stale URLs"
(let [cofx {:db {:chat/link-previews
{:cache {}
:unfurled []}}}]
(is (= {:db {:chat/link-previews
{:request-id request-id
:cache {}
:unfurled [{:url url-github :loading? true}
{:url url-gitlab :loading? true}]}}
:json-rpc/call [{:method "wakuext_unfurlURLs"
:params [[url-github url-gitlab]]}]}
(remove-rpc-callbacks
(events/unfurl-parsed-urls cofx [url-github url-gitlab]))))))
(testing "nothing unfurled, 1 cached URL and the other stale"
(let [cache {url-github preview-github}
cofx {:db {:chat/link-previews
{:cache cache
:unfurled []}}}]
(is (= {:db {:chat/link-previews
{:request-id request-id
:cache cache
:unfurled [preview-github
{:url url-gitlab :loading? true}]}}
:json-rpc/call [{:method "wakuext_unfurlURLs"
:params [[url-gitlab]]}]}
(remove-rpc-callbacks
(events/unfurl-parsed-urls cofx [url-github url-gitlab]))))))
(testing "does nothing when the URLs are identical to the last cleared ones"
(let [cofx {:db {:chat/link-previews
{:cache {url-github preview-github}
:cleared #{url-github url-gitlab}
:unfurled [preview-github preview-gitlab]}}}]
(is (nil? (events/unfurl-parsed-urls cofx [url-github url-gitlab])))))
(testing "app db has previews, but the new parsed text has no valid URLs"
(let [cache {url-github preview-github}
cofx {:db {:chat/link-previews
{:cache cache
:unfurled [preview-github]}}}]
(is (= {:db {:chat/link-previews
{:request-id request-id
:cache cache
:unfurled []}}}
(remove-rpc-callbacks
(events/unfurl-parsed-urls cofx []))))))
(testing "2 unfurled, 2 cached URLs"
(let [cache {url-github preview-github url-gitlab preview-gitlab}
cofx {:db {:chat/link-previews
{:cache cache
:unfurled [preview-github preview-gitlab]}}}]
(is (= {:db {:chat/link-previews
{:request-id request-id
:cache cache
:unfurled [preview-github preview-gitlab]}}}
(remove-rpc-callbacks
(events/unfurl-parsed-urls cofx [url-github url-gitlab]))))))
(testing "1 unfurled, 1 successful in the cache, 1 failed in the cache"
(let [cache {url-github preview-github
url-gitlab (assoc preview-gitlab :failed? true)}
cofx {:db {:chat/link-previews
{:cache cache
:unfurled [preview-github]}}}]
(is (= {:db {:chat/link-previews
{:request-id request-id
:cache cache
:unfurled [preview-github]}}}
(events/unfurl-parsed-urls cofx [url-github url-gitlab])))))
(testing "nothing unfurled, 1 failed in the cache"
(let [cache {url-gitlab (assoc preview-gitlab :failed? true)}
cofx {:db {:chat/link-previews
{:cache cache
:unfurled []}}}]
(is (= {:db {:chat/link-previews
{:request-id request-id
:cache cache
:unfurled [{:url url-github :loading? true}]}}
:json-rpc/call [{:method "wakuext_unfurlURLs"
:params [[url-github]]}]}
(remove-rpc-callbacks
(events/unfurl-parsed-urls cofx [url-github url-gitlab]))))))
(testing "empties unfurled collection when there are no valid previews"
(let [unknown-url "https://github.abcdef"
cache {unknown-url {:url unknown-url :failed? true}
url-github preview-github}
cofx {:db {:chat/link-previews
{:cache cache
:unfurled [preview-github]}}}]
(is (= {:db {:chat/link-previews
{:request-id request-id
:cache cache
:unfurled []}}}
(remove-rpc-callbacks
(events/unfurl-parsed-urls cofx [unknown-url]))))))))
(deftest unfurl-parsed-urls-success-test
(testing "does nothing if the success request-id is different than the current one"
(let [cofx {:db {:chat/link-previews
{:request-id request-id
:unfurled []
:cache {}}}}]
(is (nil? (events/unfurl-parsed-urls-success cofx "banana" [preview-github])))))
(testing "reconciles new previews with existing ones"
(let [cofx {:db {:chat/link-previews
{:request-id request-id
:unfurled [preview-github
{:url url-gitlab :loading? true}]
:cache {url-github preview-github}}}}
{db :db} (events/unfurl-parsed-urls-success cofx
request-id
[preview-gitlab])]
(is (= {:chat/link-previews
{:request-id request-id
:unfurled [preview-github preview-gitlab]
:cache {url-github preview-github
url-gitlab preview-gitlab}}}
db))))
(testing "identify and write failed preview in the cache"
(let [preview-youtube {:url "https://youtube.com" :thumbnail nil}
cofx {:db {:chat/link-previews
{:request-id request-id
:unfurled [{:url url-github :loading? true}
preview-youtube
{:url url-gitlab :loading? true}]
:cache {(:url preview-youtube) preview-youtube}}}}
{db :db} (events/unfurl-parsed-urls-success cofx
request-id
[preview-github
preview-youtube])]
(is (= {:chat/link-previews
{:request-id request-id
:unfurled [preview-github preview-youtube]
:cache {(:url preview-youtube) preview-youtube
url-github preview-github
url-gitlab {:url "https://gitlab.com" :failed? true}}}}
db)))))
(deftest clear-link-previews-test
(let [cache {url-github preview-github}
effects (events/clear-link-previews
{:db {:chat/link-previews {:unfurled [preview-github]
:request-id request-id
:cache cache}}})]
(is (= {:db {:chat/link-previews {:cache cache
:cleared #{url-github}}}}
effects))))
(deftest reset-unfurled-test
(let [cache {url-github preview-github}]
(is (= {:db {:chat/link-previews {:cache cache}}}
(events/reset-unfurled
{:db {:chat/link-previews {:unfurled [preview-github]
:request-id request-id
:cleared #{url-github}
:cache cache}}})))))
(deftest reset-all-test
(is (= {:db {:non-related-key :some-value}}
(events/reset-all
{:db {:non-related-key :some-value
:chat/link-previews {:request-id request-id
:any-other-key "farewell"}}}))))
@@ -1,14 +0,0 @@
(ns status-im2.contexts.chat.composer.link-preview.style)
(def padding-horizontal 20)
(def preview-list-padding-top 12)
(def preview-list-padding-bottom 8)
(def preview-height 56)
(def preview-list
{:padding-top preview-list-padding-top
:padding-bottom preview-list-padding-bottom
:margin-horizontal (- padding-horizontal)
;; Keep a high index, otherwise the parent gesture detector used by the
;; composer grabs the initiating gesture event.
:z-index 9999})
@@ -1,28 +0,0 @@
(ns status-im2.contexts.chat.composer.link-preview.view
(:require
[quo2.core :as quo]
[react-native.core :as rn]
[status-im2.contexts.chat.composer.link-preview.events]
[status-im2.contexts.chat.composer.link-preview.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn message-draft-link-previews
[]
(let [previews (rf/sub [:chats/link-previews-unfurled])]
[quo/url-preview-list
{:key-fn :url
:preview-width (- (:width (rn/get-window))
(* 2 style/padding-horizontal))
:container-style (when (seq previews) style/preview-list)
:container-style-item {:height style/preview-height}
:horizontal-spacing style/padding-horizontal
:loading-message (i18n/label :t/link-preview-loading-message)
:on-clear #(rf/dispatch [:link-preview/clear])
:data (map (fn [{:keys [title thumbnail hostname loading? url]}]
{:title title
:body hostname
:loading? loading?
:thumbnail (:data-uri thumbnail)
:url url})
previews)}]))
@@ -30,7 +30,7 @@
user])
(defn- f-view
[suggestions-atom props state animations max-height cursor-pos images link-previews? reply edit]
[suggestions-atom props state animations max-height cursor-pos images reply edit]
(let [suggestions (rf/sub [:chat/mention-suggestions])
opacity (reanimated/use-shared-value (if (seq suggestions) 1 0))
size (count suggestions)
@@ -39,7 +39,6 @@
:curr-height (reanimated/get-shared-value (:height animations))
:window-height (:height (rn/get-window))
:images images
:link-previews? link-previews?
:reply reply
:edit edit}
mentions-pos (utils/calc-suggestions-position cursor-pos max-height size state data)]
@@ -62,7 +61,6 @@
:accessibility-label :mentions-list}]]))
(defn view
[props state animations max-height cursor-pos images link-previews? reply edit]
[props state animations max-height cursor-pos images reply edit]
(let [suggestions-atom (reagent/atom {})]
[:f> f-view suggestions-atom props state animations max-height cursor-pos images link-previews? reply
edit]))
[:f> f-view suggestions-atom props state animations max-height cursor-pos images reply edit]))
@@ -26,17 +26,16 @@
[:f> f-blur-view layout-height focused?])
(defn- f-shell-button
[{:keys [maximized?]} {:keys [height]} {:keys [images link-previews? reply edit]}]
[{:keys [maximized?]} {:keys [height]} {:keys [images reply edit]}]
(let [insets (safe-area/get-insets)
extra-height (utils/calc-extra-content-height images link-previews? reply edit)
extra-height (utils/calc-extra-content-height images reply edit)
translate-y (reanimated/use-shared-value
(utils/calc-shell-neg-y insets maximized? extra-height))]
(rn/use-effect
(fn []
(let [extra-height (utils/calc-extra-content-height images link-previews? reply edit)]
(reanimated/animate translate-y
(utils/calc-shell-neg-y insets maximized? extra-height))))
[@maximized? images link-previews? reply edit])
(rn/use-effect (fn []
(let [extra-height (utils/calc-extra-content-height images reply edit)]
(reanimated/animate translate-y
(utils/calc-shell-neg-y insets maximized? extra-height))))
[@maximized? images reply edit])
[reanimated/view
{:style (reanimated/apply-animations-to-style
{:bottom height ; we use height of the input directly as bottom position
@@ -66,31 +66,30 @@
(if platform/ios? lines (dec lines))))
(defn calc-extra-content-height
[images? link-previews? reply? edit?]
[images? reply? edit?]
(let [height (if images? constants/images-container-height 0)
height (if link-previews? (+ height constants/links-container-height) height)
height (if reply? (+ height constants/reply-container-height) height)
height (if edit? (+ height constants/edit-container-height) height)]
height))
(defn calc-max-height
[{:keys [images link-previews? reply edit]} window-height kb-height insets]
[{:keys [images reply edit]} window-height kb-height insets]
(let [margin-top (if platform/ios? (:top insets) (+ 10 (:top insets)))
max-height (- window-height
margin-top
kb-height
constants/bar-container-height
constants/actions-container-height)
max-height (- max-height (calc-extra-content-height images link-previews? reply edit))]
max-height (- max-height (calc-extra-content-height images reply edit))]
max-height))
(defn empty-input?
[text images link-previews? reply? audio?]
(and (empty? text)
(empty? images)
(not link-previews?)
(not reply?)
(not audio?)))
[text images reply? audio?]
(and (empty? text) (empty? images) (not reply?) (not audio?)))
(defn android-elevation?
[lines images reply? edit?]
(or (> lines 1) (seq images) reply? edit?))
(defn cancel-edit-message
[{:keys [text-value]}]
@@ -119,6 +118,7 @@
neg-y (if @maximized? -50 0)]
(- (+ constants/bar-container-height
constants/actions-container-height
constants/bottom-padding
(:bottom insets)
padding
extra-height
@@ -127,10 +127,10 @@
(defn calc-suggestions-position
[cursor-pos max-height size
{:keys [maximized?]}
{:keys [insets curr-height window-height keyboard-height images link-previews? reply edit]}]
{:keys [insets curr-height window-height keyboard-height images reply edit]}]
(let [base (+ constants/composer-default-height (:bottom insets) 8)
base (+ base (- curr-height constants/input-height))
base (+ base (calc-extra-content-height images link-previews? reply edit))
base (+ base (calc-extra-content-height images reply edit))
view-height (- window-height keyboard-height (:top insets))
container-height (bounded-val
(* (/ constants/mentions-max-height 4) size)
@@ -182,7 +182,6 @@
[]
(let [chat-input (rf/sub [:chats/current-chat-input])]
{:images (seq (rf/sub [:chats/sending-image]))
:link-previews? (rf/sub [:chats/link-previews?])
:audio (rf/sub [:chats/sending-audio])
:reply (rf/sub [:chats/reply-message])
:edit (rf/sub [:chats/edit-message])
@@ -191,7 +190,7 @@
:input-content-height (:input-content-height chat-input)}))
(defn init-animations
[{:keys [input-text images link-previews? reply audio]}
[{:keys [input-text images reply audio]}
lines content-height max-height opacity background-y]
(let [initial-height (if (> lines 1)
constants/multiline-minimized-height
@@ -201,7 +200,6 @@
(if (empty-input?
input-text
images
link-previews?
reply
audio)
0.7
@@ -4,11 +4,11 @@
[react-native.core :as rn]
[react-native.gesture :as gesture]
[react-native.hooks :as hooks]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]
[utils.i18n :as i18n]
[status-im2.contexts.chat.composer.style :as style]
[status-im2.contexts.chat.composer.link-preview.view :as link-preview]
[status-im2.contexts.chat.composer.images.view :as images]
[status-im2.contexts.chat.composer.reply.view :as reply]
[status-im2.contexts.chat.composer.edit.view :as edit]
@@ -63,10 +63,7 @@
(effects/edit-mentions props state subs)
[:<>
[sub-view/shell-button state animations subs]
[mentions/view props state animations max-height cursor-pos
(:images subs)
(:link-previews? subs)
(:reply subs)
[mentions/view props state animations max-height cursor-pos (:images subs) (:reply subs)
(:edit subs)]
[gesture/gesture-detector
{:gesture (drag-gesture/drag-gesture props state animations dimensions keyboard-shown)}
@@ -108,9 +105,12 @@
:max-length constants/max-text-size
:accessibility-label :chat-message-input}]]
[gradients/view props state animations show-bottom-gradient?]]
[link-preview/message-draft-link-previews]
[images/images-list]
[actions/view props state animations window-height insets subs]]]]))
[actions/view props state animations window-height insets subs]
(when (and platform/android? (not @(:focused? state)))
[rn/view {:style {:height constants/bottom-padding
:background-color :transparent}}])
]]]))
(defn composer
[insets]
-2
View File
@@ -6,7 +6,6 @@
[status-im2.contexts.chat.messages.list.state :as chat.state]
[status-im2.contexts.chat.messages.delete-message-for-me.events :as delete-for-me]
[status-im2.contexts.chat.messages.delete-message.events :as delete-message]
[status-im2.contexts.chat.composer.link-preview.events :as link-preview]
[status-im2.navigation.events :as navigation]
[status-im2.constants :as constants]
[status-im.chat.models.loading :as loading]
@@ -178,7 +177,6 @@
(when (and community-id (not navigate-to-shell?))
{:dispatch-n [[:shell/add-switcher-card
:community-overview community-id]]})))
(link-preview/reset-all)
(delete-for-me/sync-all)
(delete-message/send-all)
(offload-messages chat-id))))
@@ -34,9 +34,7 @@
[:<>
[rn/view {:style {:margin-bottom 10}} [text/text-content first-image context]]
[rn/view
{:style (style/album-container portrait?)
:accessible true
:accessibility-label :image-container}
{:style (style/album-container portrait?)}
(map-indexed
(fn [index item]
(let [images-size-key (if (< images-count constants/max-album-photos)
@@ -18,19 +18,18 @@
(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]])
[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)}]]]))
[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,27 +0,0 @@
(ns status-im2.contexts.chat.messages.content.link-preview.view
(:require
[quo2.core :as quo]
[utils.re-frame :as rf]))
(defn nearly-square?
[{:keys [width height]}]
(if (or (zero? width) (zero? height))
false
(let [ratio (/ (max width height)
(min width height))]
(< (Math/abs (dec ratio)) 0.1))))
(defn view
[{:keys [chat-id message-id]}]
(let [previews (rf/sub [:chats/message-link-previews chat-id message-id])]
(when (seq previews)
[:<>
(for [{:keys [url title description thumbnail hostname]} previews]
^{:key url}
[quo/link-preview
{:title title
:description description
:link hostname
:thumbnail (:url thumbnail)
:thumbnail-size (when (nearly-square? thumbnail) :large)
:container-style {:margin-top 8}}])])))
@@ -1,16 +0,0 @@
(ns status-im2.contexts.chat.messages.content.link-preview.view-test
(:require [status-im2.contexts.chat.messages.content.link-preview.view :as view]
[cljs.test :refer [is deftest are]]))
(deftest nearly-square?-test
(are [pred width height] (is (pred (view/nearly-square? {:width width :height height})))
false? 0 0
true? 1 1
false? 100 89
false? 100 90
true? 100 91
true? 100 92
true? 100 101
true? 100 109
false? 100 110
false? 100 111))
@@ -3,10 +3,11 @@
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[react-native.core :as rn]
[status-im2.contexts.chat.messages.content.link-preview.view :as link-preview]
[status-im2.contexts.chat.messages.content.text.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
[status-im2.contexts.chat.messages.link-preview.view :as link-preview]
[utils.re-frame :as rf]
[utils.i18n :as i18n]))
(defn render-inline
[units {:keys [type literal destination]} chat-id]
@@ -133,7 +134,7 @@
add-edited-tag))])
(defn text-content
[message-data _]
[message-data context]
[rn/view
[render-parsed-text message-data]
[link-preview/view message-data]])
[link-preview/link-preview message-data context]])
@@ -1,7 +1,19 @@
(ns status-im2.contexts.onboarding.create-password.style
(:require [quo2.foundations.colors :as colors]))
(def flex-fill {:flex 1})
(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 heading {:margin-bottom 20})
(def heading-subtitle {:color colors/white})
@@ -1,6 +1,5 @@
(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]
@@ -67,8 +66,7 @@
:shown true}
:placeholder (i18n/label :t/password-creation-placeholder-1)
:on-change-text on-change-password
:on-focus on-input-focus
:auto-focus true}]
:on-focus on-input-focus}]
[rn/view {:style style/space-between-inputs}]
[password-with-hint
{:hint {:text hint-2-text
@@ -122,7 +120,7 @@
(count)))
(defn password-form
[]
[{:keys [scroll-to-end-fn]}]
(let [password (reagent/atom "")
repeat-password (reagent/atom "")
accepts-disclaimer? (reagent/atom false)
@@ -136,7 +134,7 @@
:as validations} (password-validations @password)
password-strength (calc-password-strength validations)
empty-password? (empty? @password)
same-passwords? (and (not empty-password?) (= @password @repeat-password))
same-passwords? (= @password @repeat-password)
meet-requirements? (and (not empty-password?)
(utils.string/at-least-n-chars? @password 10)
same-passwords?
@@ -149,7 +147,9 @@
:passwords-match? same-passwords?
:empty-password? empty-password?
:show-password-validation? @show-password-validation?
:on-input-focus #(reset! focused-input :password)
:on-input-focus (fn []
(scroll-to-end-fn)
(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}
(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?))
[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)
[help
{:validations validations
:password-strength password-strength}])
@@ -196,45 +196,25 @@
(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
[]
[:<>
[background/view true]
[:f> f-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}]]]))))
+14 -11
View File
@@ -1,16 +1,17 @@
(ns status-im2.contexts.onboarding.events
(:require
[clojure.string :as string]
[native-module.core :as native-module]
[utils.re-frame :as rf]
[taoensso.timbre :as log]
[re-frame.core :as re-frame]
[status-im.ethereum.core :as ethereum]
[status-im.utils.types :as types]
[status-im2.config :as config]
[status-im2.constants :as constants]
[taoensso.timbre :as log]
[clojure.string :as string]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[utils.security.core :as security]))
[utils.security.core :as security]
[native-module.core :as native-module]
[status-im.ethereum.core :as ethereum]
[status-im2.constants :as constants]
[status-im2.contexts.onboarding.profiles.view :as profiles.view]))
(re-frame/reg-fx
:multiaccount/create-account-and-login
@@ -139,10 +140,12 @@
{:title (i18n/label :t/multiaccount-exists-title)
:content (i18n/label :t/multiaccount-exists-content)
:confirm-button-text (i18n/label :t/unlock)
:on-accept (fn []
(re-frame/dispatch [:pop-to-root :profiles])
(re-frame/dispatch
[:multiaccounts.login.ui/multiaccount-selected key-uid]))
: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-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,6 +12,8 @@
[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]))
@@ -41,14 +43,14 @@
:context context
:button-text (i18n/label :t/remove)
:close-button-text (i18n/label :t/cancel)
: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)))))}])
: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)))))}])
(defn show-confirmation
[key-uid context]
@@ -70,9 +72,7 @@
{:content (fn [] [profile-options key-uid context])}]))
(defn profile-card
[{:keys [name key-uid customization-color keycard-pairing last-index set-hide-profiles]
:as multiaccount}
index]
[{:keys [name key-uid customization-color keycard-pairing last-index] :as multiaccount} index]
(let [last-item? (= last-index index)
profile-picture (:uri (first (:images multiaccount)))]
[quo/profile-card
@@ -89,18 +89,15 @@
{:name name
:color customization-color
:profile-picture profile-picture})
:on-card-press (fn []
(rf/dispatch
[:multiaccounts.login.ui/multiaccount-selected key-uid])
(when-not keycard-pairing (set-hide-profiles)))}]))
:on-card-press #(do
(rf/dispatch
[:multiaccounts.login.ui/multiaccount-selected key-uid])
(when-not keycard-pairing (reset! show-profiles? false)))}]))
(defn profiles-section
[{:keys [set-hide-profiles]}]
[]
(let [multiaccounts (vals (rf/sub [:multiaccounts/multiaccounts]))
profiles-data (map #(assoc %
:last-index (- (count multiaccounts) 1)
:set-hide-profiles set-hide-profiles)
multiaccounts)]
multiaccounts (map #(assoc % :last-index (- (count multiaccounts) 1)) multiaccounts)]
[rn/view
{:style style/profiles-container}
[rn/view
@@ -119,7 +116,7 @@
:override-theme :dark}
:main-icons/add]]
[rn/flat-list
{:data (sort-by :timestamp > profiles-data)
{:data (sort-by :timestamp > multiaccounts)
:key-fn :key-uid
:content-container-style {:padding-bottom 20}
:render-fn profile-card}]]))
@@ -172,8 +169,8 @@
(i18n/label :t/forgot-your-password-info-create-new-password-description)]]]]])
(defn login-section
[{:keys [set-show-profiles]}]
(let [{:keys [name customization-color error processing password]
[]
(let [{:keys [name customization-color error processing]
:as multiaccount} (rf/sub [:multiaccounts/login])
sign-in-enabled? (rf/sub [:sign-in-enabled?])
profile-picture (:uri (first (:images multiaccount)))]
@@ -183,8 +180,7 @@
{:size 32
:type :blur-bg
:icon true
:on-press set-show-profiles
:disabled processing
:on-press #(reset! show-profiles? true)
:override-theme :dark
:width 32
:accessibility-label :show-profiles
@@ -201,18 +197,18 @@
[quo/input
{:type :password
:blur? true
:disabled? processing
:override-theme :dark
:placeholder (i18n/label :t/type-your-password)
:auto-focus true
:error? (seq error)
:error? (when (not-empty error) 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 (seq error)
(when (not-empty error)
[quo/info-message
{:type :error
:size :default
@@ -223,7 +219,6 @@
{:size 40
:type :ghost
:before :i/info
:disabled processing
:accessibility-label :forget-password-button
:override-theme :dark
:style style/forget-password-button
@@ -244,12 +239,10 @@
(defn views
[]
(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}])])))
(reset! show-profiles? false)
(fn []
[:<>
[background/view true]
(if @show-profiles?
[profiles-section]
[login-section])]))
@@ -1,37 +0,0 @@
(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}]])
@@ -21,15 +21,6 @@
{:label "Container width"
:key :width
:type :text}
{:label "With logo?"
:key :with-logo?
:type :boolean}
{:label "With description?"
:key :with-description?
:type :boolean}
{:label "With thumbnail?"
:key :with-thumbnail?
:type :boolean}
{:label "Disabled text"
:key :disabled-text
:type :text}
@@ -54,17 +45,14 @@
(defn cool-preview
[]
(let [state (reagent/atom
{:title "Rarible - NFT Marketplace"
:description "Turn your products or services into publicly tradeable items"
:link "rarible.com"
:thumbnail :collectible
:width "295"
:with-logo? true
:with-thumbnail? true
:with-description? true
:enabled? true
:thumbnail-size :normal
:disabled-text "Enable Preview"})]
{:title "Rarible - NFT Marketplace"
:description "Turn your products or services into publicly tradeable items"
:link "rarible.com"
:thumbnail :collectible
:width "295"
:enabled? true
:thumbnail-size :normal
:disabled-text "Enable Preview"})]
(fn []
(let [width (utils.number/parse-int (:width @state) 295)
thumbnail (get resources/mock-images (:thumbnail @state))]
@@ -74,17 +62,14 @@
{:style {:align-items :center
:margin-top 20}}
[quo/link-preview
{:logo (when (:with-logo? @state)
(resources/get-mock-image :status-logo))
{:logo (resources/get-mock-image :status-logo)
:title (:title @state)
:description (when (:with-description? @state)
(:description @state))
:description (:description @state)
:enabled? (:enabled? @state)
:on-enable #(js/alert "Button pressed")
:disabled-text (:disabled-text @state)
:link (:link @state)
:thumbnail (when (:with-thumbnail? @state)
thumbnail)
:thumbnail thumbnail
:thumbnail-size (:thumbnail-size @state)
:container-style {:width width}}]]]))))
@@ -14,9 +14,6 @@
{:label "Body"
:key :body
:type :text}
{:label "With logo?"
:key :with-logo?
:type :boolean}
{:label "Loading?"
:key :loading?
:type :boolean}
@@ -29,7 +26,6 @@
(let [state (reagent/atom
{:title "Status - Private, Secure Communication"
:body "Status.im"
:with-logo? true
:loading? false
:loading-message "Generating preview"})]
(fn []
@@ -43,8 +39,7 @@
[quo/url-preview
{:title (:title @state)
:body (:body @state)
:logo (when (:with-logo? @state)
(resources/get-mock-image :status-logo))
:logo (resources/get-mock-image :status-logo)
:loading? (:loading? @state)
:loading-message (:loading-message @state)
:on-clear #(js/alert "Clear button pressed")}]]]])))
@@ -17,17 +17,14 @@
[]
(let [state (reagent/atom {:previews-length "3"})]
(fn []
(let [previews-length (min 6 (utils.number/parse-int (:previews-length @state)))
padding 20]
(let [previews-length (min 6 (utils.number/parse-int (:previews-length @state)))]
[rn/view {:style {:padding-bottom 150}}
[preview/customizer state descriptor]
[rn/view
{:style {:align-items :center
:margin-top 50}}
[quo/url-preview-list
{:horizontal-spacing padding
:preview-width (- (:width (rn/get-window))
(* 2 padding))
{:horizontal-spacing 20
:on-clear #(js/alert "Clear button pressed")
:key-fn :url
:data (for [index (range previews-length)
@@ -90,8 +90,7 @@
[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.keycard.keycard :as keycard]))
[status-im2.contexts.quo-preview.wallet.token-overview :as token-overview]))
(def screens-categories
{:foundations [{:name :shadows
@@ -345,10 +344,7 @@
:component network-breakdown/preview-network-breakdown}
{:name :network-amount
:options {:topBar {:visible true}}
:component network-amount/preview}]
:keycard [{:name :keycard-component
:options {:topBar {:visible true}}
:component keycard/preview-keycard}]})
:component network-amount/preview}]})
(def screens (flatten (map val screens-categories)))
@@ -43,27 +43,19 @@
:type :boolean}])
(def context-tag-descriptor
[{:label "Show avatar"
:key :show-avatar?
:type :boolean}
{:label "Label"
[{:label "Label"
:key :label
:type :text}
{:label "Channel name"
{:label "Channel Name"
:key :channel-name
:type :text}
{:label "Avatar placeholder"
:key :no-avatar-placeholder?
:type :boolean}])
:type :text}])
(defn cool-preview
[]
(let [state (reagent/atom {:label "Name"
:channel-name "Channel"
:type :group-avatar
:duration "00:32"
:show-avatar? true
:no-avatar-placeholder? false})]
(let [state (reagent/atom {:label "Name"
:channel-name "Channel"
:type :group-avatar
:duration "00:32"})]
(fn []
(let [contacts {example-pk {:public-key example-pk
:primary-name "Automatic incompatible Coati"
@@ -130,12 +122,10 @@
:show-blur-background? (:blur? @state)}
(case (:type @state)
:context-tag [quo2/context-tag
{:blur? (:blur? @state)
:size :small
:color :purple
:no-avatar-placeholder? (:no-avatar-placeholder? @state)}
(when (:show-avatar? @state)
example-photo2)
{:blur? (:blur? @state)
:size :small
:color :purple}
{:uri example-photo2}
(:label @state)
(:channel-name @state)]
-15
View File
@@ -1,15 +0,0 @@
(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
@@ -1,102 +0,0 @@
(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
@@ -1,162 +0,0 @@
(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]])))
-1
View File
@@ -14,7 +14,6 @@
[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))
+3 -3
View File
@@ -87,6 +87,7 @@
{: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)
@@ -166,7 +167,7 @@
:id comp
:options (merge (options/statusbar)
{:layout {:componentBackgroundColor :transparent
:orientation ["portrait"]}
:orientation :portrait}
:overlay {:interceptTouchOutside true}}
opts)}})))
@@ -177,8 +178,7 @@
(fn []
(show-overlay "toasts"
{:overlay {:interceptTouchOutside false}
:layout {:componentBackgroundColor :transparent
:orientation ["portrait"]}})))
:layout {:componentBackgroundColor :transparent}})))
(re-frame/reg-fx :hide-toasts (fn [] (dissmiss-overlay "toasts")))
;; bottom sheet
+4 -5
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,
@@ -27,14 +27,14 @@
{:topBar {:visible false}
:layout {:componentBackgroundColor (or background-color
(colors/theme-colors colors/white colors/neutral-100))
:orientation ["portrait"]
: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"]
:orientation :portrait
:backgroundColor colors/neutral-80-opa-80-blur})
(defn navbar
@@ -75,7 +75,7 @@
(merge
{:modalPresentationStyle :overCurrentContext
:layout {:componentBackgroundColor :transparent
:orientation ["portrait"]
:orientation :portrait
:backgroundColor :transparent}}
(if platform/android?
{:statusBar {:backgroundColor :transparent
@@ -85,7 +85,6 @@
(def sheet-options
{:layout {:componentBackgroundColor :transparent
:orientation ["portrait"]
:backgroundColor :transparent}
:modalPresentationStyle :overCurrentContext})
+1 -6
View File
@@ -31,8 +31,7 @@
[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.share.view :as share]))
[status-im2.contexts.syncing.setup-syncing.view :as settings-setup-syncing]))
(defn screens
[]
@@ -43,10 +42,6 @@
: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}
+1 -3
View File
@@ -992,9 +992,7 @@
:your-data-belongs-to-you
:your-data-belongs-to-you-description
:your-recovery-phrase
:your-recovery-phrase-description
:empty-keycard
:user-keycard})
:your-recovery-phrase-description})
;; NOTE: the rest checkpoints are based on the previous one, defined
;; like this:
-12
View File
@@ -401,15 +401,3 @@
:<- [:chat/inputs-with-mentions]
(fn [[chat-id cursor]]
(get cursor chat-id)))
(re-frame/reg-sub
:chats/link-previews-unfurled
:<- [:chat/link-previews]
(fn [previews]
(get previews :unfurled)))
(re-frame/reg-sub
:chats/link-previews?
:<- [:chats/link-previews-unfurled]
(fn [previews]
(boolean (seq previews))))
+1 -6
View File
@@ -144,12 +144,6 @@
(fn [messages [_ chat-id]]
(get messages chat-id {})))
(re-frame/reg-sub
:chats/message-link-previews
:<- [:messages/messages]
(fn [messages [_ chat-id message-id]]
(get-in messages [chat-id message-id :link-previews])))
(re-frame/reg-sub
:chats/pinned
:<- [:messages/pin-messages]
@@ -257,3 +251,4 @@
(if (= mention constants/everyone-mention-id)
(i18n/label :t/everyone-mention)
contact-name)))
+1 -5
View File
@@ -68,6 +68,7 @@
(reg-root-key-sub :supported-biometric-auth :supported-biometric-auth)
(reg-root-key-sub :connectivity/ui-status-properties :connectivity/ui-status-properties)
(reg-root-key-sub :logged-in-since :logged-in-since)
(reg-root-key-sub :link-previews-whitelist :link-previews-whitelist)
(reg-root-key-sub :app-state :app-state)
(reg-root-key-sub :home-items-show-number :home-items-show-number)
(reg-root-key-sub :waku/v2-peer-stats :peer-stats)
@@ -193,11 +194,6 @@
(reg-root-key-sub :wallet/swap-to-token-amount :wallet/swap-to-token-amount)
(reg-root-key-sub :wallet/swap-advanced-mode? :wallet/swap-advanced-mode?)
;;; Link previews
(reg-root-key-sub :link-previews-whitelist :link-previews-whitelist)
(reg-root-key-sub :chat/link-previews :chat/link-previews)
;;commands
(reg-root-key-sub :commands/select-account :commands/select-account)
-1
View File
@@ -6,7 +6,6 @@
(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.151.12",
"commit-sha1": "6fa8c113821509d744b81d90dc21c1e79c575577",
"src-sha256": "02mmhy4z6gd9xlqjhw4i4j48gd85pijyyga48if9dd3zasqkp3y1"
"version": "v0.151.5",
"commit-sha1": "31d02f5197684317ad9f14874b0980d5ca716be6",
"src-sha256": "0ih77pq3lbk6qadv018pp7gzd6vhgzcl34vm1vg2mdmv2f15zvlw"
}
+26 -26
View File
@@ -263,28 +263,28 @@ def pytest_runtest_makereport(item, call):
is_group = "xdist_group" in item.keywords._markers or "xdist_group" in item.parent.keywords._markers
error_intro, error = 'Test setup failed:', ''
final_error = '%s %s' % (error_intro, error)
# if (hasattr(report, 'wasxfail') and not case_ids_set) or (hasattr(report, 'wasxfail') and (str([mark.args[0] for mark in item.iter_markers(name='testrail_id')][0]) in str(case_ids_set))):
# if '[NOTRUN]' in report.wasxfail:
# test_suite_data.set_current_test(item.name, testrail_case_id=get_testrail_case_id(item))
# test_suite_data.current_test.create_new_testrun()
# if is_group:
# test_suite_data.current_test.group_name = item.instance.__class__.__name__
# error_intro, error = 'Test is not run, e2e blocker ', report.wasxfail
# final_error = "%s [[%s]]" % (error_intro, error)
# else:
# if is_group:
# test_suite_data.current_test.group_name = item.instance.__class__.__name__
# error = catch_error()
# final_error = '%s %s [[%s]]' % (error_intro, error, report.wasxfail)
# else:
if is_group and report.failed:
test_suite_data.current_test.group_name = item.instance.__class__.__name__
error = catch_error()
final_error = '%s %s' % (error_intro, error)
if is_sauce_env:
update_sauce_jobs(test_suite_data.current_test.group_name,
test_suite_data.current_test.testruns[-1].jobs,
report.passed)
if (hasattr(report, 'wasxfail') and not case_ids_set) or (hasattr(report, 'wasxfail') and (str([mark.args[0] for mark in item.iter_markers(name='testrail_id')][0]) in str(case_ids_set))):
if '[NOTRUN]' in report.wasxfail:
test_suite_data.set_current_test(item.name, testrail_case_id=get_testrail_case_id(item))
test_suite_data.current_test.create_new_testrun()
if is_group:
test_suite_data.current_test.group_name = item.instance.__class__.__name__
error_intro, error = 'Test is not run, e2e blocker ', report.wasxfail
final_error = "%s [[%s]]" % (error_intro, error)
else:
if is_group:
test_suite_data.current_test.group_name = item.instance.__class__.__name__
error = catch_error()
final_error = '%s %s [[%s]]' % (error_intro, error, report.wasxfail)
else:
if is_group and report.failed:
test_suite_data.current_test.group_name = item.instance.__class__.__name__
error = catch_error()
final_error = '%s %s' % (error_intro, error)
if is_sauce_env:
update_sauce_jobs(test_suite_data.current_test.group_name,
test_suite_data.current_test.testruns[-1].jobs,
report.passed)
if error:
test_suite_data.current_test.testruns[-1].error = final_error
from support.github_report import GithubHtmlReport
@@ -295,10 +295,10 @@ def pytest_runtest_makereport(item, call):
error = catch_error()
if report.failed:
current_test.testruns[-1].error = error
# if (hasattr(report, 'wasxfail') and not case_ids_set) or (hasattr(report, 'wasxfail') and (str([mark.args[0] for mark in item.iter_markers(name='testrail_id')][0]) in str(case_ids_set))):
# current_test.testruns[-1].xfail = report.wasxfail
# if error:
# current_test.testruns[-1].error = '%s [[%s]]' % (error, report.wasxfail)
if (hasattr(report, 'wasxfail') and not case_ids_set) or (hasattr(report, 'wasxfail') and (str([mark.args[0] for mark in item.iter_markers(name='testrail_id')][0]) in str(case_ids_set))):
current_test.testruns[-1].xfail = report.wasxfail
if error:
current_test.testruns[-1].error = '%s [[%s]]' % (error, report.wasxfail)
if is_sauce_env:
update_sauce_jobs(current_test.name, current_test.testruns[-1].jobs, report.passed)
if item.config.getoption('docker'):

Some files were not shown because too many files have changed in this diff Show More