[#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

@ -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
(def profile-address-label :margin-horizontal 4
{:color colors/white-opa-40})
(def share-button-container
{:flex 1
:flex-direction :column
:justify-content :center :justify-content :center
:align-items :flex-end}) :overflow :hidden})
(def icon-container (def ^:private padding-for-divider (+ padding-horizontal 4))
{:height 36 (def ^:private dashed-line-width 2)
:flex-direction :row (def ^:private dashed-line-space 4)
:align-items :center
:justify-content :space-between (def dashed-line
:padding-bottom 12}) {:flex-direction :row
:margin-left -1})
(def line
{:background-color colors/white-opa-20
:width dashed-line-width
:height 1})
(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]
[clojure.string :as string]
[oops.core :as oops]
[quo.components.buttons.button.view :as button] [quo.components.buttons.button.view :as button]
[quo.components.icon :as icon]
[quo.components.list-items.preview-list.view :as preview-list]
[quo.components.markdown.text :as text] [quo.components.markdown.text :as text]
[quo.components.share.qr-code.view :as qr-code] [quo.components.share.qr-code.view :as qr-code]
[quo.components.share.share-qr-code.style :as style] [quo.components.share.share-qr-code.style :as style]
[quo.foundations.colors :as colors] [quo.components.tabs.tab.view :as tab]
[quo.foundations.resources :as quo.resources]
[quo.theme]
[react-native.blur :as blur] [react-native.blur :as blur]
[react-native.core :as rn])) [react-native.core :as rn]
[react-native.platform :as platform]
[reagent.core :as reagent]
[utils.i18n :as i18n]))
(defn view (defn- line [] [rn/view {:style style/line}])
[{:keys [qr-image-uri link-title url-on-press url-on-long-press qr-url share-on-press]}] (defn- space [] [rn/view {:style style/line-space}])
[blur/ios-view
{:style style/qr-code-container (defn- dashed-line
:blur-type :light} [width]
[qr-code/view {:qr-image-uri qr-image-uri}] (into [rn/view {:style style/dashed-line}]
[rn/view {:style style/profile-address-container} (take (style/number-lines-and-spaces-to-fill width))
[rn/view {:style style/profile-address-column} (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 [text/text
{:size :paragraph-2 (cond-> {:size :paragraph-1
:weight :medium :weight :monospace}
:style style/profile-address-label} ellipsize? (assoc :number-of-lines 1
link-title] :ellipsize-mode :middle))
[rn/touchable-highlight qr-data-text]])
{:active-opacity 1
:underlay-color colors/neutral-80-opa-1-blur (defn- share-button
:background-color :transparent [{:keys [alignment on-press]}]
:on-press url-on-press [rn/view {:style (style/share-button-container alignment)}
:on-long-press url-on-long-press [button/button
:style style/profile-address-content-container} {:icon-only? true
[text/text :type :grey
{:style style/profile-address-content :background :blur
:size :paragraph-1 :size style/share-button-size
:weight :medium :accessibility-label :link-to-profile
:ellipsize-mode :middle :on-press on-press}
:number-of-lines 1} :i/share]])
qr-url]]]
[rn/view {:style style/share-button-container} (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 [button/button
{:icon-only? true {:icon-only? true
:type :grey :type :grey
:background :blur :background :blur
:size 32 :size 32
:accessibility-label :share-profile :accessibility-label :share-qr-code-settings
:on-press share-on-press} :on-press on-settings-press}
:i/share]]]]) :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
"Receives the following properties:
- type: :profile | :wallet-legacy | :wallet-multichain
- qr-image-uri: Image source value.
- qr-data: Text to show below the QR code.
- on-text-press: Callback for the `qr-data` text.
- on-text-long-press: Callback for the `qr-data` 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 as keywords (`[:ethereum, :my-net, ...]`).
- 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.
WARNING on Android:
Sometimes while using a blur layer on top of another on Android, this component looks
bad because of the `blur/view`, so we can set `unblur-on-android? true` to fix it.
"
[{:keys [unblur-on-android?] :as props}]
(reagent/with-let [component-width (reagent/atom nil)
container-component (if (and platform/android? unblur-on-android?)
[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}}
[rn/view
{:style {:flex 1
:justify-content :flex-end
:align-items :center
:padding-horizontal 20
:padding-vertical 30}}
[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 [quo/share-qr-code
{:qr-image-uri qr-media-server-uri (assoc @state
:link-title (:link-title @state) :qr-image-uri qr-media-server-uri
:url-on-press #(js/alert "url pressed") :qr-data qr-url)]]]))))
:url-on-long-press #(js/alert "url long pressed")
:share-on-press #(js/alert "share pressed")
:qr-url (:url @state)}]]))))

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
:full-name display-name :qr-data-label-shown abbreviated-url
:customization-color customization-color :on-share-press #(list-selection/open-share {:message profile-url})
:profile-picture profile-photo-uri}] :on-text-press #(rf/dispatch [:share/copy-text-and-show-toast
[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 {:text-to-copy profile-url
:post-copy-message (i18n/label :t/link-to-profile-copied)}]) :post-copy-message (i18n/label :t/link-to-profile-copied)}])
:on-long-press #(rf/dispatch [:share/copy-text-and-show-toast :on-text-long-press #(rf/dispatch [:share/copy-text-and-show-toast
{:text-to-copy profile-url {:text-to-copy profile-url
:post-copy-message (i18n/label :t/link-to-profile-copied)}])} :post-copy-message (i18n/label :t/link-to-profile-copied)}])
[quo/text :profile-picture profile-photo-uri
{:style style/profile-address-content :full-name display-name
:size :paragraph-1 :customization-color customization-color}]]
: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 []
[rn/view
{:flex 1
:padding-top (navigation/status-bar-height)}
[blur/view [blur/view
{:style style/blur {:style style/blur
:blur-amount 20 :blur-amount 20
:blur-radius (if platform/android? 25 10)}] :blur-radius (if platform/android? 25 10)}]
[tab-content window-width]]))) [tab-content]])

View File

@ -1,9 +1,9 @@
(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]))
@ -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?",