[#17023] Share qr code variants (#17736)

* Align docstring

* Create share-qr-code component

* Remove empty style file

* Implement common.share-qr-code including call to media server

* Add share-qr-code preview screen

* Use share-qr-code component in shell's share screen

* Add tests and some fixes
This commit is contained in:
Ulises Manuel 2023-10-31 12:35:28 -06:00 committed by GitHub
parent ca822ff51d
commit 4a874ce48d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 709 additions and 198 deletions

View File

@ -64,11 +64,11 @@
(defn- view-internal (defn- view-internal
"[preview-list opts items] "[preview-list opts items]
opts opts
{:type :user/:communities/:accounts/:tokens/:collectibles/:dapps/:network {:type :user/:communities/:accounts/:tokens/:collectibles/:dapps/:network
:size :size-32 | :size-24 | :size-20 | :size-16 | :size-14 :size :size-32 | :size-24 | :size-20 | :size-16 | :size-14
:number number of items in the list (optional) :number number of items in the list (optional)
:blur? overflow-label blur?} :blur? overflow-label blur?}
items preview list items (only 4 items is required for preview) items preview list items (only 4 items is required for preview)
" "
[{:keys [type size number blur?]} items] [{:keys [type size number blur?]} items]
(let [size-key (if (contains? properties/sizes size) size :size-24) (let [size-key (if (contains? properties/sizes size) size :size-24)

View File

@ -1,34 +1,119 @@
(ns quo.components.share.share-qr-code.component-spec (ns quo.components.share.share-qr-code.component-spec
(:require (:require [quo.components.share.share-qr-code.view :as share-qr-code]
[quo.components.share.share-qr-code.view :as share-qr-code] [test-helpers.component :as h]))
[test-helpers.component :as h]))
(defn render-share-qr-code
[{share-qr-type :type :as props}]
(let [component-rendered (h/render [share-qr-code/view {:type share-qr-type}])
rerender-fn (h/get-rerender-fn component-rendered)
share-qr-code (h/get-by-label-text :share-qr-code)]
;; Fires on-layout since it's needed to render the content
(h/fire-event :layout share-qr-code #js {:nativeEvent #js {:layout #js {:width 500}}})
(rerender-fn [share-qr-code/view props])))
(h/describe "Share QR Code component" (h/describe "Share QR Code component"
(h/test "renders share qr code component" (h/describe "Renders share-qr-code component in all types"
(h/render [share-qr-code/view (let [qr-label "Text shown below QR"]
{:link-title " A test title"}]) (h/test "Profile"
(-> (js/expect (h/get-by-text "A test title")) (render-share-qr-code {:type :profile
(.toBeTruthy))) :qr-data qr-label})
(h/is-truthy (h/get-by-text qr-label)))
(h/test "renders share qr code url" (h/test "Wallet Legacy"
(h/render [share-qr-code/view (render-share-qr-code {:type :wallet-legacy
{:qr-url " A test url"}]) :qr-data qr-label
(-> (js/expect (h/get-by-text "A test url")) :emoji "👻"})
(.toBeTruthy))) (h/is-truthy (h/get-by-text qr-label)))
(h/test "on press link event fires" (h/test "Wallet Multichain"
(let [event (h/mock-fn)] (render-share-qr-code {:type :wallet-multichain
(h/render [share-qr-code/view :qr-data qr-label
{:url-on-press event :emoji "👻"})
:qr-url " A test url"}]) (h/is-truthy (h/get-by-text qr-label)))))
(h/fire-event :press (h/get-by-text "A test url"))
(-> (js/expect event)
(.toHaveBeenCalledTimes 1))))
(h/test "on press share event fires" (h/describe "Fires all events for all types"
(let [event (h/mock-fn)] (letfn [(test-fire-events [props test-seq]
(h/render [share-qr-code/view (doseq [{:keys [test-name event-name
{:share-on-press event}]) callback-prop-key
(h/fire-event :press (h/get-by-label-text :share-profile)) accessibility-label]} test-seq
(-> (js/expect event) :let [event-fn (h/mock-fn)]]
(.toHaveBeenCalledTimes 1))))) (h/test test-name
(render-share-qr-code (assoc props callback-prop-key event-fn))
(h/fire-event event-name (h/get-by-label-text accessibility-label))
(h/was-called-times event-fn 1))))]
(h/describe "Profile"
(test-fire-events
{:type :profile}
[{:test-name "Text pressed"
:accessibility-label :share-qr-code-info-text
:event-name :press
:callback-prop-key :on-text-press}
{:test-name "Text long pressed"
:accessibility-label :share-qr-code-info-text
:event-name :long-press
:callback-prop-key :on-text-long-press}
{:test-name "Share button"
:accessibility-label :link-to-profile
:event-name :press
:callback-prop-key :on-share-press}]))
(h/describe "Wallet Legacy"
(test-fire-events
{:type :wallet-legacy :emoji "👽"}
[{:test-name "Text pressed"
:accessibility-label :share-qr-code-info-text
:event-name :press
:callback-prop-key :on-text-press}
{:test-name "Text long pressed"
:accessibility-label :share-qr-code-info-text
:event-name :long-press
:callback-prop-key :on-text-long-press}
{:test-name "Share button pressed"
:accessibility-label :link-to-profile
:event-name :press
:callback-prop-key :on-share-press}
{:test-name "Info icon pressed"
:accessibility-label :share-qr-code-info-icon
:event-name :press
:callback-prop-key :on-info-press}
{:test-name "Legacy tab pressed"
:accessibility-label :share-qr-code-legacy-tab
:event-name :press
:callback-prop-key :on-legacy-press}
{:test-name "Multichain tab pressed"
:accessibility-label :share-qr-code-multichain-tab
:event-name :press
:callback-prop-key :on-multichain-press}]))
(h/describe "Wallet Multichain"
(test-fire-events
{:type :wallet-multichain :emoji "👽"}
[{:test-name "Text pressed"
:accessibility-label :share-qr-code-info-text
:event-name :press
:callback-prop-key :on-text-press}
{:test-name "Text long pressed"
:accessibility-label :share-qr-code-info-text
:event-name :long-press
:callback-prop-key :on-text-long-press}
{:test-name "Share button pressed"
:accessibility-label :link-to-profile
:event-name :press
:callback-prop-key :on-share-press}
{:test-name "Info icon pressed"
:accessibility-label :share-qr-code-info-icon
:event-name :press
:callback-prop-key :on-info-press}
{:test-name "Legacy tab pressed"
:accessibility-label :share-qr-code-legacy-tab
:event-name :press
:callback-prop-key :on-legacy-press}
{:test-name "Multichain tab pressed"
:accessibility-label :share-qr-code-multichain-tab
:event-name :press
:callback-prop-key :on-multichain-press}
{:test-name "Settings pressed"
:accessibility-label :share-qr-code-settings
:event-name :press
:callback-prop-key :on-settings-press}])))))

View File

@ -1,45 +1,116 @@
(ns quo.components.share.share-qr-code.style (ns quo.components.share.share-qr-code.style
(:require (:require [quo.foundations.colors :as colors]))
[quo.foundations.colors :as colors]))
(def qr-code-container (def outer-container
{:padding-top 12 {:border-radius 16
:padding-horizontal 12 :width "100%"
:padding-bottom 8 :overflow :hidden})
:border-radius 16
:background-color colors/white-opa-5
:flex-direction :column
:justify-content :center
:align-items :center})
(def profile-address-column (def overlay-color colors/white-opa-5)
{:margin-horizontal :auto
:flex 4})
(def profile-address-container (def ^:private padding-horizontal 12)
(def content-container
{:z-index 1
:padding-horizontal padding-horizontal
:padding-top 12
:padding-bottom 8})
;;; Header
(def header-container
{:flex-direction :row
:margin-bottom 12})
(def header-tab-active {:background-color colors/white-opa-20})
(def header-tab-inactive {:background-color colors/white-opa-5})
(def space-between-tabs {:width 8})
(def info-icon
{:margin-left :auto
:align-self :center})
(def info-icon-color colors/white-opa-40)
;;; QR code
(defn qr-code-size
[total-width]
(- total-width (* 2 padding-horizontal)))
;;; Bottom part
(def bottom-container
{:margin-top 8
:flex-direction :row
:justify-content :space-between})
(def title {:color colors/white-opa-40})
(def share-button-size 32)
(def ^:private share-button-gap 16)
(defn share-button-container
[alignment]
{:justify-content (if (= alignment :top) :flex-start :center)
:margin-left share-button-gap})
(defn data-text
[total-width]
{:width (- total-width (* 2 padding-horizontal) share-button-size share-button-gap)})
;;; Wallet variants
(def wallet-data-and-share-container
{:margin-top 2
:flex-direction :row
:justify-content :space-between})
(def wallet-legacy-container {:flex 1})
(def wallet-multichain-container {:flex 1 :margin-top 4})
(def wallet-multichain-networks
{:flex-direction :row {:flex-direction :row
:justify-content :space-between :justify-content :space-between
:margin-top 6}) :margin-bottom 8})
(def profile-address-content-container (def wallet-multichain-data-container {:margin-top 4})
{:padding-top 2
:align-self :flex-start})
(def profile-address-content ;;; Dashed line
{:color colors/white}) (def divider-container
{:height 8
:margin-horizontal 4
:justify-content :center
:overflow :hidden})
(def profile-address-label (def ^:private padding-for-divider (+ padding-horizontal 4))
{:color colors/white-opa-40}) (def ^:private dashed-line-width 2)
(def ^:private dashed-line-space 4)
(def share-button-container (def dashed-line
{:flex 1 {:flex-direction :row
:flex-direction :column :margin-left -1})
:justify-content :center
:align-items :flex-end})
(def icon-container (def line
{:height 36 {:background-color colors/white-opa-20
:flex-direction :row :width dashed-line-width
:align-items :center :height 1})
:justify-content :space-between
:padding-bottom 12}) (def line-space
{:width dashed-line-space
:height 1})
(defn number-lines-and-spaces-to-fill
[component-width]
(let [line-and-space-width (+ dashed-line-width dashed-line-space)
width-to-fill (- component-width (* 2 padding-for-divider))
number-of-lines (* (/ width-to-fill line-and-space-width) 2)]
(inc (int number-of-lines))))
(def ^:private get-network-full-name
{"eth" :ethereum
"opt" :optimism
"arb1" :arbitrum})
(defn network-short-name-text
[network-short-name]
{:color (-> network-short-name
(get-network-full-name :unknown)
(colors/resolve-color nil))})

View File

@ -1,46 +1,249 @@
(ns quo.components.share.share-qr-code.view (ns quo.components.share.share-qr-code.view
(:require (:require [clojure.set]
[quo.components.buttons.button.view :as button] [clojure.string :as string]
[quo.components.markdown.text :as text] [oops.core :as oops]
[quo.components.share.qr-code.view :as qr-code] [quo.components.buttons.button.view :as button]
[quo.components.share.share-qr-code.style :as style] [quo.components.icon :as icon]
[quo.foundations.colors :as colors] [quo.components.list-items.preview-list.view :as preview-list]
[react-native.blur :as blur] [quo.components.markdown.text :as text]
[react-native.core :as rn])) [quo.components.share.qr-code.view :as qr-code]
[quo.components.share.share-qr-code.style :as style]
[quo.components.tabs.tab.view :as tab]
[quo.foundations.resources :as quo.resources]
[quo.theme]
[react-native.blur :as blur]
[react-native.core :as rn]
[react-native.platform :as platform]
[reagent.core :as reagent]
[utils.i18n :as i18n]))
(defn- line [] [rn/view {:style style/line}])
(defn- space [] [rn/view {:style style/line-space}])
(defn- dashed-line
[width]
(into [rn/view {:style style/dashed-line}]
(take (style/number-lines-and-spaces-to-fill width))
(cycle [[line] [space]])))
(defn- header
[{:keys [share-qr-type on-info-press on-legacy-press on-multichain-press]}]
[rn/view {:style style/header-container}
[tab/view
{:accessibility-label :share-qr-code-legacy-tab
:id :wallet-legacy-tab
:active-item-container-style style/header-tab-active
:item-container-style style/header-tab-inactive
:size 24
:active (= :wallet-legacy share-qr-type)
:on-press on-legacy-press}
(i18n/label :t/legacy)]
[rn/view {:style style/space-between-tabs}]
[tab/view
{:accessibility-label :share-qr-code-multichain-tab
:id :wallet-multichain-tab
:active-item-container-style style/header-tab-active
:item-container-style style/header-tab-inactive
:size 24
:active (= :wallet-multichain share-qr-type)
:on-press on-multichain-press}
(i18n/label :t/multichain)]
[rn/pressable
{:accessibility-label :share-qr-code-info-icon
:style style/info-icon
:on-press on-info-press
:hit-slop 6}
[icon/icon :i/info
{:size 20
:color style/info-icon-color}]]])
(defn- info-label
[share-qr-code-type]
[text/text {:size :paragraph-2 :weight :medium :style style/title}
(if (= share-qr-code-type :profile)
(i18n/label :t/link-to-profile)
(i18n/label :t/wallet-address))])
(defn- info-text
[{:keys [width on-press on-long-press ellipsize?]} qr-data-text]
[rn/pressable
{:accessibility-label :share-qr-code-info-text
:style (style/data-text width)
:on-press on-press
:on-long-press on-long-press}
[text/text
(cond-> {:size :paragraph-1
:weight :monospace}
ellipsize? (assoc :number-of-lines 1
:ellipsize-mode :middle))
qr-data-text]])
(defn- share-button
[{:keys [alignment on-press]}]
[rn/view {:style (style/share-button-container alignment)}
[button/button
{:icon-only? true
:type :grey
:background :blur
:size style/share-button-size
:accessibility-label :link-to-profile
:on-press on-press}
:i/share]])
(defn- network-colored-text
[network-short-name]
[text/text {:style (style/network-short-name-text network-short-name)}
(str network-short-name ":")])
(defn- wallet-multichain-colored-address
[full-address]
(let [[networks address] (as-> full-address $
(string/split $ ":")
[(butlast $) (last $)])
->network-hiccup-xf (map #(vector network-colored-text %))]
(as-> networks $
(into [:<>] ->network-hiccup-xf $)
(conj $ address))))
(defn- profile-bottom
[{:keys [component-width qr-data on-text-press on-text-long-press on-share-press share-qr-type]}]
[:<>
[rn/view
[info-label share-qr-type]
[info-text
{:width component-width
:ellipsize? true
:on-press on-text-press
:on-long-press on-text-long-press}
qr-data]]
[share-button
{:alignment :center
:on-press on-share-press}]])
(defn- wallet-legacy-bottom
[{:keys [share-qr-type component-width qr-data on-text-press on-text-long-press on-share-press]}]
[rn/view {:style style/wallet-legacy-container}
[info-label share-qr-type]
[rn/view {:style style/wallet-data-and-share-container}
[info-text
{:width component-width
:on-press on-text-press
:on-long-press on-text-long-press}
qr-data]
[share-button
{:alignment :top
:on-press on-share-press}]]])
(def ^:private known-networks #{:ethereum :optimism :arbitrum})
(defn- get-network-image-source
[network]
{:source (quo.resources/get-network (get known-networks network :unknown))})
(defn wallet-multichain-bottom
[{:keys [share-qr-type component-width qr-data on-text-press on-text-long-press
on-share-press networks on-settings-press]}]
[rn/view {:style style/wallet-multichain-container}
[rn/view {:style style/wallet-multichain-networks}
[preview-list/view {:type :network :size :size-32}
(map get-network-image-source networks)]
[button/button
{:icon-only? true
:type :grey
:background :blur
:size 32
:accessibility-label :share-qr-code-settings
:on-press on-settings-press}
:i/advanced]]
[rn/view {:style style/divider-container}
[dashed-line component-width]]
[rn/view {:style style/wallet-multichain-data-container}
[info-label share-qr-type]
[rn/view {:style style/wallet-data-and-share-container}
[info-text
{:width component-width
:on-press on-text-press
:on-long-press on-text-long-press}
[wallet-multichain-colored-address qr-data]]
[share-button
{:alignment :top
:on-press on-share-press}]]]])
(defn- share-qr-code
[{:keys [share-qr-type qr-image-uri component-width customization-color full-name
profile-picture emoji]
:as props}]
[rn/view {:style style/content-container}
(when (#{:wallet-legacy :wallet-multichain} share-qr-type)
[header props])
[quo.theme/provider {:theme :light}
[qr-code/view
{:qr-image-uri qr-image-uri
:size (style/qr-code-size component-width)
:avatar (if (= share-qr-type :profile)
:profile
:wallet-account)
:customization-color customization-color
:full-name full-name
:profile-picture profile-picture
:emoji emoji}]]
[rn/view {:style style/bottom-container}
(case share-qr-type
:profile [profile-bottom props]
:wallet-legacy [wallet-legacy-bottom props]
:wallet-multichain [wallet-multichain-bottom props]
nil)]])
(defn view (defn view
[{:keys [qr-image-uri link-title url-on-press url-on-long-press qr-url share-on-press]}] "Receives the following properties:
[blur/ios-view - type: :profile | :wallet-legacy | :wallet-multichain
{:style style/qr-code-container - qr-image-uri: Image source value.
:blur-type :light} - qr-data: Text to show below the QR code.
[qr-code/view {:qr-image-uri qr-image-uri}] - on-text-press: Callback for the `qr-data` text.
[rn/view {:style style/profile-address-container} - on-text-long-press: Callback for the `qr-data` text.
[rn/view {:style style/profile-address-column} - on-share-press: Callback for the share button.
[text/text - customization-color: Custom color for the QR code component.
{:size :paragraph-2 - unblur-on-android?: [Android only] disables blur for this component.
:weight :medium
:style style/profile-address-label} Depending on the `type`, different properties are accepted:
link-title] `:profile`
[rn/touchable-highlight - full-name: User full name.
{:active-opacity 1 - profile-picture: map ({:source image-source}) or any image source.
:underlay-color colors/neutral-80-opa-1-blur `:wallet-legacy`
:background-color :transparent - emoji: Emoji in a string to show in the QR code.
:on-press url-on-press - on-info-press: Callback for the info icon.
:on-long-press url-on-long-press - on-legacy-press: Callback for the legacy tab.
:style style/profile-address-content-container} - on-multichain-press: Callback for the multichain tab.
[text/text `:wallet-multichain`
{:style style/profile-address-content - networks: A vector of network names as keywords (`[:ethereum, :my-net, ...]`).
:size :paragraph-1 - on-settings-press: Callback for the settings button.
:weight :medium - emoji: Emoji in a string to show in the QR code.
:ellipsize-mode :middle - on-info-press: Callback for the info icon.
:number-of-lines 1} - on-legacy-press: Callback for the legacy tab.
qr-url]]] - on-multichain-press: Callback for the multichain tab.
[rn/view {:style style/share-button-container}
[button/button WARNING on Android:
{:icon-only? true Sometimes while using a blur layer on top of another on Android, this component looks
:type :grey bad because of the `blur/view`, so we can set `unblur-on-android? true` to fix it.
:background :blur "
:size 32 [{:keys [unblur-on-android?] :as props}]
:accessibility-label :share-profile (reagent/with-let [component-width (reagent/atom nil)
:on-press share-on-press} container-component (if (and platform/android? unblur-on-android?)
:i/share]]]]) [rn/view {:background-color style/overlay-color}]
[blur/view
{:blur-radius 20
:blur-amount 20
:blur-type :transparent
:overlay-color style/overlay-color
:background-color style/overlay-color}])]
[quo.theme/provider {:theme :dark}
[rn/view
{:accessibility-label :share-qr-code
:style style/outer-container
:on-layout #(reset! component-width (oops/oget % "nativeEvent.layout.width"))}
(conj container-component
(when @component-width
[share-qr-code
(-> props
(assoc :component-width @component-width)
(clojure.set/rename-keys {:type :share-qr-type}))]))]]))

View File

@ -36,3 +36,64 @@
:qr-size (or 400 (int size)) :qr-size (or 400 (int size))
:error-level :highest})] :error-level :highest})]
[quo/qr-code (assoc props :qr-image-uri qr-media-server-uri)])) [quo/qr-code (assoc props :qr-image-uri qr-media-server-uri)]))
(defn- get-network-short-name-url
[network]
(case network
:ethereum "eth:"
:optimism "opt:"
:arbitrum "arb1:"
(str (name network) ":")))
(defn- get-qr-data-for-wallet-multichain
[qr-data networks]
(as-> networks $
(map get-network-short-name-url $)
(apply str $)
(str $ qr-data)))
(defn share-qr-code
"Receives the following properties:
- type: :profile | :wallet-legacy | :wallet-multichain
- qr-data: Text to encode in the QR code
- qr-data-label-shown: Text to show below the QR code
- on-text-press: Callback for the `qr-data-label-shown` text.
- on-text-long-press: Callback for the `qr-data-label-shown` text.
- on-share-press: Callback for the share button.
- customization-color: Custom color for the QR code component.
- unblur-on-android?: [Android only] disables blur for this component.
Depending on the `type`, different properties are accepted:
`:profile`
- full-name: User full name.
- profile-picture: map ({:source image-source}) or any image source.
`:wallet-legacy`
- emoji: Emoji in a string to show in the QR code.
- on-info-press: Callback for the info icon.
- on-legacy-press: Callback for the legacy tab.
- on-multichain-press: Callback for the multichain tab.
`:wallet-multichain`
- networks: A vector of network names (`[:ethereum, :optimism, :my-net, ,,,]`)
they will add network prefixes to the address in
`qr-data-label-shown` and the QR code generated.
- on-settings-press: Callback for the settings button.
- emoji: Emoji in a string to show in the QR code.
- on-info-press: Callback for the info icon.
- on-legacy-press: Callback for the legacy tab.
- on-multichain-press: Callback for the multichain tab."
[{:keys [qr-data qr-data-label-shown networks]
share-qr-type :type
:as props}]
(let [label (or qr-data-label-shown qr-data)
string-to-encode (if (= share-qr-type :wallet-multichain)
(get-qr-data-for-wallet-multichain qr-data networks)
qr-data)
qr-media-server-uri (image-server/get-qr-image-uri-for-any-url
{:url string-to-encode
:port (rf/sub [:mediaserver/port])
:qr-size 600
:error-level :highest})]
[quo/share-qr-code
(assoc props
:qr-data label
:qr-image-uri qr-media-server-uri)]))

View File

@ -1,34 +1,142 @@
(ns status-im2.contexts.quo-preview.share.share-qr-code (ns status-im2.contexts.quo-preview.share.share-qr-code
(:require (:require
[quo.core :as quo] [quo.core :as quo]
[react-native.core :as rn]
[reagent.core :as reagent] [reagent.core :as reagent]
[status-im2.common.resources :as resources]
[status-im2.contexts.quo-preview.preview :as preview] [status-im2.contexts.quo-preview.preview :as preview]
[utils.image-server :as image-server] [utils.image-server :as image-server]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))
(def descriptor (def descriptor
[{:key :url :type :text} [{:key :qr-data
{:key :link-title :type :text}]) :label "QR data:"
:type :text}
{:key :type
:type :select
:options [{:key :profile}
{:key :wallet-legacy}
{:key :wallet-multichain}]}])
(def profile-descriptor
[{:key :profile-picture
:type :select
:options [{:key (resources/get-mock-image :user-picture-female2)
:value "User 1"}
{:key (resources/get-mock-image :user-picture-male4)
:value "User 2"}
{:key nil
:value "No picture"}]}
{:key :full-name
:type :text}
(preview/customization-color-option)])
(def wallet-legacy-descriptor
[{:key :emoji
:type :select
:options [{:key "🐈"}
{:key "👻"}
{:key "🐧"}]}
(preview/customization-color-option)])
(def possible-networks [:ethereum :optimism :arbitrum :myNet])
(def wallet-multichain-descriptor
[{:key :emoji
:type :select
:options [{:key "🐈"}
{:key "👻"}
{:key "🐧"}]}
(preview/customization-color-option)
{:key :networks
:type :select
:options [{:key (take 1 possible-networks)
:value "Ethereum"}
{:key (take 2 possible-networks)
:value "Ethereum and Optimism"}
{:key (take 3 possible-networks)
:value "Ethereum, Optimism and Arbitrum"}
{:key (take 4 possible-networks)
:value "Ethereum, Optimism, Arbitrum and unknown"}]}])
(defn- get-network-short-name-url
[network]
(case network
:ethereum "eth:"
:optimism "opt:"
:arbitrum "arb1:"
(str (name network) ":")))
(def ^:private profile-link
"https://join.status.im/u/zQ3shfc5Wqnu")
(def ^:private wallet-address "0x39cf6E0Ba4C4530735616e1Ee7ff5FbCB726fBd2")
(defn- set-qr-data-based-on-type
[_ state-atom {old-type :type :as _old-state} {new-type :type :as _new-state}]
(when (not= old-type new-type)
(swap! state-atom assoc
:qr-data
(if (= new-type :profile)
profile-link
wallet-address))))
(defn view (defn view
[] []
(let [state (reagent/atom {:info-button? true (let [state (reagent/atom {:type :profile
:link-title "Link to profile" :qr-data profile-link
:url "status.app/u/zQ34e1zlOdas0pKnvrweeedsasas12adjie8"})] :on-share-press #(js/alert "share pressed")
:on-text-press #(js/alert "text pressed")
:on-text-long-press #(js/alert "text long press")
:profile-picture nil
:full-name "My User"
:customization-color :purple
:emoji "🐈"
:on-info-press #(js/alert "Info pressed")
:on-legacy-press #(js/alert (str "Tab " % " pressed"))
:on-multichain-press #(js/alert (str "Tab " % " pressed"))
:networks (take 2 possible-networks)
:on-settings-press #(js/alert "Settings pressed")})
_ (add-watch state :change set-qr-data-based-on-type)]
(fn [] (fn []
(let [qr-media-server-uri (image-server/get-qr-image-uri-for-any-url (let [qr-url (if (= (:type @state) :wallet-multichain)
{:url (:url @state) (as-> (:networks @state) $
(map get-network-short-name-url $)
(apply str $)
(str $ (:qr-data @state)))
(:qr-data @state))
qr-media-server-uri (image-server/get-qr-image-uri-for-any-url
{:url qr-url
:port (rf/sub [:mediaserver/port]) :port (rf/sub [:mediaserver/port])
:qr-size 300 :qr-size 500
:error-level :highest})] :error-level :highest})
typed-descriptor (concat descriptor
(case (:type @state)
:profile profile-descriptor
:wallet-legacy wallet-legacy-descriptor
:wallet-multichain wallet-multichain-descriptor
nil))]
[preview/preview-container [preview/preview-container
{:state state {:state state
:descriptor descriptor :descriptor typed-descriptor
:component-container-style {:padding-vertical 20}} :component-container-style {:padding-horizontal 0}}
[quo/share-qr-code [rn/view
{:qr-image-uri qr-media-server-uri {:style {:flex 1
:link-title (:link-title @state) :justify-content :flex-end
:url-on-press #(js/alert "url pressed") :align-items :center
:url-on-long-press #(js/alert "url long pressed") :padding-horizontal 20
:share-on-press #(js/alert "share pressed") :padding-vertical 30}}
:qr-url (:url @state)}]])))) [rn/view
{:style {:position :absolute
:top 0
:bottom 0
:left 0
:right 0}}
[rn/image
{:style {:flex 1
:resize-mode :stretch}
:source (resources/get-mock-image :dark-blur-bg)}]]
[quo/share-qr-code
(assoc @state
:qr-image-uri qr-media-server-uri
:qr-data qr-url)]]]))))

View File

@ -29,15 +29,8 @@
:color colors/white}) :color colors/white})
(def qr-code-container (def qr-code-container
{:padding-top 12 {:margin-horizontal 20
:padding-bottom 8 :margin-top 8})
:padding-horizontal 12
:border-radius 16
:margin-top 8
:margin-horizontal screen-padding
:background-color colors/white-opa-5
:flex-direction :column
:justify-content :center})
(def emoji-hash-container (def emoji-hash-container
{:border-radius 16 {:border-radius 16

View File

@ -5,8 +5,8 @@
[quo.foundations.colors :as colors] [quo.foundations.colors :as colors]
[react-native.blur :as blur] [react-native.blur :as blur]
[react-native.core :as rn] [react-native.core :as rn]
[react-native.navigation :as navigation]
[react-native.platform :as platform] [react-native.platform :as platform]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent] [reagent.core :as reagent]
[status-im.multiaccounts.core :as multiaccounts] [status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.components.list-selection :as list-selection] [status-im.ui.components.list-selection :as list-selection]
@ -45,10 +45,9 @@
(i18n/label :t/share)]]) (i18n/label :t/share)]])
(defn profile-tab (defn profile-tab
[window-width] []
(let [{:keys [emoji-hash compressed-key customization-color display-name] (let [{:keys [emoji-hash compressed-key customization-color display-name]
:as profile} (rf/sub [:profile/profile]) :as profile} (rf/sub [:profile/profile])
qr-size (int (- window-width 64))
profile-url (str image-server/status-profile-base-url compressed-key) profile-url (str image-server/status-profile-base-url compressed-key)
profile-photo-uri (:uri (multiaccounts/displayed-photo profile)) profile-photo-uri (:uri (multiaccounts/displayed-photo profile))
abbreviated-url (address/get-abbreviated-profile-url abbreviated-url (address/get-abbreviated-profile-url
@ -57,46 +56,21 @@
emoji-hash-string (string/join emoji-hash)] emoji-hash-string (string/join emoji-hash)]
[:<> [:<>
[rn/view {:style style/qr-code-container} [rn/view {:style style/qr-code-container}
[qr-codes/qr-code [qr-codes/share-qr-code
{:url profile-url {:type :profile
:size qr-size :unblur-on-android? true
:avatar :profile :qr-data profile-url
:qr-data-label-shown abbreviated-url
:on-share-press #(list-selection/open-share {:message profile-url})
:on-text-press #(rf/dispatch [:share/copy-text-and-show-toast
{:text-to-copy profile-url
:post-copy-message (i18n/label :t/link-to-profile-copied)}])
:on-text-long-press #(rf/dispatch [:share/copy-text-and-show-toast
{:text-to-copy profile-url
:post-copy-message (i18n/label :t/link-to-profile-copied)}])
:profile-picture profile-photo-uri
:full-name display-name :full-name display-name
:customization-color customization-color :customization-color customization-color}]]
:profile-picture profile-photo-uri}]
[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
{:text-to-copy profile-url
:post-copy-message (i18n/label :t/link-to-profile-copied)}])
:on-long-press #(rf/dispatch [:share/copy-text-and-show-toast
{:text-to-copy profile-url
:post-copy-message (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-only? true
:type :grey
:background :blur
:size 32
:accessibility-label :link-to-profile
:on-press #(list-selection/open-share {:message profile-url})}
:i/share]]]]
[rn/view {:style style/emoji-hash-container} [rn/view {:style style/emoji-hash-container}
[rn/view {:style style/emoji-address-container} [rn/view {:style style/emoji-address-container}
@ -138,7 +112,7 @@
[rn/text {:style style/wip-style} "not implemented"]) [rn/text {:style style/wip-style} "not implemented"])
(defn tab-content (defn tab-content
[window-width] []
(let [selected-tab (reagent/atom :profile)] (let [selected-tab (reagent/atom :profile)]
(fn [] (fn []
[:<> [:<>
@ -154,18 +128,14 @@
{:id :wallet {:id :wallet
:label (i18n/label :t/wallet)}]}]] :label (i18n/label :t/wallet)}]}]]
(if (= @selected-tab :profile) (if (= @selected-tab :profile)
[profile-tab window-width] [profile-tab]
[wallet-tab])]))) [wallet-tab])])))
(defn view (defn view
[] []
(let [window-width (rf/sub [:dimensions/window-width])] [rn/view {:flex 1 :padding-top (safe-area/get-top)}
(fn [] [blur/view
[rn/view {:style style/blur
{:flex 1 :blur-amount 20
:padding-top (navigation/status-bar-height)} :blur-radius (if platform/android? 25 10)}]
[blur/view [tab-content]])
{:style style/blur
:blur-amount 20
:blur-radius (if platform/android? 25 10)}]
[tab-content window-width]])))

View File

@ -1,12 +1,12 @@
(ns test-helpers.component (ns test-helpers.component
"Helpers for writing component tests using React Native Testing Library." "Helpers for writing component tests using React Native Testing Library."
(:require-macros test-helpers.component) (:require-macros [test-helpers.component])
(:require (:require ["@testing-library/react-native" :as rtl]
["@testing-library/react-native" :as rtl] [camel-snake-kebab.core :as camel-snake-kebab]
[camel-snake-kebab.core :as camel-snake-kebab] [oops.core :as oops]
[quo.theme :as quo.theme] [quo.theme :as quo.theme]
[reagent.core :as reagent] [reagent.core :as reagent]
[utils.i18n :as i18n])) [utils.i18n :as i18n]))
;;;; React Native Testing Library ;;;; React Native Testing Library
@ -233,3 +233,21 @@
([element prop] (has-prop element prop js/undefined)) ([element prop] (has-prop element prop js/undefined))
([element prop value] ([element prop value]
(.toHaveProp (js/expect element) (camel-snake-kebab/->camelCaseString prop) value))) (.toHaveProp (js/expect element) (camel-snake-kebab/->camelCaseString prop) value)))
(defn get-rerender-fn
"Returns a rerender function from React Native Testing Library.
Takes a JS Object representing a component and returns a function that accepts hiccup
representing the component to rerender with the new props to rerender it.
e.g.
(let [rerender-fn (h/get-rerender-fn
(h/render [my-component {:prop-1 :A
:prop-2 :B}]))]
;; Rerenders `my-component` with the new props
(rerender-fn [my-component {:prop-1 :new-A
:prop-2 :new-B}]))
"
[component]
(fn [component-updated]
(let [rerender-fn (oops/oget component "rerender")
react-element (reagent/as-element component-updated)]
(rerender-fn react-element))))

View File

@ -859,6 +859,7 @@
"leave-community?": "Leave community?", "leave-community?": "Leave community?",
"leave-community-message": "Well be sad to see you go but remember, you can come back at any time!", "leave-community-message": "Well be sad to see you go but remember, you can come back at any time!",
"left-community": "You left “{{community}}”", "left-community": "You left “{{community}}”",
"legacy": "Legacy",
"joined": "Joined", "joined": "Joined",
"leave-group": "Leave group", "leave-group": "Leave group",
"left": "left", "left": "left",
@ -960,6 +961,7 @@
"multiaccount-exists-content": "Keys for this account already exist and cant be added again. If youve lost your password, passcode or Keycard, uninstall the app, reinstall and access your keys by entering your seed phrase", "multiaccount-exists-content": "Keys for this account already exist and cant be added again. If youve lost your password, passcode or Keycard, uninstall the app, reinstall and access your keys by entering your seed phrase",
"multiaccounts-recover-enter-phrase-text": "Enter 12, 15, 18, 21 or 24 words.\nSeparate words by a single space.", "multiaccounts-recover-enter-phrase-text": "Enter 12, 15, 18, 21 or 24 words.\nSeparate words by a single space.",
"multiaccounts-recover-enter-phrase-title": "Enter your seed phrase", "multiaccounts-recover-enter-phrase-title": "Enter your seed phrase",
"multichain": "Multichain",
"name": "Name", "name": "Name",
"name-of-token": "The name of your token", "name-of-token": "The name of your token",
"need-help": "Need help?", "need-help": "Need help?",