2229 lines
72 KiB
Plaintext
Raw Normal View History

(ns status-im.subs
(:require [cljs.spec.alpha :as spec]
[clojure.string :as string]
[taoensso.timbre :as log]
[re-frame.core :as re-frame]
[status-im.browser.core :as browser]
[status-im.chat.constants :as chat.constants]
[status-im.chat.db :as chat.db]
[status-im.chat.models :as chat.models]
[status-im.chat.models.message-list :as models.message-list]
[status-im.constants :as constants]
[status-im.contact.db :as contact.db]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.stateofus :as stateofus]
[status-im.ethereum.tokens :as tokens]
[status-im.ethereum.transactions.core :as transactions]
[status-im.fleet.core :as fleet]
[status-im.group-chats.db :as group-chats.db]
[status-im.i18n :as i18n]
[status-im.multiaccounts.model :as multiaccounts.model]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.multiaccounts.db :as multiaccounts.db]
[status-im.multiaccounts.recover.core :as recover]
[status-im.pairing.core :as pairing]
[status-im.tribute-to-talk.core :as tribute-to-talk]
[status-im.tribute-to-talk.db :as tribute-to-talk.db]
[status-im.tribute-to-talk.whitelist :as whitelist]
2019-09-02 08:43:18 +02:00
[status-im.ui.components.tabbar.styles :as tabs.styles]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.toolbar.styles :as toolbar.styles]
[status-im.ui.screens.add-new.new-public-chat.db :as db]
[status-im.ui.screens.chat.stickers.styles :as stickers.styles]
[status-im.ui.screens.mobile-network-settings.utils
:as
mobile-network-utils]
[status-im.wallet.utils :as wallet.utils]
[status-im.utils.build :as build]
[status-im.utils.config :as config]
[status-im.utils.datetime :as datetime]
[status-im.utils.hex :as utils.hex]
[status-im.utils.identicon :as identicon]
[status-im.utils.money :as money]
[status-im.utils.platform :as platform]
[status-im.utils.security :as security]
[status-im.utils.universal-links.core :as links]
[status-im.wallet.core :as wallet]
[status-im.wallet.db :as wallet.db]
[status-im.signing.gas :as signing.gas]
[status-im.utils.gfycat.core :as gfycat]
status-im.ui.screens.hardwallet.connect.subs
status-im.ui.screens.hardwallet.settings.subs
status-im.ui.screens.hardwallet.pin.subs
status-im.ui.screens.hardwallet.setup.subs
[status-im.ens.core :as ens]))
;; TOP LEVEL ===========================================================================================================
(defn reg-root-key-sub [sub-name db-key]
(re-frame/reg-sub sub-name (fn [db] (get db db-key))))
;;view
(reg-root-key-sub :view-id :view-id)
(reg-root-key-sub :navigation-stack :navigation-stack)
(reg-root-key-sub :screen-params :navigation/screen-params)
(reg-root-key-sub :two-pane-ui-enabled? :two-pane-ui-enabled?)
;;bottom sheet
(reg-root-key-sub :bottom-sheet/show? :bottom-sheet/show?)
(reg-root-key-sub :bottom-sheet/view :bottom-sheet/view)
(reg-root-key-sub :bottom-sheet/options :bottom-sheet/options)
;;general
(reg-root-key-sub :sync-state :sync-state)
(reg-root-key-sub :network-status :network-status)
(reg-root-key-sub :peers-count :peers-count)
(reg-root-key-sub :about-app/node-info :node-info)
(reg-root-key-sub :peers-summary :peers-summary)
(reg-root-key-sub :dimensions/window :dimensions/window)
(reg-root-key-sub :initial-props :initial-props)
(reg-root-key-sub :fleets/custom-fleets :custom-fleets)
(reg-root-key-sub :desktop/desktop :desktop/desktop)
(reg-root-key-sub :desktop :desktop)
(reg-root-key-sub :animations :animations)
(reg-root-key-sub :ui/search :ui/search)
(reg-root-key-sub :web3-node-version :web3-node-version)
(reg-root-key-sub :keyboard-height :keyboard-height)
(reg-root-key-sub :keyboard-max-height :keyboard-max-height)
(reg-root-key-sub :sync-data :sync-data)
(reg-root-key-sub :layout-height :layout-height)
(reg-root-key-sub :mobile-network/remember-choice? :mobile-network/remember-choice?)
(reg-root-key-sub :qr-modal :qr-modal)
(reg-root-key-sub :content-layout-height :content-layout-height)
(reg-root-key-sub :bootnodes/manage :bootnodes/manage)
2019-08-23 16:11:23 +02:00
(reg-root-key-sub :networks/current-network :networks/current-network)
(reg-root-key-sub :networks/networks :networks/networks)
(reg-root-key-sub :networks/manage :networks/manage)
(reg-root-key-sub :get-pairing-installations :pairing/installations)
(reg-root-key-sub :tooltips :tooltips)
(reg-root-key-sub :supported-biometric-auth :supported-biometric-auth)
(reg-root-key-sub :app-active-since :app-active-since)
(reg-root-key-sub :connectivity/ui-status-properties :connectivity/ui-status-properties)
(reg-root-key-sub :logged-in-since :logged-in-since)
2019-08-23 16:11:23 +02:00
;;NOTE this one is not related to ethereum network
;; it is about cellular network/ wifi network
(reg-root-key-sub :network/type :network/type)
;;profile
(reg-root-key-sub :my-profile/seed :my-profile/seed)
(reg-root-key-sub :my-profile/advanced? :my-profile/advanced?)
(reg-root-key-sub :my-profile/editing? :my-profile/editing?)
(reg-root-key-sub :my-profile/profile :my-profile/profile)
(reg-root-key-sub :profile/photo-added? :profile/photo-added?)
;;multiaccount
(reg-root-key-sub :multiaccounts/multiaccounts :multiaccounts/multiaccounts)
(reg-root-key-sub :multiaccounts/login :multiaccounts/login)
(reg-root-key-sub :multiaccount :multiaccount)
(reg-root-key-sub :multiaccount/accounts :multiaccount/accounts)
(reg-root-key-sub :get-recover-multiaccount :multiaccounts/recover)
;;chat
(reg-root-key-sub ::cooldown-enabled? :chat/cooldown-enabled?)
(reg-root-key-sub ::chats :chats)
(reg-root-key-sub ::chat-ui-props :chat-ui-props)
(reg-root-key-sub :chats/current-chat-id :current-chat-id)
(reg-root-key-sub :public-group-topic :public-group-topic)
(reg-root-key-sub :chats/loading? :chats/loading?)
(reg-root-key-sub :new-chat-name :new-chat-name)
(reg-root-key-sub :group-chat-profile/editing? :group-chat-profile/editing?)
(reg-root-key-sub :group-chat-profile/profile :group-chat-profile/profile)
(reg-root-key-sub :selected-participants :selected-participants)
;;browser
(reg-root-key-sub :browsers :browser/browsers)
(reg-root-key-sub :browser/options :browser/options)
(reg-root-key-sub :dapps/permissions :dapps/permissions)
;;stickers
(reg-root-key-sub :stickers/selected-pack :stickers/selected-pack)
(reg-root-key-sub :stickers/packs :stickers/packs)
(reg-root-key-sub :stickers/installed-packs :stickers/packs-installed)
(reg-root-key-sub :stickers/packs-owned :stickers/packs-owned)
(reg-root-key-sub :stickers/packs-pending :stickers/packs-pending)
;;mailserver
(reg-root-key-sub :mailserver/current-id :mailserver/current-id)
(reg-root-key-sub :mailserver/mailservers :mailserver/mailservers)
(reg-root-key-sub :mailserver.edit/mailserver :mailserver.edit/mailserver)
(reg-root-key-sub :mailserver/state :mailserver/state)
(reg-root-key-sub :mailserver/pending-requests :mailserver/pending-requests)
(reg-root-key-sub :mailserver/request-error? :mailserver/request-error)
(reg-root-key-sub :mailserver/fetching-gaps-in-progress :mailserver/fetching-gaps-in-progress)
(reg-root-key-sub :mailserver/gaps :mailserver/gaps)
(reg-root-key-sub :mailserver/ranges :mailserver/ranges)
;;contacts
(reg-root-key-sub ::contacts :contacts/contacts)
(reg-root-key-sub :contacts/current-contact-identity :contacts/identity)
(reg-root-key-sub :new-identity-error :contacts/new-identity-error)
(reg-root-key-sub :contacts/new-identity :contacts/new-identity)
(reg-root-key-sub :group/selected-contacts :group/selected-contacts)
;;wallet
(reg-root-key-sub :wallet :wallet)
(reg-root-key-sub :prices :prices)
(reg-root-key-sub :collectibles :collectibles)
(reg-root-key-sub :wallet/all-tokens :wallet/all-tokens)
(reg-root-key-sub :prices-loading? :prices-loading?)
(reg-root-key-sub :wallet.transactions :wallet.transactions)
(reg-root-key-sub :wallet/custom-token-screen :wallet/custom-token-screen)
(reg-root-key-sub :wallet/prepare-transaction :wallet/prepare-transaction)
;;commands
(reg-root-key-sub :commands/select-account :commands/select-account)
;;ethereum
(reg-root-key-sub :ethereum/current-block :ethereum/current-block)
;;ens
(reg-root-key-sub :ens/registration :ens/registration)
(reg-root-key-sub :ens/names :ens/names)
;;signing
(reg-root-key-sub :signing/tx :signing/tx)
(reg-root-key-sub :signing/sign :signing/sign)
(reg-root-key-sub :signing/edit-fee :signing/edit-fee)
;;intro-wizard
(reg-root-key-sub :intro-wizard-state :intro-wizard)
(reg-root-key-sub :popover/popover :popover/popover)
2019-11-18 10:29:30 +01:00
(reg-root-key-sub :add-account :add-account)
(reg-root-key-sub :keycard :hardwallet)
(reg-root-key-sub :auth-method :auth-method)
;;GENERAL ==============================================================================================================
(re-frame/reg-sub
:connection-stats
:<- [:desktop/desktop]
(fn [desktop _]
(get desktop :debug-metrics)))
;; Intro wizard
(re-frame/reg-sub
:intro-wizard
:<- [:intro-wizard-state]
:<- [:dimensions/window]
(fn [[wizard-state
{:keys [width height] :as dimensions}
view-id]]
(assoc wizard-state
:view-height height :view-width width)))
(re-frame/reg-sub
:intro-wizard/generate-key
:<- [:intro-wizard]
(fn [wizard-state]
(select-keys wizard-state [:processing? :view-height])))
(re-frame/reg-sub
:intro-wizard/choose-key
:<- [:intro-wizard]
(fn [wizard-state]
(select-keys wizard-state [:multiaccounts :selected-id :view-height])))
(re-frame/reg-sub
:intro-wizard/select-key-storage
:<- [:intro-wizard]
(fn [wizard-state]
(merge (select-keys wizard-state [:selected-storage-type :view-height :recovering?])
(if (:recovering? wizard-state)
{:forward-action :multiaccounts.recover/select-storage-next-pressed}
{:forward-action :intro-wizard/step-forward-pressed}))))
(re-frame/reg-sub
:intro-wizard/create-code
:<- [:intro-wizard]
(fn [wizard-state]
(merge (select-keys wizard-state [:confirm-failure? :encrypt-with-password? :weak-password? :view-width])
(if (:recovering? wizard-state)
{:forward-action :multiaccounts.recover/enter-password-next-pressed}
{:forward-action :intro-wizard/step-forward-pressed}))))
(re-frame/reg-sub
:intro-wizard/confirm-code
:<- [:intro-wizard]
(fn [wizard-state]
(merge (select-keys wizard-state [:confirm-failure? :encrypt-with-password? :processing? :view-width])
(if (:recovering? wizard-state)
{:forward-action :multiaccounts.recover/confirm-password-next-pressed}
{:forward-action :intro-wizard/step-forward-pressed}))))
(re-frame/reg-sub
:intro-wizard/enter-phrase
:<- [:intro-wizard]
(fn [wizard-state]
(select-keys wizard-state [:processing?
:passphrase-word-count
:next-button-disabled?
:passphrase-error])))
(re-frame/reg-sub
:intro-wizard/recovery-success
:<- [:intro-wizard]
(fn [wizard-state]
{:pubkey (get-in wizard-state [:derived constants/path-whisper-keyword :publicKey])
:name (get-in wizard-state [:derived constants/path-whisper-keyword :name])
:photo-path (get-in wizard-state [:derived constants/path-whisper-keyword :photo-path])
:processing? (:processing? wizard-state)}))
(re-frame/reg-sub
:intro-wizard/recover-existing-account?
:<- [:intro-wizard]
:<- [:multiaccounts/multiaccounts]
(fn [[intro-wizard multiaccounts]]
(recover/existing-account? (:root-key intro-wizard) multiaccounts)))
;;FIXME not needed until desktop enabled
#_(re-frame/reg-sub
:settings/logging-enabled
:<- [:desktop/desktop]
(fn [desktop _]
(get desktop :logging-enabled false)))
(re-frame/reg-sub
2019-08-23 16:11:23 +02:00
:current-network
:<- [:networks/networks]
:<- [:networks/current-network]
(fn [[networks current-network]]
(when-let [network (get networks current-network)]
(assoc network
:rpc-network? (get-in network [:config :UpstreamConfig :Enabled])))))
(re-frame/reg-sub
:chain-name
:<- [:current-network]
(fn [network]
(ethereum/network->chain-name network)))
(re-frame/reg-sub
:chain-id
:<- [:current-network]
(fn [network]
(ethereum/network->chain-id network)))
(re-frame/reg-sub
:mainnet?
:<- [:chain-id]
(fn [chain-id]
(= 1 chain-id)))
2019-08-01 22:11:59 +02:00
(re-frame/reg-sub
:network-name
2019-08-23 16:11:23 +02:00
:<- [:current-network]
2019-08-01 22:11:59 +02:00
(fn [network]
2019-08-23 16:11:23 +02:00
(:name network)))
(re-frame/reg-sub
:disconnected?
:<- [:peers-count]
(fn [peers-count]
(and (not config/nimbus-enabled?) (zero? peers-count))))
(re-frame/reg-sub
:offline?
:<- [:network-status]
:<- [:sync-state]
:<- [:disconnected?]
(fn [[network-status sync-state disconnected?]]
(or disconnected?
(= network-status :offline)
(= sync-state :offline))))
(re-frame/reg-sub
:syncing?
:<- [:sync-state]
(fn [sync-state]
(#{:pending :in-progress} sync-state)))
(re-frame/reg-sub
:dimensions/window-width
:<- [:dimensions/window]
:width)
(re-frame/reg-sub
:dimensions/window-height
:<- [:dimensions/window]
:height)
(re-frame/reg-sub
:dimensions/small-screen?
:<- [:dimensions/window-height]
(fn [height]
(< height 550)))
(re-frame/reg-sub
:get-screen-params
:<- [:screen-params]
:<- [:view-id]
(fn [[params view-id-db] [_ view-id]]
(get params (or view-id view-id-db))))
(re-frame/reg-sub
:can-navigate-back?
:<- [:navigation-stack]
(fn [stack]
(> (count stack) 1)))
(re-frame/reg-sub
:delete-swipe-position
:<- [:animations]
(fn [animations [_ type item-id]]
(get-in animations [type item-id :delete-swiped])))
(re-frame/reg-sub
:search/home-filter
:<- [:ui/search]
(fn [search]
(get search :home-filter)))
(re-frame/reg-sub
:search/currency-filter
:<- [:ui/search]
(fn [search]
(get search :currency-filter)))
(re-frame/reg-sub
:search/token-filter
:<- [:ui/search]
(fn [search]
(get search :token-filter)))
(defn- node-version [web3-node-version]
(or web3-node-version "N/A"))
(def app-short-version
(let [version (if platform/desktop? build/version build/build-no)]
(str build/version " (" version ")")))
(re-frame/reg-sub
:get-app-version
:<- [:web3-node-version]
(fn [web3-node-version]
(str app-short-version "; " (node-version web3-node-version))))
(re-frame/reg-sub
:get-app-short-version
(fn [db] app-short-version))
(re-frame/reg-sub
:get-app-node-version
:<- [:web3-node-version]
node-version)
(re-frame/reg-sub
:my-profile/recovery
:<- [:my-profile/seed]
(fn [seed]
(or seed {:step :intro})))
(re-frame/reg-sub
:bottom-sheet
:<- [:bottom-sheet/show?]
:<- [:bottom-sheet/view]
(fn [[show? view]]
{:show? show?
:view view}))
(re-frame/reg-sub
:is-contact-selected?
:<- [:group/selected-contacts]
(fn [selected-contacts [_ element]]
(-> selected-contacts
(contains? element))))
(re-frame/reg-sub
:is-participant-selected?
:<- [:selected-participants]
(fn [selected-participants [_ element]]
(-> selected-participants
(contains? element))))
2019-05-16 23:50:03 +02:00
(re-frame/reg-sub
:ethereum/chain-keyword
2019-08-23 16:11:23 +02:00
:<- [:current-network]
2019-05-16 23:50:03 +02:00
(fn [network]
(ethereum/network->chain-keyword network)))
(re-frame/reg-sub
:ethereum/native-currency
:<- [:ethereum/chain-keyword]
(fn [chain-keyword]
(tokens/native-currency chain-keyword)))
2020-01-24 14:56:21 +02:00
(reg-root-key-sub :ethereum/current-block :ethereum/current-block)
;;MULTIACCOUNT ==============================================================================================================
(re-frame/reg-sub
:multiaccount/public-key
:<- [:multiaccount]
(fn [{:keys [public-key]}]
public-key))
(re-frame/reg-sub
:multiaccount/default-account
:<- [:multiaccount/accounts]
(fn [accounts]
(ethereum/get-default-account accounts)))
(re-frame/reg-sub
:sign-in-enabled?
:<- [:multiaccounts/login]
2019-08-23 11:17:54 +02:00
(fn [{:keys [password]}]
(spec/valid? ::multiaccounts.db/password
(security/safe-unmask-data password))))
(re-frame/reg-sub
:fleets/current-fleet
:<- [:multiaccount]
(fn [multiaccount]
(fleet/current-fleet-sub multiaccount)))
(re-frame/reg-sub
:log-level/current-log-level
:<- [:multiaccount]
(fn [multiaccount]
(or (get multiaccount :log-level)
config/log-level-status-go)))
(re-frame/reg-sub
:dapps-address
:<- [:multiaccount]
(fn [acc]
(get acc :dapps-address)))
(re-frame/reg-sub
:dapps-account
:<- [:multiaccount/accounts]
:<- [:dapps-address]
(fn [[accounts address]]
(some #(when (= (:address %) address) %) accounts)))
(re-frame/reg-sub
:multiaccount/current-account
:<- [:multiaccount/accounts]
:<- [:get-screen-params :wallet-account]
(fn [[accounts acc]]
(some #(when (= (:address %) (:address acc)) %) accounts)))
(re-frame/reg-sub
:account-by-address
:<- [:multiaccount/accounts]
(fn [accounts [_ address]]
(some #(when (= (:address %) address) %) accounts)))
(re-frame/reg-sub
:multiple-multiaccounts?
:<- [:multiaccounts/multiaccounts]
(fn [multiaccounts]
(> (count multiaccounts) 1)))
2019-12-10 12:07:38 +02:00
(re-frame/reg-sub
:multiaccounts.login/keycard-account?
:<- [:multiaccounts/multiaccounts]
:<- [:multiaccounts/login]
(fn [[multiaccounts {:keys [key-uid]}]]
(get-in multiaccounts [key-uid :keycard-pairing])))
2019-11-18 10:29:30 +01:00
(re-frame/reg-sub
:accounts-without-watch-only
:<- [:multiaccount/accounts]
(fn [accounts]
(filter #(not= (:type %) :watch) accounts)))
2019-11-18 10:29:30 +01:00
(re-frame/reg-sub
:add-account-disabled?
:<- [:multiaccount/accounts]
:<- [:add-account]
(fn [[accounts {:keys [address]}]]
(or (not (ethereum/address? address))
(some #(when (= (:address %) address) %) accounts))))
(re-frame/reg-sub
:add-account-scanned-address
:<- [:add-account]
(fn [add-account]
(get add-account :scanned-address)))
;;CHAT ==============================================================================================================
(re-frame/reg-sub
:get-collectible-token
:<- [:collectibles]
(fn [collectibles [_ {:keys [symbol token]}]]
(get-in collectibles [(keyword symbol) (js/parseInt token)])))
(re-frame/reg-sub
:chats/chat
:<- [:chats/active-chats]
(fn [chats [_ chat-id]]
(get chats chat-id)))
(re-frame/reg-sub
:chats/content-layout-height
:<- [:content-layout-height]
:<- [:chats/current-chat-ui-prop :input-height]
:<- [:chats/current-chat-ui-prop :input-focused?]
:<- [:keyboard-height]
:<- [:chats/current-chat-ui-prop :input-bottom-sheet]
(fn [[home-content-layout-height input-height input-focused? kheight stickers?]]
(- (+ home-content-layout-height tabs.styles/tabs-height)
(if platform/iphone-x?
(* 2 toolbar.styles/toolbar-height)
toolbar.styles/toolbar-height)
(if input-height input-height 0)
(if stickers?
(stickers.styles/stickers-panel-height)
kheight)
(if input-focused?
(cond
platform/iphone-x? 0
platform/ios? tabs.styles/tabs-diff
:else 0)
(cond
platform/iphone-x? (* 2 tabs.styles/minimized-tabs-height)
platform/ios? tabs.styles/tabs-height
:else tabs.styles/minimized-tabs-height)))))
(re-frame/reg-sub
:chats/current-chat-ui-props
:<- [::chat-ui-props]
:<- [:chats/current-chat-id]
(fn [[chat-ui-props id]]
(get chat-ui-props id)))
(re-frame/reg-sub
:chats/current-chat-ui-prop
:<- [:chats/current-chat-ui-props]
(fn [ui-props [_ prop]]
(get ui-props prop)))
;; NOTE: The whole logic of stickers panel and input should be revised
(re-frame/reg-sub
:chats/chat-panel-height
:<- [:keyboard-max-height]
(fn [kb-height]
(cond
(and platform/iphone-x? (pos? kb-height))
(- kb-height tabs.styles/minimized-tabs-height 34)
(pos? kb-height)
(- kb-height tabs.styles/minimized-tabs-height)
platform/iphone-x? 299
platform/ios? 258
:else 272)))
(re-frame/reg-sub
:chats/input-margin
:<- [:keyboard-height]
:<- [:chats/current-chat-ui-prop :input-bottom-sheet]
(fn [[kb-height input-bottom-sheet]]
(cond
;; During the transition of bottom sheet and close of keyboard happens
;; The heights are summed up and input grows too much
(not (nil? input-bottom-sheet))
0
(and platform/iphone-x? (> kb-height 0))
(- kb-height tabs.styles/minimized-tabs-height 34)
platform/ios?
(+ kb-height (- (if (> kb-height 0)
tabs.styles/minimized-tabs-height
0)))
:default 0)))
(re-frame/reg-sub
:chats/active-chats
:<- [:contacts/contacts]
:<- [::chats]
:<- [:multiaccount]
(fn [[contacts chats multiaccount]]
(chat.db/active-chats contacts chats multiaccount)))
;; TODO: this is no useful without tribute to talk
#_(defn enrich-current-one-to-one-chat
[{:keys [contact] :as current-chat} my-public-key ttt-settings
chain-keyword prices currency]
(let [{:keys [tribute-to-talk]} contact
{:keys [disabled? snt-amount message]} tribute-to-talk
whitelisted-by? (whitelist/whitelisted-by? contact)
loading? (and (not whitelisted-by?)
(not tribute-to-talk))
show-input? (or whitelisted-by?
disabled?)
token (case chain-keyword
:mainnet :SNT
:STT)
tribute-status (if loading?
:loading
(tribute-to-talk.db/tribute-status contact))
tribute-label (tribute-to-talk.db/status-label tribute-status snt-amount)]
(cond-> (assoc current-chat
:tribute-to-talk/tribute-status tribute-status
:tribute-to-talk/tribute-label tribute-label)
(#{:required :pending :paid} tribute-status)
(assoc :tribute-to-talk/snt-amount
(tribute-to-talk.db/from-wei snt-amount)
:tribute-to-talk/message
message
:tribute-to-talk/fiat-amount (if snt-amount
(money/fiat-amount-value
snt-amount
token
(-> currency :code keyword)
prices)
"0")
:tribute-to-talk/fiat-currency (:code currency)
:tribute-to-talk/token (str " " (name token)))
(tribute-to-talk.db/enabled? ttt-settings)
(assoc :tribute-to-talk/received? (tribute-to-talk.db/tribute-received?
contact))
(= tribute-status :required)
(assoc :tribute-to-talk/on-share-my-profile
#(re-frame/dispatch
[:profile/share-profile-link my-public-key]))
show-input?
(assoc :show-input? true))))
(defn enrich-current-chat
[{:keys [messages chat-id might-have-join-time-messages?] :as chat}
ranges height input-height]
(assoc chat
:height height
:input-height input-height
:range
(get ranges chat-id)
:intro-status
(if might-have-join-time-messages?
:loading
(if (empty? messages)
:empty
:messages))))
(re-frame/reg-sub
Render markdown Fixes: https://github.com/status-im/trailofbits-audit/issues/47 Fixes: https://github.com/status-im/trailofbits-audit/issues/46 Fixes: https://github.com/status-im/trailofbits-audit/issues/44 Fixes: https://github.com/status-im/security-reports/issues/13 Fixes: https://github.com/status-im/security-reports/issues/5 Fixes: https://github.com/status-im/status-react/issues/8995 This commits re-introduce rendering of markdown text and implent a few changes: 1) Parsing of the message content is now in status-go, this includes markdown, line-count, and rtl. Parsing is not nested, as there's some rendering degradation involved as we nest components, unclear exactly if it's react-native or clojure, haven't looked too deeply into it. 2) Emojii type messages are not parsed on the sending side, not the receiving one, using the appropriate content-type 3) Fixes a few issues with chat input rendering, currrently we use `chats/current-chat` subscription which is very heavy and should not be used unless necessary, and means that any change to chat will trigger a re-render, which caused re-rendering of input container on each received message. Also to note that input-container is fairly heavy to render, and it's rendered twice at each keypress on input. The inline markdow supported is: *italic* or _italic_ **bold** or __bold__ `inline code` http://test.com links \#status-tag The block markdown supported is: \# Headers ``` code blocks ``` > Quotereply The styling is very basic at the moment, but can be improved. Adding other markdown (photo,mentions) is straightforward and should come at little performance cost (unless the component to render is heavy, i.e a photo for example). There are some behavioral changes with this commit: 1) Links are only parsed if starting with http:// or https://, meaning that blah.com won't be parsed, nor www.test.com. This behavior is consistent with discord for example and allows faster parsing at little expense to ser experience imo. Fixes a few security issues as well. 2) Content is not anymore capped (regression), that's due to the fact that before we only rendered text and react-native allowed us easily to limit the number of lines, but adding markdown support means that this strategy is not viable anymore. Performance of rendering don't see to be very much impacted by this, I would re-introduce it if necessary, but I'd rather do that in a separate PR. Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2019-11-07 14:41:37 +01:00
:chats/current-raw-chat
:<- [:chats/active-chats]
:<- [:chats/current-chat-id]
Render markdown Fixes: https://github.com/status-im/trailofbits-audit/issues/47 Fixes: https://github.com/status-im/trailofbits-audit/issues/46 Fixes: https://github.com/status-im/trailofbits-audit/issues/44 Fixes: https://github.com/status-im/security-reports/issues/13 Fixes: https://github.com/status-im/security-reports/issues/5 Fixes: https://github.com/status-im/status-react/issues/8995 This commits re-introduce rendering of markdown text and implent a few changes: 1) Parsing of the message content is now in status-go, this includes markdown, line-count, and rtl. Parsing is not nested, as there's some rendering degradation involved as we nest components, unclear exactly if it's react-native or clojure, haven't looked too deeply into it. 2) Emojii type messages are not parsed on the sending side, not the receiving one, using the appropriate content-type 3) Fixes a few issues with chat input rendering, currrently we use `chats/current-chat` subscription which is very heavy and should not be used unless necessary, and means that any change to chat will trigger a re-render, which caused re-rendering of input container on each received message. Also to note that input-container is fairly heavy to render, and it's rendered twice at each keypress on input. The inline markdow supported is: *italic* or _italic_ **bold** or __bold__ `inline code` http://test.com links \#status-tag The block markdown supported is: \# Headers ``` code blocks ``` > Quotereply The styling is very basic at the moment, but can be improved. Adding other markdown (photo,mentions) is straightforward and should come at little performance cost (unless the component to render is heavy, i.e a photo for example). There are some behavioral changes with this commit: 1) Links are only parsed if starting with http:// or https://, meaning that blah.com won't be parsed, nor www.test.com. This behavior is consistent with discord for example and allows faster parsing at little expense to ser experience imo. Fixes a few security issues as well. 2) Content is not anymore capped (regression), that's due to the fact that before we only rendered text and react-native allowed us easily to limit the number of lines, but adding markdown support means that this strategy is not viable anymore. Performance of rendering don't see to be very much impacted by this, I would re-introduce it if necessary, but I'd rather do that in a separate PR. Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2019-11-07 14:41:37 +01:00
(fn [[chats current-chat-id]]
(get chats current-chat-id)))
(re-frame/reg-sub
:chats/current-chat-input-text
:<- [:chats/current-raw-chat]
(fn [chat]
(:input-text chat)))
(re-frame/reg-sub
:chats/current-chat
:<- [:chats/current-raw-chat]
:<- [:multiaccount/public-key]
:<- [:mailserver/ranges]
:<- [:chats/content-layout-height]
:<- [:chats/current-chat-ui-prop :input-height]
(fn [[{:keys [group-chat chat-id contact messages] :as current-chat}
my-public-key ranges height input-height]]
Render markdown Fixes: https://github.com/status-im/trailofbits-audit/issues/47 Fixes: https://github.com/status-im/trailofbits-audit/issues/46 Fixes: https://github.com/status-im/trailofbits-audit/issues/44 Fixes: https://github.com/status-im/security-reports/issues/13 Fixes: https://github.com/status-im/security-reports/issues/5 Fixes: https://github.com/status-im/status-react/issues/8995 This commits re-introduce rendering of markdown text and implent a few changes: 1) Parsing of the message content is now in status-go, this includes markdown, line-count, and rtl. Parsing is not nested, as there's some rendering degradation involved as we nest components, unclear exactly if it's react-native or clojure, haven't looked too deeply into it. 2) Emojii type messages are not parsed on the sending side, not the receiving one, using the appropriate content-type 3) Fixes a few issues with chat input rendering, currrently we use `chats/current-chat` subscription which is very heavy and should not be used unless necessary, and means that any change to chat will trigger a re-render, which caused re-rendering of input container on each received message. Also to note that input-container is fairly heavy to render, and it's rendered twice at each keypress on input. The inline markdow supported is: *italic* or _italic_ **bold** or __bold__ `inline code` http://test.com links \#status-tag The block markdown supported is: \# Headers ``` code blocks ``` > Quotereply The styling is very basic at the moment, but can be improved. Adding other markdown (photo,mentions) is straightforward and should come at little performance cost (unless the component to render is heavy, i.e a photo for example). There are some behavioral changes with this commit: 1) Links are only parsed if starting with http:// or https://, meaning that blah.com won't be parsed, nor www.test.com. This behavior is consistent with discord for example and allows faster parsing at little expense to ser experience imo. Fixes a few security issues as well. 2) Content is not anymore capped (regression), that's due to the fact that before we only rendered text and react-native allowed us easily to limit the number of lines, but adding markdown support means that this strategy is not viable anymore. Performance of rendering don't see to be very much impacted by this, I would re-introduce it if necessary, but I'd rather do that in a separate PR. Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2019-11-07 14:41:37 +01:00
(when current-chat
(cond-> (enrich-current-chat current-chat ranges height input-height)
(empty? messages)
(assoc :universal-link
(links/generate-link :public-chat :external chat-id))
Render markdown Fixes: https://github.com/status-im/trailofbits-audit/issues/47 Fixes: https://github.com/status-im/trailofbits-audit/issues/46 Fixes: https://github.com/status-im/trailofbits-audit/issues/44 Fixes: https://github.com/status-im/security-reports/issues/13 Fixes: https://github.com/status-im/security-reports/issues/5 Fixes: https://github.com/status-im/status-react/issues/8995 This commits re-introduce rendering of markdown text and implent a few changes: 1) Parsing of the message content is now in status-go, this includes markdown, line-count, and rtl. Parsing is not nested, as there's some rendering degradation involved as we nest components, unclear exactly if it's react-native or clojure, haven't looked too deeply into it. 2) Emojii type messages are not parsed on the sending side, not the receiving one, using the appropriate content-type 3) Fixes a few issues with chat input rendering, currrently we use `chats/current-chat` subscription which is very heavy and should not be used unless necessary, and means that any change to chat will trigger a re-render, which caused re-rendering of input container on each received message. Also to note that input-container is fairly heavy to render, and it's rendered twice at each keypress on input. The inline markdow supported is: *italic* or _italic_ **bold** or __bold__ `inline code` http://test.com links \#status-tag The block markdown supported is: \# Headers ``` code blocks ``` > Quotereply The styling is very basic at the moment, but can be improved. Adding other markdown (photo,mentions) is straightforward and should come at little performance cost (unless the component to render is heavy, i.e a photo for example). There are some behavioral changes with this commit: 1) Links are only parsed if starting with http:// or https://, meaning that blah.com won't be parsed, nor www.test.com. This behavior is consistent with discord for example and allows faster parsing at little expense to ser experience imo. Fixes a few security issues as well. 2) Content is not anymore capped (regression), that's due to the fact that before we only rendered text and react-native allowed us easily to limit the number of lines, but adding markdown support means that this strategy is not viable anymore. Performance of rendering don't see to be very much impacted by this, I would re-introduce it if necessary, but I'd rather do that in a separate PR. Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2019-11-07 14:41:37 +01:00
(chat.models/public-chat? current-chat)
(assoc :show-input? true)
Render markdown Fixes: https://github.com/status-im/trailofbits-audit/issues/47 Fixes: https://github.com/status-im/trailofbits-audit/issues/46 Fixes: https://github.com/status-im/trailofbits-audit/issues/44 Fixes: https://github.com/status-im/security-reports/issues/13 Fixes: https://github.com/status-im/security-reports/issues/5 Fixes: https://github.com/status-im/status-react/issues/8995 This commits re-introduce rendering of markdown text and implent a few changes: 1) Parsing of the message content is now in status-go, this includes markdown, line-count, and rtl. Parsing is not nested, as there's some rendering degradation involved as we nest components, unclear exactly if it's react-native or clojure, haven't looked too deeply into it. 2) Emojii type messages are not parsed on the sending side, not the receiving one, using the appropriate content-type 3) Fixes a few issues with chat input rendering, currrently we use `chats/current-chat` subscription which is very heavy and should not be used unless necessary, and means that any change to chat will trigger a re-render, which caused re-rendering of input container on each received message. Also to note that input-container is fairly heavy to render, and it's rendered twice at each keypress on input. The inline markdow supported is: *italic* or _italic_ **bold** or __bold__ `inline code` http://test.com links \#status-tag The block markdown supported is: \# Headers ``` code blocks ``` > Quotereply The styling is very basic at the moment, but can be improved. Adding other markdown (photo,mentions) is straightforward and should come at little performance cost (unless the component to render is heavy, i.e a photo for example). There are some behavioral changes with this commit: 1) Links are only parsed if starting with http:// or https://, meaning that blah.com won't be parsed, nor www.test.com. This behavior is consistent with discord for example and allows faster parsing at little expense to ser experience imo. Fixes a few security issues as well. 2) Content is not anymore capped (regression), that's due to the fact that before we only rendered text and react-native allowed us easily to limit the number of lines, but adding markdown support means that this strategy is not viable anymore. Performance of rendering don't see to be very much impacted by this, I would re-introduce it if necessary, but I'd rather do that in a separate PR. Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2019-11-07 14:41:37 +01:00
(and (chat.models/group-chat? current-chat)
(group-chats.db/joined? my-public-key current-chat))
(assoc :show-input? true)
Render markdown Fixes: https://github.com/status-im/trailofbits-audit/issues/47 Fixes: https://github.com/status-im/trailofbits-audit/issues/46 Fixes: https://github.com/status-im/trailofbits-audit/issues/44 Fixes: https://github.com/status-im/security-reports/issues/13 Fixes: https://github.com/status-im/security-reports/issues/5 Fixes: https://github.com/status-im/status-react/issues/8995 This commits re-introduce rendering of markdown text and implent a few changes: 1) Parsing of the message content is now in status-go, this includes markdown, line-count, and rtl. Parsing is not nested, as there's some rendering degradation involved as we nest components, unclear exactly if it's react-native or clojure, haven't looked too deeply into it. 2) Emojii type messages are not parsed on the sending side, not the receiving one, using the appropriate content-type 3) Fixes a few issues with chat input rendering, currrently we use `chats/current-chat` subscription which is very heavy and should not be used unless necessary, and means that any change to chat will trigger a re-render, which caused re-rendering of input container on each received message. Also to note that input-container is fairly heavy to render, and it's rendered twice at each keypress on input. The inline markdow supported is: *italic* or _italic_ **bold** or __bold__ `inline code` http://test.com links \#status-tag The block markdown supported is: \# Headers ``` code blocks ``` > Quotereply The styling is very basic at the moment, but can be improved. Adding other markdown (photo,mentions) is straightforward and should come at little performance cost (unless the component to render is heavy, i.e a photo for example). There are some behavioral changes with this commit: 1) Links are only parsed if starting with http:// or https://, meaning that blah.com won't be parsed, nor www.test.com. This behavior is consistent with discord for example and allows faster parsing at little expense to ser experience imo. Fixes a few security issues as well. 2) Content is not anymore capped (regression), that's due to the fact that before we only rendered text and react-native allowed us easily to limit the number of lines, but adding markdown support means that this strategy is not viable anymore. Performance of rendering don't see to be very much impacted by this, I would re-introduce it if necessary, but I'd rather do that in a separate PR. Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2019-11-07 14:41:37 +01:00
(not group-chat)
(assoc :show-input? true)))))
(re-frame/reg-sub
:current-chat/one-to-one-chat?
:<- [:chats/current-raw-chat]
(fn [current-chat]
(not (or (chat.models/group-chat? current-chat)
(chat.models/public-chat? current-chat)))))
(re-frame/reg-sub
:chats/current-chat-message
:<- [:chats/current-chat]
(fn [{:keys [messages]} [_ message-id]]
(get messages message-id)))
(re-frame/reg-sub
:chats/current-chat-messages
:<- [:chats/current-chat]
(fn [{:keys [messages]}]
(or messages {})))
(re-frame/reg-sub
:chats/messages-gaps
:<- [:mailserver/gaps]
:<- [:chats/current-chat-id]
(fn [[gaps chat-id]]
(sort-by :from (vals (get gaps chat-id)))))
(re-frame/reg-sub
:chats/range
:<- [:mailserver/ranges]
:<- [:chats/current-chat-id]
(fn [[ranges chat-id]]
(get ranges chat-id)))
(re-frame/reg-sub
:chats/all-loaded?
:<- [:chats/current-chat]
(fn [chat]
(:all-loaded? chat)))
(re-frame/reg-sub
:chats/public?
:<- [:chats/current-chat]
(fn [chat]
(:public? chat)))
Fix message ordering and improve performance rec. messages This commit does a few things: ==== Ordering of messages ==== Change the ordering of messages from a mixture of timestamp/clock-value to use only clock-value. Datemarks are now not used for sorting anymore, which means that the order of messages is always causally related (not the case before, as we were breaking this property by sorting by datemark), but datemark calculation is unreliable (a reply to a message might have a timestamp < then the message that is replied to). So for timestamp calculation we naively group them ignoring "out-of-order timestamp" messages, although there's much to improve. It fixes an issue whereby the user would change their time and the message will be displayed in the past, although it is still possible to craft a message with a lower clock value and order it in the past (there's no way we can prevent this to some extent, but there are ways to mitigate, but outside the scope of this PR). ==== Performance of receiving messages ==== The app would freeze on pulling messages from a mailserver (100 or so). This is due to the JS Thread being hogged by CPU calculation, coupled with the fact that we always tried to process messages all in one go. This strategy can't scale, and given x is big enough (200,300,1000) the UI will freeze. Instead, each message is now processed separately, and we leave a gap between processing each message for the UI to respond to user input (otherwise the app freezes again). Pulling messages will be longer overall, but the app will be usuable while this happen (albeit it might slow down). Other strategies are possible (calculate off-db and do a big swap, avoiding many re-renders etc), but this is the reccommended strategy by re-frame author (Solving the CPU Hog problem), so sounds like a safe base point. The underlying data structure for holding messages was also changed, we used an immutable Red and Black Tree, same as a sorted map for clojure, but we use a js library as is twice as performing then clojure sorted map. We also don't sort messages again each time we receive them O(nlogn), but we insert them in order O(logn). Other data structures considered but discarded: 1) Plain vector, but performance prepending/insertion in the middle (both O(n)) were not great, as not really suited for these operations. 2) Linked list, appealing as append/prepend is O(1), while insertion is O(n). This is probably acceptable as messages tend to come in order (from the db, so adding N messages is O(n)), or the network (most of them prepends, or close to the head), while mailserver would not follow this path. An implementation of a linked list was built, which performed roughtly the same as a clojure sorted-map (although faster append/prepend), but not worth the complexity of having our own implementation. 3) Clojure sorted-map, probably the most versatile, performance were acceptable, but nowhere near the javascript implementation we decided on 4) Priority map, much slower than a sorted map (twice as slow) 5) Mutable sorted map, js implementation, (bintrees), not explored this very much, but from just a quick benchmark, performance were much worse that clojure immutable sorted map Given that each message is now processed separately, saving the chat / messages is also debounced to avoid spamming status-go with network requests. This is a temporary measure for now until that's done directly in status-go, without having to ping-pong with status-react. Next steps performance wise is to move stuff to status-go, parsing of transit, validation, which is heavy, at which point we can re-consider performance and how to handle messages. Fixes also an issue with the last message in the chat, we were using the last message in the chat list, which might not necessarely be the last message the chat has seen, in case messages were not loaded and a more recent message is the database (say you fetch historical messages for 1-to-1 A, you don't have any messages in 1-to-1 chat B loaded, you receive an historical message for chat B, it sets it as last message). Also use clj beans instead of js->clj for type conversion Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2019-10-24 16:23:20 +02:00
(re-frame/reg-sub
:chats/message-list
:<- [:chats/current-chat]
(fn [chat]
(:message-list chat)))
(re-frame/reg-sub
:chats/messages
:<- [:chats/current-chat]
(fn [chat]
(:messages chat)))
(defn hydrate-messages
"Pull data from messages and add it to the sorted list"
[message-list messages]
(keep #(if (= :message (% :type))
(when-let [message (messages (% :message-id))]
(merge message %))
%)
message-list))
(re-frame/reg-sub
:chats/current-chat-messages-stream
Fix message ordering and improve performance rec. messages This commit does a few things: ==== Ordering of messages ==== Change the ordering of messages from a mixture of timestamp/clock-value to use only clock-value. Datemarks are now not used for sorting anymore, which means that the order of messages is always causally related (not the case before, as we were breaking this property by sorting by datemark), but datemark calculation is unreliable (a reply to a message might have a timestamp < then the message that is replied to). So for timestamp calculation we naively group them ignoring "out-of-order timestamp" messages, although there's much to improve. It fixes an issue whereby the user would change their time and the message will be displayed in the past, although it is still possible to craft a message with a lower clock value and order it in the past (there's no way we can prevent this to some extent, but there are ways to mitigate, but outside the scope of this PR). ==== Performance of receiving messages ==== The app would freeze on pulling messages from a mailserver (100 or so). This is due to the JS Thread being hogged by CPU calculation, coupled with the fact that we always tried to process messages all in one go. This strategy can't scale, and given x is big enough (200,300,1000) the UI will freeze. Instead, each message is now processed separately, and we leave a gap between processing each message for the UI to respond to user input (otherwise the app freezes again). Pulling messages will be longer overall, but the app will be usuable while this happen (albeit it might slow down). Other strategies are possible (calculate off-db and do a big swap, avoiding many re-renders etc), but this is the reccommended strategy by re-frame author (Solving the CPU Hog problem), so sounds like a safe base point. The underlying data structure for holding messages was also changed, we used an immutable Red and Black Tree, same as a sorted map for clojure, but we use a js library as is twice as performing then clojure sorted map. We also don't sort messages again each time we receive them O(nlogn), but we insert them in order O(logn). Other data structures considered but discarded: 1) Plain vector, but performance prepending/insertion in the middle (both O(n)) were not great, as not really suited for these operations. 2) Linked list, appealing as append/prepend is O(1), while insertion is O(n). This is probably acceptable as messages tend to come in order (from the db, so adding N messages is O(n)), or the network (most of them prepends, or close to the head), while mailserver would not follow this path. An implementation of a linked list was built, which performed roughtly the same as a clojure sorted-map (although faster append/prepend), but not worth the complexity of having our own implementation. 3) Clojure sorted-map, probably the most versatile, performance were acceptable, but nowhere near the javascript implementation we decided on 4) Priority map, much slower than a sorted map (twice as slow) 5) Mutable sorted map, js implementation, (bintrees), not explored this very much, but from just a quick benchmark, performance were much worse that clojure immutable sorted map Given that each message is now processed separately, saving the chat / messages is also debounced to avoid spamming status-go with network requests. This is a temporary measure for now until that's done directly in status-go, without having to ping-pong with status-react. Next steps performance wise is to move stuff to status-go, parsing of transit, validation, which is heavy, at which point we can re-consider performance and how to handle messages. Fixes also an issue with the last message in the chat, we were using the last message in the chat list, which might not necessarely be the last message the chat has seen, in case messages were not loaded and a more recent message is the database (say you fetch historical messages for 1-to-1 A, you don't have any messages in 1-to-1 chat B loaded, you receive an historical message for chat B, it sets it as last message). Also use clj beans instead of js->clj for type conversion Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2019-10-24 16:23:20 +02:00
:<- [:chats/message-list]
:<- [:chats/messages]
:<- [:chats/messages-gaps]
:<- [:chats/range]
:<- [:chats/all-loaded?]
:<- [:chats/public?]
Fix message ordering and improve performance rec. messages This commit does a few things: ==== Ordering of messages ==== Change the ordering of messages from a mixture of timestamp/clock-value to use only clock-value. Datemarks are now not used for sorting anymore, which means that the order of messages is always causally related (not the case before, as we were breaking this property by sorting by datemark), but datemark calculation is unreliable (a reply to a message might have a timestamp < then the message that is replied to). So for timestamp calculation we naively group them ignoring "out-of-order timestamp" messages, although there's much to improve. It fixes an issue whereby the user would change their time and the message will be displayed in the past, although it is still possible to craft a message with a lower clock value and order it in the past (there's no way we can prevent this to some extent, but there are ways to mitigate, but outside the scope of this PR). ==== Performance of receiving messages ==== The app would freeze on pulling messages from a mailserver (100 or so). This is due to the JS Thread being hogged by CPU calculation, coupled with the fact that we always tried to process messages all in one go. This strategy can't scale, and given x is big enough (200,300,1000) the UI will freeze. Instead, each message is now processed separately, and we leave a gap between processing each message for the UI to respond to user input (otherwise the app freezes again). Pulling messages will be longer overall, but the app will be usuable while this happen (albeit it might slow down). Other strategies are possible (calculate off-db and do a big swap, avoiding many re-renders etc), but this is the reccommended strategy by re-frame author (Solving the CPU Hog problem), so sounds like a safe base point. The underlying data structure for holding messages was also changed, we used an immutable Red and Black Tree, same as a sorted map for clojure, but we use a js library as is twice as performing then clojure sorted map. We also don't sort messages again each time we receive them O(nlogn), but we insert them in order O(logn). Other data structures considered but discarded: 1) Plain vector, but performance prepending/insertion in the middle (both O(n)) were not great, as not really suited for these operations. 2) Linked list, appealing as append/prepend is O(1), while insertion is O(n). This is probably acceptable as messages tend to come in order (from the db, so adding N messages is O(n)), or the network (most of them prepends, or close to the head), while mailserver would not follow this path. An implementation of a linked list was built, which performed roughtly the same as a clojure sorted-map (although faster append/prepend), but not worth the complexity of having our own implementation. 3) Clojure sorted-map, probably the most versatile, performance were acceptable, but nowhere near the javascript implementation we decided on 4) Priority map, much slower than a sorted map (twice as slow) 5) Mutable sorted map, js implementation, (bintrees), not explored this very much, but from just a quick benchmark, performance were much worse that clojure immutable sorted map Given that each message is now processed separately, saving the chat / messages is also debounced to avoid spamming status-go with network requests. This is a temporary measure for now until that's done directly in status-go, without having to ping-pong with status-react. Next steps performance wise is to move stuff to status-go, parsing of transit, validation, which is heavy, at which point we can re-consider performance and how to handle messages. Fixes also an issue with the last message in the chat, we were using the last message in the chat list, which might not necessarely be the last message the chat has seen, in case messages were not loaded and a more recent message is the database (say you fetch historical messages for 1-to-1 A, you don't have any messages in 1-to-1 chat B loaded, you receive an historical message for chat B, it sets it as last message). Also use clj beans instead of js->clj for type conversion Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2019-10-24 16:23:20 +02:00
(fn [[message-list messages messages-gaps range all-loaded? public?]]
(-> (models.message-list/->seq message-list)
Fix message ordering and improve performance rec. messages This commit does a few things: ==== Ordering of messages ==== Change the ordering of messages from a mixture of timestamp/clock-value to use only clock-value. Datemarks are now not used for sorting anymore, which means that the order of messages is always causally related (not the case before, as we were breaking this property by sorting by datemark), but datemark calculation is unreliable (a reply to a message might have a timestamp < then the message that is replied to). So for timestamp calculation we naively group them ignoring "out-of-order timestamp" messages, although there's much to improve. It fixes an issue whereby the user would change their time and the message will be displayed in the past, although it is still possible to craft a message with a lower clock value and order it in the past (there's no way we can prevent this to some extent, but there are ways to mitigate, but outside the scope of this PR). ==== Performance of receiving messages ==== The app would freeze on pulling messages from a mailserver (100 or so). This is due to the JS Thread being hogged by CPU calculation, coupled with the fact that we always tried to process messages all in one go. This strategy can't scale, and given x is big enough (200,300,1000) the UI will freeze. Instead, each message is now processed separately, and we leave a gap between processing each message for the UI to respond to user input (otherwise the app freezes again). Pulling messages will be longer overall, but the app will be usuable while this happen (albeit it might slow down). Other strategies are possible (calculate off-db and do a big swap, avoiding many re-renders etc), but this is the reccommended strategy by re-frame author (Solving the CPU Hog problem), so sounds like a safe base point. The underlying data structure for holding messages was also changed, we used an immutable Red and Black Tree, same as a sorted map for clojure, but we use a js library as is twice as performing then clojure sorted map. We also don't sort messages again each time we receive them O(nlogn), but we insert them in order O(logn). Other data structures considered but discarded: 1) Plain vector, but performance prepending/insertion in the middle (both O(n)) were not great, as not really suited for these operations. 2) Linked list, appealing as append/prepend is O(1), while insertion is O(n). This is probably acceptable as messages tend to come in order (from the db, so adding N messages is O(n)), or the network (most of them prepends, or close to the head), while mailserver would not follow this path. An implementation of a linked list was built, which performed roughtly the same as a clojure sorted-map (although faster append/prepend), but not worth the complexity of having our own implementation. 3) Clojure sorted-map, probably the most versatile, performance were acceptable, but nowhere near the javascript implementation we decided on 4) Priority map, much slower than a sorted map (twice as slow) 5) Mutable sorted map, js implementation, (bintrees), not explored this very much, but from just a quick benchmark, performance were much worse that clojure immutable sorted map Given that each message is now processed separately, saving the chat / messages is also debounced to avoid spamming status-go with network requests. This is a temporary measure for now until that's done directly in status-go, without having to ping-pong with status-react. Next steps performance wise is to move stuff to status-go, parsing of transit, validation, which is heavy, at which point we can re-consider performance and how to handle messages. Fixes also an issue with the last message in the chat, we were using the last message in the chat list, which might not necessarely be the last message the chat has seen, in case messages were not loaded and a more recent message is the database (say you fetch historical messages for 1-to-1 A, you don't have any messages in 1-to-1 chat B loaded, you receive an historical message for chat B, it sets it as last message). Also use clj beans instead of js->clj for type conversion Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2019-10-24 16:23:20 +02:00
(chat.db/add-datemarks)
(hydrate-messages messages)
(chat.db/add-gaps messages-gaps range all-loaded? public?))))
(re-frame/reg-sub
:chats/current-chat-intro-status
:<- [:chats/current-chat]
:<- [:chats/current-chat-messages]
(fn [[{:keys [might-have-join-time-messages?]} messages]]
(if might-have-join-time-messages?
:loading
(if (empty? messages)
:empty
:messages))))
(re-frame/reg-sub
:chats/unviewed-messages-count
(fn [[_ chat-id]]
(re-frame/subscribe [:chats/chat chat-id]))
(fn [{:keys [unviewed-messages-count]}]
unviewed-messages-count))
(re-frame/reg-sub
:chats/photo-path
:<- [:contacts/contacts]
:<- [:multiaccount]
(fn [[contacts multiaccount] [_ id]]
(multiaccounts/displayed-photo (or (contacts id)
(when (= id (:public-key multiaccount))
multiaccount)
(contact.db/public-key->new-contact id)))))
(re-frame/reg-sub
:chats/unread-messages-number
:<- [:chats/active-chats]
(fn [chats _]
(apply + (map :unviewed-messages-count (vals chats)))))
(re-frame/reg-sub
:chats/cooldown-enabled?
:<- [:chats/current-chat]
:<- [::cooldown-enabled?]
(fn [[{:keys [public?]} cooldown-enabled?]]
(and public?
cooldown-enabled?)))
(re-frame/reg-sub
:chats/reply-message
:<- [:chats/current-chat]
(fn [{:keys [metadata messages]}]
(get messages (get-in metadata [:responding-to-message :message-id]))))
(re-frame/reg-sub
:public-chat.new/topic-error-message
:<- [:public-group-topic]
(fn [topic]
(when-not (or (empty? topic)
(db/valid-topic? topic))
(i18n/label :topic-name-error))))
(defn filter-selected-contacts
[selected-contacts contacts]
(filter #(contact.db/added? (contacts %)) selected-contacts))
(re-frame/reg-sub
:selected-contacts-count
:<- [:group/selected-contacts]
:<- [:contacts/contacts]
(fn [[selected-contacts contacts]]
(count (filter-selected-contacts selected-contacts contacts))))
(re-frame/reg-sub
:selected-participants-count
:<- [:selected-participants]
(fn [selected-participants]
(count selected-participants)))
(defn filter-contacts [selected-contacts active-contacts]
(filter #(selected-contacts (:public-key %)) active-contacts))
(re-frame/reg-sub
:selected-group-contacts
:<- [:group/selected-contacts]
:<- [:contacts/active]
(fn [[selected-contacts active-contacts]]
(filter-contacts selected-contacts active-contacts)))
(re-frame/reg-sub
:chats/transaction-status
;;TODO address here for transactions
:<- [:wallet/transactions]
:<- [:ethereum/current-block]
(fn [[transactions current-block] [_ hash]]
(when-let [transaction (get transactions hash)]
{:exists? true
:confirmed?
(-> transaction
(wallet.db/get-confirmations current-block)
(>= transactions/confirmations-count-threshold))})))
;;BOOTNODES ============================================================================================================
(re-frame/reg-sub
:custom-bootnodes/enabled?
:<- [:multiaccount]
2019-08-23 16:11:23 +02:00
:<- [:networks/current-network]
(fn [[{:keys [custom-bootnodes-enabled?]} current-network]]
(get custom-bootnodes-enabled? current-network)))
(re-frame/reg-sub
:custom-bootnodes/network-bootnodes
:<- [:multiaccount]
2019-08-23 16:11:23 +02:00
:<- [:networks/current-network]
(fn [[multiaccount current-network]]
(get-in multiaccount [:custom-bootnodes current-network])))
(re-frame/reg-sub
:get-manage-bootnode
:<- [:bootnodes/manage]
(fn [manage]
manage))
(re-frame/reg-sub
:manage-bootnode-validation-errors
:<- [:get-manage-bootnode]
(fn [manage]
(set (keep
(fn [[k {:keys [error]}]]
(when error k))
manage))))
;;BROWSER ==============================================================================================================
(re-frame/reg-sub
:browser/browsers
:<- [:browsers]
(fn [browsers]
(reduce (fn [acc [k browser]]
(update acc k assoc :url (browser/get-current-url browser)))
browsers
browsers)))
(re-frame/reg-sub
:browser/browsers-vals
:<- [:browser/browsers]
(fn [browsers]
(sort-by :timestamp > (vals browsers))))
(re-frame/reg-sub
:get-current-browser
:<- [:browser/options]
:<- [:browser/browsers]
(fn [[options browsers]]
(let [browser (get browsers (:browser-id options))]
(assoc browser :secure? (browser/secure? browser options)))))
;;STICKERS =============================================================================================================
(re-frame/reg-sub
:stickers/installed-packs-vals
:<- [:stickers/installed-packs]
(fn [packs]
(vals packs)))
(re-frame/reg-sub
:stickers/all-packs
:<- [:stickers/packs]
:<- [:stickers/installed-packs]
:<- [:stickers/packs-owned]
:<- [:stickers/packs-pending]
(fn [[packs installed owned pending]]
(map (fn [{:keys [id] :as pack}]
(cond-> pack
(get installed id) (assoc :installed true)
(get owned id) (assoc :owned true)
(get pending id) (assoc :pending true)))
(vals packs))))
(re-frame/reg-sub
:stickers/get-current-pack
:<- [:get-screen-params]
:<- [:stickers/all-packs]
(fn [[{:keys [id]} packs]]
(first (filter #(= (:id %) id) packs))))
(defn find-pack-id-for-hash [sticker-uri packs]
(some (fn [{:keys [stickers id]}]
(when (some #(= sticker-uri (:hash %)) stickers)
id))
packs))
(re-frame/reg-sub
:stickers/recent
:<- [:multiaccount]
:<- [:stickers/installed-packs-vals]
2019-08-01 22:11:59 +02:00
(fn [[{:keys [:stickers/recent-stickers]} packs]]
(map (fn [hash] {:hash hash :pack (find-pack-id-for-hash hash packs)}) recent-stickers)))
(re-frame/reg-sub
:home-items
:<- [:chats/active-chats]
:<- [:search/home-filter]
:<- [:search/filtered-chats]
(fn [[chats search-filter filtered-chats]]
(if (or (nil? search-filter)
(and platform/desktop? (empty? search-filter)))
{:all-home-items
(sort-by #(-> % second :timestamp) > chats)}
{:search-filter search-filter
:chats filtered-chats})))
;;PAIRING ==============================================================================================================
(re-frame/reg-sub
:pairing/installations
:<- [:get-pairing-installations]
:<- [:pairing/installation-id]
(fn [[installations installation-id]]
(->> installations
vals
(pairing/sort-installations installation-id))))
(re-frame/reg-sub
:pairing/installation-id
:<- [:multiaccount]
:installation-id)
(re-frame/reg-sub
:pairing/installation-name
:<- [:multiaccount]
(fn [multiaccount] (:installation-name multiaccount)))
;;PROFILE ==============================================================================================================
(re-frame/reg-sub
:get-profile-unread-messages-number
:<- [:multiaccount]
(fn [{:keys [mnemonic]}]
(if mnemonic 1 0)))
;;WALLET ==============================================================================================================
(re-frame/reg-sub
:balance
:<- [:wallet]
(fn [wallet [_ address]]
(get-in wallet [:accounts address :balance])))
(re-frame/reg-sub
:balance-default
:<- [:wallet]
:<- [:multiaccount/accounts]
(fn [[wallet accounts]]
(get-in wallet [:accounts (:address (ethereum/get-default-account accounts)) :balance])))
(re-frame/reg-sub
:balances
:<- [:wallet]
(fn [wallet]
(map :balance (vals (:accounts wallet)))))
(re-frame/reg-sub
:empty-balances?
:<- [:balances]
(fn [balances]
(every?
(fn [balance]
(every?
(fn [asset]
(or (nil? asset) (.isZero asset)))
(vals balance)))
balances)))
(re-frame/reg-sub
:price
:<- [:prices]
(fn [prices [_ fsym tsym]]
(get-in prices [fsym tsym :price])))
(re-frame/reg-sub
:last-day
:<- [:prices]
(fn [prices [_ fsym tsym]]
(get-in prices [fsym tsym :last-day])))
(re-frame/reg-sub
:wallet.settings/currency
:<- [:multiaccount]
2019-05-16 23:50:03 +02:00
(fn [settings]
(or (get settings :currency) :usd)))
2019-05-16 23:50:03 +02:00
(defn- get-balance-total-value
[balance prices currency token->decimals]
(reduce-kv (fn [acc symbol value]
(if-let [price (get-in prices [symbol currency :price])]
(+ acc (or (some-> (money/internal->formatted value symbol (token->decimals symbol))
(money/crypto->fiat price)
.toNumber)
0))
acc)) 0 balance))
(re-frame/reg-sub
:portfolio-value
:<- [:balances]
:<- [:prices]
:<- [:wallet/currency]
:<- [:ethereum/chain-keyword]
:<- [:wallet/all-tokens]
(fn [[balances prices currency chain all-tokens]]
(if (and balances prices)
(let [assets (tokens/tokens-for all-tokens chain)
token->decimals (into {} (map #(vector (:symbol %) (:decimals %)) assets))
currency-key (-> currency :code keyword)
balance-total-value (apply + (map #(get-balance-total-value % prices currency-key token->decimals) balances))]
(if (pos? balance-total-value)
(-> balance-total-value
(money/with-precision 2)
str
(i18n/format-currency (:code currency) false))
"0"))
"...")))
(re-frame/reg-sub
:account-portfolio-value
(fn [[_ address] _]
[(re-frame/subscribe [:balance address])
(re-frame/subscribe [:prices])
(re-frame/subscribe [:wallet/currency])
(re-frame/subscribe [:ethereum/chain-keyword])
(re-frame/subscribe [:wallet/all-tokens])])
(fn [[balance prices currency chain all-tokens]]
(if (and balance prices)
(let [assets (tokens/tokens-for all-tokens chain)
token->decimals (into {} (map #(vector (:symbol %) (:decimals %)) assets))
currency-key (-> currency :code keyword)
balance-total-value (get-balance-total-value balance prices currency-key token->decimals)]
(if (pos? balance-total-value)
(-> balance-total-value
(money/with-precision 2)
str
(i18n/format-currency (:code currency) false))
"0"))
"...")))
(re-frame/reg-sub
:wallet/chain-tokens
:<- [:wallet/all-tokens]
:<- [:ethereum/chain-keyword]
(fn [[all-tokens chain]]
(get all-tokens chain)))
(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
:wallet/fetching-tx-history?
:<- [:wallet]
(fn [wallet [_ address]]
(get-in wallet [:fetching address :history?])))
(re-frame/reg-sub
:wallet/fetching-recent-tx-history?
:<- [:wallet]
(fn [wallet [_ address]]
(get-in wallet [:fetching address :recent?])))
(re-frame/reg-sub
:wallet/tx-history-fetched?
:<- [:wallet]
(fn [wallet [_ address]]
(get-in wallet [:fetching address :all-fetched?])))
(re-frame/reg-sub
:wallet/etherscan-link
(fn [db [_ address]]
(let [network (:networks/current-network db)
link (get-in constants/default-networks-by-id
[network :etherscan-link])]
(when link
(str link address)))))
(re-frame/reg-sub
:wallet/error-message
:<- [:wallet]
(fn [wallet]
(or (get-in wallet [:errors :balance-update])
(get-in wallet [:errors :prices-update]))))
(re-frame/reg-sub
:wallet/visible-tokens-symbols
:<- [:ethereum/chain-keyword]
:<- [:multiaccount]
(fn [[chain current-multiaccount]]
(get-in current-multiaccount [:wallet/visible-tokens chain])))
(re-frame/reg-sub
:wallet/visible-assets
:<- [:ethereum/chain-keyword]
:<- [:wallet/visible-tokens-symbols]
:<- [:wallet/all-tokens]
(fn [[chain visible-tokens-symbols all-tokens]]
(conj (filter #(contains? visible-tokens-symbols (:symbol %))
(tokens/sorted-tokens-for all-tokens chain))
(tokens/native-currency chain))))
(re-frame/reg-sub
:wallet/visible-assets-with-amount
(fn [[_ address] _]
[(re-frame/subscribe [:balance address])
(re-frame/subscribe [:wallet/visible-assets])])
(fn [[balance visible-assets]]
(map #(assoc % :amount (get balance (:symbol %))) visible-assets)))
(defn update-value [prices currency]
(fn [{:keys [symbol decimals amount] :as token}]
(let [price (get-in prices [symbol (-> currency :code keyword) :price])]
(assoc token
:price price
:value (when (and amount price)
(-> (money/internal->formatted amount 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
(fn [[_ address] _]
[(re-frame/subscribe [:wallet/visible-assets-with-amount address])
(re-frame/subscribe [:prices])
(re-frame/subscribe [:wallet/currency])])
(fn [[assets prices currency]]
(let [{:keys [tokens nfts]} (group-by #(if (:nft? %) :nfts :tokens) assets)
tokens-with-values (map (update-value prices currency) tokens)]
{:tokens tokens-with-values
:nfts nfts})))
(defn get-asset-amount [balances sym]
(reduce #(if-let [bl (get %2 sym)]
(.plus %1 bl)
%1)
(money/bignumber 0)
balances))
(re-frame/reg-sub
:wallet/all-visible-assets-with-amount
:<- [:balances]
:<- [:wallet/visible-assets]
(fn [[balances visible-assets]]
(map #(assoc % :amount (get-asset-amount balances (:symbol %))) visible-assets)))
(re-frame/reg-sub
:wallet/all-visible-assets-with-values
:<- [:wallet/all-visible-assets-with-amount]
:<- [:prices]
:<- [:wallet/currency]
(fn [[assets prices currency]]
(let [{:keys [tokens nfts]} (group-by #(if (:nft? %) :nfts :tokens) assets)
tokens-with-values (map (update-value prices currency) tokens)]
{:tokens tokens-with-values
:nfts nfts})))
(re-frame/reg-sub
:wallet/transferrable-assets-with-amount
(fn [[_ address]]
(re-frame/subscribe [:wallet/visible-assets-with-amount address]))
(fn [all-assets]
(filter #(not (:nft? %)) all-assets)))
(re-frame/reg-sub
:wallet/currency
:<- [:wallet.settings/currency]
(fn [currency-id]
(get constants/currencies currency-id)))
;;WALLET TRANSACTIONS ==================================================================================================
(re-frame/reg-sub
:wallet/accounts
:<- [:wallet]
(fn [wallet]
(get wallet :accounts)))
(re-frame/reg-sub
:wallet/account-by-transaction-hash
:<- [:wallet/accounts]
(fn [accounts [_ hash]]
(some (fn [[address account]]
(when-let [transaction (get-in account [:transactions hash])]
(assoc transaction :address address)))
accounts)))
(re-frame/reg-sub
:wallet/transactions
:<- [:wallet]
(fn [wallet [_ address]]
(get-in wallet [:accounts address :transactions])))
2019-05-16 23:50:03 +02:00
(re-frame/reg-sub
:wallet/filters
:<- [:wallet]
(fn [wallet]
(get wallet :filters)))
2019-05-16 23:50:03 +02:00
(defn enrich-transaction
[{:keys [type to from value token] :as transaction}
contacts native-currency]
(let [[contact-address key-contact key-wallet]
(if (= type :inbound)
[from :from-contact :to-wallet]
[to :to-contact :from-wallet])
wallet (i18n/label :main-wallet)
contact (get contacts contact-address)
{:keys [symbol-display symbol decimals] :as asset}
(or token native-currency)
amount-text (if value
(wallet.utils/format-amount value decimals)
"...")
currency-text (when asset
(clojure.core/name (or symbol-display symbol)))]
(cond-> transaction
contact (assoc key-contact (:name contact))
:always (assoc key-wallet wallet
:amount-text amount-text
:currency-text currency-text))))
(re-frame/reg-sub
:wallet.transactions/transactions
(fn [[_ address] _]
[(re-frame/subscribe [:wallet/transactions address])
(re-frame/subscribe [:contacts/contacts-by-address])
(re-frame/subscribe [:ethereum/native-currency])])
(fn [[transactions contacts native-currency]]
(reduce (fn [acc [hash transaction]]
(assoc acc
hash
(enrich-transaction transaction contacts native-currency))) ;;TODO this doesn't look good for performance, we need to calculate this only once for each transaction
{}
transactions)))
(re-frame/reg-sub
:wallet.transactions/all-filters?
:<- [:wallet/filters]
(fn [filters]
(= wallet.db/default-wallet-filters
filters)))
(def filters-labels
{:inbound (i18n/label :t/incoming)
:outbound (i18n/label :t/outgoing)
:pending (i18n/label :t/pending)
:failed (i18n/label :t/failed)})
(re-frame/reg-sub
:wallet.transactions/filters
:<- [:wallet/filters]
(fn [filters]
(map (fn [id]
(let [checked? (filters id)]
{:id id
:label (filters-labels id)
:checked? checked?
:on-touch #(if checked?
(re-frame/dispatch [:wallet.transactions/remove-filter id])
(re-frame/dispatch [:wallet.transactions/add-filter id]))}))
wallet.db/default-wallet-filters)))
(re-frame/reg-sub
:wallet.transactions.filters/screen
:<- [:wallet.transactions/filters]
:<- [:wallet.transactions/all-filters?]
(fn [[filters all-filters?]]
{:all-filters? all-filters?
:filters filters
:on-touch-select-all (when-not all-filters?
#(re-frame/dispatch
[:wallet.transactions/add-all-filters]))}))
(defn- enrich-transaction-for-list
[filters
{:keys [type from-contact from to-contact to hash timestamp] :as transaction}
address]
(when (filters type)
(assoc (case type
:inbound
(assoc transaction
:label (i18n/label :t/from)
:contact-accessibility-label :sender-text
:address-accessibility-label :sender-address-text
:contact from-contact
:address from)
(assoc transaction
:label (i18n/label :t/to)
:contact-accessibility-label :recipient-name-text
:address-accessibility-label :recipient-address-text
:contact to-contact
:address to))
:time-formatted (datetime/timestamp->time timestamp)
:on-touch-fn #(re-frame/dispatch [:wallet.ui/show-transaction-details hash address]))))
(defn- group-transactions-by-date
[transactions]
(->> transactions
(group-by #(datetime/timestamp->date-key (:timestamp %)))
(sort-by key >)
(map (fn [[date-key transactions]]
{:title (datetime/timestamp->mini-date (:timestamp (first transactions)))
:key date-key
:data (sort-by :timestamp > transactions)}))))
(re-frame/reg-sub
:wallet.transactions.history/screen
(fn [[_ address] _]
[(re-frame/subscribe [:wallet.transactions/transactions address])
(re-frame/subscribe [:wallet/filters])
(re-frame/subscribe [:wallet.transactions/all-filters?])])
(fn [[transactions filters all-filters?] [_ address]]
{:all-filters? all-filters?
:transaction-history-sections
(->> transactions
vals
(keep #(enrich-transaction-for-list filters % address))
(group-transactions-by-date))}))
(re-frame/reg-sub
2019-05-16 23:50:03 +02:00
:wallet.transactions.details/current-transaction
(fn [[_ hash address] _]
[(re-frame/subscribe [:wallet.transactions/transactions address])
(re-frame/subscribe [:ethereum/native-currency])
(re-frame/subscribe [:ethereum/chain-keyword])])
(fn [[transactions native-currency chain-keyword] [_ hash _]]
2019-05-16 23:50:03 +02:00
(let [{:keys [gas-used gas-price hash timestamp type token value]
:as transaction}
(get transactions hash)
native-currency-text (name (or (:symbol-display native-currency)
(:symbol native-currency)))]
(when transaction
(merge transaction
{:gas-price-eth (if gas-price
(money/wei->str :eth
gas-price
native-currency-text)
"-")
:gas-price-gwei (if gas-price
(money/wei->str :gwei
gas-price)
"-")
:date (datetime/timestamp->long-date timestamp)}
(if (= type :unsigned)
{:block (i18n/label :not-applicable)
:cost (i18n/label :not-applicable)
:gas-limit (i18n/label :not-applicable)
:gas-used (i18n/label :not-applicable)
:nonce (i18n/label :not-applicable)
:hash (i18n/label :not-applicable)}
{:cost (when gas-used
(money/wei->str :eth
(money/fee-value gas-used gas-price)
native-currency-text))
:url (transactions/get-transaction-details-url
chain-keyword
hash)}))))))
2019-05-16 23:50:03 +02:00
(re-frame/reg-sub
:wallet.transactions.details/screen
(fn [[_ hash address] _]
[(re-frame/subscribe [:wallet.transactions.details/current-transaction hash address])
(re-frame/subscribe [:ethereum/current-block])])
(fn [[transaction current-block]]
(let [confirmations (wallet.db/get-confirmations transaction
current-block)]
2019-05-16 23:50:03 +02:00
(assoc transaction
:confirmations confirmations
:confirmations-progress
(if (>= confirmations transactions/confirmations-count-threshold)
100
(* 100 (/ confirmations transactions/confirmations-count-threshold)))))))
;;WALLET SEND ==========================================================================================================
(re-frame/reg-sub
::send-transaction
:<- [:wallet]
(fn [wallet]
(:send-transaction wallet)))
(re-frame/reg-sub
:wallet.send/symbol
:<- [::send-transaction]
(fn [send-transaction]
(:symbol send-transaction)))
(re-frame/reg-sub
:wallet.send/camera-flashlight
:<- [::send-transaction]
(fn [send-transaction]
(:camera-flashlight send-transaction)))
(re-frame/reg-sub
:wallet/settings
:<- [:wallet]
(fn [{:keys [settings]}]
(reduce-kv #(conj %1 %3) [] settings)))
(re-frame/reg-sub
:wallet.request/transaction
:<- [:wallet]
:request-transaction)
(re-frame/reg-sub
:screen-collectibles
:<- [:collectibles]
:<- [:get-screen-params]
(fn [[collectibles {:keys [symbol]}]]
(when-let [v (get collectibles symbol)]
(mapv #(assoc (second %) :id (first %)) v))))
;;UI ==============================================================================================================
;;TODO this subscription looks super weird huge and with dispatches?
(re-frame/reg-sub
:connectivity/status-properties
:<- [:network-status]
:<- [:disconnected?]
:<- [:mailserver/connecting?]
:<- [:mailserver/connection-error?]
:<- [:mailserver/request-error?]
:<- [:mailserver/fetching?]
:<- [:network/type]
:<- [:multiaccount]
(fn [[network-status disconnected? mailserver-connecting? mailserver-connection-error?
mailserver-request-error? mailserver-fetching? network-type multiaccount]]
(let [error-label (cond
(= network-status :offline)
:t/offline
mailserver-connecting?
:t/connecting
mailserver-connection-error?
:t/mailserver-reconnect
mailserver-request-error?
:t/mailserver-request-error-status
(and (mobile-network-utils/cellular? network-type)
(not (:syncing-on-mobile-network? multiaccount)))
:mobile-network
disconnected?
:t/offline
:else nil)
connected? (and (nil? error-label) (not= :mobile-network error-label))]
{:message (or error-label :t/connected)
:connected? connected?
:connecting? (= error-label :t/connecting)
:loading-indicator? (and mailserver-fetching? connected?)
:on-press-event (cond
mailserver-connection-error?
:mailserver.ui/reconnect-mailserver-pressed
mailserver-request-error?
:mailserver.ui/request-error-pressed
(= :mobile-network error-label)
:mobile-network/show-offline-sheet)})))
;;CONTACT ==============================================================================================================
(re-frame/reg-sub
::query-current-chat-contacts
:<- [:chats/current-chat]
:<- [:contacts/contacts]
(fn [[chat contacts] [_ query-fn]]
(contact.db/query-chat-contacts chat contacts query-fn)))
(re-frame/reg-sub
:contacts/contacts
:<- [::contacts]
(fn [contacts]
(contact.db/enrich-contacts contacts)))
(re-frame/reg-sub
:contacts/active
:<- [:contacts/contacts]
(fn [contacts]
(contact.db/get-active-contacts contacts)))
(re-frame/reg-sub
:contacts/active-count
:<- [:contacts/active]
(fn [active-contacts]
(count active-contacts)))
(re-frame/reg-sub
:contacts/blocked
:<- [:contacts/contacts]
(fn [contacts]
(->> contacts
(filter (fn [[_ contact]]
(contact.db/blocked? contact)))
(contact.db/sort-contacts))))
(re-frame/reg-sub
:contacts/blocked-count
:<- [:contacts/blocked]
(fn [blocked-contacts]
(count blocked-contacts)))
(re-frame/reg-sub
:contacts/current-contact
:<- [:contacts/contacts]
:<- [:contacts/current-contact-identity]
(fn [[contacts identity]]
(or (contacts identity)
(-> identity
contact.db/public-key->new-contact
contact.db/enrich-contact))))
(re-frame/reg-sub
:contacts/contact-name-by-identity
:<- [:contacts/contacts]
:<- [:multiaccount]
(fn [[contacts current-multiaccount] [_ identity]]
(let [me? (= (:public-key current-multiaccount) identity)]
(if me?
Fix message ordering and improve performance rec. messages This commit does a few things: ==== Ordering of messages ==== Change the ordering of messages from a mixture of timestamp/clock-value to use only clock-value. Datemarks are now not used for sorting anymore, which means that the order of messages is always causally related (not the case before, as we were breaking this property by sorting by datemark), but datemark calculation is unreliable (a reply to a message might have a timestamp < then the message that is replied to). So for timestamp calculation we naively group them ignoring "out-of-order timestamp" messages, although there's much to improve. It fixes an issue whereby the user would change their time and the message will be displayed in the past, although it is still possible to craft a message with a lower clock value and order it in the past (there's no way we can prevent this to some extent, but there are ways to mitigate, but outside the scope of this PR). ==== Performance of receiving messages ==== The app would freeze on pulling messages from a mailserver (100 or so). This is due to the JS Thread being hogged by CPU calculation, coupled with the fact that we always tried to process messages all in one go. This strategy can't scale, and given x is big enough (200,300,1000) the UI will freeze. Instead, each message is now processed separately, and we leave a gap between processing each message for the UI to respond to user input (otherwise the app freezes again). Pulling messages will be longer overall, but the app will be usuable while this happen (albeit it might slow down). Other strategies are possible (calculate off-db and do a big swap, avoiding many re-renders etc), but this is the reccommended strategy by re-frame author (Solving the CPU Hog problem), so sounds like a safe base point. The underlying data structure for holding messages was also changed, we used an immutable Red and Black Tree, same as a sorted map for clojure, but we use a js library as is twice as performing then clojure sorted map. We also don't sort messages again each time we receive them O(nlogn), but we insert them in order O(logn). Other data structures considered but discarded: 1) Plain vector, but performance prepending/insertion in the middle (both O(n)) were not great, as not really suited for these operations. 2) Linked list, appealing as append/prepend is O(1), while insertion is O(n). This is probably acceptable as messages tend to come in order (from the db, so adding N messages is O(n)), or the network (most of them prepends, or close to the head), while mailserver would not follow this path. An implementation of a linked list was built, which performed roughtly the same as a clojure sorted-map (although faster append/prepend), but not worth the complexity of having our own implementation. 3) Clojure sorted-map, probably the most versatile, performance were acceptable, but nowhere near the javascript implementation we decided on 4) Priority map, much slower than a sorted map (twice as slow) 5) Mutable sorted map, js implementation, (bintrees), not explored this very much, but from just a quick benchmark, performance were much worse that clojure immutable sorted map Given that each message is now processed separately, saving the chat / messages is also debounced to avoid spamming status-go with network requests. This is a temporary measure for now until that's done directly in status-go, without having to ping-pong with status-react. Next steps performance wise is to move stuff to status-go, parsing of transit, validation, which is heavy, at which point we can re-consider performance and how to handle messages. Fixes also an issue with the last message in the chat, we were using the last message in the chat list, which might not necessarely be the last message the chat has seen, in case messages were not loaded and a more recent message is the database (say you fetch historical messages for 1-to-1 A, you don't have any messages in 1-to-1 chat B loaded, you receive an historical message for chat B, it sets it as last message). Also use clj beans instead of js->clj for type conversion Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2019-10-24 16:23:20 +02:00
{:ens-name (:preferred-name current-multiaccount)
:alias (gfycat/generate-gfy identity)}
(let [contact (or (contacts identity)
(contact.db/public-key->new-contact identity))]
{:ens-name (when (:ens-verified contact)
(:name contact))
:alias (or (:alias contact)
(gfycat/generate-gfy identity))})))))
Fix message ordering and improve performance rec. messages This commit does a few things: ==== Ordering of messages ==== Change the ordering of messages from a mixture of timestamp/clock-value to use only clock-value. Datemarks are now not used for sorting anymore, which means that the order of messages is always causally related (not the case before, as we were breaking this property by sorting by datemark), but datemark calculation is unreliable (a reply to a message might have a timestamp < then the message that is replied to). So for timestamp calculation we naively group them ignoring "out-of-order timestamp" messages, although there's much to improve. It fixes an issue whereby the user would change their time and the message will be displayed in the past, although it is still possible to craft a message with a lower clock value and order it in the past (there's no way we can prevent this to some extent, but there are ways to mitigate, but outside the scope of this PR). ==== Performance of receiving messages ==== The app would freeze on pulling messages from a mailserver (100 or so). This is due to the JS Thread being hogged by CPU calculation, coupled with the fact that we always tried to process messages all in one go. This strategy can't scale, and given x is big enough (200,300,1000) the UI will freeze. Instead, each message is now processed separately, and we leave a gap between processing each message for the UI to respond to user input (otherwise the app freezes again). Pulling messages will be longer overall, but the app will be usuable while this happen (albeit it might slow down). Other strategies are possible (calculate off-db and do a big swap, avoiding many re-renders etc), but this is the reccommended strategy by re-frame author (Solving the CPU Hog problem), so sounds like a safe base point. The underlying data structure for holding messages was also changed, we used an immutable Red and Black Tree, same as a sorted map for clojure, but we use a js library as is twice as performing then clojure sorted map. We also don't sort messages again each time we receive them O(nlogn), but we insert them in order O(logn). Other data structures considered but discarded: 1) Plain vector, but performance prepending/insertion in the middle (both O(n)) were not great, as not really suited for these operations. 2) Linked list, appealing as append/prepend is O(1), while insertion is O(n). This is probably acceptable as messages tend to come in order (from the db, so adding N messages is O(n)), or the network (most of them prepends, or close to the head), while mailserver would not follow this path. An implementation of a linked list was built, which performed roughtly the same as a clojure sorted-map (although faster append/prepend), but not worth the complexity of having our own implementation. 3) Clojure sorted-map, probably the most versatile, performance were acceptable, but nowhere near the javascript implementation we decided on 4) Priority map, much slower than a sorted map (twice as slow) 5) Mutable sorted map, js implementation, (bintrees), not explored this very much, but from just a quick benchmark, performance were much worse that clojure immutable sorted map Given that each message is now processed separately, saving the chat / messages is also debounced to avoid spamming status-go with network requests. This is a temporary measure for now until that's done directly in status-go, without having to ping-pong with status-react. Next steps performance wise is to move stuff to status-go, parsing of transit, validation, which is heavy, at which point we can re-consider performance and how to handle messages. Fixes also an issue with the last message in the chat, we were using the last message in the chat list, which might not necessarely be the last message the chat has seen, in case messages were not loaded and a more recent message is the database (say you fetch historical messages for 1-to-1 A, you don't have any messages in 1-to-1 chat B loaded, you receive an historical message for chat B, it sets it as last message). Also use clj beans instead of js->clj for type conversion Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2019-10-24 16:23:20 +02:00
(re-frame/reg-sub
:messages/quote-info
:<- [:chats/messages]
:<- [:contacts/contacts]
:<- [:multiaccount]
(fn [[messages contacts current-multiaccount] [_ message-id]]
(when-let [message (get messages message-id)]
(let [identity (:from message)
me? (= (:public-key current-multiaccount) identity)]
(if me?
{:quote {:from identity
:text (get-in message [:content :text])}
:ens-name (:preferred-name current-multiaccount)
:alias (gfycat/generate-gfy identity)}
(let [contact (or (contacts identity)
(contact.db/public-key->new-contact identity))]
{:quote {:from identity
:text (get-in message [:content :text])}
:ens-name (when (:ens-verified contact)
(:name contact))
:alias (or (:alias contact)
(gfycat/generate-gfy identity))}))))))
(re-frame/reg-sub
:contacts/all-contacts-not-in-current-chat
:<- [::query-current-chat-contacts remove]
(fn [contacts]
(->> contacts
(filter contact.db/added?)
(sort-by (comp clojure.string/lower-case multiaccounts/displayed-name)))))
(re-frame/reg-sub
:contacts/current-chat-contacts
:<- [:chats/current-chat]
:<- [:contacts/contacts]
:<- [:multiaccount]
(fn [[{:keys [contacts admins]} all-contacts current-multiaccount]]
(map #(assoc % :name (multiaccounts/displayed-name %))
(contact.db/get-all-contacts-in-group-chat contacts admins all-contacts current-multiaccount))))
(re-frame/reg-sub
:contacts/contacts-by-chat
(fn [[_ _ chat-id] _]
[(re-frame/subscribe [:chats/chat chat-id])
(re-frame/subscribe [:contacts/contacts])])
(fn [[chat all-contacts] [_ query-fn]]
(contact.db/query-chat-contacts chat all-contacts query-fn)))
(re-frame/reg-sub
:contacts/chat-photo
(fn [[_ chat-id] _]
[(re-frame/subscribe [:chats/chat chat-id])
(re-frame/subscribe [:contacts/contacts-by-chat filter chat-id])])
(fn [[chat contacts] [_ chat-id]]
(when (and chat (not (:group-chat chat)))
(if (pos? (count contacts))
(multiaccounts/displayed-photo (first contacts))
(multiaccounts/displayed-photo chat)))))
(re-frame/reg-sub
:contacts/contact-by-address
:<- [:contacts/contacts]
(fn [contacts [_ address]]
(contact.db/find-contact-by-address contacts address)))
(re-frame/reg-sub
:contacts/contacts-by-address
:<- [:contacts/contacts]
(fn [contacts]
(reduce (fn [acc [_ {:keys [address] :as contact}]]
(if address
(assoc acc address contact)
acc))
{}
contacts)))
;;MAILSERVER ===========================================================================================================
(re-frame/reg-sub
:mailserver/connecting?
:<- [:mailserver/state]
(fn [state]
(#{:connecting :added} state)))
(re-frame/reg-sub
:mailserver/connection-error?
:<- [:mailserver/state]
(fn [state]
(#{:error :disconnected} state)))
(re-frame/reg-sub
:chats/fetching-gap-in-progress?
:<- [:chats/current-chat-id]
:<- [:mailserver/fetching-gaps-in-progress]
(fn [[chat-id gaps] [_ ids]]
(seq (select-keys (get gaps chat-id) ids))))
(re-frame/reg-sub
:mailserver/fetching?
:<- [:mailserver/state]
:<- [:mailserver/pending-requests]
:<- [:mailserver/connecting?]
:<- [:mailserver/connection-error?]
:<- [:mailserver/request-error?]
(fn [[state pending-requests connecting? connection-error? request-error?]]
(and pending-requests
(= state :connected)
(pos-int? pending-requests)
(not (or connecting? connection-error? request-error?)))))
(re-frame/reg-sub
:mailserver/fleet-mailservers
:<- [:fleets/current-fleet]
:<- [:mailserver/mailservers]
(fn [[current-fleet mailservers]]
(current-fleet mailservers)))
(re-frame/reg-sub
:mailserver.edit/connected?
:<- [:mailserver.edit/mailserver]
:<- [:mailserver/current-id]
(fn [[mailserver current-mailserver-id]]
(= (get-in mailserver [:id :value])
current-mailserver-id)))
(re-frame/reg-sub
:mailserver.edit/validation-errors
:<- [:mailserver.edit/mailserver]
(fn [mailserver]
(set (keep
(fn [[k {:keys [error]}]]
(when error k))
mailserver))))
(re-frame/reg-sub
:mailserver/connected?
:<- [:mailserver/state]
:<- [:disconnected?]
(fn [[mail-state disconnected?]]
(let [mailserver-connected? (= :connected mail-state)]
(and mailserver-connected?
(not disconnected?)))))
(re-frame/reg-sub
:mailserver/preferred-id
:<- [:multiaccount]
(fn [multiaccount]
(get-in multiaccount
[:pinned-mailservers (fleet/current-fleet-sub multiaccount)])))
;;SEARCH ==============================================================================================================
(defn extract-chat-attributes [chat]
(let [{:keys [name random-name tags]} (val chat)]
(into [name random-name] tags)))
(defn sort-by-timestamp
[coll]
(when (not-empty coll)
(sort-by #(-> % second :timestamp) >
(into {} coll))))
(defn apply-filter
"extract-attributes-fn is a function that take an element from the collection
and returns a vector of attributes which are strings
apply-filter returns the elements for which at least one attribute includes
the search-filter
apply-filter returns nil if there is no element that match the filter
apply-filter returns full collection if the search-filter is empty"
[search-filter coll extract-attributes-fn sort?]
(let [results (if (not-empty search-filter)
(let [search-filter (string/lower-case search-filter)]
(filter (fn [element]
(some (fn [v]
(let [s (cond (string? v) v
(keyword? v) (name v))]
(when (string? s)
(string/includes? (string/lower-case s)
search-filter))))
(extract-attributes-fn element)))
coll))
coll)]
(if sort?
(sort-by-timestamp results)
results)))
(re-frame/reg-sub
:search/filtered-chats
:<- [:chats/active-chats]
:<- [:search/home-filter]
(fn [[chats search-filter]]
(apply-filter search-filter chats extract-chat-attributes true)))
(defn extract-currency-attributes [currency]
(let [{:keys [code display-name]} (val currency)]
[code display-name]))
(re-frame/reg-sub
:search/filtered-currencies
:<- [:search/currency-filter]
(fn [search-currency-filter]
{:search-filter search-currency-filter
:currencies (apply-filter search-currency-filter constants/currencies extract-currency-attributes false)}))
(defn extract-token-attributes [token]
(let [{:keys [symbol name]} token]
[symbol name]))
(re-frame/reg-sub
:wallet/filtered-grouped-chain-tokens
:<- [:wallet/grouped-chain-tokens]
:<- [:search/token-filter]
(fn [[{custom-tokens true default-tokens nil} search-token-filter]]
{:search-filter search-token-filter
:tokens {true (apply-filter search-token-filter custom-tokens extract-token-attributes false)
nil (apply-filter search-token-filter default-tokens extract-token-attributes false)}}))
;; TRIBUTE TO TALK
(re-frame/reg-sub
:tribute-to-talk/settings
:<- [:multiaccount]
:<- [:ethereum/chain-keyword]
(fn [[multiaccount chain-keyword]]
(get-in multiaccount [:tribute-to-talk]) chain-keyword))
(re-frame/reg-sub
:tribute-to-talk/screen-params
:<- [:screen-params]
(fn [screen-params]
(get screen-params :tribute-to-talk)))
(re-frame/reg-sub
:tribute-to-talk/profile
:<- [:tribute-to-talk/settings]
:<- [:tribute-to-talk/screen-params]
(fn [[{:keys [seen? snt-amount]}
{:keys [state unavailable?]}]]
(let [state (or state (if snt-amount :completed :disabled))
snt-amount (tribute-to-talk.db/from-wei snt-amount)]
(when config/tr-to-talk-enabled?
(if unavailable?
{:subtext "Change network to enable Tribute to Talk"
:active? false
:icon :main-icons/tribute-to-talk
:icon-color colors/gray}
(cond-> {:new? (not seen?)}
(and (not (and seen?
snt-amount
(#{:signing :pending :transaction-failed :completed} state))))
(assoc :subtext (i18n/label :t/tribute-to-talk-desc))
(#{:signing :pending} state)
(assoc :activity-indicator {:animating true
:color colors/blue}
:subtext (case state
:pending (i18n/label :t/pending-confirmation)
:signing (i18n/label :t/waiting-to-sign)))
(= state :transaction-failed)
(assoc :icon :main-icons/warning
:icon-color colors/red
:subtext (i18n/label :t/transaction-failed))
(not (#{:signing :pending :transaction-failed} state))
(assoc :icon :main-icons/tribute-to-talk)
(and (= state :completed)
(not-empty snt-amount))
(assoc :accessory-value (str snt-amount " SNT"))))))))
(re-frame/reg-sub
:tribute-to-talk/enabled?
:<- [:tribute-to-talk/settings]
(fn [settings]
(tribute-to-talk.db/enabled? settings)))
(re-frame/reg-sub
:tribute-to-talk/settings-ui
:<- [:tribute-to-talk/settings]
:<- [:tribute-to-talk/screen-params]
:<- [:prices]
:<- [:wallet/currency]
(fn [[{:keys [seen? snt-amount message]
:as settings}
{:keys [step editing? state error]
:or {step :intro}
screen-snt-amount :snt-amount
screen-message :message} prices currency]]
(let [fiat-value (if snt-amount
(money/fiat-amount-value
snt-amount
:SNT
(-> currency :code keyword)
prices)
"0")]
(cond-> {:seen? seen?
:snt-amount (tribute-to-talk.db/from-wei snt-amount)
:message message
:enabled? (tribute-to-talk.db/enabled? settings)
:error error
:step step
:state (or state (if snt-amount :completed :disabled))
:editing? editing?
:fiat-value (str fiat-value " " (:code currency))}
(= step :set-snt-amount)
(assoc :snt-amount (str screen-snt-amount)
:disable-button?
(boolean (and (= step :set-snt-amount)
(or (string/blank? screen-snt-amount)
(#{"0" "0.0" "0.00"} screen-snt-amount)
(string/ends-with? screen-snt-amount ".")))))))))
;;ENS ==================================================================================================================
(re-frame/reg-sub
:ens.stateofus/registrar
2019-08-23 16:11:23 +02:00
:<- [:current-network]
(fn [network]
(let [chain (ethereum/network->chain-keyword network)]
(get stateofus/registrars chain))))
(re-frame/reg-sub
:multiaccount/usernames
:<- [:multiaccount]
(fn [multiaccount]
(:usernames multiaccount)))
(re-frame/reg-sub
:ens/preferred-name
:<- [:multiaccount]
(fn [multiaccount]
(:preferred-name multiaccount)))
(re-frame/reg-sub
:ens/search-screen
:<- [:ens/registration]
(fn [{:keys [custom-domain? username state]}]
{:state state
:username username
:custom-domain? custom-domain?}))
(defn- ens-amount-label
[chain-id]
(str (ens/registration-cost chain-id)
(case chain-id
3 " STT"
1 " SNT"
"")))
(re-frame/reg-sub
:ens/checkout-screen
:<- [:ens/registration]
:<- [:ens.stateofus/registrar]
:<- [:multiaccount/default-account]
:<- [:multiaccount/public-key]
:<- [:chain-id]
(fn [[{:keys [custom-domain? username]}
registrar default-account public-key chain-id]]
{:address (ethereum/normalized-hex (:address default-account))
:username username
:public-key public-key
:custom-domain? custom-domain?
:contract registrar
:amount-label (ens-amount-label chain-id)}))
(re-frame/reg-sub
:ens/confirmation-screen
:<- [:ens/registration]
(fn [{:keys [username state] :as ens}]
{:state state
:username username}))
(re-frame/reg-sub
:ens.name/screen
:<- [:get-screen-params :ens-name-details]
:<- [:ens/names]
(fn [[name ens]]
(let [{:keys [address public-key expiration-date releasable?]} (get ens name)
pending? (nil? address)]
(cond-> {:name name
:custom-domain? (not (string/ends-with? name ".stateofus.eth"))}
pending?
(assoc :pending? true)
(not pending?)
(assoc :address address
:public-key public-key
:releasable? releasable?
:expiration-date expiration-date)))))
(re-frame/reg-sub
:ens.main/screen
:<- [:multiaccount/usernames]
:<- [:multiaccount]
:<- [:ens/preferred-name]
2019-12-17 12:17:48 +01:00
(fn [[names multiaccount preferred-name]]
{:names names
:multiaccount multiaccount
2019-12-17 12:17:48 +01:00
:preferred-name preferred-name}))
;;SIGNING =============================================================================================================
(re-frame/reg-sub
:signing/fee
:<- [:signing/tx]
(fn [{:keys [gas gasPrice]}]
(signing.gas/calculate-max-fee gas gasPrice)))
(re-frame/reg-sub
:signing/phrase
:<- [:multiaccount]
(fn [{:keys [signing-phrase]}]
signing-phrase))
(defn- too-precise-amount?
"Checks if number has any extra digit beyond the allowed number of decimals.
It does so by checking the number against its rounded value."
[amount decimals]
(let [bn (money/bignumber amount)]
(not (.eq bn (.round bn decimals)))))
(defn get-amount-error [amount decimals]
(when (and (not (empty? amount)) decimals)
(let [normalized-amount (money/normalize amount)
value (money/bignumber normalized-amount)]
(cond
(not (money/valid? value))
{:amount-error (i18n/label :t/validation-amount-invalid-number)}
(too-precise-amount? normalized-amount decimals)
{:amount-error (i18n/label :t/validation-amount-is-too-precise {:decimals decimals})}
:else nil))))
(defn get-sufficient-funds-error
[balance symbol amount]
(when-not (money/sufficient-funds? amount (get balance symbol))
{:amount-error (i18n/label :t/wallet-insufficient-funds)}))
(defn get-sufficient-gas-error
[balance symbol amount gas gasPrice]
(if (and gas gasPrice)
(let [fee (.times gas gasPrice)
available-ether (money/bignumber (get balance :ETH 0))
available-for-gas (if (= :ETH symbol)
(.minus available-ether (money/bignumber amount))
available-ether)]
(when-not (money/sufficient-funds? fee (money/bignumber available-for-gas))
{:gas-error (i18n/label :t/wallet-insufficient-gas)}))
{:gas-error (i18n/label :t/invalid-number)}))
(re-frame/reg-sub
:signing/amount-errors
(fn [[_ address] _]
[(re-frame/subscribe [:signing/tx])
(re-frame/subscribe [:balance address])])
(fn [[{:keys [amount token gas gasPrice approve?]} balance]]
(if (and amount token (not approve?))
(let [amount-bn (money/formatted->internal (money/bignumber amount) (:symbol token) (:decimals token))
amount-error (or (get-amount-error amount (:decimals token))
(get-sufficient-funds-error balance (:symbol token) amount-bn))]
(or amount-error (get-sufficient-gas-error balance (:symbol token) amount-bn gas gasPrice)))
(get-sufficient-gas-error balance nil nil gas gasPrice))))
2019-08-23 16:11:23 +02:00
(re-frame/reg-sub
:wallet.send/prepare-transaction-with-balance
:<- [:wallet/prepare-transaction]
:<- [:wallet]
:<- [:offline?]
:<- [:wallet/all-tokens]
:<- [:ethereum/chain-keyword]
(fn [[{:keys [symbol from to amount-text] :as transaction}
wallet offline? all-tokens chain]]
(let [balance (get-in wallet [:accounts (:address from) :balance])
{:keys [decimals] :as token} (tokens/asset-for all-tokens chain symbol)
{:keys [value error]} (wallet.db/parse-amount amount-text decimals)
amount (money/formatted->internal value symbol decimals)
{:keys [amount-error] :as transaction-new}
(merge transaction
{:amount-error error}
(when amount
(get-sufficient-funds-error balance symbol amount)))]
(assoc transaction-new
:amount amount
:balance balance
:token (assoc token :amount (get balance (:symbol token)))
:sign-enabled? (and to
(nil? amount-error)
(not (nil? amount))
(not offline?))))))
(re-frame/reg-sub
:wallet.request/prepare-transaction-with-balance
:<- [:wallet/prepare-transaction]
:<- [:wallet]
:<- [:offline?]
:<- [:wallet/all-tokens]
:<- [:ethereum/chain-keyword]
(fn [[{:keys [symbol from to amount-text] :as transaction}
wallet offline? all-tokens chain]]
(let [balance (get-in wallet [:accounts (:address from) :balance])
{:keys [decimals] :as token} (tokens/asset-for all-tokens chain symbol)
{:keys [value error]} (wallet.db/parse-amount amount-text decimals)
amount (money/formatted->internal value symbol decimals)
{:keys [amount-error] :as transaction-new}
(assoc transaction
:amount-error error)]
(assoc transaction-new
:amount amount
:balance balance
:token (assoc token :amount (get balance (:symbol token)))
:sign-enabled? (and to
from
(nil? amount-error)
(not (nil? amount))
(not offline?))))))
2019-08-23 16:11:23 +02:00
;; NETWORK SETTINGS
(defn- filter-networks [network-type]
(fn [network]
(let [chain-id (ethereum/network->chain-id network)
testnet? (ethereum/testnet? chain-id)
custom? (:custom? network)]
(case network-type
:custom custom?
:mainnet (and (not custom?) (not testnet?))
:testnet (and (not custom?) testnet?)))))
(defn- label-networks [default-networks]
(fn [network]
(let [custom? (not (default-networks (:id network)))]
2019-08-23 16:11:23 +02:00
(assoc network :custom? custom?))))
(re-frame/reg-sub
:get-networks
:<- [:networks/networks]
(fn [networks]
(let [networks (map (label-networks (into #{} (map :id constants/default-networks))) (sort-by :name (vals networks)))
2019-08-23 16:11:23 +02:00
types [:mainnet :testnet :custom]]
(zipmap
types
(map #(filter (filter-networks %) networks) types)))))
(re-frame/reg-sub
:manage-network-valid?
:<- [:networks/manage]
(fn [manage]
(not-any? :error (vals manage))))