* 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:
parent
ca822ff51d
commit
4a874ce48d
|
@ -1,34 +1,119 @@
|
|||
(ns quo.components.share.share-qr-code.component-spec
|
||||
(:require
|
||||
[quo.components.share.share-qr-code.view :as share-qr-code]
|
||||
(:require [quo.components.share.share-qr-code.view :as share-qr-code]
|
||||
[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/test "renders share qr code component"
|
||||
(h/render [share-qr-code/view
|
||||
{:link-title " A test title"}])
|
||||
(-> (js/expect (h/get-by-text "A test title"))
|
||||
(.toBeTruthy)))
|
||||
(h/describe "Renders share-qr-code component in all types"
|
||||
(let [qr-label "Text shown below QR"]
|
||||
(h/test "Profile"
|
||||
(render-share-qr-code {:type :profile
|
||||
:qr-data qr-label})
|
||||
(h/is-truthy (h/get-by-text qr-label)))
|
||||
|
||||
(h/test "renders share qr code url"
|
||||
(h/render [share-qr-code/view
|
||||
{:qr-url " A test url"}])
|
||||
(-> (js/expect (h/get-by-text "A test url"))
|
||||
(.toBeTruthy)))
|
||||
(h/test "Wallet Legacy"
|
||||
(render-share-qr-code {:type :wallet-legacy
|
||||
:qr-data qr-label
|
||||
:emoji "👻"})
|
||||
(h/is-truthy (h/get-by-text qr-label)))
|
||||
|
||||
(h/test "on press link event fires"
|
||||
(let [event (h/mock-fn)]
|
||||
(h/render [share-qr-code/view
|
||||
{:url-on-press event
|
||||
:qr-url " A test url"}])
|
||||
(h/fire-event :press (h/get-by-text "A test url"))
|
||||
(-> (js/expect event)
|
||||
(.toHaveBeenCalledTimes 1))))
|
||||
(h/test "Wallet Multichain"
|
||||
(render-share-qr-code {:type :wallet-multichain
|
||||
:qr-data qr-label
|
||||
:emoji "👻"})
|
||||
(h/is-truthy (h/get-by-text qr-label)))))
|
||||
|
||||
(h/test "on press share event fires"
|
||||
(let [event (h/mock-fn)]
|
||||
(h/render [share-qr-code/view
|
||||
{:share-on-press event}])
|
||||
(h/fire-event :press (h/get-by-label-text :share-profile))
|
||||
(-> (js/expect event)
|
||||
(.toHaveBeenCalledTimes 1)))))
|
||||
(h/describe "Fires all events for all types"
|
||||
(letfn [(test-fire-events [props test-seq]
|
||||
(doseq [{:keys [test-name event-name
|
||||
callback-prop-key
|
||||
accessibility-label]} test-seq
|
||||
:let [event-fn (h/mock-fn)]]
|
||||
(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}])))))
|
||||
|
|
|
@ -1,45 +1,116 @@
|
|||
(ns quo.components.share.share-qr-code.style
|
||||
(:require
|
||||
[quo.foundations.colors :as colors]))
|
||||
(:require [quo.foundations.colors :as colors]))
|
||||
|
||||
(def qr-code-container
|
||||
{:padding-top 12
|
||||
:padding-horizontal 12
|
||||
:padding-bottom 8
|
||||
:border-radius 16
|
||||
:background-color colors/white-opa-5
|
||||
:flex-direction :column
|
||||
:justify-content :center
|
||||
:align-items :center})
|
||||
(def outer-container
|
||||
{:border-radius 16
|
||||
:width "100%"
|
||||
:overflow :hidden})
|
||||
|
||||
(def profile-address-column
|
||||
{:margin-horizontal :auto
|
||||
:flex 4})
|
||||
(def overlay-color colors/white-opa-5)
|
||||
|
||||
(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
|
||||
:justify-content :space-between
|
||||
:margin-top 6})
|
||||
:margin-bottom 8})
|
||||
|
||||
(def profile-address-content-container
|
||||
{:padding-top 2
|
||||
:align-self :flex-start})
|
||||
(def wallet-multichain-data-container {:margin-top 4})
|
||||
|
||||
(def profile-address-content
|
||||
{:color colors/white})
|
||||
|
||||
(def profile-address-label
|
||||
{:color colors/white-opa-40})
|
||||
|
||||
(def share-button-container
|
||||
{:flex 1
|
||||
:flex-direction :column
|
||||
;;; Dashed line
|
||||
(def divider-container
|
||||
{:height 8
|
||||
:margin-horizontal 4
|
||||
:justify-content :center
|
||||
:align-items :flex-end})
|
||||
:overflow :hidden})
|
||||
|
||||
(def icon-container
|
||||
{:height 36
|
||||
:flex-direction :row
|
||||
:align-items :center
|
||||
:justify-content :space-between
|
||||
:padding-bottom 12})
|
||||
(def ^:private padding-for-divider (+ padding-horizontal 4))
|
||||
(def ^:private dashed-line-width 2)
|
||||
(def ^:private dashed-line-space 4)
|
||||
|
||||
(def dashed-line
|
||||
{: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))})
|
||||
|
|
|
@ -1,46 +1,249 @@
|
|||
(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.icon :as icon]
|
||||
[quo.components.list-items.preview-list.view :as preview-list]
|
||||
[quo.components.markdown.text :as text]
|
||||
[quo.components.share.qr-code.view :as qr-code]
|
||||
[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.core :as rn]))
|
||||
[react-native.core :as rn]
|
||||
[react-native.platform :as platform]
|
||||
[reagent.core :as reagent]
|
||||
[utils.i18n :as i18n]))
|
||||
|
||||
(defn view
|
||||
[{:keys [qr-image-uri link-title url-on-press url-on-long-press qr-url share-on-press]}]
|
||||
[blur/ios-view
|
||||
{:style style/qr-code-container
|
||||
:blur-type :light}
|
||||
[qr-code/view {:qr-image-uri qr-image-uri}]
|
||||
[rn/view {:style style/profile-address-container}
|
||||
[rn/view {:style style/profile-address-column}
|
||||
(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
|
||||
{:size :paragraph-2
|
||||
:weight :medium
|
||||
:style style/profile-address-label}
|
||||
link-title]
|
||||
[rn/touchable-highlight
|
||||
{:active-opacity 1
|
||||
:underlay-color colors/neutral-80-opa-1-blur
|
||||
:background-color :transparent
|
||||
:on-press url-on-press
|
||||
:on-long-press url-on-long-press
|
||||
:style style/profile-address-content-container}
|
||||
[text/text
|
||||
{:style style/profile-address-content
|
||||
:size :paragraph-1
|
||||
:weight :medium
|
||||
:ellipsize-mode :middle
|
||||
:number-of-lines 1}
|
||||
qr-url]]]
|
||||
[rn/view {:style style/share-button-container}
|
||||
(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-profile
|
||||
:on-press share-on-press}
|
||||
:i/share]]]])
|
||||
: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
|
||||
"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}))]))]]))
|
||||
|
|
|
@ -36,3 +36,64 @@
|
|||
:qr-size (or 400 (int size))
|
||||
:error-level :highest})]
|
||||
[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)]))
|
||||
|
|
|
@ -1,34 +1,142 @@
|
|||
(ns status-im2.contexts.quo-preview.share.share-qr-code
|
||||
(:require
|
||||
[quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.common.resources :as resources]
|
||||
[status-im2.contexts.quo-preview.preview :as preview]
|
||||
[utils.image-server :as image-server]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(def descriptor
|
||||
[{:key :url :type :text}
|
||||
{:key :link-title :type :text}])
|
||||
[{:key :qr-data
|
||||
: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
|
||||
[]
|
||||
(let [state (reagent/atom {:info-button? true
|
||||
:link-title "Link to profile"
|
||||
:url "status.app/u/zQ34e1zlOdas0pKnvrweeedsasas12adjie8"})]
|
||||
(let [state (reagent/atom {:type :profile
|
||||
:qr-data profile-link
|
||||
: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 []
|
||||
(let [qr-media-server-uri (image-server/get-qr-image-uri-for-any-url
|
||||
{:url (:url @state)
|
||||
(let [qr-url (if (= (:type @state) :wallet-multichain)
|
||||
(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])
|
||||
:qr-size 300
|
||||
:error-level :highest})]
|
||||
:qr-size 500
|
||||
: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
|
||||
{:state state
|
||||
:descriptor descriptor
|
||||
:component-container-style {:padding-vertical 20}}
|
||||
:descriptor typed-descriptor
|
||||
: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
|
||||
{:qr-image-uri qr-media-server-uri
|
||||
:link-title (:link-title @state)
|
||||
:url-on-press #(js/alert "url pressed")
|
||||
:url-on-long-press #(js/alert "url long pressed")
|
||||
:share-on-press #(js/alert "share pressed")
|
||||
:qr-url (:url @state)}]]))))
|
||||
(assoc @state
|
||||
:qr-image-uri qr-media-server-uri
|
||||
:qr-data qr-url)]]]))))
|
||||
|
|
|
@ -29,15 +29,8 @@
|
|||
:color colors/white})
|
||||
|
||||
(def qr-code-container
|
||||
{:padding-top 12
|
||||
:padding-bottom 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})
|
||||
{:margin-horizontal 20
|
||||
:margin-top 8})
|
||||
|
||||
(def emoji-hash-container
|
||||
{:border-radius 16
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
[quo.foundations.colors :as colors]
|
||||
[react-native.blur :as blur]
|
||||
[react-native.core :as rn]
|
||||
[react-native.navigation :as navigation]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.multiaccounts.core :as multiaccounts]
|
||||
[status-im.ui.components.list-selection :as list-selection]
|
||||
|
@ -45,10 +45,9 @@
|
|||
(i18n/label :t/share)]])
|
||||
|
||||
(defn profile-tab
|
||||
[window-width]
|
||||
[]
|
||||
(let [{:keys [emoji-hash compressed-key customization-color display-name]
|
||||
:as profile} (rf/sub [:profile/profile])
|
||||
qr-size (int (- window-width 64))
|
||||
profile-url (str image-server/status-profile-base-url compressed-key)
|
||||
profile-photo-uri (:uri (multiaccounts/displayed-photo profile))
|
||||
abbreviated-url (address/get-abbreviated-profile-url
|
||||
|
@ -57,46 +56,21 @@
|
|||
emoji-hash-string (string/join emoji-hash)]
|
||||
[:<>
|
||||
[rn/view {:style style/qr-code-container}
|
||||
[qr-codes/qr-code
|
||||
{:url profile-url
|
||||
:size qr-size
|
||||
:avatar :profile
|
||||
:full-name display-name
|
||||
: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
|
||||
[qr-codes/share-qr-code
|
||||
{:type :profile
|
||||
:unblur-on-android? true
|
||||
: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-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
|
||||
: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]]]]
|
||||
:post-copy-message (i18n/label :t/link-to-profile-copied)}])
|
||||
:profile-picture profile-photo-uri
|
||||
:full-name display-name
|
||||
:customization-color customization-color}]]
|
||||
|
||||
[rn/view {:style style/emoji-hash-container}
|
||||
[rn/view {:style style/emoji-address-container}
|
||||
|
@ -138,7 +112,7 @@
|
|||
[rn/text {:style style/wip-style} "not implemented"])
|
||||
|
||||
(defn tab-content
|
||||
[window-width]
|
||||
[]
|
||||
(let [selected-tab (reagent/atom :profile)]
|
||||
(fn []
|
||||
[:<>
|
||||
|
@ -154,18 +128,14 @@
|
|||
{:id :wallet
|
||||
:label (i18n/label :t/wallet)}]}]]
|
||||
(if (= @selected-tab :profile)
|
||||
[profile-tab window-width]
|
||||
[profile-tab]
|
||||
[wallet-tab])])))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [window-width (rf/sub [:dimensions/window-width])]
|
||||
(fn []
|
||||
[rn/view
|
||||
{:flex 1
|
||||
:padding-top (navigation/status-bar-height)}
|
||||
[rn/view {:flex 1 :padding-top (safe-area/get-top)}
|
||||
[blur/view
|
||||
{:style style/blur
|
||||
:blur-amount 20
|
||||
:blur-radius (if platform/android? 25 10)}]
|
||||
[tab-content window-width]])))
|
||||
[tab-content]])
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
(ns test-helpers.component
|
||||
"Helpers for writing component tests using React Native Testing Library."
|
||||
(:require-macros test-helpers.component)
|
||||
(:require
|
||||
["@testing-library/react-native" :as rtl]
|
||||
(:require-macros [test-helpers.component])
|
||||
(:require ["@testing-library/react-native" :as rtl]
|
||||
[camel-snake-kebab.core :as camel-snake-kebab]
|
||||
[oops.core :as oops]
|
||||
[quo.theme :as quo.theme]
|
||||
[reagent.core :as reagent]
|
||||
[utils.i18n :as i18n]))
|
||||
|
@ -233,3 +233,21 @@
|
|||
([element prop] (has-prop element prop js/undefined))
|
||||
([element 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))))
|
||||
|
|
|
@ -859,6 +859,7 @@
|
|||
"leave-community?": "Leave community?",
|
||||
"leave-community-message": "We’ll be sad to see you go but remember, you can come back at any time!",
|
||||
"left-community": "You left “{{community}}”",
|
||||
"legacy": "Legacy",
|
||||
"joined": "Joined",
|
||||
"leave-group": "Leave group",
|
||||
"left": "left",
|
||||
|
@ -960,6 +961,7 @@
|
|||
"multiaccount-exists-content": "Keys for this account already exist and can’t be added again. If you’ve 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-title": "Enter your seed phrase",
|
||||
"multichain": "Multichain",
|
||||
"name": "Name",
|
||||
"name-of-token": "The name of your token",
|
||||
"need-help": "Need help?",
|
||||
|
|
Loading…
Reference in New Issue