[#8348] [Multi-Account] Account overview + individual wallet UI

Signed-off-by: Andrey Shovkoplyas <motor4ik@gmail.com>
This commit is contained in:
Andrey Shovkoplyas 2019-06-17 11:41:37 +02:00
parent 33840b7b84
commit 26bbac83bc
No known key found for this signature in database
GPG Key ID: EAAB7C8622D860A4
55 changed files with 988 additions and 1396 deletions

View File

@ -299,8 +299,7 @@
(let [props (merge props (let [props (merge props
{:background-color {:background-color
(case current-view (case current-view
(:wallet (:wallet-send-transaction
:wallet-send-transaction
:wallet-request-transaction :wallet-request-transaction
:wallet-send-assets :wallet-send-assets
:wallet-request-assets :wallet-request-assets
@ -308,8 +307,6 @@
:recent-recipients :recent-recipients
:wallet-send-transaction-request :wallet-send-transaction-request
:contact-code :contact-code
:wallet-modal
:wallet-onboarding-setup-modal
:wallet-settings-hook) :wallet-settings-hook)
colors/blue colors/blue
@ -318,11 +315,9 @@
"#2f3031" "#2f3031"
colors/white)}) colors/white)})
bottom-background (when (#{:wallet bottom-background (when (#{:recent-recipients
:recent-recipients
:wallet-send-assets :wallet-send-assets
:wallet-request-assets :wallet-request-assets} current-view)
:wallet-modal} current-view)
[view {:background-color colors/white [view {:background-color colors/white
:position :absolute :position :absolute
:bottom 0 :bottom 0

View File

@ -1,4 +1 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg fill="none" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="m12 22c5.5228 0 10-4.4772 10-10 0-5.52285-4.4772-10-10-10-5.52285 0-10 4.47715-10 10 0 5.5228 4.47715 10 10 10zm7.9381-11c-.3381-2.7113-2.0329-5.00126-4.3837-6.16901.065.13023.1276.26332.1878.39891.6898 1.55201 1.1314 3.56607 1.2345 5.7701zm0 2c-.3381 2.7113-2.0329 5.0013-4.3837 6.169.065-.1302.1276-.2633.1878-.3989.6898-1.552 1.1314-3.5661 1.2345-5.7701zm-4.9638-2c-.1017-1.96653-.4982-3.69454-1.0597-4.95782-.328-.73796-.6926-1.27326-1.047-1.61052-.3492-.33236-.6407-.43166-.8676-.43166s-.5184.0993-.8676.43166c-.3544.33726-.719.87256-1.047 1.61052-.56148 1.26328-.95804 2.99129-1.05973 4.95782zm-5.94863 2h5.94863c-.1017 1.9665-.4982 3.6945-1.0597 4.9578-.328.738-.6926 1.2733-1.047 1.6105-.3492.3324-.6407.4317-.8676.4317s-.5184-.0993-.8676-.4317c-.3544-.3372-.719-.8725-1.047-1.6105-.56148-1.2633-.95804-2.9913-1.05973-4.9578zm-2.00239-2c.10315-2.20403.5447-4.21809 1.23448-5.7701.06026-.13559.12288-.26868.18785-.39891-2.35076 1.16775-4.04562 3.45771-4.38372 6.16901zm-2.96139 2h2.96139c.10315 2.204.5447 4.2181 1.23448 5.7701.06026.1356.12288.2687.18785.3989-2.35076-1.1677-4.04562-3.4577-4.38372-6.169z" fill="#000" fill-rule="evenodd"/></svg>
<path d="M12 13C11.4477 13 11 13.4477 11 14V16C11 16.5523 11.4477 17 12 17C12.5523 17 13 16.5523 13 16V14C13 13.4477 12.5523 13 12 13Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 3C9.79086 3 8 4.79086 8 7V9C6.34315 9 5 10.3431 5 12V18C5 19.6569 6.34315 21 8 21H16C17.6569 21 19 19.6569 19 18V12C19 10.3431 17.6569 9 16 9V7C16 4.79086 14.2091 3 12 3ZM14 7V9H10V7C10 5.89543 10.8954 5 12 5C13.1046 5 14 5.89543 14 7ZM7 12C7 11.4477 7.44772 11 8 11H16C16.5523 11 17 11.4477 17 12V18C17 18.5523 16.5523 19 16 19H8C7.44772 19 7 18.5523 7 18V12Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 684 B

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1 @@
<svg fill="none" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="m12 22c-5.52295 0-10-4.4772-10-10 0-5.52283 4.47705-10 10-10 5.5229 0 10 4.47717 10 10 0 5.5228-4.4771 10-10 10zm0-2c4.4182 0 8-3.5817 8-8 0-4.41833-3.5818-8-8-8-4.41821 0-8 3.58167-8 8 0 4.4183 3.58179 8 8 8zm4-10h-3v6h-2v-6h-3v-2h8z" fill="#000" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 399 B

View File

@ -0,0 +1 @@
<svg fill="none" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="m17.4225 9.99594c.9999.85526 1.7523 1.81106 2.2288 2.50406-.4765.693-1.2289 1.6488-2.2288 2.5041-1.3258 1.1339-2.97 1.9959-4.9225 1.9959-1.9526 0-3.5967-.862-4.9225-1.9959-.99999-.8553-1.75231-1.8111-2.22881-2.5041.4765-.693 1.22882-1.6488 2.22881-2.50406 1.3258-1.1339 2.9699-1.99594 4.9225-1.99594 1.9525 0 3.5967.86204 4.9225 1.99594zm4.3215 2.05736c.1681.2771.1681.6163 0 .8934-.8908 1.4684-4.0855 6.0533-9.244 6.0533-5.15858 0-8.35319-4.5849-9.24406-6.0533-.16808-.2771-.16808-.6163.00001-.8934.89086-1.4684 4.08547-6.0533 9.24405-6.0533 5.1585 0 8.3532 4.5849 9.244 6.0533zm-9.244 2.9467c1.3807 0 2.5-1.1193 2.5-2.5s-1.1193-2.5-2.5-2.5-2.5 1.1193-2.5 2.5 1.1193 2.5 2.5 2.5z" fill="#000" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 845 B

View File

@ -17,7 +17,7 @@
[status-im.ui.components.list.views :as list] [status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.components.svgimage :as svgimage] [status-im.ui.components.svgimage :as svgimage]
[status-im.ui.screens.wallet.utils :as wallet.utils] [status-im.wallet.utils :as wallet.utils]
[status-im.utils.datetime :as datetime] [status-im.utils.datetime :as datetime]
[status-im.utils.money :as money] [status-im.utils.money :as money]
[status-im.utils.platform :as platform] [status-im.utils.platform :as platform]

View File

@ -535,6 +535,7 @@
:custom []}) :custom []})
;; TODO all these should be improved, we don't need to recalculate this each time, it can be done only once
(defn tokens-for (defn tokens-for
"makes sure all addresses are lower-case "makes sure all addresses are lower-case
TODO: token list should be speced and not accept non-lower-cased addresses" TODO: token list should be speced and not accept non-lower-cased addresses"

View File

@ -44,6 +44,9 @@
[status-im.stickers.core :as stickers] [status-im.stickers.core :as stickers]
[status-im.transport.core :as transport] [status-im.transport.core :as transport]
[status-im.transport.message.core :as transport.message] [status-im.transport.message.core :as transport.message]
status-im.wallet.choose-recipient.core
status-im.wallet.collectibles.core
status-im.wallet.accounts.core
[status-im.ui.components.bottom-sheet.core :as bottom-sheet] [status-im.ui.components.bottom-sheet.core :as bottom-sheet]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.screens.add-new.new-chat.db :as new-chat.db] [status-im.ui.screens.add-new.new-chat.db :as new-chat.db]
@ -61,7 +64,9 @@
[status-im.wallet.custom-tokens.core :as custom-tokens] [status-im.wallet.custom-tokens.core :as custom-tokens]
[status-im.wallet.db :as wallet.db] [status-im.wallet.db :as wallet.db]
[status-im.web3.core :as web3] [status-im.web3.core :as web3]
[taoensso.timbre :as log])) [taoensso.timbre :as log]
[status-im.chat.commands.sending :as commands.sending]
[status-im.utils.money :as money]))
;; init module ;; init module
@ -1996,3 +2001,15 @@
:dismiss-keyboard :dismiss-keyboard
(fn [] (fn []
(react/dismiss-keyboard!))) (react/dismiss-keyboard!)))
(handlers/register-handler-fx
:wallet-send-request
(fn [{:keys [db] :as cofx} [_ public-key amount symbol decimals]]
(assert public-key)
(let [request-command (get-in db [:id->command ["request" #{:personal-chats}]])]
(fx/merge cofx
(chat/start-chat public-key nil)
(commands.sending/send public-key
request-command
{:asset (name symbol)
:amount (str (money/internal->formatted amount symbol decimals))})))))

View File

@ -31,7 +31,7 @@
[status-im.ui.screens.mobile-network-settings.utils [status-im.ui.screens.mobile-network-settings.utils
:as :as
mobile-network-utils] mobile-network-utils]
[status-im.ui.screens.wallet.utils :as wallet.utils] [status-im.wallet.utils :as wallet.utils]
[status-im.utils.build :as build] [status-im.utils.build :as build]
[status-im.utils.config :as config] [status-im.utils.config :as config]
[status-im.utils.datetime :as datetime] [status-im.utils.datetime :as datetime]
@ -1022,6 +1022,21 @@
"0")) "0"))
"..."))) "...")))
(re-frame/reg-sub
:wallet/sorted-chain-tokens
:<- [:wallet/all-tokens]
:<- [:ethereum/chain-keyword]
(fn [[all-tokens chain]]
(tokens/sorted-tokens-for all-tokens chain)))
(re-frame/reg-sub
:wallet/grouped-chain-tokens
:<- [:wallet/sorted-chain-tokens]
:<- [:wallet/visible-tokens-symbols]
(fn [[all-tokens visible-tokens]]
(let [vt-set (set visible-tokens)]
(group-by :custom? (map #(assoc % :checked? (boolean (get vt-set (keyword (:symbol %))))) all-tokens)))))
(re-frame/reg-sub (re-frame/reg-sub
:wallet/balance-loading? :wallet/balance-loading?
:<- [:wallet] :<- [:wallet]
@ -1059,6 +1074,30 @@
(fn [[balance visible-assets]] (fn [[balance visible-assets]]
(map #(assoc % :amount (get balance (:symbol %))) visible-assets))) (map #(assoc % :amount (get balance (:symbol %))) visible-assets)))
(defn update-value [balance prices currency]
(fn [{:keys [symbol decimals] :as token}]
(let [price (get-in prices [symbol (-> currency :code keyword) :price])]
(assoc token
:price price
:value (when (and balance price)
(-> (money/internal->formatted (get balance symbol) symbol decimals)
(money/crypto->fiat price)
(money/with-precision 2)
str
(i18n/format-currency (:code currency))))))))
(re-frame/reg-sub
:wallet/visible-assets-with-values
:<- [:wallet/visible-assets-with-amount]
:<- [:prices]
:<- [:wallet/currency]
:<- [:balance]
(fn [[assets prices currency balance]]
(let [{:keys [tokens nfts]} (group-by #(if (:nft? %) :nfts :tokens) assets)
tokens-with-values (map (update-value balance prices currency) tokens)]
{:tokens tokens-with-values
:nfts nfts})))
(re-frame/reg-sub (re-frame/reg-sub
:wallet/transferrable-assets-with-amount :wallet/transferrable-assets-with-amount
:<- [:wallet/visible-assets-with-amount] :<- [:wallet/visible-assets-with-amount]

View File

@ -12,7 +12,7 @@
; theme - optional :default, :wallet ; theme - optional :default, :wallet
(defn list-item [{:keys [title subtitle accessories image image-path type theme on-press error] :or {type :default theme :default}}] (defn list-item [{:keys [title subtitle accessories image image-path type theme on-press error content] :or {type :default theme :default}}]
(let [small? (= :small type)] (let [small? (= :small type)]
[react/touchable-highlight {:on-press on-press :disabled (not on-press)} [react/touchable-highlight {:on-press on-press :disabled (not on-press)}
[react/view {:style (styles/container small?)} [react/view {:style (styles/container small?)}
@ -27,6 +27,7 @@
[react/image {:source (utils.image/source image-path) [react/image {:source (utils.image/source image-path)
:style (styles/photo 40)}]]) :style (styles/photo 40)}]])
;;Title ;;Title
(when title
[react/view {:style {:margin-left 16 :margin-right 16}} [react/view {:style {:margin-left 16 :margin-right 16}}
[react/text {:style (styles/title small? subtitle) [react/text {:style (styles/title small? subtitle)
:number-of-lines 1 :number-of-lines 1
@ -37,10 +38,16 @@
[react/text {:style styles/subtitle [react/text {:style styles/subtitle
:number-of-lines 1 :number-of-lines 1
:ellipsize-mode :tail} :ellipsize-mode :tail}
subtitle])] subtitle])])
;;Accessories ;;Content
(when content
(if (vector? content)
content
[content]))
[react/view {:flex 1}] [react/view {:flex 1}]
;;Accessories
(for [accessory accessories] (for [accessory accessories]
(when-not (nil? accessory)
(with-meta (with-meta
(cond (cond
(= :chevron accessory) (= :chevron accessory)
@ -50,15 +57,15 @@
[react/view [react/view
[icons/icon :main-icons/check {:color colors/gray}]] [icons/icon :main-icons/check {:color colors/gray}]]
:else :else
[react/view {:padding-right 8 :flex-shrink 1} [react/view {:margin-right 8 :flex-shrink 1}
(cond (cond
(string? accessory) (string? accessory)
[react/text {:style styles/accessory-text} [react/text {:style styles/accessory-text :number-of-lines 1}
accessory] accessory]
(vector? accessory) (vector? accessory)
accessory accessory
:else :else
[accessory])]) [accessory])])
{:key accessory})) {:key accessory})))
(when error (when error
[tooltip/tooltip error styles/error])]])) [tooltip/tooltip error styles/error])]]))

View File

@ -53,26 +53,19 @@
:stickers-pack-modal {:type :main} :stickers-pack-modal {:type :main}
:tribute-learn-more {:type :main} :tribute-learn-more {:type :main}
:show-extension-modal {:type :main} :show-extension-modal {:type :main}
:wallet {:type :wallet-tab} :wallet {:type :main}
:wallet-stack {:type :wallet-tab} :wallet-stack {:type :main}
:profile-qr-viewer {:type :modal-white} :profile-qr-viewer {:type :modal-white}
:recipient-qr-code {:type :transparent} :recipient-qr-code {:type :transparent}
:wallet-send-assets {:type :wallet} :wallet-send-assets {:type :wallet}
:wallet-request-assets {:type :wallet} :wallet-request-assets {:type :wallet}
:recent-recipients {:type :wallet} :recent-recipients {:type :wallet}
:contact-code {:type :wallet} :contact-code {:type :wallet}
:wallet-onboarding-setup {:type :wallet}
:wallet-send-transaction-request {:type :wallet} :wallet-send-transaction-request {:type :wallet}
:wallet-request-transaction {:type :wallet-tab} :wallet-request-transaction {:type :wallet-tab}
:wallet-send-transaction-chat {:type :wallet} :wallet-settings-assets {:type :main}
:wallet-send-transaction {:type :wallet-tab} :wallet-account {:type :main}
:sign-message-modal {:type :modal-wallet} :wallet-add-custom-token {:type :main}
:wallet-transaction-fee {:type :modal-wallet}
:wallet-onboarding-setup-modal {:type :modal-wallet}
:wallet-send-transaction-modal {:type :modal-wallet}
:wallet-settings-assets {:type :wallet}
:wallet-add-custom-token {:type :wallet}
:wallet-sign-message-modal {:type :modal-wallet}
:wallet-settings-hook {:type :wallet} :wallet-settings-hook {:type :wallet}
:wallet-transactions-filter {:type :modal-main}} :wallet-transactions-filter {:type :modal-main}}
view-id)) view-id))

View File

@ -9,15 +9,6 @@
status-im.web3.events status-im.web3.events
status-im.ui.screens.add-new.new-chat.navigation status-im.ui.screens.add-new.new-chat.navigation
status-im.ui.screens.profile.events status-im.ui.screens.profile.events
status-im.ui.screens.wallet.collectibles.events
status-im.ui.screens.wallet.send.events
status-im.ui.screens.wallet.request.events
status-im.ui.screens.wallet.choose-recipient.events
status-im.ui.screens.wallet.collectibles.cryptokitties.events
status-im.ui.screens.wallet.collectibles.cryptostrikers.events
status-im.ui.screens.wallet.collectibles.etheremon.events
status-im.ui.screens.wallet.collectibles.superrare.events
status-im.ui.screens.wallet.collectibles.kudos.events
status-im.ui.screens.wallet.navigation status-im.ui.screens.wallet.navigation
status-im.utils.keychain.events status-im.utils.keychain.events
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
@ -31,7 +22,6 @@
[status-im.utils.handlers :as handlers] [status-im.utils.handlers :as handlers]
[status-im.utils.http :as http] [status-im.utils.http :as http]
[status-im.utils.utils :as utils] [status-im.utils.utils :as utils]
[status-im.wallet.core :as wallet]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.biometric-auth.core :as biometric-auth] [status-im.biometric-auth.core :as biometric-auth]
[status-im.constants :as const])) [status-im.constants :as const]))

View File

@ -104,19 +104,13 @@
:value value}])) :value value}]))
(defn- my-profile-settings [{:keys [seed-backed-up? mnemonic]} (defn- my-profile-settings [{:keys [seed-backed-up? mnemonic]}
{:keys [settings] :as account} {:keys [settings]}
currency
logged-in? logged-in?
extensions] extensions]
(let [show-backup-seed? (and (not seed-backed-up?) (not (string/blank? mnemonic))) (let [show-backup-seed? (and (not seed-backed-up?) (not (string/blank? mnemonic)))
extensions-settings (vals (get extensions :settings))] extensions-settings (vals (get extensions :settings))]
[react/view [react/view
[profile.components/settings-title (i18n/label :t/settings)] [profile.components/settings-title (i18n/label :t/settings)]
[profile.components/settings-item {:label-kw :t/main-currency
:value (:code currency)
:action-fn #(re-frame/dispatch [:navigate-to :currency-settings])
:accessibility-label :currency-button}]
[profile.components/settings-item-separator]
(when (and config/hardwallet-enabled? (when (and config/hardwallet-enabled?
platform/android?) platform/android?)
[profile.components/settings-item {:label-kw :t/status-keycard [profile.components/settings-item {:label-kw :t/status-keycard

View File

@ -29,7 +29,6 @@
:edit-network :edit-network
:log-level-settings :log-level-settings
:fleet-settings :fleet-settings
:currency-settings
:mobile-network-settings :mobile-network-settings
:backup-seed :backup-seed
:tribute-to-talk :tribute-to-talk

View File

@ -55,13 +55,13 @@
[status-im.ui.screens.stickers.views :as stickers] [status-im.ui.screens.stickers.views :as stickers]
[status-im.ui.screens.wallet.collectibles.views :as collectibles] [status-im.ui.screens.wallet.collectibles.views :as collectibles]
[status-im.ui.screens.wallet.components.views :as wallet.components] [status-im.ui.screens.wallet.components.views :as wallet.components]
[status-im.ui.screens.wallet.main.views :as wallet.main]
[status-im.ui.screens.wallet.onboarding.views :as wallet.onboarding]
[status-im.ui.screens.wallet.request.views :as request] [status-im.ui.screens.wallet.request.views :as request]
[status-im.ui.screens.wallet.send.views :as send] [status-im.ui.screens.wallet.send.views :as send]
[status-im.ui.screens.wallet.settings.views :as wallet-settings] [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.transactions.views :as wallet-transactions]
[status-im.ui.screens.wallet.custom-tokens.views :as custom-tokens])) [status-im.ui.screens.wallet.custom-tokens.views :as custom-tokens]
[status-im.ui.screens.wallet.accounts.views :as wallet.accounts]
[status-im.ui.screens.wallet.account.views :as wallet.account]))
(def all-screens (def all-screens
{:login login/login {:login login/login
@ -103,10 +103,9 @@
:tribute-learn-more [:modal tr-to-talk/learn-more] :tribute-learn-more [:modal tr-to-talk/learn-more]
:chat-modal [:modal chat/chat-modal] :chat-modal [:modal chat/chat-modal]
:show-extension-modal [:modal extensions.module/show-extension-modal-view] :show-extension-modal [:modal extensions.module/show-extension-modal-view]
:wallet-onboarding-setup-modal [:modal wallet.onboarding/modal] :wallet wallet.accounts/accounts-overview
:wallet wallet.main/wallet :wallet-account wallet.account/account
:collectibles-list collectibles/collectibles-list :collectibles-list collectibles/collectibles-list
:wallet-onboarding-setup wallet.onboarding/screen
:contact-code wallet.components/contact-code :contact-code wallet.components/contact-code
:wallet-send-transaction send/send-transaction :wallet-send-transaction send/send-transaction
:recent-recipients wallet.components/recent-recipients :recent-recipients wallet.components/recent-recipients

View File

@ -3,6 +3,7 @@
(def wallet-stack (def wallet-stack
{:name :wallet-stack {:name :wallet-stack
:screens [:wallet :screens [:wallet
:wallet-account
:collectibles-list :collectibles-list
:wallet-onboarding-setup :wallet-onboarding-setup
:contact-code :contact-code
@ -25,5 +26,6 @@
:extension-screen-holder :extension-screen-holder
:wallet-settings-assets :wallet-settings-assets
:wallet-add-custom-token :wallet-add-custom-token
:wallet-custom-token-details] :wallet-custom-token-details
:currency-settings]
:config {:initialRouteName :wallet}}) :config {:initialRouteName :wallet}})

View File

@ -10,7 +10,7 @@
(views/defview fee-bottom-sheet [fee-display-symbol] (views/defview fee-bottom-sheet [fee-display-symbol]
(views/letsubs [{gas-edit :gas gas-price-edit :gasPrice max-fee :max-fee} [:signing/edit-fee]] (views/letsubs [{gas-edit :gas gas-price-edit :gasPrice max-fee :max-fee} [:signing/edit-fee]]
[react/view [react/view
[react/view {:style {:margin-horizontal 16 :margin-vertical 8}} [react/view {:style {:margin-horizontal 16 :margin-top 8}}
[react/text {:style {:typography :title-bold}} (i18n/label :t/network-fee)] [react/text {:style {:typography :title-bold}} (i18n/label :t/network-fee)]
[react/view {:style {:flex-direction :row :margin-top 8}} [react/view {:style {:flex-direction :row :margin-top 8}}
[react/view {:flex 1} [react/view {:flex 1}
@ -35,7 +35,7 @@
:auto-focus false}]] :auto-focus false}]]
[react/view {:margin-top 58 :margin-left 10} [react/view {:margin-top 58 :margin-left 10}
[react/text (i18n/label :t/gwei)]]] [react/text (i18n/label :t/gwei)]]]
[react/view {:margin-vertical 28 :align-items :center} [react/view {:margin-vertical 16 :align-items :center}
[react/text {:style {:color colors/gray}} (i18n/label :t/wallet-transaction-total-fee)] [react/text {:style {:color colors/gray}} (i18n/label :t/wallet-transaction-total-fee)]
[react/view {:height 8}] [react/view {:height 8}]
[react/nested-text {:style {:font-size 17}} [react/nested-text {:style {:font-size 17}}

View File

@ -7,7 +7,7 @@
[reagent.core :as reagent] [reagent.core :as reagent]
[status-im.ui.components.list-item.views :as list-item] [status-im.ui.components.list-item.views :as list-item]
[status-im.ui.components.button.view :as button] [status-im.ui.components.button.view :as button]
[status-im.ui.screens.wallet.utils :as wallet.utils] [status-im.wallet.utils :as wallet.utils]
[status-im.ui.components.list.views :as list] [status-im.ui.components.list.views :as list]
[status-im.ui.components.chat-icon.screen :as chat-icon] [status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.ui.components.icons.vector-icons :as icons] [status-im.ui.components.icons.vector-icons :as icons]

View File

@ -14,7 +14,12 @@
[status-im.ui.screens.mobile-network-settings.view :as mobile-network-settings] [status-im.ui.screens.mobile-network-settings.view :as mobile-network-settings]
[status-im.ui.screens.home.sheet.views :as home.sheet] [status-im.ui.screens.home.sheet.views :as home.sheet]
[status-im.ui.screens.routing.core :as routing] [status-im.ui.screens.routing.core :as routing]
[status-im.ui.screens.signing.views :as signing])) [status-im.ui.screens.signing.views :as signing]
status-im.ui.screens.wallet.collectibles.etheremon.views
status-im.ui.screens.wallet.collectibles.cryptostrikers.views
status-im.ui.screens.wallet.collectibles.cryptokitties.views
status-im.ui.screens.wallet.collectibles.superrare.views
status-im.ui.screens.wallet.collectibles.kudos.views))
(defonce rand-label (when js/goog.DEBUG (rand/id))) (defonce rand-label (when js/goog.DEBUG (rand/id)))

View File

@ -0,0 +1,22 @@
(ns status-im.ui.screens.wallet.account.styles
(:require [status-im.ui.components.colors :as colors]))
(defn card [window-width]
{:width (- window-width 64) :height 161
:background-color colors/blue
:shadow-offset {:width 0 :height 2}
:shadow-radius 8
:shadow-opacity 1
:shadow-color "rgba(0, 9, 26, 0.12)"
:elevation 2
:border-radius 8
:justify-content :space-between})
(def divider
{:height 52
:width 1
:background-color (colors/alpha colors/black 0.2)
:shadow-offset {:width 0 :height 2}
:shadow-radius 8
:shadow-opacity 1
:shadow-color "rgba(0, 9, 26, 0.12)"})

View File

@ -0,0 +1,112 @@
(ns status-im.ui.screens.wallet.account.views
(:require-macros [status-im.utils.views :as views])
(:require [status-im.ui.components.react :as react]
[status-im.ui.components.status-bar.view :as status-bar]
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.components.colors :as colors]
[re-frame.core :as re-frame]
[status-im.ui.screens.wallet.accounts.views :as accounts]
[status-im.ui.screens.wallet.accounts.sheets :as sheets]
[reagent.core :as reagent]
[status-im.ui.components.bottom-bar.styles :as tabs.styles]
[status-im.ui.components.list.views :as list]
[status-im.i18n :as i18n]
[status-im.ui.components.icons.vector-icons :as icons]
[status-im.ui.screens.wallet.account.styles :as styles]
[status-im.ui.screens.wallet.transactions.views :as history]
[status-im.ethereum.core :as ethereum]))
(def state (reagent/atom {:tab :assets}))
(defn toolbar-view [title]
[react/view
[status-bar/status-bar]
[toolbar/toolbar {:transparent? true}
toolbar/default-nav-back
[toolbar/content-title title]
[toolbar/actions
[{:icon :main-icons/more
:icon-opts {:color :black}
:handler #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content sheets/account-settings
:content-height 130}])}]]]])
(defn button [label icon handler]
[react/touchable-highlight {:on-press handler :style {:flex 1}}
[react/view {:flex 1 :align-items :center :justify-content :center}
[react/view {:flex-direction :row :align-items :center}
[icons/icon icon {:color colors/white}]
[react/text {:style {:margin-left 8 :color colors/white}} label]]]])
(views/defview account-card []
(views/letsubs [currency [:wallet/currency]
portfolio-value [:portfolio-value]
window-width [:dimensions/window-width]
{:keys [address]} [:account/account]]
[react/view {:style (styles/card window-width)}
[react/view {:padding 16 :padding-bottom 12 :flex 1 :justify-content :space-between}
[react/nested-text {:style {:color colors/white-transparent :line-height 38
:font-weight "600" :font-size 32}}
(accounts/total-tilde portfolio-value)
[{:style {:color colors/white}} portfolio-value]
" "
(:code currency)]
[react/text {:number-of-lines 1 :ellipsize-mode :middle
:style {:width (/ window-width 3)
:line-height 22 :font-size 13
:font-family "monospace"
:color (colors/alpha colors/white 0.7)}}
(ethereum/normalized-address address)]]
[react/view {:position :absolute :top 12 :right 12}
[react/touchable-highlight {:on-press #(re-frame/dispatch [:wallet.accounts/share])}
[icons/icon :main-icons/share {:color colors/white}]]]
[react/view {:height 52 :background-color (colors/alpha colors/black 0.2)
: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])]
[react/view {:style styles/divider}]
[button (i18n/label :t/receive) :main-icons/receive #(re-frame/dispatch [:navigate-to :wallet-request-transaction])]]]))
(views/defview transactions []
(views/letsubs [{:keys [transaction-history-sections]}
[:wallet.transactions.history/screen]]
[history/history-list transaction-history-sections]))
(views/defview assets-and-collections []
(views/letsubs [{:keys [tokens nfts]} [:wallet/visible-assets-with-values]
currency [:wallet/currency]]
(let [{:keys [tab]} @state]
[react/view {:flex 1}
[react/view {:flex-direction :row :margin-bottom 8 :padding-horizontal 4}
[accounts/tab-title state :assets (i18n/label :t/wallet-assets) (= tab :assets)]
(when (seq nfts)
[accounts/tab-title state :nft (i18n/label :t/wallet-collectibles) (= tab :nft)])
[accounts/tab-title state :history (i18n/label :t/history) (= tab :history)]]
(cond
(= tab :assets)
[list/flat-list {:data tokens
:default-separator? false
:key-fn :name
:footer [react/view
{:style {:height tabs.styles/tabs-diff
:align-self :stretch}}]
:render-fn (accounts/render-asset (:code currency))}]
(= tab :nft)
[list/flat-list {:data nfts
:default-separator? false
:key-fn :name
:footer [react/view
{:style {:height tabs.styles/tabs-diff
:align-self :stretch}}]
:render-fn accounts/render-collectible}]
(= tab :history)
[transactions])])))
(defn account []
[react/view {:flex 1 :background-color colors/white}
[toolbar-view "Status account"]
[react/scroll-view
[react/view {:padding-left 16}
[react/scroll-view {:horizontal true}
[react/view {:flex-direction :row :padding-top 8 :padding-bottom 12}
[account-card]]]]
[assets-and-collections]]])

View File

@ -0,0 +1,65 @@
(ns status-im.ui.screens.wallet.accounts.sheets
(:require [re-frame.core :as re-frame]
[status-im.ui.components.action-button.action-button :as action-button]
[status-im.ui.components.react :as react]
[status-im.i18n :as i18n]
[status-im.ui.components.colors :as colors]))
(defn hide-sheet-and-dispatch [event]
(re-frame/dispatch [:bottom-sheet/hide-sheet])
(re-frame/dispatch event))
(defn accounts-options [seed-backed-up?]
(fn []
[react/view
[action-button/action-button {:label (i18n/label :t/wallet-manage-assets)
:icon :main-icons/token
:icon-opts {:color :blue}
:on-press #(hide-sheet-and-dispatch [:navigate-to :wallet-settings-assets])}]
[action-button/action-button {:label (i18n/label :t/set-currency)
:icon :main-icons/language
:icon-opts {:color :blue}
:on-press #(hide-sheet-and-dispatch [:navigate-to :currency-settings])}]
[action-button/action-button-disabled {:label (i18n/label :t/view-signing)
:icon :main-icons/info
:icon-opts {:color :blue}}]
(when-not seed-backed-up?
[action-button/action-button {:label (i18n/label :t/wallet-backup-recovery-title)
:icon :main-icons/security
:icon-opts {:color colors/red}
:label-style {:color colors/red}
:cyrcle-color (colors/alpha colors/red 0.1)
:on-press #(hide-sheet-and-dispatch [:navigate-to :backup-seed])}])]))
(defn send-receive []
[react/view
[action-button/action-button {:label (i18n/label :t/wallet-send)
:icon :main-icons/send
:accessibility-label :send-transaction-button
:icon-opts {:color :blue}
:on-press #(hide-sheet-and-dispatch [:navigate-to :wallet-send-transaction])}]
[action-button/action-button {:label (i18n/label :t/receive)
:icon :main-icons/receive
:accessibility-label :receive-transaction-button
:icon-opts {:color :blue}
:on-press #(hide-sheet-and-dispatch [:navigate-to :wallet-request-transaction])}]])
(defn add-account []
[react/view
[action-button/action-button-disabled {:label (i18n/label :t/add-an-account)
:icon :main-icons/add
:icon-opts {:color :blue}
:on-press #(hide-sheet-and-dispatch [:navigate-to :wallet-send-transaction])}]
[action-button/action-button-disabled {:label (i18n/label :t/add-a-watch-account)
:icon :main-icons/watch
:icon-opts {:color :blue}
:on-press #(hide-sheet-and-dispatch [:navigate-to :wallet-request-transaction])}]])
(defn account-settings []
[react/view
[action-button/action-button-disabled {:label (i18n/label :t/account-settings)
:icon :main-icons/info
:icon-opts {:color :blue}}]
[action-button/action-button-disabled {:label (i18n/label :t/export-account)
:icon :main-icons/copy
:icon-opts {:color :blue}}]])

View File

@ -0,0 +1,35 @@
(ns status-im.ui.screens.wallet.accounts.styles
(:require [status-im.ui.components.colors :as colors]))
(def card
{:width 156
:height 145
:background-color colors/blue
:shadow-offset {:width 0 :height 2}
:shadow-radius 8
:shadow-opacity 1
:shadow-color "rgba(0, 9, 26, 0.12)"
:elevation 2
:border-radius 8
:justify-content :space-between
:padding 12
:padding-bottom 6
:margin-top 5
:margin-bottom 5})
(def add-card
{:width 156
:height 145
:margin-left 16
:margin-top 5
:margin-right 5
:margin-bottom 5
:background-color colors/white
:shadow-offset {:width 0 :height 2}
:shadow-radius 8
:shadow-opacity 1
:shadow-color "rgba(0, 9, 26, 0.12)"
:elevation 2
:border-radius 8
:justify-content :center
:align-items :center})

View File

@ -0,0 +1,167 @@
(ns status-im.ui.screens.wallet.accounts.views
(:require-macros [status-im.utils.views :as views])
(:require [status-im.ui.components.react :as react]
[status-im.ui.components.status-bar.view :as status-bar]
[status-im.ui.components.icons.vector-icons :as icons]
[status-im.ui.components.toolbar.styles :as toolbar.styles]
[status-im.ui.components.colors :as colors]
[status-im.i18n :as i18n]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.ui.components.list-item.views :as list-item]
[status-im.wallet.utils :as wallet.utils]
[status-im.ui.components.bottom-bar.styles :as tabs.styles]
[reagent.core :as reagent]
[status-im.utils.money :as money]
[re-frame.core :as re-frame]
[status-im.ui.screens.wallet.accounts.sheets :as sheets]
[status-im.ethereum.core :as ethereum]
[status-im.ui.screens.wallet.accounts.styles :as styles]))
(def state (reagent/atom {:tab :assets}))
(defn total-tilde [value]
(when (and (not= "0" value) (not= "..." value)) "~"))
(views/defview account-card [name]
(views/letsubs [currency [:wallet/currency]
portfolio-value [:portfolio-value]
{:keys [address]} [:account/account]]
[react/touchable-highlight {:on-press #(re-frame/dispatch [:navigate-to :wallet-account])
:on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content sheets/send-receive
:content-height 130}])}
[react/view {:style styles/card}
[react/view {:flex-direction :row :align-items :center :justify-content :space-between}
[react/nested-text {:style {:color colors/white-transparent :font-weight "500"}}
(total-tilde portfolio-value)
[{:style {:color colors/white}} portfolio-value]
" "
(:code currency)]
[react/touchable-highlight {:on-press #(re-frame/dispatch [:wallet.accounts/share])}
[icons/icon :main-icons/share {:color colors/white}]]]
[react/view
[react/text {:style {:color colors/white :font-weight "500" :line-height 22}} name]
[react/text {:number-of-lines 1 :ellipsize-mode :middle
:style {:line-height 22 :font-size 13
:font-family "monospace"
:color (colors/alpha colors/white 0.7)}}
(ethereum/normalized-address address)]]]]))
(defn add-card []
[react/touchable-highlight {:on-press #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content sheets/add-account
:content-height 130}])}
[react/view {:style styles/add-card}
[react/view {:width 40 :height 40 :justify-content :center :border-radius 20
:align-items :center :background-color (colors/alpha colors/blue 0.1) :margin-bottom 8}
[icons/icon :main-icons/add {:color colors/blue}]]
[react/text {:style {:color colors/blue}} (i18n/label :t/add-account)]]])
(defn tab-title [state key label active?]
[react/view {:align-items :center}
[react/touchable-highlight {:on-press #(swap! state assoc :tab key)
:underlay-color colors/gray-lighter
:style {:border-radius 8}}
[react/view {:padding-horizontal 12 :padding-vertical 9}
[react/text {:style {:font-weight "500" :color (if active? colors/black colors/gray) :line-height 22}}
label]]]
(when active?
[react/view {:width 24 :height 3 :border-radius 4 :background-color colors/blue}])])
(defn render-asset [currency]
(fn [{:keys [icon decimals amount color value] :as token}]
[list-item/list-item
{:content [react/view {:style {:margin-horizontal 16 :justify-content :center :flex-shrink 1}}
[react/view {:flex-direction :row}
[react/text {:style {:font-weight "500" :flex-shrink 0.5} :number-of-lines 1 :ellipsize-mode :tail}
(wallet.utils/format-amount amount decimals)]
[react/text {:style {:font-weight "500" :color colors/gray :margin-left 6}
:number-of-lines 1}
(wallet.utils/display-symbol token)]]
(when value
[react/text {:style {:color colors/gray}} (str value " " currency)])]
:image (if icon
[list/item-image icon]
[chat-icon/custom-icon-view-list (:name token) color])}]))
(defn render-collectible [{:keys [name icon amount] :as collectible}]
(let [items-number (money/to-fixed amount)
details? (pos? items-number)]
[react/touchable-highlight
(when details?
{:on-press #(re-frame/dispatch [:show-collectibles-list collectible])})
[list-item/list-item
{:title (wallet.utils/display-symbol collectible)
:subtitle name
:image [list/item-image icon]
:accessories [items-number :chevron]}]]))
(views/defview assets-and-collections []
(views/letsubs [{:keys [tokens nfts]} [:wallet/visible-assets-with-values]
currency [:wallet/currency]]
(let [{:keys [tab]} @state]
[react/view {:flex 1}
[react/view {:flex-direction :row :margin-bottom 8 :margin-horizontal 4}
[tab-title state :assets (i18n/label :t/wallet-assets) (= tab :assets)]
[tab-title state :nft (i18n/label :t/wallet-collectibles) (= tab :nft)]]
(if (= tab :assets)
[list/flat-list {:data tokens
:default-separator? false
:key-fn :name
:footer [react/view
{:style {:height tabs.styles/tabs-diff
:align-self :stretch}}]
:render-fn (render-asset (:code currency))}]
(if (seq nfts)
[list/flat-list {:data nfts
:default-separator? false
:key-fn :name
:footer [react/view
{:style {:height tabs.styles/tabs-diff
:align-self :stretch}}]
:render-fn render-collectible}]
[react/view {:align-items :center :margin-top 32}
[react/text {:style {:color colors/gray}}
(i18n/label :t/no-collectibles)]]))])))
(views/defview total-value []
(views/letsubs [currency [:wallet/currency]
portfolio-value [:portfolio-value]]
[react/view
[react/nested-text {:style {:font-size 32 :color colors/gray :font-weight "600"}}
(total-tilde portfolio-value)
[{:style {:color colors/black}} portfolio-value]
" "
(:code currency)]
[react/text {:style {:color colors/gray}} (i18n/label :t/wallet-total-value)]]))
(views/defview accounts-options []
(views/letsubs [{:keys [seed-backed-up?]} [:account/account]]
[react/view {:flex-direction :row :align-items :center}
[react/view {:flex 1 :padding-left 16}
(when-not seed-backed-up?
[react/view {:flex-direction :row :align-items :center}
[react/view {:width 14 :height 14 :background-color colors/gray :border-radius 7 :align-items :center
:justify-content :center :margin-right 9}
[react/text {:style {:color colors/white :font-size 13 :font-weight "700"}} "!"]]
[react/text {:style {:color colors/gray}} (i18n/label :t/back-up-your-seed-phrase)]])]
[react/touchable-highlight {:on-press #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (sheets/accounts-options seed-backed-up?)
:content-height (if seed-backed-up? 190 250)}])}
[react/view {:height toolbar.styles/toolbar-height :width toolbar.styles/toolbar-height :align-items :center
:justify-content :center}
[icons/icon :main-icons/more]]]]))
(defn accounts-overview []
[react/view {:flex 1}
[status-bar/status-bar]
[react/scroll-view
[accounts-options]
[react/view {:margin-top 8 :padding-horizontal 16}
[total-value]
[react/scroll-view {:horizontal true}
[react/view {:flex-direction :row :padding-top 11 :padding-bottom 12}
[account-card "Status account"]
[add-card]]]]
[assets-and-collections]]])

View File

@ -1,122 +0,0 @@
(ns status-im.ui.screens.wallet.choose-recipient.events
(:require [re-frame.core :as re-frame]
[status-im.constants :as constants]
[status-im.contact.db :as contact.db]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.eip55 :as eip55]
[status-im.ethereum.eip681 :as eip681]
[status-im.ethereum.ens :as ens]
[status-im.i18n :as i18n]
[status-im.utils.handlers :as handlers]
[status-im.utils.money :as money]))
(handlers/register-handler-fx
:wallet/toggle-flashlight
(fn [{:keys [db]}]
(let [flashlight-state (get-in db [:wallet :send-transaction :camera-flashlight])
toggled-state (if (= :on flashlight-state) :off :on)]
{:db (assoc-in db [:wallet :send-transaction :camera-flashlight] toggled-state)})))
(defn- find-address-name [db address]
(:name (contact.db/find-contact-by-address (:contacts/contacts db) address)))
(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))
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))))))))
(defn- extract-details
"First try to parse as EIP681 URI, if not assume this is an address directly.
Returns a map containing at least the `address` and `chain-id` keys"
[s chain-id all-tokens]
(or (let [m (eip681/parse-uri s)]
(merge m (eip681/extract-request-details m all-tokens)))
(when (ethereum/address? s)
{:address s :chain-id chain-id})))
;; 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]
(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]
(i18n/label :t/changed-amount-warning {:old old-amount :new new-amount})))
(defn use-default-eth-gas [fx]
(assoc-in fx [:db :wallet :send-transaction :gas]
(ethereum/default-transaction-gas)))
(re-frame/reg-fx
:resolve-address
(fn [{:keys [registry ens-name cb]}]
(ens/get-addr registry ens-name cb)))
(handlers/register-handler-fx
:wallet.send/set-recipient
(fn [{:keys [db]} [_ recipient]]
(let [chain (ethereum/chain-keyword db)]
(if (ens/is-valid-eth-name? recipient)
{:resolve-address {:registry (get ens/ens-registries chain)
:ens-name recipient
:cb #(re-frame/dispatch [:wallet.send/set-recipient %])}}
(if (ethereum/address? recipient)
(let [checksum (eip55/address->checksum recipient)]
(if (eip55/valid-address-checksum? checksum)
{:db (assoc-in db [:wallet :send-transaction :to] checksum)
: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})})))))
(handlers/register-handler-fx
:wallet/fill-request-from-url
(fn [{{:keys [network] :wallet/keys [all-tokens] :as db} :db} [_ data origin]]
(let [current-chain-id (get-in constants/default-networks [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))]
(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)
(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}))))))
(handlers/register-handler-fx
:wallet/fill-request-from-contact
(fn [{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]}))

View File

@ -9,7 +9,6 @@
[status-im.ui.components.status-bar.view :as status-bar] [status-im.ui.components.status-bar.view :as status-bar]
[status-im.ui.components.toolbar.actions :as actions] [status-im.ui.components.toolbar.actions :as actions]
[status-im.ui.components.toolbar.view :as toolbar] [status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.components.colors :as colors]
[status-im.ui.screens.wallet.choose-recipient.styles :as styles] [status-im.ui.screens.wallet.choose-recipient.styles :as styles]
[status-im.utils.platform :as platform])) [status-im.utils.platform :as platform]))

View File

@ -1,31 +0,0 @@
(ns status-im.ui.screens.wallet.collectibles.cryptokitties.events
(:require [status-im.utils.handlers :as handlers]
[status-im.ui.screens.wallet.collectibles.events :as collectibles]
[status-im.utils.http :as http]))
(def ck :CK)
(handlers/register-handler-fx
:load-kitties
(fn [{db :db} [_ ids]]
{:db db
:http-get-n (mapv (fn [id]
{:url (str "https://api.cryptokitties.co/kitties/" id)
:success-event-creator (fn [o]
[:load-collectible-success ck {id (http/parse-payload o)}])
:failure-event-creator (fn [o]
[:load-collectible-failure ck {id (http/parse-payload o)}])})
ids)}))
;; TODO(andrey) Each HTTP call will return up to 100 kitties. Maybe we need to implement some kind of paging later
(defmethod collectibles/load-collectibles-fx ck [_ _ items-number address _]
{:http-get {:url (str "https://api.cryptokitties.co/kitties?offset=0&limit="
items-number
"&owner_wallet_address="
address
"&parents=false")
:success-event-creator (fn [o]
[:load-kitties (map :id (:kitties (http/parse-payload o)))])
:failure-event-creator (fn [o]
[:load-collectibles-failure (http/parse-payload o)])
:timeout-ms 10000}})

View File

@ -1,12 +0,0 @@
(ns status-im.ui.screens.wallet.collectibles.cryptostrikers.events
(:require [status-im.ui.screens.wallet.collectibles.events :as collectibles]
[status-im.utils.http :as http]))
(def strikers :STRK)
(defmethod collectibles/load-collectible-fx strikers [_ _ id]
{:http-get {:url (str "https://us-central1-cryptostrikers-prod.cloudfunctions.net/cards/" id)
:success-event-creator (fn [o]
[:load-collectible-success strikers {id (http/parse-payload o)}])
:failure-event-creator (fn [o]
[:load-collectible-failure strikers {id (http/parse-payload o)}])}})

View File

@ -1,12 +0,0 @@
(ns status-im.ui.screens.wallet.collectibles.etheremon.events
(:require [status-im.ui.screens.wallet.collectibles.events :as collectibles]
[status-im.utils.http :as http]))
(def emona :EMONA)
(defmethod collectibles/load-collectible-fx emona [_ _ id]
{:http-get {:url (str "https://www.etheremon.com/api/monster/get_data?monster_ids=" id)
:success-event-creator (fn [o]
[:load-collectible-success emona (:data (http/parse-payload o))])
:failure-event-creator (fn [o]
[:load-collectible-failure emona {id (http/parse-payload o)}])}})

View File

@ -1,74 +0,0 @@
(ns status-im.ui.screens.wallet.collectibles.events
(:require [re-frame.core :as re-frame]
[status-im.browser.core :as browser]
[status-im.constants :as constants]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.erc721 :as erc721]
[status-im.ethereum.tokens :as tokens]
[status-im.utils.handlers :as handlers]
[status-im.utils.money :as money]))
(defmulti load-collectible-fx (fn [_ symbol _] symbol))
(defmethod load-collectible-fx :default [_ _ _] nil)
(defmulti load-collectibles-fx (fn [_ symbol _ _] symbol))
(defmethod load-collectibles-fx :default [all-tokens symbol items-number address chain-id]
{:load-collectibles-fx [all-tokens symbol items-number address chain-id]})
(handlers/register-handler-fx
:show-collectibles-list
(fn [{:keys [db]} [_ address {:keys [symbol amount] :as collectible}]]
(let [chain-id (get-in constants/default-networks [(:network db) :config :NetworkId])
all-tokens (:wallet/all-tokens db)
items-number (money/to-number amount)
loaded-items-number (count (get-in db [:collectibles symbol]))]
(merge (when (not= items-number loaded-items-number)
(load-collectibles-fx all-tokens symbol items-number address chain-id))
{:dispatch [:navigate-to :collectibles-list collectible]}))))
(defn load-token [i items-number contract address symbol]
(when (< i items-number)
(erc721/token-of-owner-by-index contract address i
(fn [response]
(load-token (inc i) items-number contract address symbol)
(re-frame/dispatch [:load-collectible symbol (.toNumber response)])))))
(re-frame/reg-fx
:load-collectibles-fx
(fn [[all-tokens symbol items-number address chain-id]]
(let [chain (ethereum/chain-id->chain-keyword chain-id)
contract (:address (tokens/symbol->token all-tokens chain symbol))]
(load-token 0 items-number contract address symbol))))
(handlers/register-handler-fx
:load-collectible
(fn [cofx [_ symbol token-id]]
(load-collectible-fx cofx symbol token-id)))
(handlers/register-handler-fx
:store-collectibles
(fn [{db :db} [_ symbol collectibles]]
{:db (update-in db [:collectibles symbol] merge
(reduce #(assoc %1 (:tokenId %2) %2) {} collectibles))}))
(handlers/register-handler-fx
:load-collectible-success
(fn [{db :db} [_ symbol collectibles]]
{:db (update-in db [:collectibles symbol] merge collectibles)}))
(handlers/register-handler-fx
:load-collectibles-failure
(fn [{db :db} [_ reason]]
{:db (update-in db [:collectibles symbol :errors] merge reason)}))
(handlers/register-handler-fx
:load-collectible-failure
(fn [{db :db} [_]]
{:db db}))
(handlers/register-handler-fx
:open-collectible-in-browser
(fn [cofx [_ url]]
(browser/open-url cofx url)))

View File

@ -1,45 +0,0 @@
(ns status-im.ui.screens.wallet.collectibles.kudos.events
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.constants :as constants]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.erc721 :as erc721]
[status-im.ethereum.tokens :as tokens]
[status-im.ui.screens.wallet.collectibles.events :as collectibles]
[status-im.utils.handlers :as handlers]
[status-im.utils.http :as http]))
(def kudos :KDO)
(defmethod collectibles/load-collectible-fx kudos [{db :db} symbol id]
(let [chain-id (get-in constants/default-networks [(:network db) :config :NetworkId])
all-tokens (:wallet/all-tokens db)]
{:erc721-token-uri [all-tokens symbol id chain-id]}))
(re-frame/reg-fx
:erc721-token-uri
(fn [[all-tokens symbol tokenId chain-id]]
(let [chain (ethereum/chain-id->chain-keyword chain-id)
contract (:address (tokens/symbol->token all-tokens chain symbol))]
(erc721/token-uri contract
tokenId
#(re-frame/dispatch [:token-uri-success
tokenId
(when %
(subs % (.indexOf % "http")))]))))) ;; extra chars in rinkeby
(handlers/register-handler-fx
:token-uri-success
(fn [_ [_ tokenId token-uri]]
{:http-get {:url
token-uri
:success-event-creator
(fn [o]
[:load-collectible-success kudos {tokenId (update (http/parse-payload o)
:image
string/replace
#"http:"
"https:")}]) ;; http in mainnet
:failure-event-creator
(fn [o]
[:load-collectible-failure kudos {tokenId (http/parse-payload o)}])}}))

View File

@ -1,41 +0,0 @@
(ns status-im.ui.screens.wallet.collectibles.superrare.events
(:require [status-im.ethereum.core :as ethereum]
[status-im.ui.screens.wallet.collectibles.events :as collectibles]
[status-im.utils.http :as http]
[status-im.utils.types :as types]))
(def superrare :SUPR)
(defmethod collectibles/load-collectible-fx superrare [_ _ ids]
{:http-get-n (mapv (fn [id]
{:url id
:success-event-creator (fn [o]
[:load-collectible-success superrare {id (http/parse-payload o)}])
:failure-event-creator (fn [o]
[:load-collectible-failure superrare {id (http/parse-payload o)}])})
ids)})
(def graphql-url "https://api.pixura.io/graphql")
(defn graphql-query [address]
(str "{
collectiblesByOwner: allErc721Tokens(condition: {owner: \"" address "\"}) {
collectibles: nodes {
tokenId,
metadata: erc721MetadatumByTokenId {
metadataUri,
description,
name,
imageUri
}}}}"))
(defmethod collectibles/load-collectibles-fx superrare [_ _ _ address _]
{:http-post {:url graphql-url
:data (types/clj->json {:query (graphql-query (ethereum/naked-address address))})
:opts {:headers {"Content-Type" "application/json"}}
:success-event-creator (fn [{:keys [response-body]}]
[:store-collectibles superrare
(get-in (http/parse-payload response-body) [:data :collectiblesByOwner :collectibles])])
:failure-event-creator (fn [{:keys [response-body]}]
[:load-collectibles-failure (http/parse-payload response-body)])
:timeout-ms 10000}})

View File

@ -3,8 +3,6 @@
(:require [status-im.ui.components.colors :as colors] (:require [status-im.ui.components.colors :as colors]
[status-im.ui.components.styles :as styles])) [status-im.ui.components.styles :as styles]))
;; Components
(def cartouche-container (def cartouche-container
{:flex 1 {:flex 1
:margin-top 16 :margin-top 16
@ -31,18 +29,6 @@
:justify-content :space-between :justify-content :space-between
:align-items :center}) :align-items :center})
(def cartouche-text-wrapper
{:flex-direction :row
:justify-content :space-between
:padding-horizontal 15
:padding-vertical 15})
(def cartouche-primary-text
{:color colors/white})
(def cartouche-secondary-text
{:color colors/white-transparent})
(def text-content (def text-content
{:color colors/white}) {:color colors/white})

View File

@ -25,7 +25,7 @@
:as :as
choose-recipient] choose-recipient]
[status-im.ui.screens.wallet.components.styles :as styles] [status-im.ui.screens.wallet.components.styles :as styles]
[status-im.ui.screens.wallet.utils :as wallet.utils] [status-im.wallet.utils :as wallet.utils]
[status-im.utils.core :as utils.core] [status-im.utils.core :as utils.core]
[status-im.utils.money :as money] [status-im.utils.money :as money]
[status-im.utils.utils :as utils.utils]) [status-im.utils.utils :as utils.utils])
@ -90,19 +90,6 @@
[vector-icons/icon icon (merge {:color :white} icon-opts)]] [vector-icons/icon icon (merge {:color :white} icon-opts)]]
content)]]])]) content)]]])])
(defn- cartouche-primary-text [s]
[react/text {:style styles/cartouche-primary-text}
s])
(defn cartouche-secondary-text [s]
[react/text {:style styles/cartouche-secondary-text}
s])
(defn cartouche-text-content [primary secondary]
[react/view styles/cartouche-text-wrapper
[cartouche-primary-text primary]
[cartouche-secondary-text secondary]])
(defn view-asset [symbol] (defn view-asset [symbol]
[react/view [react/view
[react/i18n-text {:style styles/label :key :wallet-asset}] [react/i18n-text {:style styles/label :key :wallet-asset}]

View File

@ -8,7 +8,7 @@
[status-im.ui.components.text-input.view :as text-input] [status-im.ui.components.text-input.view :as text-input]
[status-im.ui.components.common.common :as components.common] [status-im.ui.components.common.common :as components.common]
[clojure.string :as string] [clojure.string :as string]
[status-im.ui.screens.wallet.utils :as wallet.utils] [status-im.wallet.utils :as wallet.utils]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.ui.components.action-button.action-button :as action-button])) [status-im.ui.components.action-button.action-button :as action-button]))

View File

@ -1,123 +0,0 @@
(ns status-im.ui.screens.wallet.main.styles
(:require-macros [status-im.utils.styles :refer [defstyle]])
(:require [status-im.ui.components.colors :as colors]))
;; Main section
(defstyle main-section
{:flex 1})
(defstyle scroll-bottom
{:background-color colors/white
:zIndex -1
:position :absolute
:left 0
:right 0
:android {:height 0}
:ios {:height 9999}})
(def backup-seed-phrase-container
{:flex-direction :row
:background-color colors/black-transparent
:margin 16
:border-radius 8
:padding-top 10
:padding-bottom 10
:padding-horizontal 16})
(def backup-seed-phrase-title
{:color colors/white})
(def backup-seed-phrase-description
{:color colors/white-transparent})
(def total-balance-text
{:text-align :center
:padding-top 49
:padding-bottom 38
:background-color colors/blue
:color colors/white-transparent
:font-size 32})
(def total-balance-value
{:font-weight "700"
:color colors/white})
(def total-balance-currency
{:font-weight "700"})
(def snackbar-container
{:background-color colors/gray})
(def snackbar-text
{:color colors/white
:margin-horizontal 50
:margin-vertical 10
:text-align :center})
;; Actions section
(def action
{:background-color colors/white-transparent
:width 40
:height 40
:border-radius 50})
(def action-label
{:color :white})
(def action-separator
{:height 1
:background-color colors/white-light-transparent
:margin-left 70})
;; Assets section
(def asset-section
{:flex 1
:padding-top 5
:padding-bottom 20})
(def asset-section-header
{:color colors/gray
:margin-left 16
:margin-top 7
:margin-bottom 3})
(def asset-item-container
{:flex 1
:flex-direction :row
:align-items :center
:justify-content :space-between})
(def asset-item-value-container
{:flex 1
:flex-direction :row
:align-items :center})
(def asset-item-value
{:flex -1
:font-size 16})
(def asset-item-currency
{:font-size 16
:color colors/gray
:margin-left 6})
(def asset-item-price
{:font-size 16
:color colors/gray
:margin-left 6})
(def wallet-address
{:color colors/white
:text-align :center})
(def address-section
{:flex-grow 1
:align-items :center
:padding 20})
(def modal-history
{:flex 1
:background-color :white})

View File

@ -1,201 +0,0 @@
(ns status-im.ui.screens.wallet.main.views
(:require-macros [status-im.utils.views :as views])
(:require [reagent.core :as reagent]
[re-frame.core :as re-frame]
[status-im.i18n :as i18n]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react]
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.screens.wallet.onboarding.views :as onboarding.views]
[status-im.ui.screens.wallet.styles :as wallet.styles]
[status-im.ui.screens.wallet.main.styles :as styles]
[status-im.ui.screens.wallet.settings.views :as settings]
[status-im.ui.screens.wallet.utils :as wallet.utils]
[status-im.utils.money :as money]
[status-im.utils.platform :as platform]
[status-im.ui.components.toolbar.actions :as action]
status-im.ui.screens.wallet.collectibles.etheremon.views
status-im.ui.screens.wallet.collectibles.cryptostrikers.views
status-im.ui.screens.wallet.collectibles.cryptokitties.views
status-im.ui.screens.wallet.collectibles.superrare.views
status-im.ui.screens.wallet.collectibles.kudos.views
[status-im.ui.components.status-bar.view :as status-bar.view]
[status-im.ui.screens.wallet.transactions.views :as transactions.views]
[status-im.ui.components.chat-icon.screen :as chat-icon]))
(defn toolbar-modal [modal-history?]
[react/view
[status-bar.view/status-bar {:type :modal-wallet}]
[toolbar/toolbar {:transparent? true}
[toolbar/nav-button (action/close-white action/default-handler)]
[toolbar/content-wrapper]
[toolbar/actions
[{:icon (if modal-history? :main-icons/wallet :main-icons/two-arrows)
:icon-opts {:color :white
:accessibility-label (if modal-history? :wallet-modal-button :transaction-history-button)}
:handler #(re-frame/dispatch [:set-in [:wallet :modal-history?] (not modal-history?)])}]]]])
(defn- total-section [value currency]
[react/nested-text {:style styles/total-balance-text}
(when (and
(not= "0" value)
(not= "..." value))
"~")
[{:style styles/total-balance-value
:accessibility-label :total-amount-value-text}
value]
[{:style styles/total-balance-currency
:accessibility-label :total-amount-currency-text}
(str " " (:code currency))]])
(defn- backup-seed-phrase []
[react/touchable-highlight {:on-press #(re-frame/dispatch [:navigate-to :backup-seed])
:style {:background-color colors/blue}
:underlay-color colors/blue}
[react/view {:style styles/backup-seed-phrase-container}
[react/view
[react/i18n-text {:style styles/backup-seed-phrase-title
:key :wallet-backup-recovery-title}]
[react/i18n-text {:style styles/backup-seed-phrase-description
:key :wallet-backup-recovery-description}]]
[react/view {:style {:flex 1}}]
[react/view {:style {:align-items :center
:justify-content :center}}
[vector-icons/icon :main-icons/next {:color colors/white}]]]])
(def actions
[{:label (i18n/label :t/wallet-send)
:accessibility-label :send-transaction-button
:icon :main-icons/send
:action #(re-frame/dispatch [:navigate-to :wallet-send-transaction])}
{:label (i18n/label :t/receive)
:accessibility-label :receive-transaction-button
:icon :main-icons/receive
:action #(re-frame/dispatch [:navigate-to :wallet-request-transaction])}
{:label (i18n/label :t/transaction-history)
:accessibility-label :transaction-history-button
:icon :main-icons/history
:action #(re-frame/dispatch [:navigate-to :transactions-history])}])
(defn- render-asset [currency]
(fn [{:keys [symbol icon decimals amount color] :as token}]
(let [asset-value (re-frame/subscribe [:asset-value symbol decimals (-> currency :code keyword)])]
[react/view {:style styles/asset-item-container}
[list/item
(if icon
[list/item-image icon]
[chat-icon/custom-icon-view-list (:name token) color])
[react/view {:style styles/asset-item-value-container}
[react/text {:style styles/asset-item-value
:number-of-lines 1
:ellipsize-mode :tail
:accessibility-label (str (-> symbol name clojure.string/lower-case) "-asset-value-text")}
(wallet.utils/format-amount amount decimals)]
[react/text {:style styles/asset-item-currency
:number-of-lines 1}
(wallet.utils/display-symbol token)]]
[react/text {:style styles/asset-item-price
:number-of-lines 1}
(or @asset-value "...")]]])))
(def item-icon-forward
[list/item-icon {:icon :main-icons/next
:style {:width 12}
:icon-opts {:color :gray}}])
(defn- render-collectible [address-hex {:keys [symbol name icon amount] :as collectible}]
(let [items-number (money/to-fixed amount)
details? (pos? items-number)]
[react/touchable-highlight
(when details?
{:on-press #(re-frame/dispatch [:show-collectibles-list address-hex collectible])})
[react/view {:style styles/asset-item-container}
[list/item
[list/item-image icon]
[react/view {:style styles/asset-item-value-container}
[react/text {:style styles/asset-item-value
:number-of-lines 1
:ellipsize-mode :tail
:accessibility-label (str (-> symbol clojure.core/name clojure.string/lower-case)
"-collectible-value-text")}
(or items-number "...")]
[react/text {:style styles/asset-item-currency
:number-of-lines 1}
name]]
(when details?
item-icon-forward)]]]))
(defn group-assets [v]
(group-by #(if (:nft? %) :nfts :tokens) v))
(defn- asset-section [assets currency address-hex]
(let [{:keys [tokens nfts]} (group-assets assets)]
[list/section-list
{:scroll-enabled false
:style styles/asset-section
:key-fn (comp str :symbol)
:render-section-header-fn (fn [{:keys [title data]}]
(when (not-empty data)
[react/text {:style styles/asset-section-header}
title]))
:sections [{:title (i18n/label :t/wallet-assets)
:key :assets
:data tokens
:render-fn (render-asset currency)}
{:title (i18n/label :t/wallet-collectibles)
:key :collectibles
:data nfts
:render-fn #(render-collectible address-hex %)}]}]))
(defn snackbar [error-message]
[react/view styles/snackbar-container
[react/text {:style styles/snackbar-text}
(i18n/label error-message)]])
(views/defview wallet-root [modal?]
(views/letsubs [assets [:wallet/visible-assets-with-amount]
currency [:wallet/currency]
portfolio-value [:portfolio-value]
{:keys [seed-backed-up?]} [:account/account]
error-message [:wallet/error-message]
address-hex [:account/hex-address]]
[react/view styles/main-section
[status-bar.view/status-bar {:type :wallet-tab}]
[settings/toolbar-view]
[react/scroll-view {:end-fill-color
colors/white
:refresh-control
(reagent/as-element
[(react/refresh-control) {:on-refresh #(re-frame/dispatch [:wallet.ui/pull-to-refresh])
:tint-color :white
:refreshing false}])}
(if error-message
[snackbar error-message]
[total-section portfolio-value currency])
;; this view is a hack to hide the 1px high white line gap on android
(when platform/android?
[react/view {:style {:background-color colors/blue
:height 1
:margin -1}}])
(when (and (not modal?)
(not seed-backed-up?)
(some (fn [{:keys [amount]}]
(and amount (not (.isZero amount))))
assets))
[backup-seed-phrase])
[react/view {:style {:background-color colors/blue}}
[list/flat-list
{:data actions
:key-fn (fn [_ i] (str i))
:render-fn #(list/render-action % {:action-label-style {:font-size 17}})}]]
[asset-section assets currency address-hex]
(when platform/ios?
[react/view {:style styles/scroll-bottom}])]]))
(views/defview wallet []
(views/letsubs [{:keys [wallet-set-up-passed?]} [:account/account]]
(if (not wallet-set-up-passed?)
[onboarding.views/onboarding]
[wallet-root])))

View File

@ -1,93 +0,0 @@
(ns status-im.ui.screens.wallet.onboarding.styles
(:require [status-im.ui.components.colors :as colors]))
(def border-top-justify
{:justify-content :space-between
:flex 1})
(def signing-phrase
{:background-color colors/white
:border-radius 8
:flex-direction :row})
(def signing-word
{:flex 1
:height 66
:border-left-color "#ECECF0"
:border-left-width 1
:align-items :center
:justify-content :center})
(def signing-word-text
{:typography :main-semibold})
(def bottom-buttons
{:background-color colors/blue
:padding-vertical 8})
(def got-it-button-text
{:padding-horizontal 0})
(def modal
{:flex 1
:background-color colors/blue})
(def bottom-button-container
{:flex-direction :row,
:border-top-width 1
:background-color colors/blue
:border-top-color colors/white-light-transparent})
(def explanation-container
{:margin-top 40
:margin-left 2
:margin-right 2
:align-items :center})
(def super-safe-text
{:typography :header
:color colors/white
:margin-bottom 12})
(def super-safe-explainer-text
{:color colors/white
:text-align :center
:margin-bottom 30})
;; onboarding screen styles
(def root
{:flex 1
:background-color colors/blue
:align-items :center
:justify-content :center
:padding-horizontal 30})
(def onboarding-image-container
{:flex 1
:align-items :center
:justify-content :center})
(def onboarding-image
{:width 285
:height 312})
(def onboarding-title
{:typography :header
:text-align :center
:color colors/white})
(def onboarding-text
{:margin-top 8
:margin-bottom 32
:font-size 14
:text-align :center
:color colors/white-transparent})
(def set-up-button
{:flex-direction :row
:background-color (colors/alpha colors/black 0.1)
:margin-bottom 32})
(def set-up-button-label
{:color colors/white})

View File

@ -1,132 +0,0 @@
(ns status-im.ui.screens.wallet.onboarding.views
(:require
[clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.i18n :as i18n]
[status-im.react-native.resources :as resources]
[status-im.ui.components.button.view :as button]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.common.common :as components.common]
[status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.components.react :as react]
[status-im.ui.components.status-bar.view :as status-bar]
[status-im.ui.components.styles :as components.styles]
[status-im.ui.components.toolbar.actions :as actions]
[status-im.ui.screens.wallet.components.views :as wallet.components]
[status-im.ui.screens.wallet.onboarding.styles :as styles]
[status-im.utils.utils :as utils])
(:require-macros [status-im.utils.views :as views]))
(defn display-confirmation [on-accept]
(utils/show-confirmation
{:title (i18n/label :t/wallet-set-up-confirm-title)
:content (i18n/label :t/wallet-set-up-confirm-description)
:cancel-button-text (i18n/label :t/see-it-again)
:confirm-button-text (i18n/label :t/got-it)
:on-accept on-accept}))
(defn info-bubble [text]
;; keeping styles inline here as we are going to
;; want to move this somewhere for reuse.
[react/view {:style {:padding-top 10
:align-items :center}}
[react/view {:style {:align-items :center
:position :absolute
:top 0
:width 34
:zIndex 1
:background-color colors/blue}}
[vector-icons/icon :main-icons/info {:color colors/white}]]
[react/view
{:style {:border-color (colors/alpha colors/white 0.6)
:border-width 1
:border-radius 8
:padding-top 15
:padding-bottom 15
:padding-left 20
:padding-right 20
:align-items :center}}
[react/text
{:style {:color (colors/alpha colors/white 0.6)
:text-align :center}}
text]]])
(defn toolbar []
^{:key "toolbar"}
[wallet.components/toolbar
{:transparent? true}
(actions/back-white #(re-frame/dispatch [:wallet.setup.ui/navigate-back-pressed]))
(i18n/label :t/wallet-set-up-title)])
(defn main-panel [signing-phrase on-confirm]
(let [signing-words (string/split signing-phrase #" ")]
^{:key "main-panel-view"}
[react/view {:style styles/border-top-justify}
[react/view] ;; crappy way to vertically center things
[react/view {:style {:padding-left 36 :padding-right 36}}
[react/view {:style styles/signing-phrase
:accessibility-label :signing-phrase}
(map
(fn [word container-style]
^{:key (str "signing-word-" word)}
[react/view container-style
[react/text {:style styles/signing-word-text
:number-of-lines 1}
word]])
signing-words
(cons
(dissoc styles/signing-word
:border-left-color
:border-left-width)
(repeat styles/signing-word))
(cons true (repeat false)))]
[react/view {:style styles/explanation-container}
[react/text {:style styles/super-safe-text}
(i18n/label :t/wallet-set-up-safe-transactions-title)]
[react/text
{:style styles/super-safe-explainer-text}
(i18n/label :t/wallet-set-up-signing-explainer)]
(info-bubble
(i18n/label :t/wallet-set-up-signing-explainer-warning))]]
[react/view {:style styles/bottom-button-container}
[button/button {:on-press on-confirm
:text-style styles/got-it-button-text
:style {:padding-vertical 9}
:accessibility-label :done-button
:fit-to-text? false}
(i18n/label :t/got-it)
nil]]]))
(views/defview screen []
(views/letsubs [{:keys [signing-phrase]} [:account/account]]
[wallet.components/simple-screen
{:avoid-keyboard? true}
(toolbar)
(main-panel
signing-phrase
(partial display-confirmation #(re-frame/dispatch [:accounts.ui/wallet-set-up-confirmed false])))]))
(views/defview modal []
(views/letsubs [{:keys [signing-phrase]} [:account/account]]
[react/view styles/modal
[status-bar/status-bar {:type :modal-wallet}]
[react/view components.styles/flex
(toolbar)
(main-panel
signing-phrase
(partial display-confirmation #(re-frame/dispatch [:accounts.ui/wallet-set-up-confirmed true])))]]))
(defn onboarding []
[react/view styles/root
[react/view {:style styles/onboarding-image-container}
[react/image {:source (resources/get-image :wallet-welcome)
:style styles/onboarding-image}]]
[react/text {:style styles/onboarding-title}
(i18n/label :t/wallet-onboarding-title)]
[react/text {:style styles/onboarding-text}
(i18n/label :t/wallet-onboarding-description)]
[components.common/button
{:button-style styles/set-up-button
:label-style styles/set-up-button-label
:on-press #(re-frame/dispatch [:navigate-to :wallet-onboarding-setup])
:label (i18n/label :t/wallet-onboarding-set-up)}]])

View File

@ -1,34 +0,0 @@
(ns status-im.ui.screens.wallet.request.events
(:require [status-im.chat.commands.sending :as commands-sending]
[status-im.chat.models :as chat-model]
[status-im.utils.fx :as fx]
[status-im.utils.handlers :as handlers]
[status-im.utils.money :as money]
[status-im.wallet.db :as wallet.db]))
(handlers/register-handler-fx
:wallet-send-request
(fn [{:keys [db] :as cofx} [_ public-key amount symbol decimals]]
(assert public-key)
(let [request-command (get-in db [:id->command ["request" #{:personal-chats}]])]
(fx/merge cofx
(chat-model/start-chat public-key nil)
(commands-sending/send public-key
request-command
{:asset (name symbol)
:amount (str (money/internal->formatted amount symbol decimals))})))))
(handlers/register-handler-fx
:wallet.request/set-and-validate-amount
(fn [{:keys [db]} [_ amount symbol decimals]]
(let [{:keys [value error]} (wallet.db/parse-amount amount decimals)]
{:db (-> db
(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-error] error))})))
(handlers/register-handler-fx
:wallet.request/set-symbol
(fn [{:keys [db]} [_ symbol]]
{:db (-> db
(assoc-in [:wallet :request-transaction :symbol] symbol))}))

View File

@ -1,6 +1,5 @@
(ns status-im.ui.screens.wallet.request.views (ns status-im.ui.screens.wallet.request.views
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.eip55 :as eip55] [status-im.ethereum.eip55 :as eip55]
[status-im.ethereum.eip681 :as eip681] [status-im.ethereum.eip681 :as eip681]
[status-im.ethereum.tokens :as tokens] [status-im.ethereum.tokens :as tokens]
@ -16,16 +15,13 @@
[status-im.ui.components.toolbar.view :as toolbar] [status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.screens.wallet.components.views :as wallet.components] [status-im.ui.screens.wallet.components.views :as wallet.components]
[status-im.ui.screens.wallet.request.styles :as styles] [status-im.ui.screens.wallet.request.styles :as styles]
[status-im.ui.screens.wallet.utils :as wallet.utils] [status-im.wallet.utils :as wallet.utils]
[status-im.utils.utils :as utils]) [status-im.utils.utils :as utils])
(:require-macros [status-im.utils.views :as views])) (:require-macros [status-im.utils.views :as views]))
;; 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 [chain [:ethereum/chain-keyword] (views/letsubs [chain [:ethereum/chain-keyword]
{:keys [to to-name public-key]} [:wallet.send/transaction]
{:keys [amount amount-error amount-text symbol to {:keys [amount amount-error amount-text symbol to
to-name public-key]} [:wallet.request/transaction] to-name public-key]} [:wallet.request/transaction]
network-status [:network-status] network-status [:network-status]
@ -65,8 +61,6 @@
(i18n/label :t/send-request) (i18n/label :t/send-request)
[vector-icons/icon :main-icons/next {:color :white}]]]]]))) [vector-icons/icon :main-icons/next {:color :white}]]]]])))
;; Main screen
(defn send-transaction-request-button [value] (defn send-transaction-request-button [value]
[button/primary-button {:on-press #(re-frame/dispatch [:navigate-to :wallet-send-transaction-request]) [button/primary-button {:on-press #(re-frame/dispatch [:navigate-to :wallet-send-transaction-request])
:style styles/send-request :style styles/send-request

View File

@ -1,43 +0,0 @@
(ns status-im.ui.screens.wallet.send.events
(:require [status-im.utils.handlers :as handlers]
[status-im.utils.money :as money]
[status-im.wallet.db :as wallet.db]
[status-im.ethereum.tokens :as tokens]
[status-im.ethereum.abi-spec :as abi-spec]
[status-im.ethereum.core :as ethereum]
[status-im.signing.core :as signing]))
(defn set-and-validate-amount-db [db amount symbol decimals]
(let [{:keys [value error]} (wallet.db/parse-amount amount decimals)]
(-> 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))))
(handlers/register-handler-fx
:wallet.send/set-and-validate-amount
(fn [{:keys [db]} [_ amount symbol decimals]]
{:db (set-and-validate-amount-db db amount symbol decimals)}))
(handlers/register-handler-fx
:wallet.send/set-symbol
(fn [{:keys [db]} [_ symbol]]
{:db (-> db
(assoc-in [:wallet :send-transaction :symbol] symbol)
(assoc-in [:wallet :send-transaction :amount] nil)
(assoc-in [:wallet :send-transaction :amount-text] nil)
(assoc-in [:wallet :send-transaction :asset-error] nil))}))
(handlers/register-handler-fx
:wallet.ui/sign-transaction-button-clicked
(fn [{:keys [db] :as cofx} _]
(let [{:keys [to symbol amount]} (get-in cofx [:db :wallet :send-transaction])
{:keys [symbol address]} (tokens/asset-for (:wallet/all-tokens db) (keyword (:chain db)) symbol)
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
:value amount-hex}
{:to (ethereum/normalized-address address)
:data (abi-spec/encode "transfer(address,uint256)" [to-norm amount-hex])})
:on-result [:navigate-back]}))))

View File

@ -1,7 +1,6 @@
(ns status-im.ui.screens.wallet.send.styles (ns status-im.ui.screens.wallet.send.styles
(:require-macros [status-im.utils.styles :refer [defstyle]]) (: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]
[status-im.ui.screens.wallet.components.styles :as wallet.components.styles])) [status-im.ui.screens.wallet.components.styles :as wallet.components.styles]))
(def send-transaction-form (def send-transaction-form
@ -21,14 +20,6 @@
:padding-top 12 :padding-top 12
:padding-horizontal 12}) :padding-horizontal 12})
(def spinner-container
{:position :absolute
:left 0
:top 0
:right 0
:bottom 0
:justify-content :center})
(def signing-phrase-container (def signing-phrase-container
{:border-radius 8 {:border-radius 8
:height 36 :height 36
@ -69,48 +60,14 @@
:padding 8 :padding 8
:align-items :center}) :align-items :center})
(def advanced-button-wrapper
{:align-items :center})
(def advanced-wrapper
{:margin-top 24
:margin-bottom 16})
(def gas-container-wrapper
{:flex 1
:flex-direction :row})
(def gas-input-wrapper
{:align-items :center
:justify-content :space-between
:flex-direction :row})
(def advanced-options-text-wrapper
{:flex 1
:flex-direction :row
:justify-content :space-between
:margin-vertical 15})
(def advanced-label (def advanced-label
{:text-align-vertical :center {:text-align-vertical :center
:margin-left 4}) :margin-left 4})
(def advanced-fees-text
{:color colors/white})
(def advanced-fees-details-text
{:color colors/white-transparent})
(def transaction-fee-block-wrapper
{:flex-direction :row})
(def transaction-fee-info (def transaction-fee-info
{:flex-direction :row {:flex-direction :row
:margin 15}) :margin 15})
(def transaction-fee-info-text-wrapper
{:flex-shrink 1})
(def transaction-fee-info-icon (def transaction-fee-info-icon
{:border-radius 25 {:border-radius 25
:width 25 :width 25
@ -136,9 +93,6 @@
{:background-color colors/blue {:background-color colors/blue
:padding-vertical 8}) :padding-vertical 8})
(def fee-buttons
{:background-color colors/blue})
(def password-error-tooltip (def password-error-tooltip
{:bottom-value 15 {:bottom-value 15
:color colors/red-light}) :color colors/red-light})

View File

@ -13,7 +13,6 @@
[status-im.ui.components.styles :as components.styles] [status-im.ui.components.styles :as components.styles]
[status-im.ui.components.toolbar.view :as toolbar] [status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.screens.wallet.components.views :as wallet.components] [status-im.ui.screens.wallet.components.views :as wallet.components]
[status-im.ui.screens.wallet.main.views :as wallet.main.views]
[status-im.ui.screens.wallet.send.styles :as styles] [status-im.ui.screens.wallet.send.styles :as styles]
[status-im.ui.components.toolbar.actions :as actions])) [status-im.ui.components.toolbar.actions :as actions]))
@ -51,8 +50,6 @@
:ref #(reset! scroll %) :ref #(reset! scroll %)
:on-content-size-change #(when (and scroll @scroll) :on-content-size-change #(when (and scroll @scroll)
(.scrollToEnd @scroll))} (.scrollToEnd @scroll))}
(when-not online?
[wallet.main.views/snackbar :t/error-cant-send-transaction-offline])
[react/view styles/send-transaction-form [react/view styles/send-transaction-form
[wallet.components/recipient-selector [wallet.components/recipient-selector
{:address to {:address to

View File

@ -42,9 +42,9 @@
:cyrcle-color (colors/alpha colors/red 0.1) :cyrcle-color (colors/alpha colors/red 0.1)
:on-press #(hide-sheet-and-dispatch [:wallet.custom-token.ui/remove-pressed token])}])])) :on-press #(hide-sheet-and-dispatch [:wallet.custom-token.ui/remove-pressed token])}])]))
(defn- render-token [{:keys [symbol name icon color custom?] :as token} visible-tokens] (defn- render-token [{:keys [symbol name icon color custom? checked?] :as token}]
[list/list-item-with-checkbox [list/list-item-with-checkbox
{:checked? (contains? visible-tokens (keyword symbol)) {:checked? checked?
:on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet {:content (custom-token-actions-view token) :on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet {:content (custom-token-actions-view token)
:content-height (if custom? 128 68)}]) :content-height (if custom? 128 68)}])
:on-value-change #(re-frame/dispatch [:wallet.settings/toggle-visible-token (keyword symbol) %])} :on-value-change #(re-frame/dispatch [:wallet.settings/toggle-visible-token (keyword symbol) %])}
@ -57,10 +57,7 @@
[list/item-secondary symbol]]]]) [list/item-secondary symbol]]]])
(defview manage-assets [] (defview manage-assets []
(letsubs [chain [:ethereum/chain-keyword] (letsubs [{custom-tokens true default-tokens nil} [:wallet/grouped-chain-tokens]]
visible-tokens [:wallet/visible-tokens-symbols]
all-tokens [:wallet/all-tokens]]
(let [{custom-tokens true default-tokens nil} (group-by :custom? (tokens/sorted-tokens-for all-tokens chain))]
[react/view (merge components.styles/flex {:background-color :white}) [react/view (merge components.styles/flex {:background-color :white})
[status-bar/status-bar] [status-bar/status-bar]
[toolbar] [toolbar]
@ -81,7 +78,7 @@
:stickySectionHeadersEnabled false :stickySectionHeadersEnabled false
:render-section-header-fn (fn [{:keys [title data]}] :render-section-header-fn (fn [{:keys [title data]}]
[list-header/list-header title]) [list-header/list-header title])
:render-fn #(render-token % visible-tokens)}]]]))) :render-fn render-token}]]]))
(defn- create-payload [address] (defn- create-payload [address]
{:address (ethereum/normalized-address address)}) {:address (ethereum/normalized-address address)})

View File

@ -1,15 +0,0 @@
(ns status-im.ui.screens.wallet.styles
(:require-macros [status-im.utils.styles :refer [defstyle]])
(:require [status-im.ui.components.colors :as colors]))
;; wallet
(defn button-container [enabled?]
(merge {:flex-direction :row
:align-items :center}
(when-not enabled?
{:opacity 0.4})))
(def wallet-modal-container
{:flex 1
:background-color colors/blue})

View File

@ -1,102 +0,0 @@
(ns status-im.ui.screens.wallet.transaction-fee.views
(:require [re-frame.core :as re-frame]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.tokens :as tokens]
[status-im.i18n :as i18n]
[status-im.ui.components.bottom-buttons.view :as bottom-buttons]
[status-im.ui.components.button.view :as button]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.react :as react]
[status-im.ui.components.styles :as components.styles]
[status-im.ui.components.toolbar.actions :as act]
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.components.tooltip.views :as tooltip]
[status-im.ui.screens.wallet.components.views :as components]
[status-im.ui.screens.wallet.send.styles :as styles]
[status-im.ui.screens.wallet.utils :as wallet.utils]
[status-im.utils.money :as money])
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn- toolbar [title]
[toolbar/toolbar
{:style {:border-bottom-color colors/white-light-transparent}}
[toolbar/nav-button (act/close-white act/default-handler)]
[toolbar/content-title {:color :white} title]])
(defview transaction-fee []
(letsubs [send-transaction [:wallet.send/transaction]
chain [:ethereum/chain-keyword]
{gas-edit :gas
max-fee :max-fee
gas-price-edit :gas-price} [:wallet/edit]
all-tokens [:wallet/all-tokens]]
(let [{:keys [amount symbol]} send-transaction
gas (:value gas-edit)
gas-price (:value gas-price-edit)
native-currency (tokens/native-currency chain)
{:keys [decimals] :as token} (tokens/asset-for all-tokens chain symbol)]
[components/simple-screen {:status-bar-type :modal-wallet}
[toolbar (i18n/label :t/wallet-transaction-fee)]
[react/view components.styles/flex
[react/view {:flex-direction :row}
[react/view styles/gas-container-wrapper
[components/cartouche {}
(i18n/label :t/gas-limit)
[react/view styles/gas-input-wrapper
[react/text-input (merge styles/transaction-fee-input
{:on-change-text #(re-frame/dispatch [:wallet.send/edit-value :gas %])
:default-value gas
:accessibility-label :gas-limit-input})]]]
(when (:invalid? gas-edit)
[tooltip/tooltip (i18n/label :t/invalid-number) styles/gas-input-error-tooltip])]
[react/view styles/gas-container-wrapper
[components/cartouche {}
(i18n/label :t/gas-price)
[react/view styles/gas-input-wrapper
[react/text-input (merge styles/transaction-fee-input
{:on-change-text #(re-frame/dispatch [:wallet.send/edit-value :gas-price %])
:default-value gas-price
:accessibility-label :gas-price-input})]
[components/cartouche-secondary-text
(i18n/label :t/gwei)]]]
(when (:invalid? gas-price-edit)
[tooltip/tooltip
(i18n/label (if (= :invalid-number (:invalid? gas-price-edit))
:t/invalid-number
:t/wallet-send-min-wei))
styles/gas-input-error-tooltip])]]
[react/view styles/transaction-fee-info
[react/view styles/transaction-fee-info-icon
[react/text {:style styles/transaction-fee-info-icon-text} "?"]]
[react/view styles/transaction-fee-info-text-wrapper
[react/i18n-text {:style styles/advanced-fees-text
:key :wallet-transaction-fee-details}]]]
[components/separator]
[react/view styles/transaction-fee-block-wrapper
[components/cartouche {:disabled? true}
(i18n/label :t/amount)
[react/view {:accessibility-label :amount-input}
[components/cartouche-text-content
(str (money/to-fixed (money/internal->formatted amount symbol decimals)))
(wallet.utils/display-symbol token)]]]
[components/cartouche {:disabled? true}
(i18n/label :t/wallet-transaction-total-fee)
[react/view {:accessibility-label :total-fee-input}
[components/cartouche-text-content
(str max-fee " " (wallet.utils/display-symbol native-currency))]]]]
[bottom-buttons/bottom-buttons styles/fee-buttons
[button/button {:on-press #(re-frame/dispatch [:wallet.send/reset-gas-default])
:accessibility-label :reset-to-default-button}
(i18n/label :t/reset-default)]
[button/button {:on-press #(do (re-frame/dispatch [:wallet.send/set-gas-details
(:value-number gas-edit)
(:value-number gas-price-edit)])
(act/default-handler))
:accessibility-label :done-button
:disabled? (or (:invalid? gas-edit)
(:invalid? gas-price-edit))}
(i18n/label :t/done)]]]])))

View File

@ -1,15 +0,0 @@
(ns status-im.ui.screens.wallet.transaction-sent.styles
(:require [status-im.ui.components.colors :as colors]
[status-im.utils.platform :as platform]))
(def transaction-sent
{:typography :header
:color colors/white
:margin-bottom 8})
(def transaction-sent-description
{:typography :title
:color (colors/alpha colors/white 0.6)
:text-align :center
:padding-horizontal 30
:margin-bottom 35})

View File

@ -1,69 +0,0 @@
(ns status-im.ui.screens.wallet.transaction-sent.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [status-im.ui.components.react :as react]
[status-im.ui.components.status-bar.view :as status-bar]
[status-im.ui.components.icons.vector-icons :as vi]
[status-im.react-native.resources :as resources]
[status-im.ui.screens.wallet.transaction-sent.styles :as styles]
[re-frame.core :as re-frame]
[status-im.i18n :as i18n]
[status-im.utils.platform :as platform]
[status-im.ui.components.colors :as colors]))
(defn- ok-circle []
[react/view {:background-color colors/black-transparent
:width 160
:height 160
:border-radius 81
:align-items :center
:justify-content :center}
[react/view {:background-color colors/white
:width 80
:height 80
:border-radius 41
:shadow-radius 4
:shadow-offset {:width 0 :height 2}
:shadow-opacity 0.8
:shadow-color "rgba(43, 59, 71, 0.12)"
:align-items :center
:justify-content :center}
[vi/tiny-icon :tiny-icons/tiny-check {:color colors/blue}]]])
(defn- transaction-sent-message []
[react/view {:align-items :center}
[react/text {:style styles/transaction-sent
:accessibility-label :transaction-sent-text}
(i18n/label :t/transaction-sent)]
[react/i18n-text {:style styles/transaction-sent-description
:key :transaction-description}]])
(defn- bottom-action-button [on-next]
[react/touchable-highlight {:on-press on-next
:style {:border-top-width 1
:border-color colors/white-light-transparent}
:accessibility-label :got-it-button}
[react/view {:align-items :center
:padding-vertical 18}
[react/text {:style {:color colors/white}}
(i18n/label :t/done)]]])
(defn- sent-screen [{:keys [on-next]}]
{:pre [(fn? on-next)]}
[react/view {:flex 1}
[react/view {:flex 0.7}] ;; spacer
[react/view {:align-items :center} (ok-circle)]
[react/view {:flex 1}] ;; spacer
(transaction-sent-message)
(bottom-action-button on-next)])
(defview transaction-sent []
(letsubs [chat-id [:chats/current-chat-id]]
[react/view {:flex 1 :background-color colors/blue}
[status-bar/status-bar {:type :transparent}]
(sent-screen {:on-next #(re-frame/dispatch [:close-transaction-sent-screen chat-id])})]))
(defview transaction-sent-modal []
(letsubs [chat-id [:chats/current-chat-id]]
[react/view {:flex 1 :background-color colors/blue}
[status-bar/status-bar {:type :modal-wallet}]
(sent-screen {:on-next #(re-frame/dispatch [:close-transaction-sent-screen chat-id])})]))

View File

@ -0,0 +1,16 @@
(ns status-im.wallet.accounts.core
(:require [re-frame.core :as re-frame]
[status-im.ethereum.core :as ethereum]
[status-im.utils.fx :as fx]
[status-im.ethereum.eip55 :as eip55]
[status-im.ui.components.list-selection :as list-selection]))
(re-frame/reg-fx
:list.selection/open-share
(fn [obj]
(list-selection/open-share obj)))
(fx/defn set-symbol-request
{:events [:wallet.accounts/share]}
[{:keys [db]}]
{:list.selection/open-share {:message (eip55/address->checksum (ethereum/current-address db))}})

View File

@ -0,0 +1,122 @@
(ns status-im.wallet.choose-recipient.core
(:require [re-frame.core :as re-frame]
[status-im.constants :as constants]
[status-im.contact.db :as contact.db]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.eip55 :as eip55]
[status-im.ethereum.eip681 :as eip681]
[status-im.ethereum.ens :as ens]
[status-im.i18n :as i18n]
[status-im.utils.money :as money]
[status-im.utils.fx :as fx]))
(fx/defn toggle-flashlight
{:events [:wallet/toggle-flashlight]}
[{:keys [db]}]
(let [flashlight-state (get-in db [:wallet :send-transaction :camera-flashlight])
toggled-state (if (= :on flashlight-state) :off :on)]
{:db (assoc-in db [:wallet :send-transaction :camera-flashlight] toggled-state)}))
(defn- find-address-name [db address]
(:name (contact.db/find-contact-by-address (:contacts/contacts db) address)))
(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))
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))))))))
(defn- extract-details
"First try to parse as EIP681 URI, if not assume this is an address directly.
Returns a map containing at least the `address` and `chain-id` keys"
[s chain-id all-tokens]
(or (let [m (eip681/parse-uri s)]
(merge m (eip681/extract-request-details m all-tokens)))
(when (ethereum/address? s)
{:address s :chain-id chain-id})))
;; 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]
(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]
(i18n/label :t/changed-amount-warning {:old old-amount :new new-amount})))
(defn use-default-eth-gas [fx]
(assoc-in fx [:db :wallet :send-transaction :gas]
(ethereum/default-transaction-gas)))
(re-frame/reg-fx
:resolve-address
(fn [{:keys [registry ens-name cb]}]
(ens/get-addr registry ens-name cb)))
(fx/defn set-recipient
{:events [:wallet.send/set-recipient]}
[{:keys [db]} recipient]
(let [chain (ethereum/chain-keyword db)]
(if (ens/is-valid-eth-name? recipient)
{:resolve-address {:registry (get ens/ens-registries chain)
:ens-name recipient
:cb #(re-frame/dispatch [:wallet.send/set-recipient %])}}
(if (ethereum/address? recipient)
(let [checksum (eip55/address->checksum recipient)]
(if (eip55/valid-address-checksum? checksum)
{:db (assoc-in db [:wallet :send-transaction :to] checksum)
: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]}
[{{:keys [network] :wallet/keys [all-tokens] :as db} :db} data origin]
(let [current-chain-id (get-in constants/default-networks [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))]
(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)
(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})))))
(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]})

View File

@ -0,0 +1,203 @@
(ns status-im.wallet.collectibles.core
(:require [re-frame.core :as re-frame]
[status-im.browser.core :as browser]
[status-im.constants :as constants]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.erc721 :as erc721]
[status-im.ethereum.tokens :as tokens]
[status-im.utils.handlers :as handlers]
[status-im.utils.money :as money]
[status-im.utils.http :as http]
[clojure.string :as string]
[status-im.utils.types :as types]))
;;TODO: REPLACE ALL HANDLERS BY FX/DEFN
(defmulti load-collectible-fx (fn [_ symbol _] symbol))
(defmethod load-collectible-fx :default [_ _ _] nil)
(defmulti load-collectibles-fx (fn [_ symbol _ _] symbol))
(defmethod load-collectibles-fx :default [all-tokens symbol items-number address chain-id]
{:load-collectibles-fx [all-tokens symbol items-number address chain-id]})
(defn load-token [i items-number contract address symbol]
(when (< i items-number)
(erc721/token-of-owner-by-index contract address i
(fn [response]
(load-token (inc i) items-number contract address symbol)
(re-frame/dispatch [:load-collectible symbol response])))))
(re-frame/reg-fx
:load-collectibles-fx
(fn [[all-tokens symbol items-number address chain-id]]
(let [chain (ethereum/chain-id->chain-keyword chain-id)
contract (:address (tokens/symbol->token all-tokens chain symbol))]
(load-token 0 items-number contract address symbol))))
(handlers/register-handler-fx
:show-collectibles-list
(fn [{:keys [db]} [_ {:keys [symbol amount] :as collectible}]]
(let [chain-id (get-in constants/default-networks [(:network db) :config :NetworkId])
all-tokens (:wallet/all-tokens db)
items-number (money/to-number amount)
loaded-items-number (count (get-in db [:collectibles symbol]))]
(merge (when (not= items-number loaded-items-number)
(load-collectibles-fx all-tokens symbol items-number (ethereum/current-address db) chain-id))
{:dispatch [:navigate-to :collectibles-list collectible]}))))
;; Crypto Kitties
(def ck :CK)
(handlers/register-handler-fx
:load-kitties
(fn [{db :db} [_ ids]]
{:db db
:http-get-n (mapv (fn [id]
{:url (str "https://api.cryptokitties.co/kitties/" id)
:success-event-creator (fn [o]
[:load-collectible-success ck {id (http/parse-payload o)}])
:failure-event-creator (fn [o]
[:load-collectible-failure ck {id (http/parse-payload o)}])})
ids)}))
;; TODO(andrey) Each HTTP call will return up to 100 kitties. Maybe we need to implement some kind of paging later
(defmethod load-collectibles-fx ck [_ _ items-number address _]
{:http-get {:url (str "https://api.cryptokitties.co/kitties?offset=0&limit="
items-number
"&owner_wallet_address="
address
"&parents=false")
:success-event-creator (fn [o]
[:load-kitties (map :id (:kitties (http/parse-payload o)))])
:failure-event-creator (fn [o]
[:load-collectibles-failure (http/parse-payload o)])
:timeout-ms 10000}})
;; Crypto Strikers
(def strikers :STRK)
(defmethod load-collectible-fx strikers [_ _ id]
{:http-get {:url (str "https://us-central1-cryptostrikers-prod.cloudfunctions.net/cards/" id)
:success-event-creator (fn [o]
[:load-collectible-success strikers {id (http/parse-payload o)}])
:failure-event-creator (fn [o]
[:load-collectible-failure strikers {id (http/parse-payload o)}])}})
;;Etheremona
(def emona :EMONA)
(defmethod load-collectible-fx emona [_ _ id]
{:http-get {:url (str "https://www.etheremon.com/api/monster/get_data?monster_ids=" id)
:success-event-creator (fn [o]
[:load-collectible-success emona (:data (http/parse-payload o))])
:failure-event-creator (fn [o]
[:load-collectible-failure emona {id (http/parse-payload o)}])}})
;;Kudos
(def kudos :KDO)
(defmethod load-collectible-fx kudos [{db :db} symbol id]
(let [chain-id (get-in constants/default-networks [(:network db) :config :NetworkId])
all-tokens (:wallet/all-tokens db)]
{:erc721-token-uri [all-tokens symbol id chain-id]}))
(re-frame/reg-fx
:erc721-token-uri
(fn [[all-tokens symbol tokenId chain-id]]
(let [chain (ethereum/chain-id->chain-keyword chain-id)
contract (:address (tokens/symbol->token all-tokens chain symbol))]
(erc721/token-uri contract
tokenId
#(re-frame/dispatch [:token-uri-success
tokenId
(when %
(subs % (.indexOf % "http")))]))))) ;; extra chars in rinkeby
;;Superrare
(def superrare :SUPR)
(defmethod load-collectible-fx superrare [_ _ ids]
{:http-get-n (mapv (fn [id]
{:url id
:success-event-creator (fn [o]
[:load-collectible-success superrare {id (http/parse-payload o)}])
:failure-event-creator (fn [o]
[:load-collectible-failure superrare {id (http/parse-payload o)}])})
ids)})
(def graphql-url "https://api.pixura.io/graphql")
(defn graphql-query [address]
(str "{
collectiblesByOwner: allErc721Tokens(condition: {owner: \"" address "\"}) {
collectibles: nodes {
tokenId,
metadata: erc721MetadatumByTokenId {
metadataUri,
description,
name,
imageUri
}}}}"))
(defmethod load-collectibles-fx superrare [_ _ _ address _]
{:http-post {:url graphql-url
:data (types/clj->json {:query (graphql-query (ethereum/naked-address address))})
:opts {:headers {"Content-Type" "application/json"}}
:success-event-creator (fn [{:keys [response-body]}]
[:store-collectibles superrare
(get-in (http/parse-payload response-body) [:data :collectiblesByOwner :collectibles])])
:failure-event-creator (fn [{:keys [response-body]}]
[:load-collectibles-failure (http/parse-payload response-body)])
:timeout-ms 10000}})
(handlers/register-handler-fx
:token-uri-success
(fn [_ [_ tokenId token-uri]]
{:http-get {:url
token-uri
:success-event-creator
(fn [o]
[:load-collectible-success kudos {tokenId (update (http/parse-payload o)
:image
string/replace
#"http:"
"https:")}]) ;; http in mainnet
:failure-event-creator
(fn [o]
[:load-collectible-failure kudos {tokenId (http/parse-payload o)}])}}))
;;
(handlers/register-handler-fx
:load-collectible
(fn [cofx [_ symbol token-id]]
(load-collectible-fx cofx symbol token-id)))
(handlers/register-handler-fx
:store-collectibles
(fn [{db :db} [_ symbol collectibles]]
{:db (update-in db [:collectibles symbol] merge
(reduce #(assoc %1 (:tokenId %2) %2) {} collectibles))}))
(handlers/register-handler-fx
:load-collectible-success
(fn [{db :db} [_ symbol collectibles]]
{:db (update-in db [:collectibles symbol] merge collectibles)}))
(handlers/register-handler-fx
:load-collectibles-failure
(fn [{db :db} [_ reason]]
{:db (update-in db [:collectibles symbol :errors] merge reason)}))
(handlers/register-handler-fx
:load-collectible-failure
(fn [{db :db} [_]]
{:db db}))
(handlers/register-handler-fx
:open-collectible-in-browser
(fn [cofx [_ url]]
(browser/open-url cofx url)))

View File

@ -3,20 +3,43 @@
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[status-im.accounts.update.core :as accounts.update] [status-im.accounts.update.core :as accounts.update]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.ethereum.abi-spec :as abi-spec]
[status-im.ethereum.core :as ethereum] [status-im.ethereum.core :as ethereum]
[status-im.ethereum.json-rpc :as json-rpc] [status-im.ethereum.json-rpc :as json-rpc]
[status-im.ethereum.tokens :as tokens] [status-im.ethereum.tokens :as tokens]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.ui.screens.navigation :as navigation] [status-im.ui.screens.navigation :as navigation]
[status-im.ui.screens.wallet.utils :as wallet.utils] [status-im.wallet.utils :as wallet.utils]
[status-im.utils.config :as config] [status-im.utils.config :as config]
[status-im.utils.core :as utils.core] [status-im.utils.core :as utils.core]
[status-im.utils.fx :as fx] [status-im.utils.fx :as fx]
[status-im.utils.money :as money] [status-im.utils.money :as money]
[status-im.utils.prices :as prices] [status-im.utils.prices :as prices]
[status-im.utils.utils :as utils.utils] [status-im.utils.utils :as utils.utils]
[taoensso.timbre :as log])) [taoensso.timbre :as log]
[status-im.wallet.db :as wallet.db]
[status-im.ethereum.abi-spec :as abi-spec]
[status-im.signing.core :as signing]))
(re-frame/reg-fx
:wallet/get-balance
(fn [{:keys [account-id on-success on-error]}]
(json-rpc/call
{:method "eth_getBalance"
:params [account-id "latest"]
:on-success on-success
:on-error on-error})))
;; TODO(oskarth): At some point we want to get list of relevant
;; assets to get prices for
(re-frame/reg-fx
:wallet/get-prices
(fn [{:keys [from to mainnet? success-event error-event chaos-mode?]}]
(prices/get-prices from
to
mainnet?
#(re-frame/dispatch [success-event %])
#(re-frame/dispatch [error-event %])
chaos-mode?)))
(defn assoc-error-message [db error-type err] (defn assoc-error-message [db error-type err]
(assoc-in db [:wallet :errors error-type] (or err :unknown-error))) (assoc-in db [:wallet :errors error-type] (or err :unknown-error)))
@ -48,29 +71,6 @@
{:db (assoc-in db [:wallet :current-transaction] hash)} {:db (assoc-in db [:wallet :current-transaction] hash)}
(navigation/navigate-to-cofx :wallet-transaction-details nil))) (navigation/navigate-to-cofx :wallet-transaction-details nil)))
;; FX
(re-frame/reg-fx
:wallet/get-balance
(fn [{:keys [account-id on-success on-error]}]
(json-rpc/call
{:method "eth_getBalance"
:params [account-id "latest"]
:on-success on-success
:on-error on-error})))
;; TODO(oskarth): At some point we want to get list of relevant
;; assets to get prices for
(re-frame/reg-fx
:wallet/get-prices
(fn [{:keys [from to mainnet? success-event error-event chaos-mode?]}]
(prices/get-prices from
to
mainnet?
#(re-frame/dispatch [success-event %])
#(re-frame/dispatch [error-event %])
chaos-mode?)))
(defn- validate-token-name! (defn- validate-token-name!
[{:keys [address symbol name]}] [{:keys [address symbol name]}]
(json-rpc/eth-call (json-rpc/eth-call
@ -161,6 +161,7 @@
[{:keys [db] :as cofx}] [{:keys [db] :as cofx}]
(let [custom-tokens (get-in db [:account/account :settings :wallet :custom-tokens]) (let [custom-tokens (get-in db [:account/account :settings :wallet :custom-tokens])
chain (ethereum/chain-keyword db) chain (ethereum/chain-keyword db)
;;TODO why do we need all tokens ? chain can be changed only through relogin
all-tokens (merge-with all-tokens (merge-with
merge merge
(utils.core/map-values #(utils.core/index-by :address %) (utils.core/map-values #(utils.core/index-by :address %)
@ -301,3 +302,53 @@
(toggle-visible-token symbol true) (toggle-visible-token symbol true)
;;TODO(goranjovic): move `update-token-balance-success` function to wallet models ;;TODO(goranjovic): move `update-token-balance-success` function to wallet models
(update-token-balance symbol balance))) (update-token-balance symbol balance)))
(defn set-and-validate-amount-db [db amount symbol decimals]
(let [{:keys [value error]} (wallet.db/parse-amount amount decimals)]
(-> 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))))
(fx/defn set-and-validate-amount
{:events [:wallet.send/set-and-validate-amount]}
[{:keys [db]} amount symbol decimals]
{:db (set-and-validate-amount-db db amount symbol decimals)})
(fx/defn set-symbol
{:events [:wallet.send/set-symbol]}
[{:keys [db]} symbol]
{:db (-> db
(assoc-in [:wallet :send-transaction :symbol] symbol)
(assoc-in [:wallet :send-transaction :amount] nil)
(assoc-in [:wallet :send-transaction :amount-text] nil)
(assoc-in [:wallet :send-transaction :asset-error] nil))})
(fx/defn sign-transaction-button-clicked
{:events [:wallet.ui/sign-transaction-button-clicked]}
[{:keys [db] :as cofx}]
(let [{:keys [to symbol amount]} (get-in cofx [:db :wallet :send-transaction])
{:keys [symbol address]} (tokens/asset-for (:wallet/all-tokens db) (keyword (:chain db)) symbol)
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
:value amount-hex}
{:to (ethereum/normalized-address address)
:data (abi-spec/encode "transfer(address,uint256)" [to-norm amount-hex])})
:on-result [:navigate-back]})))
(fx/defn set-and-validate-amount-request
{:events [:wallet.request/set-and-validate-amount]}
[{:keys [db]} amount symbol decimals]
(let [{:keys [value error]} (wallet.db/parse-amount amount decimals)]
{:db (-> db
(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-error] error))}))
(fx/defn set-symbol-request
{:events [:wallet.request/set-symbol]}
[{:keys [db]} symbol]
{:db (-> db
(assoc-in [:wallet :request-transaction :symbol] symbol))})

View File

@ -2,7 +2,6 @@
(:require [clojure.string :as string] (:require [clojure.string :as string]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[status-im.ethereum.core :as ethereum] [status-im.ethereum.core :as ethereum]
[status-im.ethereum.decode :as decode]
[status-im.ethereum.json-rpc :as json-rpc] [status-im.ethereum.json-rpc :as json-rpc]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.ui.components.colors :as colors] [status-im.ui.components.colors :as colors]

View File

@ -1,4 +1,4 @@
(ns status-im.ui.screens.wallet.utils (ns status-im.wallet.utils
(:require [status-im.utils.money :as money])) (:require [status-im.utils.money :as money]))
(defn format-amount [amount decimals] (defn format-amount [amount decimals]

View File

@ -1137,5 +1137,15 @@
"ens-your-usernames": "Your usernames", "ens-your-usernames": "Your usernames",
"ens-no-usernames": "You don't have any username connected", "ens-no-usernames": "You don't have any username connected",
"ens-understand": "I understand that my wallet address will be publicly connected to my username.", "ens-understand": "I understand that my wallet address will be publicly connected to my username.",
"ens-transaction-pending": "Transaction pending..." "ens-transaction-pending": "Transaction pending...",
"add-account" : "Add account",
"add-an-account" : "Add an account",
"back-up-your-seed-phrase" : "Back up your seed phrase",
"add-a-watch-account" : "Add a watch-only account",
"account-settings" : "Account settings",
"export-account" : "Export account",
"set-currency" : "Set currency",
"view-signing" : "View signing phrase",
"history" : "History",
"no-collectibles" : "No collectibles available"
} }