Bottom tab animation on leaving/entering main tabs

This commit is contained in:
Roman Volosovskyi 2019-02-28 18:55:56 +02:00
parent de5c23d39f
commit a30c379a87
No known key found for this signature in database
GPG Key ID: 0238A4B5ECEE70DE
14 changed files with 344 additions and 191 deletions

View File

@ -38,7 +38,7 @@
"keyboardDidHide"
(fn [_]
(dispatch [:show-tab-bar])
(when (zero? @keyboard-height)
(when-not (zero? @keyboard-height)
(dispatch [:set :keyboard-height 0]))))
(.hide react/splash-screen)
(.addEventListener react/app-state "change" app-state-change-handler)

View File

@ -7,7 +7,8 @@
[status-im.chat.db :as chat.db]
[status-im.models.transactions :as transactions]
[status-im.utils.platform :as platform]
[status-im.ui.components.bottom-bar.styles :as tabs-styles]))
[status-im.ui.components.bottom-bar.styles :as tabs-styles]
[status-im.ui.components.bottom-bar.styles :as tabs.styles]))
(re-frame/reg-sub ::chats :chats)
(re-frame/reg-sub ::access-scope->command-id :access-scope->command-id)
@ -77,10 +78,13 @@
(fn [kb-height]
(cond
(and platform/iphone-x? (> kb-height 0))
(- kb-height 34 tabs-styles/tabs-height)
platform/ios? (- kb-height (if (> kb-height 0)
tabs-styles/tabs-height
0))
(- kb-height (* 2 tabs.styles/minimized-tabs-height))
platform/ios?
(+ kb-height (- (if (> kb-height 0)
tabs.styles/minimized-tabs-height
0)))
:default 0)))
(re-frame/reg-sub

View File

@ -56,4 +56,6 @@
(defn get-layout [value-xy]
(js->clj (.getLayout value-xy)))
(defn easing [] js/ReactNative.Easing)
(defn easing [] js/ReactNative.Easing)
(defn cubic [] (.-cubic (easing)))

View File

@ -10,16 +10,22 @@
[status-im.i18n :as i18n]
[re-frame.core :as re-frame]))
(defonce visible? (animation/create-value 1))
(defonce last-to-value (atom 1))
(defn animate
([visible duration to]
(animate visible duration to nil))
([visible duration to callback]
(animation/start
(animation/timing visible
{:toValue to
:duration duration
:useNativeDriver true})
callback)))
(when (not= to @last-to-value)
(reset! last-to-value to)
(animation/start
(animation/timing visible
{:toValue to
:duration duration
:easing (animation/cubic)
:useNativeDriver true})
callback))))
(def tabs-list-data
[{:nav-stack :chat-stack
@ -80,17 +86,64 @@
:active? (= current-view-id nav-stack)
:nav-stack nav-stack}])]])
(defn tabs-animation-wrapper [visible? keyboard-shown? tab]
[react/animated-view
{:style (tabs.styles/animated-container visible? keyboard-shown?)}
[react/safe-area-view [tabs tab]]])
(defn main-tab? [view-id]
(contains?
#{:home :wallet :dapps :my-profile :wallet-onboarding-setup}
view-id))
(defn minimize-bar [view-id]
(if (main-tab? view-id)
(animate visible? 150 1)
(animate visible? 150 tabs.styles/minimized-tab-ratio)))
(defn tabs-animation-wrapper-ios
[content]
[react/view
[react/view
{:style tabs.styles/title-cover-wrapper}
content
(when platform/iphone-x?
[react/view
{:style tabs.styles/ios-titles-cover}])]
[react/safe-area-view {:flex 1}]])
(defn tabs-animation-wrapper-android
[keyboard-shown? view-id content]
[react/view
{:style (tabs.styles/animation-wrapper
keyboard-shown?
(main-tab? view-id))}
[react/view
{:style tabs.styles/title-cover-wrapper}
content]])
(defn tabs-animation-wrapper [keyboard-shown? view-id tab]
(reagent.core/create-class
{:component-will-update
(fn [this new-params]
(let [old-view-id (get (.-argv (.-props this)) 2)
new-view-id (get new-params 2)]
(when (not= new-view-id old-view-id)
(minimize-bar new-view-id))))
:reagent-render
(fn [keyboard-shown? view-id tab]
(if platform/ios?
[tabs-animation-wrapper-ios
[react/animated-view
{:style (tabs.styles/animated-container visible? keyboard-shown?)}
[tabs tab]]]
[tabs-animation-wrapper-android
keyboard-shown?
view-id
[react/animated-view
{:style (tabs.styles/animated-container visible? keyboard-shown?)}
[tabs tab]]]))}))
(def disappearance-duration 150)
(def appearance-duration 100)
(defn bottom-bar [_]
(defn bottom-bar [_ view-id]
(let [keyboard-shown? (reagent/atom false)
visible? (animation/create-value 1)
listeners (atom [])]
(reagent/create-class
{:component-will-mount
@ -106,7 +159,10 @@
(.addListener react/keyboard "keyboardDidHide"
(fn []
(reset! keyboard-shown? false)
(animate visible? appearance-duration 1)))])))
(animate visible? appearance-duration
(if (main-tab? @view-id)
1
tabs.styles/minimized-tab-ratio))))])))
:component-will-unmount
(fn []
(when (not-empty @listeners)
@ -114,7 +170,7 @@
(when listener
(.remove listener)))))
:reagent-render
(fn [args]
(fn [args view-id]
(let [idx (.. (:navigation args)
-state
-index)
@ -123,6 +179,4 @@
1 :wallet-stack
2 :profile-stack
:chat-stack)]
(if platform/ios?
[react/safe-area-view [tabs tab]]
[tabs-animation-wrapper visible? @keyboard-shown? tab])))})))
[tabs-animation-wrapper @keyboard-shown? @view-id tab]))})))

View File

@ -10,6 +10,13 @@
platform/ios? 52
platform/desktop? 68))
(def minimized-tabs-height 36)
(def tabs-diff (- tabs-height minimized-tabs-height))
(def minimized-tab-ratio
(/ minimized-tabs-height tabs-height))
(def tab-height (dec tabs-height))
(def tabs-container
@ -94,10 +101,34 @@
:right 0
:background-color :white
:elevation 8
:position (when keyboard-shown? :absolute)
:position (when (or platform/ios?
keyboard-shown?)
:absolute)
:transform [{:translateY
(animation/interpolate
visible?
{:inputRange [0 1]
:outputRange [tabs-height 0]})}]})
(def ios-titles-cover
{:background-color :white
:position :absolute
:height (- tabs-height minimized-tabs-height)
:align-self :stretch
:top tabs-height
:right 0
:left 0})
(def title-cover-wrapper
{:position :absolute
:height tabs-height
:bottom (if platform/iphone-x? 34 0)
:right 0
:left 0})
(defn animation-wrapper [keyboard-shown? main-tab?]
{:height (cond
keyboard-shown? 0
main-tab? tabs-height
:else minimized-tabs-height)
:align-self :stretch})

View File

@ -25,7 +25,8 @@
[status-im.ui.screens.chat.stickers.views :as stickers]
[status-im.ui.screens.chat.styles.main :as style]
[status-im.ui.screens.chat.toolbar-content :as toolbar-content]
[status-im.utils.platform :as platform])
[status-im.utils.platform :as platform]
[status-im.utils.utils :as utils])
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn add-contact-bar [public-key]
@ -84,18 +85,18 @@
:modal? modal?
:current-public-key current-public-key)])
(def animation-duration 200)
(defview messages-view-animation [message-view]
;; smooths out appearance of message-view
(letsubs [opacity (animation/create-value 0)
duration (if platform/android? 100 200)
timeout (if platform/android? 50 0)]
(letsubs [opacity (animation/create-value 0)]
{:component-did-mount (fn [_]
(animation/start
(animation/anim-sequence
[(animation/anim-delay timeout)
(animation/spring opacity {:toValue 1
:duration duration
:useNativeDriver true})])))}
(animation/timing
opacity
{:toValue 1
:duration animation-duration
:useNativeDriver true})))}
[react/with-activity-indicator
{:style style/message-view-preview
:preview [react/view style/message-view-preview]}

View File

@ -16,7 +16,8 @@
[status-im.ui.screens.home.styles :as styles]
[status-im.ui.screens.home.views.inner-item :as inner-item]
[status-im.utils.platform :as platform]
[status-im.utils.utils :as utils])
[status-im.utils.utils :as utils]
[status-im.ui.components.bottom-bar.styles :as tabs.styles])
(:require-macros [status-im.utils.views :as views]))
(defn- toolbar [show-welcome? show-sync-state sync-state latest-block-number logged-in?]
@ -59,21 +60,25 @@
[icons/icon :main-icons/add {:color :white}])]]])
(defn home-list-item [[home-item-id home-item]]
(let [delete-action (if (:chat-id home-item)
(if (and (:group-chat home-item)
(not (:public? home-item)))
:group-chats.ui/remove-chat-pressed
:chat.ui/remove-chat)
:browser.ui/remove-browser-pressed)
inner-item-view (if (:chat-id home-item)
inner-item/home-list-chat-item-inner-view
inner-item/home-list-browser-item-inner-view)]
[list/deletable-list-item {:type :chats
:id home-item-id
:on-delete #(do
(re-frame/dispatch [:set-swipe-position :chats home-item-id false])
(re-frame/dispatch [delete-action home-item-id]))}
[inner-item-view home-item]]))
(if (= home-item-id :empty)
[react/view
{:style {:height tabs.styles/tabs-diff
:align-self :stretch}}]
(let [delete-action (if (:chat-id home-item)
(if (and (:group-chat home-item)
(not (:public? home-item)))
:group-chats.ui/remove-chat-pressed
:chat.ui/remove-chat)
:browser.ui/remove-browser-pressed)
inner-item-view (if (:chat-id home-item)
inner-item/home-list-chat-item-inner-view
inner-item/home-list-browser-item-inner-view)]
[list/deletable-list-item {:type :chats
:id home-item-id
:on-delete #(do
(re-frame/dispatch [:set-swipe-position :chats home-item-id false])
(re-frame/dispatch [delete-action home-item-id]))}
[inner-item-view home-item]])))
;;do not remove view-id and will-update or will-unmount handlers, this is how it works
(views/defview welcome [view-id]
@ -259,7 +264,8 @@
previous-position)))
(show-search!)))
false)}))
[list/flat-list {:data all-home-items
[list/flat-list {:data (conj (vec all-home-items)
[:empty {}])
:key-fn first
:end-fill-color colors/white
:on-scroll-begin-drag

View File

@ -319,4 +319,7 @@
[react/view styles/my-profile-info-container
[my-profile-settings current-account shown-account currency (nil? login-data)]]
(when (nil? login-data)
[advanced shown-account on-show-advanced])]]])))
[advanced shown-account on-show-advanced])
[react/view
{:align-self :stretch
:height 16}]]]])))

View File

@ -2,43 +2,24 @@
(:require [status-im.utils.config :as config]))
(def chat-stack
{:name :chat-stack
:screens [{:name :chat-main-stack
:screens (cond->
[:home
:chat
:profile
:new
:new-chat
:qr-scanner
:take-picture
:new-group
:add-participants-toggle-list
:contact-toggle-list
:group-chat-profile
:new-public-chat
:open-dapp
:dapp-description
:browser
:stickers
:stickers-pack]
config/hardwallet-enabled?
(concat [:hardwallet-connect :enter-pin]))
:config {:initialRouteName :home}}
:chat-modal
:show-extension-modal
:stickers-pack-modal
{:name :wallet-send-modal-stack
:screens [:wallet-send-transaction-modal
:wallet-transaction-sent-modal
:wallet-transaction-fee]
:config {:initialRouteName :wallet-send-transaction-modal}}
{:name :wallet-send-modal-stack-with-onboarding
:screens [:wallet-onboarding-setup-modal
:wallet-send-transaction-modal
:wallet-transaction-sent-modal
:wallet-transaction-fee]
:config {:initialRouteName :wallet-onboarding-setup-modal}}
:wallet-sign-message-modal]
:config {:mode :modal
:initialRouteName :chat-main-stack}})
{:name :chat-stack
:screens (cond-> [:home
:chat
:profile
:new
:new-chat
:qr-scanner
:take-picture
:new-group
:add-participants-toggle-list
:contact-toggle-list
:group-chat-profile
:new-public-chat
:open-dapp
:dapp-description
:browser
:stickers
:stickers-pack]
config/hardwallet-enabled?
(concat [:hardwallet-connect :enter-pin]))
:config {:initialRouteName :home}})

View File

@ -13,30 +13,62 @@
[status-im.ui.screens.routing.chat-stack :as chat-stack]
[status-im.ui.screens.routing.wallet-stack :as wallet-stack]
[status-im.ui.screens.routing.profile-stack :as profile-stack]
[status-im.ui.screens.routing.modals :as modals]
[status-im.ui.components.bottom-bar.core :as bottom-bar]
[status-im.ui.components.status-bar.view :as status-bar]))
[status-im.ui.components.status-bar.view :as status-bar]
[status-im.ui.components.bottom-bar.styles :as tabs.styles]))
(defn navigation-events [view-id modal?]
(defonce view-id (reagent.core/atom nil))
(defn navigation-events [current-view-id modal?]
[:> navigation/navigation-events
{:on-will-focus
(fn []
(log/debug :on-will-focus view-id)
(when (not= @view-id current-view-id)
(reset! view-id current-view-id))
(log/debug :on-will-focus current-view-id)
(when modal?
(status-bar/set-status-bar view-id))
(re-frame/dispatch [:screens/on-will-focus view-id]))
(status-bar/set-status-bar current-view-id))
(re-frame/dispatch [:screens/on-will-focus current-view-id]))
:on-did-focus
(fn []
(log/debug :on-did-focus view-id)
(when-not modal?
(status-bar/set-status-bar view-id)))}])
(status-bar/set-status-bar current-view-id)))}])
(defn wrap [view-id component]
(defn wrap
"Wraps screen with main view and adds navigation-events component"
[view-id component]
(fn []
(let [main-view (react/create-main-screen-view view-id)]
[main-view common-styles/flex
[component]
[navigation-events view-id false]])))
(if platform/ios?
[main-view (assoc common-styles/flex
:margin-bottom
(cond
;; there is no need to show bottom nav bar on
;; `intro-login-stack` screens
(contains?
intro-login-stack/all-screens
view-id)
0
;; :wallet-onboarding-setup is the only screen
;; except main tabs which requires maximised
;; bottom nav bar, that's why it requires an extra
;; bottom margin, otherwise bottom nav bar will
;; partially cover the screen
(contains?
#{:wallet-onboarding-setup}
view-id)
tabs.styles/tabs-height
:else
tabs.styles/minimized-tabs-height))
[component]
[navigation-events view-id false]]
[main-view common-styles/flex
[component]
[navigation-events view-id false]]))))
(defn wrap-modal [modal-view component]
"Wraps modal screen with necessary styling and adds :on-request-close handler
@ -73,8 +105,21 @@
(nav-reagent/stack-navigator
routes
(cond->
(merge {:headerMode "none"
:cardStyle {:backgroundColor (when platform/ios? :white)}}
(merge {:headerMode "none"
:cardStyle {:backgroundColor (when platform/ios? :white)}
:onTransitionStart (fn [n]
(let [idx (.. n
-navigation
-state
-index)
routes (.. n
-navigation
-state
-routes)]
(when (and (array? routes) (int? idx))
(let [route (aget routes idx)
route-name (keyword (.-routeName route))]
(bottom-bar/minimize-bar route-name)))))}
(prepare-config config)))))
(defn switch-navigator [routes config]
@ -125,18 +170,30 @@
(map build-screen)
(into {})))
(defn wrap-bottom-bar
[nav]
[bottom-bar/bottom-bar nav view-id])
(defn get-main-component [view-id]
(log/debug :component view-id)
(switch-navigator
(into {}
[(build-screen (intro-login-stack/intro-login-stack view-id))
[:tabs
{:screen (tab-navigator
(->> [(build-screen chat-stack/chat-stack)
(build-screen wallet-stack/wallet-stack)
(build-screen profile-stack/profile-stack)]
(into {}))
{:initialRouteName :chat-stack
:tabBarComponent (reagent.core/reactify-component
bottom-bar/bottom-bar)})}]])
[:tabs-and-modals
{:screen
(stack-navigator
(merge
{:tabs
{:screen (tab-navigator
(->> [(build-screen chat-stack/chat-stack)
(build-screen wallet-stack/wallet-stack)
(build-screen profile-stack/profile-stack)]
(into {}))
{:initialRouteName :chat-stack
:tabBarComponent (reagent.core/reactify-component
wrap-bottom-bar)})}}
(stack-screens modals/modal-screens))
{:mode :modal
:initialRouteName :tabs
:onTransitionStart (fn [])})}]])
{:initialRouteName :intro-login-stack}))

View File

@ -1,6 +1,19 @@
(ns status-im.ui.screens.routing.intro-login-stack
(:require [status-im.utils.config :as config]))
(def all-screens
#{:login
:progress
:create-account
:recover
:accounts
:intro
:hardwallet-authentication-method
:hardwallet-connect
:enter-pin
:hardwallet-setup
:hardwallet-success})
(defn intro-login-stack [view-id]
{:name :intro-login-stack
:screens (cond-> [:login

View File

@ -0,0 +1,23 @@
(ns status-im.ui.screens.routing.modals)
(def modal-screens
[{:name :wallet-send-modal-stack
:screens [:wallet-send-transaction-modal
:wallet-transaction-sent-modal
:wallet-transaction-fee]
:config {:initialRouteName :wallet-send-transaction-modal}}
{:name :wallet-send-modal-stack-with-onboarding
:screens [:wallet-onboarding-setup-modal
:wallet-send-transaction-modal
:wallet-transaction-sent-modal
:wallet-transaction-fee]
:config {:initialRouteName :wallet-onboarding-setup-modal}}
:chat-modal
:show-extension-modal
:stickers-pack-modal
:wallet-sign-message-modal
:selection-modal-screen
:wallet-settings-assets
:wallet-transaction-fee
:wallet-transactions-filter
:profile-qr-viewer])

View File

@ -3,43 +3,39 @@
(def profile-stack
{:name :profile-stack
:screens [{:name :main-profile-stack
:screens (cond-> [:my-profile
:contacts-list
:blocked-users-list
:profile-photo-capture
:about-app
:bootnodes-settings
:installations
:edit-bootnode
:offline-messaging-settings
:edit-mailserver
:help-center
:dapps-permissions
:manage-dapps-permissions
:extensions-settings
:edit-extension
:show-extension
:network-settings
:network-details
:edit-network
:log-level-settings
:fleet-settings
:currency-settings
:mobile-network-settings
:backup-seed
:tribute-to-talk
:qr-scanner]
:screens (cond-> [:my-profile
:contacts-list
:blocked-users-list
:profile-photo-capture
:about-app
:bootnodes-settings
:installations
:edit-bootnode
:offline-messaging-settings
:edit-mailserver
:help-center
:dapps-permissions
:manage-dapps-permissions
:extensions-settings
:edit-extension
:show-extension
:network-settings
:network-details
:edit-network
:log-level-settings
:fleet-settings
:currency-settings
:mobile-network-settings
:backup-seed
:tribute-to-talk
:qr-scanner]
config/hardwallet-enabled?
(concat [:hardwallet-authentication-method
:hardwallet-connect
:hardwallet-setup
:hardwallet-success
:keycard-settings
:reset-card
:enter-pin]))
:config {:initialRouteName :my-profile}}
:profile-qr-viewer]
:config {:mode :modal
:initialRouteName :main-profile-stack}})
config/hardwallet-enabled?
(concat [:hardwallet-authentication-method
:hardwallet-connect
:hardwallet-setup
:hardwallet-success
:keycard-settings
:reset-card
:enter-pin]))
:config {:initialRouteName :my-profile}})

View File

@ -2,42 +2,24 @@
(def wallet-stack
{:name :wallet-stack
:screens [{:name :main-wallet-stack
:screens [:wallet
:collectibles-list
:wallet-onboarding-setup
:wallet-send-transaction-chat
:contact-code
{:name :send-transaction-stack
:screens [:wallet-send-transaction
:recent-recipients
:wallet-transaction-sent
:recipient-qr-code
:wallet-send-assets]}
{:name :request-transaction-stack
:screens [:wallet-request-transaction
:wallet-send-transaction-request
:wallet-request-assets
:recent-recipients]}
:unsigned-transactions
:transactions-history
:wallet-transaction-details
:wallet-settings-hook]
:config {:initialRouteName :wallet}}
:selection-modal-screen
{:name :wallet-send-modal-stack
:screens [:wallet-send-transaction-modal
:wallet-transaction-sent-modal
:wallet-transaction-fee]
:config {:initialRouteName :wallet-send-transaction-modal}}
{:name :wallet-send-modal-stack-with-onboarding
:screens [:wallet-onboarding-setup-modal
:wallet-send-transaction-modal
:screens [:wallet
:collectibles-list
:wallet-onboarding-setup
:wallet-send-transaction-chat
:contact-code
{:name :send-transaction-stack
:screens [:wallet-send-transaction
:recent-recipients
:wallet-transaction-sent
:wallet-transaction-fee]
:config {:initialRouteName :wallet-send-modal-stack-with-onboarding}}
:wallet-settings-assets
:wallet-transaction-fee
:wallet-transactions-filter]
:config {:mode :modal
:initialRouteName :main-wallet-stack}})
:recipient-qr-code
:wallet-send-assets]}
{:name :request-transaction-stack
:screens [:wallet-request-transaction
:wallet-send-transaction-request
:wallet-request-assets
:recent-recipients]}
:unsigned-transactions
:transactions-history
:wallet-transaction-details
:wallet-settings-hook]
:config {:initialRouteName :wallet}})