This commit is contained in:
parent
caba84e439
commit
32bbf4e533
|
@ -1,30 +1,17 @@
|
||||||
|
|
||||||
// Send command/response
|
// Send command/response
|
||||||
|
|
||||||
|
var assetSendParam = {
|
||||||
function amountParameterBox(params, context) {
|
name: "asset",
|
||||||
|
type: status.types.TEXT,
|
||||||
|
suggestions: function (params) {
|
||||||
return {
|
return {
|
||||||
title: I18n.t('send_title'),
|
markup: status.components.chooseAsset("asset", 0)
|
||||||
showBack: true,
|
|
||||||
markup: status.components.view({
|
|
||||||
flex: 1
|
|
||||||
}, [
|
|
||||||
status.components.text({
|
|
||||||
style: {
|
|
||||||
fontSize: 14,
|
|
||||||
color: "rgb(147, 155, 161)",
|
|
||||||
paddingTop: 12,
|
|
||||||
paddingLeft: 16,
|
|
||||||
paddingRight: 16,
|
|
||||||
paddingBottom: 20
|
|
||||||
}
|
|
||||||
},
|
|
||||||
I18n.t('send_specify_amount')
|
|
||||||
)
|
|
||||||
])
|
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
placeholder: I18n.t('currency_placeholder')
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
var recipientSendParam = {
|
var recipientSendParam = {
|
||||||
name: "recipient",
|
name: "recipient",
|
||||||
|
@ -41,15 +28,26 @@ function amountSendParam() {
|
||||||
return {
|
return {
|
||||||
name: "amount",
|
name: "amount",
|
||||||
type: status.types.NUMBER,
|
type: status.types.NUMBER,
|
||||||
suggestions: amountParameterBox.bind(this)
|
placeholder: I18n.t('amount_placeholder')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var paramsPersonalSend = [amountSendParam()];
|
var paramsPersonalSend = [assetSendParam, amountSendParam()];
|
||||||
var paramsGroupSend = [recipientSendParam, amountSendParam()];
|
var paramsGroupSend = [recipientSendParam, amountSendParam()];
|
||||||
|
|
||||||
function validateSend(validateRecipient, params, context) {
|
function validateSend(validateRecipient, params, context) {
|
||||||
|
|
||||||
|
var allowedAssets = context["allowed-assets"];
|
||||||
|
var asset = params["asset"];
|
||||||
|
|
||||||
|
if(!allowedAssets.hasOwnProperty(asset)){
|
||||||
|
return {
|
||||||
|
markup: status.components.validationMessage(
|
||||||
|
"Invalid asset",
|
||||||
|
"Unknown token - " + asset
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (!params["amount"]) {
|
if (!params["amount"]) {
|
||||||
return {
|
return {
|
||||||
|
@ -62,11 +60,12 @@ function validateSend(validateRecipient, params, context) {
|
||||||
|
|
||||||
var amount = params["amount"].replace(",", ".");
|
var amount = params["amount"].replace(",", ".");
|
||||||
var amountSplitted = amount.split(".");
|
var amountSplitted = amount.split(".");
|
||||||
if (amountSplitted.length === 2 && amountSplitted[1].length > 18) {
|
var decimals = allowedAssets[asset];
|
||||||
|
if (amountSplitted.length === 2 && amountSplitted[1].length > decimals) {
|
||||||
return {
|
return {
|
||||||
markup: status.components.validationMessage(
|
markup: status.components.validationMessage(
|
||||||
I18n.t('validation_title'),
|
I18n.t('validation_title'),
|
||||||
I18n.t('validation_amount_is_too_small')
|
I18n.t('validation_amount_is_too_small') + decimals
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -135,6 +134,17 @@ status.response(groupSend);
|
||||||
|
|
||||||
// Request command
|
// Request command
|
||||||
|
|
||||||
|
var assetRequestParam = {
|
||||||
|
name: "asset",
|
||||||
|
type: status.types.TEXT,
|
||||||
|
suggestions: function (params) {
|
||||||
|
return {
|
||||||
|
markup: status.components.chooseAsset("asset", 0)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
placeholder: I18n.t('currency_placeholder')
|
||||||
|
};
|
||||||
|
|
||||||
var recipientRequestParam = {
|
var recipientRequestParam = {
|
||||||
name: "recipient",
|
name: "recipient",
|
||||||
type: status.types.TEXT,
|
type: status.types.TEXT,
|
||||||
|
@ -148,15 +158,17 @@ var recipientRequestParam = {
|
||||||
|
|
||||||
var amountRequestParam = {
|
var amountRequestParam = {
|
||||||
name: "amount",
|
name: "amount",
|
||||||
type: status.types.NUMBER
|
type: status.types.NUMBER,
|
||||||
|
placeholder: I18n.t('amount_placeholder')
|
||||||
};
|
};
|
||||||
|
|
||||||
var paramsPersonalRequest = [amountRequestParam];
|
var paramsPersonalRequest = [assetRequestParam, amountRequestParam];
|
||||||
var paramsGroupRequest = [recipientRequestParam, amountRequestParam];
|
var paramsGroupRequest = [recipientRequestParam, amountRequestParam];
|
||||||
|
|
||||||
function handlePersonalRequest(params, context) {
|
function handlePersonalRequest(params, context) {
|
||||||
var val = params["amount"].replace(",", ".");
|
var val = params["amount"].replace(",", ".");
|
||||||
var network = context["network"];
|
var network = context["network"];
|
||||||
|
var asset = params["asset"];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
event: "request",
|
event: "request",
|
||||||
|
@ -165,8 +177,9 @@ function handlePersonalRequest(params, context) {
|
||||||
params: {
|
params: {
|
||||||
network: network,
|
network: network,
|
||||||
amount: val,
|
amount: val,
|
||||||
|
asset: asset
|
||||||
},
|
},
|
||||||
prefill: [val]
|
prefill: [asset, val]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -194,7 +207,7 @@ function handleGroupRequest(params, context) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateRequest(validateRecipient, params) {
|
function validateRequest(validateRecipient, params, context) {
|
||||||
if (!params["bot-db"]) {
|
if (!params["bot-db"]) {
|
||||||
params["bot-db"] = {};
|
params["bot-db"] = {};
|
||||||
}
|
}
|
||||||
|
@ -210,6 +223,18 @@ function validateRequest(validateRecipient, params) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var allowedAssets = context["allowed-assets"];
|
||||||
|
var asset = params["asset"];
|
||||||
|
|
||||||
|
if(!allowedAssets.hasOwnProperty(asset)){
|
||||||
|
return {
|
||||||
|
markup: status.components.validationMessage(
|
||||||
|
"Invalid asset",
|
||||||
|
"Unknown token - " + asset
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (!params["amount"]) {
|
if (!params["amount"]) {
|
||||||
return {
|
return {
|
||||||
markup: status.components.validationMessage(
|
markup: status.components.validationMessage(
|
||||||
|
@ -221,11 +246,12 @@ function validateRequest(validateRecipient, params) {
|
||||||
|
|
||||||
var amount = params.amount.replace(",", ".");
|
var amount = params.amount.replace(",", ".");
|
||||||
var amountSplitted = amount.split(".");
|
var amountSplitted = amount.split(".");
|
||||||
if (amountSplitted.length === 2 && amountSplitted[1].length > 18) {
|
var decimals = allowedAssets[asset];
|
||||||
|
if (amountSplitted.length === 2 && amountSplitted[1].length > decimals) {
|
||||||
return {
|
return {
|
||||||
markup: status.components.validationMessage(
|
markup: status.components.validationMessage(
|
||||||
I18n.t('validation_title'),
|
I18n.t('validation_title'),
|
||||||
I18n.t('validation_amount_is_too_small')
|
I18n.t('validation_amount_is_too_small') + decimals
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,9 @@ I18n.translations = {
|
||||||
send_specify_amount: 'Specify amount',
|
send_specify_amount: 'Specify amount',
|
||||||
send_sending_to: 'to ',
|
send_sending_to: 'to ',
|
||||||
|
|
||||||
|
currency_placeholder: 'Currency',
|
||||||
|
amount_placeholder: 'Amount',
|
||||||
|
|
||||||
eth: 'ETH',
|
eth: 'ETH',
|
||||||
|
|
||||||
request_title: 'Request ETH',
|
request_title: 'Request ETH',
|
||||||
|
@ -20,7 +23,7 @@ I18n.translations = {
|
||||||
validation_tx_failed: 'Transaction failed',
|
validation_tx_failed: 'Transaction failed',
|
||||||
validation_amount_specified: 'Amount must be specified',
|
validation_amount_specified: 'Amount must be specified',
|
||||||
validation_invalid_number: 'Amount is not valid number',
|
validation_invalid_number: 'Amount is not valid number',
|
||||||
validation_amount_is_too_small: 'Amount is too precise. Max number of decimals is 18.',
|
validation_amount_is_too_small: 'Max number of decimals is ',
|
||||||
validation_insufficient_amount: 'Insufficient funds for gas * price + value (balance '
|
validation_insufficient_amount: 'Insufficient funds for gas * price + value (balance '
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -215,6 +215,14 @@ function chooseContact(titleText, botDbKey, argumentIndex) {
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function chooseAsset(botDbKey, argumentIndex) {
|
||||||
|
return ['choose-asset', {
|
||||||
|
"bot-db-key": botDbKey,
|
||||||
|
index: argumentIndex
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function separator() {
|
function separator() {
|
||||||
return ['separator'];
|
return ['separator'];
|
||||||
}
|
}
|
||||||
|
@ -276,6 +284,7 @@ var status = {
|
||||||
webView: webView,
|
webView: webView,
|
||||||
validationMessage: validationMessage,
|
validationMessage: validationMessage,
|
||||||
bridgedWebView: bridgedWebView,
|
bridgedWebView: bridgedWebView,
|
||||||
|
chooseAsset: chooseAsset,
|
||||||
chooseContact: chooseContact,
|
chooseContact: chooseContact,
|
||||||
subscribe: subscribe,
|
subscribe: subscribe,
|
||||||
dispatch: dispatch,
|
dispatch: dispatch,
|
||||||
|
|
|
@ -6,24 +6,39 @@
|
||||||
[status-im.utils.handlers :as handlers]
|
[status-im.utils.handlers :as handlers]
|
||||||
[status-im.i18n :as i18n]
|
[status-im.i18n :as i18n]
|
||||||
[status-im.utils.platform :as platform]
|
[status-im.utils.platform :as platform]
|
||||||
[status-im.chat.events.shortcuts :as shortcuts]))
|
[status-im.chat.events.shortcuts :as shortcuts]
|
||||||
|
[status-im.utils.ethereum.tokens :as tokens]))
|
||||||
|
|
||||||
;;;; Helper fns
|
;;;; Helper fns
|
||||||
|
|
||||||
|
;;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 [network account]
|
||||||
|
(let [chain (keyword (ethereum/network-names network))
|
||||||
|
visible-token-symbols (get-in account [:settings :wallet :visible-tokens chain])]
|
||||||
|
(->> (tokens/tokens-for chain)
|
||||||
|
(filter #(not (:nft? %)))
|
||||||
|
(filter #(contains? visible-token-symbols (:symbol %)))
|
||||||
|
(map #(vector (-> % :symbol clojure.core/name)
|
||||||
|
(:decimals %)))
|
||||||
|
(into {"ETH" 18}))))
|
||||||
|
|
||||||
(defn- generate-context
|
(defn- generate-context
|
||||||
"Generates context for jail call"
|
"Generates context for jail call"
|
||||||
[current-account-id chat-id group-chat? to network]
|
[account current-account-id chat-id group-chat? to network]
|
||||||
(merge {:platform platform/os
|
(merge {:platform platform/os
|
||||||
:network (ethereum/network-names network)
|
:network (ethereum/network-names network)
|
||||||
:from current-account-id
|
:from current-account-id
|
||||||
:to to
|
:to to
|
||||||
|
:allowed-assets (clj->js (allowed-assets network account))
|
||||||
:chat {:chat-id chat-id
|
:chat {:chat-id chat-id
|
||||||
:group-chat (boolean group-chat?)}}
|
:group-chat (boolean group-chat?)}}
|
||||||
i18n/delimeters))
|
i18n/delimeters))
|
||||||
|
|
||||||
(defn request-command-message-data
|
(defn request-command-message-data
|
||||||
"Requests command message data from jail"
|
"Requests command message data from jail"
|
||||||
[{:contacts/keys [contacts] :keys [network] :as db}
|
[{:contacts/keys [contacts] :account/keys [account] :keys [network] :as db}
|
||||||
{{:keys [command command-scope-bitmask bot params type]} :content
|
{{:keys [command command-scope-bitmask bot params type]} :content
|
||||||
:keys [chat-id group-id] :as message}
|
:keys [chat-id group-id] :as message}
|
||||||
{:keys [data-type] :as opts}]
|
{:keys [data-type] :as opts}]
|
||||||
|
@ -36,7 +51,7 @@
|
||||||
to (get-in contacts [chat-id :address])
|
to (get-in contacts [chat-id :address])
|
||||||
address (get-in db [:account/account :address])
|
address (get-in db [:account/account :address])
|
||||||
jail-params {:parameters params
|
jail-params {:parameters params
|
||||||
:context (generate-context address chat-id (models.message/group-message? message) to network)}]
|
:context (generate-context account address chat-id (models.message/group-message? message) to network)}]
|
||||||
{:db db
|
{:db db
|
||||||
:call-jail [{:jail-id bot
|
:call-jail [{:jail-id bot
|
||||||
:path path
|
:path path
|
||||||
|
|
|
@ -208,6 +208,7 @@
|
||||||
(not prevent-auto-focus?)
|
(not prevent-auto-focus?)
|
||||||
(merge fx' (chat-input-focus (:db fx') :seq-input-ref)))))))
|
(merge fx' (chat-input-focus (:db fx') :seq-input-ref)))))))
|
||||||
|
|
||||||
|
;; TODO(goranjovic) - generalize setting something as a command argument
|
||||||
(defn set-contact-as-command-argument
|
(defn set-contact-as-command-argument
|
||||||
"Sets contact as command argument for active chat"
|
"Sets contact as command argument for active chat"
|
||||||
[db {:keys [bot-db-key contact arg-index]}]
|
[db {:keys [bot-db-key contact arg-index]}]
|
||||||
|
@ -233,6 +234,31 @@
|
||||||
(min (count input-text)))]
|
(min (count input-text)))]
|
||||||
(merge fx (update-text-selection new-db new-selection)))))))
|
(merge fx (update-text-selection new-db new-selection)))))))
|
||||||
|
|
||||||
|
;; TODO(goranjovic) - generalize setting something as a command argument
|
||||||
|
(defn set-asset-as-command-argument
|
||||||
|
"Sets asset as command argument for active chat"
|
||||||
|
[db {:keys [bot-db-key asset arg-index]}]
|
||||||
|
(let [name (string/replace (name (:symbol asset)) (re-pattern constants/arg-wrapping-char) "")
|
||||||
|
command-owner (get-in (input-model/selected-chat-command db) [:command :owner-id])]
|
||||||
|
(-> db
|
||||||
|
(set-command-argument arg-index name true)
|
||||||
|
(bots-events/set-in-bot-db {:bot command-owner
|
||||||
|
:path [:public (keyword bot-db-key)]
|
||||||
|
:value asset})
|
||||||
|
(as-> fx
|
||||||
|
(let [{:keys [current-chat-id]
|
||||||
|
:as new-db} (:db fx)
|
||||||
|
arg-position (input-model/argument-position new-db)
|
||||||
|
input-text (get-in new-db [:chats current-chat-id :input-text])
|
||||||
|
command-args (cond-> (input-model/split-command-args input-text)
|
||||||
|
(input-model/text-ends-with-space? input-text) (conj ""))
|
||||||
|
new-selection (->> command-args
|
||||||
|
(take (+ 3 arg-position))
|
||||||
|
(input-model/join-command-args)
|
||||||
|
count
|
||||||
|
(min (count input-text)))]
|
||||||
|
(merge fx (update-text-selection new-db new-selection)))))))
|
||||||
|
|
||||||
;; function creating "message shaped" data from command, because that's what `request-command-message-data` expects
|
;; function creating "message shaped" data from command, because that's what `request-command-message-data` expects
|
||||||
(defn- command->message
|
(defn- command->message
|
||||||
[{:keys [bot-db current-chat-id chats]} {:keys [command] :as command-params}]
|
[{:keys [bot-db current-chat-id chats]} {:keys [command] :as command-params}]
|
||||||
|
@ -468,6 +494,12 @@
|
||||||
(fn [{:keys [db]} [params]]
|
(fn [{:keys [db]} [params]]
|
||||||
(set-contact-as-command-argument db params)))
|
(set-contact-as-command-argument db params)))
|
||||||
|
|
||||||
|
(handlers/register-handler-fx
|
||||||
|
:set-asset-as-command-argument
|
||||||
|
[re-frame/trim-v]
|
||||||
|
(fn [{:keys [db]} [params]]
|
||||||
|
(set-asset-as-command-argument db params)))
|
||||||
|
|
||||||
(handlers/register-handler-db
|
(handlers/register-handler-db
|
||||||
:show-suggestions
|
:show-suggestions
|
||||||
(fn [db _]
|
(fn [db _]
|
||||||
|
|
|
@ -2,26 +2,31 @@
|
||||||
(:require [status-im.ui.screens.wallet.send.events :as send.events]
|
(:require [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.wallet.choose-recipient.events :as choose-recipient.events]
|
||||||
[status-im.ui.screens.navigation :as navigation]
|
[status-im.ui.screens.navigation :as navigation]
|
||||||
[status-im.utils.ethereum.core :as ethereum]))
|
[status-im.utils.ethereum.core :as ethereum]
|
||||||
|
[status-im.utils.ethereum.tokens :as tokens]
|
||||||
|
[taoensso.timbre :as log]))
|
||||||
|
|
||||||
;; TODO(goranjovic) - update to include tokens in https://github.com/status-im/status-react/issues/3233
|
;; TODO(goranjovic) - update to include tokens in https://github.com/status-im/status-react/issues/3233
|
||||||
(defn- transaction-details [contact]
|
(defn- transaction-details [contact symbol]
|
||||||
(-> contact
|
(-> contact
|
||||||
(select-keys [:name :address :whisper-identity])
|
(select-keys [:name :address :whisper-identity])
|
||||||
(assoc :symbol :ETH
|
(assoc :symbol symbol
|
||||||
:gas (ethereum/estimate-gas :ETH)
|
:gas (ethereum/estimate-gas symbol)
|
||||||
:from-chat? true)))
|
:from-chat? true)))
|
||||||
|
|
||||||
(defn send-shortcut-fx [{:account/keys [account] :as db} contact params]
|
(defn send-shortcut-fx [{:account/keys [account] :as db} contact params]
|
||||||
|
(let [chain (keyword (ethereum/network-names (:network db)))
|
||||||
|
symbol (-> params :asset keyword)
|
||||||
|
{:keys [decimals]} (tokens/asset-for chain symbol)]
|
||||||
(merge {:db (-> db
|
(merge {:db (-> db
|
||||||
(send.events/set-and-validate-amount-db (:amount params) :ETH 18)
|
(send.events/set-and-validate-amount-db (:amount params) symbol decimals)
|
||||||
(choose-recipient.events/fill-request-details (transaction-details contact))
|
(choose-recipient.events/fill-request-details (transaction-details contact symbol))
|
||||||
(update-in [:wallet :send-transaction] dissoc :id :password :wrong-password?)
|
(update-in [:wallet :send-transaction] dissoc :id :password :wrong-password?)
|
||||||
(navigation/navigate-to
|
(navigation/navigate-to
|
||||||
(if (:wallet-set-up-passed? account)
|
(if (:wallet-set-up-passed? account)
|
||||||
:wallet-send-transaction-chat
|
:wallet-send-transaction-chat
|
||||||
:wallet-onboarding-setup)))}
|
:wallet-onboarding-setup)))}
|
||||||
(send.events/update-gas-price db false)))
|
(send.events/update-gas-price db false))))
|
||||||
|
|
||||||
(def shortcuts
|
(def shortcuts
|
||||||
{"send" send-shortcut-fx})
|
{"send" send-shortcut-fx})
|
||||||
|
|
|
@ -378,7 +378,7 @@
|
||||||
{:params (cond-> params
|
{:params (cond-> params
|
||||||
(= (:name command) constants/command-send)
|
(= (:name command) constants/command-send)
|
||||||
(assoc :network (ethereum/network-names network)
|
(assoc :network (ethereum/network-names network)
|
||||||
:fiat-amount (money/usd-amount (:amount params) prices)
|
:fiat-amount (money/usd-amount (:amount params) (-> params :asset keyword) prices)
|
||||||
:tx-hash tx-hash))})
|
:tx-hash tx-hash))})
|
||||||
content' (assoc content
|
content' (assoc content
|
||||||
:command (:name command)
|
:command (:name command)
|
||||||
|
|
|
@ -75,7 +75,7 @@
|
||||||
:text-align-vertical :center
|
:text-align-vertical :center
|
||||||
:flex 1
|
:flex 1
|
||||||
:android {:top -1}
|
:android {:top -1}
|
||||||
:ios {:line-height min-input-height}})
|
:ios {:line-height 43}})
|
||||||
|
|
||||||
(defnstyle seq-input-text [left container-width]
|
(defnstyle seq-input-text [left container-width]
|
||||||
{:min-width (- container-width left)
|
{:min-width (- container-width left)
|
||||||
|
|
|
@ -288,15 +288,13 @@
|
||||||
(fn [[{:keys [input-text]} command]]
|
(fn [[{:keys [input-text]} command]]
|
||||||
(when (and (string/ends-with? (or input-text "") chat-constants/spacing-char)
|
(when (and (string/ends-with? (or input-text "") chat-constants/spacing-char)
|
||||||
(not (get-in command [:command :sequential-params])))
|
(not (get-in command [:command :sequential-params])))
|
||||||
(let [input (string/trim (or input-text ""))
|
(let [real-args (remove string/blank? (:args command))]
|
||||||
real-args (remove string/blank? (:args command))]
|
|
||||||
(cond
|
(cond
|
||||||
(and command (empty? real-args))
|
(and command (empty? real-args))
|
||||||
(get-in command [:command :params 0 :placeholder])
|
(get-in command [:command :params 0 :placeholder])
|
||||||
|
|
||||||
(and command
|
(and command
|
||||||
(= (count real-args) 1)
|
(= (count real-args) 1))
|
||||||
(input-model/text-ends-with-space? input))
|
|
||||||
(get-in command [:command :params 1 :placeholder]))))))
|
(get-in command [:command :params 1 :placeholder]))))))
|
||||||
|
|
||||||
(reg-sub
|
(reg-sub
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
(ns status-im.chat.views.api.choose-asset
|
||||||
|
(:require-macros [status-im.utils.views :refer [defview letsubs]])
|
||||||
|
(:require [re-frame.core :as re-frame]
|
||||||
|
[status-im.ui.components.list.views :as list]
|
||||||
|
[status-im.ui.components.react :as react]
|
||||||
|
[status-im.utils.money :as money]
|
||||||
|
[status-im.chat.views.api.styles :as styles]))
|
||||||
|
|
||||||
|
(defn clean-asset [asset]
|
||||||
|
(select-keys asset [:name :symbol :decimals :address]))
|
||||||
|
|
||||||
|
(defn- render-asset [arg-index bot-db-key]
|
||||||
|
(fn [{:keys [name symbol amount decimals] :as asset}]
|
||||||
|
[react/touchable-highlight {:on-press #(re-frame/dispatch
|
||||||
|
[:set-asset-as-command-argument {:arg-index arg-index
|
||||||
|
:bot-db-key bot-db-key
|
||||||
|
:asset (clean-asset asset)}])}
|
||||||
|
[react/view styles/asset-container
|
||||||
|
[react/view styles/asset-main
|
||||||
|
[react/image {:source (-> asset :icon :source)
|
||||||
|
:style styles/asset-icon}]
|
||||||
|
[react/text {:style styles/asset-symbol} symbol]
|
||||||
|
[react/text {:style styles/asset-name} name]]
|
||||||
|
[react/text {:style styles/asset-balance}
|
||||||
|
(str (money/internal->formatted amount symbol decimals))]]]))
|
||||||
|
|
||||||
|
(def assets-separator [react/view styles/asset-separator])
|
||||||
|
|
||||||
|
(defview choose-asset-view [{arg-index :index
|
||||||
|
bot-db-key :bot-db-key}]
|
||||||
|
(letsubs [assets [:wallet/visible-assets-with-amount]]
|
||||||
|
[react/view
|
||||||
|
[list/flat-list {:data (filter #(not (:nft? %)) assets)
|
||||||
|
:key-fn (comp name :symbol)
|
||||||
|
:render-fn (render-asset arg-index bot-db-key)
|
||||||
|
:enableEmptySections true
|
||||||
|
:separator assets-separator
|
||||||
|
:keyboardShouldPersistTaps :always
|
||||||
|
:bounces false}]]))
|
|
@ -0,0 +1,35 @@
|
||||||
|
(ns status-im.chat.views.api.styles
|
||||||
|
(:require [status-im.ui.components.colors :as colors]))
|
||||||
|
|
||||||
|
(def asset-container
|
||||||
|
{:flex-direction :row
|
||||||
|
:align-items :center
|
||||||
|
:justify-content :space-between
|
||||||
|
:padding-vertical 11})
|
||||||
|
|
||||||
|
(def asset-main
|
||||||
|
{:flex 1
|
||||||
|
:flex-direction :row
|
||||||
|
:align-items :center})
|
||||||
|
|
||||||
|
(def asset-icon
|
||||||
|
{:width 30
|
||||||
|
:height 30
|
||||||
|
:margin-left 14
|
||||||
|
:margin-right 12})
|
||||||
|
|
||||||
|
(def asset-symbol
|
||||||
|
{:color colors/black})
|
||||||
|
|
||||||
|
(def asset-name
|
||||||
|
{:color colors/gray
|
||||||
|
:padding-left 4})
|
||||||
|
|
||||||
|
(def asset-balance
|
||||||
|
{:color colors/gray
|
||||||
|
:padding-right 14})
|
||||||
|
|
||||||
|
(def asset-separator
|
||||||
|
{:height 1
|
||||||
|
:background-color colors/gray-light
|
||||||
|
:margin-left 56})
|
|
@ -77,7 +77,7 @@
|
||||||
(defview message-content-command-send
|
(defview message-content-command-send
|
||||||
[{:keys [content timestamp-str outgoing group-chat]}]
|
[{:keys [content timestamp-str outgoing group-chat]}]
|
||||||
(letsubs [network [:network-name]]
|
(letsubs [network [:network-name]]
|
||||||
(let [{{:keys [amount fiat-amount tx-hash] send-network :network} :params} content
|
(let [{{:keys [amount fiat-amount tx-hash asset] send-network :network} :params} content
|
||||||
recipient-name (get-in content [:params :bot-db :public :recipient])
|
recipient-name (get-in content [:params :bot-db :public :recipient])
|
||||||
network-mismatch? (and (seq send-network) (not= network send-network))]
|
network-mismatch? (and (seq send-network) (not= network send-network))]
|
||||||
[react/view style/command-send-message-view
|
[react/view style/command-send-message-view
|
||||||
|
@ -91,7 +91,7 @@
|
||||||
"."]
|
"."]
|
||||||
[react/text {:style (style/command-send-currency-text outgoing)
|
[react/text {:style (style/command-send-currency-text outgoing)
|
||||||
:font :default}
|
:font :default}
|
||||||
(i18n/label :eth)]]]]
|
asset]]]]
|
||||||
(when fiat-amount
|
(when fiat-amount
|
||||||
[react/view style/command-send-fiat-amount
|
[react/view style/command-send-fiat-amount
|
||||||
[react/text {:style style/command-send-fiat-amount-text}
|
[react/text {:style style/command-send-fiat-amount-text}
|
||||||
|
|
|
@ -89,9 +89,9 @@
|
||||||
(merge command {:prefill prefill
|
(merge command {:prefill prefill
|
||||||
:prefill-bot-db (or prefill-bot-db prefillBotDb)})
|
:prefill-bot-db (or prefill-bot-db prefillBotDb)})
|
||||||
command)
|
command)
|
||||||
{:keys [amount] request-network :network} params
|
{:keys [amount asset] request-network :network} params
|
||||||
recipient-name (get-in params [:bot-db :public :recipient])
|
recipient-name (get-in params [:bot-db :public :recipient])
|
||||||
usd-amount (money/usd-amount amount prices)
|
usd-amount (money/usd-amount amount (keyword asset) prices)
|
||||||
network-mismatch? (and request-network (not= request-network network))
|
network-mismatch? (and request-network (not= request-network network))
|
||||||
on-press-handler (cond
|
on-press-handler (cond
|
||||||
network-mismatch? nil
|
network-mismatch? nil
|
||||||
|
@ -117,7 +117,7 @@
|
||||||
"."]
|
"."]
|
||||||
[text {:style (st/command-request-currency-text outgoing)
|
[text {:style (st/command-request-currency-text outgoing)
|
||||||
:font :default}
|
:font :default}
|
||||||
(i18n/label :eth)]]]
|
asset]]]
|
||||||
[view st/command-request-fiat-amount-row
|
[view st/command-request-fiat-amount-row
|
||||||
[text {:style st/command-request-fiat-amount-text}
|
[text {:style st/command-request-fiat-amount-text}
|
||||||
(str "~ " usd-amount " " (i18n/label :usd-currency))]]
|
(str "~ " usd-amount " " (i18n/label :usd-currency))]]
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
[status-im.ui.components.react :as components]
|
[status-im.ui.components.react :as components]
|
||||||
[status-im.chat.views.input.validation-messages :as chat-validation-messages]
|
[status-im.chat.views.input.validation-messages :as chat-validation-messages]
|
||||||
[status-im.chat.views.api.choose-contact :as choose-contact]
|
[status-im.chat.views.api.choose-contact :as choose-contact]
|
||||||
|
[status-im.chat.views.api.choose-asset :as choose-asset]
|
||||||
[status-im.ui.components.qr-code-viewer.views :as qr-code-viewer]
|
[status-im.ui.components.qr-code-viewer.views :as qr-code-viewer]
|
||||||
[status-im.ui.components.chat-preview :as chat-preview]
|
[status-im.ui.components.chat-preview :as chat-preview]
|
||||||
[status-im.utils.handlers :refer [register-handler]]
|
[status-im.utils.handlers :refer [register-handler]]
|
||||||
|
@ -32,6 +33,7 @@
|
||||||
:activity-indicator components/activity-indicator
|
:activity-indicator components/activity-indicator
|
||||||
:validation-message chat-validation-messages/validation-message
|
:validation-message chat-validation-messages/validation-message
|
||||||
:choose-contact choose-contact/choose-contact-view
|
:choose-contact choose-contact/choose-contact-view
|
||||||
|
:choose-asset choose-asset/choose-asset-view
|
||||||
:separator parameter-box-separator})
|
:separator parameter-box-separator})
|
||||||
|
|
||||||
(defn get-element [n]
|
(defn get-element [n]
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
[status-im.ui.components.common.common :as components.common]))
|
[status-im.ui.components.common.common :as components.common]))
|
||||||
|
|
||||||
(defn command-short-preview
|
(defn command-short-preview
|
||||||
[{:keys [command] {:keys [amount]} :params}]
|
[{:keys [command] {:keys [amount asset]} :params}]
|
||||||
[chat-preview/text {}
|
[chat-preview/text {}
|
||||||
(str
|
(str
|
||||||
(i18n/label (if (= command constants/command-request)
|
(i18n/label (if (= command constants/command-request)
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
:command-sending))
|
:command-sending))
|
||||||
(i18n/label-number amount)
|
(i18n/label-number amount)
|
||||||
" "
|
" "
|
||||||
(i18n/label :eth))])
|
asset)])
|
||||||
|
|
||||||
(defn message-content-text [{:keys [content] :as message}]
|
(defn message-content-text [{:keys [content] :as message}]
|
||||||
[react/view styles/last-message-container
|
[react/view styles/last-message-container
|
||||||
|
|
|
@ -19,12 +19,15 @@
|
||||||
{:gas (ethereum/estimate-gas symbol)
|
{:gas (ethereum/estimate-gas symbol)
|
||||||
:symbol symbol}))
|
:symbol symbol}))
|
||||||
|
|
||||||
|
(def transaction-request-default
|
||||||
|
{:symbol :ETH})
|
||||||
|
|
||||||
(defmethod navigation/preload-data! :wallet-request-transaction
|
(defmethod navigation/preload-data! :wallet-request-transaction
|
||||||
[db [event]]
|
[db [event]]
|
||||||
(if (= event :navigate-back)
|
(if (= event :navigate-back)
|
||||||
db
|
db
|
||||||
(-> db
|
(-> db
|
||||||
(update :wallet dissoc :request-transaction)
|
(assoc-in [:wallet :request-transaction] transaction-request-default)
|
||||||
(assoc-in [:wallet :send-transaction] transaction-send-default))))
|
(assoc-in [:wallet :send-transaction] transaction-send-default))))
|
||||||
|
|
||||||
(defmethod navigation/preload-data! :wallet-send-transaction
|
(defmethod navigation/preload-data! :wallet-send-transaction
|
||||||
|
|
|
@ -10,21 +10,21 @@
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
::wallet-send-chat-request
|
::wallet-send-chat-request
|
||||||
[re-frame/trim-v]
|
[re-frame/trim-v]
|
||||||
(fn [{{:contacts/keys [contacts]} :db :as cofx} [amount]]
|
(fn [{{:contacts/keys [contacts]} :db :as cofx} [asset amount]]
|
||||||
(handlers-macro/merge-fx cofx
|
(handlers-macro/merge-fx cofx
|
||||||
{:dispatch [:send-current-message]}
|
{:dispatch [:send-current-message]}
|
||||||
(input-events/select-chat-input-command
|
(input-events/select-chat-input-command
|
||||||
(assoc (get-in contacts chat-const/request-command-ref) :prefill [amount]) nil true))))
|
(assoc (get-in contacts chat-const/request-command-ref) :prefill [asset amount]) nil true))))
|
||||||
|
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
:wallet-send-request
|
:wallet-send-request
|
||||||
[re-frame/trim-v]
|
[re-frame/trim-v]
|
||||||
(fn [_ [whisper-identity amount]]
|
(fn [_ [whisper-identity amount symbol decimals]]
|
||||||
(assert whisper-identity)
|
(assert whisper-identity)
|
||||||
{:dispatch-n [[:navigate-back]
|
{:dispatch-n [[:navigate-back]
|
||||||
[:navigate-to-clean :home]
|
[:navigate-to-clean :home]
|
||||||
[:add-chat-loaded-event whisper-identity
|
[:add-chat-loaded-event whisper-identity
|
||||||
[::wallet-send-chat-request (str (money/wei->ether amount))]]
|
[::wallet-send-chat-request (name symbol) (str (money/internal->formatted amount symbol decimals))]]
|
||||||
[:start-chat whisper-identity]]}))
|
[:start-chat whisper-identity]]}))
|
||||||
|
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
|
@ -34,9 +34,15 @@
|
||||||
|
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
:wallet.request/set-and-validate-amount
|
:wallet.request/set-and-validate-amount
|
||||||
(fn [{:keys [db]} [_ amount]]
|
(fn [{:keys [db]} [_ amount symbol decimals]]
|
||||||
(let [{:keys [value error]} (wallet-db/parse-amount amount :ETH)]
|
(let [{:keys [value error]} (wallet-db/parse-amount amount symbol)]
|
||||||
{:db (-> db
|
{:db (-> db
|
||||||
(assoc-in [:wallet :request-transaction :amount] (money/ether->wei value))
|
(assoc-in [:wallet :request-transaction :amount] (money/formatted->internal value symbol decimals))
|
||||||
(assoc-in [:wallet :request-transaction :amount-text] amount)
|
(assoc-in [:wallet :request-transaction :amount-text] amount)
|
||||||
(assoc-in [:wallet :request-transaction :amount-error] error))})))
|
(assoc-in [:wallet :request-transaction :amount-error] error))})))
|
||||||
|
|
||||||
|
(handlers/register-handler-fx
|
||||||
|
:wallet.request/set-symbol
|
||||||
|
(fn [{:keys [db]} [_ symbol]]
|
||||||
|
{:db (-> db
|
||||||
|
(assoc-in [:wallet :request-transaction :symbol] symbol))}))
|
||||||
|
|
|
@ -19,15 +19,18 @@
|
||||||
[status-im.ui.screens.wallet.components.views :as components]
|
[status-im.ui.screens.wallet.components.views :as components]
|
||||||
[status-im.utils.ethereum.core :as ethereum]
|
[status-im.utils.ethereum.core :as ethereum]
|
||||||
[status-im.utils.ethereum.eip681 :as eip681]
|
[status-im.utils.ethereum.eip681 :as eip681]
|
||||||
[status-im.utils.utils :as utils]))
|
[status-im.utils.utils :as utils]
|
||||||
|
[status-im.utils.ethereum.tokens :as tokens]))
|
||||||
|
|
||||||
;; Request screen
|
;; Request screen
|
||||||
|
|
||||||
(views/defview send-transaction-request []
|
(views/defview send-transaction-request []
|
||||||
;; TODO(jeluard) both send and request flows should be merged
|
;; TODO(jeluard) both send and request flows should be merged
|
||||||
(views/letsubs [{:keys [to to-name whisper-identity]} [:wallet.send/transaction]
|
(views/letsubs [network [:get-current-account-network]
|
||||||
{:keys [amount amount-error amount-text]} [:wallet.request/transaction]
|
{:keys [to to-name whisper-identity]} [:wallet.send/transaction]
|
||||||
|
{:keys [amount amount-error amount-text symbol]} [:wallet.request/transaction]
|
||||||
scroll (atom nil)]
|
scroll (atom nil)]
|
||||||
|
(let [{:keys [decimals] :as token} (tokens/asset-for (ethereum/network->chain-keyword network) symbol)]
|
||||||
[comp/simple-screen {:avoid-keyboard? true}
|
[comp/simple-screen {:avoid-keyboard? true}
|
||||||
[comp/toolbar (i18n/label :t/new-request)]
|
[comp/toolbar (i18n/label :t/new-request)]
|
||||||
[react/view components.styles/flex
|
[react/view components.styles/flex
|
||||||
|
@ -38,24 +41,24 @@
|
||||||
:address to
|
:address to
|
||||||
:name to-name
|
:name to-name
|
||||||
:request? true}]
|
:request? true}]
|
||||||
[components/asset-selector {:disabled? true
|
[components/asset-selector {:disabled? false
|
||||||
:symbol :ETH}]
|
:type :request
|
||||||
|
:symbol symbol}]
|
||||||
[components/amount-selector {:error amount-error
|
[components/amount-selector {:error amount-error
|
||||||
:amount amount
|
:amount amount
|
||||||
:amount-text amount-text
|
:amount-text amount-text
|
||||||
:input-options {:max-length 21
|
:input-options {:max-length 21
|
||||||
:on-focus (fn [] (when @scroll (utils/set-timeout #(.scrollToEnd @scroll) 100)))
|
:on-focus (fn [] (when @scroll (utils/set-timeout #(.scrollToEnd @scroll) 100)))
|
||||||
:on-change-text #(re-frame/dispatch [:wallet.request/set-and-validate-amount %])}}
|
:on-change-text #(re-frame/dispatch [:wallet.request/set-and-validate-amount % symbol decimals])}}
|
||||||
{:decimals 18
|
token]]]
|
||||||
:symbol :ETH}]]]
|
|
||||||
[bottom-buttons/bottom-buttons styles/bottom-buttons
|
[bottom-buttons/bottom-buttons styles/bottom-buttons
|
||||||
nil ;; Force a phantom button to ensure consistency with other transaction screens which define 2 buttons
|
nil ;; Force a phantom button to ensure consistency with other transaction screens which define 2 buttons
|
||||||
[button/button {:disabled? (not (and to amount))
|
[button/button {:disabled? (not (and to amount))
|
||||||
:on-press #(re-frame/dispatch [:wallet-send-request whisper-identity amount])
|
:on-press #(re-frame/dispatch [:wallet-send-request whisper-identity amount symbol decimals])
|
||||||
:text-style {:padding-horizontal 0}
|
:text-style {:padding-horizontal 0}
|
||||||
:accessibility-label :sent-request-button}
|
:accessibility-label :sent-request-button}
|
||||||
(i18n/label :t/send-request)
|
(i18n/label :t/send-request)
|
||||||
[vector-icons/icon :icons/forward {:color :white}]]]]]))
|
[vector-icons/icon :icons/forward {:color :white}]]]]])))
|
||||||
|
|
||||||
;; Main screen
|
;; Main screen
|
||||||
|
|
||||||
|
|
|
@ -7,12 +7,7 @@
|
||||||
(re-frame/reg-sub ::send-transaction
|
(re-frame/reg-sub ::send-transaction
|
||||||
:<- [:wallet]
|
:<- [:wallet]
|
||||||
(fn [wallet]
|
(fn [wallet]
|
||||||
(let [transaction (:send-transaction wallet)]
|
(:send-transaction wallet)))
|
||||||
;NOTE(goranjovic): the transactions started from chat using /send command
|
|
||||||
; are only in ether, so this parameter defaults to ETH
|
|
||||||
(if (:symbol transaction)
|
|
||||||
transaction
|
|
||||||
(assoc transaction :symbol :ETH)))))
|
|
||||||
|
|
||||||
(re-frame/reg-sub :wallet.send/symbol
|
(re-frame/reg-sub :wallet.send/symbol
|
||||||
:<- [::send-transaction]
|
:<- [::send-transaction]
|
||||||
|
|
|
@ -139,10 +139,10 @@
|
||||||
(when (and amount balance)
|
(when (and amount balance)
|
||||||
(.greaterThanOrEqualTo balance amount)))
|
(.greaterThanOrEqualTo balance amount)))
|
||||||
|
|
||||||
(defn usd-amount [amount-str prices]
|
(defn usd-amount [amount-str from prices]
|
||||||
(-> amount-str
|
(-> amount-str
|
||||||
(js/parseFloat)
|
(js/parseFloat)
|
||||||
bignumber
|
bignumber
|
||||||
(crypto->fiat (get-in prices [:ETH :USD :price]))
|
(crypto->fiat (get-in prices [from :USD :price] (bignumber 0)))
|
||||||
(with-precision 2)
|
(with-precision 2)
|
||||||
str))
|
str))
|
||||||
|
|
|
@ -50,6 +50,7 @@ class TestTransaction(SingleDeviceTestCase):
|
||||||
chat_view = home_view.get_chat_with_user(recipient['username']).click()
|
chat_view = home_view.get_chat_with_user(recipient['username']).click()
|
||||||
chat_view.commands_button.click()
|
chat_view.commands_button.click()
|
||||||
chat_view.send_command.click()
|
chat_view.send_command.click()
|
||||||
|
chat_view.eth_asset.click()
|
||||||
chat_view.send_as_keyevent(transaction_amount)
|
chat_view.send_as_keyevent(transaction_amount)
|
||||||
wallet_view = chat_view.get_wallet_view()
|
wallet_view = chat_view.get_wallet_view()
|
||||||
chat_view.send_message_button.click_until_presence_of_element(wallet_view.sign_in_phrase)
|
chat_view.send_message_button.click_until_presence_of_element(wallet_view.sign_in_phrase)
|
||||||
|
@ -253,6 +254,7 @@ class TestTransactions(MultipleDeviceTestCase):
|
||||||
one_to_one_chat_device_2.click_until_presence_of_element(device_2_chat.commands_button)
|
one_to_one_chat_device_2.click_until_presence_of_element(device_2_chat.commands_button)
|
||||||
device_1_chat.commands_button.click_until_presence_of_element(device_1_chat.request_command)
|
device_1_chat.commands_button.click_until_presence_of_element(device_1_chat.request_command)
|
||||||
device_1_chat.request_command.click()
|
device_1_chat.request_command.click()
|
||||||
|
device_1_chat.eth_asset.click()
|
||||||
device_1_chat.send_as_keyevent(amount)
|
device_1_chat.send_as_keyevent(amount)
|
||||||
device_1_chat.send_message_button.click()
|
device_1_chat.send_message_button.click()
|
||||||
device_2_chat.send_eth_to_request(amount, sender['password'], wallet_set_up=True)
|
device_2_chat.send_eth_to_request(amount, sender['password'], wallet_set_up=True)
|
||||||
|
|
|
@ -43,6 +43,12 @@ class RequestCommand(BaseButton):
|
||||||
self.locator = self.Locator.accessibility_id('request-payment-button')
|
self.locator = self.Locator.accessibility_id('request-payment-button')
|
||||||
|
|
||||||
|
|
||||||
|
class EthAsset(BaseButton):
|
||||||
|
def __init__(self, driver):
|
||||||
|
super(EthAsset, self).__init__(driver)
|
||||||
|
self.locator = self.Locator.text_selector('ETH')
|
||||||
|
|
||||||
|
|
||||||
class FaucetCommand(BaseButton):
|
class FaucetCommand(BaseButton):
|
||||||
def __init__(self, driver):
|
def __init__(self, driver):
|
||||||
super(FaucetCommand, self).__init__(driver)
|
super(FaucetCommand, self).__init__(driver)
|
||||||
|
@ -200,6 +206,7 @@ class ChatView(BaseView):
|
||||||
self.commands_button = CommandsButton(self.driver)
|
self.commands_button = CommandsButton(self.driver)
|
||||||
self.send_command = SendCommand(self.driver)
|
self.send_command = SendCommand(self.driver)
|
||||||
self.request_command = RequestCommand(self.driver)
|
self.request_command = RequestCommand(self.driver)
|
||||||
|
self.eth_asset = EthAsset(self.driver)
|
||||||
self.faucet_command = FaucetCommand(self.driver)
|
self.faucet_command = FaucetCommand(self.driver)
|
||||||
self.faucet_send_command = FaucetSendCommand(self.driver)
|
self.faucet_send_command = FaucetSendCommand(self.driver)
|
||||||
|
|
||||||
|
@ -292,6 +299,7 @@ class ChatView(BaseView):
|
||||||
def send_transaction_in_1_1_chat(self, amount, password, wallet_set_up=False):
|
def send_transaction_in_1_1_chat(self, amount, password, wallet_set_up=False):
|
||||||
self.commands_button.click()
|
self.commands_button.click()
|
||||||
self.send_command.click()
|
self.send_command.click()
|
||||||
|
self.eth_asset.click()
|
||||||
self.send_as_keyevent(amount)
|
self.send_as_keyevent(amount)
|
||||||
send_transaction_view = self.get_send_transaction_view()
|
send_transaction_view = self.get_send_transaction_view()
|
||||||
if wallet_set_up:
|
if wallet_set_up:
|
||||||
|
@ -327,6 +335,7 @@ class ChatView(BaseView):
|
||||||
def request_transaction_in_1_1_chat(self, amount):
|
def request_transaction_in_1_1_chat(self, amount):
|
||||||
self.commands_button.click()
|
self.commands_button.click()
|
||||||
self.request_command.click()
|
self.request_command.click()
|
||||||
|
self.eth_asset.click()
|
||||||
self.send_as_keyevent(amount)
|
self.send_as_keyevent(amount)
|
||||||
self.send_message_button.click()
|
self.send_message_button.click()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue