Full implementation of the `/reauest` command + tests

This commit is contained in:
janherich 2018-07-16 15:08:37 +02:00
parent 2cdac26cda
commit 6a342fd3a0
No known key found for this signature in database
GPG Key ID: C23B473AFBE94D13
3 changed files with 314 additions and 66 deletions

View File

@ -1,22 +1,27 @@
(ns status-im.chat.commands.impl.transactions
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [clojure.string :as string]
[reagent.core :as reagent]
[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.data-store.requests :as requests-store]
[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.ui.components.animation :as animation]
[status-im.i18n :as i18n]
[status-im.constants :as constants]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.tokens :as tokens]
[status-im.utils.datetime :as datetime]
[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]))
;; common `send/request` functionality
(defn- render-asset [selected-event-creator]
(fn [{:keys [name symbol amount decimals] :as asset}]
[react/touchable-highlight
@ -54,58 +59,6 @@
" "
(:asset parameters))]))
(defview send-status [tx-hash outgoing]
(letsubs [confirmed? [:transaction-confirmed? tx-hash]
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 transactions-styles/command-send-status-container
[vector-icons/icon (if confirmed? :icons/check :icons/dots)
{:color colors/blue
:container-style (transactions-styles/command-send-status-icon outgoing)}]
[react/view
[react/text {:style transactions-styles/command-send-status-text}
(i18n/label (cond
confirmed? :status-confirmed
tx-exists? :status-pending
:else :status-tx-not-found))]]]]))
(defview send-preview
[{:keys [content timestamp-str outgoing group-chat]}]
(letsubs [network [:network-name]]
(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 transactions-styles/command-send-message-view
[react/view
[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 (transactions-styles/command-amount-currency-separator outgoing)}
"."]
[react/text {:style (transactions-styles/command-send-currency-text outgoing)
:font :default}
asset]]]]
(when fiat-amount
[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 transactions-styles/command-send-recipient-text}
(str
(i18n/label :send-sending-to)
" "
recipient-name)])
[react/view
[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
@ -158,6 +111,60 @@
{:title "Amount"
:description (str "Max number of decimals is " asset-decimals)})))))
;; `/send` command
(defview send-status [tx-hash outgoing]
(letsubs [confirmed? [:transaction-confirmed? tx-hash]
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 transactions-styles/command-send-status-container
[vector-icons/icon (if confirmed? :icons/check :icons/dots)
{:color colors/blue
:container-style (transactions-styles/command-send-status-icon outgoing)}]
[react/view
[react/text {:style transactions-styles/command-send-status-text}
(i18n/label (cond
confirmed? :status-confirmed
tx-exists? :status-pending
:else :status-tx-not-found))]]]]))
(defview send-preview
[{:keys [content timestamp-str outgoing group-chat]}]
(letsubs [network [:network-name]]
(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 transactions-styles/command-send-message-view
[react/view
[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 (transactions-styles/command-amount-currency-separator outgoing)}
"."]
[react/text {:style (transactions-styles/command-send-currency-text outgoing)
:font :default}
asset]]]]
(when fiat-amount
[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 transactions-styles/command-send-recipient-text}
(str
(i18n/label :send-sending-to)
" "
recipient-name)])
[react/view
[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])]])))
;; TODO(goranjovic) - update to include tokens in https://github.com/status-im/status-react/issues/3233
(defn- transaction-details [contact symbol]
(-> contact
@ -166,8 +173,6 @@
:gas (ethereum/estimate-gas symbol)
:from-chat? true)))
;; `/send` command
(deftype PersonalSendCommand []
protocol/Command
(id [_]
@ -180,15 +185,15 @@
;; 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]}]
(yield-control [_ {:keys [amount asset]} {: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)
symbol (keyword asset)
{:keys [decimals]} (tokens/asset-for chain symbol)]
(merge {:db (-> db
(send.events/set-and-validate-amount-db (:amount parameters) symbol decimals)
(send.events/set-and-validate-amount-db amount symbol decimals)
(choose-recipient.events/fill-request-details
(transaction-details recipient-contact symbol))
(update-in [:wallet :send-transaction] dissoc :id :password :wrong-password?)
@ -213,6 +218,124 @@
;; `/request` command
(def request-message-icon-scale-delay 600)
(defn set-chat-command [message-id command]
(let [metadata {:to-message-id message-id}]
(re-frame/dispatch [:select-chat-input-command command metadata])))
(def min-scale 1)
(def max-scale 1.3)
(defn button-animation [val to-value loop? answered?]
(animation/anim-sequence
[(animation/anim-delay
(if (and @loop? (not @answered?))
request-message-icon-scale-delay
0))
(animation/spring val {:toValue to-value
:useNativeDriver true})]))
(defn request-button-animation-logic
[{:keys [to-value val loop? answered?] :as context}]
(animation/start
(button-animation val to-value loop? answered?)
#(if (and @loop? (not @answered?))
(let [new-value (if (= to-value min-scale) max-scale min-scale)
context' (assoc context :to-value new-value)]
(request-button-animation-logic context'))
(animation/start
(button-animation val min-scale loop? answered?)))))
(defn request-button-label
"The request button label will be in the form of `request-the-command-name`"
[command-name]
(keyword (str "request-" (name command-name))))
(defn request-button [message-id _ on-press-handler]
(let [scale-anim-val (animation/create-value min-scale)
answered? (re-frame/subscribe [:is-request-answered? message-id])
loop? (reagent/atom true)
context {:to-value max-scale
:val scale-anim-val
:answered? answered?
:loop? loop?}]
(reagent/create-class
{:display-name "request-button"
:component-did-mount
(if (or (nil? on-press-handler) @answered?) (fn []) #(request-button-animation-logic context))
:component-will-unmount
#(reset! loop? false)
:reagent-render
(fn [message-id {command-icon :icon :as command} on-press-handler]
(when command
[react/touchable-highlight
{:on-press on-press-handler
:style transactions-styles/command-request-image-touchable
:accessibility-label (request-button-label (:name command))}
[react/animated-view {:style (transactions-styles/command-request-image-view command scale-anim-val)}
(when command-icon
[react/icon command-icon transactions-styles/command-request-image])]]))})))
(defview request-preview
[{:keys [message-id content outgoing timestamp timestamp-str group-chat]} {:keys [db]}]
(letsubs [answered? [:is-request-answered? message-id]
status-initialized? [:get :status-module-initialized?]
network [:network-name]
prices [:prices]]
(let [{:keys [amount asset fiat-amount currency] request-network :network} (:params content)
recipient-name (get-in content [:params :bot-db :public :recipient])
network-mismatch? (and request-network (not= request-network network))
command (get-in db [:id->command ["send" #{:personal-chats}] :type])
on-press-handler (cond
network-mismatch? nil
(and (not answered?)
status-initialized?) #(set-chat-command message-id command))]
[react/view
[react/touchable-highlight
{:on-press on-press-handler}
[react/view (transactions-styles/command-request-message-view outgoing)
[react/view
[react/view
[react/text {:style (transactions-styles/command-request-header-text outgoing)}
(i18n/label :transaction-request)]]
[react/view transactions-styles/command-request-row
[react/text {:style transactions-styles/command-request-amount-text
:font :medium}
amount
[react/text {:style (transactions-styles/command-amount-currency-separator outgoing)}
"."]
[react/text {:style (transactions-styles/command-request-currency-text outgoing)
:font :default}
asset]]]
[react/view transactions-styles/command-request-fiat-amount-row
[react/text {:style transactions-styles/command-request-fiat-amount-text}
(str "~ " fiat-amount " " (or currency (i18n/label :usd-currency)))]]
(when (and group-chat recipient-name)
[react/text {:style transactions-styles/command-request-recipient-text}
(str
(i18n/label :request-requesting-from)
" "
recipient-name)])
(when network-mismatch?
[react/text {:style transactions-styles/command-request-network-text}
(str (i18n/label :on) " " request-network)])
[react/view transactions-styles/command-request-timestamp-row
[react/text {:style (transactions-styles/command-request-timestamp-text outgoing)}
(str
(datetime/timestamp->mini-date timestamp)
" "
(i18n/label :at)
" "
timestamp-str)]]
(when-not outgoing
[react/view
[react/view transactions-styles/command-request-separator-line]
[react/view transactions-styles/command-request-button
[react/text {:style (transactions-styles/command-request-button-text answered?)
:on-press on-press-handler}
(i18n/label (if answered? :command-button-sent :command-button-send))]]])]]]])))
(deftype PersonalRequestCommand []
protocol/Command
(id [_]
@ -225,10 +348,14 @@
(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)))
(on-receive [_ {:keys [message-id chat-id]} {:keys [db]}]
(let [request {:chat-id chat-id
:message-id message-id
:response "send"
:status "open"}]
{:db (assoc-in db [:chats chat-id :requests message-id] request)
:data-store/tx [(requests-store/save-request-tx request)]}))
(short-preview [_ command-message _]
(personal-send-request-short-preview command-message))
(preview [_ command-message _]
nil))
(preview [_ command-message cofx]
(request-preview command-message cofx)))

View File

@ -1,6 +1,7 @@
(ns status-im.chat.commands.impl.transactions.styles
(:require-macros [status-im.utils.styles :refer [defstyle]])
(:require [status-im.ui.components.colors :as colors]))
(:require [status-im.ui.components.colors :as colors]
[status-im.ui.components.styles :as styles]))
(def asset-container
{:flex-direction :row
@ -88,6 +89,15 @@
:letter-spacing 1
:color (if outgoing colors/wild-blue-yonder colors/blue-transparent-40)})
(defn command-request-currency-text [outgoing]
{:font-size 22
:letter-spacing 1
:color (if outgoing colors/wild-blue-yonder colors/gray)})
(defn command-request-timestamp-text [outgoing]
{:font-size 12
:color (if outgoing colors/wild-blue-yonder colors/gray)})
(def command-send-fiat-amount
{:flex-direction :column
:justify-content :flex-end
@ -106,3 +116,77 @@
{:color (if outgoing colors/wild-blue-yonder colors/gray)
:margin-top 6
:font-size 12})
(def command-request-image-touchable
{:position :absolute
:top 0
:right -8
:align-items :center
:justify-content :center
:width 48
:height 48})
(defn command-request-image-view [command scale]
{:width 32
:height 32
:border-radius 16
:background-color (:color command)
:transform [{:scale scale}]})
(def command-request-image
{:position :absolute
:top 9
:left 10
:width 12
:height 13})
(defn command-request-message-view [outgoing]
{:border-radius 14
:padding-vertical 4
:background-color (if outgoing colors/hawkes-blue styles/color-white)})
(defn command-request-header-text [outgoing]
{:font-size 12
:color (if outgoing colors/wild-blue-yonder colors/gray)})
(def command-request-row
{:flex-direction :row
:margin-top 6})
(defstyle command-request-amount-text
{:font-size 22
:ios {:letter-spacing -0.5}
:color colors/black})
(def command-request-separator-line
{:background-color colors/gray-light
:height 1
:border-radius 8
:margin-top 10})
(def command-request-button
{:align-items :center
:padding-top 8})
(defn command-request-button-text [answered?]
{:font-size 15
:color (if answered? colors/gray colors/blue)})
(def command-request-fiat-amount-row
{:margin-top 6})
(def command-request-fiat-amount-text
{:font-size 12
:color colors/black})
(def command-request-recipient-text
{:color colors/blue
:font-size 14
:line-height 18})
(def command-request-network-text
{:color colors/red})
(def command-request-timestamp-row
{:margin-top 6})

View File

@ -3,8 +3,6 @@
[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"
@ -13,6 +11,10 @@
:address "0xAA"
:whisper-identity "0xBB"}}}})
;; testing the `/send` command
(def personal-send-command (transactions/PersonalSendCommand.))
(deftest personal-send-command-test
(testing "That correct parameters are defined"
(is (= (into #{} (map :id) (protocol/parameters personal-send-command))
@ -36,3 +38,38 @@
(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)))))
;; testing the `/request` command
(def personal-request-command (transactions/PersonalRequestCommand.))
(deftest personal-send-command-test
(testing "That correct parameters are defined"
(is (= (into #{} (map :id) (protocol/parameters personal-request-command))
#{:asset :amount})))
(testing "Parameters validation"
(is (= (protocol/validate personal-request-command {:asset "TST"} cofx)
{:title "Invalid Asset"
:description "Unknown token - TST"}))
(is (= (protocol/validate personal-request-command {:asset "SNT"} cofx)
{:title "Amount"
:description "Amount must be specified"}))
(is (= (protocol/validate personal-request-command {:asset "SNT" :amount "a"} cofx)
{:title "Amount"
:description "Amount is not valid number"}))
(is (= (protocol/validate personal-request-command {:asset "ETH" :amount "0.54354353454353453453454353453445345545"} cofx)
{:title "Amount"
:description "Max number of decimals is 18"}))
(is (= (protocol/validate personal-request-command {:asset "ETH" :amount "0.01"} cofx)
nil)))
(testing "On receive adds pending request when `/request` command is received"
(let [fx (protocol/on-receive personal-request-command
{:chat-id "recipient"
:message-id "0xAA"}
cofx)]
(is (= (get-in fx [:db :chats "recipient" :requests "0xAA"])
{:chat-id "recipient"
:message-id "0xAA"
:response "send"
:status "open"})))))