Compare commits

..
Author SHA1 Message Date
tumanov-alex 6a40a7e3bb Init 2023-11-02 17:29:22 +01:00
flexsurfer e6068fd4c1 small ns changes (#17793) 2023-11-02 14:23:34 +01:00
Yevheniia Berdnyk 579b382c44 e2e: fix for edit message with a mention 2023-11-02 12:38:26 +02:00
yqrashawnandpavloburykh 859cb19886 fix(universal-link): more new link format, handle old link format (#17721)
Co-authored-by: pavloburykh <pavlo@status.im>
2023-11-02 15:31:49 +08:00
Omar Basem 2d92b515b8 Documentation: composer (#17756)
* Docs: composer
2023-11-02 08:53:22 +04:00
Icaro Motta 972e1eee01 Composer - Fix conflicting touchable and gesture (#17680)
Fix the unreliable swipe behavior of link previews in the chat composer. In many
occasions, we noticed the horizontal scroll wasn't being triggered properly or
not at all. In other occasions, the swipe gesture would incorrectly register a
press on the parent `Touchable` component.

Fixes https://github.com/status-im/status-mobile/issues/16599
2023-11-01 13:31:14 -03:00
Ibrahem Khalil 6c2b437e62 Follow up to fix color picker on wallet (#17748) 2023-11-01 15:18:53 +02:00
Nikolay 94a3e266a9 Edit derivation path (#17741) 2023-11-01 14:04:21 +01:00
Lungu Cristian 716007dc3c fix: removed bounce from lightbox text-sheet (#17664)
fix: improving UI and gestures

fix: expanding on press now works reliably

fix: center text and disable gestures for short messages

feat: added styling for one-lined messages

fix: small opacity fix

ref: small condition change

fix: horizontal swiping being unreliable with vertical scrolling

ref: reusing shared value from lightbox animations

fix: text-sheet bottom gradient not visible on android

fix: addressed qa issues 1,2,4

fix: zoomable-image stuck if moved vertically when full screen

clean: removed unused var

chore: removed unused require

fix: don't allow returning text-sheet down and up in one move

fix: text-sheet bar styling was wrong

fix: adjusted gradient animation with designs

fix: text-sheet bar opacity
2023-11-01 13:20:10 +02:00
Mohsen 233d1b3261 [#17288] refactor: migrate previews to new api (#17776) 2023-11-01 11:31:32 +03:00
Yevheniia Berdnyk 25381b601f e2e: test for sending image with camera 2023-10-31 23:23:04 +02:00
Ulises Manuel 4a874ce48d [#17023] Share qr code variants (#17736)
* Align docstring

* Create share-qr-code component

* Remove empty style file

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

* Add share-qr-code preview screen

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

* Add tests and some fixes
2023-10-31 12:35:28 -06:00
mmilad75 ca822ff51d Connect Wallet Home Page to backend - Accounts card (#17447)
* add functionality

* fix lint issues

* change class component to functional component

* fix refactor-data function

* rename events and fix reagent-render structure

* change rf/defn to re-frame/reg-event-fx

* convert camelCase to kebab-case

* refactor on-error callback

* refactor on-success callback

* replace merge with assoc and conj

* refactor calculate-balance structure

* rename reagent-render

* move calculate-balance into a sub

* use created sub in the view

* fix lint issues

* move calculate-balance function to wallet sub

* change on-success callback

* revert podfile.lock

* remove extra line

* add template for test

* edit calculate-balance and calculate-raw-balance

* add tests

* revert podfile.lock and fix lint issues

* rename refactor-data

* add toFixed to get-balance

* fix lint issues

* finalize code

* replace re-frame/reg-event-fx with rf/defn

* resolve comments

* remove use-fixtures in the test

* break down calculate-balance

* fix lint issues

* fix placeholder color

* move accounts to view-internal

* remove accounts param in wallet-2/get-wallet-token

* Connect Wallet Account Page to backend (basic) (#17700)

* pass address to wallet-accounts screen

* move get-balance-by-address to common/utils

* add functionality

* fix lint issues

* move logic to sub

* convert filter+first to some

* change code structure

* rename wallet-2 to wallet

* add tests

* fix lint issues

* fix style

* remove unused code

* remove view-internal defn

* fix lint issues

* fix networks param issue

* fix .toFixed issue
2023-10-31 21:48:21 +03:30
Yevheniia Berdnyk 88b3d7d2e6 e2e: fix for app closed after closing gallery 2023-10-31 12:04:46 +02:00
Yevheniia Berdnyk 90d3a5a39d e2e: test fixes 2023-10-31 02:21:52 +02:00
Mohamed Javid dd8beeb848 [Fix] "network-routing" component test (#17760)
This commit moves the "network-routing" component test to the quo ns from quo2 ns.

Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com>
2023-10-30 20:51:31 +05:30
116 changed files with 2180 additions and 1342 deletions
+2 -4
View File
@@ -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"
-7
View File
@@ -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;
});
}
+11 -13
View File
@@ -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,31 +0,0 @@
(ns quo.components.messages.message.style
(:require [quo.foundations.colors :as colors]))
(defn container
[theme header? reacted? state pinned-by customization-color]
{:padding-horizontal 12
:padding-vertical (if header? 8 4)
:padding-bottom (when reacted? (if header? 12 8))
:border-radius 16
:background-color (cond
pinned-by (colors/resolve-color customization-color theme 5)
(= state :default) (colors/theme-colors colors/white colors/neutral-5 theme)
:else (colors/theme-colors colors/neutral-5
colors/neutral-90
theme))
}
)
(def avatar-container
{:padding-vertical 4
:margin-right 8
})
(def pin-indicator-container
{:flex-direction :row
:align-items :center
:padding-left 40
:margin-bottom 2
})
(def message-content {})
@@ -1,103 +0,0 @@
(ns quo.components.messages.message.view
(:require [quo.components.avatars.user-avatar.view :as user-avatar]
[quo.components.icon :as icon]
[quo.components.info.info-message :as info-message]
[quo.components.markdown.text :as text]
[quo.components.messages.author.view :as author]
[quo.components.messages.message.style :as style]
[quo.components.selectors.react.view :as react]
[quo.foundations.colors :as colors]
[quo.theme :as theme]
[react-native.core :as rn]
[reagent.core :as reagent]
[utils.i18n :as i18n]))
(defn- pin-indicator
[{:keys [theme pinned-by customization-color]}]
[rn/view {:style style/pin-indicator-container}
[icon/icon :i/pin
{:color (colors/resolve-color customization-color theme)
:size 16}]
[text/text
{:size :label
:weight :medium
:style {:margin-left 2 :color (colors/resolve-color customization-color theme)}}
pinned-by]]
)
(defn- internal-view
[]
(let [state (reagent/atom :default)]
(fn [{:keys [theme header? reacted? delivered? avatar-props author-props on-press context
customization-color]}]
(println context)
(let [{:keys [content-type pinned-by reactions]} context]
[rn/pressable
{:style (style/container theme header? reacted? @state (seq pinned-by) customization-color)
:on-press-in #(reset! state :pressed)
:on-press-out #(reset! state :default)
:on-press on-press
}
(when (seq pinned-by)
[pin-indicator
{:theme theme
:header? header?
:pinned-by pinned-by
:customization-color customization-color}])
[rn/view {:style {:flex-direction :row :margin-left (when-not header? 40)}}
(when header?
[rn/view {:style style/avatar-container}
[user-avatar/user-avatar
(merge avatar-props {:size :small})]
])
[rn/view
(when header? [author/view (merge author-props {:size 13})])
[rn/view {:style style/message-content}
(condp = content-type
:text
[text/text "This is a simple message"]
:image
[text/text "image"]
:text-and-image
[text/text "image with text"]
:audio
[text/text "audio"]
:gif
[text/text "gif"]
:sticker
[text/text "sticker"]
:text-and-code
[text/text "code with text"]
:code
[text/text "code"]
:text-and-link-preview
[text/text "link preview with text"]
:text-and-internal-link
[text/text "internal link with text"]
:internal-link
[text/text "internal link"]
:text-and-forward-message
[text/text "forward message with text"]
:forward-message
[text/text "forward message"]
[text/text "not implemented"])]
(when (seq reactions)
[react/view
{:reactions reactions
:use-case :pinned
:add-reaction? true
:container-style {:margin-top 8}}])
(when delivered?
[info-message/info-message
{:type :default
:size :tiny
:icon :i/sent
:style {:margin-top 2}
} (i18n/label :t/sent)])
]]
])
))
)
(def view (theme/with-theme internal-view))
@@ -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}])))))
+105 -34
View File
@@ -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))})
+246 -43
View File
@@ -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}))]))]]))
@@ -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]))
+2 -2
View File
@@ -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]
@@ -85,7 +86,6 @@
quo.components.markdown.text
quo.components.messages.author.view
quo.components.messages.gap
quo.components.messages.message.view
quo.components.messages.system-message.view
quo.components.navigation.bottom-nav-tab.view
quo.components.navigation.floating-shell-button.view
@@ -230,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)
@@ -300,7 +301,6 @@
;;;; Messages
(def author quo.components.messages.author.view/view)
(def gap quo.components.messages.gap/gap)
(def message quo.components.messages.message.view/view)
(def system-message quo.components.messages.system-message.view/system-message)
;;;; Notifications
+1
View File
@@ -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]
+1 -1
View File
@@ -24,7 +24,7 @@
(defn universal-link
[community-id]
(str (:external universal-links/domains)
"/c/"
"/c#"
community-id))
(defn <-request-to-join-community-rpc
+51 -23
View File
@@ -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))
+32 -2
View File
@@ -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"}]
+2 -21
View File
@@ -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,4 +1,4 @@
(ns status-im2.data-store.wallet
(ns status-im2.common.data-store.wallet
(:require
clojure.set))
+61
View File
@@ -36,3 +36,64 @@
:qr-size (or 400 (int size))
:error-level :highest})]
[quo/qr-code (assoc props :qr-image-uri qr-media-server-uri)]))
(defn- get-network-short-name-url
[network]
(case network
:ethereum "eth:"
:optimism "opt:"
:arbitrum "arb1:"
(str (name network) ":")))
(defn- get-qr-data-for-wallet-multichain
[qr-data networks]
(as-> networks $
(map get-network-short-name-url $)
(apply str $)
(str $ qr-data)))
(defn share-qr-code
"Receives the following properties:
- type: :profile | :wallet-legacy | :wallet-multichain
- qr-data: Text to encode in the QR code
- qr-data-label-shown: Text to show below the QR code
- on-text-press: Callback for the `qr-data-label-shown` text.
- on-text-long-press: Callback for the `qr-data-label-shown` text.
- on-share-press: Callback for the share button.
- customization-color: Custom color for the QR code component.
- unblur-on-android?: [Android only] disables blur for this component.
Depending on the `type`, different properties are accepted:
`:profile`
- full-name: User full name.
- profile-picture: map ({:source image-source}) or any image source.
`:wallet-legacy`
- emoji: Emoji in a string to show in the QR code.
- on-info-press: Callback for the info icon.
- on-legacy-press: Callback for the legacy tab.
- on-multichain-press: Callback for the multichain tab.
`:wallet-multichain`
- networks: A vector of network names (`[:ethereum, :optimism, :my-net, ,,,]`)
they will add network prefixes to the address in
`qr-data-label-shown` and the QR code generated.
- on-settings-press: Callback for the settings button.
- emoji: Emoji in a string to show in the QR code.
- on-info-press: Callback for the info icon.
- on-legacy-press: Callback for the legacy tab.
- on-multichain-press: Callback for the multichain tab."
[{:keys [qr-data qr-data-label-shown networks]
share-qr-type :type
:as props}]
(let [label (or qr-data-label-shown qr-data)
string-to-encode (if (= share-qr-type :wallet-multichain)
(get-qr-data-for-wallet-multichain qr-data networks)
qr-data)
qr-media-server-uri (image-server/get-qr-image-uri-for-any-url
{:url string-to-encode
:port (rf/sub [:mediaserver/port])
:qr-size 600
:error-level :highest})]
[quo/share-qr-code
(assoc props
:qr-data label
:qr-image-uri qr-media-server-uri)]))
+1
View File
@@ -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)
+23 -18
View File
@@ -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]}
@@ -320,10 +320,10 @@
:render-fn render-fn
:on-viewable-items-changed on-viewable-items-changed
:on-content-size-change (fn [_ y]
;; NOTE(alwx): here we set the initial value of
;; `scroll-y` which is needed because by default the
;; chat is scrolled to the bottom and no initial
;; `on-scroll` event is getting triggered
;; NOTE(alwx): here we set the initial value of `scroll-y`
;; which is needed because by default the chat is
;; scrolled to the bottom and no initial `on-scroll`
;; event is getting triggered
(let [scroll-y-shared (reanimated/get-shared-value
scroll-y)
content-height-shared (reanimated/get-shared-value
@@ -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,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,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"
+20 -19
View File
@@ -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]
@@ -100,7 +102,6 @@
[status-im2.contexts.quo-preview.markdown.text :as text]
[status-im2.contexts.quo-preview.messages.author :as messages-author]
[status-im2.contexts.quo-preview.messages.gap :as messages-gap]
[status-im2.contexts.quo-preview.messages.message :as message]
[status-im2.contexts.quo-preview.messages.system-message :as system-message]
[status-im2.contexts.quo-preview.navigation.bottom-nav-tab :as
bottom-nav-tab]
@@ -270,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
@@ -332,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
@@ -349,8 +352,6 @@
:component messages-author/view}
{:name :gap
:component messages-gap/view}
{:name :message
:component message/view}
{:name :system-messages
:component system-message/view}]
:navigation [{:name :bottom-nav-tab
@@ -362,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
@@ -438,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
@@ -1,59 +0,0 @@
(ns status-im2.contexts.quo-preview.messages.message
(:require [quo.core :as quo]
[reagent.core :as reagent]
[status-im2.common.resources :as resources]
[status-im2.constants :as constants]
[status-im2.contexts.quo-preview.preview :as preview]))
(def descriptor
[{:key :header? :type :boolean}
{:key :reacted? :type :boolean}
{:key :delivered? :type :boolean}
{:key :pinned-by :type :text}
(preview/customization-color-option)])
(defn- gen-quantity
[max-count _]
(rand-int max-count))
(def ^:private memo-gen-quantity (memoize gen-quantity))
(defn- normalize-state
[state]
(merge @state
{:context {:pinned-by (:pinned-by @state)
:reactions (when (:reacted? @state)
(mapv (fn [reaction-id]
{:emoji-reaction-id reaction-id
:emoji-id reaction-id
:emoji (get constants/reactions reaction-id)
:quantity (memo-gen-quantity 10 reaction-id)
;; :own (contains? @pressed-reactions
;; reaction-id)
})
[1 2]))}
})
)
(defn view
[]
(let [state (reagent/atom {:avatar-props {:full-name "Alisher Yakupov"
:ring? true
:status-indicator? true
:online? true
:profile-picture (resources/get-mock-image
:user-picture-male5)
:customization-color :blue}
:author-props {:primary-name "Alisher Yakupov"
:secondary-name "zQ3...9d4Gs0"
:time-str "09:30"}
:customization-color :blue
:pinned-by "Steve"
}
)]
(fn []
[preview/preview-container
{:state state
:descriptor descriptor
:component-container-style {:padding-vertical 50
:padding-horizontal 20}}
[quo/message (normalize-state state)]])))
@@ -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
+24 -54
View File
@@ -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)))
@@ -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
+21 -1
View File
@@ -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]
+39 -24
View File
@@ -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}}]
+2 -1
View File
@@ -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]))
+6 -1
View File
@@ -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}
+6 -1
View File
@@ -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)
+51
View File
@@ -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"])))))

Some files were not shown because too many files have changed in this diff Show More