From 07304200175334a5912d26e21336062a690a4e40 Mon Sep 17 00:00:00 2001 From: Julien Eluard Date: Thu, 26 Oct 2017 15:13:24 +0200 Subject: [PATCH] [BUG #2228] Implemented wallet filtering screen --- src/status_im/chat/views/bottom_info.cljs | 14 +-- src/status_im/constants.cljs | 12 +- src/status_im/translations/en.cljs | 4 + .../ui/components/checkbox/styles.cljs | 4 +- .../ui/components/checkbox/view.cljs | 18 +-- .../ui/components/item_checkbox.cljs | 6 - src/status_im/ui/components/list/styles.cljs | 6 + src/status_im/ui/components/list/views.cljs | 13 ++- src/status_im/ui/components/toolbar/view.cljs | 4 +- src/status_im/ui/screens/db.cljs | 2 + src/status_im/ui/screens/events.cljs | 1 + .../ui/screens/wallet/main/styles.cljs | 9 -- .../screens/wallet/transactions/events.cljs | 21 ++++ .../screens/wallet/transactions/styles.cljs | 9 ++ .../ui/screens/wallet/transactions/subs.cljs | 4 + .../ui/screens/wallet/transactions/views.cljs | 110 +++++++++--------- .../react_native/js_dependencies.cljs | 1 - test/cljs/status_im/test/runner.cljs | 2 + .../test/wallet/transactions/views.cljs | 12 ++ 19 files changed, 161 insertions(+), 91 deletions(-) create mode 100644 src/status_im/ui/screens/wallet/transactions/events.cljs create mode 100644 test/cljs/status_im/test/wallet/transactions/views.cljs diff --git a/src/status_im/chat/views/bottom_info.cljs b/src/status_im/chat/views/bottom_info.cljs index 6c877dc8e2..c3f24f99af 100644 --- a/src/status_im/chat/views/bottom_info.cljs +++ b/src/status_im/chat/views/bottom_info.cljs @@ -3,13 +3,13 @@ (:require [re-frame.core :refer [subscribe dispatch]] [reagent.core :as r] [status-im.ui.components.react :refer [view - animated-view - image - text - icon - touchable-highlight - list-view - list-item]] + animated-view + image + text + icon + touchable-highlight + list-view + list-item]] [status-im.ui.components.chat-icon.screen :refer [chat-icon-view-menu-item]] [status-im.chat.styles.screen :as st] [status-im.i18n :refer [label label-pluralize message-status-label]] diff --git a/src/status_im/constants.cljs b/src/status_im/constants.cljs index 9707a72bee..f80888dd2e 100644 --- a/src/status_im/constants.cljs +++ b/src/status_im/constants.cljs @@ -1,5 +1,6 @@ (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])) (def ethereum-rpc-url "http://localhost:8545") @@ -25,6 +26,15 @@ (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] (->> networks (map (fn [[network-name {:keys [config] :as data}]] diff --git a/src/status_im/translations/en.cljs b/src/status_im/translations/en.cljs index d4dd36b1f5..4407bb9cfe 100644 --- a/src/status_im/translations/en.cljs +++ b/src/status_im/translations/en.cljs @@ -276,6 +276,10 @@ :confirmations-helper-text "Please wait for at least 12 confirmations to make sure your transaction is processed securely" :copy-transaction-hash "Copy transaction hash" :open-on-etherscan "Open on Etherscan.io" + :incoming "Incoming" + :outgoing "Outgoing" + :pending "Pending" + :postponed "Postponed" ;;webview :web-view-error "oops, error" diff --git a/src/status_im/ui/components/checkbox/styles.cljs b/src/status_im/ui/components/checkbox/styles.cljs index 2b3e3e162d..2c97d0ad14 100644 --- a/src/status_im/ui/components/checkbox/styles.cljs +++ b/src/status_im/ui/components/checkbox/styles.cljs @@ -2,11 +2,13 @@ (:require-macros [status-im.utils.styles :refer [defnstyle]]) (:require [status-im.ui.components.styles :as st])) +(def wrapper + {:padding 16}) + (defnstyle icon-check-container [checked?] {:background-color (if checked? st/color-light-blue st/color-gray5) :alignItems :center :justifyContent :center - :margin-right 16 :android {:border-radius 2 :width 17 :height 17} diff --git a/src/status_im/ui/components/checkbox/view.cljs b/src/status_im/ui/components/checkbox/view.cljs index 13767ae057..ff4aae522b 100644 --- a/src/status_im/ui/components/checkbox/view.cljs +++ b/src/status_im/ui/components/checkbox/view.cljs @@ -1,11 +1,13 @@ (ns status-im.ui.components.checkbox.view - (:require [cljs.spec.alpha :as s] - [status-im.ui.components.checkbox.styles :as cst] - [status-im.ui.components.react :as rn] - [status-im.utils.platform :as p])) + (:require [reagent.core :as reagent] + [status-im.ui.components.checkbox.styles :as styles] + [status-im.ui.components.react :as react])) -(defn checkbox [{:keys [on-press checked?]}] - [rn/touchable-highlight {:on-press on-press} - [rn/view (cst/icon-check-container checked?) +;; TODO(jeluard) Migrate to native checkbox provided by RN 0.49 +;; https://facebook.github.io/react-native/docs/checkbox.html + +(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? - [rn/icon :check_on cst/check-icon])]]) \ No newline at end of file + [react/icon :check_on styles/check-icon])]]) \ No newline at end of file diff --git a/src/status_im/ui/components/item_checkbox.cljs b/src/status_im/ui/components/item_checkbox.cljs index 0b817a3fa9..e69de29bb2 100644 --- a/src/status_im/ui/components/item_checkbox.cljs +++ b/src/status_im/ui/components/item_checkbox.cljs @@ -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) - diff --git a/src/status_im/ui/components/list/styles.cljs b/src/status_im/ui/components/list/styles.cljs index 59c23f27b8..78f450fe20 100644 --- a/src/status_im/ui/components/list/styles.cljs +++ b/src/status_im/ui/components/list/styles.cljs @@ -10,6 +10,12 @@ {:flex 1 :flex-direction :column}) +(def item-checkbox + {:flex 1 + :flex-direction :column + :align-items :center + :justify-content :center}) + (def primary-text-base {:font-size 17 :color styles/color-black}) diff --git a/src/status_im/ui/components/list/views.cljs b/src/status_im/ui/components/list/views.cljs index f5835e88d6..1394abb87a 100644 --- a/src/status_im/ui/components/list/views.cljs +++ b/src/status_im/ui/components/list/views.cljs @@ -20,6 +20,7 @@ " (:require [reagent.core :as r] [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.icons.vector-icons :as vi] [status-im.utils.platform :as p])) @@ -72,6 +73,11 @@ [& 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] (fn [data] ;; For details on passed data @@ -114,9 +120,10 @@ (let [{:keys [section]} (js->clj data :keywordize-keys true)] (r/as-element (f section))))) -(defn- default-render-section-header [{:keys [title]}] - [rn/text {:style lst/section-header} - title]) +(defn- default-render-section-header [{:keys [title data]}] + (when (seq data) + [rn/text {:style lst/section-header} + title])) (defn- wrap-per-section-render-fn [props] ;; TODO(jeluard) Somehow wrapping `:render-fn` does not work diff --git a/src/status_im/ui/components/toolbar/view.cljs b/src/status_im/ui/components/toolbar/view.cljs index 9884fd9441..e0b024da83 100644 --- a/src/status_im/ui/components/toolbar/view.cljs +++ b/src/status_im/ui/components/toolbar/view.cljs @@ -75,9 +75,11 @@ nil 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/view {:style (merge tst/item tst/toolbar-action)} + (when overlay-style + [rn/view overlay-style]) [vi/icon icon icon-opts]]]) (defn actions [v] diff --git a/src/status_im/ui/screens/db.cljs b/src/status_im/ui/screens/db.cljs index b5fc899b38..583692220c 100644 --- a/src/status_im/ui/screens/db.cljs +++ b/src/status_im/ui/screens/db.cljs @@ -34,6 +34,7 @@ :tags [] :sync-state :done :wallet {} + :wallet.transactions constants/default-wallet-transactions :prices {} :notifications {} :network constants/default-network @@ -177,6 +178,7 @@ :discoveries/request-discoveries-timer :discoveries/new-discover :wallet/wallet + :wallet/wallet.transactions :prices/prices :prices/prices-loading? :notifications/notifications])) diff --git a/src/status_im/ui/screens/events.cljs b/src/status_im/ui/screens/events.cljs index c92408de28..22166e8a3d 100644 --- a/src/status_im/ui/screens/events.cljs +++ b/src/status_im/ui/screens/events.cljs @@ -19,6 +19,7 @@ status-im.ui.screens.qr-scanner.events status-im.ui.screens.wallet.events status-im.ui.screens.wallet.send.events + status-im.ui.screens.wallet.transactions.events status-im.ui.screens.wallet.choose-recipient.events [re-frame.core :refer [dispatch reg-fx reg-cofx] :as re-frame] [status-im.native-module.core :as status] diff --git a/src/status_im/ui/screens/wallet/main/styles.cljs b/src/status_im/ui/screens/wallet/main/styles.cljs index f095bf0d57..47af26613a 100644 --- a/src/status_im/ui/screens/wallet/main/styles.cljs +++ b/src/status_im/ui/screens/wallet/main/styles.cljs @@ -111,12 +111,3 @@ (defn asset-border [color] {: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}) diff --git a/src/status_im/ui/screens/wallet/transactions/events.cljs b/src/status_im/ui/screens/wallet/transactions/events.cljs new file mode 100644 index 0000000000..2e2e29741f --- /dev/null +++ b/src/status_im/ui/screens/wallet/transactions/events.cljs @@ -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))) \ No newline at end of file diff --git a/src/status_im/ui/screens/wallet/transactions/styles.cljs b/src/status_im/ui/screens/wallet/transactions/styles.cljs index 62eaf9cf10..0e8249a6ca 100644 --- a/src/status_im/ui/screens/wallet/transactions/styles.cljs +++ b/src/status_im/ui/screens/wallet/transactions/styles.cljs @@ -225,3 +225,12 @@ {:background-color styles/color-light-gray3 :height 1 :margin-vertical 10}) + +(def corner-dot + {:position :absolute + :top 0 + :right 0 + :width 4 + :height 4 + :border-radius 2 + :background-color styles/color-cyan}) \ No newline at end of file diff --git a/src/status_im/ui/screens/wallet/transactions/subs.cljs b/src/status_im/ui/screens/wallet/transactions/subs.cljs index dd962917ea..56b5a457f1 100644 --- a/src/status_im/ui/screens/wallet/transactions/subs.cljs +++ b/src/status_im/ui/screens/wallet/transactions/subs.cljs @@ -161,3 +161,7 @@ (if (>= confirmations max-confirmations) 100 (* 100 (/ confirmations max-confirmations)))))) + +(reg-sub :wallet.transactions/filters + (fn [db] + (get-in db [:wallet.transactions :filters]))) \ No newline at end of file diff --git a/src/status_im/ui/screens/wallet/transactions/views.cljs b/src/status_im/ui/screens/wallet/transactions/views.cljs index 7d793567b5..f49598ccc7 100644 --- a/src/status_im/ui/screens/wallet/transactions/views.cljs +++ b/src/status_im/ui/screens/wallet/transactions/views.cljs @@ -12,18 +12,9 @@ [status-im.i18n :as i18n] [status-im.ui.screens.wallet.transactions.styles :as transactions.styles] [status-im.ui.screens.wallet.views :as wallet.views] - [status-im.utils.money :as money] - [status-im.utils.utils :as utils]) + [status-im.utils.money :as money]) (: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 [{:keys [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])} (i18n/label :t/transactions-sign-all)]) -(def history-action - {:icon :icons/filter - :handler #(utils/show-popup "TODO" "Not implemented") #_(re-frame/dispatch [:navigate-to-modal :wallet-transactions-sign-all])}) +(defn history-action [filter?] + (merge + {: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/default-nav-back [toolbar/content-title (i18n/label :t/transactions)] (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] @@ -106,14 +103,22 @@ ;; TODO(yenda) hook with re-frame (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 [] (letsubs [transactions-history-list [:wallet.transactions/transactions-history-list] transactions-loading? [:wallet.transactions/transactions-loading?] - error-message [:wallet.transactions/error-message?]] - [react/view {:style styles/flex} + error-message [:wallet.transactions/error-message?] + filter-data [:wallet.transactions/filters]] + [react/view styles/flex (when 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 :empty-component (empty-text (i18n/label :t/transactions-history-empty)) :on-refresh #(re-frame/dispatch [:update-transactions]) @@ -128,47 +133,43 @@ ;; Filter history -(defn- item-tokens [{:keys [symbol label checked?]}] +(defn- item-filter [{:keys [icon checked? path]} content] [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-primary label] - [list/item-secondary symbol]] - [checkbox/checkbox {:checked? true #_checked?}]]) + [list/item-secondary symbol]]]) -(defn- item-type [{:keys [id label checked?]}] - [list/item - [list/item-icon (transaction-type->icon (keyword id))] - [list/item-content - [list/item-primary-only label]] - [checkbox/checkbox checked?]]) +(defn- item-filter-type [{:keys [id label checked?]}] + (let [kid (keyword id)] + [item-filter {:icon (transaction-type->icon kid) :checked? checked? :path {:type kid}} + [list/item-content + [list/item-primary-only label]]])) -(def filter-data - [{:title (i18n/label :t/transactions-filter-tokens) - :key :tokens - :renderItem (list/wrap-render-fn item-tokens) - :data [{:symbol "GNO" :label "Gnosis"} - {:symbol "SNT" :label "Status Network Token"} - {: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"}]}]) +(defn- wrap-filter-data [m] + ;; TODO(jeluard) Restore tokens filtering once token support is added + [{:title (i18n/label :t/transactions-filter-type) + :key :type + :renderItem (list/wrap-render-fn item-filter-type) + :data (:type m)}]) (defview filter-history [] - [] - [react/view - [toolbar/toolbar {} - [toolbar/nav-clear-text (i18n/label :t/done)] - [toolbar/content-title (i18n/label :t/transactions-filter-title)] - [toolbar/text-action {:handler #(utils/show-popup "TODO" "Select All")} - (i18n/label :t/transactions-filter-select-all)]] - [react/view {:style styles/flex} - [list/section-list {:sections filter-data}]]]) + (letsubs [filter-data [:wallet.transactions/filters]] + [react/view styles/flex + [toolbar/toolbar {} + [toolbar/nav-clear-text (i18n/label :t/done)] + [toolbar/content-title (i18n/label :t/transactions-filter-title)] + [toolbar/text-action {:handler #(re-frame/dispatch [:wallet.transactions/filter-all]) + :disabled? (all-checked? filter-data)} + (i18n/label :t/transactions-filter-select-all)]] + [react/view {:style styles/flex} + [list/section-list {:sections (wrap-filter-data filter-data)}]]])) (defn history-tab [active?] [react/text {:uppercase? true @@ -195,10 +196,11 @@ (defview transactions [] (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} [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 {:navigation-event :navigation-replace :tab-style transactions.styles/tab diff --git a/test/cljs/status_im/react_native/js_dependencies.cljs b/test/cljs/status_im/react_native/js_dependencies.cljs index df87d74935..974d61f13f 100644 --- a/test/cljs/status_im/react_native/js_dependencies.cljs +++ b/test/cljs/status_im/react_native/js_dependencies.cljs @@ -5,7 +5,6 @@ (def autolink #js {:default #js {}}) (def config #js {:default #js {}}) (def camera #js {:constants #js {}}) -(def circle-checkbox #js {}) (def contacts #js {}) (def dialogs #js {}) (def dismiss-keyboard #js {}) diff --git a/test/cljs/status_im/test/runner.cljs b/test/cljs/status_im/test/runner.cljs index 4bc9f2f795..d282666208 100644 --- a/test/cljs/status_im/test/runner.cljs +++ b/test/cljs/status_im/test/runner.cljs @@ -5,6 +5,7 @@ [status-im.test.accounts.events] [status-im.test.wallet.events] [status-im.test.wallet.transactions.subs] + [status-im.test.wallet.transactions.views] [status-im.test.profile.events] [status-im.test.bots.events] [status-im.test.chat.models.input] @@ -37,6 +38,7 @@ 'status-im.test.wallet.events 'status-im.test.bots.events 'status-im.test.wallet.transactions.subs + 'status-im.test.wallet.transactions.views 'status-im.test.chat.models.input 'status-im.test.i18n 'status-im.test.utils.utils diff --git a/test/cljs/status_im/test/wallet/transactions/views.cljs b/test/cljs/status_im/test/wallet/transactions/views.cljs new file mode 100644 index 0000000000..5bce9ad188 --- /dev/null +++ b/test/cljs/status_im/test/wallet/transactions/views.cljs @@ -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}]})))) \ No newline at end of file