Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a40a7e3bb | ||
|
|
e6068fd4c1 | ||
|
|
579b382c44 | ||
|
|
859cb19886 | ||
|
|
2d92b515b8 | ||
|
|
972e1eee01 | ||
|
|
6c2b437e62 | ||
|
|
94a3e266a9 | ||
|
|
716007dc3c | ||
|
|
233d1b3261 | ||
|
|
25381b601f | ||
|
|
4a874ce48d | ||
|
|
ca822ff51d | ||
|
|
88b3d7d2e6 | ||
|
|
90d3a5a39d | ||
|
|
dd8beeb848 |
+2
-4
@@ -106,11 +106,9 @@
|
||||
:target :node-test
|
||||
;; Uncomment line below to `make test-watch` a specific file
|
||||
;; :ns-regexp "status-im2.subs.messages-test$"
|
||||
:main
|
||||
status-im.test-runner/main
|
||||
:main status-im.test-runner/main
|
||||
;; set :ui-driven to true to let shadow-cljs inject node-repl
|
||||
:ui-driven
|
||||
true
|
||||
:ui-driven true
|
||||
:closure-defines
|
||||
{status-im2.config/POKT_TOKEN #shadow/env "POKT_TOKEN"
|
||||
status-im2.config/INFURA_TOKEN #shadow/env "INFURA_TOKEN"
|
||||
|
||||
@@ -6,10 +6,3 @@ export function infoLayout(input, isTop) {
|
||||
return isTop ? input.value : -input.value;
|
||||
});
|
||||
}
|
||||
|
||||
export function textSheet(input, isHeight) {
|
||||
return useDerivedValue(function () {
|
||||
'worklet';
|
||||
return isHeight ? input.value : -input.value;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
(ns quo.components.colors.color.style
|
||||
(:require
|
||||
[quo.components.colors.color.constants :as constants]
|
||||
[quo.foundations.colors :as colors]))
|
||||
[quo.foundations.colors :as colors]
|
||||
[utils.responsiveness :as responsiveness]))
|
||||
|
||||
(defn color-button-common
|
||||
[window-width]
|
||||
@@ -10,9 +10,9 @@
|
||||
:border-width 4
|
||||
:border-radius 24
|
||||
:margin-right (-> window-width
|
||||
(- constants/IPHONE_11_PRO_VIEWPORT_WIDTH) ;We want the design to be 100%
|
||||
;identical to Figma on iPhone 11
|
||||
;Pro, So we're using it's VW here.
|
||||
(- responsiveness/IPHONE_11_PRO_VIEWPORT_WIDTH) ;We want the design to be 100%
|
||||
;identical to Figma on iPhone 11
|
||||
;Pro, So we're using it's VW here.
|
||||
(/ 6) ;Dividing by an appropriate factor to get a reasonable value for each VW
|
||||
;on other devices (This will yield 0 on iPhone 11 Pro, Which is what we
|
||||
;want)
|
||||
@@ -25,14 +25,12 @@
|
||||
([color selected?]
|
||||
(color-button color selected? nil nil))
|
||||
([color selected? idx window-width]
|
||||
(merge (color-button-common window-width)
|
||||
(when selected?
|
||||
{:border-top-color (colors/alpha color 0.4)
|
||||
:border-end-color (colors/alpha color 0.4)
|
||||
:border-bottom-color (colors/alpha color 0.2)
|
||||
:border-start-color (colors/alpha color 0.2)}
|
||||
(when (zero? idx)
|
||||
{:margin-left -4})))))
|
||||
(cond-> (color-button-common window-width)
|
||||
selected? (assoc :border-top-color (colors/alpha color 0.4)
|
||||
:border-end-color (colors/alpha color 0.4)
|
||||
:border-bottom-color (colors/alpha color 0.2)
|
||||
:border-start-color (colors/alpha color 0.2))
|
||||
(zero? idx) (assoc :margin-left -4))))
|
||||
|
||||
(defn color-circle
|
||||
[color border?]
|
||||
|
||||
@@ -17,8 +17,9 @@
|
||||
- `default-selected` Default selected color name.
|
||||
- `on-change` Callback called when a color is selected `(fn [color-name])`.
|
||||
- `blur?` Boolean to enable blur background support.}"
|
||||
[{:keys [default-selected window-width]}]
|
||||
(let [selected (reagent/atom default-selected)]
|
||||
[{:keys [default-selected]}]
|
||||
(let [selected (reagent/atom default-selected)
|
||||
{window-width :width} (rn/get-window)]
|
||||
(fn [{:keys [blur? on-change feng-shui? container-style]}]
|
||||
[rn/scroll-view
|
||||
{:horizontal true
|
||||
|
||||
@@ -18,13 +18,19 @@
|
||||
(h/render [bottom-actions/view
|
||||
{:actions :2-actions
|
||||
:button-one-label button-one
|
||||
:button-one-props {:icon-left :i/arrow-left}
|
||||
:button-two-label button-two}])
|
||||
(h/is-truthy (h/get-by-text button-one))
|
||||
(h/is-truthy (h/get-by-label-text :icon))
|
||||
(h/is-truthy (h/get-by-text button-two))))
|
||||
|
||||
(h/test "render disabled button"
|
||||
(h/render [bottom-actions/view
|
||||
{:description "Sample description"
|
||||
:disabled? true
|
||||
:button-one-props {:disabled? true}
|
||||
:button-one-label "button"}])
|
||||
(h/is-disabled (h/get-by-label-text :button-one))))
|
||||
(h/is-disabled (h/get-by-label-text :button-one)))
|
||||
|
||||
(h/test "sane defaults"
|
||||
(h/render [bottom-actions/view {}])
|
||||
(h/is-truthy (h/get-by-label-text :button-one))))
|
||||
|
||||
@@ -17,34 +17,35 @@
|
||||
:description - string (default nil) - Description to display below the title
|
||||
:button-one-label - string (default nil) - Label for the first button
|
||||
:button-two-label - string (default nil) - Label for the second button
|
||||
:button-one-press - fn (default nil) - Function to call when the first button is pressed
|
||||
:button-two-press - fn (default nil) - Function to call when the second button is pressed
|
||||
:button-one-props - map with props for button one
|
||||
:button-two-props - map with props for button two
|
||||
:theme - :light/:dark
|
||||
:scroll - bool (default false) - Whether the iOS Home Indicator should be rendered
|
||||
:button-one-type - same as button/button :type
|
||||
:button-two-type - same as button/button :type"
|
||||
:scroll? - bool (default false) - Whether the iOS Home Indicator should be rendered"
|
||||
[props]
|
||||
(let [{:keys [actions description button-one-label button-two-label
|
||||
button-one-press button-two-press theme
|
||||
scroll? button-one-type button-two-type disabled?]}
|
||||
button-one-props button-two-props theme scroll?]}
|
||||
(merge default-props props)]
|
||||
[:<>
|
||||
[rn/view {:style style/buttons-container}
|
||||
(when (= actions :2-actions)
|
||||
[button/button
|
||||
{:size 40
|
||||
:background (when scroll? :blur)
|
||||
:container-style style/button-container-2-actions
|
||||
:type button-two-type
|
||||
:on-press button-two-press} button-two-label])
|
||||
(merge
|
||||
{:size 40
|
||||
:background (when scroll? :blur)
|
||||
:container-style style/button-container-2-actions
|
||||
:theme theme
|
||||
:accessibility-label :button-two}
|
||||
button-two-props)
|
||||
button-two-label])
|
||||
[button/button
|
||||
{:size 40
|
||||
:container-style style/button-container
|
||||
:type button-one-type
|
||||
:disabled? disabled?
|
||||
:background (when scroll? :blur)
|
||||
:on-press button-one-press
|
||||
:accessibility-label :button-one} button-one-label]]
|
||||
(merge
|
||||
{:size 40
|
||||
:container-style style/button-container
|
||||
:background (when scroll? :blur)
|
||||
:theme theme
|
||||
:accessibility-label :button-one}
|
||||
button-one-props)
|
||||
button-one-label]]
|
||||
(when description
|
||||
[text/text
|
||||
{:size :paragraph-2
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
(ns quo.components.dropdowns.dropdown-input.view
|
||||
(:require
|
||||
[quo.components.dropdowns.dropdown.properties :as properties]
|
||||
[quo.components.dropdowns.dropdown.style :as style]
|
||||
[quo.components.icon :as icon]
|
||||
[quo.components.markdown.text :as text]
|
||||
[quo.foundations.customization-colors :as customization-colors]
|
||||
[quo.theme :as theme]
|
||||
[react-native.blur :as blur]
|
||||
[react-native.core :as rn]))
|
||||
|
||||
(defn view-internal
|
||||
[]
|
||||
[rn/pressable
|
||||
{:on-press nil}])
|
||||
|
||||
(def view
|
||||
"Props:
|
||||
- :on-press - function to call when pressed"
|
||||
(theme/with-theme view-internal))
|
||||
@@ -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)
|
||||
|
||||
@@ -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}])))))
|
||||
|
||||
@@ -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))})
|
||||
|
||||
@@ -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}))]))]]))
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
(ns quo2.components.wallet.network-routing.component-spec
|
||||
(ns quo.components.wallet.network-routing.component-spec
|
||||
(:require [oops.core :as oops]
|
||||
[quo2.components.wallet.network-routing.view :as network-routing]
|
||||
[quo.components.wallet.network-routing.view :as network-routing]
|
||||
[reagent.core :as reagent]
|
||||
[test-helpers.component :as h]))
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
quo.components.drawers.permission-context.view
|
||||
quo.components.dropdowns.dropdown.view
|
||||
quo.components.dropdowns.network-dropdown.view
|
||||
quo.components.dropdowns.dropdown-input.view
|
||||
quo.components.empty-state.empty-state.view
|
||||
quo.components.gradient.gradient-cover.view
|
||||
[quo.components.graph.interactive-graph.view :as interactive-graph]
|
||||
@@ -229,6 +230,7 @@
|
||||
;;;; Dropdowns
|
||||
(def dropdown quo.components.dropdowns.dropdown.view/view)
|
||||
(def network-dropdown quo.components.dropdowns.network-dropdown.view/view)
|
||||
(def dropdown-input quo.components.dropdowns.dropdown-input.view/view)
|
||||
|
||||
;;;; Empty State
|
||||
(def empty-state quo.components.empty-state.empty-state.view/empty-state)
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
[quo.components.wallet.keypair.component-spec]
|
||||
[quo.components.wallet.network-amount.component-spec]
|
||||
[quo.components.wallet.network-bridge.component-spec]
|
||||
[quo.components.wallet.network-routing.component-spec]
|
||||
[quo.components.wallet.progress-bar.component-spec]
|
||||
[quo.components.wallet.summary-info.component-spec]
|
||||
[quo.components.wallet.token-input.component-spec]
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
(defn universal-link
|
||||
[community-id]
|
||||
(str (:external universal-links/domains)
|
||||
"/c/"
|
||||
"/c#"
|
||||
community-id))
|
||||
|
||||
(defn <-request-to-join-community-rpc
|
||||
|
||||
@@ -30,6 +30,10 @@
|
||||
|
||||
(def handled-schemes (set (into uri-schemes web-urls)))
|
||||
|
||||
(def group-chat-extractor
|
||||
{[#"(.*)" :params] {"" :group-chat
|
||||
"/" :group-chat}})
|
||||
|
||||
(def eip-extractor
|
||||
{#{[:prefix "-" :address]
|
||||
[:address]}
|
||||
@@ -39,9 +43,15 @@
|
||||
|
||||
(def routes
|
||||
[""
|
||||
{handled-schemes {["c/" :community-data] :community
|
||||
["cc/" :chat-data] :community-chat
|
||||
["u/" :user-data] :user}
|
||||
{handled-schemes {["c/" :community-data] :community
|
||||
["cc/" :community-data] :community-chat
|
||||
["p/" :chat-id] :private-chat
|
||||
["cr/" :community-id] :community-requests
|
||||
"g/" group-chat-extractor
|
||||
["wallet/" :account] :wallet-account
|
||||
["u/" :user-data] :user
|
||||
"c" :community
|
||||
"u" :user}
|
||||
ethereum-scheme eip-extractor}])
|
||||
|
||||
(defn parse-query-params
|
||||
@@ -58,7 +68,6 @@
|
||||
|
||||
(defn match-uri
|
||||
[uri]
|
||||
;;
|
||||
(let [;; bidi has trouble parse path with `=` in it extract `=` here and add back to parsed
|
||||
;; base64url regex based on https://datatracker.ietf.org/doc/html/rfc4648#section-5 may
|
||||
;; include invalid base64 (invalid length, length of any base64 encoded string must be a
|
||||
@@ -70,6 +79,7 @@
|
||||
uri-without-equal-in-path
|
||||
(if equal-end-of-base64url (string/replace-first uri equal-end-of-base64url "") uri)
|
||||
|
||||
;; fragment is the one after `#`, usually user-id, ens-name, community-id
|
||||
fragment (parse-fragment uri)
|
||||
ens? (ens/is-valid-eth-name? fragment)
|
||||
|
||||
@@ -87,8 +97,18 @@
|
||||
(and equal-end-of-base64url (= handler :community) (:community-data route-params))
|
||||
(update-in [:route-params :community-data] #(str % equal-end-of-base64url))
|
||||
|
||||
(and equal-end-of-base64url (= handler :community-chat) (:chat-data route-params))
|
||||
(update-in [:route-params :chat-data] #(str % equal-end-of-base64url))
|
||||
(and equal-end-of-base64url (= handler :community-chat) (:community-data route-params))
|
||||
(update-in [:route-params :community-data] #(str % equal-end-of-base64url))
|
||||
|
||||
(and fragment (= handler :community-chat) (:community-data route-params))
|
||||
(assoc-in [:route-params :community-id] fragment)
|
||||
|
||||
(and fragment
|
||||
(= handler :community-chat)
|
||||
(:community-data route-params)
|
||||
(string? (:community-data route-params))
|
||||
(re-find constants/regx-starts-with-uuid (:community-data route-params)))
|
||||
(assoc-in [:route-params :community-channel-id] (:community-data route-params))
|
||||
|
||||
(and equal-end-of-base64url (= handler :user) (:user-data route-params))
|
||||
(update-in [:route-params :user-data] #(str % equal-end-of-base64url))
|
||||
@@ -174,6 +194,15 @@
|
||||
(cb {:type :private-chat
|
||||
:error :invalid-chat-id})))))
|
||||
|
||||
(defn match-community-channel-async
|
||||
[{:keys [community-channel-id community-id]} cb]
|
||||
(if (validators/valid-compressed-key? community-id)
|
||||
(native-module/deserialize-and-compress-key
|
||||
community-id
|
||||
#(cb {:type :community-chat :chat-id (str % community-channel-id)}))
|
||||
(cb {:type :community-chat
|
||||
:error :not-found})))
|
||||
|
||||
(defn match-browser
|
||||
[uri {:keys [domain]}]
|
||||
;; NOTE: We rebuild domain from original URI and matched domain
|
||||
@@ -238,8 +267,8 @@
|
||||
:community))
|
||||
|
||||
(defn handle-uri
|
||||
[chain _chats uri cb]
|
||||
(let [{:keys [handler route-params]} (match-uri uri)]
|
||||
[chain chats uri cb]
|
||||
(let [{:keys [handler route-params query-params]} (match-uri uri)]
|
||||
(log/info "[router] uri " uri " matched " handler " with " route-params)
|
||||
(cond
|
||||
|
||||
@@ -253,36 +282,35 @@
|
||||
(and (= handler :user) (:user-id route-params))
|
||||
(match-contact-async chain route-params cb)
|
||||
|
||||
;; ;; NOTE: removed in `match-uri`, might need this in the future
|
||||
;; (= handler :private-chat)
|
||||
;; (match-private-chat-async chain route-params cb)
|
||||
;; NOTE: removed in `match-uri`, might need this in the future
|
||||
(= handler :private-chat)
|
||||
(match-private-chat-async chain route-params cb)
|
||||
|
||||
;; ;; NOTE: removed in `match-uri`, might need this in the future
|
||||
;; (= handler :group-chat)
|
||||
;; (cb (match-group-chat chats query-params))
|
||||
;; NOTE: removed in `match-uri`, might need this in the future
|
||||
(= handler :group-chat)
|
||||
(cb (match-group-chat chats query-params))
|
||||
|
||||
(validators/valid-public-key? uri)
|
||||
(match-contact-async chain {:user-id uri} cb)
|
||||
|
||||
;; ;; NOTE: removed in `match-uri`, might need this in the future
|
||||
;; (= handler :community-requests)
|
||||
;; (cb {:type handler :community-id (:community-id route-params)})
|
||||
;; NOTE: removed in `match-uri`, might need this in the future
|
||||
(= handler :community-requests)
|
||||
(cb {:type handler :community-id (:community-id route-params)})
|
||||
|
||||
(and (= handler :community) (:community-id route-params))
|
||||
(cb {:type (community-route-type route-params)
|
||||
:community-id (:community-id route-params)})
|
||||
|
||||
;; ;; TODO: jump to community overview for now, should jump to community channel
|
||||
;; (and (= handler :community-chat) (:chat-id route-params))
|
||||
;; (cb {:type handler :chat-id (:chat-id route-params)})
|
||||
(and (= handler :community-chat) (:community-channel-id route-params) (:community-id route-params))
|
||||
(match-community-channel-async route-params cb)
|
||||
|
||||
(and (= handler :community-chat) (:community-id route-params))
|
||||
(cb {:type (community-route-type route-params)
|
||||
:community-id (:community-id route-params)})
|
||||
|
||||
;; ;; NOTE: removed in `match-uri`, might need this in the future
|
||||
;; (= handler :wallet-account)
|
||||
;; (cb (match-wallet-account route-params))
|
||||
;; NOTE: removed in `match-uri`, might need this in the future
|
||||
(= handler :wallet-account)
|
||||
(cb (match-wallet-account route-params))
|
||||
|
||||
(address/address? uri)
|
||||
(cb (address->eip681 uri))
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
:query-params (when (= 3 (count expected)) (last expected))
|
||||
:uri uri})
|
||||
|
||||
"https://status.app/u#zQ3shwQPhRuDJSjVGVBnTjCdgXy5i9WQaeVPdGJD6yTarJQSj"
|
||||
[:user
|
||||
{:user-id "zQ3shwQPhRuDJSjVGVBnTjCdgXy5i9WQaeVPdGJD6yTarJQSj"}]
|
||||
"https://status.app/u/G10A4B0JdgwyRww90WXtnP1oNH1ZLQNM0yX0Ja9YyAMjrqSZIYINOHCbFhrnKRAcPGStPxCMJDSZlGCKzmZrJcimHY8BbcXlORrElv_BbQEegnMDPx1g9C5VVNl0fE4y#zQ3shwQPhRuDJSjVGVBnTjCdgXy5i9WQaeVPdGJD6yTarJQSj"
|
||||
[:user
|
||||
{:user-data
|
||||
@@ -31,23 +34,46 @@
|
||||
"G10A4B0JdgwyRww90WXtnP1oNH1ZLQNM0yX0Ja9YyAMjrqSZIYINOHCbFhrnKRAcPGStPxCMJDSZlGCKzmZrJcimHY8BbcXlORrElv_BbQEegnMDPx1g9C5VVNl0fE4y"
|
||||
:user-id "zQ3shwQPhRuDJSjVGVBnTjCdgXy5i9WQaeVPdGJD6yTarJQSj"}]
|
||||
|
||||
"status-app://u#zQ3shwQPhRuDJSjVGVBnTjCdgXy5i9WQaeVPdGJD6yTarJQSj"
|
||||
[:user
|
||||
{:user-id "zQ3shwQPhRuDJSjVGVBnTjCdgXy5i9WQaeVPdGJD6yTarJQSj"}]
|
||||
|
||||
"https://status.app/cc/G54AAKwObLdpiGjXnckYzRcOSq0QQAS_CURGfqVU42ceGHCObstUIknTTZDOKF3E8y2MSicncpO7fTskXnoACiPKeejvjtLTGWNxUhlT7fyQS7Jrr33UVHluxv_PLjV2ePGw5GQ33innzeK34pInIgUGs5RjdQifMVmURalxxQKwiuoY5zwIjixWWRHqjHM=#zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"
|
||||
[:community-chat
|
||||
{:chat-data
|
||||
{:community-data
|
||||
"G54AAKwObLdpiGjXnckYzRcOSq0QQAS_CURGfqVU42ceGHCObstUIknTTZDOKF3E8y2MSicncpO7fTskXnoACiPKeejvjtLTGWNxUhlT7fyQS7Jrr33UVHluxv_PLjV2ePGw5GQ33innzeK34pInIgUGs5RjdQifMVmURalxxQKwiuoY5zwIjixWWRHqjHM="
|
||||
:community-id "zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"}]
|
||||
|
||||
"status-app://cc/G54AAKwObLdpiGjXnckYzRcOSq0QQAS_CURGfqVU42ceGHCObstUIknTTZDOKF3E8y2MSicncpO7fTskXnoACiPKeejvjtLTGWNxUhlT7fyQS7Jrr33UVHluxv_PLjV2ePGw5GQ33innzeK34pInIgUGs5RjdQifMVmURalxxQKwiuoY5zwIjixWWRHqjHM=#zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"
|
||||
[:community-chat
|
||||
{:chat-data
|
||||
{:community-data
|
||||
"G54AAKwObLdpiGjXnckYzRcOSq0QQAS_CURGfqVU42ceGHCObstUIknTTZDOKF3E8y2MSicncpO7fTskXnoACiPKeejvjtLTGWNxUhlT7fyQS7Jrr33UVHluxv_PLjV2ePGw5GQ33innzeK34pInIgUGs5RjdQifMVmURalxxQKwiuoY5zwIjixWWRHqjHM="
|
||||
:community-id "zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"}]
|
||||
|
||||
"https://status.app/cc/c432709e-fc73-440d-bb67-cb3a0929dfda#zQ3shZL6dXiFCbDyxnXxwQa9v8QFC2q19subFtyxd7kVszMVo"
|
||||
[:community-chat
|
||||
{:community-data
|
||||
"c432709e-fc73-440d-bb67-cb3a0929dfda"
|
||||
:community-channel-id
|
||||
"c432709e-fc73-440d-bb67-cb3a0929dfda"
|
||||
:community-id "zQ3shZL6dXiFCbDyxnXxwQa9v8QFC2q19subFtyxd7kVszMVo"}]
|
||||
|
||||
"status-app://cc/c432709e-fc73-440d-bb67-cb3a0929dfda#zQ3shZL6dXiFCbDyxnXxwQa9v8QFC2q19subFtyxd7kVszMVo"
|
||||
[:community-chat
|
||||
{:community-data
|
||||
"c432709e-fc73-440d-bb67-cb3a0929dfda"
|
||||
:community-channel-id
|
||||
"c432709e-fc73-440d-bb67-cb3a0929dfda"
|
||||
:community-id "zQ3shZL6dXiFCbDyxnXxwQa9v8QFC2q19subFtyxd7kVszMVo"}]
|
||||
|
||||
"https://status.app/c/iyKACkQKB0Rvb2RsZXMSJ0NvbG9yaW5nIHRoZSB3b3JsZCB3aXRoIGpveSDigKIg4bSXIOKAohiYohsiByMxMzFEMkYqAwEhMwM=#zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"
|
||||
[:community
|
||||
{:community-data
|
||||
"iyKACkQKB0Rvb2RsZXMSJ0NvbG9yaW5nIHRoZSB3b3JsZCB3aXRoIGpveSDigKIg4bSXIOKAohiYohsiByMxMzFEMkYqAwEhMwM="
|
||||
:community-id "zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"}]
|
||||
"https://status.app/c#zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"
|
||||
[:community
|
||||
{:community-id "zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"}]
|
||||
|
||||
"status-app://c/iyKACkQKB0Rvb2RsZXMSJ0NvbG9yaW5nIHRoZSB3b3JsZCB3aXRoIGpveSDigKIg4bSXIOKAohiYohsiByMxMzFEMkYqAwEhMwM=#zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"
|
||||
[:community
|
||||
@@ -55,6 +81,10 @@
|
||||
"iyKACkQKB0Rvb2RsZXMSJ0NvbG9yaW5nIHRoZSB3b3JsZCB3aXRoIGpveSDigKIg4bSXIOKAohiYohsiByMxMzFEMkYqAwEhMwM="
|
||||
:community-id "zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"}]
|
||||
|
||||
"status-app://c#zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"
|
||||
[:community
|
||||
{:community-id "zQ3shYSHp7GoiXaauJMnDcjwU2yNjdzpXLosAWapPS4CFxc11"}]
|
||||
|
||||
"ethereum:0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7"
|
||||
[:ethereum {:address "0x89205a3a3b2a69de6dbf7f01ed13b2108b2c43e7"}]
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
(ns status-im.utils.universal-links.core
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[goog.string :as gstring]
|
||||
[native-module.core :as native-module]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.group-chats.core :as group-chats]
|
||||
@@ -9,7 +8,6 @@
|
||||
[status-im.router.core :as router]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.wallet.choose-recipient.core :as choose-recipient]
|
||||
[status-im2.constants :as constants]
|
||||
[status-im2.navigation.events :as navigation]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.ethereum.chain :as chain]
|
||||
@@ -27,27 +25,11 @@
|
||||
(def links
|
||||
{:private-chat "%s/p/%s"
|
||||
:community-requests "%s/cr/%s"
|
||||
:community "%s/c/%s"
|
||||
:community "%s/c#%s"
|
||||
:group-chat "%s/g/%s"
|
||||
:user "%s/u/%s"
|
||||
:user "%s/u#%s"
|
||||
:browse "%s/b/%s"})
|
||||
|
||||
(defn generate-link
|
||||
[link-type domain-type param]
|
||||
(gstring/format (get links link-type)
|
||||
(get domains domain-type)
|
||||
param))
|
||||
|
||||
(defn universal-link?
|
||||
[url]
|
||||
(boolean
|
||||
(re-matches constants/regx-universal-link url)))
|
||||
|
||||
(defn deep-link?
|
||||
[url]
|
||||
(boolean
|
||||
(re-matches constants/regx-deep-link url)))
|
||||
|
||||
(rf/defn handle-browse
|
||||
[cofx {:keys [url]}]
|
||||
(log/info "universal-links: handling browse" url)
|
||||
@@ -220,7 +202,6 @@
|
||||
#_(native-module/start-searching-for-local-pairing-peers
|
||||
#(log/info "[local-pairing] errors from local-pairing-preflight-outbound-check ->" %)))
|
||||
|
||||
|
||||
(defn finalize
|
||||
"Remove event listener for url"
|
||||
[]
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
(is (nil? (get-in (links/handle-url {:db db} "some-url")
|
||||
[:db :universal-links/url]))))
|
||||
(testing "Handle a custom string"
|
||||
(is (= (get-in (links/handle-url {:db db} "https://status.app/u/statuse2e")
|
||||
(is (= (get-in (links/handle-url {:db db} "https://status.app/u#statuse2e")
|
||||
[::router/handle-uri :uri])
|
||||
"https://status.app/u/statuse2e")))))))
|
||||
"https://status.app/u#statuse2e")))))))
|
||||
|
||||
(deftest url-event-listener
|
||||
(testing "the url is not nil"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
(def links
|
||||
{:private-chat "%s/p/%s"
|
||||
:user "%s/u/%s"
|
||||
:user "%s/u#%s"
|
||||
:browse "%s/b/%s"})
|
||||
|
||||
(defn universal-link?
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
(ns status-im2.data-store.wallet
|
||||
(ns status-im2.common.data-store.wallet
|
||||
(:require
|
||||
clojure.set))
|
||||
|
||||
@@ -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)]))
|
||||
|
||||
@@ -184,6 +184,7 @@
|
||||
(def regx-ens #"^(?=.{5,255}$)([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z]{2,}$")
|
||||
(def regx-address #"^0x[a-fA-F0-9]{40}$")
|
||||
(def regx-address-contains #"(?i)0x[a-fA-F0-9]{40}")
|
||||
(def regx-starts-with-uuid #"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}")
|
||||
|
||||
(def ^:const dapp-permission-contact-code "contact-code")
|
||||
(def ^:const dapp-permission-web3 "web3")
|
||||
|
||||
@@ -34,14 +34,14 @@
|
||||
(zipmap (repeat nil))))
|
||||
([kv] (-> (init-contact) (merge kv))))
|
||||
|
||||
(def url-regex #"^https?://status.app/u/(.+)")
|
||||
(def url-regex #"^https?://status.app/u(/([a-zA-Z0-9_-]+)(={0,2}))?#(.+)")
|
||||
|
||||
(defn ->id
|
||||
[{:keys [input] :as contact}]
|
||||
(let [trimmed-input (utils.string/safe-trim input)]
|
||||
(->> {:id (if (empty? trimmed-input)
|
||||
nil
|
||||
(if-some [[_ id] (re-matches url-regex trimmed-input)]
|
||||
(if-some [id (last (re-matches url-regex trimmed-input))]
|
||||
id
|
||||
trimmed-input))}
|
||||
(merge contact))))
|
||||
@@ -175,8 +175,11 @@
|
||||
(let [contact (get-in db [:contacts/new-identity])]
|
||||
(when (= (:input contact) input)
|
||||
(let [state (cond
|
||||
(or (string/includes? (:message err) "fallback failed")
|
||||
(string/includes? (:message err) "no such host"))
|
||||
(and (string? err) (string/includes? err "invalid public key"))
|
||||
{:state :invalid :msg :t/not-a-chatkey}
|
||||
(and (string? (:message err))
|
||||
(or (string/includes? (:message err) "fallback failed")
|
||||
(string/includes? (:message err) "no such host")))
|
||||
{:state :invalid :msg :t/lost-connection}
|
||||
:else {:state :invalid})]
|
||||
{:db (assoc db :contacts/new-identity (merge contact state))}))))
|
||||
|
||||
@@ -11,8 +11,9 @@
|
||||
(def ckey "zQ3shWj4WaBdf2zYKCkXe6PHxDxNTzZyid1i75879Ue9cX9gA")
|
||||
(def ens "esep")
|
||||
(def ens-stateofus-eth (str ens ".stateofus.eth"))
|
||||
(def link-ckey (str "https://status.app/u/" ckey))
|
||||
(def link-ens (str "https://status.app/u/" ens))
|
||||
(def link-ckey (str "https://status.app/u#" ckey))
|
||||
(def link-ckey-with-encoded-data (str "https://status.app/u/CwSACgcKBVBhdmxvAw==#" ckey))
|
||||
(def link-ens (str "https://status.app/u#" ens))
|
||||
|
||||
;;; unit tests (no app-db involved)
|
||||
|
||||
@@ -22,56 +23,61 @@
|
||||
:input i}))
|
||||
(events/init-contact e))
|
||||
|
||||
"" {:user-public-key user-ukey
|
||||
:input ""
|
||||
:type :empty
|
||||
:state :empty}
|
||||
"" {:user-public-key user-ukey
|
||||
:input ""
|
||||
:type :empty
|
||||
:state :empty}
|
||||
|
||||
" " {:user-public-key user-ukey
|
||||
:input " "
|
||||
:type :empty
|
||||
:state :empty}
|
||||
" " {:user-public-key user-ukey
|
||||
:input " "
|
||||
:type :empty
|
||||
:state :empty}
|
||||
|
||||
ukey {:user-public-key user-ukey
|
||||
:input ukey
|
||||
:id ukey
|
||||
:type :public-key
|
||||
:public-key ukey
|
||||
:state :invalid
|
||||
:msg :t/not-a-chatkey}
|
||||
ukey {:user-public-key user-ukey
|
||||
:input ukey
|
||||
:id ukey
|
||||
:type :public-key
|
||||
:public-key ukey
|
||||
:state :invalid
|
||||
:msg :t/not-a-chatkey}
|
||||
|
||||
ens {:user-public-key user-ukey
|
||||
:input ens
|
||||
:id ens
|
||||
:type :ens
|
||||
:ens ens-stateofus-eth
|
||||
:state :resolve-ens}
|
||||
ens {:user-public-key user-ukey
|
||||
:input ens
|
||||
:id ens
|
||||
:type :ens
|
||||
:ens ens-stateofus-eth
|
||||
:state :resolve-ens}
|
||||
|
||||
(str " " ens) {:user-public-key user-ukey
|
||||
:input (str " " ens)
|
||||
:id ens
|
||||
:type :ens
|
||||
:ens ens-stateofus-eth
|
||||
:state :resolve-ens}
|
||||
(str " " ens) {:user-public-key user-ukey
|
||||
:input (str " " ens)
|
||||
:id ens
|
||||
:type :ens
|
||||
:ens ens-stateofus-eth
|
||||
:state :resolve-ens}
|
||||
|
||||
ckey {:user-public-key user-ukey
|
||||
:input ckey
|
||||
:id ckey
|
||||
:type :compressed-key
|
||||
:state :decompress-key}
|
||||
ckey {:user-public-key user-ukey
|
||||
:input ckey
|
||||
:id ckey
|
||||
:type :compressed-key
|
||||
:state :decompress-key}
|
||||
|
||||
link-ckey {:user-public-key user-ukey
|
||||
:input link-ckey
|
||||
:id ckey
|
||||
:type :compressed-key
|
||||
:state :decompress-key}
|
||||
link-ckey {:user-public-key user-ukey
|
||||
:input link-ckey
|
||||
:id ckey
|
||||
:type :compressed-key
|
||||
:state :decompress-key}
|
||||
link-ckey-with-encoded-data {:user-public-key user-ukey
|
||||
:input link-ckey-with-encoded-data
|
||||
:id ckey
|
||||
:type :compressed-key
|
||||
:state :decompress-key}
|
||||
|
||||
link-ens {:user-public-key user-ukey
|
||||
:input link-ens
|
||||
:id ens
|
||||
:type :ens
|
||||
:ens ens-stateofus-eth
|
||||
:state :resolve-ens}))
|
||||
link-ens {:user-public-key user-ukey
|
||||
:input link-ens
|
||||
:id ens
|
||||
:type :ens
|
||||
:ens ens-stateofus-eth
|
||||
:state :resolve-ens}))
|
||||
|
||||
;;; event handler tests (no callbacks)
|
||||
|
||||
|
||||
@@ -80,11 +80,10 @@
|
||||
:button (when empty-input?
|
||||
{:on-press paste-on-input
|
||||
:text (i18n/label :t/paste)})
|
||||
;; NOTE: `scanned` has priority over `@input-value`, we clean it when the input
|
||||
;; is updated so that it's `nil` and `@input-value` is shown.
|
||||
;; To fastly clean it, we use `dispatch-sync`.
|
||||
;; This call could be avoided if `::qr-scanner/scan-code` were able to receive a
|
||||
;; callback function, not only a re-frame event as callback.
|
||||
;; NOTE: `scanned` has priority over `@input-value`, we clean it when the input is updated
|
||||
;; so that it's `nil` and `@input-value` is shown. To fastly clean it, we use
|
||||
;; `dispatch-sync`. This call could be avoided if `::qr-scanner/scan-code` were able to
|
||||
;; receive a callback function, not only a re-frame event as callback.
|
||||
:value (or scanned @input-value)
|
||||
:on-change-text (fn [new-text]
|
||||
(reset! input-value new-text)
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn send-message
|
||||
"Minimize composer, animate-out background overlay, clear input and flush state"
|
||||
[{:keys [sending-images? sending-links?]}
|
||||
{:keys [text-value focused? maximized?]}
|
||||
{:keys [height saved-height last-height opacity background-y container-opacity]}
|
||||
@@ -47,6 +48,7 @@
|
||||
(let [{:keys [text-value]} state
|
||||
customization-color (rf/sub [:profile/customization-color])]
|
||||
(rn/use-effect (fn []
|
||||
;; Handle send button opacity animation and z-index when input content changes
|
||||
(if (or (seq @text-value) images?)
|
||||
(when (or (not= @z-index 1) (not= (reanimated/get-shared-value btn-opacity) 1))
|
||||
(reset! z-index 1)
|
||||
@@ -172,11 +174,12 @@
|
||||
[edit]
|
||||
(let [images-count (count (vals (rf/sub [:chats/sending-image])))]
|
||||
[quo/composer-button
|
||||
{:on-press (if edit
|
||||
#(js/alert "This feature is temporarily unavailable in edit mode.")
|
||||
#(go-to-camera images-count))
|
||||
:icon :i/camera
|
||||
:container-style {:margin-right 12}}]))
|
||||
{:on-press (if edit
|
||||
#(js/alert "This feature is temporarily unavailable in edit mode.")
|
||||
#(go-to-camera images-count))
|
||||
:accessibility-label :camera-button
|
||||
:icon :i/camera
|
||||
:container-style {:margin-right 12}}]))
|
||||
|
||||
|
||||
(defn open-photo-selector
|
||||
|
||||
@@ -17,9 +17,15 @@
|
||||
|
||||
(def ^:const empty-opacity 0.7)
|
||||
|
||||
(def ^:const images-container-height 76)
|
||||
(def ^:const images-padding-top 12)
|
||||
(def ^:const images-padding-bottom 8)
|
||||
(def ^:const images-container-height
|
||||
(+ actions-container-height images-padding-top images-padding-bottom))
|
||||
|
||||
(def ^:const links-container-height 76)
|
||||
(def ^:const links-padding-top 12)
|
||||
(def ^:const links-padding-bottom 8)
|
||||
(def ^:const links-container-height
|
||||
(+ actions-container-height links-padding-top links-padding-bottom))
|
||||
|
||||
(def ^:const reply-container-height 32)
|
||||
|
||||
|
||||
@@ -168,20 +168,13 @@
|
||||
(defn link-previews
|
||||
[{:keys [sending-links?]}
|
||||
{:keys [text-value maximized?]}
|
||||
{:keys [height saved-height last-height]}
|
||||
{:keys [height saved-height]}
|
||||
{:keys [link-previews?]}]
|
||||
(rn/use-effect
|
||||
(fn []
|
||||
(if-not @maximized?
|
||||
(let [value (if link-previews?
|
||||
constants/links-container-height
|
||||
(- constants/links-container-height))]
|
||||
(when (not= @sending-links? link-previews?)
|
||||
(reanimated/animate height (+ (reanimated/get-shared-value saved-height) value))
|
||||
(reanimated/set-shared-value saved-height
|
||||
(+ (reanimated/get-shared-value saved-height) value))
|
||||
(reanimated/set-shared-value last-height
|
||||
(+ (reanimated/get-shared-value last-height) value))))
|
||||
(when (not= @sending-links? link-previews?)
|
||||
(reanimated/animate height (reanimated/get-shared-value saved-height)))
|
||||
(let [curr-text @text-value]
|
||||
(reset! text-value (str @text-value " "))
|
||||
(js/setTimeout #(reset! text-value curr-text) 100)))
|
||||
@@ -191,7 +184,7 @@
|
||||
(defn use-images
|
||||
[{:keys [sending-images? input-ref]}
|
||||
{:keys [text-value maximized?]}
|
||||
{:keys [container-opacity height saved-height last-height]}
|
||||
{:keys [container-opacity height saved-height]}
|
||||
{:keys [images]}]
|
||||
(rn/use-effect
|
||||
(fn []
|
||||
@@ -200,15 +193,8 @@
|
||||
(when (and (not @sending-images?) (seq images) @input-ref)
|
||||
(.focus ^js @input-ref))
|
||||
(if-not @maximized?
|
||||
(let [value (if (seq images)
|
||||
constants/images-container-height
|
||||
(- constants/images-container-height))]
|
||||
(when (not= @sending-images? (boolean (seq images)))
|
||||
(reanimated/animate height (+ (reanimated/get-shared-value saved-height) value))
|
||||
(reanimated/set-shared-value saved-height
|
||||
(+ (reanimated/get-shared-value saved-height) value))
|
||||
(reanimated/set-shared-value last-height
|
||||
(+ (reanimated/get-shared-value last-height) value))))
|
||||
(when (not= @sending-images? (boolean (seq images)))
|
||||
(reanimated/animate height (reanimated/get-shared-value saved-height)))
|
||||
(let [curr-text @text-value]
|
||||
(reset! text-value (str @text-value " "))
|
||||
(js/setTimeout #(reset! text-value curr-text) 100)))
|
||||
|
||||
@@ -59,7 +59,6 @@
|
||||
[{:keys [input-ref] :as props}
|
||||
{:keys [gesture-enabled?] :as state}
|
||||
{:keys [height saved-height last-height opacity background-y container-opacity] :as animations}
|
||||
{:keys [images link-previews?]}
|
||||
{:keys [max-height lines] :as dimensions}
|
||||
keyboard-shown]
|
||||
(let [expanding? (atom true)
|
||||
@@ -82,7 +81,7 @@
|
||||
(gesture/on-update
|
||||
(fn [event]
|
||||
(let [translation (oops/oget event "translationY")
|
||||
min-height (utils/get-min-height lines images link-previews?)
|
||||
min-height (utils/get-min-height lines)
|
||||
new-height (- (reanimated/get-shared-value saved-height) translation)
|
||||
bounded-height (utils.number/value-in-range new-height min-height max-height)]
|
||||
(when keyboard-shown
|
||||
|
||||
@@ -4,24 +4,27 @@
|
||||
[react-native.reanimated :as reanimated]
|
||||
[status-im2.contexts.chat.composer.constants :as constants]))
|
||||
|
||||
(defn top-gradient-style
|
||||
[opacity z-index]
|
||||
(defn- top-gradient-style
|
||||
[opacity z-index showing-extra-space?]
|
||||
(reanimated/apply-animations-to-style
|
||||
{:opacity opacity}
|
||||
{:height 80
|
||||
{:height (when (pos-int? z-index) 80)
|
||||
:position :absolute
|
||||
:z-index z-index
|
||||
:top 0
|
||||
:top (+ constants/bar-container-height
|
||||
(if showing-extra-space?
|
||||
constants/edit-container-height
|
||||
0))
|
||||
:left 0
|
||||
:right 0}))
|
||||
|
||||
(defn top-gradient
|
||||
[opacity z-index]
|
||||
[opacity z-index showing-extra-space?]
|
||||
{:colors [(colors/theme-colors colors/white-opa-0 colors/neutral-95-opa-0)
|
||||
(colors/theme-colors colors/white colors/neutral-95)]
|
||||
:start {:x 0 :y 1}
|
||||
:end {:x 0 :y 0}
|
||||
:style (top-gradient-style opacity z-index)})
|
||||
:style (top-gradient-style opacity z-index showing-extra-space?)})
|
||||
|
||||
(def bottom-gradient-style
|
||||
{:height constants/line-height
|
||||
|
||||
@@ -3,22 +3,25 @@
|
||||
[react-native.core :as rn]
|
||||
[react-native.linear-gradient :as linear-gradient]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[status-im2.contexts.chat.composer.gradients.style :as style]))
|
||||
|
||||
[status-im2.contexts.chat.composer.gradients.style :as style]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn f-view
|
||||
[{:keys [input-ref]}
|
||||
{:keys [gradient-z-index]}
|
||||
{:keys [gradient-opacity]}
|
||||
show-bottom-gradient?]
|
||||
[:<>
|
||||
[reanimated/linear-gradient (style/top-gradient gradient-opacity @gradient-z-index)]
|
||||
(when show-bottom-gradient?
|
||||
[rn/touchable-without-feedback
|
||||
{:on-press #(when @input-ref (.focus ^js @input-ref))
|
||||
:accessibility-label :bottom-gradient}
|
||||
[linear-gradient/linear-gradient (style/bottom-gradient)]])])
|
||||
(let [showing-extra-space? (boolean (or (rf/sub [:chats/edit-message])
|
||||
(rf/sub [:chats/reply-message])))]
|
||||
[:<>
|
||||
[reanimated/linear-gradient
|
||||
(style/top-gradient gradient-opacity @gradient-z-index showing-extra-space?)]
|
||||
(when show-bottom-gradient?
|
||||
[rn/pressable
|
||||
{:on-press #(when @input-ref (.focus ^js @input-ref))
|
||||
:accessibility-label :bottom-gradient}
|
||||
[linear-gradient/linear-gradient (style/bottom-gradient)]])]))
|
||||
|
||||
(defn view
|
||||
[props state animations show-bottom-gradient?]
|
||||
[:f> f-view props state animations show-bottom-gradient?])
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn focus
|
||||
"Animate to the `saved-height`, display background-overlay if needed, and set cursor position"
|
||||
[{:keys [input-ref] :as props}
|
||||
{:keys [text-value focused? lock-selection? saved-cursor-position]}
|
||||
{:keys [height saved-height last-height opacity background-y container-opacity]
|
||||
@@ -36,6 +37,7 @@
|
||||
(reset! show-floating-scroll-down-button? false))
|
||||
|
||||
(defn blur
|
||||
"Save the current height, minimize the composer, animate-out the background, and save cursor position"
|
||||
[{:keys [text-value focused? lock-selection? cursor-position saved-cursor-position gradient-z-index
|
||||
maximized? recording?]}
|
||||
{:keys [height saved-height last-height gradient-opacity container-opacity opacity background-y]}
|
||||
@@ -43,14 +45,12 @@
|
||||
{:keys [images link-previews? reply]}]
|
||||
(when-not @recording?
|
||||
(let [lines (utils/calc-lines (- @content-height constants/extra-content-offset))
|
||||
min-height (utils/get-min-height lines images link-previews?)
|
||||
min-height (utils/get-min-height lines)
|
||||
reopen-height (utils/calc-reopen-height text-value
|
||||
min-height
|
||||
max-height
|
||||
content-height
|
||||
saved-height
|
||||
images
|
||||
link-previews?)]
|
||||
saved-height)]
|
||||
(reset! focused? false)
|
||||
(rf/dispatch [:chat.ui/set-input-focused false])
|
||||
(reanimated/set-shared-value last-height reopen-height)
|
||||
@@ -69,24 +69,23 @@
|
||||
(rf/dispatch [:chat.ui/set-input-maximized false])))))
|
||||
|
||||
(defn content-size-change
|
||||
"Save new text height, expand composer if possible, show background overlay if needed"
|
||||
[event
|
||||
{:keys [maximized? lock-layout? text-value]}
|
||||
{:keys [height saved-height opacity background-y]}
|
||||
{:keys [images link-previews?]}
|
||||
{:keys [content-height window-height max-height]}
|
||||
keyboard-shown]
|
||||
(when keyboard-shown
|
||||
(let [event-size (oops/oget event "nativeEvent.contentSize.height")
|
||||
content-size (+ event-size constants/extra-content-offset)
|
||||
lines (utils/calc-lines event-size)
|
||||
content-size (if (or (= lines 1) (empty? @text-value))
|
||||
constants/input-height
|
||||
(if (= lines 2) constants/multiline-minimized-height content-size))
|
||||
new-height (utils.number/value-in-range content-size
|
||||
constants/input-height
|
||||
max-height)
|
||||
bottom-content-height (utils/calc-bottom-content-height images link-previews?)
|
||||
new-height (min (+ new-height bottom-content-height) max-height)]
|
||||
(let [event-size (oops/oget event "nativeEvent.contentSize.height")
|
||||
content-size (+ event-size constants/extra-content-offset)
|
||||
lines (utils/calc-lines event-size)
|
||||
content-size (if (or (= lines 1) (empty? @text-value))
|
||||
constants/input-height
|
||||
(if (= lines 2) constants/multiline-minimized-height content-size))
|
||||
new-height (utils.number/value-in-range content-size
|
||||
constants/input-height
|
||||
max-height)
|
||||
new-height (min new-height max-height)]
|
||||
(reset! content-height content-size)
|
||||
(when (utils/update-height? content-size height max-height maximized?)
|
||||
(reanimated/animate height new-height)
|
||||
@@ -105,6 +104,7 @@
|
||||
(reset! lock-layout? (> lines 2)))))
|
||||
|
||||
(defn scroll
|
||||
"Hide or show top gradient while scroll"
|
||||
[event
|
||||
{:keys [scroll-y]}
|
||||
{:keys [gradient-z-index focused?]}
|
||||
@@ -120,12 +120,10 @@
|
||||
(js/setTimeout #(reset! gradient-z-index 0) 300))))
|
||||
|
||||
(defn change-text
|
||||
"Update `text-value`, update cursor selection, find links, find mentions"
|
||||
[text
|
||||
{:keys [input-ref record-reset-fn]}
|
||||
{:keys [text-value cursor-position recording?]}]
|
||||
(debounce/debounce-and-dispatch [:link-preview/unfurl-urls text]
|
||||
constants/unfurl-debounce-ms)
|
||||
|
||||
(reset! text-value text)
|
||||
(reagent/next-tick #(when @input-ref
|
||||
(.setNativeProps ^js @input-ref
|
||||
@@ -135,11 +133,14 @@
|
||||
(@record-reset-fn)
|
||||
(reset! recording? false))
|
||||
(rf/dispatch [:chat.ui/set-chat-input-text text])
|
||||
(debounce/debounce-and-dispatch [:link-preview/unfurl-urls text]
|
||||
constants/unfurl-debounce-ms)
|
||||
(if (string/ends-with? text "@")
|
||||
(rf/dispatch [:mention/on-change-text text])
|
||||
(debounce/debounce-and-dispatch [:mention/on-change-text text] 300)))
|
||||
|
||||
(defn selection-change
|
||||
"A method that handles our custom selector for `B I U`"
|
||||
[event
|
||||
{:keys [input-ref selection-event selection-manager]}
|
||||
{:keys [lock-selection? cursor-position first-level? menu-items]}]
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
(ns status-im2.contexts.chat.composer.images.style
|
||||
(:require
|
||||
[quo.foundations.colors :as colors]))
|
||||
[quo.foundations.colors :as colors]
|
||||
[status-im2.contexts.chat.composer.constants :as constants]))
|
||||
|
||||
(def image-container
|
||||
{:padding-top 12
|
||||
:padding-bottom 8
|
||||
{:padding-top constants/images-padding-top
|
||||
:padding-bottom constants/images-padding-bottom
|
||||
:padding-right 12})
|
||||
|
||||
(defn remove-photo-container
|
||||
@@ -32,4 +33,3 @@
|
||||
{:width 56
|
||||
:height 56
|
||||
:border-radius 12})
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
(async-storage/set-item! :kb-default-height (str height)))))
|
||||
|
||||
(defn handle-emoji-kb-ios
|
||||
"Opening emoji KB on iOS while maximized will cause a flicker up and down. This method handles that."
|
||||
[event
|
||||
{:keys [emoji-kb-extra-height]}
|
||||
{:keys [text-value]}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
(ns status-im2.contexts.chat.composer.link-preview.style)
|
||||
(ns status-im2.contexts.chat.composer.link-preview.style
|
||||
(:require [status-im2.contexts.chat.composer.constants :as constants]))
|
||||
|
||||
(def padding-horizontal 20)
|
||||
(def preview-list-padding-top 12)
|
||||
(def preview-list-padding-bottom 8)
|
||||
(def preview-height 56)
|
||||
|
||||
(def preview-list
|
||||
{:padding-top preview-list-padding-top
|
||||
:padding-bottom preview-list-padding-bottom
|
||||
{:padding-top constants/links-padding-top
|
||||
:padding-bottom constants/links-padding-bottom
|
||||
:margin-horizontal (- padding-horizontal)
|
||||
;; Keep a high index, otherwise the parent gesture detector used by the
|
||||
;; composer grabs the initiating gesture event.
|
||||
|
||||
@@ -31,18 +31,19 @@
|
||||
|
||||
(defn- f-view
|
||||
[suggestions-atom props state animations max-height cursor-pos images link-previews? reply edit]
|
||||
(let [suggestions (rf/sub [:chat/mention-suggestions])
|
||||
opacity (reanimated/use-shared-value (if (seq suggestions) 1 0))
|
||||
size (count suggestions)
|
||||
data {:keyboard-height @(:kb-height state)
|
||||
:insets (safe-area/get-insets)
|
||||
:curr-height (reanimated/get-shared-value (:height animations))
|
||||
:window-height (:height (rn/get-window))
|
||||
:images images
|
||||
:link-previews? link-previews?
|
||||
:reply reply
|
||||
:edit edit}
|
||||
mentions-pos (utils/calc-suggestions-position cursor-pos max-height size state data)]
|
||||
(let [suggestions (rf/sub [:chat/mention-suggestions])
|
||||
opacity (reanimated/use-shared-value (if (seq suggestions) 1 0))
|
||||
size (count suggestions)
|
||||
data {:keyboard-height @(:kb-height state)
|
||||
:insets (safe-area/get-insets)
|
||||
:curr-height (reanimated/get-shared-value (:height animations))
|
||||
:window-height (:height (rn/get-window))
|
||||
:images images
|
||||
:link-previews? link-previews?
|
||||
:reply reply
|
||||
:edit edit}
|
||||
mentions-pos
|
||||
(utils/calc-suggestions-position cursor-pos max-height size state data images link-previews?)]
|
||||
(rn/use-effect
|
||||
(fn []
|
||||
(if (seq suggestions)
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
[react-native.reanimated :as reanimated]
|
||||
[status-im2.contexts.chat.composer.constants :as constants]))
|
||||
|
||||
(def border-top-radius 20)
|
||||
|
||||
(defn shadow
|
||||
[focused? theme]
|
||||
(if platform/ios?
|
||||
@@ -27,8 +29,8 @@
|
||||
(reanimated/apply-animations-to-style
|
||||
{:opacity container-opacity}
|
||||
(merge
|
||||
{:border-top-left-radius 20
|
||||
:border-top-right-radius 20
|
||||
{:border-top-left-radius border-top-radius
|
||||
:border-top-right-radius border-top-radius
|
||||
:padding-horizontal 20
|
||||
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)
|
||||
:z-index 3
|
||||
@@ -55,7 +57,8 @@
|
||||
[height max-height]
|
||||
(reanimated/apply-animations-to-style
|
||||
{:height height}
|
||||
{:max-height max-height}))
|
||||
{:max-height max-height
|
||||
:z-index 1}))
|
||||
|
||||
(defn input-view
|
||||
[{:keys [recording?]}]
|
||||
@@ -68,19 +71,17 @@
|
||||
(defn input-text
|
||||
[{:keys [saved-emoji-kb-extra-height]}
|
||||
{:keys [focused? maximized?]}
|
||||
{:keys [link-previews? images]}
|
||||
{:keys [max-height theme]}]
|
||||
(merge typography/paragraph-1
|
||||
{:color (colors/theme-colors :black :white theme)
|
||||
:text-align-vertical :top
|
||||
:position (if @saved-emoji-kb-extra-height :relative :absolute)
|
||||
:top 0
|
||||
:left 0
|
||||
:right (when (or focused? platform/ios?) 0)
|
||||
:max-height (- max-height
|
||||
(if link-previews? constants/links-container-height 0)
|
||||
(if (seq images) constants/images-container-height 0))
|
||||
:padding-bottom (when @maximized? 0)}))
|
||||
(assoc typography/paragraph-1
|
||||
:color (colors/theme-colors :black :white theme)
|
||||
:text-align-vertical :top
|
||||
:position (if @saved-emoji-kb-extra-height :relative :absolute)
|
||||
:top 0
|
||||
:left 0
|
||||
:right (when (or focused? platform/ios?) 0)
|
||||
:max-height max-height
|
||||
:padding-bottom (when @maximized? 0)))
|
||||
|
||||
(defn background
|
||||
[opacity background-y window-height]
|
||||
(reanimated/apply-animations-to-style
|
||||
@@ -102,8 +103,8 @@
|
||||
:left 0
|
||||
:right 0
|
||||
:bottom 0
|
||||
:border-top-right-radius 20
|
||||
:border-top-left-radius 20
|
||||
:border-top-right-radius border-top-radius
|
||||
:border-top-left-radius border-top-radius
|
||||
:overflow :hidden}))
|
||||
|
||||
(defn blur-view
|
||||
|
||||
@@ -57,43 +57,40 @@
|
||||
|
||||
(defn calc-top-content-height
|
||||
[reply? edit?]
|
||||
(let [height (if reply? constants/reply-container-height 0)
|
||||
height (if edit? (+ height constants/edit-container-height) height)]
|
||||
height))
|
||||
(cond-> 0
|
||||
reply? (+ constants/reply-container-height)
|
||||
edit? (+ constants/edit-container-height)))
|
||||
|
||||
(defn calc-bottom-content-height
|
||||
[images link-previews?]
|
||||
(let [height (if (seq images) constants/images-container-height 0)
|
||||
height (if link-previews? (+ height constants/links-container-height) height)]
|
||||
height))
|
||||
(cond-> 0
|
||||
(seq images) (+ constants/images-container-height)
|
||||
link-previews? (+ constants/links-container-height)))
|
||||
|
||||
(defn calc-reopen-height
|
||||
[text-value min-height max-height content-height saved-height images link-previews?]
|
||||
[text-value min-height max-height content-height saved-height]
|
||||
(if (empty? @text-value)
|
||||
min-height
|
||||
(let [bottom-content-height (calc-bottom-content-height images link-previews?)
|
||||
input-height (min (+ @content-height bottom-content-height)
|
||||
(reanimated/get-shared-value saved-height))]
|
||||
(let [input-height (min @content-height
|
||||
(reanimated/get-shared-value saved-height))]
|
||||
(min max-height input-height))))
|
||||
|
||||
(defn get-min-height
|
||||
[lines images link-previews?]
|
||||
(let [input-height (if (> lines 1)
|
||||
constants/multiline-minimized-height
|
||||
constants/input-height)
|
||||
bottom-content-height (calc-bottom-content-height images link-previews?)]
|
||||
(+ input-height bottom-content-height)))
|
||||
[lines]
|
||||
(if (> lines 1)
|
||||
constants/multiline-minimized-height
|
||||
constants/input-height))
|
||||
|
||||
(defn calc-max-height
|
||||
[{:keys [reply edit]} window-height kb-height insets]
|
||||
(let [margin-top (if platform/ios? (:top insets) (+ 10 (:top insets)))
|
||||
max-height (- window-height
|
||||
margin-top
|
||||
kb-height
|
||||
constants/bar-container-height
|
||||
constants/actions-container-height)
|
||||
max-height (- max-height (calc-top-content-height reply edit))]
|
||||
max-height))
|
||||
[{:keys [reply edit images link-previews?]} window-height kb-height insets]
|
||||
(let [margin-top (if platform/ios? (:top insets) (+ 10 (:top insets)))]
|
||||
(- window-height
|
||||
margin-top
|
||||
kb-height
|
||||
constants/bar-container-height
|
||||
constants/actions-container-height
|
||||
(calc-top-content-height reply edit)
|
||||
(calc-bottom-content-height images link-previews?))))
|
||||
|
||||
(defn empty-input?
|
||||
[text images link-previews? reply? audio?]
|
||||
@@ -124,21 +121,12 @@
|
||||
sub-text-lines-in-view (- sub-text-lines scrolled-lines)]
|
||||
(* sub-text-lines-in-view constants/line-height)))
|
||||
|
||||
(defn calc-shell-neg-y
|
||||
[insets maximized? extra-height]
|
||||
(let [padding 12
|
||||
neg-y (if @maximized? -50 0)]
|
||||
(- (+ constants/bar-container-height
|
||||
constants/actions-container-height
|
||||
(:bottom insets)
|
||||
padding
|
||||
extra-height
|
||||
neg-y))))
|
||||
|
||||
(defn calc-suggestions-position
|
||||
[cursor-pos max-height size
|
||||
{:keys [maximized?]}
|
||||
{:keys [insets curr-height window-height keyboard-height reply edit]}]
|
||||
{:keys [insets curr-height window-height keyboard-height reply edit]}
|
||||
images
|
||||
link-previews?]
|
||||
(let [base (+ constants/composer-default-height (:bottom insets) 8)
|
||||
base (+ base (- curr-height constants/input-height))
|
||||
base (+ base (calc-top-content-height reply edit))
|
||||
@@ -152,10 +140,11 @@
|
||||
(+ constants/actions-container-height (:bottom insets))
|
||||
(+ constants/actions-container-height (:bottom insets) (- max-height cursor-pos) 18))
|
||||
(if (< (+ base container-height) view-height)
|
||||
base
|
||||
(let [bottom-content-height (calc-bottom-content-height images link-previews?)]
|
||||
(+ base bottom-content-height))
|
||||
(+ constants/actions-container-height (:bottom insets) (- curr-height cursor-pos) 18)))))
|
||||
|
||||
(defn init-props
|
||||
(defn init-non-reactive-state
|
||||
[]
|
||||
{:input-ref (atom nil)
|
||||
:selectable-input-ref (atom nil)
|
||||
@@ -171,7 +160,7 @@
|
||||
:selection-event (atom nil)
|
||||
:selection-manager (rn/selectable-text-input-manager)})
|
||||
|
||||
(defn init-state
|
||||
(defn init-reactive-state
|
||||
[]
|
||||
{:text-value (reagent/atom "")
|
||||
:cursor-position (reagent/atom 0)
|
||||
|
||||
@@ -36,15 +36,19 @@
|
||||
theme]} props state]
|
||||
(let [{:keys [chat-screen-loaded?]
|
||||
:as subscriptions} (utils/init-subs)
|
||||
content-height (reagent/atom (or (:input-content-height
|
||||
content-height (reagent/atom (or (:input-content-height ; Actual text height
|
||||
subscriptions)
|
||||
constants/input-height))
|
||||
{:keys [keyboard-shown]} (hooks/use-keyboard)
|
||||
max-height (utils/calc-max-height subscriptions
|
||||
max-height (utils/calc-max-height subscriptions ; Max allowed height for the
|
||||
; composer view
|
||||
window-height
|
||||
@(:kb-height state)
|
||||
insets)
|
||||
lines (utils/calc-lines (- @content-height constants/extra-content-offset))
|
||||
lines (utils/calc-lines (- @content-height constants/extra-content-offset)) ; Current
|
||||
; lines
|
||||
; count
|
||||
;; Maximum number of lines that can be displayed when composer in maximized
|
||||
max-lines (utils/calc-lines max-height)
|
||||
animations (utils/init-animations
|
||||
subscriptions
|
||||
@@ -59,6 +63,7 @@
|
||||
:lines lines
|
||||
:max-lines max-lines}
|
||||
show-bottom-gradient? (utils/show-bottom-gradient? state dimensions)
|
||||
;; Cursor position, needed to determine where to display the mentions view
|
||||
cursor-pos (utils/cursor-y-position-relative-to-container
|
||||
props
|
||||
state)]
|
||||
@@ -86,7 +91,7 @@
|
||||
[sub-view/shell-button state scroll-to-bottom-fn show-floating-scroll-down-button?]
|
||||
[gesture/gesture-detector
|
||||
{:gesture
|
||||
(drag-gesture/drag-gesture props state animations subscriptions dimensions keyboard-shown)}
|
||||
(drag-gesture/drag-gesture props state animations dimensions keyboard-shown)}
|
||||
[reanimated/view
|
||||
{:style (style/sheet-container insets state animations theme)
|
||||
:on-layout #(handler/layout % state blur-height)}
|
||||
@@ -97,7 +102,9 @@
|
||||
[edit/view state]])
|
||||
[reanimated/touchable-opacity
|
||||
{:active-opacity 1
|
||||
:on-press (when @(:input-ref props) #(.focus ^js @(:input-ref props)))
|
||||
:on-press (fn []
|
||||
(when-let [ref @(:input-ref props)]
|
||||
(.focus ^js ref)))
|
||||
:style (style/input-container (:height animations) max-height)
|
||||
:accessibility-label :message-input-container}
|
||||
[rn/selectable-text-input
|
||||
@@ -113,7 +120,6 @@
|
||||
:on-content-size-change #(handler/content-size-change %
|
||||
state
|
||||
animations
|
||||
subscriptions
|
||||
dimensions
|
||||
(or keyboard-shown
|
||||
(:edit subscriptions)))
|
||||
@@ -122,23 +128,21 @@
|
||||
:on-selection-change #(handler/selection-change % props state)
|
||||
:on-selection #(selection/on-selection % props state)
|
||||
:keyboard-appearance (quo.theme/theme-value :light :dark)
|
||||
:max-height max-height
|
||||
:max-font-size-multiplier 1
|
||||
:multiline true
|
||||
:placeholder (i18n/label :t/type-something)
|
||||
:placeholder-text-color (colors/theme-colors colors/neutral-30 colors/neutral-50)
|
||||
:style (style/input-text props
|
||||
state
|
||||
subscriptions
|
||||
{:max-height max-height
|
||||
:theme theme})
|
||||
:max-length constants/max-text-size
|
||||
:accessibility-label :chat-message-input}]]
|
||||
(when chat-screen-loaded?
|
||||
[:<>
|
||||
[gradients/view props state animations show-bottom-gradient?]
|
||||
[link-preview/view]
|
||||
[images/images-list]])]
|
||||
:accessibility-label :chat-message-input}]]]
|
||||
(when chat-screen-loaded?
|
||||
[:<>
|
||||
[gradients/view props state animations show-bottom-gradient?]
|
||||
[link-preview/view]
|
||||
[images/images-list]])
|
||||
[:f> actions/view props state animations window-height insets scroll-to-bottom-fn
|
||||
subscriptions]]]]]))
|
||||
|
||||
@@ -147,7 +151,7 @@
|
||||
(let [window-height (:height (rn/get-window))
|
||||
theme (quo.theme/use-theme-value)
|
||||
opacity (reanimated/use-shared-value 0)
|
||||
background-y (reanimated/use-shared-value (- window-height))
|
||||
background-y (reanimated/use-shared-value (- window-height)) ; Y position of background overlay
|
||||
blur-height (reanimated/use-shared-value (+ constants/composer-default-height
|
||||
(:bottom insets)))
|
||||
extra-params {:insets insets
|
||||
@@ -158,10 +162,11 @@
|
||||
:opacity opacity
|
||||
:background-y background-y
|
||||
:theme theme}
|
||||
props (utils/init-props)
|
||||
state (utils/init-state)]
|
||||
props (utils/init-non-reactive-state)
|
||||
state (utils/init-reactive-state)]
|
||||
[rn/view (when platform/ios? {:style {:z-index 1}})
|
||||
[reanimated/view {:style (style/background opacity background-y window-height)}]
|
||||
[reanimated/view {:style (style/background opacity background-y window-height)}] ; background
|
||||
; overlay
|
||||
[sub-view/blur-view
|
||||
{:layout-height blur-height
|
||||
:focused? (:focused? state)
|
||||
|
||||
@@ -49,15 +49,15 @@
|
||||
[item index _ render-data]
|
||||
[:f> f-small-image item index _ render-data])
|
||||
|
||||
|
||||
(defn bottom-view
|
||||
[messages index scroll-index insets animations derived item-width props state]
|
||||
[messages index scroll-index insets animations derived item-width props state transparent?]
|
||||
(let [padding-horizontal (- (/ item-width 2) (/ c/focused-image-size 2))]
|
||||
[reanimated/linear-gradient
|
||||
{:colors [colors/neutral-100-opa-100 colors/neutral-100-opa-50]
|
||||
:start {:x 0 :y 1}
|
||||
:end {:x 0 :y 0}
|
||||
:style (style/gradient-container insets animations derived)}
|
||||
{:colors [colors/neutral-100-opa-100 colors/neutral-100-opa-80 colors/neutral-100-opa-0]
|
||||
:location [0.2 0.9]
|
||||
:start {:x 0 :y 1}
|
||||
:end {:x 0 :y 0}
|
||||
:style (style/gradient-container insets animations derived transparent?)}
|
||||
[text-sheet/view messages animations state props]
|
||||
[rn/flat-list
|
||||
{:ref #(reset! (:small-list-ref props) %)
|
||||
|
||||
@@ -62,14 +62,16 @@
|
||||
|
||||
;;;; BOTTOM-VIEW
|
||||
(defn gradient-container
|
||||
[insets {:keys [opacity]} {:keys [bottom-layout]}]
|
||||
[insets {:keys [opacity]} {:keys [bottom-layout]} transparent?]
|
||||
(reanimated/apply-animations-to-style
|
||||
{:transform [{:translateY bottom-layout}]
|
||||
:opacity opacity}
|
||||
{:position :absolute
|
||||
:overflow :visible
|
||||
:display (if @transparent? :none :flex)
|
||||
:bottom 0
|
||||
:padding-bottom (:bottom insets)
|
||||
:padding-top c/text-min-height
|
||||
:z-index 3}))
|
||||
|
||||
(defn content-container
|
||||
@@ -80,17 +82,27 @@
|
||||
:justify-content :center})
|
||||
|
||||
|
||||
(defn background
|
||||
(defn background-bottom-gradient
|
||||
[{:keys [overlay-opacity]} z-index]
|
||||
(reanimated/apply-animations-to-style
|
||||
{:opacity overlay-opacity}
|
||||
{:background-color colors/neutral-100-opa-70
|
||||
:position :absolute
|
||||
:top 0
|
||||
:bottom 0
|
||||
:z-index z-index
|
||||
:left 0
|
||||
:right 0}))
|
||||
{:opacity (reanimated/interpolate overlay-opacity [0 0.1 0.4 1] [0 0.1 1 1])}
|
||||
{:position :absolute
|
||||
:top 0
|
||||
:bottom 0
|
||||
:z-index z-index
|
||||
:left 0
|
||||
:right 0}))
|
||||
|
||||
(defn background-top-gradient
|
||||
[{:keys [overlay-opacity]} z-index]
|
||||
(reanimated/apply-animations-to-style
|
||||
{:opacity (reanimated/interpolate overlay-opacity [0.3 1] [0 1])}
|
||||
{:position :absolute
|
||||
:top 0
|
||||
:bottom 0
|
||||
:z-index z-index
|
||||
:left 0
|
||||
:right 0}))
|
||||
|
||||
(defn bottom-inset-cover-up
|
||||
[insets]
|
||||
|
||||
@@ -13,10 +13,11 @@
|
||||
:left 0
|
||||
:right 0}))
|
||||
|
||||
(def text-style
|
||||
(defn text-style
|
||||
[expanding-message?]
|
||||
{:color colors/white
|
||||
:margin-horizontal 20
|
||||
:margin-bottom constants/text-margin
|
||||
:align-items (when-not expanding-message? :center)
|
||||
:flex-grow 1})
|
||||
|
||||
(def bar-container
|
||||
@@ -31,30 +32,37 @@
|
||||
{:width 32
|
||||
:height 4
|
||||
:border-radius 100
|
||||
:background-color colors/white-opa-40
|
||||
:border-width 0.5
|
||||
:border-color colors/neutral-100})
|
||||
:background-color colors/white-opa-10})
|
||||
|
||||
(defn top-gradient
|
||||
[{:keys [gradient-opacity]} insets]
|
||||
(reanimated/apply-animations-to-style
|
||||
{:opacity gradient-opacity}
|
||||
{:position :absolute
|
||||
:left 0
|
||||
:right 0
|
||||
:top (- (+ (:top insets)
|
||||
constants/top-view-height))
|
||||
:height (+ (:top insets)
|
||||
constants/top-view-height
|
||||
constants/bar-container-height
|
||||
constants/text-margin
|
||||
(* constants/line-height 2))
|
||||
:z-index 1}))
|
||||
[{:keys [derived-value]} {:keys [top]} insets max-height]
|
||||
(let [initial-top (- (+ (:top insets)
|
||||
constants/top-view-height))
|
||||
height (+ (:top insets)
|
||||
constants/top-view-height
|
||||
(* constants/line-height 2))]
|
||||
(reanimated/apply-animations-to-style
|
||||
{:opacity (reanimated/interpolate derived-value
|
||||
[max-height (+ max-height constants/line-height)]
|
||||
[0 1])
|
||||
:top (reanimated/interpolate top
|
||||
[0 (- max-height)]
|
||||
[initial-top (- initial-top max-height)]
|
||||
{:extrapolateLeft "clamp"
|
||||
:extrapolateRight "clamp"})}
|
||||
{:position :absolute
|
||||
:left 0
|
||||
:height height
|
||||
:right 0
|
||||
:z-index 1})))
|
||||
|
||||
(def bottom-gradient
|
||||
{:position :absolute
|
||||
:left 0
|
||||
:right 0
|
||||
:height 28
|
||||
:bottom 0
|
||||
:z-index 1})
|
||||
(defn bottom-gradient
|
||||
[bottom-inset]
|
||||
(let [gradient-distance (+ constants/small-list-height bottom-inset)]
|
||||
{:position :absolute
|
||||
:left 0
|
||||
:right 0
|
||||
:height (+ gradient-distance (* 2 constants/line-height))
|
||||
:bottom (- gradient-distance)
|
||||
:opacity 0.8
|
||||
:z-index 1}))
|
||||
|
||||
@@ -3,41 +3,69 @@
|
||||
[oops.core :as oops]
|
||||
[react-native.gesture :as gesture]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[status-im2.contexts.chat.lightbox.constants :as constants]
|
||||
[utils.worklets.lightbox :as worklet]))
|
||||
[reagent.core :as r]
|
||||
[status-im2.contexts.chat.lightbox.constants :as constants]))
|
||||
|
||||
(defn- collapse-sheet
|
||||
[{:keys [derived-value overlay-opacity saved-top expanded? overlay-z-index]}]
|
||||
(reanimated/animate derived-value constants/text-min-height)
|
||||
(reanimated/animate overlay-opacity 0)
|
||||
(reanimated/set-shared-value saved-top (- constants/text-min-height))
|
||||
(reset! expanded? false)
|
||||
(js/setTimeout #(reset! overlay-z-index 0) 300))
|
||||
|
||||
(defn sheet-gesture
|
||||
[{:keys [derived-value saved-top overlay-opacity gradient-opacity]}
|
||||
expanded-height max-height overlay-z-index expanded? dragging?]
|
||||
(-> (gesture/gesture-pan)
|
||||
(gesture/on-start (fn []
|
||||
(reset! overlay-z-index 1)
|
||||
(reset! dragging? true)
|
||||
(reanimated/animate gradient-opacity 0)))
|
||||
(gesture/on-update
|
||||
(fn [e]
|
||||
(let [new-value (+ (reanimated/get-shared-value saved-top) (oops/oget e "translationY"))
|
||||
bounded-value (max (min (- new-value) expanded-height) constants/text-min-height)
|
||||
progress (/ (- new-value) max-height)]
|
||||
(reanimated/set-shared-value overlay-opacity progress)
|
||||
(reanimated/set-shared-value derived-value bounded-value))))
|
||||
(gesture/on-end
|
||||
(fn []
|
||||
(if (or (> (- (reanimated/get-shared-value derived-value))
|
||||
(reanimated/get-shared-value saved-top))
|
||||
(= (reanimated/get-shared-value derived-value)
|
||||
constants/text-min-height))
|
||||
(do ; minimize
|
||||
(reanimated/animate derived-value constants/text-min-height)
|
||||
(reanimated/animate overlay-opacity 0)
|
||||
(reanimated/set-shared-value saved-top (- constants/text-min-height))
|
||||
(reset! expanded? false)
|
||||
(js/setTimeout #(reset! overlay-z-index 0) 300))
|
||||
(reanimated/set-shared-value saved-top
|
||||
(- (reanimated/get-shared-value derived-value))))
|
||||
(when (= (reanimated/get-shared-value derived-value) expanded-height)
|
||||
(reset! expanded? true))
|
||||
(reset! dragging? false)))))
|
||||
expanded-height max-height full-height overlay-z-index expanded? dragging? expanding-message?]
|
||||
(let [disable-gesture-update (r/atom false)]
|
||||
(-> (gesture/gesture-pan)
|
||||
(gesture/enabled expanding-message?)
|
||||
(gesture/on-start (fn []
|
||||
(reset! overlay-z-index 1)
|
||||
(reset! dragging? true)
|
||||
(reset! disable-gesture-update false)
|
||||
(when (not expanded?)
|
||||
(reanimated/animate gradient-opacity 0))))
|
||||
(gesture/on-update
|
||||
(fn [e]
|
||||
(when-not @disable-gesture-update
|
||||
(let [event-value (oops/oget e :translationY)
|
||||
old-value (reanimated/get-shared-value saved-top)
|
||||
new-value (+ old-value event-value)
|
||||
progress (/ (- new-value) max-height)
|
||||
reached-expanded? (< new-value (- max-height))
|
||||
upper-boundary? (< new-value (- full-height))
|
||||
lower-boundary? (and (> new-value (- constants/text-min-height))
|
||||
(pos? event-value))]
|
||||
(when (and (not upper-boundary?) (not lower-boundary?))
|
||||
(reset! expanded? false)
|
||||
(reanimated/set-shared-value overlay-opacity progress)
|
||||
(reanimated/set-shared-value derived-value (- new-value)))
|
||||
(when reached-expanded? (reset! expanded? true))
|
||||
(when lower-boundary?
|
||||
(reset! disable-gesture-update true)
|
||||
(collapse-sheet {:derived-value derived-value
|
||||
:overlay-opacity overlay-opacity
|
||||
:saved-top saved-top
|
||||
:expanded? expanded?
|
||||
:overlay-z-index overlay-z-index}))))))
|
||||
(gesture/on-end
|
||||
(fn []
|
||||
(let [shared-derived-value (reanimated/get-shared-value derived-value)
|
||||
below-max-height? (< shared-derived-value max-height)
|
||||
below-saved-top? (> (- shared-derived-value)
|
||||
(reanimated/get-shared-value saved-top))]
|
||||
(if (and below-max-height? below-saved-top?)
|
||||
(collapse-sheet {:derived-value derived-value
|
||||
:overlay-opacity overlay-opacity
|
||||
:saved-top saved-top
|
||||
:expanded? expanded?
|
||||
:overlay-z-index overlay-z-index})
|
||||
(reanimated/set-shared-value saved-top
|
||||
(- shared-derived-value)))
|
||||
(when (= shared-derived-value expanded-height)
|
||||
(reset! expanded? true))
|
||||
(reset! dragging? false)))))))
|
||||
|
||||
(defn expand-sheet
|
||||
[{:keys [derived-value overlay-opacity saved-top]}
|
||||
@@ -49,12 +77,6 @@
|
||||
(reset! overlay-z-index 1)
|
||||
(reset! expanded? true)))
|
||||
|
||||
(defn on-scroll
|
||||
[e expanded? dragging? {:keys [gradient-opacity]}]
|
||||
(if (and (> (oops/oget e "nativeEvent.contentOffset.y") 0) expanded? (not dragging?))
|
||||
(reanimated/animate gradient-opacity 1)
|
||||
(reanimated/animate gradient-opacity 0)))
|
||||
|
||||
(defn on-layout
|
||||
[e text-height]
|
||||
(reset! text-height (oops/oget e "nativeEvent.layout.height")))
|
||||
@@ -68,5 +90,5 @@
|
||||
|
||||
(defn init-derived-animations
|
||||
[{:keys [derived-value]}]
|
||||
{:height (worklet/text-sheet derived-value true)
|
||||
:top (worklet/text-sheet derived-value false)})
|
||||
{:height derived-value
|
||||
:top (reanimated/interpolate derived-value [0 1] [0 -1])})
|
||||
|
||||
@@ -13,13 +13,7 @@
|
||||
[status-im2.contexts.chat.lightbox.text-sheet.utils :as utils]
|
||||
[status-im2.contexts.chat.messages.content.text.view :as message-view]))
|
||||
|
||||
(defn bar
|
||||
[text-height]
|
||||
(when (> text-height (* constants/line-height 2))
|
||||
[rn/view {:style style/bar-container}
|
||||
[rn/view {:style style/bar}]]))
|
||||
|
||||
(defn text-sheet
|
||||
(defn- text-sheet
|
||||
[messages overlay-opacity overlay-z-index text-sheet-lock?]
|
||||
(let [text-height (reagent/atom 0)
|
||||
expanded? (reagent/atom false)
|
||||
@@ -33,50 +27,63 @@
|
||||
constants/top-view-height
|
||||
(:bottom insets)
|
||||
(when platform/ios? (:top insets)))
|
||||
expanded-height (min max-height
|
||||
(+ constants/bar-container-height
|
||||
@text-height
|
||||
constants/text-margin))
|
||||
full-height (+ constants/bar-container-height
|
||||
constants/text-margin
|
||||
constants/line-height
|
||||
@text-height)
|
||||
expanded-height (min max-height full-height)
|
||||
animations (utils/init-animations overlay-opacity)
|
||||
derived (utils/init-derived-animations animations)]
|
||||
[gesture/gesture-detector
|
||||
{:gesture (utils/sheet-gesture animations
|
||||
expanded-height
|
||||
max-height
|
||||
overlay-z-index
|
||||
expanded?
|
||||
dragging?)}
|
||||
[reanimated/touchable-opacity
|
||||
{:active-opacity 1
|
||||
:on-press
|
||||
#(utils/expand-sheet animations
|
||||
expanded-height
|
||||
max-height
|
||||
overlay-z-index
|
||||
expanded?
|
||||
text-sheet-lock?)
|
||||
:style (style/sheet-container derived)}
|
||||
[bar @text-height]
|
||||
[reanimated/linear-gradient
|
||||
{:colors [colors/neutral-100-opa-0 colors/neutral-100]
|
||||
:start {:x 0 :y 1}
|
||||
:end {:x 0 :y 0}
|
||||
:style (style/top-gradient animations insets)}]
|
||||
[linear-gradient/linear-gradient
|
||||
{:colors [colors/neutral-100-opa-50 colors/neutral-100-opa-0]
|
||||
:start {:x 0 :y 1}
|
||||
:end {:x 0 :y 0}
|
||||
:style style/bottom-gradient}]
|
||||
[gesture/scroll-view
|
||||
{:scroll-enabled @expanded?
|
||||
:scroll-event-throttle 16
|
||||
:on-scroll #(utils/on-scroll % @expanded? @dragging? animations)
|
||||
:style {:height (- max-height constants/bar-container-height)}}
|
||||
[message-view/render-parsed-text
|
||||
{:content content
|
||||
:chat-id chat-id
|
||||
:style-override style/text-style
|
||||
:on-layout #(utils/on-layout % text-height)}]]]]))))
|
||||
derived (utils/init-derived-animations animations)
|
||||
expanding-message? (> @text-height (* constants/line-height 2))]
|
||||
[rn/view
|
||||
[reanimated/linear-gradient
|
||||
{:colors [colors/neutral-100-opa-0 colors/neutral-100]
|
||||
:pointer-events :none
|
||||
:locations [0 0.3]
|
||||
:start {:x 0 :y 1}
|
||||
:end {:x 0 :y 0}
|
||||
:style (style/top-gradient animations derived insets max-height)}]
|
||||
[gesture/gesture-detector
|
||||
{:gesture (utils/sheet-gesture animations
|
||||
expanded-height
|
||||
max-height
|
||||
full-height
|
||||
overlay-z-index
|
||||
expanded?
|
||||
dragging?
|
||||
expanding-message?)}
|
||||
[gesture/gesture-detector
|
||||
{:gesture (-> (gesture/gesture-tap)
|
||||
(gesture/enabled (and expanding-message? (not @expanded?)))
|
||||
(gesture/on-start (fn []
|
||||
(utils/expand-sheet animations
|
||||
expanded-height
|
||||
max-height
|
||||
overlay-z-index
|
||||
expanded?
|
||||
text-sheet-lock?))))}
|
||||
[reanimated/view {:style (style/sheet-container derived)}
|
||||
(when expanding-message?
|
||||
[rn/view {:style style/bar-container}
|
||||
[rn/view {:style style/bar}]])
|
||||
[linear-gradient/linear-gradient
|
||||
{:colors [colors/neutral-100-opa-100 colors/neutral-100-opa-70 colors/neutral-100-opa-0]
|
||||
:start {:x 0 :y 1}
|
||||
:end {:x 0 :y 0}
|
||||
:locations [0.7 0.8 1]
|
||||
:style (style/bottom-gradient (:bottom insets))}]
|
||||
[gesture/scroll-view
|
||||
{:scroll-enabled false
|
||||
:scroll-event-throttle 16
|
||||
:bounces false
|
||||
:style {:height (- max-height constants/bar-container-height)}
|
||||
:content-container-style {:padding-top (when (not expanding-message?)
|
||||
constants/bar-container-height)}}
|
||||
[message-view/render-parsed-text
|
||||
{:content content
|
||||
:chat-id chat-id
|
||||
:style-override (style/text-style expanding-message?)
|
||||
:on-layout #(utils/on-layout % text-height)}]]]]]]))))
|
||||
|
||||
(defn view
|
||||
[messages {:keys [overlay-opacity]} {:keys [overlay-z-index]} {:keys [text-sheet-lock?]}]
|
||||
|
||||
@@ -75,7 +75,16 @@
|
||||
{:transform [{:translateY (:pan-y animations)}
|
||||
{:translateX (:pan-x animations)}]}
|
||||
{})}
|
||||
[reanimated/view {:style (style/background animations @(:overlay-z-index state))}]
|
||||
[reanimated/linear-gradient
|
||||
{:colors [colors/neutral-100-opa-0 colors/neutral-100-opa-0 colors/neutral-100-opa-100
|
||||
colors/neutral-100]
|
||||
:locations [0.3 0.4 0.6 1]
|
||||
:pointer-events :none
|
||||
:style (style/background-bottom-gradient animations @(:overlay-z-index state))}]
|
||||
[reanimated/linear-gradient
|
||||
{:colors [colors/neutral-100-opa-50 colors/neutral-100]
|
||||
:pointer-events :none
|
||||
:style (style/background-top-gradient animations @(:overlay-z-index state))}]
|
||||
[gesture/flat-list
|
||||
{:ref #(reset! (:flat-list-ref props) %)
|
||||
:key-fn :message-id
|
||||
@@ -85,6 +94,7 @@
|
||||
:data @data
|
||||
:render-fn image
|
||||
:render-data {:opacity-value (:opacity animations)
|
||||
:overlay-opacity (:overlay-opacity animations)
|
||||
:border-value (:border animations)
|
||||
:full-screen-scale (:full-screen-scale animations)
|
||||
:images-opacity (:images-opacity animations)
|
||||
@@ -105,9 +115,12 @@
|
||||
:shows-vertical-scroll-indicator false
|
||||
:shows-horizontal-scroll-indicator false
|
||||
:on-viewable-items-changed handle-items-changed}]]]
|
||||
(when (and (not @transparent?) (not landscape?))
|
||||
;; NOTE: not un-mounting bottom-view based on `transparent?` (like we do with the top-view
|
||||
;; above), since we need to save the state of the text-sheet position. Instead, we use
|
||||
;; the `:display` style property to hide the bottom-sheet.
|
||||
(when (not landscape?)
|
||||
[:f> bottom-view/bottom-view messages index scroll-index insets animations derived
|
||||
item-width props state])]))
|
||||
item-width props state transparent?])]))
|
||||
|
||||
(defn- f-lightbox
|
||||
[]
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
[react-native.navigation :as navigation]
|
||||
[react-native.orientation :as orientation]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.contexts.chat.lightbox.animations :as anim]
|
||||
[status-im2.contexts.chat.lightbox.zoomable-image.constants :as constants]
|
||||
@@ -107,12 +106,36 @@
|
||||
(rescale constants/min-scale true)))
|
||||
|
||||
(defn toggle-opacity
|
||||
[index {:keys [opacity-value border-value full-screen-scale transparent? props]} portrait?]
|
||||
[index
|
||||
{:keys [opacity-value overlay-opacity border-value full-screen-scale transparent?
|
||||
props]}
|
||||
portrait? last-overlay-opacity]
|
||||
(let [{:keys [small-list-ref timers text-sheet-lock?]} props
|
||||
opacity (reanimated/get-shared-value opacity-value)
|
||||
scale-value (inc (/ constants/margin
|
||||
(:width (rn/get-window))))]
|
||||
(if (= opacity 1)
|
||||
(if @transparent?
|
||||
(do
|
||||
(js/clearTimeout (:hide-0 @timers))
|
||||
(js/clearTimeout (:hide-1 @timers))
|
||||
(reset! transparent? (not @transparent?))
|
||||
(swap! timers assoc
|
||||
:show-0
|
||||
(js/setTimeout #((anim/animate opacity-value 1)
|
||||
(anim/animate overlay-opacity @last-overlay-opacity)
|
||||
(reset! last-overlay-opacity 0))
|
||||
50))
|
||||
(swap! timers assoc
|
||||
:show-1
|
||||
(js/setTimeout #(when @small-list-ref
|
||||
(.scrollToIndex ^js @small-list-ref #js {:animated false :index index}))
|
||||
100))
|
||||
(anim/animate border-value 16)
|
||||
(anim/animate full-screen-scale 1)
|
||||
(when portrait?
|
||||
(swap! timers assoc
|
||||
:show-2
|
||||
(js/setTimeout #(navigation/merge-options "lightbox" {:statusBar {:visible true}})
|
||||
(if platform/ios? 150 50)))))
|
||||
(do
|
||||
(js/clearTimeout (:show-0 @timers))
|
||||
(js/clearTimeout (:show-1 @timers))
|
||||
@@ -122,26 +145,13 @@
|
||||
(js/setTimeout #(navigation/merge-options "lightbox" {:statusBar {:visible false}})
|
||||
(if platform/ios? 75 0)))
|
||||
(anim/animate opacity-value 0)
|
||||
(reset! last-overlay-opacity (anim/get-val overlay-opacity))
|
||||
(anim/animate overlay-opacity 0)
|
||||
(anim/animate border-value 0)
|
||||
(anim/animate full-screen-scale scale-value)
|
||||
(swap! timers assoc :hide-1 (js/setTimeout #(reset! transparent? (not @transparent?)) 400))
|
||||
(reset! text-sheet-lock? true)
|
||||
(js/setTimeout #(reset! text-sheet-lock? false) 300))
|
||||
(do
|
||||
(js/clearTimeout (:hide-0 @timers))
|
||||
(js/clearTimeout (:hide-1 @timers))
|
||||
(reset! transparent? (not @transparent?))
|
||||
(swap! timers assoc :show-0 (js/setTimeout #(anim/animate opacity-value 1) 50))
|
||||
(swap! timers assoc
|
||||
:show-1
|
||||
(js/setTimeout #(when @small-list-ref
|
||||
(.scrollToIndex ^js @small-list-ref #js {:animated false :index index}))
|
||||
100))
|
||||
(when portrait?
|
||||
(swap! timers assoc
|
||||
:show-2
|
||||
(js/setTimeout #(navigation/merge-options "lightbox" {:statusBar {:visible true}})
|
||||
(if platform/ios? 150 50))))))
|
||||
(anim/animate border-value (if (= opacity 1) 0 16))
|
||||
(anim/animate full-screen-scale (if (= opacity 1) scale-value 1))))
|
||||
(js/setTimeout #(reset! text-sheet-lock? false) 300)))))
|
||||
|
||||
;;; Dimensions
|
||||
(defn get-dimensions
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
[react-native.orientation :as orientation]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[reagent.core :as r]
|
||||
[status-im2.contexts.chat.lightbox.animations :as anim]
|
||||
[status-im2.contexts.chat.lightbox.zoomable-image.constants :as c]
|
||||
[status-im2.contexts.chat.lightbox.zoomable-image.style :as style]
|
||||
@@ -209,7 +210,8 @@
|
||||
image-dimensions-nil?]
|
||||
(let [{:keys [transparent? set-full-height?]} render-data
|
||||
portrait? (= curr-orientation orientation/portrait)
|
||||
on-tap #(utils/toggle-opacity index render-data portrait?)
|
||||
last-overlay-opacity (r/atom 0)
|
||||
on-tap #(utils/toggle-opacity index render-data portrait? last-overlay-opacity)
|
||||
tap (tap-gesture on-tap)
|
||||
double-tap (double-tap-gesture dimensions animations rescale transparent? on-tap)
|
||||
pinch (pinch-gesture dimensions animations state rescale transparent? on-tap)
|
||||
@@ -242,8 +244,8 @@
|
||||
zoom-out-signal (rf/sub [:lightbox/zoom-out-signal])
|
||||
{:keys [set-full-height? curr-orientation]} render-data
|
||||
focused? (= shared-element-id message-id)
|
||||
;; TODO - remove `image-dimensions` check,
|
||||
;; once https://github.com/status-im/status-desktop/issues/10944 is fixed
|
||||
;; TODO - remove `image-dimensions` check, once
|
||||
;; https://github.com/status-im/status-desktop/issues/10944 is fixed
|
||||
image-dimensions-nil? (not (and image-width image-height))
|
||||
dimensions (utils/get-dimensions
|
||||
(or image-width (:screen-width render-data))
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
(defn community-link
|
||||
[id]
|
||||
(str "https://status.app/c/" id))
|
||||
(str "https://status.app/c#" id))
|
||||
|
||||
(rf/defn cache-link-preview-data
|
||||
{:events [:chat.ui/cache-link-preview-data]}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
(:require
|
||||
[clojure.string :as string]
|
||||
[oops.core :as oops]
|
||||
[quo.components.colors.color.constants :as constants]
|
||||
[quo.core :as quo]
|
||||
[quo.foundations.colors :as colors]
|
||||
[react-native.blur :as blur]
|
||||
@@ -15,7 +14,8 @@
|
||||
[status-im2.contexts.onboarding.create-profile.style :as style]
|
||||
[status-im2.contexts.onboarding.select-photo.method-menu.view :as method-menu]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
[utils.re-frame :as rf]
|
||||
[utils.responsiveness :as responsiveness]))
|
||||
|
||||
;; NOTE - validation should match with Desktop
|
||||
;; https://github.com/status-im/status-desktop/blob/2ba96803168461088346bf5030df750cb226df4c/ui/imports/utils/Constants.qml#L468
|
||||
@@ -34,12 +34,6 @@
|
||||
(defn length-not-valid [s] (< (count (string/trim (str s))) min-length))
|
||||
(def scroll-view-height (reagent/atom 0))
|
||||
(def content-container-height (reagent/atom 0))
|
||||
(defn iphone-11-Pro-20-pixel-from-width
|
||||
[window-width]
|
||||
;; Divide iPhone 11 Pro VW by the desired value
|
||||
(let [calculate-ratio (/ constants/IPHONE_11_PRO_VIEWPORT_WIDTH 20)]
|
||||
;; Divide window width by the ratio to get a dynamic value. Based on the VW width
|
||||
(int (/ window-width calculate-ratio))))
|
||||
|
||||
(defn validation-message
|
||||
[s]
|
||||
@@ -208,7 +202,8 @@
|
||||
:default-selected :blue
|
||||
:on-change on-change
|
||||
:window-width window-width
|
||||
:container-style {:padding-left (iphone-11-Pro-20-pixel-from-width window-width)}}]]]
|
||||
:container-style {:padding-left (responsiveness/iphone-11-Pro-20-pixel-from-width
|
||||
window-width)}}]]]
|
||||
|
||||
[rn/keyboard-avoiding-view
|
||||
{:style {:position :absolute
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
{:db (assoc db
|
||||
:chats/loading? true
|
||||
:networks/current-network current-network
|
||||
:wallet/tokens-loading? true
|
||||
:networks/networks (merge networks config/default-networks-by-id)
|
||||
:profile/profile (merge profile settings))}
|
||||
(notifications/load-preferences)
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
(ns dev.component-preview.events
|
||||
(ns status-im2.contexts.quo-preview.component-preview.events
|
||||
(:require
|
||||
[quo.core :as quo]
|
||||
[re-frame.core :as re-frame]))
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
(ns dev.component-preview.view
|
||||
(ns status-im2.contexts.quo-preview.component-preview.view
|
||||
(:require
|
||||
[react-native.core :as rn]
|
||||
[utils.re-frame :as rf]))
|
||||
@@ -21,12 +21,24 @@
|
||||
:key :actions
|
||||
:options [{:key :1-action}
|
||||
{:key :2-actions}]}
|
||||
{:type :select
|
||||
:key :button-two-type
|
||||
:options button-options}
|
||||
{:type :select
|
||||
:key :button-one-type
|
||||
:options button-options}
|
||||
{:label "Button 1 type"
|
||||
:type :select
|
||||
:key :type
|
||||
:options button-options
|
||||
:path [:button-one-props]}
|
||||
{:label "Button 1 disabled?"
|
||||
:type :boolean
|
||||
:key :disabled?
|
||||
:path [:button-one-props]}
|
||||
{:label "Button 2 type"
|
||||
:type :select
|
||||
:key :type
|
||||
:options button-options
|
||||
:path [:button-two-props]}
|
||||
{:label "Button 2 disabled?"
|
||||
:type :boolean
|
||||
:key :disabled?
|
||||
:path [:button-two-props]}
|
||||
{:key :description
|
||||
:type :text}
|
||||
{:key :button-one-label
|
||||
@@ -42,10 +54,11 @@
|
||||
:description description
|
||||
:button-one-label button-one
|
||||
:button-two-label button-two
|
||||
:button-one-press (button-press 2)
|
||||
:button-two-press (button-press 1)
|
||||
:button-one-type :primary
|
||||
:button-two-type :grey
|
||||
:button-one-props {:on-press (button-press 1)
|
||||
:type :primary
|
||||
:icon-left :i/arrow-up}
|
||||
:button-two-props {:on-press (button-press 2)
|
||||
:type :grey}
|
||||
:scroll? false})]
|
||||
(fn []
|
||||
[preview/preview-container
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
(ns status-im2.contexts.quo-preview.dropdowns.dropdown-input
|
||||
(: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]))
|
||||
|
||||
(def descriptor
|
||||
[])
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [state (reagent/atom {})]
|
||||
[:f>
|
||||
(fn []
|
||||
[preview/preview-container
|
||||
{:state state
|
||||
:descriptor descriptor}
|
||||
[quo/dropdown
|
||||
{:on-press #(js/console.log "sooqa")
|
||||
:icon? true}
|
||||
[quo/text
|
||||
"Dropdown"]]])]))
|
||||
@@ -36,7 +36,7 @@
|
||||
{:key :show-alert-on-press?
|
||||
:type :boolean}])
|
||||
|
||||
(defn preview-token-network
|
||||
(defn view
|
||||
[]
|
||||
(let [state (reagent/atom {:token (quo.resources/get-token :snt)
|
||||
:label "Status"
|
||||
|
||||
@@ -59,6 +59,8 @@
|
||||
[status-im2.contexts.quo-preview.dropdowns.dropdown :as dropdown]
|
||||
[status-im2.contexts.quo-preview.dropdowns.network-dropdown :as
|
||||
network-dropdown]
|
||||
[status-im2.contexts.quo-preview.dropdowns.dropdown-input :as
|
||||
dropdown-input]
|
||||
[status-im2.contexts.quo-preview.empty-state.empty-state :as empty-state]
|
||||
[status-im2.contexts.quo-preview.foundations.shadows :as shadows]
|
||||
[status-im2.contexts.quo-preview.gradient.gradient-cover :as gradient-cover]
|
||||
@@ -269,7 +271,9 @@
|
||||
:dropdowns [{:name :dropdown
|
||||
:component dropdown/view}
|
||||
{:name :network-dropdown
|
||||
:component network-dropdown/view}]
|
||||
:component network-dropdown/view}
|
||||
{:name :dropdown-input
|
||||
:component dropdown-input/view}]
|
||||
:empty-state [{:name :empty-state
|
||||
:component empty-state/view}]
|
||||
:gradient [{:name :gradient-cover
|
||||
@@ -331,7 +335,7 @@
|
||||
{:name :saved-contact-address
|
||||
:component saved-contact-address/view}
|
||||
{:name :token-network
|
||||
:component token-network/preview-token-network}
|
||||
:component token-network/view}
|
||||
{:name :token-value
|
||||
:component token-value/view}
|
||||
{:name :user-list
|
||||
@@ -359,9 +363,9 @@
|
||||
{:name :floating-shell-button
|
||||
:component floating-shell-button/view}]
|
||||
:notifications [{:name :activity-logs
|
||||
:component activity-logs/preview-activity-logs}
|
||||
:component activity-logs/view}
|
||||
{:name :activity-logs-photos
|
||||
:component activity-logs-photos/preview-activity-logs-photos}
|
||||
:component activity-logs-photos/view}
|
||||
{:name :toast
|
||||
:component toast/view}
|
||||
{:name :notification
|
||||
@@ -435,27 +439,27 @@
|
||||
{:name :tags
|
||||
:component tags/view}
|
||||
{:name :tiny-tag
|
||||
:component tiny-tag/preview-tiny-tag}
|
||||
:component tiny-tag/view}
|
||||
{:name :token-tag
|
||||
:component token-tag/view}]
|
||||
:text-combinations [{:name :text-combinations
|
||||
:component text-combinations/view}]
|
||||
:wallet [{:name :account-card :component account-card/preview-account-card}
|
||||
:wallet [{:name :account-card :component account-card/view}
|
||||
{:name :account-origin :component account-origin/view}
|
||||
{:name :account-overview
|
||||
:component account-overview/preview-account-overview}
|
||||
{:name :keypair :component keypair/preview}
|
||||
{:name :network-amount :component network-amount/preview}
|
||||
{:name :network-bridge :component network-bridge/preview}
|
||||
{:name :network-link :component network-link/preview}
|
||||
{:name :network-routing :component network-routing/preview}
|
||||
{:name :progress-bar :component progress-bar/preview}
|
||||
{:name :summary-info :component summary-info/preview}
|
||||
{:name :token-input :component token-input/preview}
|
||||
:component account-overview/view}
|
||||
{:name :keypair :component keypair/view}
|
||||
{:name :network-amount :component network-amount/view}
|
||||
{:name :network-bridge :component network-bridge/view}
|
||||
{:name :network-link :component network-link/view}
|
||||
{:name :network-routing :component network-routing/view}
|
||||
{:name :progress-bar :component progress-bar/view}
|
||||
{:name :summary-info :component summary-info/view}
|
||||
{:name :token-input :component token-input/view}
|
||||
{:name :wallet-activity :component wallet-activity/view}
|
||||
{:name :transaction-summary :component transaction-summary/view}
|
||||
{:name :wallet-overview
|
||||
:component wallet-overview/preview-wallet-overview}]
|
||||
:component wallet-overview/view}]
|
||||
:keycard [{:name :keycard-component :component keycard/view}]})
|
||||
|
||||
(defn- category-view
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
{:body "Your favorite color is Turquoise."
|
||||
:title "What's my favorite color?"})
|
||||
|
||||
(defn preview-activity-logs
|
||||
(defn view
|
||||
[]
|
||||
(let [state (reagent/atom {:button-1-label "Decline"
|
||||
:button-1-type :danger
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
(resources/get-mock-image :photo2)
|
||||
(resources/get-mock-image :photo3)])
|
||||
|
||||
(defn preview-activity-logs-photos
|
||||
(defn view
|
||||
[]
|
||||
(let [state (reagent/atom {:count 1})]
|
||||
(fn []
|
||||
|
||||
@@ -258,7 +258,11 @@
|
||||
:padding-horizontal 20}}
|
||||
(doall
|
||||
(for [desc descriptors
|
||||
:let [descriptor (merge desc {:state state})]]
|
||||
:let [desc-path (:path desc)
|
||||
new-state (if desc-path
|
||||
(reagent/cursor state desc-path)
|
||||
state)
|
||||
descriptor (assoc desc :state new-state)]]
|
||||
^{:key (:key desc)}
|
||||
[:<>
|
||||
(case (:type desc)
|
||||
|
||||
@@ -62,16 +62,16 @@
|
||||
[]
|
||||
(let [media-server-port (rf/sub [:mediaserver/port])
|
||||
state (reagent/atom
|
||||
{:url "https://join.status.im/status"
|
||||
:size 250
|
||||
:avatar :none
|
||||
:profile-picture (resources/get-mock-image :user-picture-male5)
|
||||
{:url "https://status.app/u#zQ34e1zlOdas0pKnvrweeedsasas12adjie8"
|
||||
:size 250
|
||||
:avatar :none
|
||||
:profile-picture (resources/get-mock-image :user-picture-male5)
|
||||
:customization-color :army
|
||||
:full-name "Full Name"
|
||||
:emoji "🍒"
|
||||
:picture (resources/get-mock-image :community-logo)
|
||||
:f-name "First Name"
|
||||
:l-name "Last Name"})]
|
||||
:full-name "Full Name"
|
||||
:emoji "🍒"
|
||||
:picture (resources/get-mock-image :community-logo)
|
||||
:f-name "First Name"
|
||||
:l-name "Last Name"})]
|
||||
(fn []
|
||||
(let [qr-media-server-uri (image-server/get-qr-image-uri-for-any-url
|
||||
{:url (:url @state)
|
||||
|
||||
@@ -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://status.app/u#zQ34e1zlOdas0pKnvrweeedsasas12adjie8")
|
||||
|
||||
(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)]]]))))
|
||||
|
||||
@@ -6,14 +6,10 @@
|
||||
[status-im2.contexts.quo-preview.preview :as preview]))
|
||||
|
||||
(def descriptor
|
||||
[{:label "Blur?"
|
||||
:key :blur?
|
||||
:type :boolean}
|
||||
{:label "Label"
|
||||
:key :label
|
||||
:type :text}])
|
||||
[{:key :blur? :type :boolean}
|
||||
{:key :label :type :text}])
|
||||
|
||||
(defn preview-tiny-tag
|
||||
(defn view
|
||||
[]
|
||||
(let [state (reagent/atom {:blur? false
|
||||
:label "1,000 SNT"})]
|
||||
|
||||
@@ -1,56 +1,27 @@
|
||||
(ns status-im2.contexts.quo-preview.wallet.account-card
|
||||
(:require
|
||||
[quo.components.icon :as icon]
|
||||
[quo.components.markdown.text :as text]
|
||||
[quo.core :as quo]
|
||||
[quo.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.contexts.quo-preview.preview :as preview]
|
||||
[utils.collection]))
|
||||
|
||||
(def descriptor
|
||||
[{:label "Type:"
|
||||
:key :type
|
||||
[{:key :type
|
||||
:type :select
|
||||
:options [{:key :default
|
||||
:value "Default"}
|
||||
{:key :watch-only
|
||||
:value "Watch Only"}
|
||||
{:key :add-account
|
||||
:value "Add Account"}
|
||||
{:key :empty
|
||||
:value "Empty"}
|
||||
{:key :missing-keypair
|
||||
:value "Missing Keypair"}]}
|
||||
{:label "Customization color:"
|
||||
:key :customization-color
|
||||
:type :select
|
||||
:options (map (fn [[color-kw _]]
|
||||
{:key color-kw
|
||||
:value (name color-kw)})
|
||||
colors/customization)}
|
||||
{:label "Name:"
|
||||
:key :name
|
||||
:type :text}
|
||||
{:label "Balance:"
|
||||
:key :balance
|
||||
:type :text}
|
||||
{:label "Percentage value:"
|
||||
:key :percentage-value
|
||||
:type :text}
|
||||
{:label "Amount:"
|
||||
:key :amount
|
||||
:type :text}
|
||||
{:label "Metrics:"
|
||||
:key :metrics?
|
||||
:type :boolean}
|
||||
{:label "Loading:"
|
||||
:key :loading?
|
||||
:type :boolean}
|
||||
{:label "Emoji:"
|
||||
:key :emoji
|
||||
:type :text}])
|
||||
:options [{:key :default}
|
||||
{:key :watch-only}
|
||||
{:key :add-account}
|
||||
{:key :empty}
|
||||
{:key :missing-keypair}]}
|
||||
(preview/customization-color-option)
|
||||
{:key :name :type :text}
|
||||
{:key :balance :type :text}
|
||||
{:key :percentage-value :type :text}
|
||||
{:key :amount :type :text}
|
||||
{:key :metrics? :type :boolean}
|
||||
{:key :loading? :type :boolean}
|
||||
{:key :emoji :type :text}])
|
||||
|
||||
(defn initial-state
|
||||
[type]
|
||||
@@ -101,7 +72,7 @@
|
||||
:type :add-account}))
|
||||
|
||||
|
||||
(defn preview-account-card
|
||||
(defn view
|
||||
[]
|
||||
(let [state (reagent/atom (initial-state :default))]
|
||||
[:f>
|
||||
@@ -112,23 +83,4 @@
|
||||
[preview/preview-container
|
||||
{:state state
|
||||
:descriptor descriptor}
|
||||
[rn/view
|
||||
{:style {:margin-vertical 40
|
||||
:padding-left 40
|
||||
:flex-direction :row
|
||||
:align-items :center}}
|
||||
[text/text
|
||||
{:size :heading-1
|
||||
:weight :semi-bold}
|
||||
"Account card"]
|
||||
[rn/view
|
||||
{:style {:width 20
|
||||
:height 20
|
||||
:border-radius 60
|
||||
:background-color colors/success-50
|
||||
:align-items :center
|
||||
:justify-content :center
|
||||
:margin-left 8}}
|
||||
[icon/icon :i/check {:color colors/white :size 16}]]]
|
||||
[rn/view {:style {:align-items :center :margin-bottom 20}}
|
||||
[quo/account-card @state]]])]))
|
||||
[quo/account-card @state]])]))
|
||||
|
||||
@@ -1,53 +1,30 @@
|
||||
(ns status-im2.contexts.quo-preview.wallet.account-overview
|
||||
(:require
|
||||
[quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.contexts.quo-preview.preview :as preview]))
|
||||
|
||||
(def descriptor
|
||||
[{:label "Time frame string"
|
||||
:key :time-frame-string
|
||||
:type :text}
|
||||
{:label "Time frame to string"
|
||||
:key :time-frame-to-string
|
||||
:type :text}
|
||||
{:label "Percentage change"
|
||||
:key :percentage-change
|
||||
:type :text}
|
||||
{:label "Currency change"
|
||||
:key :currency-change
|
||||
:type :text}
|
||||
{:label "Current value"
|
||||
:key :current-value
|
||||
:type :text}
|
||||
{:label "State"
|
||||
:key :state
|
||||
[{:key :time-frame-string :type :text}
|
||||
{:key :time-frame-to-string :type :text}
|
||||
{:key :percentage-change :type :text}
|
||||
{:key :currency-change :type :text}
|
||||
{:key :current-value :type :text}
|
||||
{:key :state
|
||||
:type :select
|
||||
:options [{:key :default
|
||||
:value "Default"}
|
||||
{:key :loading
|
||||
:value "Loading"}]}
|
||||
{:label "Metrics"
|
||||
:key :metrics
|
||||
:options [{:key :default}
|
||||
{:key :loading}]}
|
||||
{:key :metrics
|
||||
:type :select
|
||||
:options [{:key "Positive"
|
||||
:value :positive}
|
||||
{:key "Negative"
|
||||
:value :negative}]}
|
||||
{:label "Account name"
|
||||
:key :account-name
|
||||
:type :text}
|
||||
:options [{:key :positive}
|
||||
{:key :negative}]}
|
||||
{:key :account-name :type :text}
|
||||
(preview/customization-color-option)
|
||||
{:label "Account"
|
||||
:key :account
|
||||
{:key :account
|
||||
:type :select
|
||||
:options [{:key :watched-address
|
||||
:value "Watched address"}
|
||||
{:key :default
|
||||
:value "Default"}]}
|
||||
{:label "Time frame"
|
||||
:key :time-frame
|
||||
:options [{:key :watched-address}
|
||||
{:key :default}]}
|
||||
{:key :time-frame
|
||||
:type :select
|
||||
:options [{:key :one-week
|
||||
:value "1 Week"}
|
||||
@@ -62,7 +39,7 @@
|
||||
{:key :custom
|
||||
:value "Custom"}]}])
|
||||
|
||||
(defn preview-account-overview
|
||||
(defn view
|
||||
[]
|
||||
(let [state (reagent/atom {:metrics :positive
|
||||
:currency-change "€0.00"
|
||||
@@ -76,11 +53,9 @@
|
||||
:customization-color :blue})]
|
||||
(fn []
|
||||
[preview/preview-container
|
||||
{:state state
|
||||
:descriptor descriptor}
|
||||
[rn/view {:style {:padding-bottom 150}}
|
||||
[rn/view
|
||||
{:style {:padding-vertical 60
|
||||
:flex-direction :row
|
||||
:justify-content :center}}
|
||||
[quo/account-overview @state]]]])))
|
||||
{:state state
|
||||
:descriptor descriptor
|
||||
:component-container-style {:padding-vertical 60
|
||||
:flex-direction :row
|
||||
:justify-content :center}}
|
||||
[quo/account-overview @state]])))
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
(ns status-im2.contexts.quo-preview.wallet.keypair
|
||||
(:require
|
||||
[quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.contexts.quo-preview.preview :as preview]))
|
||||
|
||||
@@ -62,30 +61,20 @@
|
||||
(map (fn [account] (assoc account :blur? blur?)) accounts))
|
||||
|
||||
(def descriptor
|
||||
[{:label "Stored:"
|
||||
:key :stored
|
||||
[{:key :stored
|
||||
:type :select
|
||||
:options [{:key :on-device
|
||||
:value "On device"}
|
||||
{:key :on-keycard
|
||||
:value "On Keycard"}]}
|
||||
{:label "Action:"
|
||||
:key :action
|
||||
:options [{:key :on-device}
|
||||
{:key :on-keycard}]}
|
||||
{:key :action
|
||||
:type :select
|
||||
:options [{:key :selector
|
||||
:value "Selector"}
|
||||
{:key :options
|
||||
:value "Options"}]}
|
||||
{:label "Type:"
|
||||
:key :type
|
||||
:options [{:key :selector}
|
||||
{:key :options}]}
|
||||
{:key :type
|
||||
:type :select
|
||||
:options [{:key :default-keypair
|
||||
:value "Default keypair"}
|
||||
{:key :other
|
||||
:value "Other"}]}
|
||||
:options [{:key :default-keypair}
|
||||
{:key :other}]}
|
||||
(preview/customization-color-option)
|
||||
{:key :blur?
|
||||
:type :boolean}])
|
||||
{:key :blur? :type :boolean}])
|
||||
|
||||
(def default-details
|
||||
{:full-name "John Doe"
|
||||
@@ -93,7 +82,7 @@
|
||||
|
||||
(def other-details {:full-name "Metamask"})
|
||||
|
||||
(defn preview
|
||||
(defn view
|
||||
[]
|
||||
(let [state (reagent/atom {:customization-color :blue
|
||||
:type :default-keypair
|
||||
@@ -103,18 +92,17 @@
|
||||
:blur? false})]
|
||||
(fn []
|
||||
[preview/preview-container
|
||||
{:state state
|
||||
:descriptor descriptor
|
||||
:blur? (:blur? @state)
|
||||
:show-blur-background? true
|
||||
:blur-dark-only? true
|
||||
:blur-height 400}
|
||||
[rn/view
|
||||
{:style {:padding-vertical 30
|
||||
:flex-direction :row
|
||||
:justify-content :center}}
|
||||
[quo/keypair
|
||||
(merge
|
||||
@state
|
||||
{:details (if (= (:type @state) :default-keypair) default-details other-details)
|
||||
:accounts (get-accounts (:blur? @state))})]]])))
|
||||
{:state state
|
||||
:descriptor descriptor
|
||||
:blur? (:blur? @state)
|
||||
:show-blur-background? true
|
||||
:blur-dark-only? true
|
||||
:blur-height 400
|
||||
:component-container-style {:padding-vertical 30
|
||||
:flex-direction :row
|
||||
:justify-content :center}}
|
||||
[quo/keypair
|
||||
(merge
|
||||
@state
|
||||
{:details (if (= (:type @state) :default-keypair) default-details other-details)
|
||||
:accounts (get-accounts (:blur? @state))})]])))
|
||||
|
||||
@@ -1,32 +1,24 @@
|
||||
(ns status-im2.contexts.quo-preview.wallet.network-amount
|
||||
(:require
|
||||
[quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.contexts.quo-preview.preview :as preview]))
|
||||
|
||||
|
||||
(def descriptor
|
||||
[{:label "Amount:"
|
||||
:key :amount
|
||||
:type :text}
|
||||
{:label "Token:"
|
||||
:key :token
|
||||
[{:key :amount :type :text}
|
||||
{:key :token
|
||||
:type :select
|
||||
:options [{:key :eth
|
||||
:value "ETH"}
|
||||
{:key :snt
|
||||
:value "SNT"}]}])
|
||||
:options [{:key :eth}
|
||||
{:key :snt}]}])
|
||||
|
||||
(defn preview
|
||||
(defn view
|
||||
[]
|
||||
(let [state (reagent/atom {:amount "5.123456"
|
||||
:token :eth})]
|
||||
(fn []
|
||||
[preview/preview-container
|
||||
{:state state
|
||||
:descriptor descriptor}
|
||||
[rn/view
|
||||
{:style {:flex 1
|
||||
:padding-horizontal 20}}
|
||||
[quo/network-amount @state]]])))
|
||||
{:state state
|
||||
:descriptor descriptor
|
||||
:component-container-style {:padding-horizontal 20}}
|
||||
[quo/network-amount @state]])))
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
(ns status-im2.contexts.quo-preview.wallet.network-bridge
|
||||
(:require
|
||||
[quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.contexts.quo-preview.preview :as preview]))
|
||||
|
||||
|
||||
(def descriptor
|
||||
[{:label "Network:"
|
||||
:key :network
|
||||
[{:key :network
|
||||
:type :select
|
||||
:options [{:key :ethereum
|
||||
:value "Ethereum"}
|
||||
{:key :optimism
|
||||
:value "Optimism"}
|
||||
{:key :arbitrum
|
||||
:value "Arbitrum"}]}
|
||||
{:label "Status:"
|
||||
:key :status
|
||||
:options [{:key :ethereum}
|
||||
{:key :optimism}
|
||||
{:key :arbitrum}]}
|
||||
{:key :status
|
||||
:type :select
|
||||
:options [{:key :default :value :default}
|
||||
{:key :locked :value :locked}
|
||||
@@ -25,19 +19,16 @@
|
||||
{:key :disabled :value :disabled}
|
||||
{:key :add :value :add}]}])
|
||||
|
||||
(defn preview
|
||||
(defn view
|
||||
[]
|
||||
(let [state (reagent/atom {:network :ethereum
|
||||
:status :default
|
||||
:amount "50 SNT"})]
|
||||
(fn []
|
||||
[preview/preview-container
|
||||
{:state state
|
||||
:descriptor descriptor}
|
||||
[rn/view
|
||||
{:style {:flex 1
|
||||
:padding-horizontal 20}}
|
||||
[rn/view
|
||||
{:style {:flex 1
|
||||
:margin-top 50
|
||||
:align-items :center}} [quo/network-bridge @state]]]])))
|
||||
{:state state
|
||||
:descriptor descriptor
|
||||
:component-container-style {:padding-horizontal 20
|
||||
:margin-top 50
|
||||
:align-items :center}}
|
||||
[quo/network-bridge @state]])))
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
(ns status-im2.contexts.quo-preview.wallet.network-link
|
||||
(:require
|
||||
[quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.contexts.quo-preview.preview :as preview]))
|
||||
|
||||
@@ -14,35 +13,27 @@
|
||||
:vault "Arbitrum"}])
|
||||
|
||||
(def descriptor
|
||||
[{:label "Shape:"
|
||||
:key :shape
|
||||
[{:key :shape
|
||||
:type :select
|
||||
:options [{:key :linear
|
||||
:value "Linear"}
|
||||
{:key :1x
|
||||
:value "1x"}
|
||||
{:key :2x
|
||||
:value "2x"}]}
|
||||
{:label "Source:"
|
||||
:key :source
|
||||
:options [{:key :linear}
|
||||
{:key :1x}
|
||||
{:key :2x}]}
|
||||
{:key :source
|
||||
:type :select
|
||||
:options networks}
|
||||
{:label "Destination:"
|
||||
:key :destination
|
||||
{:key :destination
|
||||
:type :select
|
||||
:options networks}])
|
||||
|
||||
(defn preview
|
||||
(defn view
|
||||
[]
|
||||
(let [state (reagent/atom {:shape :linear
|
||||
:source :ethereum
|
||||
:destination :optimism})]
|
||||
(fn []
|
||||
[preview/preview-container
|
||||
{:state state
|
||||
:descriptor descriptor}
|
||||
[rn/view
|
||||
{:style {:flex 1
|
||||
:padding-top 40
|
||||
:align-items :center}}
|
||||
[quo/network-link @state]]])))
|
||||
{:state state
|
||||
:descriptor descriptor
|
||||
:component-container-style {:padding-top 40
|
||||
:align-items :center}}
|
||||
[quo/network-link @state]])))
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
[status-im2.contexts.quo-preview.preview :as preview]))
|
||||
|
||||
(def descriptor
|
||||
[{:label "Number of networks"
|
||||
:key :number-networks
|
||||
[{:key :number-networks
|
||||
:type :select
|
||||
:options [{:key 2} {:key 3} {:key 4} {:key 5}]}])
|
||||
|
||||
@@ -78,7 +77,7 @@
|
||||
[quo/text "Total amount: " (reduce + (map :amount asked-networks))]]
|
||||
asked-networks)]))))
|
||||
|
||||
(defn preview
|
||||
(defn view
|
||||
[]
|
||||
(let [descriptor-state (reagent/atom {:total-amount 400
|
||||
:number-networks 4
|
||||
@@ -88,7 +87,8 @@
|
||||
:max-amount-3 250
|
||||
:max-amount-4 200})]
|
||||
(fn []
|
||||
[preview/preview-container {:state descriptor-state :descriptor descriptor}
|
||||
[rn/view {:style {:flex 1 :margin-vertical 28}}
|
||||
^{:key (str "preview-network-routing-" (:number-networks @descriptor-state))}
|
||||
[preview-internal @descriptor-state]]])))
|
||||
[preview/preview-container
|
||||
{:state descriptor-state
|
||||
:descriptor descriptor
|
||||
:component-container-style {:margin-vertical 28}}
|
||||
[preview-internal @descriptor-state]])))
|
||||
|
||||
@@ -1,34 +1,26 @@
|
||||
(ns status-im2.contexts.quo-preview.wallet.progress-bar
|
||||
(:require
|
||||
[quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.contexts.quo-preview.preview :as preview]))
|
||||
|
||||
(def descriptor
|
||||
[{:label "State:"
|
||||
:key :state
|
||||
[{:key :state
|
||||
:type :select
|
||||
:options [{:key :pending
|
||||
:value "pending"}
|
||||
{:key :confirmed
|
||||
:value "confirmed"}
|
||||
{:key :finalized
|
||||
:value "finalized"}
|
||||
{:key :error
|
||||
:value "error"}]}
|
||||
:options [{:key :pending}
|
||||
{:key :confirmed}
|
||||
{:key :finalized}
|
||||
{:key :error}]}
|
||||
(preview/customization-color-option)])
|
||||
|
||||
(defn preview
|
||||
(defn view
|
||||
[]
|
||||
(let [state (reagent/atom {:state :pending
|
||||
:customization-color :blue})]
|
||||
(fn []
|
||||
[preview/preview-container
|
||||
{:state state
|
||||
:descriptor descriptor}
|
||||
[rn/view
|
||||
{:style {:flex 1
|
||||
:padding-top 40
|
||||
:align-items :center}}
|
||||
[quo/progress-bar @state]]])))
|
||||
{:state state
|
||||
:descriptor descriptor
|
||||
:component-container-style {:padding-top 40
|
||||
:align-items :center}}
|
||||
[quo/progress-bar @state]])))
|
||||
|
||||
@@ -1,30 +1,22 @@
|
||||
(ns status-im2.contexts.quo-preview.wallet.summary-info
|
||||
(: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]))
|
||||
|
||||
|
||||
(def descriptor
|
||||
[{:label "Type:"
|
||||
:key :type
|
||||
[{:key :type
|
||||
:type :select
|
||||
:options [{:key :status-account
|
||||
:value "Status Account"}
|
||||
{:key :user
|
||||
:value "User"}
|
||||
{:key :saved-account
|
||||
:value "Saved Account"}
|
||||
{:key :account
|
||||
:value "Account"}]}
|
||||
{:label "Networks?:"
|
||||
:key :networks?
|
||||
:type :boolean}])
|
||||
:options [{:key :status-account}
|
||||
{:key :user}
|
||||
{:key :saved-account}
|
||||
{:key :account}]}
|
||||
{:key :networks? :type :boolean}])
|
||||
|
||||
|
||||
(defn preview
|
||||
(defn view
|
||||
[]
|
||||
(let [state (reagent/atom {:type :status-account
|
||||
:networks? true
|
||||
@@ -48,9 +40,7 @@
|
||||
(fn []
|
||||
(let [account-props (if (= (:type @state) :status-account) status-account-props user-props)]
|
||||
[preview/preview-container
|
||||
{:state state
|
||||
:descriptor descriptor}
|
||||
[rn/view
|
||||
{:style {:flex 1
|
||||
:padding-horizontal 20}}
|
||||
[quo/summary-info (merge @state {:account-props account-props})]]]))))
|
||||
{:state state
|
||||
:descriptor descriptor
|
||||
:component-container-style {:padding-horizontal 20}}
|
||||
[quo/summary-info (merge @state {:account-props account-props})]]))))
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
(:require
|
||||
[quo.core :as quo]
|
||||
[quo.foundations.resources :as resources]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.contexts.quo-preview.preview :as preview]))
|
||||
|
||||
@@ -14,22 +13,16 @@
|
||||
(def title "Max: 200 SNT")
|
||||
|
||||
(def descriptor
|
||||
[{:label "Token:"
|
||||
:key :token
|
||||
[{:key :token
|
||||
:type :select
|
||||
:options [{:key :eth
|
||||
:value "ETH"}
|
||||
{:key :snt
|
||||
:value "SNT"}]}
|
||||
{:label "Currency:"
|
||||
:key :currency
|
||||
:options [{:key :eth}
|
||||
{:key :snt}]}
|
||||
{:key :currency
|
||||
:type :select
|
||||
:options [{:key :usd
|
||||
:value "USD"}
|
||||
{:key :eur
|
||||
:value "EUR"}]}])
|
||||
:options [{:key :usd}
|
||||
{:key :eur}]}])
|
||||
|
||||
(defn preview
|
||||
(defn view
|
||||
[]
|
||||
(let [state (reagent/atom {:token :eth
|
||||
:currency :usd
|
||||
@@ -39,13 +32,9 @@
|
||||
:customization-color :blue})]
|
||||
(fn []
|
||||
[preview/preview-container
|
||||
{:state state
|
||||
:descriptor descriptor}
|
||||
[rn/view
|
||||
{:style {:flex 1
|
||||
:padding-horizontal 20}}
|
||||
[rn/view
|
||||
{:style {:flex 1
|
||||
:margin-top 50
|
||||
:align-items :center}}
|
||||
[quo/token-input @state]]]])))
|
||||
{:state state
|
||||
:descriptor descriptor
|
||||
:component-container-style {:padding-horizontal 20
|
||||
:margin-top 50
|
||||
:align-items :center}}
|
||||
[quo/token-input @state]])))
|
||||
|
||||
@@ -222,7 +222,7 @@
|
||||
{:state component-state
|
||||
:descriptor descriptor
|
||||
:show-blur-background? true
|
||||
:component-container-style {:align-self :center}}
|
||||
:component-container-style {:padding-bottom 50}}
|
||||
[quo/transaction-summary
|
||||
(merge {:on-press #(js/alert "Dropdown pressed")}
|
||||
@component-state)]])))
|
||||
|
||||
@@ -2,25 +2,18 @@
|
||||
(:require
|
||||
[quo.core :as quo]
|
||||
[quo.foundations.resources :as quo.resources]
|
||||
[react-native.core :as rn]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.contexts.quo-preview.preview :as preview]))
|
||||
|
||||
(def descriptor
|
||||
[{:label "State"
|
||||
:key :state
|
||||
[{:key :state
|
||||
:type :select
|
||||
:options [{:key :loading
|
||||
:value "Loading"}
|
||||
{:key :default
|
||||
:value "Default"}]}
|
||||
{:label "Time frame"
|
||||
:key :time-frame
|
||||
:options [{:key :loading}
|
||||
{:key :default}]}
|
||||
{:key :time-frame
|
||||
:type :select
|
||||
:options [{:key :none
|
||||
:value "None"}
|
||||
{:key :selected
|
||||
:value "Selected"}
|
||||
:options [{:key :none}
|
||||
{:key :selected}
|
||||
{:key :one-week
|
||||
:value "1 Week"}
|
||||
{:key :one-month
|
||||
@@ -31,24 +24,19 @@
|
||||
:value "1 Year"}
|
||||
{:key :all-time
|
||||
:value "All time"}
|
||||
{:key :custom
|
||||
:value "Custom"}]}
|
||||
{:label "Metrics"
|
||||
:key :metrics
|
||||
{:key :custom}]}
|
||||
{:key :metrics
|
||||
:type :select
|
||||
:options [{:key :none
|
||||
:value "None"}
|
||||
{:key :positive
|
||||
:value "Positive"}
|
||||
{:key :negative
|
||||
:value "Negative"}]}])
|
||||
:options [{:key :none}
|
||||
{:key :positive}
|
||||
{:key :negative}]}])
|
||||
|
||||
(def ^:private networks-list
|
||||
[{:source (quo.resources/get-network :ethereum)}
|
||||
{:source (quo.resources/get-network :optimism)}
|
||||
{:source (quo.resources/get-network :arbitrum)}])
|
||||
|
||||
(defn preview-wallet-overview
|
||||
(defn view
|
||||
[]
|
||||
(let [state (reagent/atom {:state :default
|
||||
:time-frame :one-week
|
||||
@@ -61,14 +49,12 @@
|
||||
:percentage-change "0.00%"})]
|
||||
(fn []
|
||||
[preview/preview-container
|
||||
{:state state
|
||||
:descriptor descriptor}
|
||||
[rn/view {:padding-bottom 150}
|
||||
[rn/view
|
||||
{:padding-vertical 60
|
||||
:flex-direction :row
|
||||
:justify-content :center}
|
||||
[quo/wallet-overview
|
||||
(assoc @state
|
||||
:networks networks-list
|
||||
:dropdown-on-press #(js/alert "On pressed dropdown"))]]]])))
|
||||
{:state state
|
||||
:descriptor descriptor
|
||||
:component-container-style {:padding-vertical 60
|
||||
:flex-direction :row
|
||||
:justify-content :center}}
|
||||
[quo/wallet-overview
|
||||
(assoc @state
|
||||
:networks networks-list
|
||||
:dropdown-on-press #(js/alert "On pressed dropdown"))]])))
|
||||
|
||||
@@ -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
|
||||
[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]])
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
(ns status-im2.contexts.wallet.account.style)
|
||||
|
||||
(def container
|
||||
{:flex 1})
|
||||
|
||||
(def tabs
|
||||
{:padding-left 20
|
||||
:padding-vertical 12})
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
(:require
|
||||
[quo.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.contexts.wallet.account.style :as style]
|
||||
[status-im2.contexts.wallet.account.tabs.view :as tabs]
|
||||
[status-im2.contexts.wallet.common.temp :as temp]
|
||||
[status-im2.contexts.wallet.common.utils :as utils]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
@@ -57,14 +57,13 @@
|
||||
{:id :about :label (i18n/label :t/about) :accessibility-label :about}])
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [top (safe-area/get-top)
|
||||
selected-tab (reagent/atom (:id (first tabs-data)))]
|
||||
[account-address]
|
||||
(let [selected-tab (reagent/atom (:id (first tabs-data)))]
|
||||
(fn []
|
||||
(let [networks (rf/sub [:wallet/network-details])]
|
||||
[rn/view
|
||||
{:style {:flex 1
|
||||
:margin-top top}}
|
||||
(let [account-address (or account-address (rf/sub [:get-screen-params :wallet-accounts]))
|
||||
account (rf/sub [:wallet/account account-address])
|
||||
networks (rf/sub [:wallet/network-details])]
|
||||
[rn/view {:style style/container}
|
||||
[quo/page-nav
|
||||
{:type :wallet-networks
|
||||
:background :blur
|
||||
@@ -79,7 +78,11 @@
|
||||
:gradient-cover? true
|
||||
:customization-color :purple}])
|
||||
:emoji "🍑"}}]
|
||||
[quo/account-overview temp/account-overview-state]
|
||||
[quo/account-overview
|
||||
{:current-value (utils/prettify-balance (:balance account))
|
||||
:account-name (:name account)
|
||||
:account :default
|
||||
:customization-color :blue}]
|
||||
[quo/wallet-graph {:time-frame :empty}]
|
||||
[quo/wallet-ctas
|
||||
{:send-action #(rf/dispatch [:open-modal :wallet-select-address])
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
:data networks-list}]
|
||||
[quo/bottom-actions
|
||||
{:button-one-label (i18n/label :t/update)
|
||||
:disabled? true
|
||||
:button-one-press on-save}]])
|
||||
:button-one-props {:disabled? true
|
||||
:on-press on-save}}]])
|
||||
|
||||
(def view (quo.theme/with-theme view-internal))
|
||||
|
||||
@@ -12,8 +12,9 @@
|
||||
on-change-color
|
||||
on-change-emoji section-label bottom-action? bottom-action-label bottom-action-props
|
||||
custom-bottom-action]} & children]
|
||||
(let [{:keys [top bottom]} (safe-area/get-insets)
|
||||
margin-top (if (false? margin-top?) 0 top)]
|
||||
(let [{:keys [top bottom]} (safe-area/get-insets)
|
||||
margin-top (if (false? margin-top?) 0 top)
|
||||
{window-width :width} (rn/get-window)]
|
||||
[rn/keyboard-avoiding-view
|
||||
{:style (style/root-container margin-top)
|
||||
:keyboard-vertical-offset (- bottom)}
|
||||
@@ -58,7 +59,8 @@
|
||||
[quo/color-picker
|
||||
{:default-selected account-color
|
||||
:on-change on-change-color
|
||||
:container-style style/color-picker}]]
|
||||
:container-style style/color-picker
|
||||
:window-width window-width}]]
|
||||
[quo/divider-line {:container-style style/divider-2}]
|
||||
(when section-label
|
||||
[quo/section-label
|
||||
|
||||
@@ -6,6 +6,30 @@
|
||||
[full-name]
|
||||
(first (string/split full-name #" ")))
|
||||
|
||||
(defn get-balance-by-address
|
||||
[balances address]
|
||||
(->> balances
|
||||
(filter #(= (:address %) address))
|
||||
first
|
||||
:balance))
|
||||
|
||||
(defn get-account-by-address
|
||||
[accounts address]
|
||||
(some #(when (= (:address %) address) %) accounts))
|
||||
|
||||
(defn prettify-balance
|
||||
[balance]
|
||||
(str "$" (.toFixed (if (number? balance) balance 0) 2)))
|
||||
|
||||
(defn get-derivation-path
|
||||
[number-of-accounts]
|
||||
(str constants/path-wallet-root "/" number-of-accounts))
|
||||
|
||||
(defn format-derivation-path
|
||||
[path]
|
||||
(string/replace path "/" " / "))
|
||||
|
||||
(defn get-formatted-derivation-path
|
||||
[number-of-accounts]
|
||||
(let [path (get-derivation-path number-of-accounts)]
|
||||
(format-derivation-path path)))
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
(ns status-im2.contexts.wallet.create-account.edit-derivation-path.component-spec
|
||||
(:require
|
||||
[status-im2.contexts.wallet.create-account.edit-derivation-path.view :as edit-derivation-path]
|
||||
[test-helpers.component :as h]))
|
||||
|
||||
(h/describe "Edit derivation path page"
|
||||
(h/test "Default render"
|
||||
(h/render [edit-derivation-path/view {}])
|
||||
(h/is-truthy (h/get-by-translation-text :t/edit-derivation-path))
|
||||
(h/is-truthy (h/get-by-translation-text :t/path-format))
|
||||
(h/is-truthy (h/get-by-translation-text :t/derivation-path))
|
||||
(h/is-truthy (h/get-by-translation-text :t/reveal-address))
|
||||
(h/is-truthy (h/get-by-translation-text :t/save)))
|
||||
|
||||
|
||||
(h/test "Reveal address pressed"
|
||||
(let [on-reveal (h/mock-fn)]
|
||||
(h/render [edit-derivation-path/view {:on-reveal on-reveal}])
|
||||
(h/fire-event :press (h/get-by-translation-text :t/reveal-address))
|
||||
(h/was-called on-reveal)
|
||||
(h/wait-for #(h/is-truthy (h/get-by-translation-text :t/address-activity)))))
|
||||
|
||||
(h/test "Reset button pressed"
|
||||
(let [on-reset (h/mock-fn)]
|
||||
(h/render [edit-derivation-path/view {:on-reset on-reset}])
|
||||
(h/fire-event :press (h/get-by-translation-text :t/reset))
|
||||
(h/was-called on-reset)
|
||||
(h/wait-for #(h/is-truthy (h/get-by-translation-text :t/derive-addresses))))))
|
||||
@@ -0,0 +1,49 @@
|
||||
(ns status-im2.contexts.wallet.create-account.edit-derivation-path.style
|
||||
(:require [quo.foundations.colors :as colors]))
|
||||
|
||||
(defn screen
|
||||
[top]
|
||||
{:flex 1
|
||||
:margin-top top})
|
||||
|
||||
(def header
|
||||
{:padding-horizontal 20
|
||||
:padding-top 12
|
||||
:padding-bottom 8})
|
||||
|
||||
(def tag
|
||||
{:padding-horizontal 20
|
||||
:flex-direction :row})
|
||||
|
||||
(def input-container
|
||||
{:padding-horizontal 20
|
||||
:padding-top 12})
|
||||
|
||||
(defn save-button-container
|
||||
[bottom]
|
||||
{:flex 1
|
||||
:justify-content :flex-end
|
||||
:padding-bottom bottom})
|
||||
|
||||
(def revealed-address-container
|
||||
{:padding-horizontal 20
|
||||
:padding-top 24})
|
||||
|
||||
(defn revealed-address
|
||||
[theme]
|
||||
{:border-width 1
|
||||
:border-color (colors/resolve-color :success theme 40)
|
||||
:border-style :dashed
|
||||
:border-radius 16
|
||||
:padding-horizontal 12
|
||||
:padding-vertical 7})
|
||||
|
||||
(def info
|
||||
{:margin-vertical 9
|
||||
:padding-left 2})
|
||||
|
||||
(def temporal-placeholder
|
||||
{:height 94
|
||||
:background-color colors/danger-50
|
||||
:align-items :center
|
||||
:justify-content :center})
|
||||
@@ -0,0 +1,120 @@
|
||||
(ns status-im2.contexts.wallet.create-account.edit-derivation-path.view
|
||||
(:require
|
||||
[quo.core :as quo]
|
||||
[quo.theme :as quo.theme]
|
||||
[react-native.core :as rn]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.contexts.wallet.common.temp :as temp]
|
||||
[status-im2.contexts.wallet.common.utils :as utils]
|
||||
[status-im2.contexts.wallet.create-account.edit-derivation-path.style :as style]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(defn- view-internal
|
||||
"States:
|
||||
default(filled)
|
||||
| -> (reveal-action) -> show
|
||||
| -> (clear-action) -> empty -> (derive-action) -> choose -> (choose-action) -> show"
|
||||
[{:keys [on-reset on-reveal]}]
|
||||
(let [top (safe-area/get-top)
|
||||
bottom (safe-area/get-bottom)
|
||||
state (reagent/atom :default)
|
||||
reveal-action (fn [_]
|
||||
(reset! state :show)
|
||||
(when on-reveal
|
||||
(on-reveal)))
|
||||
clear-action #(reset! state :empty)
|
||||
derive-action #(reset! state :choose)
|
||||
choose-action #(reset! state :show)
|
||||
path-value (reagent/atom (utils/get-formatted-derivation-path 3))
|
||||
handle-path-change (fn [v]
|
||||
(reset! path-value v)
|
||||
(when (empty? v)
|
||||
(clear-action)))
|
||||
reset-path-value (fn [_]
|
||||
(reset! path-value "")
|
||||
(clear-action)
|
||||
(when on-reset
|
||||
(on-reset)))]
|
||||
(fn [{:keys [theme]}]
|
||||
[rn/view
|
||||
{:style (style/screen top)}
|
||||
[quo/page-nav
|
||||
{:background :blur
|
||||
:icon-name :i/close
|
||||
:on-press #(rf/dispatch [:navigate-back])}]
|
||||
[quo/text
|
||||
{:size :heading-1
|
||||
:weight :semi-bold
|
||||
:style style/header}
|
||||
(i18n/label :t/edit-derivation-path)]
|
||||
[rn/view {:style style/tag}
|
||||
[quo/context-tag
|
||||
{:type :icon
|
||||
:size 24
|
||||
:icon :i/placeholder
|
||||
:style style/tag
|
||||
:context "Alisher Card"}]]
|
||||
[rn/view {:style style/temporal-placeholder}
|
||||
[quo/text "Dropdown input will be here"]
|
||||
[quo/text (i18n/label :t/path-format)]]
|
||||
[quo/input
|
||||
{:container-style style/input-container
|
||||
:value @path-value
|
||||
:label (i18n/label :t/derivation-path)
|
||||
:placeholder (utils/get-formatted-derivation-path 3)
|
||||
:button {:on-press reset-path-value
|
||||
:text (i18n/label :t/reset)}
|
||||
:on-change-text handle-path-change}]
|
||||
|
||||
(case @state
|
||||
:default
|
||||
[quo/bottom-actions
|
||||
{:theme theme
|
||||
:actions :1-action
|
||||
:button-one-label (i18n/label :t/reveal-address)
|
||||
:button-one-props {:type :outline
|
||||
:icon-left :i/keycard-card
|
||||
:on-press reveal-action}}]
|
||||
|
||||
:empty
|
||||
[quo/bottom-actions
|
||||
{:theme theme
|
||||
:actions :1-action
|
||||
:button-one-label (i18n/label :t/derive-addresses)
|
||||
:button-one-props {:type :outline
|
||||
:icon-left :i/keycard-card
|
||||
:on-press derive-action}}]
|
||||
|
||||
:show
|
||||
[rn/view {:style style/revealed-address-container}
|
||||
[rn/view {:style (style/revealed-address theme)}
|
||||
[quo/text
|
||||
{:weight :monospace}
|
||||
temp/address]]
|
||||
[quo/info-message
|
||||
{:type :success
|
||||
:icon :i/done
|
||||
:style style/info}
|
||||
(i18n/label :t/address-activity)]]
|
||||
|
||||
:choose
|
||||
[rn/view {:style style/temporal-placeholder}
|
||||
[quo/text "Dropdown input will be here"]
|
||||
[quo/button
|
||||
{:on-press (fn [_]
|
||||
(reset! path-value (utils/get-formatted-derivation-path 1))
|
||||
(choose-action))} "Choose"]]
|
||||
nil)
|
||||
|
||||
[rn/view {:style (style/save-button-container bottom)}
|
||||
[quo/bottom-actions
|
||||
{:theme theme
|
||||
:actions :1-action
|
||||
:button-one-label (i18n/label :t/save)
|
||||
:button-one-props {:type :primary
|
||||
:on-press #(js/alert "Save!")
|
||||
:disabled? true}}]]])))
|
||||
|
||||
(def view (quo.theme/with-theme view-internal))
|
||||
@@ -9,7 +9,8 @@
|
||||
[status-im2.contexts.wallet.common.utils :as utils]
|
||||
[status-im2.contexts.wallet.create-account.style :as style]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
[utils.re-frame :as rf]
|
||||
[utils.responsiveness :refer [iphone-11-Pro-20-pixel-from-width]]))
|
||||
|
||||
(def diamond-emoji "\uD83D\uDC8E")
|
||||
|
||||
@@ -21,29 +22,37 @@
|
||||
(defn get-keypair-data
|
||||
[name derivation-path]
|
||||
[{:title (keypair-string name)
|
||||
:button-props {:title (i18n/label :t/edit)}
|
||||
:left-icon :i/placeholder
|
||||
:action :button
|
||||
:action-props {:on-press #(js/alert "Button pressed!")
|
||||
:button-text (i18n/label :t/edit)
|
||||
:alignment :flex-start}
|
||||
:description :text
|
||||
:description-props {:text (i18n/label :t/on-device)}}
|
||||
{:title (i18n/label :t/derivation-path)
|
||||
:button-props {:title (i18n/label :t/edit)}
|
||||
:action :button
|
||||
:action-props {:on-press #(rf/dispatch [:navigate-to :wallet-edit-derivation-path])
|
||||
:button-text (i18n/label :t/edit)
|
||||
:icon-left :i/placeholder
|
||||
:alignment :flex-start}
|
||||
:left-icon :i/derivated-path
|
||||
:description :text
|
||||
:description-props {:text derivation-path}}])
|
||||
|
||||
(defn- view-internal
|
||||
[]
|
||||
(let [top (safe-area/get-top)
|
||||
bottom (safe-area/get-bottom)
|
||||
account-color (reagent/atom :blue)
|
||||
emoji (reagent/atom diamond-emoji)
|
||||
number-of-accounts (count (rf/sub [:profile/wallet-accounts]))
|
||||
account-name (reagent/atom (i18n/label :t/default-account-name
|
||||
{:number (inc number-of-accounts)}))
|
||||
derivation-path (reagent/atom (utils/get-derivation-path number-of-accounts))
|
||||
{:keys [public-key]} (rf/sub [:profile/profile])
|
||||
on-change-text #(reset! account-name %)
|
||||
display-name (first (rf/sub [:contacts/contact-two-names-by-identity public-key]))]
|
||||
(let [top (safe-area/get-top)
|
||||
bottom (safe-area/get-bottom)
|
||||
account-color (reagent/atom :blue)
|
||||
emoji (reagent/atom diamond-emoji)
|
||||
number-of-accounts (count (rf/sub [:profile/wallet-accounts]))
|
||||
account-name (reagent/atom (i18n/label :t/default-account-name
|
||||
{:number (inc number-of-accounts)}))
|
||||
derivation-path (reagent/atom (utils/get-derivation-path number-of-accounts))
|
||||
{:keys [public-key]} (rf/sub [:profile/profile])
|
||||
on-change-text #(reset! account-name %)
|
||||
display-name (first (rf/sub [:contacts/contact-two-names-by-identity public-key]))
|
||||
{window-width :width} (rn/get-window)]
|
||||
(fn [{:keys [theme]}]
|
||||
[rn/view
|
||||
{:style {:flex 1
|
||||
@@ -93,8 +102,8 @@
|
||||
[quo/color-picker
|
||||
{:default-selected @account-color
|
||||
:on-change #(reset! account-color %)
|
||||
:container-style {:padding-horizontal 12
|
||||
:padding-vertical 12}}]]
|
||||
:container-style {:padding-vertical 12
|
||||
:padding-left (iphone-11-Pro-20-pixel-from-width window-width)}}]]
|
||||
[quo/divider-line]
|
||||
[quo/category
|
||||
{:list-type :settings
|
||||
|
||||
@@ -1,11 +1,31 @@
|
||||
(ns status-im2.contexts.wallet.events
|
||||
(:require
|
||||
[native-module.core :as native-module]
|
||||
[status-im2.data-store.wallet :as data-store]
|
||||
[status-im2.common.data-store.wallet :as data-store]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.security.core :as security]))
|
||||
|
||||
(rf/defn get-wallet-token
|
||||
{:events [:wallet/get-wallet-token]}
|
||||
[{:keys [db]}]
|
||||
(let [params (map :address (:profile/wallet-accounts db))]
|
||||
{:json-rpc/call [{:method "wallet_getWalletToken"
|
||||
:params [params]
|
||||
:on-success #(rf/dispatch [:wallet/get-wallet-token-success %])
|
||||
:on-error (fn [error]
|
||||
(log/info "failed to get wallet token"
|
||||
{:event :wallet/get-wallet-token
|
||||
:error error
|
||||
:params params}))}]}))
|
||||
|
||||
(rf/defn get-wallet-token-success
|
||||
{:events [:wallet/get-wallet-token-success]}
|
||||
[{:keys [db]} data]
|
||||
{:db (assoc db
|
||||
:wallet/tokens data
|
||||
:wallet/tokens-loading? false)})
|
||||
|
||||
(rf/defn scan-address-success
|
||||
{:events [:wallet/scan-address-success]}
|
||||
[{:keys [db]} address]
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
[status-im2.contexts.wallet.common.activity-tab.view :as activity]
|
||||
[status-im2.contexts.wallet.common.collectibles-tab.view :as collectibles]
|
||||
[status-im2.contexts.wallet.common.temp :as temp]
|
||||
[status-im2.contexts.wallet.common.token-value.view :as token-value]
|
||||
[status-im2.contexts.wallet.common.utils :as utils]
|
||||
[status-im2.contexts.wallet.home.style :as style]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]))
|
||||
@@ -28,30 +28,43 @@
|
||||
:on-press #(rf/dispatch [:navigate-to :wallet-address-watch])
|
||||
:add-divider? true}]]])
|
||||
|
||||
(def account-cards
|
||||
[{:name "Account 1"
|
||||
:balance "€0.00"
|
||||
:percentage-value "€0.00"
|
||||
:customization-color :blue
|
||||
:type :empty
|
||||
:emoji "🍑"
|
||||
:on-press #(rf/dispatch [:navigate-to :wallet-accounts])}
|
||||
{:customization-color :blue
|
||||
:on-press #(rf/dispatch
|
||||
[:show-bottom-sheet {:content new-account}])
|
||||
:type :add-account}])
|
||||
(defn- add-account-placeholder
|
||||
[color]
|
||||
{:customization-color color
|
||||
:on-press #(rf/dispatch [:show-bottom-sheet {:content new-account}])
|
||||
:type :add-account})
|
||||
|
||||
(def tabs-data
|
||||
[{:id :assets :label (i18n/label :t/assets) :accessibility-label :assets-tab}
|
||||
{:id :collectibles :label (i18n/label :t/collectibles) :accessibility-label :collectibles-tab}
|
||||
{:id :activity :label (i18n/label :t/activity) :accessibility-label :activity-tab}])
|
||||
|
||||
(defn account-cards
|
||||
[{:keys [accounts loading? balances profile]}]
|
||||
(let [accounts-with-balances
|
||||
(mapv
|
||||
(fn [account]
|
||||
(assoc account
|
||||
:type :empty
|
||||
:customization-color (:customization-color profile)
|
||||
:on-press #(rf/dispatch [:navigate-to :wallet-accounts (:address account)])
|
||||
:loading? loading?
|
||||
:balance (utils/prettify-balance
|
||||
(utils/get-balance-by-address balances (:address account)))))
|
||||
accounts)]
|
||||
(conj accounts-with-balances (add-account-placeholder (:customization-color profile)))))
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [top (safe-area/get-top)
|
||||
selected-tab (reagent/atom (:id (first tabs-data)))]
|
||||
(rf/dispatch [:wallet/get-wallet-token])
|
||||
(let [selected-tab (reagent/atom (:id (first tabs-data)))]
|
||||
(fn []
|
||||
(let [networks (rf/sub [:wallet/network-details])]
|
||||
(let [accounts (rf/sub [:profile/wallet-accounts])
|
||||
top (safe-area/get-top)
|
||||
loading? (rf/sub [:wallet/tokens-loading?])
|
||||
balances (rf/sub [:wallet/balances])
|
||||
profile (rf/sub [:profile/profile])
|
||||
networks (rf/sub [:wallet/network-details])]
|
||||
[rn/view
|
||||
{:style {:margin-top top
|
||||
:flex 1}}
|
||||
@@ -62,13 +75,15 @@
|
||||
{:on-long-press #(rf/dispatch [:show-bottom-sheet
|
||||
{:content temp/wallet-temporary-navigation}])}
|
||||
[quo/wallet-graph {:time-frame :empty}]]
|
||||
[rn/view {:style style/accounts-container}
|
||||
[rn/flat-list
|
||||
{:style style/accounts-list
|
||||
:data account-cards
|
||||
:horizontal true
|
||||
:separator [rn/view {:style {:width 12}}]
|
||||
:render-fn quo/account-card}]]
|
||||
[rn/flat-list
|
||||
{:style style/accounts-list
|
||||
:data (account-cards {:accounts accounts
|
||||
:loading? loading?
|
||||
:balances balances
|
||||
:profile profile})
|
||||
:horizontal true
|
||||
:separator [rn/view {:style {:width 12}}]
|
||||
:render-fn quo/account-card}]
|
||||
[quo/tabs
|
||||
{:style style/tabs
|
||||
:size 32
|
||||
@@ -77,7 +92,7 @@
|
||||
:on-change #(reset! selected-tab %)}]
|
||||
(case @selected-tab
|
||||
:assets [rn/flat-list
|
||||
{:render-fn token-value/view
|
||||
{:render-fn quo/token-value
|
||||
:data temp/tokens
|
||||
:key :assets-list
|
||||
:content-container-style {:padding-horizontal 8}}]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
(ns status-im2.core-spec
|
||||
(:require
|
||||
[status-im2.contexts.chat.messages.content.audio.component-spec]
|
||||
[status-im2.contexts.communities.actions.community-options.component-spec]))
|
||||
[status-im2.contexts.communities.actions.community-options.component-spec]
|
||||
[status-im2.contexts.wallet.create-account.edit-derivation-path.component-spec]))
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
(ns status-im2.navigation.screens
|
||||
(:require
|
||||
[dev.component-preview.view :as component-preview]
|
||||
[status-im.ui.screens.screens :as old-screens]
|
||||
[status-im2.config :as config]
|
||||
[status-im2.contexts.add-new-contact.views :as add-new-contact]
|
||||
@@ -28,6 +27,7 @@
|
||||
[status-im2.contexts.onboarding.syncing.results.view :as syncing-results]
|
||||
[status-im2.contexts.onboarding.welcome.view :as welcome]
|
||||
[status-im2.contexts.profile.profiles.view :as profiles]
|
||||
[status-im2.contexts.quo-preview.component-preview.view :as component-preview]
|
||||
[status-im2.contexts.quo-preview.main :as quo.preview]
|
||||
[status-im2.contexts.shell.activity-center.view :as activity-center]
|
||||
[status-im2.contexts.shell.jump-to.view :as shell]
|
||||
@@ -40,6 +40,7 @@
|
||||
[status-im2.contexts.wallet.account.view :as wallet-accounts]
|
||||
[status-im2.contexts.wallet.address-watch.view :as wallet-address-watch]
|
||||
[status-im2.contexts.wallet.collectible.view :as wallet-collectible]
|
||||
[status-im2.contexts.wallet.create-account.edit-derivation-path.view :as wallet-edit-derivation-path]
|
||||
[status-im2.contexts.wallet.create-account.view :as wallet-create-account]
|
||||
[status-im2.contexts.wallet.edit-account.view :as wallet-edit-account]
|
||||
[status-im2.contexts.wallet.saved-address.view :as wallet-saved-address]
|
||||
@@ -243,6 +244,7 @@
|
||||
:component emoji-picker/view}
|
||||
|
||||
{:name :wallet-accounts
|
||||
:options {:insets {:top? true}}
|
||||
:component wallet-accounts/view}
|
||||
|
||||
{:name :wallet-edit-account
|
||||
@@ -257,6 +259,9 @@
|
||||
{:name :wallet-create-account
|
||||
:component wallet-create-account/view}
|
||||
|
||||
{:name :wallet-edit-derivation-path
|
||||
:component wallet-edit-derivation-path/view}
|
||||
|
||||
{:name :wallet-saved-address
|
||||
:component wallet-saved-address/view}
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
status-im2.subs.pairing
|
||||
status-im2.subs.profile
|
||||
status-im2.subs.shell
|
||||
status-im2.subs.wallet.networks))
|
||||
status-im2.subs.wallet.networks
|
||||
status-im2.subs.wallet.wallet))
|
||||
|
||||
(defn reg-root-key-sub
|
||||
[sub-name db-key]
|
||||
@@ -142,6 +143,10 @@
|
||||
(reg-root-key-sub :communities/selected-tab :communities/selected-tab)
|
||||
(reg-root-key-sub :contract-communities :contract-communities)
|
||||
|
||||
;;wallet
|
||||
(reg-root-key-sub :wallet/tokens :wallet/tokens)
|
||||
(reg-root-key-sub :wallet/tokens-loading? :wallet/tokens-loading?)
|
||||
|
||||
;;activity center
|
||||
(reg-root-key-sub :activity-center :activity-center)
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
(ns status-im2.subs.wallet.wallet
|
||||
(:require [clojure.string :as string]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im2.contexts.wallet.common.utils :as utils]
|
||||
[utils.number]))
|
||||
|
||||
(defn- calculate-raw-balance
|
||||
[raw-balance decimals]
|
||||
(if-let [n (utils.number/parse-int raw-balance nil)]
|
||||
(/ n (Math/pow 10 (utils.number/parse-int decimals)))
|
||||
0))
|
||||
|
||||
(defn- total-per-token
|
||||
[item]
|
||||
(reduce (fn [ac balances]
|
||||
(+ (calculate-raw-balance (:rawBalance balances)
|
||||
(:decimals item))
|
||||
ac))
|
||||
0
|
||||
(vals (:balancesPerChain item))))
|
||||
|
||||
(defn- calculate-balance
|
||||
[address tokens]
|
||||
(let [token (get tokens (keyword (string/lower-case address)))
|
||||
result (reduce
|
||||
(fn [acc item]
|
||||
(let [total-values (* (total-per-token item)
|
||||
(get-in item [:marketValuesPerCurrency :USD :price]))]
|
||||
(+ acc total-values)))
|
||||
0
|
||||
token)]
|
||||
result))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:wallet/balances
|
||||
:<- [:profile/wallet-accounts]
|
||||
:<- [:wallet/tokens]
|
||||
(fn [[accounts tokens]]
|
||||
(for [{:keys [address]} accounts]
|
||||
{:address address
|
||||
:balance (calculate-balance address tokens)})))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:wallet/account
|
||||
:<- [:profile/wallet-accounts]
|
||||
:<- [:wallet/balances]
|
||||
(fn [[accounts balances] [_ account-address]]
|
||||
(assoc
|
||||
(utils/get-account-by-address accounts account-address)
|
||||
:balance
|
||||
(utils/get-balance-by-address balances account-address))))
|
||||
@@ -0,0 +1,83 @@
|
||||
(ns status-im2.subs.wallet.wallet-test
|
||||
(:require [cljs.test :refer [is testing]]
|
||||
[re-frame.db :as rf-db]
|
||||
status-im2.subs.root
|
||||
[test-helpers.unit :as h]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(def tokens
|
||||
{:0x1 [{:decimals 1
|
||||
:symbol "ETH"
|
||||
:name "Ether"
|
||||
:balancesPerChain {:1 {:rawBalance "20"
|
||||
:hasError false}
|
||||
:2 {:rawBalance "10"
|
||||
:hasError false}}
|
||||
:marketValuesPerCurrency {:USD {:price 1000}}} ;; total should be 3000
|
||||
{:decimals 2
|
||||
:symbol "DAI"
|
||||
:name "Dai Stablecoin"
|
||||
:balancesPerChain {:1 {:rawBalance "100"
|
||||
:hasError false}
|
||||
:2 {:rawBalance "150"
|
||||
:hasError false}}
|
||||
:marketValuesPerCurrency {:USD {:price 100}}}] ;; total should be 250
|
||||
:0x2 [{:decimals 3
|
||||
:symbol "ETH"
|
||||
:name "Ether"
|
||||
:balancesPerChain {:1 {:rawBalance "2500"
|
||||
:hasError false}
|
||||
:2 {:rawBalance "3000"
|
||||
:hasError false}
|
||||
:3 {:rawBalance "<nil>"
|
||||
:hasError false}}
|
||||
:marketValuesPerCurrency {:USD {:price 200}}} ;; total should be 1100
|
||||
{:decimals 10
|
||||
:symbol "DAI"
|
||||
:name "Dai Stablecoin"
|
||||
:balancesPerChain {:1 {:rawBalance "10000000000"
|
||||
:hasError false}
|
||||
:2 {:rawBalance "0"
|
||||
:hasError false}
|
||||
:3 {:rawBalance "<nil>"
|
||||
:hasError false}}
|
||||
:marketValuesPerCurrency {:USD {:price 1000}}}]}) ;; total should be 1000
|
||||
|
||||
(def accounts
|
||||
[{:address "0x1"
|
||||
:name "Main account"
|
||||
:hidden false
|
||||
:removed false}
|
||||
{:address "0x2"
|
||||
:name "Secondary account"
|
||||
:hidden false
|
||||
:removed false}])
|
||||
|
||||
(h/deftest-sub :wallet/balances
|
||||
[sub-name]
|
||||
(testing "returns vector of maps containing :address and :balance"
|
||||
(swap! rf-db/app-db assoc
|
||||
:profile/wallet-accounts accounts
|
||||
:wallet/tokens tokens)
|
||||
(is (= [{:address "0x1"
|
||||
:balance 3250}
|
||||
{:address "0x2"
|
||||
:balance 2100}]
|
||||
(rf/sub [sub-name])))))
|
||||
|
||||
(h/deftest-sub :wallet/account
|
||||
[sub-name]
|
||||
(testing "returns current account with balance base on the account-address"
|
||||
(swap! rf-db/app-db assoc
|
||||
:profile/wallet-accounts accounts
|
||||
:wallet/tokens tokens
|
||||
:wallet/balances [{:address "0x1"
|
||||
:balance 3250}
|
||||
{:address "0x2"
|
||||
:balance 2100}])
|
||||
(is (= {:address "0x1"
|
||||
:name "Main account"
|
||||
:hidden false
|
||||
:removed false
|
||||
:balance 3250}
|
||||
(rf/sub [sub-name "0x1"])))))
|
||||
@@ -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))))
|
||||
|
||||
@@ -43,11 +43,11 @@
|
||||
|
||||
(defn get-abbreviated-profile-url
|
||||
"The goal here is to generate a string that begins with
|
||||
status.app/u/ joined with the 1st 5 characters
|
||||
status.app/u# joined with the 1st 5 characters
|
||||
of the compressed public key followed by an ellipsis followed by
|
||||
the last 10 characters of the compressed public key"
|
||||
[base-url public-key]
|
||||
(if (and public-key base-url (> (count public-key) 17) (= "status.app/u/" base-url))
|
||||
(if (and public-key base-url (> (count public-key) 17) (= "status.app/u#" base-url))
|
||||
(let [first-part-of-public-pk (subs public-key 0 5)
|
||||
ellipsis "..."
|
||||
public-key-size (count public-key)
|
||||
|
||||
@@ -22,22 +22,22 @@
|
||||
|
||||
(deftest test-get-abbreviated-profile-url
|
||||
(testing "Ensure the function correctly generates an abbreviated profile URL for a valid public key"
|
||||
(is (= "status.app/u/zQ3sh...mrdYpzeFUa"
|
||||
(is (= "status.app/u#zQ3sh...mrdYpzeFUa"
|
||||
(utils.address/get-abbreviated-profile-url
|
||||
"status.app/u/"
|
||||
"status.app/u#"
|
||||
"zQ3shPrnUhhR42JJn3QdhodGest8w8MjiH8hPaimrdYpzeFUa"))))
|
||||
|
||||
(testing "Ensure the function returns nil when given an empty public key"
|
||||
(is (nil? (utils.address/get-abbreviated-profile-url "status.app/u/" ""))))
|
||||
(is (nil? (utils.address/get-abbreviated-profile-url "status.app/u#" ""))))
|
||||
|
||||
(testing "Ensure the function returns nil when given a nil public key"
|
||||
(is (nil? (utils.address/get-abbreviated-profile-url "status.app/u/" nil))))
|
||||
(is (nil? (utils.address/get-abbreviated-profile-url "status.app/u#" nil))))
|
||||
|
||||
(testing "Ensure the function returns nil when given an incorrect base URL"
|
||||
(is (nil? (utils.address/get-abbreviated-profile-url
|
||||
"status.app/uwu/"
|
||||
"status.app/uwu#"
|
||||
"zQ3shPrnUhhR42JJn3QdhodGest8w8MjiH8hPaimrdYpzeFUa"))))
|
||||
|
||||
(testing "Ensure the function returns nil when given a public key shorter than 17 characters"
|
||||
(is (nil? (utils.address/get-abbreviated-profile-url "status.app/u/" "abc")))
|
||||
(is (nil? (utils.address/get-abbreviated-profile-url "status.app/u/" "1234")))))
|
||||
(is (nil? (utils.address/get-abbreviated-profile-url "status.app/u#" "abc")))
|
||||
(is (nil? (utils.address/get-abbreviated-profile-url "status.app/u#" "1234")))))
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
(def ^:const account-initials-action "/accountInitials")
|
||||
(def ^:const contact-images-action "/contactImages")
|
||||
(def ^:const generate-qr-action "/GenerateQRCode")
|
||||
(def ^:const status-profile-base-url "https://status.app/u/")
|
||||
(def ^:const status-profile-base-url-without-https "status.app/u/")
|
||||
(def ^:const status-profile-base-url "https://status.app/u#")
|
||||
(def ^:const status-profile-base-url-without-https "status.app/u#")
|
||||
|
||||
(defn get-font-file-ready
|
||||
"setup font file and get the absolute path to it
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user