send transaction GUI

Signed-off-by: Andrey Shovkoplyas <motor4ik@gmail.com>
This commit is contained in:
Andrey Shovkoplyas 2019-11-16 10:56:09 +01:00
parent 182bfef295
commit 507cc5cf39
No known key found for this signature in database
GPG Key ID: EAAB7C8622D860A4
71 changed files with 794 additions and 1395 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 B

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 465 B

After

Width:  |  Height:  |  Size: 721 B

View File

@ -302,7 +302,13 @@
(defn create-main-screen-view [current-view]
(fn [props & children]
(apply vector (adapt-class (object/get js-dependencies/safe-area-context "SafeAreaView")) props children)))
(apply
vector
(adapt-class (object/get js-dependencies/safe-area-context "SafeAreaView"))
(cond-> props
(= current-view :qr-scanner)
(assoc :background-color :black))
children)))
(defn main-screen-modal-view [current-view & components]
;; NOTE on Android we use Modal component and it manages statusbar area by itself

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 B

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 464 B

After

Width:  |  Height:  |  Size: 721 B

View File

@ -26,8 +26,7 @@
(cond
(= permission constants/dapp-permission-qr-code)
(fx/merge (assoc-in cofx [:db :browser/options :yielding-control?] true)
(qr-scanner/scan-qr-code {}
{:handler :browser.bridge.callback/qr-code-scanned
(qr-scanner/scan-qr-code {:handler :browser.bridge.callback/qr-code-scanned
:cancel-handler :browser.bridge.callback/qr-code-canceled
:data {:dapp-name dapp-name
:permission permission

View File

@ -12,10 +12,9 @@
(if js/goog.DEBUG
(.ignoreWarnings (.-YellowBox js-dependencies/react-native)
#js
["re-frame: overwriting"
"Warning: componentWillMount is deprecated and will be removed in the next major version. Use componentDidMount instead. As a temporary workaround, you can rename to UNSAFE_componentWillMount."
"Warning: componentWillUpdate is deprecated and will be removed in the next major version. Use componentDidUpdate instead. As a temporary workaround, you can rename to UNSAFE_componentWillUpdate."])
#js ["re-frame: overwriting"
"Warning: componentWillMount is deprecated and will be removed in the next major version. Use componentDidMount instead. As a temporary workaround, you can rename to UNSAFE_componentWillMount."
"Warning: componentWillUpdate is deprecated and will be removed in the next major version. Use componentDidUpdate instead. As a temporary workaround, you can rename to UNSAFE_componentWillUpdate."])
(aset js/console "disableYellowBox" true))
(defn init [app-root]

View File

@ -34,6 +34,10 @@
(or (some #(when (= i (:id (val %))) (key %)) chains)
:custom))
(defn chain-id->chain-name [i]
(or (some #(when (= i (:id (val %))) (:name (val %))) chains)
:custom))
(defn chain-keyword->chain-id [k]
(get-in chains [k :id]))
@ -91,6 +95,9 @@
(defn network->chain-keyword [network]
(chain-id->chain-keyword (network->chain-id network)))
(defn network->network-name [network]
(chain-id->chain-name (network->chain-id network)))
(defn network->chain-name [network]
(-> network
network->chain-keyword

View File

@ -222,7 +222,7 @@
(handlers/register-handler-fx
:mailserver.callback/qr-code-scanned
(fn [cofx [_ _ url]]
(fn [cofx [_ url _]]
(mailserver/set-url-from-qr cofx url)))
(handlers/register-handler-fx
@ -327,7 +327,7 @@
(handlers/register-handler-fx
:bootnodes.callback/qr-code-scanned
(fn [cofx [_ _ url]]
(fn [cofx [_ url _]]
(bootnodes/set-bootnodes-from-qr cofx url)))
(handlers/register-handler-fx
@ -386,30 +386,32 @@
(handlers/register-handler-fx
:browser.bridge.callback/qr-code-scanned
(fn [cofx [_ _ data qr-code-data]]
(fn [cofx [_ data qr-code-data]]
(browser/handle-scanned-qr-code cofx data (:data qr-code-data))))
(handlers/register-handler-fx
:browser.bridge.callback/qr-code-canceled
(fn [cofx [_ _ qr-code-data]]
(fn [cofx [_ qr-code-data _]]
(browser/handle-canceled-qr-code cofx (:data qr-code-data))))
;; qr-scanner module
(handlers/register-handler-fx
:qr-scanner.ui/scan-qr-code-pressed
(fn [cofx [_ identifier handler & [opts]]]
(qr-scanner/scan-qr-code cofx identifier (merge {:handler handler} opts))))
(fn [cofx [_ opts]]
(qr-scanner/scan-qr-code cofx opts)))
(handlers/register-handler-fx
:qr-scanner.callback/scan-qr-code-success
(fn [cofx [_ context data]]
(qr-scanner/set-qr-code cofx context data)))
(fn [cofx [_ opts data]]
(qr-scanner/set-qr-code cofx opts data)))
(handlers/register-handler-fx
:qr-scanner.callback/scan-qr-code-cancel
(fn [cofx [_ context]]
(qr-scanner/set-qr-code-cancel cofx context)))
(fn [cofx [_ opts]]
(fx/merge cofx
(qr-scanner/set-qr-code-cancel opts)
(navigation/navigate-back))))
;; privacy-policy module
@ -1268,7 +1270,7 @@
(handlers/register-handler-fx
:contact/qr-code-scanned
[(re-frame/inject-cofx :random-id-generator)]
(fn [{:keys [db] :as cofx} [_ _ contact-identity]]
(fn [{:keys [db] :as cofx} [_ contact-identity _]]
(let [current-multiaccount (:multiaccount db)
fx {:db (assoc db :contacts/new-identity contact-identity)}
validation-result (new-chat.db/validate-pub-key db contact-identity)]
@ -1574,6 +1576,11 @@
(fn []
(react/dismiss-keyboard!)))
(handlers/register-handler-fx
:dismiss-keyboard
(fn [_]
{:dismiss-keyboard nil}))
(handlers/register-handler-fx
:wallet-send-request
(fn [{:keys [db] :as cofx} [_ public-key amount symbol decimals]]

View File

@ -5,33 +5,22 @@
[status-im.utils.fx :as fx]))
(fx/defn scan-qr-code
[{:keys [db]} {:keys [deny-handler] :as identifier} qr-codes]
{:db (assoc-in db [:qr-codes identifier] qr-codes)
:request-permissions-fx {:permissions [:camera]
:on-allowed #(re-frame/dispatch
[:navigate-to :qr-scanner
{:current-qr-context identifier}])
:on-denied (if (nil? deny-handler)
(fn []
(utils/set-timeout
#(utils/show-popup (i18n/label :t/error)
(i18n/label :t/camera-access-error))
50))
#(re-frame/dispatch [deny-handler qr-codes]))}})
[_ opts]
{:request-permissions-fx
{:permissions [:camera]
:on-allowed #(re-frame/dispatch [:navigate-to :qr-scanner opts])
:on-denied (fn []
(utils/set-timeout
#(utils/show-popup (i18n/label :t/error)
(i18n/label :t/camera-access-error))
50))}})
(fx/defn set-qr-code
[{:keys [db]} context data]
(merge {:db (-> db
(update :qr-codes dissoc context)
(dissoc :current-qr-context))}
(when-let [qr-codes (get-in db [:qr-codes context])]
{:dispatch [(:handler qr-codes) context data (dissoc qr-codes :handler)]})))
[{:keys [db]} opts data]
(when-let [handler (:handler opts)]
{:dispatch [handler data opts]}))
(fx/defn set-qr-code-cancel
[{:keys [db]} context]
(merge {:db (-> db
(update :qr-codes dissoc context)
(dissoc :current-qr-context))}
(when-let [qr-codes (get-in db [:qr-codes context])]
(when-let [handler (:cancel-handler qr-codes)]
{:dispatch [handler context qr-codes]}))))
[_ opts]
(when-let [handler (:cancel-handler opts)]
{:dispatch [handler opts]}))

View File

@ -166,6 +166,7 @@
(reg-root-key-sub :prices-loading? :prices-loading?)
(reg-root-key-sub :wallet.transactions :wallet.transactions)
(reg-root-key-sub :wallet/custom-token-screen :wallet/custom-token-screen)
(reg-root-key-sub :wallet/prepare-transaction :wallet/prepare-transaction)
;;ethereum
(reg-root-key-sub :ethereum/current-block :ethereum/current-block)
@ -284,6 +285,12 @@
(fn [network]
(ethereum/network->chain-name network)))
(re-frame/reg-sub
:network-name
:<- [:current-network]
(fn [network]
(ethereum/network->network-name network)))
(re-frame/reg-sub
:chain-id
:<- [:current-network]
@ -488,6 +495,12 @@
(fn [[macc acc]]
(some #(when (= (:address %) (:address acc)) %) (:accounts macc))))
(re-frame/reg-sub
:account-by-address
:<- [:multiaccount]
(fn [macc [_ address]]
(some #(when (= (:address %) address) %) (:accounts macc))))
(re-frame/reg-sub
:multiple-multiaccounts?
:<- [:multiaccounts/multiaccounts]
@ -539,7 +552,7 @@
:<- [:chats/current-chat-ui-prop :input-height]
:<- [:chats/current-chat-ui-prop :input-focused?]
:<- [:keyboard-height]
:<- [:chats/current-chat-ui-prop :show-stickers?]
:<- [:chats/current-chat-ui-prop :input-bottom-sheet]
(fn [[home-content-layout-height input-height input-focused? kheight stickers?]]
(- (+ home-content-layout-height tabs.styles/tabs-height)
(if platform/iphone-x?
@ -2070,21 +2083,27 @@
(get-sufficient-gas-error balance nil nil gas gasPrice))))
(re-frame/reg-sub
:wallet.send/transaction
:<- [::send-transaction]
:wallet.send/prepare-transaction-with-balance
:<- [:wallet/prepare-transaction]
:<- [:wallet]
:<- [:offline?]
:<- [:wallet/all-tokens]
:<- [:ethereum/chain-keyword]
(fn [[{:keys [amount symbol from to amount-error] :as transaction}
(fn [[{:keys [symbol from to amount-text] :as transaction}
wallet offline? all-tokens chain]]
(let [balance (get-in wallet [:accounts from :balance])
token (tokens/asset-for all-tokens chain symbol)]
(assoc (merge transaction
(when amount
(get-sufficient-funds-error balance symbol amount)))
(let [balance (get-in wallet [:accounts (:address from) :balance])
{:keys [decimals] :as token} (tokens/asset-for all-tokens chain symbol)
{:keys [value error]} (wallet.db/parse-amount amount-text decimals)
amount (money/formatted->internal value symbol decimals)
{:keys [amount-error] :as transaction-new}
(merge transaction
{:amount-error error}
(when amount
(get-sufficient-funds-error balance symbol amount)))]
(assoc transaction-new
:amount amount
:balance balance
:token token
:token (assoc token :amount (get balance (:symbol token)))
:sign-enabled? (and to
(nil? amount-error)
(not (nil? amount))

View File

@ -0,0 +1,67 @@
(ns status-im.ui.components.bottom-panel.views
(:require-macros [status-im.utils.views :as views])
(:require [status-im.ui.components.react :as react]
[status-im.ui.components.animation :as anim]
[reagent.core :as reagent]))
(defn hide-panel-anim
[bottom-anim-value alpha-value window-height]
(anim/start
(anim/parallel
[(anim/spring bottom-anim-value {:toValue (- window-height)
:useNativeDriver true})
(anim/timing alpha-value {:toValue 0
:duration 500
:useNativeDriver true})])))
(defn show-panel-anim
[bottom-anim-value alpha-value]
(anim/start
(anim/parallel
[(anim/spring bottom-anim-value {:toValue 40
:useNativeDriver true})
(anim/timing alpha-value {:toValue 0.4
:duration 500
:useNativeDriver true})])))
(defn bottom-panel [obj render window-height]
(let [bottom-anim-value (anim/create-value window-height)
alpha-value (anim/create-value 0)
clear-timeout (atom nil)
update? (atom nil)
current-obj (reagent/atom nil)]
(reagent/create-class
{:component-will-update (fn [_ [_ obj _ _]]
(when @clear-timeout (js/clearTimeout @clear-timeout))
(when (or (not= obj @current-obj) @update?)
(cond
@update?
(do (reset! update? false)
(show-panel-anim bottom-anim-value alpha-value))
(and @current-obj obj)
(do (reset! update? true)
(js/setTimeout #(reset! current-obj obj) 600)
(hide-panel-anim bottom-anim-value alpha-value (- window-height)))
obj
(do (reset! current-obj obj)
(show-panel-anim bottom-anim-value alpha-value))
:else
(do (reset! clear-timeout (js/setTimeout #(reset! current-obj nil) 600))
(hide-panel-anim bottom-anim-value alpha-value (- window-height))))))
:reagent-render (fn []
(when @current-obj
[react/keyboard-avoiding-view {:style {:position :absolute :top 0 :bottom 0 :left 0 :right 0}}
[react/view {:flex 1}
[react/animated-view {:flex 1 :background-color :black :opacity alpha-value}]
[react/animated-view {:style {:position :absolute
:transform [{:translateY bottom-anim-value}]
:bottom 0 :left 0 :right 0}}
[react/view {:flex 1}
[render @current-obj]]]]]))})))
(views/defview animated-bottom-panel [val signing-view]
(views/letsubs [{window-height :height} [:dimensions/window]]
[bottom-panel (when val (select-keys val [:from :contact :amount :token :approve? :message])) signing-view window-height]))

View File

@ -16,7 +16,7 @@
(= :previous type)
{:padding-right 20 :padding-left 12}
:else nil)
{:padding-vertical 11 :border-radius 8
{:height 44 :border-radius 8
:align-items :center :justify-content :center
:background-color (cond
(#{:secondary :next :previous} type)
@ -51,7 +51,7 @@
Spec: https://www.figma.com/file/cb4p8AxLtTF3q1L6JYDnKN15/Index?node-id=858%3A0"
[{:keys [label type theme disabled? on-press accessibility-label style] :or {type :main theme :blue}}]
[{:keys [label type theme disabled? on-press accessibility-label style container-style] :or {type :main theme :blue}}]
(let [label (utils.label/stringify label)]
[react/touchable-opacity (cond-> {:on-press on-press
:active-opacity 0.5
@ -61,7 +61,7 @@
(assoc :disabled (boolean disabled?))
accessibility-label
(assoc :accessibility-label accessibility-label))
[react/view {:style (style-container type disabled? theme)}
[react/view {:style (merge (style-container type disabled? theme) container-style)}
[react/view {:flex-direction :row :align-items :center}
(when (= type :previous)
[vector-icons/icon :main-icons/back {:container-style {:width 24 :height 24 :margin-right 4}

View File

@ -49,11 +49,14 @@
(def green "#44d058") ;; icon for successful inboud transaction
(def green-transparent-10 (alpha green 0.1)) ;; icon for successful inboud transaction
(def purple "#887af9")
(def orange "#FE8F59")
(def chat-colors ["#fa6565"
"#7cda00"
"#887af9"
purple
"#51d0f0"
"#fe8f59"
orange
"#d37ef4"])
(def account-colors ["#9B832F"
@ -61,7 +64,7 @@
"#1D806F"
"#FA6565"
"#7CDA00"
"#887AF9"
purple
"#8B3131"])
(def text black)

View File

@ -196,5 +196,4 @@
(def error
{:bottom-value 0
:color colors/red-light
:font-size 12})

View File

@ -14,6 +14,6 @@
:android (create-status-bar-style {:translucent? true
:bar-style "dark-content"})})
(styles/def status-bar-transparent
(styles/def status-bar-black
{:ios (create-status-bar-style {:background-color colors/transparent})
:android (create-status-bar-style {:translucent? true})})
:android (create-status-bar-style {:background-color colors/black})})

View File

@ -4,7 +4,7 @@
[status-im.utils.platform :as platform]))
(defn get-config [view-id]
(or (get {:recipient-qr-code {:type :transparent}}
(or (get {:qr-scanner {:type :black}}
view-id)
{:type :main}))
@ -26,12 +26,12 @@
network-activity-indicator-visible
translucent]}
(case type
:transparent styles/status-bar-transparent
:black styles/status-bar-black
styles/status-bar-default)]
(when bar-style
(.setBarStyle react/status-bar-class (clj->js bar-style)) true)
(when (and background-color platform/android?)
(.setBackgroundColor react/status-bar-class (clj->js background-color)))
(when bar-style
(.setBarStyle react/status-bar-class (clj->js bar-style)))
(when hidden
(.setHidden react/status-bar-class (clj->js hidden)))
(when network-activity-indicator-visible

View File

@ -31,7 +31,7 @@
(def tabs-list-data
(->>
[{:nav-stack :chat-stack
:content {:title (i18n/label :t/chats)
:content {:title (i18n/label :t/chat)
:icon :main-icons/message}
:count-subscription :chats/unread-messages-number
:accessibility-label :home-tab-button}

View File

@ -33,10 +33,10 @@
(defn nav-text
([text] (nav-text nil text))
([{:keys [handler] :as props} text]
[react/text (utils/deep-merge {:style styles/item-text
:on-press (or handler #(re-frame/dispatch [:navigate-back]))}
props)
text]))
[react/touchable-highlight {:on-press (or handler #(re-frame/dispatch [:navigate-back]))}
[react/text (utils/deep-merge {:style styles/item-text}
props)
text]]))
(defn nav-clear-text
([text] (nav-clear-text nil text))

View File

@ -3,11 +3,12 @@
[status-im.utils.styles :as styles]))
(def tooltip-container
{:position :absolute
:align-items :center
:left 0
:right 0
:top 0})
{:position :absolute
:align-items :center
:pointer-events :none
:left 0
:right 0
:top 0})
(styles/def bottom-tooltip-container
{:position :absolute
@ -28,10 +29,14 @@
(defn tooltip-text-container [color]
{:padding-horizontal 16
:padding-vertical 9
:padding-vertical 6
:elevation 3
:background-color color
:elevation 2
:border-radius 8})
:border-radius 8
:shadow-radius 12
:shadow-offset {:width 0 :height 4}
:shadow-opacity 0.16
:shadow-color "rgba(0, 34, 51)"})
(def bottom-tooltip-text-container
{:flex-direction :row
@ -43,8 +48,9 @@
:border-radius 8})
(defn tooltip-text [font-size]
{:color colors/red
:font-size font-size})
{:color colors/red
:line-height 15
:font-size font-size})
(def bottom-tooltip-text
{:color colors/white})

View File

@ -18,9 +18,9 @@
(when label
[react/view (styles/tooltip-text-container color)
[react/text {:style (styles/tooltip-text font-size)} label]])
[vector-icons/icon :icons/tooltip-triangle (assoc
styles/tooltip-triangle
:color color)]]]))
#_[vector-icons/icon :icons/tooltip-triangle (assoc
styles/tooltip-triangle
:color color)]]]))
(views/defview bottom-tooltip-info [label on-close]
(views/letsubs [bottom-anim-value (animation/create-value 150)

View File

@ -5,6 +5,5 @@
(handlers/register-handler-fx
:handle-qr-code
(fn [cofx [_ _ data]]
(log/debug "qr code scanned with data " data)
(fn [cofx [_ data _]]
(models/handle-qr-code cofx data)))

View File

@ -10,7 +10,6 @@
[status-im.ui.screens.add-new.styles :as add-new.styles]
[status-im.ui.screens.add-new.new-chat.styles :as styles]
[status-im.utils.platform :as platform]
[reagent.core :as reagent]
[status-im.ui.components.list-item.views :as list-item]
[status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.multiaccounts.core :as multiaccounts]))
@ -21,9 +20,6 @@
:accessories [:chevron]
:on-press #(re-frame/dispatch [:chat.ui/start-chat (:public-key row) {:navigation-reset? true}])}])
;;TODO workaround for https://github.com/facebook/react-native/issues/23653 (https://github.com/status-im/status-react/issues/8548)
(def tw (reagent/atom "95%"))
(views/defview new-chat []
(views/letsubs [contacts [:contacts/active]
new-identity [:contacts/new-identity]
@ -32,12 +28,11 @@
[toolbar.view/simple-toolbar (i18n/label :t/new-chat) true]
[react/view add-new.styles/new-chat-container
[react/view add-new.styles/new-chat-input-container
[react/text-input {:ref (fn [v] (js/setTimeout #(reset! tw (if v "100%" "95%")) 100))
:on-change-text #(re-frame/dispatch [:new-chat/set-new-identity %])
[react/text-input {:on-change-text #(re-frame/dispatch [:new-chat/set-new-identity %])
:on-submit-editing #(when (and new-identity (not error-message))
(re-frame/dispatch [:contact.ui/contact-code-submitted]))
:placeholder (i18n/label :t/enter-contact-code)
:style (add-new.styles/input @tw)
:style add-new.styles/input
;; This input is fine to preserve inputs
;; so its contents will not be erased
;; in onWillBlur navigation event handler
@ -46,8 +41,8 @@
:return-key-type :go}]]
(when-not platform/desktop?
[react/touchable-highlight {:on-press #(re-frame/dispatch [:qr-scanner.ui/scan-qr-code-pressed
{:toolbar-title (i18n/label :t/new-contact)}
:contact/qr-code-scanned])
{:title (i18n/label :t/new-contact)
:handler :contact/qr-code-scanned}])
:style add-new.styles/button-container
:accessibility-label :scan-contact-code-button}
[react/view

View File

@ -26,9 +26,8 @@
:padding-horizontal 16
:height 52})
(styles/defn input [w]
(styles/def input
{:padding-horizontal 14
:width w
:desktop {:height 30
:width "100%"}
:android {:padding 0}})

View File

@ -45,8 +45,8 @@
:accessibility-label :scan-qr-code-button
:icon :main-icons/qr
:on-press #(re-frame/dispatch [:qr-scanner.ui/scan-qr-code-pressed
{:toolbar-title (i18n/label :t/scan-qr)}
:handle-qr-code])}])])
{:title (i18n/label :t/scan-qr)
:handler :handle-qr-code}])}])])
(defn add-new []
[react/view {:flex 1 :background-color :white}

View File

@ -26,8 +26,8 @@
(def qr-code
[react/touchable-highlight {:on-press #(re-frame/dispatch [:qr-scanner.ui/scan-qr-code-pressed
{:toolbar-title (i18n/label :t/add-bootnode)}
:bootnodes.callback/qr-code-scanned])
{:title (i18n/label :t/add-bootnode)
:handler :bootnodes.callback/qr-code-scanned}])
:style styles/qr-code}
[react/view
[vector-icons/icon :main-icons/qr {:color colors/blue}]]])

View File

@ -0,0 +1,59 @@
(ns status-im.ui.screens.chat.extensions.views
(:require-macros [status-im.utils.views :as views])
(:require [status-im.ui.components.react :as react]
[re-frame.core :as re-frame]
[status-im.utils.platform :as platform]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.icons.vector-icons :as icons]
[status-im.ui.components.animation :as anim]
[status-im.i18n :as i18n]))
(def panel-height 264)
(defn button [showing?]
[react/touchable-highlight
{:on-press (fn [_]
(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-bottom-sheet (when-not showing? :extensions)}])
(when-not platform/desktop? (js/setTimeout #(react/dismiss-keyboard!) 100)))
:accessibility-label :show-extensions-icon}
[icons/icon :main-icons/commands {:container-style {:margin 14 :margin-right 10}
:color (if showing? colors/blue colors/gray)}]])
(defn show-panel-anim
[bottom-anim-value alpha-value]
(anim/start
(anim/parallel
[(anim/spring bottom-anim-value {:toValue 0
:useNativeDriver true})
(anim/timing alpha-value {:toValue 1
:duration 500
:useNativeDriver true})])))
(views/defview extensions-view []
(views/letsubs [bottom-anim-value (anim/create-value panel-height)
alpha-value (anim/create-value 0)]
{:component-did-mount #(show-panel-anim bottom-anim-value alpha-value)}
[react/animated-view {:style {:background-color :white
:height panel-height
:transform [{:translateY bottom-anim-value}]
:opacity alpha-value}}
[react/view
[react/touchable-highlight
{:on-press #(re-frame/dispatch [:wallet/prepare-transaction-from-chat])}
[react/view {:width 128 :height 128 :justify-content :space-between
:padding-horizontal 10 :padding-vertical 12
:background-color (colors/alpha colors/purple 0.2) :border-radius 16 :margin-left 8}
[react/view {:background-color colors/purple :width 40 :height 40 :border-radius 20 :align-items :center
:justify-content :center}
[icons/icon :main-icons/send {:color colors/white}]]
[react/text {:typography :medium} (i18n/label :t/send-transaction)]]]
;;TODO not implemented yet
#_[react/touchable-highlight
{:on-press #(re-frame/dispatch [:wallet/prepare-transaction-from-chat])}
[react/view {:width 128 :height 128 :justify-content :space-between
:padding-horizontal 10 :padding-vertical 12 :margin-top 8
:background-color (colors/alpha colors/orange 0.2) :border-radius 16 :margin-left 8}
[react/view {:background-color colors/orange :width 40 :height 40 :border-radius 20 :align-items :center
:justify-content :center}
[icons/icon :main-icons/receive {:color colors/white}]]
[react/text {:typography :medium} (i18n/label :t/request-transaction)]]]]]))

View File

@ -21,7 +21,8 @@
[status-im.utils.utils :as utils]
[status-im.utils.config :as config]
[taoensso.timbre :as log]
[status-im.ui.screens.chat.stickers.views :as stickers]))
[status-im.ui.screens.chat.stickers.views :as stickers]
[status-im.ui.screens.chat.extensions.views :as extensions]))
(defview basic-text-input [{:keys [set-container-width-fn height single-line-input?]}]
(letsubs [input-text [:chats/current-chat-input-text]
@ -35,7 +36,7 @@
:editable (not cooldown-enabled?)
:blur-on-submit false
:on-focus #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-focused? true
:show-stickers? false
:input-bottom-sheet nil
:messages-focused? false}])
:on-blur #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-focused? false}])
:on-submit-editing #(when single-line-input?
@ -66,7 +67,7 @@
:editable (not cooldown-enabled?)
:blur-on-submit false
:on-focus #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-focused? true
:show-stickers? false
:input-bottom-sheet nil
:messages-focused? false}])
:on-blur #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-focused? false}])
:submit-shortcut {:key "Enter"}
@ -187,14 +188,13 @@
mainnet? [:mainnet?]
input-text [:chats/current-chat-input-text]
result-box [:chats/current-chat-ui-prop :result-box]
show-stickers? [:chats/current-chat-ui-prop :show-stickers?]
state-text (reagent/atom "")]
input-bottom-sheet [:chats/current-chat-ui-prop :input-bottom-sheet]
state-text (reagent/atom "")]
{:component-will-unmount #(when platform/desktop?
(re-frame/dispatch [:chat.ui/set-chat-input-text @state-text]))
:component-did-mount #(when-not (string/blank? input-text) (reset! state-text input-text))}
(let [single-line-input? (:singleLineInput result-box)
component (reagent/current-component)
set-text #(reset! state-text %)
input-text-empty? (if platform/desktop?
(string/blank? state-text)
@ -209,9 +209,10 @@
[react/view {:style style/input-container}
[input-view {:single-line-input? single-line-input? :set-text set-text :state-text state-text}]
(when (and input-text-empty? mainnet?)
[stickers/button show-stickers?])
(if input-text-empty?
[commands-button]
[stickers/button (= :stickers input-bottom-sheet)])
(when (and input-text-empty?) ;;TODO show only for 1-1 chats?
[extensions/button (= :extensions input-bottom-sheet)])
(when-not input-text-empty?
(if platform/desktop?
[send-button/send-button-view {:input-text @state-text}
#(do

View File

@ -15,7 +15,7 @@
{:on-press (fn [_]
(re-frame/dispatch
[:chat.ui/set-chat-ui-props {:messages-focused? true
:show-stickers? false}])
:input-bottom-sheet nil}])
(react/dismiss-keyboard!))}
[react/view style/datemark-mobile
[react/text {:style style/datemark-text}

View File

@ -296,7 +296,7 @@
(when (and (= content-type constants/content-type-sticker) (:pack content))
(re-frame/dispatch [:stickers/open-sticker-pack (:pack content)]))
(re-frame/dispatch [:chat.ui/set-chat-ui-props {:messages-focused? true
:show-stickers? false}])
:input-bottom-sheet nil}])
(when-not platform/desktop?
(react/dismiss-keyboard!)))))
:on-long-press #(when (or (= content-type constants/content-type-text)

View File

@ -19,14 +19,14 @@
(def icon-container (+ (* icon-horizontal-margin 2) icon-size))
(def scroll-x (reagent/atom 0))
(defn button [show-stickers?]
(defn button [stickers-showing?]
[react/touchable-highlight
{:on-press (fn [_]
(re-frame/dispatch [:chat.ui/set-chat-ui-props {:show-stickers? (not show-stickers?)}])
(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-bottom-sheet (when-not stickers-showing? :stickers)}])
(when-not platform/desktop? (js/setTimeout #(react/dismiss-keyboard!) 100)))
:accessibility-label :show-stickers-icon}
[vector-icons/icon :main-icons/stickers {:container-style {:margin 14 :margin-right 6}
:color (if show-stickers? colors/blue colors/gray)}]])
:color (if stickers-showing? colors/blue colors/gray)}]])
(defn- no-stickers-yet-panel []
[react/view {:style {:flex 1 :align-items :center :justify-content :center}}

View File

@ -26,7 +26,8 @@
[status-im.ui.screens.profile.tribute-to-talk.views
:as
tribute-to-talk.views]
[status-im.utils.platform :as platform])
[status-im.utils.platform :as platform]
[status-im.ui.screens.chat.extensions.views :as extensions])
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn add-contact-bar
@ -287,7 +288,7 @@
:on-press (fn [_]
(re-frame/dispatch
[:chat.ui/set-chat-ui-props {:messages-focused? true
:show-stickers? false}])
:input-bottom-sheet nil}])
(react/dismiss-keyboard!))}
[react/view (style/intro-header-container height intro-status no-messages)
;; Icon section
@ -396,7 +397,7 @@
(letsubs [{:keys [public? chat-id chat-name show-input? group-chat contact] :as current-chat}
[:chats/current-chat]
current-chat-id [:chats/current-chat-id]
show-stickers? [:chats/current-chat-ui-prop :show-stickers?]
input-bottom-sheet [:chats/current-chat-ui-prop :input-bottom-sheet]
two-pane-ui-enabled? [:two-pane-ui-enabled?]
anim-translate-y (animation/create-value
(if two-pane-ui-enabled? 0 connectivity/neg-connectivity-bar-height))]
@ -432,8 +433,12 @@
[messages-view current-chat modal?])]]
(when show-input?
[input/container])
(when show-stickers?
[stickers/stickers-view])]))
(case input-bottom-sheet
:stickers
[stickers/stickers-view]
:extensions
[extensions/extensions-view]
nil)]))
(defview chat []
[chat-root false])

View File

@ -6,7 +6,6 @@
status-im.transport.db
status-im.multiaccounts.db
status-im.contact.db
status-im.ui.screens.qr-scanner.db
status-im.ui.screens.group.db
status-im.chat.specs
status-im.ui.screens.profile.db
@ -183,6 +182,8 @@
(spec/def :popover/popover (spec/nilable map?))
(spec/def :wallet/prepare-transaction (spec/nilable map?))
(spec/def ::db (spec/keys :opt [:contacts/contacts
:contacts/new-identity
:contacts/new-identity-error
@ -248,6 +249,7 @@
:bottom-sheet/view
:bottom-sheet/options
:wallet/custom-token-screen
:wallet/prepare-transaction
:signing/in-progress?
:signing/queue
:signing/sign
@ -282,9 +284,6 @@
:navigation/navigation-stack
:navigation/prev-tab-view-id
:navigation/prev-view-id
:qr/qr-codes
:qr/qr-modal
:qr/current-qr-context
:chat/chats
:chat/current-chat-id
:chat/chat-id

View File

@ -27,7 +27,7 @@
:on-change-text #(re-frame/dispatch [:set :new-chat-name %])
:default-value new-group-name
:placeholder (i18n/label :t/set-a-topic)
:style (add-new.styles/input "100%")
:style add-new.styles/input
:accessibility-label :chat-name-input}]])
(defn- render-contact [contact]

View File

@ -40,8 +40,8 @@
:accessibility-label :scan-qr-code-button
:icon :main-icons/qr
:on-press #(hide-sheet-and-dispatch [:qr-scanner.ui/scan-qr-code-pressed
{:toolbar-title (i18n/label :t/scan-qr)}
:handle-qr-code])}]
{:title (i18n/label :t/scan-qr)
:handler :handle-qr-code}])}]
[list-item/list-item
{:theme :action
:title :t/invite-friends

View File

@ -34,8 +34,8 @@
(def qr-code
[react/touchable-highlight {:on-press #(re-frame/dispatch [:qr-scanner.ui/scan-qr-code-pressed
{:toolbar-title (i18n/label :t/add-mailserver)}
:mailserver.callback/qr-code-scanned])
{:title (i18n/label :t/add-mailserver)
:handler :mailserver.callback/qr-code-scanned}])
:style styles/qr-code}
[react/view
[vector-icons/icon :main-icons/qr {:color colors/blue}]]])

View File

@ -1,8 +0,0 @@
(ns status-im.ui.screens.qr-scanner.db
(:require [cljs.spec.alpha :as s]))
;;on scan qr
(s/def :qr/qr-codes (s/nilable map?))
;;used in qr modal screen
(s/def :qr/qr-modal (s/nilable map?))
(s/def :qr/current-qr-context (s/nilable map?))

View File

@ -1,77 +1,11 @@
(ns status-im.ui.screens.qr-scanner.styles
(:require-macros [status-im.utils.styles :as styles])
(:require [status-im.ui.components.colors :as colors]
[status-im.ui.components.toolbar.styles :as toolbar.styles]))
(ns status-im.ui.screens.qr-scanner.styles)
(def barcode-scanner-container
{:flex 1
:background-color :white})
(styles/def barcode-scanner
{:flex 1
:elevation -10
:android {:marginTop 10}})
(def rectangle-container
{:position :absolute
:left 0
:top toolbar.styles/toolbar-height
:bottom 0
:right 0
:flex 1
:align-items :center
:justify-content :center
:background-color :transparent})
(def rectangle
{:height 250
:width 250
:background-color :transparent})
(def corner-left-top
{:position :absolute
:left 0
:top 0
:width 56
:height 56})
(def corner-right-top
{:position :absolute
:right 0
:top 0
:width 56
:height 56})
(def corner-right-bottom
{:position :absolute
:right 0
:bottom 0
:width 56
:height 56})
(def corner-left-bottom
{:position :absolute
:left 0
:bottom 0
:width 56
:height 56})
(def import-button
{:position :absolute
:right 16
:flex 1
:height 50
:align-items :center})
(def import-button-content
{:flex 1
:flex-direction :row
:height 50
:align-items :center
:align-self :center})
(def import-text
{:flex 1
:flex-direction :column
:color colors/white
:margin-left 8})
(def viewfinder-port
{:position :absolute
:left 0
:top 0
:bottom 0
:right 0
:align-items :center
:justify-content :center
:flex 1})

View File

@ -1,51 +1,58 @@
(ns status-im.ui.screens.qr-scanner.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [reagent.core :as reagent]
[re-frame.core :as re-frame]
(:require [re-frame.core :as re-frame]
[status-im.i18n :as i18n]
[status-im.ui.components.react :as react]
[status-im.ui.components.camera :as camera]
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.components.react :as react]
[status-im.ui.components.toolbar.view :as topbar]
[status-im.ui.screens.qr-scanner.styles :as styles]
[status-im.ui.components.toolbar.actions :as actions]))
[status-im.ui.components.colors :as colors]))
(defview qr-scanner-toolbar [title identifier]
[react/view
[toolbar/toolbar {:style {:background-color :white}}
[toolbar/nav-button (actions/back
#(do
(re-frame/dispatch [:qr-scanner.callback/scan-qr-code-cancel identifier])
(re-frame/dispatch [:navigate-back])))]
[toolbar/content-title title]]])
(defn- topbar [camera-flashlight {:keys [title] :as opts}]
[topbar/toolbar
{:transparent? true}
[topbar/nav-text
{:style {:color colors/white :margin-left 16}
:handler #(re-frame/dispatch [:qr-scanner.callback/scan-qr-code-cancel opts])}
(i18n/label :t/cancel)]
[topbar/content-title {:color :white}
(or title (i18n/label :t/scan-qr))]
#_[topbar/actions [{:icon (if (= :on camera-flashlight)
:main-icons/flash-active
:main-icons/flash-inactive)
:icon-opts {:color :white}
:handler #(re-frame/dispatch [:wallet/toggle-flashlight])}]]])
(defn on-barcode-read [identifier data]
(re-frame/dispatch [:qr-scanner.callback/scan-qr-code-success identifier (camera/get-qr-code-data data)]))
(defn corner [border1 border2 corner]
[react/view (assoc {:border-color :white :width 60 :height 60} border1 5 border2 5 corner 32)])
;; identifier is passed via navigation params instead of subs in order to ensure
;; that two separate instances of `qr-scanner` screen can work simultaneously
(defview qr-scanner [{identifier :current-qr-context} screen-focused?]
(letsubs [camera-initialized? (reagent/atom false)
barcode-read? (reagent/atom false)]
[react/view styles/barcode-scanner-container
[qr-scanner-toolbar (or (:toolbar-title identifier) (i18n/label :t/scan-qr)) identifier]
;; camera component should be hidden if screen is not shown
;; otherwise another screen with camera from a different stack
;; will not work properly
(when @screen-focused?
[camera/camera {:onBarCodeRead #(if (:multiple? identifier)
(on-barcode-read identifier %)
(when-not @barcode-read?
(do (reset! barcode-read? true)
(on-barcode-read identifier %))))
:ref #(reset! camera-initialized? true)
:style styles/barcode-scanner}])
[react/view styles/rectangle-container
[react/view styles/rectangle
[react/image {:source {:uri :corner_left_top}
:style styles/corner-left-top}]
[react/image {:source {:uri :corner_right_top}
:style styles/corner-right-top}]
[react/image {:source {:uri :corner_right_bottom}
:style styles/corner-right-bottom}]
[react/image {:source {:uri :corner_left_bottom}
:style styles/corner-left-bottom}]]]]))
(defn- viewfinder [size]
[react/view {:style styles/viewfinder-port}
[react/view {:width size :height size :justify-content :space-between}
[react/view {:flex-direction :row :justify-content :space-between}
[corner :border-top-width :border-left-width :border-top-left-radius]
[corner :border-top-width :border-right-width :border-top-right-radius]]
[react/view {:flex-direction :row :justify-content :space-between}
[corner :border-bottom-width :border-left-width :border-bottom-left-radius]
[corner :border-bottom-width :border-right-width :border-bottom-right-radius]]]])
(defn on-barcode-read [opts data]
(re-frame/dispatch [:qr-scanner.callback/scan-qr-code-success opts (camera/get-qr-code-data data)]))
(defview qr-scanner []
(letsubs [read-once? (atom false)
{:keys [height width]} [:dimensions/window]
camera-flashlight [:wallet.send/camera-flashlight]
opts [:get-screen-params]]
[react/view {:style {:flex 1 :background-color colors/black}}
[topbar camera-flashlight opts]
[react/with-activity-indicator
{}
[camera/camera
{:style {:flex 1}
;:torchMode (camera/set-torch camera-flashlight)
:captureAudio false
:onBarCodeRead #(when-not @read-once?
(reset! read-once? true)
(on-barcode-read opts %))}]]
[viewfinder (int (* 2 (/ (min height width) 3)))]]))

View File

@ -3,6 +3,5 @@
(def browser-stack
{:name :browser-stack
:screens [:open-dapp
:browser
:qr-scanner]
:browser]
:config {:initialRouteName :open-dapp}})

View File

@ -7,7 +7,6 @@
:select-chat
:profile
:new
:qr-scanner
:take-picture
:new-group
:add-participants-toggle-list

View File

@ -11,4 +11,6 @@
:welcome
:keycard-welcome
:new-chat
:new-public-chat])
:new-public-chat
:contact-code
:qr-scanner])

View File

@ -35,7 +35,6 @@
:mobile-network-settings
:backup-seed
:tribute-to-talk
:qr-scanner
:my-profile-ext-settings]
config/hardwallet-enabled?

View File

@ -1,7 +1,6 @@
(ns status-im.ui.screens.routing.screens
(:require [status-im.ui.screens.about-app.views :as about-app]
[status-im.ui.screens.multiaccounts.login.views :as login]
[status-im.ui.screens.multiaccounts.recover.views :as multiaccounts.recover]
[status-im.ui.screens.multiaccounts.views :as multiaccounts]
[status-im.ui.screens.add-new.new-chat.views :as new-chat]
[status-im.ui.screens.add-new.new-public-chat.view :as new-public-chat]
@ -59,8 +58,6 @@
[status-im.ui.screens.stickers.views :as stickers]
[status-im.ui.screens.wallet.collectibles.views :as collectibles]
[status-im.ui.screens.wallet.components.views :as wallet.components]
[status-im.ui.screens.wallet.request.views :as request]
[status-im.ui.screens.wallet.send.views :as send]
[status-im.ui.screens.wallet.settings.views :as wallet-settings]
[status-im.ui.screens.wallet.transactions.views :as wallet-transactions]
[status-im.ui.screens.wallet.custom-tokens.views :as custom-tokens]
@ -124,7 +121,7 @@
:profile profile.contact/profile
:new add-new/add-new
:new-chat [:modal new-chat/new-chat]
:qr-scanner qr-scanner/qr-scanner
:qr-scanner [:modal qr-scanner/qr-scanner]
:new-group group/new-group
:add-participants-toggle-list group/add-participants-toggle-list
:contact-toggle-list group/contact-toggle-list
@ -140,14 +137,7 @@
:wallet wallet.accounts/accounts-overview
:wallet-account wallet.account/account
:collectibles-list collectibles/collectibles-list
:contact-code wallet.components/contact-code
:wallet-send-transaction send/send-transaction
:recent-recipients wallet.components/recent-recipients
:select-account wallet.components/accounts
:recipient-qr-code wallet.components/recipient-qr-code
:wallet-send-assets wallet.components/send-assets
:wallet-send-transaction-request request/send-transaction-request
:wallet-request-assets wallet.components/request-assets
:contact-code [:modal wallet.components/contact-code]
:wallet-transaction-details wallet-transactions/transaction-details
:wallet-settings-hook wallet-settings/settings-hook
:wallet-settings-assets wallet-settings/manage-assets

View File

@ -10,19 +10,6 @@
:account-settings
:collectibles-list
:wallet-onboarding-setup
:contact-code
{:name :send-transaction-stack
:screens [:wallet-send-transaction
:recent-recipients
:select-account
:enter-pin-sign
:hardwallet-connect-sign
:recipient-qr-code
:wallet-send-assets]}
{:name :request-transaction-stack
:screens [:wallet-send-transaction-request
:wallet-request-assets
:recent-recipients]}
:wallet-transaction-details
:wallet-settings-hook
:wallet-settings-assets

View File

@ -7,7 +7,6 @@
:justify-content :space-between
:padding-top 16
:padding-left 16
:padding-right 24
:margin-bottom 11})
(def message-header

View File

@ -4,8 +4,6 @@
[re-frame.core :as re-frame]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.animation :as anim]
[reagent.core :as reagent]
[status-im.ui.components.list-item.views :as list-item]
[status-im.ui.components.button :as button]
[status-im.ui.components.copyable-text :as copyable-text]
@ -22,55 +20,31 @@
[status-im.ui.screens.signing.styles :as styles]
[status-im.react-native.resources :as resources]
[status-im.ui.screens.hardwallet.pin.views :as pin.views]
[status-im.ui.components.bottom-panel.views :as bottom-panel]
[status-im.utils.utils :as utils]))
(defn hide-panel-anim
[bottom-anim-value alpha-value window-height]
(anim/start
(anim/parallel
[(anim/spring bottom-anim-value {:toValue (- window-height)
:useNativeDriver true})
(anim/timing alpha-value {:toValue 0
:duration 500
:useNativeDriver true})])))
(defn show-panel-anim
[bottom-anim-value alpha-value]
(anim/start
(anim/parallel
[(anim/spring bottom-anim-value {:toValue 40
:useNativeDriver true})
(anim/timing alpha-value {:toValue 0.4
:duration 500
:useNativeDriver true})])))
(defn separator []
[react/view {:height 1 :background-color colors/gray-lighter}])
(defn displayed-name [contact]
(if (or (:preferred-name contact) (:name contact))
(multiaccounts/displayed-name contact)
(:address contact)))
(utils/get-shortened-checksum-address (:address contact))))
(defn contact-item [title contact]
[list-item/list-item
{:title-prefix title
{:title title
:title-prefix-width 45
:type :small
:title
[copyable-text/copyable-text-view
{:copied-text (displayed-name contact)}
[react/text
{:ellipsize-mode :middle
:number-of-lines 1
:style {:color colors/gray
:font-family "monospace"
;; since this goes in list-item title
;; which has design constraints
;; specified in figma spec,
;; better to do this
:line-height 22}}
(displayed-name contact)]]}])
:accessories
[[copyable-text/copyable-text-view
{:copied-text (displayed-name contact)}
[react/text
{:ellipsize-mode :middle
:number-of-lines 1
:style {:font-family "monospace"
:line-height 22}}
(displayed-name contact)]]]}])
(defn token-item [{:keys [icon color] :as token} display-symbol]
(when token
@ -107,9 +81,10 @@
[{:style {:color colors/black}} (displayed-name contact)]]
[react/text {:style {:margin-top 6 :color colors/gray}}
(str fee " " fee-display-symbol " " (string/lower-case (i18n/label :t/network-fee)))])]
[react/touchable-highlight (when-not in-progress? {:on-press #(re-frame/dispatch [:signing.ui/cancel-is-pressed])})
[react/view {:padding 6}
[react/text {:style {:color colors/blue}} (i18n/label :t/cancel)]]]])
[button/button (merge {:type :secondary
:container-style {:padding-horizontal 24}
:label (i18n/label :t/cancel)}
(when-not in-progress? {:on-press #(re-frame/dispatch [:signing.ui/cancel-is-pressed])}))]])
(views/defview keycard-pin-view []
(views/letsubs [pin [:hardwallet/pin]
@ -268,6 +243,13 @@
{:content (fn [] [sheets/fee-bottom-sheet fee-display-symbol])
:content-height 270}])}]))
(views/defview network-item []
(views/letsubs [network-name [:network-name]]
[list-item/list-item
{:title :t/network
:type :small
:accessories [[react/text network-name]]}]))
(views/defview sheet [{:keys [from contact amount token approve?] :as tx}]
(views/letsubs [fee [:signing/fee]
sign [:signing/sign]
@ -275,7 +257,8 @@
{:keys [amount-error gas-error]} [:signing/amount-errors (:address from)]
keycard-multiaccount? [:keycard-multiaccount?]
prices [:prices]
wallet-currency [:wallet/currency]]
wallet-currency [:wallet/currency]
mainnet? [:mainnet?]]
(let [display-symbol (wallet.utils/display-symbol token)
fee-display-symbol (wallet.utils/display-symbol (tokens/native-currency chain))]
[react/view styles/sheet
@ -285,6 +268,10 @@
[react/view {:padding-top 20}
[password-view sign]]
[react/view
(when-not mainnet?
[react/view
[network-item]
[separator]])
[contact-item (i18n/label :t/from) from]
[separator]
[contact-item (i18n/label :t/to) contact]
@ -300,47 +287,11 @@
:disabled? (or amount-error gas-error)
:label :t/sign-with-password}])]])])))
(defn signing-view [tx window-height]
(let [bottom-anim-value (anim/create-value window-height)
alpha-value (anim/create-value 0)
clear-timeout (atom nil)
current-tx (reagent/atom nil)
update? (reagent/atom nil)]
(reagent/create-class
{:component-will-update (fn [_ [_ tx _]]
(when @clear-timeout (js/clearTimeout @clear-timeout))
(cond
@update?
(do (reset! update? false)
(show-panel-anim bottom-anim-value alpha-value))
(and @current-tx tx)
(do (reset! update? true)
(js/setTimeout #(reset! current-tx tx) 600)
(hide-panel-anim bottom-anim-value alpha-value (- window-height)))
tx
(do (reset! current-tx tx)
(show-panel-anim bottom-anim-value alpha-value))
:else
(do (reset! clear-timeout (js/setTimeout #(reset! current-tx nil) 500))
(hide-panel-anim bottom-anim-value alpha-value (- window-height)))))
:reagent-render (fn []
(when @current-tx
[react/keyboard-avoiding-view {:style {:position :absolute :top 0 :bottom 0 :left 0 :right 0}}
[react/view {:flex 1}
[react/animated-view {:flex 1 :background-color :black :opacity alpha-value}]
[react/animated-view {:style {:position :absolute
:transform [{:translateY bottom-anim-value}]
:bottom 0 :left 0 :right 0}}
[react/view {:flex 1}
(if (:message @current-tx)
[message-sheet]
[sheet @current-tx])]]]]))})))
(views/defview signing []
(views/letsubs [tx [:signing/tx]
{window-height :height} [:dimensions/window]]
;;we use select-keys here because we don't want to update view if other keys in map is changed
[signing-view (when tx (select-keys tx [:from :contact :amount :token :approve? :message])) window-height]))
(views/letsubs [tx [:signing/tx]]
[bottom-panel/animated-bottom-panel
;;we use select-keys here because we don't want to update view if other keys in map are changed
(when tx (select-keys tx [:from :contact :amount :token :approve? :message]))
#(if (:message %)
[message-sheet]
[sheet %])]))

View File

@ -20,6 +20,7 @@
[status-im.ui.screens.popover.views :as popover]
[status-im.ui.screens.multiaccounts.recover.views :as recover.views]
[status-im.utils.dimensions :as dimensions]
[status-im.ui.screens.wallet.send.views :as wallet]
status-im.ui.screens.wallet.collectibles.etheremon.views
status-im.ui.screens.wallet.collectibles.cryptostrikers.views
status-im.ui.screens.wallet.collectibles.cryptokitties.views
@ -167,6 +168,7 @@
;; see https://reactnavigation.org/docs/en/state-persistence.html#development-mode
:persistNavigationState (when js/goog.DEBUG persist-state)
:loadNavigationState (when js/goog.DEBUG load-state)}]
[wallet/prepare-transaction]
[signing/signing]
[bottom-sheet]
[popover/popover]]]))})))

View File

@ -39,7 +39,7 @@
[icons/icon icon {:color colors/white}]
[react/text {:style {:margin-left 8 :color colors/white}} label]]]])
(views/defview account-card [{:keys [address color]}]
(views/defview account-card [{:keys [address color] :as account}]
(views/letsubs [currency [:wallet/currency]
portfolio-value [:account-portfolio-value address]
window-width [:dimensions/window-width]]
@ -62,9 +62,15 @@
:accessibility-label :share-wallet-address-icon}]]]
[react/view {:height 52 :background-color colors/black-transparent-20
:border-bottom-right-radius 8 :border-bottom-left-radius 8 :flex-direction :row}
[button (i18n/label :t/wallet-send) :main-icons/send #(re-frame/dispatch [:navigate-to :wallet-send-transaction address])]
[button
(i18n/label :t/wallet-send)
:main-icons/send
#(re-frame/dispatch [:wallet/prepare-transaction-from-wallet account])]
[react/view {:style styles/divider}]
[button (i18n/label :t/receive) :main-icons/receive #(re-frame/dispatch [:show-popover {:view :share-account :address address}])]]]))
[button
(i18n/label :t/receive)
:main-icons/receive
#(re-frame/dispatch [:show-popover {:view :share-account :address address}])]]]))
(defn render-collectible [address]
(fn [{:keys [name icon amount] :as collectible}]

View File

@ -35,20 +35,22 @@
:accessibility-label :wallet-backup-recovery-title
:on-press #(hide-sheet-and-dispatch [:navigate-to :backup-seed])}])]))
(defn send-receive [address]
(defn send-receive [account]
[react/view
[list-item/list-item
{:theme :action
:title :t/wallet-send
:icon :main-icons/send
:accessibility-label :send-transaction-button
:on-press #(hide-sheet-and-dispatch [:navigate-to :wallet-send-transaction address])}]
:on-press #(hide-sheet-and-dispatch [:wallet/prepare-transaction-from-wallet account])}]
[list-item/list-item
{:theme :action
:title :t/receive
:icon :main-icons/receive
:accessibility-label :receive-transaction-button
:on-press #(hide-sheet-and-dispatch [:show-popover {:view :share-account :address address}])}]])
:on-press #(hide-sheet-and-dispatch
[:show-popover {:view :share-account
:address (:address account)}])}]])
(defn add-account []
[react/view

View File

@ -22,7 +22,7 @@
portfolio-value [:account-portfolio-value address]]
[react/touchable-highlight {:on-press #(re-frame/dispatch [:navigate-to :wallet-account account])
:on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (fn [] [sheets/send-receive address])
{:content (fn [] [sheets/send-receive account])
:content-height 130}])}
[react/view {:style (styles/card color)}
[react/view {:flex-direction :row :align-items :center :justify-content :space-between}
@ -61,16 +61,18 @@
(when active?
[react/view {:width 24 :height 3 :border-radius 4 :background-color colors/blue}])])
(defn render-asset [currency]
(defn render-asset [currency & [on-press]]
(fn [{:keys [icon decimals amount color value] :as token}]
[list-item/list-item
{:title-prefix (wallet.utils/format-amount amount decimals)
:title (wallet.utils/display-symbol token)
:title-color-override colors/gray
:subtitle (str (if value value 0) " " currency)
:icon (if icon
[list/item-image icon]
[chat-icon/custom-icon-view-list (:name token) color])}]))
(cond-> {:title-prefix (wallet.utils/format-amount amount decimals)
:title (wallet.utils/display-symbol token)
:title-color-override colors/gray
:subtitle (str (if value value 0) " " currency)
:icon (if icon
[list/item-image icon]
[chat-icon/custom-icon-view-list (:name token) color])}
on-press
(assoc :on-press #(on-press token)))]))
(views/defview assets []
(views/letsubs [{:keys [tokens nfts]} [:wallet/all-visible-assets-with-values]

View File

@ -1,90 +0,0 @@
(ns status-im.ui.screens.wallet.choose-recipient.styles
(:require [status-im.ui.components.colors :as colors]))
(def wallet-container
{:flex 1
:background-color colors/blue})
(def recipient-button
{:flex-direction :row
:justify-content :space-between
:margin-vertical 10
:margin-left 20})
(def recipient-button-text
{:color :white
:align-self :center
:font-size 14})
(def qr-container
{:position :absolute
:top 0
:left 0
:right 0
:bottom 0})
(def preview
{:flex 1})
(def corner-dimensions
{:position :absolute
:width 40
:height 40})
(defn corner-left-bottom [height width size]
(merge corner-dimensions {:bottom (int (/ (- height size) 2))
:left (int (/ (- width size) 2))}))
(defn corner-right-bottom [height width size]
(merge corner-dimensions {:right (int (/ (- width size) 2))
:bottom (int (/ (- height size) 2))}))
(defn corner-left-top [height width size]
(merge corner-dimensions {:top (int (/ (- height size) 2))
:left (int (/ (- width size) 2))}))
(defn corner-right-top [height width size]
(merge corner-dimensions {:top (int (/ (- height size) 2))
:right (int (/ (- width size) 2))}))
(def viewfinder-port {:position :absolute
:left 0
:top 0
:bottom 0
:right 0
:flex 1})
(defn viewfinder-translucent [height width size side]
(let [top-bottom-width width
top-bottom-height (int (/ (- height size) 2))
left-right-width (int (/ (- width size) 2))
left-right-height (- height (* 2 top-bottom-height))]
(cond-> {:position :absolute
:background-color :black
:opacity 0.7}
(= :top side) (assoc :height top-bottom-height
:width top-bottom-width)
(= :right side) (assoc :height left-right-height
:width left-right-width
:top top-bottom-height
:right 0)
(= :bottom side) (assoc :height top-bottom-height
:width top-bottom-width
:bottom 0
:left 0)
(= :left side) (assoc :height left-right-height
:width left-right-width
:top top-bottom-height
:left 0))))
(def qr-code
{:flex 1
:background-color colors/white-transparent
:align-items :stretch})
(defn qr-code-text [dimensions]
{:zIndex 1
:padding-top 16
:color :white
:text-align :center
:padding-vertical 16})

View File

@ -1,69 +0,0 @@
(ns status-im.ui.screens.wallet.choose-recipient.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :as re-frame]
[status-im.i18n :as i18n]
[status-im.ui.components.toolbar :as toolbar]
[status-im.ui.components.camera :as camera]
[status-im.ui.components.react :as react]
[status-im.ui.components.toolbar.actions :as actions]
[status-im.ui.components.toolbar.view :as topbar]
[status-im.ui.screens.wallet.choose-recipient.styles :as styles]
[status-im.utils.platform :as platform]))
(defn- topbar [camera-flashlight]
[topbar/toolbar
{:transparent? true}
[topbar/nav-button (actions/back-white actions/default-handler)]
[topbar/content-title {:color :white}
(i18n/label :t/wallet-choose-recipient)]
[topbar/actions [{:icon (if (= :on camera-flashlight)
:main-icons/flash-active
:main-icons/flash-inactive)
:icon-opts {:color :white}
:handler #(re-frame/dispatch [:wallet/toggle-flashlight])}]]])
(defn- viewfinder [{:keys [height width]} size]
(let [height (cond-> height
platform/iphone-x? (- 78))]
[react/view {:style styles/viewfinder-port}
[react/view {:style (styles/viewfinder-translucent height width size :top)}]
[react/view {:style (styles/viewfinder-translucent height width size :right)}]
[react/view {:style (styles/viewfinder-translucent height width size :bottom)}]
[react/view {:style (styles/viewfinder-translucent height width size :left)}]
[react/image {:source {:uri :corner_left_top}
:style (styles/corner-left-top height width size)}]
[react/image {:source {:uri :corner_right_top}
:style (styles/corner-right-top height width size)}]
[react/image {:source {:uri :corner_left_bottom}
:style (styles/corner-left-bottom height width size)}]
[react/image {:source {:uri :corner_right_bottom}
:style (styles/corner-right-bottom height width size)}]]))
(defn- size [{:keys [height width]}]
(int (* 2 (/ (min height width) 3))))
(defview choose-recipient []
(letsubs [read-once? (atom false)
dimensions [:dimensions/window]
camera-flashlight [:wallet.send/camera-flashlight]]
[react/view {:style styles/qr-code}
[topbar camera-flashlight]
[react/text {:style (styles/qr-code-text dimensions)
:accessibility-label :scan-qr-code-with-wallet-address-text}
(i18n/label :t/scan-qr-code)]
[react/view {:style styles/qr-container
:pointer-events :none}
[react/with-activity-indicator
{}
[camera/camera {:style styles/preview
;:torchMode (camera/set-torch camera-flashlight)
:onBarCodeRead #(when-not @read-once?
(reset! read-once? true)
(re-frame/dispatch [:wallet/fill-request-from-url (camera/get-qr-code-data %) :qr]))}]]
[viewfinder dimensions (size dimensions)]]
[toolbar/toolbar
{:center {:type :secondary
:disabled? false
:on-press #(re-frame/dispatch [:navigate-back])
:accessibility-label :cancel-button
:label :t/cancel}}]]))

View File

@ -1,178 +1,10 @@
(ns status-im.ui.screens.wallet.components.styles
(:require [status-im.ui.components.colors :as colors]
[status-im.ui.components.styles :as components.styles]))
(def cartouche-container
{:flex 1
:margin-top 16
:margin-horizontal 16})
(defn cartouche-content-wrapper [disabled?]
(merge
{:flex-direction :row
:margin-top 8
:border-radius components.styles/border-radius
:padding-left 14
:padding-right 8}
(if disabled?
{:border-width 1
:border-color colors/gray-lighter}
{:background-color colors/gray-lighter})))
(def cartouche-icon-wrapper
{:flex 1
:flex-direction :row
:justify-content :space-between
:align-items :center})
(def text-content
{:color colors/black})
(def text-secondary-content
{:color colors/gray})
(def text
{:margin-right 10})
(def text-input
(merge text-content
{:flex 1
:padding-bottom 0
:padding-top 0
:height 52}))
(defn contact-code-text-input [w]
{:text-align-vertical :top
:padding-top 16
:padding-left 2
:padding-right 8
:width w
:height 72})
(def label
{:color colors/white})
(def network
{:color colors/white
:font-size 13})
(def network-container
{:flex-direction :row
:padding-horizontal 13
:padding-vertical 11
:align-items :center})
(def asset-container-read-only
{:margin-top 8
:height 52
:border-color colors/white-transparent-10
:border-width 1
:justify-content :center
:padding-left 14
:padding-vertical 14
:padding-right 8
:border-radius 8})
(def asset-content-container
{:flex-direction :row
:align-items :center
:margin-vertical 11})
(def asset-icon
{:background-color colors/gray-lighter
:border-radius 50})
(def asset-text-content
{:flex 1
:flex-direction :row
:justify-content :space-between
:align-items :center
:flex-wrap :wrap
:margin-left 10})
(def asset-label-content
{:flex-direction :row
:margin-right 10})
(def asset-label
{:margin-right 10})
(def asset-text
{:color colors/white})
(def container-disabled
{:border-width 1
:border-color colors/white-transparent-10
:border-radius 8})
(def wallet-container
{:flex-direction :row
:margin-top 8
:height 52
:border-width 1
:border-color colors/white-transparent-10
:align-items :center
:padding 14
:border-radius 8})
(def wallet-name
{:color colors/white})
(defn participant [address?]
{:color (if address? colors/black colors/gray)
:flex-shrink 1})
(def recipient-container
{:flex-direction :row})
(def recipient-icon
{:margin-top 11})
(def recipient-name
{:flex 1
:flex-direction :column
:margin-horizontal 12
:margin-vertical 16})
(def recipient-address
{:margin-vertical 17})
(def recipient-no-address
{:color colors/gray})
(:require [status-im.ui.components.colors :as colors]))
(def recent-recipients
{:flex 1
:background-color colors/white})
(def wallet-value-container
{:flex 1
:flex-direction :row})
(def wallet-value
{:padding-left 6
:color colors/white-transparent})
(def wallet-value-amount
{:flex -1})
(def separator
{:height 1
:margin-horizontal 15
:background-color colors/white-transparent-10})
(def button-text
{:color colors/white})
(def network-text
{:flex 1
:color colors/black
:font-size 14
:margin-left 16})
(def network-icon
{:width 40
:height 40
:border-radius (/ 40 2)
:background-color colors/green
:align-items :center
:justify-content :center})
{:height 1
:background-color colors/gray-lighter})

View File

@ -2,341 +2,45 @@
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.eip55 :as eip55]
[status-im.ethereum.tokens :as tokens]
[status-im.i18n :as i18n]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.components.toolbar :as toolbar]
[status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.components.list-selection :as list-selection]
[status-im.ui.components.list.styles :as list.styles]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react]
[status-im.ui.components.styles :as components.styles]
[status-im.ui.components.toolbar.actions :as actions]
[status-im.ui.components.toolbar.view :as topbar]
[status-im.ui.components.tooltip.views :as tooltip]
[status-im.ui.screens.chat.photos :as photos]
[status-im.ui.screens.wallet.choose-recipient.views
:as
choose-recipient]
[status-im.ui.screens.wallet.components.styles :as styles]
[status-im.wallet.utils :as wallet.utils]
[status-im.utils.core :as utils.core]
[status-im.utils.money :as money]
[status-im.utils.utils :as utils.utils])
[status-im.ui.components.text-input.view :as text-input]
[status-im.ui.components.colors :as colors])
(:require-macros [status-im.utils.views :as views]))
;; Wallet tab has a different coloring scheme (dark) that forces color changes (background, text)
;; It might be replaced by some theme mechanism
(defn text-input [props text]
[react/text-input (utils.core/deep-merge {:placeholder-text-color colors/gray
:selection-color colors/black
:style {:color colors/black
:height 52}}
props)
text])
(def default-action (actions/back-white actions/default-handler))
(defn topbar [title]
[topbar/simple-toolbar title])
(defn- top-view [avoid-keyboard?]
(if avoid-keyboard?
react/keyboard-avoiding-view
react/view))
(defn simple-screen
([toolbar content] (simple-screen nil toolbar content))
([{:keys [avoid-keyboard?]} toolbar content]
[(top-view avoid-keyboard?) {:flex 1}
toolbar
content]))
(defn- cartouche-content [{:keys [disabled?]} content]
[react/view {:style (styles/cartouche-content-wrapper disabled?)}
[react/view {:flex 1}
content]])
(defn cartouche [{:keys [disabled? on-press icon icon-opts] :or {icon :main-icons/next} :as m} header content]
[react/view {:style styles/cartouche-container}
[react/text
header]
(if (or disabled? (nil? on-press))
[cartouche-content m content]
[react/touchable-highlight {:on-press on-press}
[react/view
[cartouche-content m
(if-not (true? disabled?)
[react/view styles/cartouche-icon-wrapper
[react/view {:flex 1} ;; Let content shrink if needed
content]
[vector-icons/icon icon icon-opts]]
content)]]])])
(defn view-asset [symbol]
[react/view
[react/i18n-text {:style styles/label :key :wallet-asset}]
[react/view styles/asset-container-read-only
[react/text {:style styles/asset-text}
(name symbol)]]])
(defn- type->handler [k]
(case k
:send :wallet.send/set-symbol
:request :wallet.request/set-symbol
(throw (str "Unknown type: " k))))
(defn- render-token [{:keys [symbol name icon decimals amount color] :as token} type]
[list/touchable-item #(do (re-frame/dispatch [(type->handler type) symbol])
(re-frame/dispatch [:navigate-back]))
[react/view
[list/item
(if icon
[list/item-image icon]
[chat-icon/custom-icon-view-list name color])
[list/item-content
[react/view {:flex-direction :row}
[react/text {:style styles/text}
name]
[react/text {:style {:text-transform :uppercase}}
(wallet.utils/display-symbol token)]]
[list/item-secondary (wallet.utils/format-amount amount decimals)]]]]])
(views/defview assets [type address]
(views/letsubs [assets [:wallet/transferrable-assets-with-amount address]]
[simple-screen
[topbar (i18n/label :t/wallet-assets)]
[react/view {:style (assoc components.styles/flex :background-color :white)}
[list/flat-list {:default-separator? true
:data assets
:key-fn (comp str :symbol)
:render-fn #(render-token % type)}]]]))
(views/defview send-assets []
(views/letsubs [address [:get-screen-params]]
[assets :send address]))
(views/defview request-assets []
(views/letsubs [address [:get-screen-params]]
[assets :request address]))
(defn- type->view [k]
(case k
:send :wallet-send-assets
:request :wallet-request-assets
(throw (str "Unknown type: " k))))
(views/defview asset-selector [{:keys [disabled? type symbol error address]}]
(views/letsubs [balance [:balance address]
chain [:ethereum/chain-keyword]
all-tokens [:wallet/all-tokens]]
(let [{:keys [name icon decimals color] :as token} (tokens/asset-for all-tokens chain symbol)]
(when name
[react/view
[cartouche {:disabled? disabled? :on-press #(re-frame/dispatch [:navigate-to (type->view type) address])}
(i18n/label :t/wallet-asset)
[react/view {:style styles/asset-content-container
:accessibility-label :choose-asset-button}
(if icon
[list/item-image (assoc icon :style styles/asset-icon :image-style {:width 32 :height 32})]
[chat-icon/custom-icon-view-list name color 32])
[react/view styles/asset-text-content
[react/view styles/asset-label-content
[react/text {:style (merge styles/text-content styles/asset-label)}
name]
[react/text {:style styles/text-secondary-content}
(wallet.utils/display-symbol token)]]
[react/text {:style (merge styles/text-secondary-content styles/asset-label)}
(str (wallet.utils/format-amount (get balance symbol) decimals))]]]]
(when error
[tooltip/tooltip error {}])]))))
(defn- recipient-address [address modal?]
[react/text {:style (merge styles/recipient-address (when-not address styles/recipient-no-address))
:accessibility-label :recipient-address-text}
(or (eip55/address->checksum (ethereum/normalized-address address))
(if modal?
(i18n/label :t/new-contract)
(i18n/label :t/specify-recipient)))])
(views/defview recipient-contact [address name request?]
(views/letsubs [contact [:contacts/contact-by-address address]]
(let [address? (and (not (nil? address)) (not= address ""))]
[react/view styles/recipient-container
[react/view styles/recipient-icon
(when contact
[photos/photo
;;TODO this should be done in a subscription
(multiaccounts/displayed-photo contact)
{:size list.styles/image-size}])]
[react/view {:style styles/recipient-name}
[react/text {:style (styles/participant true)
:accessibility-label (if request? :contact-name-text :recipient-name-text)
:number-of-lines 1}
name]
[react/text {:style (styles/participant (and (not name) address?))
:accessibility-label (if request? :contact-address-text :recipient-address-text)}
(eip55/address->checksum (ethereum/normalized-address address))]]])))
(defn render-contact [contact request?]
[list/touchable-item #(re-frame/dispatch [:wallet/fill-request-from-contact contact request?])
[list/item
[photos/photo
;;TODO this should be done in a subscription
(multiaccounts/displayed-photo contact)
{:size list.styles/image-size}]
[list/item-content
[list/item-primary {:accessibility-label :contact-name-text}
(multiaccounts/displayed-name contact)]
[react/text {:style list.styles/secondary-text
:accessibility-label :contact-address-text}
(eip55/address->checksum (ethereum/normalized-address (:address contact)))]]]])
(defn render-account [account]
[list/touchable-item #(re-frame/dispatch [:wallet/fill-request-from-contact account false])
[list/item
[chat-icon/custom-icon-view-list (:name account) (:color account)]
[list/item-content
[list/item-primary {:accessibility-label :contact-name-text}
(:name account)]
[react/text {:style list.styles/secondary-text
:accessibility-label :contact-address-text}
(eip55/address->checksum (ethereum/normalized-address (:address account)))]]]])
(views/defview recent-recipients []
(views/letsubs [contacts [:contacts/active]
{:keys [request?]} [:get-screen-params :recent-recipients]]
[simple-screen
[topbar (i18n/label :t/recipient)]
[react/view styles/recent-recipients
[list/flat-list {:data contacts
:key-fn :address
:render-fn #(render-contact % request?)}]]]))
(views/defview accounts []
(views/letsubs [{:keys [accounts]} [:multiaccount]]
[simple-screen
[topbar (i18n/label :t/accounts)]
[react/view styles/recent-recipients
[list/flat-list {:data accounts
:key-fn :address
:render-fn render-account}]]]))
;;TODO workaround for https://github.com/facebook/react-native/issues/23653 (https://github.com/status-im/status-react/issues/8548)
(def tw (reagent/atom "95%"))
(views/defview contact-code []
(views/letsubs [content (reagent/atom nil)]
[simple-screen {:avoid-keyboard? true}
[topbar (i18n/label :t/recipient)]
[react/view components.styles/flex
[cartouche {}
(i18n/label :t/recipient)
[text-input {:ref (fn [v] (js/setTimeout #(reset! tw (if v "100%" "95%")) 100))
:multiline true
:style (styles/contact-code-text-input @tw)
:placeholder (i18n/label :t/recipient-code)
:on-change-text #(reset! content %)
:accessibility-label :recipient-address-input}]]
[toolbar/toolbar {:center {:type :secondary
:disabled? (string/blank? @content)
:on-press #(re-frame/dispatch [:wallet.send/set-recipient @content])
:label :t/done}}]]]))
(defn recipient-qr-code []
[choose-recipient/choose-recipient])
(defn- request-camera-permissions []
(re-frame/dispatch [:request-permissions {:permissions [:camera]
:on-allowed #(re-frame/dispatch [:navigate-to :recipient-qr-code])
:on-denied #(utils.utils/set-timeout
(fn []
(utils.utils/show-popup (i18n/label :t/error)
(i18n/label :t/camera-access-error)))
50)}]))
(defn- on-choose-recipient [contact-only? request?]
(list-selection/show {:title (i18n/label :t/wallet-choose-recipient)
;;TODO temporary hide for v1
#_(concat
[{:label (i18n/label :t/recent-recipients)
:action #(re-frame/dispatch [:navigate-to :recent-recipients {:request? request?}])}]
(when-not contact-only?))
:options [{:label (i18n/label :t/accounts)
:action #(re-frame/dispatch [:navigate-to :select-account])}
{:label (i18n/label :t/scan-qr)
:action request-camera-permissions}
{:label (i18n/label :t/recipient-code)
:action #(re-frame/dispatch [:navigate-to :contact-code])}]}))
(defn recipient-selector [{:keys [name address disabled? contact-only? request? modal?]}]
[cartouche {:on-press #(on-choose-recipient contact-only? request?)
:disabled? disabled?
:icon :main-icons/more
:icon-opts {:accessibility-label :choose-contact-button}}
(i18n/label :t/wallet-choose-recipient)
[react/view {:accessibility-label :choose-recipient-button}
(if name
[recipient-contact address name request?]
[recipient-address address modal?])]])
(defn amount-input [{:keys [input-options amount amount-text disabled?]}
{:keys [symbol decimals]}]
[react/view {:style components.styles/flex
:accessibility-label :specify-amount-button}
[text-input
(merge
input-options
;; We only auto-correct and prettify user's input when it is valid and positive.
;; Otherwise, user might want to fix his input and autocorrection will give more harm than good.
;; Positive check is because we don't want to replace unfinished 0.000 with just plain 0, that is annoying and
;; potentially dangerous on this screen (e.g. sending 7 ETH instead of 0.0007)
{:default-value (if (empty? amount-text)
(str (money/to-fixed (money/internal->formatted amount symbol decimals)))
amount-text)}
(if disabled?
{:editable false
:placeholder ""}
{:keyboard-type :numeric
:placeholder (i18n/label :t/amount-placeholder)
:style components.styles/flex
:accessibility-label :amount-input}))]])
(defn amount-selector [{:keys [error disabled?] :as m} token]
[react/view
[cartouche {:disabled? disabled?}
(i18n/label :t/amount)
[amount-input m token]]
(when error
[tooltip/tooltip error])])
(defn separator []
[react/view styles/separator])
(defn button-text [label]
[react/text {:style styles/button-text}
label])
(defn- recipient-topbar [content]
[topbar/toolbar {:transparent? true}
[topbar/nav-text
{:style {:margin-left 16}
:handler #(do
(re-frame/dispatch [:set-in [:wallet/prepare-transaction :modal-opened?] false])
(re-frame/dispatch [:navigate-back]))}
(i18n/label :t/cancel)]
[topbar/content-title {}
(i18n/label :t/recipient)]
[topbar/text-action
{:disabled? (string/blank? content)
:style {:margin-right 16}
:handler #(re-frame/dispatch [:wallet.send/set-recipient content])}
(i18n/label :t/done)]])
(views/defview network-info []
(views/letsubs [network-id [:chain-id]]
[react/view
[react/view styles/network-container
[react/view styles/network-icon
[vector-icons/icon :main-icons/network {:color :white}]]
[react/text
{:style {:flex 1
:padding-left 16}}
(cond (ethereum/testnet? network-id)
(i18n/label :t/testnet-text {:testnet (get-in ethereum/chains [(ethereum/chain-id->chain-keyword network-id) :name] "Unknown")})
(ethereum/sidechain? network-id)
(i18n/label :t/sidechain-text {:sidechain (get-in ethereum/chains [(ethereum/chain-id->chain-keyword network-id) :name] "Unknown")})
:else
(i18n/label :t/mainnet-text))]]]))
(views/defview contact-code []
(views/letsubs [content (reagent/atom nil)]
[react/view {:flex 1}
[recipient-topbar @content]
[react/view
[text-input/text-input-with-label
{:multiline true
:container {:margin 16 :padding-vertical 16 :height 72}
:style {:text-align-vertical :top :height 42}
:placeholder "0x... or username.domain.eth" ;(i18n/label :t/recipient-code)
:on-change-text #(reset! content %)
:accessibility-label :recipient-address-input}]
[react/text {:style {:color colors/gray :margin-horizontal 16}}
"Enter address or username of the recepient"]]]))

View File

@ -1,13 +1,7 @@
(ns status-im.ui.screens.wallet.navigation
(:require [status-im.constants :as constants]
[status-im.ui.screens.navigation :as navigation]
(:require [status-im.ui.screens.navigation :as navigation]
[status-im.ui.screens.wallet.signing-phrase.views :as signing-phrase]))
(defn transaction-send-default [address]
{:method constants/web3-send-transaction
:from address
:symbol :ETH})
(defmethod navigation/preload-data! :wallet-stack
[db [event]]
(let [wallet-set-up-passed? (get-in db [:multiaccount :wallet-set-up-passed?])]
@ -15,20 +9,6 @@
db
(assoc db :popover/popover {:view [signing-phrase/signing-phrase]}))))
(defmethod navigation/preload-data! :wallet-send-transaction-request
[db [event _ address]]
(if (= event :navigate-back)
db
(-> db
(assoc-in [:wallet :request-transaction] {:symbol :ETH})
(assoc-in [:wallet :send-transaction] (transaction-send-default address)))))
(defmethod navigation/preload-data! :wallet-send-transaction
[db [event _ address]]
(if (= event :navigate-back)
db
(assoc-in db [:wallet :send-transaction] (transaction-send-default address))))
(defmethod navigation/preload-data! :wallet-add-custom-token
[db [event]]
(dissoc db :wallet/custom-token-screen))

View File

@ -1,10 +0,0 @@
(ns status-im.ui.screens.wallet.request.db
(:require [cljs.spec.alpha :as spec]
[status-im.utils.money :as money]))
(spec/def ::amount (spec/nilable money/valid?))
(spec/def ::amount-error (spec/nilable string?))
(spec/def ::amount-text (spec/nilable string?))
(spec/def ::symbol (spec/nilable keyword?))
(spec/def :wallet/request-transaction (spec/keys :opt-un [::amount ::amount-error ::amount-text ::symbol]))

View File

@ -1,28 +0,0 @@
(ns status-im.ui.screens.wallet.request.styles
(:require [status-im.ui.components.colors :as colors]))
(def hint
{:color colors/white-transparent})
(def footer
{:color colors/white
:border-color colors/white-transparent-10})
(def bottom-buttons
{:background-color colors/blue})
(def request-wrapper
{:flex 1
:flex-direction :column})
;; Request panel
(def request-details-wrapper
{:padding-bottom 60})
(def send-request
{:background-color colors/black-transparent
:margin-top 12
:margin-bottom 16
:margin-horizontal 16
:height 42})

View File

@ -3,61 +3,12 @@
[reagent.core :as reagent]
[status-im.ethereum.eip55 :as eip55]
[status-im.ethereum.eip681 :as eip681]
[status-im.ethereum.tokens :as tokens]
[status-im.i18n :as i18n]
[status-im.ui.components.button :as button]
[status-im.ui.components.copyable-text :as copyable-text]
[status-im.ui.components.qr-code-viewer.views :as qr-code-viewer]
[status-im.ui.components.react :as react]
[status-im.ui.components.styles :as components.styles]
[status-im.ui.components.toolbar :as toolbar]
[status-im.ui.screens.wallet.components.views :as wallet.components]
[status-im.ui.screens.wallet.request.styles :as styles]
[status-im.utils.utils :as utils]
[status-im.wallet.utils :as wallet.utils])
[status-im.ui.components.react :as react])
(:require-macros [status-im.utils.views :as views]))
;;TODO DEPRECATED
(views/defview send-transaction-request []
;; TODO(jeluard) both send and request flows should be merged
(views/letsubs [chain [:ethereum/chain-keyword]
{:keys [amount amount-error amount-text symbol to
to-name public-key]} [:wallet.request/transaction]
network-status [:network-status]
all-tokens [:wallet/all-tokens]
scroll (atom nil)]
(let [{:keys [decimals] :as token} (tokens/asset-for all-tokens chain symbol)]
[wallet.components/simple-screen {:avoid-keyboard? true}
[wallet.components/topbar (i18n/label :t/new-request)]
[react/view components.styles/flex
[wallet.components/network-info]
[react/scroll-view {:ref #(reset! scroll %) :keyboardShouldPersistTaps :always}
[react/view styles/request-details-wrapper
[wallet.components/recipient-selector
{:contact-only? true
:address to
:name to-name
:request? true}]
[wallet.components/asset-selector
{:disabled? false
:type :request
:symbol symbol}]
[wallet.components/amount-selector
{:error amount-error
:disabled? (= :offline network-status)
:amount amount
:amount-text amount-text
:input-options {:on-focus (fn [] (when @scroll (utils/set-timeout #(.scrollToEnd @scroll) 100)))
:on-change-text #(re-frame/dispatch [:wallet.request/set-and-validate-amount % symbol decimals])}}
token]]]
[toolbar/toolbar
{:right {:type :next
:disabled? (or amount-error (not (and to amount)))
:on-press #(re-frame/dispatch [:wallet-send-request public-key amount
(wallet.utils/display-symbol token) decimals])
:accessibility-label :sent-request-button
:label :t/send-request}}]]])))
(views/defview share-address []
(views/letsubs [{:keys [address]} [:popover/popover]
chain-id [:chain-id]

View File

@ -1,42 +0,0 @@
(ns status-im.ui.screens.wallet.send.db
(:require [cljs.spec.alpha :as spec]
[status-im.utils.money :as money]
[status-im.utils.security :as security]))
; transaction
(spec/def ::from (spec/nilable string?))
(spec/def ::to (spec/nilable string?))
(spec/def ::amount (spec/nilable money/valid?))
(spec/def ::gas (spec/nilable money/valid?))
(spec/def ::original-gas (spec/nilable money/valid?))
(spec/def ::gas-price (spec/nilable money/valid?))
; dapp transaction
(spec/def ::data (spec/nilable string?))
(spec/def ::nonce (spec/nilable string?))
(spec/def ::to-name (spec/nilable string?))
(spec/def ::amount-error (spec/nilable string?))
(spec/def ::asset-error (spec/nilable string?))
(spec/def ::amount-text (spec/nilable string?))
(spec/def ::password (spec/nilable #(instance? security/MaskedData %)))
(spec/def ::wrong-password? (spec/nilable boolean?))
(spec/def ::id (spec/nilable string?))
(spec/def ::show-password-input? (spec/nilable boolean?))
(spec/def ::height double?)
(spec/def ::width double?)
(spec/def ::camera-flashlight #{:on :off})
(spec/def ::in-progress? boolean?)
(spec/def ::from-chat? (spec/nilable boolean?))
(spec/def ::symbol (spec/nilable keyword?))
(spec/def ::advanced? boolean?)
(spec/def ::public-key (spec/nilable string?))
(spec/def ::method (spec/nilable string?))
(spec/def ::tx-hash (spec/nilable string?))
(spec/def ::on-result (spec/nilable any?))
(spec/def ::on-error (spec/nilable any?))
(spec/def :wallet/send-transaction (spec/keys :opt-un [::amount ::to ::to-name ::amount-error ::asset-error ::amount-text
::password ::show-password-input? ::id ::from ::data ::nonce
::camera-flashlight ::in-progress? ::on-result ::on-error
::wrong-password? ::from-chat? ::symbol ::advanced?
::gas ::gas-price ::original-gas ::public-key ::method ::tx-hash]))

View File

@ -0,0 +1,70 @@
(ns status-im.ui.screens.wallet.send.sheets
(:require-macros [status-im.utils.views :refer [defview letsubs] :as views])
(:require [re-frame.core :as re-frame]
[status-im.i18n :as i18n]
[status-im.ui.components.react :as react]
[status-im.ui.components.list-item.views :as list-item]
[status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.ui.components.list.views :as list]
[status-im.ui.screens.wallet.accounts.views :as wallet.accounts]
[status-im.utils.utils :as utils.utils]))
(views/defview assets [address]
(views/letsubs [{:keys [tokens]} [:wallet/visible-assets-with-values address]
currency [:wallet/currency]]
[list/flat-list
{:data tokens
:key-fn (comp str :symbol)
:render-fn (wallet.accounts/render-asset
(:code currency)
#(re-frame/dispatch [:wallet.send/set-symbol (:symbol %)]))}]))
(defn render-account [field]
(fn [account]
[list-item/list-item
{:icon [chat-icon/custom-icon-view-list (:name account) (:color account)]
:title (:name account)
:on-press #(re-frame/dispatch [:wallet.send/set-field field account])}]))
(views/defview accounts-list [field]
(views/letsubs [{:keys [accounts]} [:multiaccount]]
[list/flat-list {:data accounts
:key-fn :address
:render-fn (render-account field)}]))
(defn- request-camera-permissions []
(let [options {:handler :wallet.send/qr-scanner-result
:cancel-handler :wallet.send/qr-scanner-cancel}]
(re-frame/dispatch
[:request-permissions
{:permissions [:camera]
:on-allowed #(re-frame/dispatch [:wallet.send/qr-scanner-allowed options])
:on-denied
#(utils.utils/set-timeout
(fn []
(utils.utils/show-popup (i18n/label :t/error)
(i18n/label :t/camera-access-error)))
50)}])))
(defn show-accounts-list []
(re-frame/dispatch [:bottom-sheet/hide-sheet])
(js/setTimeout #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (fn [] [accounts-list :to])
:content-height 300}]) 400))
(defn choose-recipient []
[react/view
(for [item [{:title (i18n/label :t/accounts)
:icon :main-icons/profile
:theme :action
:on-press show-accounts-list}
{:title (i18n/label :t/scan-qr)
:icon :main-icons/qr
:theme :action
:on-press request-camera-permissions}
{:title (i18n/label :t/recipient-code)
:icon :main-icons/address
:theme :action
:on-press #(re-frame/dispatch [:wallet.send/navigate-to-recipient-code])}]]
^{:key item}
[list-item/list-item item])])

View File

@ -1,56 +1,14 @@
(ns status-im.ui.screens.wallet.send.styles
(:require [status-im.ui.components.colors :as colors]
[status-im.ui.screens.wallet.components.styles :as wallet.components.styles]))
(ns status-im.ui.screens.wallet.send.styles)
(def send-transaction-form
{:flex 1
:padding-bottom 60})
(defn sheet [small-screen?]
{:background-color :white
:border-top-right-radius 16
:border-top-left-radius 16
:padding-bottom (if small-screen? 40 60)})
(def signing-phrase-description
{:padding-top 8})
(def password-container
{:flex 1
:padding-vertical 20})
(def password
{:padding 0
:height 20})
(def processing-view
{:position :absolute
:top 0
:bottom 0
:right 0
:left 0
:align-items :center
:justify-content :center
:background-color (str colors/black "1A")})
(def empty-text
{:text-align :center
:margin-top 22
:margin-horizontal 92})
(def advanced-button
{:flex-direction :row
:background-color colors/black-transparent
:border-radius 50
:padding 8
:align-items :center})
(def advanced-label
{:text-align-vertical :center
:margin-left 4})
(def transaction-fee-info
{:flex-direction :row
:margin 15})
(def transaction-fee-input
{:keyboard-type :numeric
:auto-capitalize "none"
:placeholder "0.000"
:placeholder-text-color colors/white-transparent
:selection-color colors/white
:style wallet.components.styles/text-input})
(defn header [small-screen?]
{:flex-direction :row
:align-items :center
:justify-content :space-between
:padding-top (when-not small-screen? 16)
:padding-left 16})

View File

@ -1,67 +1,122 @@
(ns status-im.ui.screens.wallet.send.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require-macros [status-im.utils.views :refer [defview letsubs] :as views])
(:require [re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.i18n :as i18n]
[status-im.ui.components.react :as react]
[status-im.ui.components.styles :as components.styles]
[status-im.ui.components.toolbar.view :as topbar]
[status-im.ui.components.toolbar :as toolbar]
[status-im.ui.screens.wallet.components.views :as wallet.components]
[status-im.ui.screens.wallet.send.styles :as styles]))
[status-im.ui.screens.wallet.send.styles :as styles]
[status-im.ui.components.bottom-panel.views :as bottom-panel]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.list-item.views :as list-item]
[status-im.ui.components.tooltip.views :as tooltip]
[status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.ui.components.list.views :as list]
[status-im.wallet.utils :as wallet.utils]
[status-im.ui.components.icons.vector-icons :as icons]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.screens.wallet.send.sheets :as sheets]
[status-im.ui.screens.wallet.components.views :as components]
[status-im.utils.utils :as utils]
[status-im.ui.components.button :as button]))
(defn- sign-transaction-button [sign-enabled?]
[toolbar/toolbar
{:right {:type :next
:disabled? (not sign-enabled?)
:on-press #(re-frame/dispatch [:wallet.ui/sign-transaction-button-clicked])
:accessibility-label :sign-transaction-button
:label :t/transactions-sign-transaction}}])
(defn header [small-screen?]
[react/view (styles/header small-screen?)
[react/view {:flex 1}
[react/text {:style (merge {:typography :title-bold} (when small-screen? {:font-size 15}))}
(i18n/label :t/send-transaction)]]
[button/button {:type :secondary
:container-style {:padding-horizontal 24}
:label (i18n/label :t/cancel)
:on-press #(re-frame/dispatch [:set :wallet/prepare-transaction nil])}]])
;;TODO DEPRECATED
(defn- render-send-transaction-view
[{:keys [transaction scroll amount-input]}]
(let [{:keys [from amount amount-text amount-error token sign-enabled?
asset-error to to-name symbol]} transaction]
[wallet.components/simple-screen {:avoid-keyboard? true}
[topbar/simple-toolbar (i18n/label :t/send-transaction)]
[react/view components.styles/flex
[wallet.components/network-info]
[react/scroll-view {:keyboard-should-persist-taps :never
:keyboard-dismiss-mode :on-drag
:ref #(reset! scroll %)
:on-content-size-change #(when (and scroll @scroll)
(.scrollToEnd @scroll))}
[react/view styles/send-transaction-form
[wallet.components/recipient-selector
{:address to
:name to-name}]
[wallet.components/asset-selector
{:error asset-error
:address from
:type :send
:symbol symbol}]
[wallet.components/amount-selector
{:error amount-error
:amount amount
:amount-text amount-text
:input-options {:on-change-text #(re-frame/dispatch [:wallet.send/set-and-validate-amount %])
:ref (partial reset! amount-input)}} token]]]
[sign-transaction-button sign-enabled?]]]))
(defn asset-selector [{:keys [token from]}]
(let [{:keys [name icon color]} token]
[react/touchable-highlight
{:on-press #(do
(re-frame/dispatch [:dismiss-keyboard])
(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (fn [] [sheets/assets (:address from)])
:content-height 300}]))}
[react/view {:style {:flex-direction :row
:align-items :center
:margin-left 16}
:accessibility-label :choose-asset-button}
(if icon
[list/item-image (assoc icon :style {:background-color colors/gray-lighter
:border-radius 50} :image-style {:width 32 :height 32})]
[chat-icon/custom-icon-view-list name color 32])
[react/text {:style {:margin-left 8}}
(wallet.utils/display-symbol token)]
[icons/icon :main-icons/dropdown {:color colors/gray}]]]))
(defn- send-transaction-view [{:keys [scroll]}]
(let [amount-input (atom nil)
handler #(when (and scroll @scroll @amount-input (.isFocused @amount-input)) (.scrollToEnd @scroll))]
(reagent/create-class
{:component-did-mount (fn [_]
;;NOTE(goranjovic): keyboardDidShow is for android and keyboardWillShow for ios
(.addListener react/keyboard "keyboardDidShow" handler)
(.addListener react/keyboard "keyboardWillShow" handler))
:reagent-render (fn [opts] (render-send-transaction-view
(assoc opts :amount-input amount-input)))})))
(defn render-account [account {:keys [amount decimals] :as token}]
[list-item/list-item
{:icon [chat-icon/custom-icon-view-list (:name account) (:color account)]
:title (:name account)
:subtitle (str (wallet.utils/format-amount amount decimals)
" "
(wallet.utils/display-symbol token))
:accessories [:chevron]
:on-press #(do
(re-frame/dispatch [:dismiss-keyboard])
(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (fn [] [sheets/accounts-list :from])
:content-height 300}]))}])
(defview send-transaction []
(letsubs [transaction [:wallet.send/transaction]
scroll (atom nil)]
[send-transaction-view {:transaction transaction
:scroll scroll}]))
(defn render-contact [contact from-chat?]
(if from-chat?
[list-item/list-item {:title (multiaccounts/displayed-name contact)
:icon (multiaccounts/displayed-photo contact)}]
[list-item/list-item
{:title (utils/get-shortened-checksum-address
(if (string? contact) contact (:address contact)))
:subtitle (when-not contact (i18n/label :t/wallet-choose-recipient))
:on-press #(do
(re-frame/dispatch [:dismiss-keyboard])
(re-frame/dispatch [:bottom-sheet/show-sheet
{:content sheets/choose-recipient
:content-height 200}]))
:accessories [:chevron]}]))
(views/defview sheet [_]
(views/letsubs [{:keys [amount-error amount-text from token to sign-enabled? from-chat?] :as tx}
[:wallet.send/prepare-transaction-with-balance]
small-screen? [:dimensions/small-screen?]]
[react/view (styles/sheet small-screen?)
[header small-screen?]
[react/view (merge {:flex-direction :row :padding-horizontal 24 :align-items :center}
(when-not small-screen?
{:margin-top 16 :margin-bottom 16}))
[react/text-input
{:style {:font-size (if small-screen? 24 38)
:color (when amount-error colors/red)
:flex-shrink 1}
:keyboard-type :numeric
:accessibility-label :amount-input
:default-value amount-text
:auto-focus true
:on-change-text #(re-frame/dispatch [:wallet.send/set-amount-text %])
:placeholder "0.0 "}]
[asset-selector tx]
(when amount-error
[tooltip/tooltip amount-error {:bottom-value 2
:font-size 12}])]
[components/separator]
[list-item/list-item {:type :section-header :title :t/from}]
[render-account from token]
[list-item/list-item {:type :section-header :title :t/to}]
[render-contact to from-chat?]
[toolbar/toolbar
{:center {:label :t/wallet-send
:disabled? (not sign-enabled?)
:on-press #(re-frame/dispatch [:wallet.ui/sign-transaction-button-clicked tx])}}]]))
(defview prepare-transaction []
(letsubs [tx [:wallet/prepare-transaction]]
[bottom-panel/animated-bottom-panel
;;we use select-keys here because we don't want to update view if other keys in map are changed
;; and because modal screen (qr code scanner) can't be opened over bottom sheet we have to use :modal-opened?
;; to hide our transaction panel
(when (and tx (not (:modal-opened? tx)))
(select-keys tx [:from-chat?]))
sheet]))

View File

@ -81,8 +81,8 @@
(navigation/navigate-to-cofx (assoc-in cofx [:db :contacts/identity] public-key) :profile nil))))
(fx/defn handle-eip681 [cofx url]
{:dispatch-n [[:navigate-to :wallet-send-transaction]
[:wallet/fill-request-from-url url :deep-link]]})
{:dispatch-n [[:navigate-to :wallet]
[:wallet/fill-request-from-url url]]})
(defn handle-not-found [full-url]
(log/info "universal-links: no handler for " full-url))

View File

@ -8,7 +8,8 @@
[status-im.ethereum.ens :as ens]
[status-im.i18n :as i18n]
[status-im.utils.money :as money]
[status-im.utils.fx :as fx]))
[status-im.utils.fx :as fx]
[status-im.ui.screens.navigation :as navigation]))
(fx/defn toggle-flashlight
{:events [:wallet/toggle-flashlight]}
@ -23,21 +24,27 @@
(defn- fill-request-details [db {:keys [address name value symbol gas gasPrice public-key from-chat?]} request?]
{:pre [(not (nil? address))]}
(let [name (or name (find-address-name db address))
;;TODO request isn't implemented
data-path (if request?
[:wallet :request-transaction]
[:wallet :send-transaction])]
(update-in db data-path
(fn [{old-symbol :symbol :as old-transaction}]
(let [symbol-changed? (not= old-symbol symbol)]
(cond-> (assoc old-transaction :to address :to-name name :public-key public-key)
value (assoc :amount value)
symbol (assoc :symbol symbol)
(and gas symbol-changed?) (assoc :gas (money/bignumber gas))
from-chat? (assoc :from-chat? from-chat?)
(and gasPrice symbol-changed?)
(assoc :gas-price (money/bignumber gasPrice))
(and symbol (not gasPrice) symbol-changed?)
(assoc :gas-price (ethereum/estimate-gas symbol))))))))
:wallet/prepare-transaction
:wallet/prepare-transaction)]
(update db data-path
(fn [{old-symbol :symbol :as old-transaction}]
(let [symbol-changed? (not= old-symbol symbol)]
(cond-> (assoc old-transaction
:to address :to-name name :public-key public-key
:gas (money/bignumber gas)
:gas-price (money/bignumber gasPrice))
value (assoc :amount value)
symbol (assoc :symbol symbol)
from-chat? (assoc :from-chat? from-chat?)))))))
;;TODO request isn't implemented
(fx/defn fill-request-from-contact
{:events [:wallet/fill-request-from-contact]}
[{db :db} {:keys [address name public-key]} request?]
{:db (fill-request-details db {:address address :name name :public-key public-key} request?)
:dispatch [:navigate-back]})
(defn- extract-details
"First try to parse as EIP681 URI, if not assume this is an address directly.
@ -51,16 +58,13 @@
;; NOTE(janherich) - whenever changing assets, we want to clear the previusly set amount/amount-text
(defn changed-asset [{:keys [db] :as fx} old-symbol new-symbol]
(-> fx
(merge {:wallet/update-gas-price
{:success-event :wallet/update-gas-price-success
:edit? false}})
(assoc-in [:db :wallet :send-transaction :amount] nil)
(assoc-in [:db :wallet :send-transaction :amount-text] nil)
(assoc-in [:db :wallet :send-transaction :asset-error]
(assoc-in [:db :wallet/prepare-transaction :amount] nil)
(assoc-in [:db :wallet/prepare-transaction :amount-text] nil)
(assoc-in [:db :wallet/prepare-transaction :asset-error]
(i18n/label :t/changed-asset-warning {:old old-symbol :new new-symbol}))))
(defn changed-amount-warning [fx old-amount new-amount]
(assoc-in fx [:db :wallet :send-transaction :amount-error]
(assoc-in fx [:db :wallet/prepare-transaction :amount-error]
(i18n/label :t/changed-amount-warning {:old old-amount :new new-amount})))
(defn use-default-eth-gas [fx]
@ -84,41 +88,48 @@
(let [checksum (eip55/address->checksum recipient)]
(if (eip55/valid-address-checksum? checksum)
{:db (-> db
(assoc-in [:wallet :send-transaction :to] checksum)
(assoc-in [:wallet :send-transaction :to-name] nil))
(assoc-in [:wallet/prepare-transaction :to] checksum)
(assoc-in [:wallet/prepare-transaction :modal-opened?] false))
:dispatch [:navigate-back]}
{:ui/show-error (i18n/label :t/wallet-invalid-address-checksum {:data recipient})}))
{:ui/show-error (i18n/label :t/wallet-invalid-address {:data recipient})}))))
(fx/defn fill-request-from-url
{:events [:wallet/fill-request-from-url]}
[{{:networks/keys [current-network] :wallet/keys [all-tokens] :as db} :db} data origin]
(let [current-chain-id (get-in constants/default-networks [current-network :config :NetworkId])
[{{:networks/keys [current-network] :wallet/keys [all-tokens] :as db} :db} data]
(let [current-chain-id (get-in constants/default-networks [current-network :config :NetworkId])
{:keys [address chain-id] :as details} (extract-details data current-chain-id all-tokens)
valid-network? (boolean (= current-chain-id chain-id))
previous-state (get-in db [:wallet :send-transaction])
old-symbol (:symbol previous-state)
new-symbol (:symbol details)
old-amount (:amount previous-state)
new-amount (:value details)
new-gas (:gas details)
symbol-changed? (and old-symbol new-symbol (not= old-symbol new-symbol))]
valid-network? (boolean (= current-chain-id chain-id))
previous-state (get db :wallet/prepare-transaction)
old-symbol (:symbol previous-state)
new-symbol (:symbol details)
old-amount (:amount previous-state)
new-amount (:value details)
symbol-changed? (and old-symbol new-symbol (not= old-symbol new-symbol))
amount-changed? (and old-amount new-amount (not= old-amount new-amount))]
(cond-> {:db db}
(not= :deep-link origin) (assoc :dispatch [:navigate-back]) ;; Only navigate-back when called from within wallet
(and address valid-network?) (update :db #(fill-request-details % details false))
symbol-changed? (changed-asset old-symbol new-symbol)
(and old-amount new-amount (not= old-amount new-amount)) (changed-amount-warning old-amount new-amount)
;; NOTE(goranjovic) - the next line is there is because QR code scanning switches the amount to ETH
;; automatically, so we need to update the gas limit accordingly. The check for origin screen is there
;; so that we wouldn't also switch gas limit to ETH specific if the user pastes address as text.
;; We need to check if address is defined so that we wouldn't trigger this behavior when invalid QR is scanned
;; (e.g. public-key)
(and address (= origin :qr) (not new-gas) symbol-changed?) (use-default-eth-gas)
amount-changed? (changed-amount-warning old-amount new-amount)
(not (:from previous-state))
(update :db assoc-in [:wallet/prepare-transaction :from]
(ethereum/get-default-account (get-in db [:multiaccount :accounts])))
(not old-symbol)
(update :db assoc-in [:wallet/prepare-transaction :symbol] (or new-symbol :ETH))
(not address) (assoc :ui/show-error (i18n/label :t/wallet-invalid-address {:data data}))
(and address (not valid-network?)) (assoc :ui/show-error (i18n/label :t/wallet-invalid-chain-id {:data data :chain current-chain-id})))))
(and address (not valid-network?))
(assoc :ui/show-error (i18n/label :t/wallet-invalid-chain-id
{:data data :chain current-chain-id})))))
(fx/defn fill-request-from-contact
{:events [:wallet/fill-request-from-contact]}
[{db :db} {:keys [address name public-key]} request?]
{:db (fill-request-details db {:address address :name name :public-key public-key} request?)
:dispatch [:navigate-back]})
(fx/defn qr-scanner-result
{:events [:wallet.send/qr-scanner-result]}
[{db :db :as cofx} data opts]
(fx/merge cofx
{:db (assoc-in db [:wallet/prepare-transaction :modal-opened?] false)}
(navigation/navigate-back)
(fill-request-from-url data)))
(fx/defn qr-scanner-cancel
{:events [:wallet.send/qr-scanner-cancel]}
[{db :db} _]
{:db (assoc-in db [:wallet/prepare-transaction :modal-opened?] false)})

View File

@ -20,7 +20,9 @@
[status-im.wallet.db :as wallet.db]
[status-im.ethereum.abi-spec :as abi-spec]
[status-im.signing.core :as signing]
[clojure.string :as string]))
[clojure.string :as string]
[status-im.contact.db :as contact.db]
[status-im.ui.components.bottom-sheet.core :as bottom-sheet]))
(re-frame/reg-fx
:wallet/get-balance
@ -342,17 +344,9 @@
(multiaccounts.update/update-settings cofx new-settings {})))
(fx/defn set-and-validate-amount
{:events [:wallet.send/set-and-validate-amount]}
{:events [:wallet.send/set-amount-text]}
[{:keys [db]} amount]
(let [chain (ethereum/chain-keyword db)
all-tokens (:wallet/all-tokens db)
symbol (get-in db [:wallet :send-transaction :symbol])
{:keys [decimals]} (tokens/asset-for all-tokens chain symbol)
{:keys [value error]} (wallet.db/parse-amount amount decimals)]
{:db (-> db
(assoc-in [:wallet :send-transaction :amount] (money/formatted->internal value symbol decimals))
(assoc-in [:wallet :send-transaction :amount-text] amount)
(assoc-in [:wallet :send-transaction :amount-error] error))}))
{:db (assoc-in db [:wallet/prepare-transaction :amount-text] amount)})
(fx/defn set-symbol
{:events [:wallet.send/set-symbol]}
@ -365,21 +359,27 @@
(fx/defn sign-transaction-button-clicked
{:events [:wallet.ui/sign-transaction-button-clicked]}
[{:keys [db] :as cofx}]
(let [{:keys [to symbol amount from]} (get-in cofx [:db :wallet :send-transaction])
{:keys [symbol address]} (tokens/asset-for (:wallet/all-tokens db)
(ethereum/chain-keyword db)
symbol)
[{:keys [db] :as cofx} {:keys [to amount from token from-chat? gas gasPrice]}]
(let [{:keys [symbol address]} token
amount-hex (str "0x" (abi-spec/number-to-hex amount))
to-norm (ethereum/normalized-address to)]
(signing/sign cofx {:tx-obj (if (= symbol :ETH)
{:to to-norm
:from from
:value amount-hex}
{:to (ethereum/normalized-address address)
:from from
:data (abi-spec/encode "transfer(address,uint256)" [to-norm amount-hex])})
:on-result [:navigate-back]})))
to-norm (ethereum/normalized-address (if (string? to) to (:address to)))
from-address (:address from)]
(fx/merge cofx
{:db (dissoc db :wallet/prepare-transaction)}
#(if from-chat?
nil;;TODO from chat, send request message or if ens name sign tx and send tx message
(signing/sign % {:tx-obj (if (= symbol :ETH)
{:to to-norm
:from from-address
:value amount-hex}
{:to (ethereum/normalized-address address)
:from from-address
:data (abi-spec/encode
"transfer(address,uint256)"
[to-norm amount-hex])
;;Note: data from qr (eip681)
:gas gas
:gasPrice gasPrice})})))))
(fx/defn set-and-validate-amount-request
{:events [:wallet.request/set-and-validate-amount]}
@ -390,8 +390,60 @@
(assoc-in [:wallet :request-transaction :amount-text] amount)
(assoc-in [:wallet :request-transaction :amount-error] error))}))
;;TODO request isn't implemented
(fx/defn set-symbol-request
{:events [:wallet.request/set-symbol]}
[{:keys [db]} symbol]
{:db (-> db
(assoc-in [:wallet :request-transaction :symbol] symbol))})
{:db (assoc-in db [:wallet :request-transaction :symbol] symbol)})
(fx/defn prepare-transaction-from-chat
{:events [:wallet/prepare-transaction-from-chat]}
[{:keys [db]}]
(let [identity (:current-chat-id db)]
{:db (assoc db :wallet/prepare-transaction
{:from (ethereum/get-default-account (get-in db [:multiaccount :accounts]))
:to (or (get-in db [:contacts/contacts identity])
(-> identity
contact.db/public-key->new-contact
contact.db/enrich-contact))
:symbol :ETH
:from-chat? true})}))
(fx/defn prepare-transaction-from-wallet
{:events [:wallet/prepare-transaction-from-wallet]}
[{:keys [db]} account]
{:db (assoc db :wallet/prepare-transaction
{:from account
:to nil
:symbol :ETH
:from-chat? false})})
(fx/defn qr-scanner-allowed
{:events [:wallet.send/qr-scanner-allowed]}
[{:keys [db] :as cofx} options]
(fx/merge cofx
{:db (assoc-in db [:wallet/prepare-transaction :modal-opened?] true)}
(bottom-sheet/hide-bottom-sheet)
(navigation/navigate-to-cofx :qr-scanner options)))
(fx/defn wallet-send-set-symbol
{:events [:wallet.send/set-symbol]}
[{:keys [db] :as cofx} symbol]
(fx/merge cofx
{:db (assoc-in db [:wallet/prepare-transaction :symbol] symbol)}
(bottom-sheet/hide-bottom-sheet)))
(fx/defn wallet-send-set-field
{:events [:wallet.send/set-field]}
[{:keys [db] :as cofx} field value]
(fx/merge cofx
{:db (assoc-in db [:wallet/prepare-transaction field] value)}
(bottom-sheet/hide-bottom-sheet)))
(fx/defn navigate-to-recipient-code
{:events [:wallet.send/navigate-to-recipient-code]}
[{:keys [db] :as cofx}]
(fx/merge cofx
{:db (assoc-in db [:wallet/prepare-transaction :modal-opened?] true)}
(bottom-sheet/hide-bottom-sheet)
(navigation/navigate-to-cofx :contact-code nil)))

View File

@ -1,8 +1,6 @@
(ns status-im.wallet.db
(:require [cljs.spec.alpha :as spec]
[status-im.i18n :as i18n]
status-im.ui.screens.wallet.request.db
status-im.ui.screens.wallet.send.db
[status-im.utils.money :as money]
[status-im.utils.priority-map :refer [empty-transaction-map]]))
@ -25,9 +23,7 @@
(spec/def :wallet/balance any?)
(spec/def :wallet/filters set?)
(spec/def :wallet/wallet (spec/keys :opt-un [:wallet/send-transaction
:wallet/request-transaction
:wallet/transactions-queue
(spec/def :wallet/wallet (spec/keys :opt-un [:wallet/transactions-queue
:wallet/balance-loading?
:wallet/errors
:wallet/transactions

View File

@ -9,9 +9,9 @@
;;NOTE(goranjovic) - we are internally using symbol ETH for native currencies of any ethereum network
;; some sidechains have different names for this native currency, which we handle with `symbol-display` override.
(defn display-symbol [{:keys [symbol-display symbol] :as token}]
(when token
(clojure.core/name (or symbol-display symbol))))
(defn display-symbol [{:keys [symbol-display symbol]}]
(when-let [name (or symbol-display symbol)]
(clojure.core/name name)))
;;NOTE(goranjovic) - in addition to custom symbol display, some sidechain native currencies are listed under a different
;; ticker on exchange networks. We handle that with `symbol-exchange` override.

View File

@ -463,7 +463,7 @@
"finishing-card-setup-steps": "> Loading keys to the card\n> Generating multiaccount",
"fleet": "Fleet",
"fleet-settings": "Fleet settings",
"follow-your-interests": "Follow your interests in one of the many Public Chats.",
"follow-your-interests": "Follow your interests in one of the many Public Chat.",
"free": "↓ Free",
"from": "From",
"gas-limit": "Gas limit",