diff --git a/src/quo/components/list_items/preview_list/view.cljs b/src/quo/components/list_items/preview_list/view.cljs index 4b5f2bab74..8666ebb8c5 100644 --- a/src/quo/components/list_items/preview_list/view.cljs +++ b/src/quo/components/list_items/preview_list/view.cljs @@ -64,11 +64,11 @@ (defn- view-internal "[preview-list opts items] opts - {:type :user/:communities/:accounts/:tokens/:collectibles/:dapps/:network - :size :size-32 | :size-24 | :size-20 | :size-16 | :size-14 - :number number of items in the list (optional) - :blur? overflow-label blur?} - items preview list items (only 4 items is required for preview) + {:type :user/:communities/:accounts/:tokens/:collectibles/:dapps/:network + :size :size-32 | :size-24 | :size-20 | :size-16 | :size-14 + :number number of items in the list (optional) + :blur? overflow-label blur?} + items preview list items (only 4 items is required for preview) " [{:keys [type size number blur?]} items] (let [size-key (if (contains? properties/sizes size) size :size-24) diff --git a/src/quo/components/share/share_qr_code/component_spec.cljs b/src/quo/components/share/share_qr_code/component_spec.cljs index 308dcb4309..00864029df 100644 --- a/src/quo/components/share/share_qr_code/component_spec.cljs +++ b/src/quo/components/share/share_qr_code/component_spec.cljs @@ -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] - [test-helpers.component :as h])) + (: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}]))))) diff --git a/src/quo/components/share/share_qr_code/style.cljs b/src/quo/components/share/share_qr_code/style.cljs index b92d30c9dc..3dfb5f3029 100644 --- a/src/quo/components/share/share_qr_code/style.cljs +++ b/src/quo/components/share/share_qr_code/style.cljs @@ -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}) +;;; Dashed line +(def divider-container + {:height 8 + :margin-horizontal 4 + :justify-content :center + :overflow :hidden}) -(def profile-address-label - {:color colors/white-opa-40}) +(def ^:private padding-for-divider (+ padding-horizontal 4)) +(def ^:private dashed-line-width 2) +(def ^:private dashed-line-space 4) -(def share-button-container - {:flex 1 - :flex-direction :column - :justify-content :center - :align-items :flex-end}) +(def dashed-line + {:flex-direction :row + :margin-left -1}) -(def icon-container - {:height 36 - :flex-direction :row - :align-items :center - :justify-content :space-between - :padding-bottom 12}) +(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))}) diff --git a/src/quo/components/share/share_qr_code/view.cljs b/src/quo/components/share/share_qr_code/view.cljs index d8681b9b7c..52c5aa0fc7 100644 --- a/src/quo/components/share/share_qr_code/view.cljs +++ b/src/quo/components/share/share_qr_code/view.cljs @@ -1,46 +1,249 @@ (ns quo.components.share.share-qr-code.view - (:require - [quo.components.buttons.button.view :as button] - [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] - [react-native.blur :as blur] - [react-native.core :as rn])) + (: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.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 - [{: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} - [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} - [button/button - {:icon-only? true - :type :grey - :background :blur - :size 32 - :accessibility-label :share-profile - :on-press share-on-press} - :i/share]]]]) + "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}))]))]])) diff --git a/src/status_im2/common/qr_code_viewer/style.cljs b/src/status_im2/common/qr_code_viewer/style.cljs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/status_im2/common/qr_codes/view.cljs b/src/status_im2/common/qr_codes/view.cljs index ef4f0f55d2..0aa1a6da5c 100644 --- a/src/status_im2/common/qr_codes/view.cljs +++ b/src/status_im2/common/qr_codes/view.cljs @@ -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)])) diff --git a/src/status_im2/contexts/quo_preview/share/share_qr_code.cljs b/src/status_im2/contexts/quo_preview/share/share_qr_code.cljs index c438438036..3a3f4a505c 100644 --- a/src/status_im2/contexts/quo_preview/share/share_qr_code.cljs +++ b/src/status_im2/contexts/quo_preview/share/share_qr_code.cljs @@ -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}} - [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)}]])))) + :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 + (assoc @state + :qr-image-uri qr-media-server-uri + :qr-data qr-url)]]])))) diff --git a/src/status_im2/contexts/shell/share/style.cljs b/src/status_im2/contexts/shell/share/style.cljs index 20a3500997..e7519e6ec8 100644 --- a/src/status_im2/contexts/shell/share/style.cljs +++ b/src/status_im2/contexts/shell/share/style.cljs @@ -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 diff --git a/src/status_im2/contexts/shell/share/view.cljs b/src/status_im2/contexts/shell/share/view.cljs index f75bef6de6..fdb5f8bb10 100644 --- a/src/status_im2/contexts/shell/share/view.cljs +++ b/src/status_im2/contexts/shell/share/view.cljs @@ -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 + [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-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 - :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]]]] + :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)} - [blur/view - {:style style/blur - :blur-amount 20 - :blur-radius (if platform/android? 25 10)}] - [tab-content window-width]]))) + [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]]) diff --git a/src/test_helpers/component.cljs b/src/test_helpers/component.cljs index 8a67df9818..19eb73c223 100644 --- a/src/test_helpers/component.cljs +++ b/src/test_helpers/component.cljs @@ -1,12 +1,12 @@ (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] - [camel-snake-kebab.core :as camel-snake-kebab] - [quo.theme :as quo.theme] - [reagent.core :as reagent] - [utils.i18n :as i18n])) + (: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])) ;;;; React Native Testing Library @@ -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)))) diff --git a/translations/en.json b/translations/en.json index d275ac443d..65df21ed3f 100644 --- a/translations/en.json +++ b/translations/en.json @@ -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?",