[BUG #2228] Implemented wallet filtering screen

This commit is contained in:
Julien Eluard 2017-10-26 15:13:24 +02:00 committed by Julien Eluard
parent 0403a7cdc1
commit 0730420017
19 changed files with 161 additions and 91 deletions

View File

@ -3,13 +3,13 @@
(:require [re-frame.core :refer [subscribe dispatch]] (:require [re-frame.core :refer [subscribe dispatch]]
[reagent.core :as r] [reagent.core :as r]
[status-im.ui.components.react :refer [view [status-im.ui.components.react :refer [view
animated-view animated-view
image image
text text
icon icon
touchable-highlight touchable-highlight
list-view list-view
list-item]] list-item]]
[status-im.ui.components.chat-icon.screen :refer [chat-icon-view-menu-item]] [status-im.ui.components.chat-icon.screen :refer [chat-icon-view-menu-item]]
[status-im.chat.styles.screen :as st] [status-im.chat.styles.screen :as st]
[status-im.i18n :refer [label label-pluralize message-status-label]] [status-im.i18n :refer [label label-pluralize message-status-label]]

View File

@ -1,5 +1,6 @@
(ns status-im.constants (ns status-im.constants
(:require [status-im.utils.types :as types] (:require [status-im.i18n :as i18n]
[status-im.utils.types :as types]
[status-im.utils.config :as config])) [status-im.utils.config :as config]))
(def ethereum-rpc-url "http://localhost:8545") (def ethereum-rpc-url "http://localhost:8545")
@ -25,6 +26,15 @@
(def default-network "testnet_rpc") (def default-network "testnet_rpc")
(def default-wallet-transactions
{:filters
{:type [{:id :inbound :label (i18n/label :t/incoming) :checked? true}
{:id :outbound :label (i18n/label :t/outgoing) :checked? true}
{:id :pending :label (i18n/label :t/pending) :checked? true}
;; TODO(jeluard) Restore once we support postponing transaction
#_
{:id :postponed :label (i18n/label :t/postponed) :checked? true}]}})
(defn- transform-config [networks] (defn- transform-config [networks]
(->> networks (->> networks
(map (fn [[network-name {:keys [config] :as data}]] (map (fn [[network-name {:keys [config] :as data}]]

View File

@ -276,6 +276,10 @@
:confirmations-helper-text "Please wait for at least 12 confirmations to make sure your transaction is processed securely" :confirmations-helper-text "Please wait for at least 12 confirmations to make sure your transaction is processed securely"
:copy-transaction-hash "Copy transaction hash" :copy-transaction-hash "Copy transaction hash"
:open-on-etherscan "Open on Etherscan.io" :open-on-etherscan "Open on Etherscan.io"
:incoming "Incoming"
:outgoing "Outgoing"
:pending "Pending"
:postponed "Postponed"
;;webview ;;webview
:web-view-error "oops, error" :web-view-error "oops, error"

View File

@ -2,11 +2,13 @@
(:require-macros [status-im.utils.styles :refer [defnstyle]]) (:require-macros [status-im.utils.styles :refer [defnstyle]])
(:require [status-im.ui.components.styles :as st])) (:require [status-im.ui.components.styles :as st]))
(def wrapper
{:padding 16})
(defnstyle icon-check-container [checked?] (defnstyle icon-check-container [checked?]
{:background-color (if checked? st/color-light-blue st/color-gray5) {:background-color (if checked? st/color-light-blue st/color-gray5)
:alignItems :center :alignItems :center
:justifyContent :center :justifyContent :center
:margin-right 16
:android {:border-radius 2 :android {:border-radius 2
:width 17 :width 17
:height 17} :height 17}

View File

@ -1,11 +1,13 @@
(ns status-im.ui.components.checkbox.view (ns status-im.ui.components.checkbox.view
(:require [cljs.spec.alpha :as s] (:require [reagent.core :as reagent]
[status-im.ui.components.checkbox.styles :as cst] [status-im.ui.components.checkbox.styles :as styles]
[status-im.ui.components.react :as rn] [status-im.ui.components.react :as react]))
[status-im.utils.platform :as p]))
(defn checkbox [{:keys [on-press checked?]}] ;; TODO(jeluard) Migrate to native checkbox provided by RN 0.49
[rn/touchable-highlight {:on-press on-press} ;; https://facebook.github.io/react-native/docs/checkbox.html
[rn/view (cst/icon-check-container checked?)
(defn checkbox [{:keys [on-value-change checked?]}]
[react/touchable-highlight {:style styles/wrapper :on-press #(do (when on-value-change (on-value-change (not checked?))))}
[react/view (styles/icon-check-container checked?)
(when checked? (when checked?
[rn/icon :check_on cst/check-icon])]]) [react/icon :check_on styles/check-icon])]])

View File

@ -1,6 +0,0 @@
(ns status-im.ui.components.item-checkbox
(:require [reagent.core :as r]
[status-im.react-native.js-dependencies :as rn-dependencies]))
(def item-checkbox rn-dependencies/camera)

View File

@ -10,6 +10,12 @@
{:flex 1 {:flex 1
:flex-direction :column}) :flex-direction :column})
(def item-checkbox
{:flex 1
:flex-direction :column
:align-items :center
:justify-content :center})
(def primary-text-base (def primary-text-base
{:font-size 17 {:font-size 17
:color styles/color-black}) :color styles/color-black})

View File

@ -20,6 +20,7 @@
" "
(:require [reagent.core :as r] (:require [reagent.core :as r]
[status-im.ui.components.list.styles :as lst] [status-im.ui.components.list.styles :as lst]
[status-im.ui.components.checkbox.view :as checkbox]
[status-im.ui.components.react :as rn] [status-im.ui.components.react :as rn]
[status-im.ui.components.icons.vector-icons :as vi] [status-im.ui.components.icons.vector-icons :as vi]
[status-im.utils.platform :as p])) [status-im.utils.platform :as p]))
@ -72,6 +73,11 @@
[& children] [& children]
(into [rn/view {:style lst/item-text-view}] (keep identity children))) (into [rn/view {:style lst/item-text-view}] (keep identity children)))
(defn item-checkbox
[{:keys [style] :as props}]
[rn/view {:style (merge style lst/item-checkbox)}
[checkbox/checkbox props]])
(defn- wrap-render-fn [f] (defn- wrap-render-fn [f]
(fn [data] (fn [data]
;; For details on passed data ;; For details on passed data
@ -114,9 +120,10 @@
(let [{:keys [section]} (js->clj data :keywordize-keys true)] (let [{:keys [section]} (js->clj data :keywordize-keys true)]
(r/as-element (f section))))) (r/as-element (f section)))))
(defn- default-render-section-header [{:keys [title]}] (defn- default-render-section-header [{:keys [title data]}]
[rn/text {:style lst/section-header} (when (seq data)
title]) [rn/text {:style lst/section-header}
title]))
(defn- wrap-per-section-render-fn [props] (defn- wrap-per-section-render-fn [props]
;; TODO(jeluard) Somehow wrapping `:render-fn` does not work ;; TODO(jeluard) Somehow wrapping `:render-fn` does not work

View File

@ -75,9 +75,11 @@
nil nil
tst/item]) tst/item])
(defn- icon-action [icon icon-opts handler] (defn- icon-action [icon {:keys [overlay-style] :as icon-opts} handler]
[rn/touchable-highlight {:on-press handler} [rn/touchable-highlight {:on-press handler}
[rn/view {:style (merge tst/item tst/toolbar-action)} [rn/view {:style (merge tst/item tst/toolbar-action)}
(when overlay-style
[rn/view overlay-style])
[vi/icon icon icon-opts]]]) [vi/icon icon icon-opts]]])
(defn actions [v] (defn actions [v]

View File

@ -34,6 +34,7 @@
:tags [] :tags []
:sync-state :done :sync-state :done
:wallet {} :wallet {}
:wallet.transactions constants/default-wallet-transactions
:prices {} :prices {}
:notifications {} :notifications {}
:network constants/default-network :network constants/default-network
@ -177,6 +178,7 @@
:discoveries/request-discoveries-timer :discoveries/request-discoveries-timer
:discoveries/new-discover :discoveries/new-discover
:wallet/wallet :wallet/wallet
:wallet/wallet.transactions
:prices/prices :prices/prices
:prices/prices-loading? :prices/prices-loading?
:notifications/notifications])) :notifications/notifications]))

View File

@ -19,6 +19,7 @@
status-im.ui.screens.qr-scanner.events status-im.ui.screens.qr-scanner.events
status-im.ui.screens.wallet.events status-im.ui.screens.wallet.events
status-im.ui.screens.wallet.send.events status-im.ui.screens.wallet.send.events
status-im.ui.screens.wallet.transactions.events
status-im.ui.screens.wallet.choose-recipient.events status-im.ui.screens.wallet.choose-recipient.events
[re-frame.core :refer [dispatch reg-fx reg-cofx] :as re-frame] [re-frame.core :refer [dispatch reg-fx reg-cofx] :as re-frame]
[status-im.native-module.core :as status] [status-im.native-module.core :as status]

View File

@ -111,12 +111,3 @@
(defn asset-border [color] (defn asset-border [color]
{:border-color color :border-width 1 :border-radius 32}) {:border-color color :border-width 1 :border-radius 32})
(def corner-dot
{:position :absolute
:top 12
:right 6
:width 4
:height 4
:border-radius 2
:background-color styles/color-cyan})

View File

@ -0,0 +1,21 @@
(ns status-im.ui.screens.wallet.transactions.events
(:require [status-im.utils.handlers :as handlers]))
(defn- mark-all-checked [filters]
(update filters :type #(map (fn [m] (assoc m :checked? true)) %)))
(defn- mark-checked [filters {:keys [type] :as m} checked?]
(update filters :type #(map (fn [{:keys [id] :as m}] (if (= type id) (assoc m :checked? checked?) m)) %)))
(defn- update-filters [db f]
(update-in db [:wallet.transactions :filters] f))
(handlers/register-handler-db
:wallet.transactions/filter
(fn [db [_ path checked?]]
(update-filters db #(mark-checked % path checked?))))
(handlers/register-handler-db
:wallet.transactions/filter-all
(fn [db]
(update-filters db mark-all-checked)))

View File

@ -225,3 +225,12 @@
{:background-color styles/color-light-gray3 {:background-color styles/color-light-gray3
:height 1 :height 1
:margin-vertical 10}) :margin-vertical 10})
(def corner-dot
{:position :absolute
:top 0
:right 0
:width 4
:height 4
:border-radius 2
:background-color styles/color-cyan})

View File

@ -161,3 +161,7 @@
(if (>= confirmations max-confirmations) (if (>= confirmations max-confirmations)
100 100
(* 100 (/ confirmations max-confirmations)))))) (* 100 (/ confirmations max-confirmations))))))
(reg-sub :wallet.transactions/filters
(fn [db]
(get-in db [:wallet.transactions :filters])))

View File

@ -12,18 +12,9 @@
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.ui.screens.wallet.transactions.styles :as transactions.styles] [status-im.ui.screens.wallet.transactions.styles :as transactions.styles]
[status-im.ui.screens.wallet.views :as wallet.views] [status-im.ui.screens.wallet.views :as wallet.views]
[status-im.utils.money :as money] [status-im.utils.money :as money])
[status-im.utils.utils :as utils])
(:require-macros [status-im.utils.views :refer [defview letsubs]])) (:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn- show-not-implemented! []
(utils/show-popup "TODO" "Not implemented yet!"))
(defn on-sign-transaction
[password]
;; TODO(yenda) implement
(re-frame/dispatch [:accept-transactions password]))
(defn on-delete-transaction (defn on-delete-transaction
[{:keys [id]}] [{:keys [id]}]
(re-frame/dispatch [:wallet/discard-unsigned-transaction-with-confirmation id])) (re-frame/dispatch [:wallet/discard-unsigned-transaction-with-confirmation id]))
@ -34,16 +25,22 @@
:handler #(re-frame/dispatch [:navigate-to-modal :wallet-transactions-sign-all])} :handler #(re-frame/dispatch [:navigate-to-modal :wallet-transactions-sign-all])}
(i18n/label :t/transactions-sign-all)]) (i18n/label :t/transactions-sign-all)])
(def history-action (defn history-action [filter?]
{:icon :icons/filter (merge
:handler #(utils/show-popup "TODO" "Not implemented") #_(re-frame/dispatch [:navigate-to-modal :wallet-transactions-sign-all])}) {:icon :icons/filter
:handler #(re-frame/dispatch [:navigate-to-modal :wallet-transactions-filter])}
(when filter? {:icon-opts {:overlay-style transactions.styles/corner-dot}})))
(defn toolbar-view [current-tab unsigned-transactions-count] (defn- all-checked? [filter-data]
(and (every? :checked? (:type filter-data))
(every? :checked? (:tokens filter-data))))
(defn- toolbar-view [current-tab unsigned-transactions-count filter-data]
[toolbar/toolbar {:flat? true} [toolbar/toolbar {:flat? true}
toolbar/default-nav-back toolbar/default-nav-back
[toolbar/content-title (i18n/label :t/transactions)] [toolbar/content-title (i18n/label :t/transactions)]
(case current-tab (case current-tab
:transactions-history [toolbar/actions [history-action]] :transactions-history [toolbar/actions [(history-action (not (all-checked? filter-data)))]]
:unsigned-transactions nil)]) ;; TODO (andrey) implement [unsigned-action unsigned-transactions-count] :unsigned-transactions nil)]) ;; TODO (andrey) implement [unsigned-action unsigned-transactions-count]
@ -106,14 +103,22 @@
;; TODO(yenda) hook with re-frame ;; TODO(yenda) hook with re-frame
(defn- empty-text [s] [react/text {:style transactions.styles/empty-text} s]) (defn- empty-text [s] [react/text {:style transactions.styles/empty-text} s])
(defn filtered-transaction? [transaction filter-data]
;; TODO(jeluard) extend to token when available
(:checked? (some #(when (= (:type transaction) (:id %)) %) (:type filter-data))))
(defn update-transactions [m filter-data]
(update m :data (fn [v] (filter #(filtered-transaction? % filter-data) v))))
(defview history-list [] (defview history-list []
(letsubs [transactions-history-list [:wallet.transactions/transactions-history-list] (letsubs [transactions-history-list [:wallet.transactions/transactions-history-list]
transactions-loading? [:wallet.transactions/transactions-loading?] transactions-loading? [:wallet.transactions/transactions-loading?]
error-message [:wallet.transactions/error-message?]] error-message [:wallet.transactions/error-message?]
[react/view {:style styles/flex} filter-data [:wallet.transactions/filters]]
[react/view styles/flex
(when error-message (when error-message
[wallet.views/error-message-view transactions.styles/error-container transactions.styles/error-message]) [wallet.views/error-message-view transactions.styles/error-container transactions.styles/error-message])
[list/section-list {:sections transactions-history-list [list/section-list {:sections (map #(update-transactions % filter-data) transactions-history-list)
:render-fn render-transaction :render-fn render-transaction
:empty-component (empty-text (i18n/label :t/transactions-history-empty)) :empty-component (empty-text (i18n/label :t/transactions-history-empty))
:on-refresh #(re-frame/dispatch [:update-transactions]) :on-refresh #(re-frame/dispatch [:update-transactions])
@ -128,47 +133,43 @@
;; Filter history ;; Filter history
(defn- item-tokens [{:keys [symbol label checked?]}] (defn- item-filter [{:keys [icon checked? path]} content]
[list/item [list/item
[list/item-icon (transaction-type->icon :pending)] ;; TODO(jeluard) add proper token data [list/item-icon icon]
content
[list/item-checkbox {:checked? checked? :on-value-change #(re-frame/dispatch [:wallet.transactions/filter path %])}]])
#_ ;; TODO(jeluard) Will be used for ERC20 tokens
(defn- item-filter-tokens [{:keys [symbol label checked?]}]
[item-filter {:icon (transaction-type->icon :pending) :checked? checked?} ;; TODO(jeluard) add proper token icon
[list/item-content [list/item-content
[list/item-primary label] [list/item-primary label]
[list/item-secondary symbol]] [list/item-secondary symbol]]])
[checkbox/checkbox {:checked? true #_checked?}]])
(defn- item-type [{:keys [id label checked?]}] (defn- item-filter-type [{:keys [id label checked?]}]
[list/item (let [kid (keyword id)]
[list/item-icon (transaction-type->icon (keyword id))] [item-filter {:icon (transaction-type->icon kid) :checked? checked? :path {:type kid}}
[list/item-content [list/item-content
[list/item-primary-only label]] [list/item-primary-only label]]]))
[checkbox/checkbox checked?]])
(def filter-data (defn- wrap-filter-data [m]
[{:title (i18n/label :t/transactions-filter-tokens) ;; TODO(jeluard) Restore tokens filtering once token support is added
:key :tokens [{:title (i18n/label :t/transactions-filter-type)
:renderItem (list/wrap-render-fn item-tokens) :key :type
:data [{:symbol "GNO" :label "Gnosis"} :renderItem (list/wrap-render-fn item-filter-type)
{:symbol "SNT" :label "Status Network Token"} :data (:type m)}])
{:symbol "SGT" :label "Status Genesis Token"}
{:symbol "GOL" :label "Golem"}]}
{:title (i18n/label :t/transactions-filter-type)
:key :type
:renderItem (list/wrap-render-fn item-type)
:data [{:id :incoming :label "Incoming"}
{:id :outgoing :label "Outgoing"}
{:id :pending :label "Pending"}
{:id :postponed :label "Postponed"}]}])
(defview filter-history [] (defview filter-history []
[] (letsubs [filter-data [:wallet.transactions/filters]]
[react/view [react/view styles/flex
[toolbar/toolbar {} [toolbar/toolbar {}
[toolbar/nav-clear-text (i18n/label :t/done)] [toolbar/nav-clear-text (i18n/label :t/done)]
[toolbar/content-title (i18n/label :t/transactions-filter-title)] [toolbar/content-title (i18n/label :t/transactions-filter-title)]
[toolbar/text-action {:handler #(utils/show-popup "TODO" "Select All")} [toolbar/text-action {:handler #(re-frame/dispatch [:wallet.transactions/filter-all])
(i18n/label :t/transactions-filter-select-all)]] :disabled? (all-checked? filter-data)}
[react/view {:style styles/flex} (i18n/label :t/transactions-filter-select-all)]]
[list/section-list {:sections filter-data}]]]) [react/view {:style styles/flex}
[list/section-list {:sections (wrap-filter-data filter-data)}]]]))
(defn history-tab [active?] (defn history-tab [active?]
[react/text {:uppercase? true [react/text {:uppercase? true
@ -195,10 +196,11 @@
(defview transactions [] (defview transactions []
(letsubs [unsigned-transactions-count [:wallet.transactions/unsigned-transactions-count] (letsubs [unsigned-transactions-count [:wallet.transactions/unsigned-transactions-count]
current-tab [:get :view-id]] current-tab [:get :view-id]
filter-data [:wallet.transactions/filters]]
[react/view {:style styles/flex} [react/view {:style styles/flex}
[status-bar/status-bar] [status-bar/status-bar]
[toolbar-view current-tab unsigned-transactions-count] [toolbar-view current-tab unsigned-transactions-count filter-data]
[tabs/swipable-tabs tabs-list current-tab true [tabs/swipable-tabs tabs-list current-tab true
{:navigation-event :navigation-replace {:navigation-event :navigation-replace
:tab-style transactions.styles/tab :tab-style transactions.styles/tab

View File

@ -5,7 +5,6 @@
(def autolink #js {:default #js {}}) (def autolink #js {:default #js {}})
(def config #js {:default #js {}}) (def config #js {:default #js {}})
(def camera #js {:constants #js {}}) (def camera #js {:constants #js {}})
(def circle-checkbox #js {})
(def contacts #js {}) (def contacts #js {})
(def dialogs #js {}) (def dialogs #js {})
(def dismiss-keyboard #js {}) (def dismiss-keyboard #js {})

View File

@ -5,6 +5,7 @@
[status-im.test.accounts.events] [status-im.test.accounts.events]
[status-im.test.wallet.events] [status-im.test.wallet.events]
[status-im.test.wallet.transactions.subs] [status-im.test.wallet.transactions.subs]
[status-im.test.wallet.transactions.views]
[status-im.test.profile.events] [status-im.test.profile.events]
[status-im.test.bots.events] [status-im.test.bots.events]
[status-im.test.chat.models.input] [status-im.test.chat.models.input]
@ -37,6 +38,7 @@
'status-im.test.wallet.events 'status-im.test.wallet.events
'status-im.test.bots.events 'status-im.test.bots.events
'status-im.test.wallet.transactions.subs 'status-im.test.wallet.transactions.subs
'status-im.test.wallet.transactions.views
'status-im.test.chat.models.input 'status-im.test.chat.models.input
'status-im.test.i18n 'status-im.test.i18n
'status-im.test.utils.utils 'status-im.test.utils.utils

View File

@ -0,0 +1,12 @@
(ns status-im.test.wallet.transactions.views
(:require [cljs.test :refer [deftest is testing]]
[status-im.ui.screens.wallet.transactions.views :as views]))
(deftest filtered-transaction?
(is (not (true? (views/filtered-transaction? {:type :inbound} {:type [{:id :outbound :checked? true}]}))))
(is (not (true? (views/filtered-transaction? {:type :inbound} {:type [{:id :inbound :checked? false}]}))))
(is (true? (views/filtered-transaction? {:type :inbound} {:type [{:id :inbound :checked? true}]})))
(is (true? (views/filtered-transaction? {:type :inbound} {:type [{:id :outbound :checked? true} {:id :inbound :checked? true}]}))))
(deftest update-transactions
(is (= {:data '()} (views/update-transactions {:data {:type :inbound}} {:type [{:id :outbound :checked? true}]}))))