Complete implementation of the `/send` command + tests
This commit is contained in:
parent
6672400041
commit
03598d47c2
|
@ -1,14 +1,21 @@
|
|||
(ns status-im.chat.commands.impl.transactions
|
||||
(:require-macros [status-im.utils.views :refer [defview letsubs]])
|
||||
(:require [re-frame.core :as re-frame]
|
||||
(:require [clojure.string :as string]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.chat.commands.protocol :as protocol]
|
||||
[status-im.chat.commands.impl.transactions.styles :as transactions-styles]
|
||||
[status-im.chat.events.requests :as request-events]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.components.icons.vector-icons :as vector-icons]
|
||||
[status-im.ui.components.colors :as colors]
|
||||
[status-im.ui.components.list.views :as list]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.chat.commands.impl.transactions.styles :as transactions-styles]
|
||||
[status-im.chat.styles.message.message :as message-styles]))
|
||||
[status-im.constants :as constants]
|
||||
[status-im.utils.ethereum.core :as ethereum]
|
||||
[status-im.utils.ethereum.tokens :as tokens]
|
||||
[status-im.ui.screens.wallet.send.events :as send.events]
|
||||
[status-im.ui.screens.wallet.choose-recipient.events :as choose-recipient.events]
|
||||
[status-im.ui.screens.navigation :as navigation]))
|
||||
|
||||
(defn- render-asset [selected-event-creator]
|
||||
(fn [{:keys [name symbol amount decimals] :as asset}]
|
||||
|
@ -38,7 +45,7 @@
|
|||
:keyboardShouldPersistTaps :always
|
||||
:bounces false}]]))
|
||||
|
||||
(defn send-short-preview
|
||||
(defn personal-send-request-short-preview
|
||||
[{:keys [content]}]
|
||||
(let [parameters (:params content)]
|
||||
[react/text {}
|
||||
|
@ -52,12 +59,12 @@
|
|||
tx-exists? [:wallet-transaction-exists? tx-hash]]
|
||||
[react/touchable-highlight {:on-press #(when tx-exists?
|
||||
(re-frame/dispatch [:show-transaction-details tx-hash]))}
|
||||
[react/view message-styles/command-send-status-container
|
||||
[react/view transactions-styles/command-send-status-container
|
||||
[vector-icons/icon (if confirmed? :icons/check :icons/dots)
|
||||
{:color colors/blue
|
||||
:container-style (message-styles/command-send-status-icon outgoing)}]
|
||||
:container-style (transactions-styles/command-send-status-icon outgoing)}]
|
||||
[react/view
|
||||
[react/text {:style message-styles/command-send-status-text}
|
||||
[react/text {:style transactions-styles/command-send-status-text}
|
||||
(i18n/label (cond
|
||||
confirmed? :status-confirmed
|
||||
tx-exists? :status-pending
|
||||
|
@ -69,65 +76,159 @@
|
|||
(let [{{:keys [amount fiat-amount tx-hash asset currency] send-network :network} :params} content
|
||||
recipient-name (get-in content [:params :bot-db :public :recipient])
|
||||
network-mismatch? (and (seq send-network) (not= network send-network))]
|
||||
[react/view message-styles/command-send-message-view
|
||||
[react/view transactions-styles/command-send-message-view
|
||||
[react/view
|
||||
[react/view message-styles/command-send-amount-row
|
||||
[react/view message-styles/command-send-amount
|
||||
[react/text {:style message-styles/command-send-amount-text
|
||||
[react/view transactions-styles/command-send-amount-row
|
||||
[react/view transactions-styles/command-send-amount
|
||||
[react/text {:style transactions-styles/command-send-amount-text
|
||||
:font :medium}
|
||||
amount
|
||||
[react/text {:style (message-styles/command-amount-currency-separator outgoing)}
|
||||
[react/text {:style (transactions-styles/command-amount-currency-separator outgoing)}
|
||||
"."]
|
||||
[react/text {:style (message-styles/command-send-currency-text outgoing)
|
||||
[react/text {:style (transactions-styles/command-send-currency-text outgoing)
|
||||
:font :default}
|
||||
asset]]]]
|
||||
(when fiat-amount
|
||||
[react/view message-styles/command-send-fiat-amount
|
||||
[react/text {:style message-styles/command-send-fiat-amount-text}
|
||||
[react/view transactions-styles/command-send-fiat-amount
|
||||
[react/text {:style transactions-styles/command-send-fiat-amount-text}
|
||||
(str "~ " fiat-amount " " (or currency (i18n/label :usd-currency)))]])
|
||||
(when (and group-chat
|
||||
recipient-name)
|
||||
[react/text {:style message-styles/command-send-recipient-text}
|
||||
[react/text {:style transactions-styles/command-send-recipient-text}
|
||||
(str
|
||||
(i18n/label :send-sending-to)
|
||||
" "
|
||||
recipient-name)])
|
||||
[react/view
|
||||
[react/text {:style (message-styles/command-send-timestamp outgoing)}
|
||||
[react/text {:style (transactions-styles/command-send-timestamp outgoing)}
|
||||
(str (i18n/label :sent-at) " " timestamp-str)]]
|
||||
[send-status tx-hash outgoing]
|
||||
(when network-mismatch?
|
||||
[react/text send-network])]])))
|
||||
|
||||
(def personal-send-request-params
|
||||
[{:id :asset
|
||||
:type :text
|
||||
:placeholder "Currency"
|
||||
;; Suggestion components should be structured in such way that they will just take
|
||||
;; one argument, event-creator fn used to construct event to fire whenever something
|
||||
;; is selected.
|
||||
:suggestions choose-asset}
|
||||
{:id :amount
|
||||
:type :number
|
||||
:placeholder "Amount"}])
|
||||
|
||||
;;TODO(goranjovic): currently we only allow tokens which are enabled in Manage assets here
|
||||
;; because balances are only fetched for them. Revisit this decision with regard to battery/network consequences
|
||||
;; if we were to update all balances.
|
||||
(defn- allowed-assets [{:account/keys [account] :keys [chain]}]
|
||||
(let [chain-keyword (keyword chain)
|
||||
visible-tokens (get-in account [:settings :wallet :visible-tokens chain-keyword])]
|
||||
(into {"ETH" 18}
|
||||
(comp (filter #(and (not (:nft? %))
|
||||
(contains? visible-tokens (:symbol %))))
|
||||
(map (juxt (comp name :symbol) :decimals)))
|
||||
(tokens/tokens-for chain-keyword))))
|
||||
|
||||
(defn- personal-send-request-validation [{:keys [asset amount]} {:keys [db]}]
|
||||
(let [asset-decimals (get (allowed-assets db) asset)]
|
||||
(cond
|
||||
|
||||
(not asset-decimals)
|
||||
{:title "Invalid Asset"
|
||||
:description (str "Unknown token - " asset)}
|
||||
|
||||
(not amount)
|
||||
{:title "Amount"
|
||||
:description "Amount must be specified"}
|
||||
|
||||
:else
|
||||
(let [sanitised-str (string/replace amount #"," ".")
|
||||
portions (string/split sanitised-str ".")
|
||||
decimals (get portions 1)
|
||||
amount (js/parseFloat sanitised-str)]
|
||||
(cond
|
||||
|
||||
(or (js/isNaN amount)
|
||||
(> (count portions) 2))
|
||||
{:title "Amount"
|
||||
:description "Amount is not valid number"}
|
||||
|
||||
(and decimals (> decimals asset-decimals))
|
||||
{:title "Amount"
|
||||
:description (str "Max number of decimals is " asset-decimals)})))))
|
||||
|
||||
;; TODO(goranjovic) - update to include tokens in https://github.com/status-im/status-react/issues/3233
|
||||
(defn- transaction-details [contact symbol]
|
||||
(-> contact
|
||||
(select-keys [:name :address :whisper-identity])
|
||||
(assoc :symbol symbol
|
||||
:gas (ethereum/estimate-gas symbol)
|
||||
:from-chat? true)))
|
||||
|
||||
;; `/send` command
|
||||
|
||||
(deftype PersonalSendCommand []
|
||||
protocol/Command
|
||||
(id [_]
|
||||
:send)
|
||||
"send")
|
||||
(scope [_]
|
||||
#{:personal-chats})
|
||||
(parameters [_]
|
||||
[{:id :asset
|
||||
:type :text
|
||||
:placeholder "Currency"
|
||||
;; Suggestion components should be structured in such way that they will just take
|
||||
;; one argument, event-creator fn used to construct event to fire whenever something
|
||||
;; is selected.
|
||||
:suggestions choose-asset}
|
||||
{:id :amount
|
||||
:type :number
|
||||
:placeholder "Amount"}])
|
||||
(validate [_ _ _]
|
||||
;; There is no validation for the `/send` command, as it's fully delegated to the wallet
|
||||
nil)
|
||||
(yield-control [_ parameters cofx]
|
||||
;; navigate to wallet
|
||||
nil)
|
||||
(on-send [_ message-id parameters cofx]
|
||||
(when-let [tx-hash (get-in cofx [:db :wallet :send-transaction :tx-hash])]
|
||||
{:dispatch [:update-transactions]}))
|
||||
personal-send-request-params)
|
||||
(validate [_ parameters cofx]
|
||||
;; Only superficial/formatting validation, "real validation" will be performed
|
||||
;; by the wallet, where we yield control in the next step
|
||||
(personal-send-request-validation parameters cofx))
|
||||
(yield-control [_ parameters {:keys [db]}]
|
||||
;; Prefill wallet and navigate there
|
||||
(let [recipient-contact (get-in db [:contacts/contacts (:current-chat-id db)])
|
||||
sender-account (:account/account db)
|
||||
chain (keyword (:chain db))
|
||||
symbol (-> parameters :asset keyword)
|
||||
{:keys [decimals]} (tokens/asset-for chain symbol)]
|
||||
(merge {:db (-> db
|
||||
(send.events/set-and-validate-amount-db (:amount parameters) symbol decimals)
|
||||
(choose-recipient.events/fill-request-details
|
||||
(transaction-details recipient-contact symbol))
|
||||
(update-in [:wallet :send-transaction] dissoc :id :password :wrong-password?)
|
||||
(navigation/navigate-to
|
||||
(if (:wallet-set-up-passed? sender-account)
|
||||
:wallet-send-transaction-chat
|
||||
:wallet-onboarding-setup)))}
|
||||
(send.events/update-gas-price db false))))
|
||||
(on-send [_ _ _ _]
|
||||
;; TODO(janherich) - remove this once periodic updates are implemented
|
||||
{:dispatch [:update-transactions]})
|
||||
(on-receive [_ _ _]
|
||||
nil)
|
||||
;; TODOD(janherich) - this just copyies the current logic but still seems super weird,
|
||||
;; remove/reconsider once periodic updates are implemented
|
||||
{:dispatch [:update-transactions]
|
||||
:dispatch-later [{:ms constants/command-send-status-update-interval-ms
|
||||
:dispatch [:update-transactions]}]})
|
||||
(short-preview [_ command-message _]
|
||||
(send-short-preview command-message))
|
||||
(personal-send-request-short-preview command-message))
|
||||
(preview [_ command-message _]
|
||||
(send-preview command-message)))
|
||||
|
||||
;; `/request` command
|
||||
|
||||
(deftype PersonalRequestCommand []
|
||||
protocol/Command
|
||||
(id [_]
|
||||
"request")
|
||||
(scope [_]
|
||||
#{:personal-chats})
|
||||
(parameters [_]
|
||||
personal-send-request-params)
|
||||
(validate [_ parameters cofx]
|
||||
(personal-send-request-validation parameters cofx))
|
||||
(yield-control [_ _ _])
|
||||
(on-send [_ _ _ _])
|
||||
(on-receive [_ command-message cofx]
|
||||
(let [{:keys [chat-id message-id]} command-message]
|
||||
(request-events/add-request chat-id message-id cofx)))
|
||||
(short-preview [_ command-message _]
|
||||
(personal-send-request-short-preview command-message))
|
||||
(preview [_ command-message _]
|
||||
nil))
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
(ns status-im.chat.commands.impl.transactions.styles
|
||||
(:require-macros [status-im.utils.styles :refer [defstyle]])
|
||||
(:require [status-im.ui.components.colors :as colors]))
|
||||
|
||||
(def asset-container
|
||||
|
@ -33,3 +34,75 @@
|
|||
{:height 1
|
||||
:background-color colors/gray-light
|
||||
:margin-left 56})
|
||||
|
||||
(def command-send-status-container
|
||||
{:margin-top 6
|
||||
:flex-direction :row})
|
||||
|
||||
(defn command-send-status-icon [outgoing]
|
||||
{:background-color (if outgoing
|
||||
colors/blue-darker
|
||||
colors/blue-transparent)
|
||||
:width 24
|
||||
:height 24
|
||||
:border-radius 16
|
||||
:padding-top 4
|
||||
:padding-left 4})
|
||||
|
||||
(defstyle command-send-status-text
|
||||
{:color colors/blue
|
||||
:android {:margin-top 3}
|
||||
:ios {:margin-top 4}
|
||||
:margin-left 6
|
||||
:font-size 12})
|
||||
|
||||
(def command-send-message-view
|
||||
{:flex-direction :column
|
||||
:align-items :flex-start})
|
||||
|
||||
(def command-send-amount-row
|
||||
{:flex-direction :row
|
||||
:justify-content :space-between})
|
||||
|
||||
(def command-send-amount
|
||||
{:flex-direction :column
|
||||
:align-items :flex-end
|
||||
:max-width 250})
|
||||
|
||||
(defstyle command-send-amount-text
|
||||
{:font-size 22
|
||||
:color colors/blue
|
||||
:ios {:letter-spacing -0.5}})
|
||||
|
||||
(def command-send-currency
|
||||
{:flex-direction :column
|
||||
:align-items :flex-end})
|
||||
|
||||
(defn command-amount-currency-separator [outgoing]
|
||||
{:opacity 0
|
||||
:color (if outgoing colors/hawkes-blue colors/white)})
|
||||
|
||||
(defn command-send-currency-text [outgoing]
|
||||
{:font-size 22
|
||||
:margin-left 4
|
||||
:letter-spacing 1
|
||||
:color (if outgoing colors/wild-blue-yonder colors/blue-transparent-40)})
|
||||
|
||||
(def command-send-fiat-amount
|
||||
{:flex-direction :column
|
||||
:justify-content :flex-end
|
||||
:margin-top 6})
|
||||
|
||||
(def command-send-fiat-amount-text
|
||||
{:font-size 12
|
||||
:color colors/black})
|
||||
|
||||
(def command-send-recipient-text
|
||||
{:color colors/blue
|
||||
:font-size 14
|
||||
:line-height 18})
|
||||
|
||||
(defn command-send-timestamp [outgoing]
|
||||
{:color (if outgoing colors/wild-blue-yonder colors/gray)
|
||||
:margin-top 6
|
||||
:font-size 12})
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
(validate [this parameters cofx]
|
||||
"Function validating the parameters once command is send. Takes parameters map
|
||||
and `cofx` map as argument, returns either `nil` meaning that no errors were
|
||||
found and command send workflow can proceed, or sequence of errors to display")
|
||||
found and command send workflow can proceed, or one/more errors to display.
|
||||
Each error is represented by the map containing `:title` and `:description` keys.")
|
||||
(yield-control [this parameters cofx]
|
||||
"Optional function, which if implemented, can step out of the normal command
|
||||
workflow (`validate-and-send`) and yield control back to application before sending.
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
(ns status-im.chat.commands.styles.validation
|
||||
(:require [status-im.ui.components.styles :as common]))
|
||||
|
||||
(defn root [bottom]
|
||||
{:flex-direction :column
|
||||
:left 0
|
||||
:right 0
|
||||
:bottom bottom
|
||||
:position :absolute})
|
||||
|
||||
(def message-container
|
||||
{:background-color common/color-red
|
||||
:padding 16})
|
||||
|
||||
(def message-title
|
||||
{:color common/color-white
|
||||
:font-size 12})
|
||||
|
||||
(def message-description
|
||||
{:color common/color-white
|
||||
:font-size 12
|
||||
:opacity 0.9})
|
|
@ -0,0 +1,10 @@
|
|||
(ns status-im.chat.commands.validation
|
||||
(:require [status-im.ui.components.react :as react]
|
||||
[status-im.chat.commands.styles.validation :as styles]))
|
||||
|
||||
(defn validation-message [{:keys [title description]}]
|
||||
[react/view styles/message-container
|
||||
[react/text {:style styles/message-title}
|
||||
title]
|
||||
[react/text {:style styles/message-description}
|
||||
description]])
|
|
@ -0,0 +1,38 @@
|
|||
(ns status-im.test.chat.commands.impl.transactions
|
||||
(:require [cljs.test :refer-macros [deftest is testing]]
|
||||
[status-im.chat.commands.impl.transactions :as transactions]
|
||||
[status-im.chat.commands.protocol :as protocol]))
|
||||
|
||||
(def personal-send-command (transactions/PersonalSendCommand.))
|
||||
|
||||
(def cofx {:db {:account/account {:settings {:wallet {:visible-tokens {:mainnet #{:SNT}}}}
|
||||
:wallet-set-up-passed? true}
|
||||
:chain "mainnet"
|
||||
:current-chat-id "recipient"
|
||||
:contacts/contacts {"recipient" {:name "Recipient"
|
||||
:address "0xAA"
|
||||
:whisper-identity "0xBB"}}}})
|
||||
|
||||
(deftest personal-send-command-test
|
||||
(testing "That correct parameters are defined"
|
||||
(is (= (into #{} (map :id) (protocol/parameters personal-send-command))
|
||||
#{:asset :amount})))
|
||||
(testing "Parameters validation"
|
||||
(is (= (protocol/validate personal-send-command {:asset "TST"} cofx)
|
||||
{:title "Invalid Asset"
|
||||
:description "Unknown token - TST"}))
|
||||
(is (= (protocol/validate personal-send-command {:asset "SNT"} cofx)
|
||||
{:title "Amount"
|
||||
:description "Amount must be specified"}))
|
||||
(is (= (protocol/validate personal-send-command {:asset "SNT" :amount "a"} cofx)
|
||||
{:title "Amount"
|
||||
:description "Amount is not valid number"}))
|
||||
(is (= (protocol/validate personal-send-command {:asset "ETH" :amount "0.54354353454353453453454353453445345545"} cofx)
|
||||
{:title "Amount"
|
||||
:description "Max number of decimals is 18"}))
|
||||
(is (= (protocol/validate personal-send-command {:asset "ETH" :amount "0.01"} cofx)
|
||||
nil)))
|
||||
(testing "Yielding control prefills wallet"
|
||||
(let [fx (protocol/yield-control personal-send-command {:asset "ETH" :amount "0.01"} cofx)]
|
||||
(is (= (get-in fx [:db :wallet :send-transaction :amount-text]) "0.01"))
|
||||
(is (= (get-in fx [:db :wallet :send-transaction :symbol]) :ETH)))))
|
|
@ -27,6 +27,7 @@
|
|||
[status-im.test.chat.views.message]
|
||||
[status-im.test.chat.views.photos]
|
||||
[status-im.test.chat.commands.core]
|
||||
[status-im.test.chat.commands.impl.transactions]
|
||||
[status-im.test.i18n]
|
||||
[status-im.test.protocol.web3.inbox]
|
||||
[status-im.test.utils.utils]
|
||||
|
@ -82,6 +83,7 @@
|
|||
'status-im.test.chat.views.message
|
||||
'status-im.test.chat.views.photos
|
||||
'status-im.test.chat.commands.core
|
||||
'status-im.test.chat.commands.impl.transactions
|
||||
'status-im.test.i18n
|
||||
'status-im.test.transport.core
|
||||
'status-im.test.transport.inbox
|
||||
|
|
Loading…
Reference in New Issue