reworked main tabs using svg icons [#1748

This commit is contained in:
Andrey Shovkoplyas 2017-09-06 12:30:09 +03:00 committed by Roman Volosovskyi
parent 1a6bfd06a6
commit f88c3feaf6
12 changed files with 173 additions and 206 deletions

View File

@ -1,5 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<g fill="none" fill-rule="evenodd">
<path fill="#939BA1" fill-rule="nonzero" d="M6,5.99539757 L6,14.0046024 C6,14.5443356 6.44661595,15 6.99754465,15 L15,15 L19,18 L19,5.99539757 C19,5.4556644 18.5529553,5 18.0014977,5 L6.99850233,5 C6.44748943,5 6,5.44565467 6,5.99539757 Z M6.99754465,17 C5.34334703,17 4,15.6502334 4,14.0046024 L4,5.99539757 C4,4.33970333 5.34429545,3 6.99850233,3 L18.0014977,3 C19.6557893,3 21,4.34933905 21,5.99539757 L21,18 C21,19.7818097 18.8457162,20.6741433 17.5857864,19.4142136 L14.1715729,17 L6.99754465,17 Z M10,11.5 C10.8284271,11.5 11.5,10.8284271 11.5,10 C11.5,9.17157288 10.8284271,8.5 10,8.5 C9.17157288,8.5 8.5,9.17157288 8.5,10 C8.5,10.8284271 9.17157288,11.5 10,11.5 Z M15,11.5 C15.8284271,11.5 16.5,10.8284271 16.5,10 C16.5,9.17157288 15.8284271,8.5 15,8.5 C14.1715729,8.5 13.5,9.17157288 13.5,10 C13.5,10.8284271 14.1715729,11.5 15,11.5 Z"/> <path fill="#939BA1" fill-rule="nonzero" d="M6,5.99539757 L6,14.0046024 C6,14.5443356 6.44661595,15 6.99754465,15 L15,15 L19,18 L19,5.99539757 C19,5.4556644 18.5529553,5 18.0014977,5 L6.99850233,5 C6.44748943,5 6,5.44565467 6,5.99539757 Z M6.99754465,17 C5.34334703,17 4,15.6502334 4,14.0046024 L4,5.99539757 C4,4.33970333 5.34429545,3 6.99850233,3 L18.0014977,3 C19.6557893,3 21,4.34933905 21,5.99539757 L21,18 C21,19.7818097 18.8457162,20.6741433 17.5857864,19.4142136 L14.1715729,17 L6.99754465,17 Z M10,11.5 C10.8284271,11.5 11.5,10.8284271 11.5,10 C11.5,9.17157288 10.8284271,8.5 10,8.5 C9.17157288,8.5 8.5,9.17157288 8.5,10 C8.5,10.8284271 9.17157288,11.5 10,11.5 Z M15,11.5 C15.8284271,11.5 16.5,10.8284271 16.5,10 C16.5,9.17157288 15.8284271,8.5 15,8.5 C14.1715729,8.5 13.5,9.17157288 13.5,10 C13.5,10.8284271 14.1715729,11.5 15,11.5 Z"/>
</g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 987 B

After

Width:  |  Height:  |  Size: 940 B

View File

@ -1,5 +1,5 @@
(ns status-im.chat.views.input.animations.expandable (ns status-im.chat.views.input.animations.expandable
(:require-macros [status-im.utils.views :refer [defview]]) (:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [reagent.core :as r] (:require [reagent.core :as r]
[reagent.impl.component :as rc] [reagent.impl.component :as rc]
[re-frame.core :refer [dispatch subscribe]] [re-frame.core :refer [dispatch subscribe]]
@ -45,12 +45,12 @@
:tension 60})))))) :tension 60}))))))
(defview overlay-view [] (defview overlay-view []
[max-height (subscribe [:get-max-container-area-height]) (letsubs [max-height [:get-max-container-area-height]
layout-height (subscribe [:get :layout-height]) layout-height [:get :layout-height]
view-height-to (subscribe [:get :expandable-view-height-to-value])] view-height-to [:get :expandable-view-height-to-value]]
(let [related-height (/ @view-height-to @max-height)] (let [related-height (/ view-height-to max-height)]
(when (> related-height 0.6) (when (> related-height 0.6)
[animated-view {:style (style/result-box-overlay @layout-height (- related-height (/ 0.4 related-height)))}]))) [animated-view {:style (style/result-box-overlay layout-height (- related-height (/ 0.4 related-height)))}]))))
(defn expandable-view [{:keys [key height hide-overlay?]} & _] (defn expandable-view [{:keys [key height hide-overlay?]} & _]
(let [anim-value (anim/create-value 0) (let [anim-value (anim/create-value 0)

View File

@ -81,6 +81,8 @@
(concat (concat
[svg (merge default-viewbox style)] [svg (merge default-viewbox style)]
(icon-fn (icon-fn
(cond
(keyword? color)
(case color (case color
:dark st/icon-dark-color :dark st/icon-dark-color
:gray st/icon-gray-color :gray st/icon-gray-color
@ -88,6 +90,10 @@
:active st/color-blue4 :active st/color-blue4
:white st/color-white :white st/color-white
:red st/icon-red-color :red st/icon-red-color
st/icon-dark-color)
(string? color)
color
:else
st/icon-dark-color)))) st/icon-dark-color))))
(throw (js/Error. (str "Unknown icon: " name))))])) (throw (js/Error. (str "Unknown icon: " name))))]))

View File

@ -1,119 +0,0 @@
(ns status-im.components.main-tabs
(:require-macros [status-im.utils.views :refer [defview]]
[cljs.core.async.macros :as am])
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[reagent.core :as r]
[status-im.components.react :refer [view swiper]]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.drawer.view :refer [drawer-view]]
[status-im.components.tabs.bottom-shadow :refer [bottom-shadow-view]]
[status-im.ui.screens.chats-list.views :refer [chats-list]]
[status-im.ui.screens.discover.views :refer [discover]]
[status-im.ui.screens.contacts.views :refer [contact-groups-list]]
[status-im.ui.screens.wallet.main.views :refer [wallet]]
[status-im.components.tabs.views :refer [tabs]]
[status-im.components.tabs.styles :as st]
[status-im.components.styles :as common-st]
[status-im.i18n :refer [label]]
[cljs.core.async :as a]))
(def tab-list
[{:view-id :wallet
:title (label :t/wallet)
:screen wallet
:icon-inactive :icon_wallet_gray
:icon-active :icon_wallet_active}
{:view-id :chat-list
:title (label :t/chats)
:screen chats-list
:icon-inactive :icon_chats
:icon-active :icon_chats_active}
{:view-id :discover
:title (label :t/discover)
:screen discover
:icon-inactive :icon_discover
:icon-active :icon_discover_active}
{:view-id :contact-list
:title (label :t/contacts)
:screen contact-groups-list
:icon-inactive :icon_contacts
:icon-active :icon_contacts_active}])
(def tab->index (reduce #(assoc %1 (:view-id %2) (count %1)) {} tab-list))
(def index->tab (clojure.set/map-invert tab->index))
(defn get-tab-index [view-id]
(get tab->index view-id 0))
(defn scroll-to [prev-view-id view-id]
(let [p (get-tab-index prev-view-id)
n (get-tab-index view-id)]
(- n p)))
(defonce scrolling? (atom false))
(defn on-scroll-end [swiped? scroll-ended view-id]
(fn [_ state]
(when @scrolling?
(a/put! scroll-ended true))
(let [{:strs [index]} (js->clj state)
new-view-id (index->tab index)]
(when-not (= view-id new-view-id)
(reset! swiped? true)
(dispatch [:navigate-to-tab new-view-id])))))
(defn start-scrolling-loop
"Loop that synchronizes tabs scrolling to avoid an inconsistent state."
[scroll-start scroll-ended]
(am/go-loop [[swiper to] (a/<! scroll-start)]
;; start scrolling
(reset! scrolling? true)
(.scrollBy swiper to)
;; lock loop until scroll ends
(a/alts! [scroll-ended (a/timeout 2000)])
(reset! scrolling? false)
(recur (a/<! scroll-start))))
(defn main-tabs []
(let [view-id (subscribe [:get :view-id])
prev-view-id (subscribe [:get :prev-view-id])
tabs-hidden? (subscribe [:tabs-hidden?])
main-swiper (r/atom nil)
swiped? (r/atom false)
scroll-start (a/chan 10)
scroll-ended (a/chan 10)
tabs-were-hidden? (atom @tabs-hidden?)]
(r/create-class
{:component-did-mount
#(start-scrolling-loop scroll-start scroll-ended)
:component-will-update
(fn []
(if @swiped?
(reset! swiped? false)
(when (and (= @tabs-were-hidden? @tabs-hidden?) @main-swiper)
(let [to (scroll-to @prev-view-id @view-id)]
(a/put! scroll-start [@main-swiper to]))))
(reset! tabs-were-hidden? @tabs-hidden?))
:display-name "main-tabs"
:reagent-render
(fn []
[view common-st/flex
[status-bar {:type (if (= @view-id :wallet) :wallet :main)}]
[view common-st/flex
[drawer-view
[view {:style common-st/flex}
[swiper (merge
(st/main-swiper @tabs-hidden?)
{:index (get-tab-index @view-id)
:loop false
:ref #(reset! main-swiper %)
:on-momentum-scroll-end (on-scroll-end swiped? scroll-ended @view-id)})
(doall
(map-indexed (fn [index {vid :view-id screen :screen}]
^{:key index} [screen (= @view-id vid)]) tab-list))]
[tabs {:style (st/tabs-container @tabs-hidden?)
:selected-view-id @view-id
:tab-list tab-list}]
(when-not @tabs-hidden?
[bottom-shadow-view])]]]])})))

View File

@ -19,6 +19,7 @@
(def color-gray5 "#d9dae1") (def color-gray5 "#d9dae1")
(def color-gray6 "#212121") (def color-gray6 "#212121")
(def color-gray7 "#9fa3b4") (def color-gray7 "#9fa3b4")
(def color-gray8 "#6E777E")
(def color-dark "#49545d") (def color-dark "#49545d")
(def color-steel "#838b91") (def color-steel "#838b91")
(def color-white "white") (def color-white "white")
@ -35,6 +36,7 @@
(def color-dark-blue-3 "#191f37") (def color-dark-blue-3 "#191f37")
(def color-light-gray "#EEF2F5") (def color-light-gray "#EEF2F5")
(def color-light-gray2 "#ececf0") (def color-light-gray2 "#ececf0")
(def color-light-gray3 "#e8ebec")
(def color-red "red") (def color-red "red")
(def color-red-2 "#d84b4b") (def color-red-2 "#d84b4b")
(def color-red-3 "#FFC1BD") (def color-red-3 "#FFC1BD")

View File

@ -1,12 +0,0 @@
(ns status-im.components.tabs.bottom-shadow
(:require [status-im.components.tabs.styles :as st]
[status-im.components.react :refer [linear-gradient]]
[status-im.utils.platform :refer [platform-specific]]))
(defn bottom-shadow-view []
(when (get-in platform-specific [:tabs :tab-shadows?])
[linear-gradient {:locations [0 0.98 1]
:colors ["rgba(24, 52, 76, 0)" "rgba(24, 52, 76, 0.085)" "rgba(24, 52, 76, 0.165)"]
:style (merge
st/bottom-gradient
(get-in platform-specific [:component-styles :bottom-gradient]))}]))

View File

@ -1,29 +1,24 @@
(ns status-im.components.tabs.styles (ns status-im.components.tabs.styles
(:require [status-im.components.styles :as st] (:require-macros [status-im.utils.styles :refer [defnstyle defstyle]])
(:require [status-im.components.styles :as styles]
[status-im.utils.platform :as p])) [status-im.utils.platform :as p]))
(def tabs-height (if p/ios? 62 56)) (def tabs-height (if p/ios? 52 56))
(def tab-height (dec tabs-height)) (def tab-height (dec tabs-height))
(def bottom-gradient
{:position :absolute
:bottom 55
:left 0
:right 0})
(defn tabs-container [hidden?] (defn tabs-container [hidden?]
{:position :absolute {:position :absolute
:bottom (if hidden? (- tabs-height) 0) :bottom (if hidden? (- tabs-height) 0)
:left 0 :left 0
:right 0 :right 0
:height tabs-height :height tabs-height
:background-color st/color-white :background-color styles/color-white
:transform [{:translateY 1}]}) :transform [{:translateY 1}]})
(def tabs-container-line (def tabs-container-line
{:border-top-width 1 {:border-top-width 1
:border-top-color "#D7D7D7"}) :border-top-color styles/color-light-gray3})
(def tabs-inner-container (def tabs-inner-container
{:flexDirection :row {:flexDirection :row
@ -38,17 +33,16 @@
:justifyContent :center :justifyContent :center
:alignItems :center}) :alignItems :center})
(defn tab-title [active?] (defnstyle tab-title [active?]
{:font-size (if-not (or active? p/ios?) 12 14) {:ios {:font-size 11}
:android {:font-size 12}
:margin-top 3
: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? styles/color-blue4 styles/color-gray8)})
(def tab-icon (defn tab-icon [active?]
{:width 24 {:color (if active? styles/color-blue4 styles/color-gray4)})
:height 24
:marginBottom 1
:align-self :center})
(def tab-container (def tab-container
{:flex 1 {:flex 1

View File

@ -1,32 +1,21 @@
(ns status-im.components.tabs.views (ns status-im.components.tabs.views
(:require-macros [status-im.utils.views :refer [defview]]) (:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] (:require [re-frame.core :refer [dispatch]]
[status-im.components.react :refer [view [status-im.components.react :as react]
animated-view [status-im.components.icons.vector-icons :as vi]
text-input [status-im.components.tabs.styles :as styles]))
text
image
touchable-highlight]]
[reagent.core :as r]
[status-im.components.tabs.styles :as st]
[status-im.components.animation :as anim]
[status-im.utils.platform :as p]))
(defn tab [{:keys [view-id title icon-active icon-inactive selected-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)]
[touchable-highlight {:style st/tab [react/touchable-highlight {:style styles/tab
:disabled active? :disabled active?
:on-press #(dispatch [:navigate-to-tab view-id])} :on-press #(dispatch [:navigate-to-tab view-id])}
[view {:style st/tab-container} [react/view {:style styles/tab-container}
(when-let [icon (if active? icon-active icon-inactive)] (when-let [icon (if active? icon-active icon-inactive)]
[view [react/view
[image {:source {:uri icon} [vi/icon icon (styles/tab-icon active?)]])
:style st/tab-icon}]]) [react/view
[view [react/text {:style (styles/tab-title active?)}
[text (merge
(if-not icon-active {:uppercase? (get-in p/platform-specific [:uppercase?])})
{:style (st/tab-title active?)
:font (if (and p/ios? active?) :medium :regular)})
title]]]])) title]]]]))
(defn- create-tab [index data selected-view-id] (defn- create-tab [index data selected-view-id]
@ -35,16 +24,15 @@
:selected-view-id selected-view-id})] :selected-view-id selected-view-id})]
[tab data])) [tab data]))
(defn- tabs-container [style children] (defview tabs-container [style children]
(let [tabs-hidden? (subscribe [:tabs-hidden?]) (letsubs [tabs-hidden? [:tabs-hidden?]]
shadows? (get-in p/platform-specific [:tabs :tab-shadows?])] [react/animated-view {:style (merge style
[animated-view {:style (merge style styles/tabs-container-line)
(when-not shadows? st/tabs-container-line)) :pointer-events (if tabs-hidden? :none :auto)}
:pointerEvents (if @tabs-hidden? :none :auto)}
children])) children]))
(defn tabs [{:keys [style tab-list selected-view-id]}] (defn tabs [{:keys [style tab-list selected-view-id]}]
[tabs-container style [tabs-container style
(into (into
[view st/tabs-inner-container] [react/view styles/tabs-inner-container]
(map-indexed #(create-tab %1 %2 selected-view-id) tab-list))]) (map-indexed #(create-tab %1 %2 selected-view-id) tab-list))])

View File

@ -0,0 +1,113 @@
(ns status-im.ui.screens.main-tabs.views
(:require-macros [status-im.utils.views :as views]
[cljs.core.async.macros :as async-macros])
(:require [re-frame.core :as re-frame]
[status-im.components.react :as react]
[status-im.components.tabs.styles :as styles]
[status-im.components.styles :as common-styles]
[status-im.i18n :as i18n]
[cljs.core.async :as async]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.drawer.view :refer [drawer-view]]
[status-im.ui.screens.chats-list.views :refer [chats-list]]
[status-im.ui.screens.discover.views :refer [discover]]
[status-im.ui.screens.contacts.views :refer [contact-groups-list]]
[status-im.ui.screens.wallet.main.views :refer [wallet]]
[status-im.components.tabs.views :refer [tabs]]))
(def tab-list
[{:view-id :wallet
:title (i18n/label :t/wallet)
:screen wallet
:icon-inactive :icons/wallet
:icon-active :icons/wallet-active}
{:view-id :chat-list
:title (i18n/label :t/chats)
:screen chats-list
:icon-inactive :icons/chats
:icon-active :icons/chats-active}
{:view-id :discover
:title (i18n/label :t/discover)
:screen discover
:icon-inactive :icons/discover
:icon-active :icons/discover-active}
{:view-id :contact-list
:title (i18n/label :t/contacts)
:screen contact-groups-list
:icon-inactive :icons/contacts
:icon-active :icons/contacts-active}])
(def tab->index (reduce #(assoc %1 (:view-id %2) (count %1)) {} tab-list))
(def index->tab (clojure.set/map-invert tab->index))
(defn get-tab-index [view-id]
(get tab->index view-id 0))
(defn scroll-to [prev-view-id view-id]
(let [p (get-tab-index prev-view-id)
n (get-tab-index view-id)]
(- n p)))
(defonce scrolling? (atom false))
(defn on-scroll-end [swiped? scroll-ended view-id]
(fn [_ state]
(when @scrolling?
(async/put! scroll-ended true))
(let [{:strs [index]} (js->clj state)
new-view-id (index->tab index)]
(when-not (= view-id new-view-id)
(reset! swiped? true)
(re-frame/dispatch [:navigate-to-tab new-view-id])))))
(defn start-scrolling-loop
"Loop that synchronizes tabs scrolling to avoid an inconsistent state."
[scroll-start scroll-ended]
(async-macros/go-loop [[swiper to] (async/<! scroll-start)]
;; start scrolling
(reset! scrolling? true)
(.scrollBy swiper to)
;; lock loop until scroll ends
(async/alts! [scroll-ended (async/timeout 2000)])
(reset! scrolling? false)
(recur (async/<! scroll-start))))
(views/defview main-tabs []
(views/letsubs [view-id [:get :view-id]
prev-view-id [:get :prev-view-id]
tabs-hidden? [:tabs-hidden?]
main-swiper (atom nil)
swiped? (atom false)
scroll-start (async/chan 10)
scroll-ended (async/chan 10)
tabs-were-hidden? (atom @tabs-hidden?)]
{:component-did-mount
(fn []
(start-scrolling-loop scroll-start scroll-ended))
:component-will-update
(fn []
(if @swiped?
(reset! swiped? false)
(when (and (= @tabs-were-hidden? tabs-hidden?) @main-swiper)
(let [to (scroll-to prev-view-id view-id)]
(async/put! scroll-start [@main-swiper to]))))
(reset! tabs-were-hidden? tabs-hidden?))}
[react/view common-styles/flex
[status-bar {:type (if (= view-id :wallet) :wallet :main)}]
[react/view common-styles/flex
[drawer-view
[react/view {:style common-styles/flex}
[react/swiper
(merge
(styles/main-swiper tabs-hidden?)
{:index (get-tab-index view-id)
:loop false
:ref #(reset! main-swiper %)
:on-momentum-scroll-end (on-scroll-end swiped? scroll-ended view-id)})
(doall
(map-indexed (fn [index {vid :view-id screen :screen}]
^{:key index} [screen (= view-id vid)]) tab-list))]
[tabs {:style (styles/tabs-container tabs-hidden?)
:selected-view-id view-id
:tab-list tab-list}]]]]]))

View File

@ -4,7 +4,7 @@
[status-im.utils.platform :refer [android?]] [status-im.utils.platform :refer [android?]]
[status-im.components.react :refer [view modal]] [status-im.components.react :refer [view modal]]
[status-im.components.styles :as common-styles] [status-im.components.styles :as common-styles]
[status-im.components.main-tabs :refer [main-tabs]] [status-im.ui.screens.main-tabs.views :refer [main-tabs]]
[status-im.components.context-menu :refer [menu-context]] [status-im.components.context-menu :refer [menu-context]]
[status-im.ui.screens.accounts.login.views :refer [login]] [status-im.ui.screens.accounts.login.views :refer [login]]
@ -60,12 +60,10 @@
(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 main-tabs (:wallet :chat-list :discover :contact-list) main-tabs
:wallet-list wallet-list-screen :wallet-list wallet-list-screen
:wallet-send-transaction send-transaction :wallet-send-transaction send-transaction
:discover main-tabs
:discover-search-results discover-search-results :discover-search-results discover-search-results
:chat-list main-tabs
:new-chat new-chat :new-chat new-chat
:new-group new-group :new-group new-group
:edit-contact-group edit-contact-group :edit-contact-group edit-contact-group
@ -75,7 +73,6 @@
:edit-group-contact-list edit-contact-group-contact-list :edit-group-contact-list edit-contact-group-contact-list
:edit-chat-group-contact-list edit-chat-group-contact-list :edit-chat-group-contact-list edit-chat-group-contact-list
:new-public-chat new-public-chat :new-public-chat new-public-chat
:contact-list main-tabs
:contact-toggle-list contact-toggle-list :contact-toggle-list contact-toggle-list
:group-contacts contact-list :group-contacts contact-list
:reorder-groups reorder-groups :reorder-groups reorder-groups

View File

@ -4,7 +4,7 @@
(defn atom? [sub] (defn atom? [sub]
(or (vector? sub) (or (vector? sub)
(and (seq sub) (and (seq sub)
(#{'atom `reagent.core/atom} (first sub))))) (#{`reagent.core/atom} (first sub)))))
(defn walk-sub [sub form->sym] (defn walk-sub [sub form->sym]
(if (coll? sub) (if (coll? sub)

View File

@ -1,6 +1,6 @@
(ns status-im.test.components.main-tabs (ns status-im.test.components.main-tabs
(:require [cljs.test :refer-macros [deftest is testing]] (:require [cljs.test :refer-macros [deftest is testing]]
[status-im.components.main-tabs :as tabs])) [status-im.ui.screens.main-tabs.views :as tabs]))
(deftest tab->index (deftest tab->index
(is (contains? tabs/tab->index :chat-list)) (is (contains? tabs/tab->index :chat-list))