Introduced tab switching for wallet transactions screen

This commit is contained in:
Julien Eluard 2017-08-22 09:53:15 +02:00 committed by Roman Volosovskyi
parent 2ff20eb830
commit 55fd200c71
26 changed files with 308 additions and 95 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 625 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1017 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "icon_wallet_active.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "icon_wallet_gray.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,12 +1,14 @@
(ns status-im.components.button.styles (ns status-im.components.button.styles
(:require-macros [status-im.utils.styles :refer [defstyle]])
(:require [status-im.components.styles :as st])) (:require [status-im.components.styles :as st]))
(def border-color st/color-white-transparent-2) (def border-color st/color-white-transparent-2)
(def button-borders (defstyle button-borders
{:background-color border-color {:background-color border-color
:margin 5 :margin-horizontal 5
:border-radius 8}) :android {:border-radius 4}
:ios {:border-radius 8}})
(def action-buttons-container (def action-buttons-container
(merge (merge

View File

@ -1,7 +1,8 @@
(ns status-im.components.button.view (ns status-im.components.button.view
(:require [cljs.spec.alpha :as s] (:require [cljs.spec.alpha :as s]
[status-im.components.button.styles :as cst] [status-im.components.button.styles :as cst]
[status-im.components.react :as rn])) [status-im.components.react :as rn]
[status-im.utils.platform :as p]))
(defn button [{:keys [on-press style text text-style disabled?] (defn button [{:keys [on-press style text text-style disabled?]
:or {style cst/action-button}}] :or {style cst/action-button}}]
@ -10,7 +11,7 @@
[rn/text {:style (or text-style [rn/text {:style (or text-style
(if disabled? cst/action-button-text-disabled cst/action-button-text)) (if disabled? cst/action-button-text-disabled cst/action-button-text))
:font :medium :font :medium
:uppercase? false} :uppercase? p/android?}
text]]]) text]]])
(defn primary-button [m] (defn primary-button [m]

View File

@ -13,12 +13,12 @@
(def primary-text (def primary-text
{:font-size 20 {:font-size 20
:color st/color-black :color st/color-black
:margin-top 13}) :padding-top 13})
(def secondary-text (def secondary-text
{:font-size 16 {:font-size 16
:color st/color-gray4 :color st/color-gray4
:margin-top 6}) :padding-top 6})
(def item-icon (def item-icon
{:width 40 {:width 40
@ -31,4 +31,25 @@
(def action-buttons (def action-buttons
{:flex 1 {:flex 1
:flex-direction :row}) :flex-direction :row
:padding-vertical 12})
(def base-separator
{:height 1
:background-color st/color-gray5
:opacity 0.5
:margin-top 12
:margin-bottom 16})
(def separator
(merge
base-separator
{:margin-left 70}))
(def section-separator base-separator)
(def section-header
{:margin-vertical 2
:margin-bottom 12
:margin-top 16
:margin-left 16})

View File

@ -1,23 +1,62 @@
(ns status-im.components.list.views (ns status-im.components.list.views
(:require [status-im.components.react :as rn] (:require [reagent.core :as r]
[reagent.core :as r] [status-im.components.list.styles :as lst]
[status-im.components.common.common :as common] [status-im.components.react :as rn]
[status-im.utils.platform :as p])) [status-im.utils.platform :as p]))
(def flat-list-class (rn/get-class "FlatList")) (def flat-list-class (rn/get-class "FlatList"))
(def section-list-class (rn/get-class "SectionList"))
(defn- wrap-render-fn [f] (defn- wrap-render-fn [f]
(fn [o] (fn [data]
(let [{:keys [item index separators]} (js->clj o :keywordize-keys true)] ;; For details on passed data
;; https://facebook.github.io/react-native/docs/sectionlist.html#rendersectionheader
(let [{:keys [item index separators]} (js->clj data :keywordize-keys true)]
(r/as-element (f item index separators))))) (r/as-element (f item index separators)))))
(defn- separator []
[rn/view lst/separator])
(defn- section-separator []
[rn/view lst/section-separator])
(defn base-list-props [render-fn empty-component]
(merge {:renderItem (wrap-render-fn render-fn)
:keyExtractor (fn [_ i] i)}
(when p/ios? {:ItemSeparatorComponent (fn [] (r/as-element [separator]))})
; TODO(jeluard) Does not work with our current ReactNative version
(when empty-component {:ListEmptyComponent (r/as-element [empty-component])})))
(defn flat-list (defn flat-list
"A wrapper for FlatList. "A wrapper for FlatList.
See https://facebook.github.io/react-native/docs/flatlist.html" See https://facebook.github.io/react-native/docs/flatlist.html"
([data render-fn] (flat-list data render-fn {})) ([data render-fn] (flat-list data render-fn {}))
([data render-fn props] ([data render-fn {:keys [empty-component] :as props}]
[flat-list-class (merge {:data (clj->js data) (if (and (empty? data) empty-component)
:renderItem (wrap-render-fn render-fn) ;; TODO(jeluard) remove when native :ListEmptyComponent is supported
:keyExtractor (fn [_ i] i)} empty-component
(when p/ios? {:ItemSeparatorComponent (fn [] (r/as-element [common/list-separator]))}) [flat-list-class
props)])) (merge (base-list-props render-fn empty-component)
{:data (clj->js data)}
props)])))
(defn- wrap-render-section-header-fn [f]
(fn [data]
;; For details on passed data
;; https://facebook.github.io/react-native/docs/sectionlist.html#rendersectionheader
(let [{:keys [isection]} (js->clj data :keywordize-keys true)]
(r/as-element (f section)))))
(defn section-list
"A wrapper for SectionList.
See https://facebook.github.io/react-native/docs/sectionlist.html"
([data render-fn render-section-header-fn] (section-list data render-fn render-section-header-fn {}))
([data render-fn render-section-header-fn {:keys [empty-component] :as props}]
(if (and (empty? data) empty-component)
empty-component
[section-list-class
(merge (base-list-props render-fn empty-component)
{:sections (clj->js data)
:renderSectionHeader (wrap-render-section-header-fn render-section-header-fn)}
(when p/ios? {:SectionSeparatorComponent (fn [] (r/as-element [section-separator]))})
props)])))

View File

@ -20,6 +20,12 @@
(def tab-list (def tab-list
(concat (concat
(when config/wallet-tab-enabled?
[{:view-id :wallet
:title (label :t/wallet)
:screen wallet
:icon-inactive :icon_wallet_gray
:icon-active :icon_wallet_active}])
[{:view-id :chat-list [{:view-id :chat-list
:title (label :t/chats) :title (label :t/chats)
:screen chats-list :screen chats-list
@ -34,13 +40,7 @@
:title (label :t/contacts) :title (label :t/contacts)
:screen contact-groups-list :screen contact-groups-list
:icon-inactive :icon_contacts :icon-inactive :icon_contacts
:icon-active :icon_contacts_active}] :icon-active :icon_contacts_active}]))
(when config/wallet-tab-enabled?
[{:view-id :wallet
:title "Wallet"
:screen wallet
:icon-inactive :icon_contacts
:icon-active :icon_contacts_active}])))
(def tab->index (reduce #(assoc %1 (:view-id %2) (count %1)) {} tab-list)) (def tab->index (reduce #(assoc %1 (:view-id %2) (count %1)) {} tab-list))
@ -115,8 +115,8 @@
(doall (doall
(map-indexed (fn [index {vid :view-id screen :screen}] (map-indexed (fn [index {vid :view-id screen :screen}]
^{:key index} [screen (= @view-id vid)]) tab-list))] ^{:key index} [screen (= @view-id vid)]) tab-list))]
[tabs {:selected-view-id @view-id [tabs {:style (st/tabs-container @tabs-hidden?)
:prev-view-id @prev-view-id :selected-view-id @view-id
:tab-list tab-list}] :tab-list tab-list}]
(when-not @tabs-hidden? (when-not @tabs-hidden?
[bottom-shadow-view])]]]])}))) [bottom-shadow-view])]]]])})))

View File

@ -40,7 +40,6 @@
(defn tab-title [active?] (defn tab-title [active?]
{:font-size (if-not (or active? p/ios?) 12 14) {:font-size (if-not (or active? p/ios?) 12 14)
:height 16
:min-width 60 :min-width 60
:text-align :center :text-align :center
:color (if active? st/color-light-blue st/color-gray4)}) :color (if active? st/color-light-blue st/color-gray4)})
@ -57,10 +56,14 @@
:justifyContent :center :justifyContent :center
:alignItems :center}) :alignItems :center})
(def swiper
{:shows-pagination false})
(defn main-swiper [tabs-hidden?] (defn main-swiper [tabs-hidden?]
(merge
swiper
{:position :absolute {:position :absolute
:top 0 :top 0
:left 0 :left 0
:right 0 :right 0
:bottom (if tabs-hidden? 0 tabs-height) :bottom (if tabs-hidden? 0 tabs-height)}))
:shows-pagination false})

View File

@ -12,38 +12,37 @@
[status-im.components.animation :as anim] [status-im.components.animation :as anim]
[status-im.utils.platform :as p])) [status-im.utils.platform :as p]))
(defn tab [{:keys [view-id title icon-active icon-inactive selected-view-id prev-view-id]}] (defn tab [{:keys [view-id title icon-active icon-inactive selected-view-id]}]
(let [active? (= view-id selected-view-id) (let [active? (= view-id selected-view-id)]
previous? (= view-id prev-view-id)]
[touchable-highlight {:style st/tab [touchable-highlight {:style st/tab
:disabled active? :disabled active?
:onPress #(dispatch [:navigate-to-tab view-id])} :onPress #(dispatch [:navigate-to-tab view-id])}
[view {:style st/tab-container} [view {:style st/tab-container}
(when-let [icon (if active? icon-active icon-inactive)]
[view [view
[image {:source {:uri (if active? icon-active icon-inactive)} [image {:source {:uri icon}
:style st/tab-icon}]] :style st/tab-icon}]])
[view [view
[text {:style (st/tab-title active?) [text {:style (st/tab-title active?)
:font (if (and p/ios? active?) :medium :regular)} :font (if (and p/ios? active?) :medium :regular)}
title]]]])) title]]]]))
(defn- create-tab [index data selected-view-id prev-view-id] (defn- create-tab [index data selected-view-id]
(let [data (merge data {:key index (let [data (merge data {:key index
:index index :index index
:selected-view-id selected-view-id :selected-view-id selected-view-id})]
:prev-view-id prev-view-id})]
[tab data])) [tab data]))
(defn- tabs-container [& children] (defn- tabs-container [style children]
(let [tabs-hidden? (subscribe [:tabs-hidden?]) (let [tabs-hidden? (subscribe [:tabs-hidden?])
shadows? (get-in p/platform-specific [:tabs :tab-shadows?])] shadows? (get-in p/platform-specific [:tabs :tab-shadows?])]
(into [animated-view {:style (merge (st/tabs-container @tabs-hidden?) [animated-view {:style (merge style
(if-not shadows? st/tabs-container-line)) (when-not shadows? st/tabs-container-line))
:pointerEvents (if @tabs-hidden? :none :auto)}] :pointerEvents (if @tabs-hidden? :none :auto)}
children))) children]))
(defn tabs [{:keys [tab-list selected-view-id prev-view-id]}] (defn tabs [{:keys [style tab-list selected-view-id]}]
[tabs-container [tabs-container style
(into (into
[view st/tabs-inner-container] [view st/tabs-inner-container]
(map-indexed #(create-tab %1 %2 selected-view-id prev-view-id) tab-list))]) (map-indexed #(create-tab %1 %2 selected-view-id) tab-list))])

View File

@ -44,7 +44,8 @@
(defstyle toolbar-title-text (defstyle toolbar-title-text
{:color text1-color {:color text1-color
:letter-spacing -0.2 :letter-spacing -0.2
:font-size 17}) :font-size 17
:ios {:text-align "center"}})
(def toolbar-border-container (def toolbar-border-container
(get-in p/platform-specific [:component-styles :toolbar-border-container])) (get-in p/platform-specific [:component-styles :toolbar-border-container]))

View File

@ -329,8 +329,13 @@
:testfairy-message "You are using app installed from a nightly build. For testing purposes this build includes session recording if wifi connection is used, so all your interaction with app is saved (as video and log) and might be used by development team to investigate possible issues. Saved video/log do not include your passwords. Recording is done only if app is installed from a nightly build. Nothing is recorded if app is installed from PlayStore or TestFlight." :testfairy-message "You are using app installed from a nightly build. For testing purposes this build includes session recording if wifi connection is used, so all your interaction with app is saved (as video and log) and might be used by development team to investigate possible issues. Saved video/log do not include your passwords. Recording is done only if app is installed from a nightly build. Nothing is recorded if app is installed from PlayStore or TestFlight."
;; wallet ;; wallet
:wallet "Wallet"
:transactions "Transactions" :transactions "Transactions"
:transactions-to "To" :transactions-to "To"
:transactions-sign "Sign" :transactions-sign "Sign"
:transactions-sign-all "Sign all" :transactions-sign-all "Sign all"
:transactions-delete "Delete"}) :transactions-delete "Delete"
:transactions-history "History"
:transactions-unsigned "Unsigned"
:transactions-history-empty "You don't have a history transactions"
:transactions-unsigned-empty "You don't have unsigned transactions"})

View File

@ -58,6 +58,8 @@
(when view-id (when view-id
(let [current-view (validate-current-view view-id signed-up?)] (let [current-view (validate-current-view view-id signed-up?)]
(let [component (case current-view (let [component (case current-view
:wallet-transactions-unsigned wallet-transactions
:wallet-transactions-history wallet-transactions
:wallet main-tabs :wallet main-tabs
:wallet-send-transaction send-transaction :wallet-send-transaction send-transaction
:discover main-tabs :discover main-tabs
@ -85,7 +87,8 @@
:profile-photo-capture profile-photo-capture :profile-photo-capture profile-photo-capture
:accounts accounts :accounts accounts
:login login :login login
:recover recover)] :recover recover
(throw (str "Unknown view: " current-view)))]
[(if android? menu-context view) common-styles/flex [(if android? menu-context view) common-styles/flex
[view common-styles/flex [view common-styles/flex
@ -102,5 +105,6 @@
:transaction-details transaction-details :transaction-details transaction-details
:confirmation-success confirmation-success :confirmation-success confirmation-success
:contact-list-modal contact-list-modal :contact-list-modal contact-list-modal
:wallet-transactions wallet-transactions)] :wallet-transactions wallet-transactions
(throw (str "Unknown modal view: " modal-view)))]
[component])]])]]))))) [component])]])]])))))

View File

@ -7,8 +7,15 @@
(def toolbar-right-action (def toolbar-right-action
{:color st/color-blue4 {:color st/color-blue4
:font-size 18 :font-size 17
:margin-right 12}) :margin-right 12})
(def main-section (def main-section
{:background-color st/color-white}) {:flex 1
:position :relative
:background-color st/color-white})
(def empty-text
{:text-align :center
:margin-top 22
:margin-horizontal 92})

View File

@ -1,55 +1,144 @@
(ns status-im.ui.screens.wallet.history.views (ns status-im.ui.screens.wallet.history.views
(:require-macros [status-im.utils.views :refer [defview]]) (:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [status-im.components.button.view :as btn] (:require [reagent.core :as r]
[status-im.components.button.view :as btn]
[status-im.components.react :as rn] [status-im.components.react :as rn]
[status-im.components.list.styles :as list-st] [status-im.components.list.styles :as list-st]
[status-im.components.list.views :as list] [status-im.components.list.views :as list]
[status-im.components.tabs.styles :as tst]
[status-im.components.tabs.views :as tabs]
[status-im.components.toolbar-new.view :as toolbar] [status-im.components.toolbar-new.view :as toolbar]
[status-im.ui.screens.wallet.history.styles :as st] [status-im.ui.screens.wallet.history.styles :as st]
[status-im.utils.utils :as utils]
[status-im.i18n :as i18n])) [status-im.i18n :as i18n]))
(defn on-sign-transaction
[m]
;; TODO(yenda) implement
(utils/show-popup "TODO" "Sign Transaction"))
(defn on-sign-all-transactions
[m]
;; TODO(yenda) implement
(utils/show-popup "TODO" "Sign All Transactions"))
(defn on-delete-transaction
[m]
;; TODO(yenda) implement
(utils/show-popup "TODO" "Delete Transaction"))
(defn unsigned-action [] (defn unsigned-action []
[rn/text {:style st/toolbar-right-action} [rn/text {:style st/toolbar-right-action :onPress on-sign-all-transactions}
(i18n/label :t/transactions-sign-all)]) (i18n/label :t/transactions-sign-all)])
(defn toolbar-view [] (defn history-action []
;; TODO(jeluard)
[rn/text {:style st/toolbar-right-action}
"AAAAA"])
(defn toolbar-view [view-id]
[toolbar/toolbar [toolbar/toolbar
{:title (i18n/label :t/transactions) {:title (i18n/label :t/transactions)
:title-style {:text-align "center"} :custom-action
:custom-action [unsigned-action]}]) [(if (= @view-id :wallet-transactions-unsigned) unsigned-action history-action)]}])
(defn- icon-status [k] (defn- icon-status [k]
(case k (case k
:pending :dropdown_white :pending :dropdown_white
:dropdown_white)) :dropdown_white))
(defn render-transaction (defn action-buttons [m ]
[item] [rn/view {:style list-st/action-buttons}
[btn/primary-button {:text (i18n/label :t/transactions-sign) :on-press #(on-sign-transaction m)}]
[btn/secondary-button {:text (i18n/label :t/transactions-delete) :on-press #(on-delete-transaction m)}]])
(defn- unsigned? [state] (= "unsigned" state))
(defn transaction-details [{:keys [to state] {:keys [value symbol]} :content :as m}]
[rn/view {:style list-st/item-text-view}
[rn/text {:style list-st/primary-text} (str value " " symbol)]
[rn/text {:style list-st/secondary-text :ellipsize-mode "middle" :number-of-lines 1} (str (i18n/label :t/transactions-to) " " to)]
(if (unsigned? state)
[action-buttons m])])
(defn render-transaction [m]
[rn/view {:style list-st/item} [rn/view {:style list-st/item}
[rn/image {:source {:uri :console} [rn/image {:source {:uri :console}
:style list-st/item-icon}] :style list-st/item-icon}]
[rn/view {:style list-st/item-text-view} [transaction-details m]
(let [m (:content item)]
[rn/text {:style list-st/primary-text} (str (:value m) " " (:symbol m))])
[rn/text {:style list-st/secondary-text} (str (i18n/label :t/transactions-to) " " (:to item))]
[rn/view {:style list-st/action-buttons}
[btn/primary-button {:text (i18n/label :t/transactions-sign)}]
[btn/secondary-button {:text (i18n/label :t/transactions-delete)}]]]
[rn/icon :forward_gray list-st/secondary-action]]) [rn/icon :forward_gray list-st/secondary-action]])
(defn render-section-header [m]
[rn/text {:style list-st/section-header} (:title m)])
(def dummy-transaction-data (def dummy-transaction-data
[{:to "0xAAAAA" :content {:value "0,4909" :symbol "ETH"}} [{:to "0x829bd824b016326a401d083b33d092293333a830" :content {:value "0,4909" :symbol "ETH"} :state :unsigned}
{:to "0xAAAAA" :content {:value "10000" :symbol "SGT"}}]) {:to "0x829bd824b016326a401d083b33d092293333a830" :content {:value "10000" :symbol "SGT"} :state :unsigned}
{:to "0x829bd824b016326a401d083b33d092293333a830" :content {:value "10000" :symbol "SGT"} :state :unsigned}])
(defn main-section [] (def dummy-transaction-data-sorted
[{:title "Postponed"
:key :postponed
:data [{:to "0x829bd824b016326a401d083b33d092293333a830" :content {:value "0,4909" :symbol "ETH"} :state :pending}
{:to "0x829bd824b016326a401d083b33d092293333a830" :content {:value "10000" :symbol "SGT"} :state :pending}
{:to "0x829bd824b016326a401d083b33d092293333a830" :content {:value "10000" :symbol "SGT"} :state :sent}]}
{:title "Pending"
:key :pending
:data [{:to "0x829bd824b016326a401d083b33d092293333a830" :content {:value "0,4909" :symbol "ETH"} :state :pending}
{:to "0x829bd824b016326a401d083b33d092293333a830" :content {:value "10000" :symbol "SGT"} :state :pending}
{:to "0x829bd824b016326a401d083b33d092293333a830" :content {:value "10000" :symbol "SGT"} :state :sent}]}])
;; TODO(yenda) hook with re-frame
(defn empty-text [s]
[rn/text {:style st/empty-text} s])
(defview history-list []
[list/section-list dummy-transaction-data-sorted render-transaction render-section-header
{:empty-component (empty-text (i18n/label :t/transactions-history-empty))}])
(defview unsigned-list []
[list/flat-list dummy-transaction-data render-transaction
{:empty-component (empty-text (i18n/label :t/transactions-unsigned-empty))}])
(def tab-list
[{:view-id :wallet-transactions-unsigned
:title (i18n/label :t/transactions-unsigned)
:screen unsigned-list}
{:view-id :wallet-transactions-history
:title (i18n/label :t/transactions-history)
:screen history-list}])
(def tab->index (reduce #(assoc %1 (:view-id %2) (count %1)) {} tab-list))
(defn get-tab-index [view-id]
(get tab->index view-id 0))
;; TODO(jeluard) whole swipe logic
;; extract navigate-tab action (on tap)
(defn main-section [view-id]
[rn/view {:style st/main-section} [rn/view {:style st/main-section}
[list/flat-list dummy-transaction-data render-transaction]]) [tabs/tabs {:selected-view-id @view-id
:tab-list tab-list}]
[rn/swiper (merge tst/swiper
{:index (get-tab-index @view-id)
:loop false
;:ref #(reset! swiper %)
;:on-momentum-scroll-end (on-scroll-end swiped? scroll-ended @view-id)
})
(doall
(map-indexed (fn [index {screen :screen}]
^{:key index} [screen]) tab-list))]])
;; TODO must reflect selected wallet ;; TODO(yenda) must reflect selected wallet
(def initial-tab (-> tab-list first :view-id))
(defview wallet-transactions [] (defview wallet-transactions []
[] []
(let [view-id (r/atom initial-tab)]
[rn/view {:style st/wallet-transactions-container} [rn/view {:style st/wallet-transactions-container}
[toolbar-view] [toolbar-view view-id]
[rn/scroll-view [rn/scroll-view
[main-section]]]) [main-section view-id]]]))