diff --git a/src/js/worklet_factory.js b/src/js/worklet_factory.js index 8abb76d33c..8c437248ea 100644 --- a/src/js/worklet_factory.js +++ b/src/js/worklet_factory.js @@ -1,4 +1,4 @@ -import { useDerivedValue } from 'react-native-reanimated'; +import { useDerivedValue, withTiming, withSequence, withDelay, Easing } from 'react-native-reanimated'; // Generic Worklets @@ -32,38 +32,99 @@ export function applyAnimationsToStyle(animations, style) { // Switcher Worklets -export function switcherCloseButtonOpacity (switcherButtonOpacity) { +export function stackOpacity (stackId, selectedStackId) { return useDerivedValue( function () { 'worklet' - return 1 - switcherButtonOpacity.value; + return selectedStackId.value == stackId ? 1 : 0; } ); } -export function switcherScreenRadius (switcherScreenSize) { +export function stackPointer (stackId, selectedStackId) { return useDerivedValue( function () { 'worklet' - return switcherScreenSize.value/2; + return selectedStackId.value == stackId ? "auto" : "none"; } ); } -export function switcherScreenBottomPosition (switcherScreenRadius, switcherPressedRadius, initalPosition) { +export function bottomTabIconColor (stackId, selectedStackId, passThrough, selectedTabColor, defaultColor, passThroughColor) { return useDerivedValue( function () { 'worklet' - return initalPosition + switcherPressedRadius - switcherScreenRadius.value; + if (selectedStackId.value == stackId){ + return selectedTabColor; + } + else if (passThrough.value){ + return passThroughColor; + } + else { + return defaultColor; + } } ); } -export function switcherContainerBottomPosition (switcherScreenBottom, heightOffset) { + +// Home Stack + +const defaultDurationAndEasing = { + duration: 300, + easing: Easing.bezier(0, 0, 1, 1), +} + +export function homeStackOpacity (homeStackOpen) { return useDerivedValue( function () { 'worklet' - return - (switcherScreenBottom.value + heightOffset); + return withTiming(homeStackOpen.value ? 1 : 0, defaultDurationAndEasing); + } + ); +} + +export function homeStackTop (homeStackOpen, top) { + return useDerivedValue( + function () { + 'worklet' + return withTiming(homeStackOpen.value ? 0 : top, defaultDurationAndEasing); + } + ); +} + +export function homeStackLeft (selectedStackId, animateHomeStackLeft, homeStackOpen, left) { + return useDerivedValue( + function () { + 'worklet' + if (animateHomeStackLeft.value) { + var leftValue = left[selectedStackId.value]; + if (homeStackOpen.value) { + return withSequence(withTiming(leftValue, {duration: 0}), withTiming(0, defaultDurationAndEasing)) + } else { + return withTiming(leftValue, defaultDurationAndEasing); + } + } else { + return 0; + } + } + ); +} + +export function homeStackPointer (homeStackOpen) { + return useDerivedValue( + function () { + 'worklet' + return homeStackOpen.value ? "auto" : "none"; + } + ); +} + +export function homeStackScale (homeStackOpen, minimizeScale) { + return useDerivedValue( + function () { + 'worklet' + return withTiming(homeStackOpen.value ? 1 : minimizeScale, defaultDurationAndEasing); } ); } diff --git a/src/quo/react_native.cljs b/src/quo/react_native.cljs index 0c16dc2a16..1e7dcf3f3b 100644 --- a/src/quo/react_native.cljs +++ b/src/quo/react_native.cljs @@ -56,6 +56,8 @@ {:keyboardVerticalOffset (+ 44 (:status-bar-height @navigation-const))})] (reagent/children this)))) +(def status-bar (.-StatusBar ^js rn)) + (def keyboard (.-Keyboard ^js rn)) (def dismiss-keyboard! #(.dismiss ^js keyboard)) diff --git a/src/quo2/components/buttons/dynamic_button.cljs b/src/quo2/components/buttons/dynamic_button.cljs index cf02f32c71..6ed8b1325c 100644 --- a/src/quo2/components/buttons/dynamic_button.cljs +++ b/src/quo2/components/buttons/dynamic_button.cljs @@ -62,9 +62,10 @@ (let [pressed? (reagent/atom false)] (fn [{:keys [type on-press count customization-color style]}] [rn/touchable-without-feedback - {:on-press-in #(reset! pressed? true) - :on-press-out #(reset! pressed? false) - :on-press on-press} + {:on-press-in #(reset! pressed? true) + :on-press-out #(reset! pressed? false) + :on-press on-press + :accessibility-label type} [rn/view {:style (merge {:flex-direction :row :height 24 diff --git a/src/quo2/components/navigation/bottom_nav_tab.cljs b/src/quo2/components/navigation/bottom_nav_tab.cljs index b9ab3d70de..6bfecae9fa 100644 --- a/src/quo2/components/navigation/bottom_nav_tab.cljs +++ b/src/quo2/components/navigation/bottom_nav_tab.cljs @@ -1,71 +1,77 @@ (ns quo2.components.navigation.bottom-nav-tab (:require [quo.react-native :as rn] - [reagent.core :as reagent] + [quo2.reanimated :as reanimated] [quo2.foundations.colors :as colors] - [quo2.components.icon :as icon] + [quo2.components.icons.icons :as icons] [quo2.components.counter.counter :as counter])) (defn toggle-background-color [background-color press-out? pass-through?] - (let [color (cond - press-out? nil - pass-through? colors/white-opa-5 - :else colors/neutral-70)] - (reset! background-color color))) + (reanimated/set-shared-value + background-color + (cond + press-out? "transparent" + pass-through? colors/white-opa-5 + :else colors/neutral-70))) (defn bottom-nav-tab "[bottom-nav-tab opts] opts {:icon :main-icons2/communities - :selected? true/false :new-notifications? true/false :notification-indicator :unread-dot/:counter :counter-label number :on-press bottom-tab on-press function :pass-through? true/false + :icon-color-anim reanimated shared value " - [_] - (let [background-color (reagent/atom nil)] - (fn [{:keys [icon selected? new-notifications? notification-indicator counter-label on-press pass-through?]}] - [rn/touchable-without-feedback - {:on-press on-press - :on-press-in #(toggle-background-color background-color false pass-through?) - :on-press-out #(toggle-background-color background-color true pass-through?)} - [rn/view {:style {:width 90 - :height 40 - :background-color @background-color - :border-radius 10}} - [rn/hole-view {:style {:padding-left 33 - :padding-top 8} - :key new-notifications? ;; Key is required to force removal of holes - :holes (cond - (not new-notifications?) ;; No new notifications, remove holes - [] + [{:keys [icon new-notifications? notification-indicator counter-label + on-press pass-through? icon-color-anim accessibility-label]}] + [:f> + (fn [] + (let [icon-animated-style (reanimated/apply-animations-to-style + {:tint-color icon-color-anim} + {:width 24 + :height 24}) + background-color (reanimated/use-shared-value "transparent") + background-animated-style (reanimated/apply-animations-to-style + {:background-color background-color} + {:width 90 + :height 40 + :border-radius 10})] + [rn/touchable-without-feedback + {:on-press on-press + :on-press-in #(toggle-background-color background-color false pass-through?) + :on-press-out #(toggle-background-color background-color true pass-through?) + :accessibility-label accessibility-label} + [reanimated/view {:style background-animated-style} + [rn/hole-view {:style {:padding-left 33 + :padding-top 8} + :key new-notifications? ;; Key is required to force removal of holes + :holes (cond + (not new-notifications?) ;; No new notifications, remove holes + [] - (= notification-indicator :unread-dot) - [{:x 50 :y 5 :width 10 :height 10 :borderRadius 5}] + (= notification-indicator :unread-dot) + [{:x 50 :y 5 :width 10 :height 10 :borderRadius 5}] - :else - [{:x 47 :y 1 :width 18 :height 18 :borderRadius 7}])} - [icon/icon - icon - {:size 24 - :color (cond - selected? colors/white - pass-through? colors/white-opa-40 - :else colors/neutral-50)}]] - (when new-notifications? - (if (= notification-indicator :counter) - [counter/counter {:outline false - :override-text-color colors/white - :override-bg-color colors/primary-50 - :style {:position :absolute - :left 48 - :top 2}} - counter-label] - [rn/view {:style {:width 8 - :height 8 - :border-radius 4 - :top 6 - :left 51 - :position :absolute - :background-color colors/primary-50}}]))]]))) + :else + [{:x 47 :y 1 :width 18 :height 18 :borderRadius 7}])} + [reanimated/image + {:style icon-animated-style + :source (icons/icon-source (keyword (str icon 24)))}]] + (when new-notifications? + (if (= notification-indicator :counter) + [counter/counter {:outline false + :override-text-color colors/white + :override-bg-color colors/primary-50 + :style {:position :absolute + :left 48 + :top 2}} + counter-label] + [rn/view {:style {:width 8 + :height 8 + :border-radius 4 + :top 6 + :left 51 + :position :absolute + :background-color colors/primary-50}}]))]]))]) diff --git a/src/quo2/components/navigation/top_nav.cljs b/src/quo2/components/navigation/top_nav.cljs index 2698ae5079..5d32fbd204 100644 --- a/src/quo2/components/navigation/top_nav.cljs +++ b/src/quo2/components/navigation/top_nav.cljs @@ -44,8 +44,7 @@ open-scanner show-qr open-activity-center style avatar counter-label]}] (let [button-common-props (get-button-common-props type)] [rn/view {:style (merge - {:height 56 - :flex 1} + {:height 56} style)} ;; Left Section [rn/touchable-without-feedback {:on-press open-profile} diff --git a/src/quo2/components/reactions/reaction.cljs b/src/quo2/components/reactions/reaction.cljs index bae3a853e7..0104e7f661 100644 --- a/src/quo2/components/reactions/reaction.cljs +++ b/src/quo2/components/reactions/reaction.cljs @@ -16,14 +16,16 @@ (defn open-reactions-menu [{:keys [on-press]}] (let [dark? (theme/dark?)] - [rn/touchable-opacity {:on-press on-press - :style (merge reaction-styling + [rn/touchable-opacity + {:on-press on-press + :accessibility-label :emoji-reaction-add + :style (merge reaction-styling {:padding-horizontal 9 - :border-width 1 - :margin-top 5 - :border-color (if dark? - colors/neutral-70 - colors/neutral-30)})} + :border-width 1 + :margin-top 5 + :border-color (if dark? + colors/neutral-70 + colors/neutral-30)})} [icons/icon :main-icons2/add {:size 20 :color (if dark? @@ -32,26 +34,30 @@ (defn reaction "Add your emoji as a param here" - [{:keys [emoji clicks neutral? on-press]}] + [{:keys [emoji clicks neutral? on-press accessibility-label]}] (let [dark? (theme/dark?) text-color (if dark? colors/white colors/neutral-100) numeric-value (int clicks) clicks-positive? (pos? numeric-value)] - [rn/touchable-opacity {:on-press on-press - :style (merge reaction-styling - (cond-> {:background-color - (if dark? - (if neutral? - colors/neutral-70 - :transparent) - (if neutral? - colors/neutral-30 - :transparent))} - (and dark? (not neutral?)) (assoc :border-color colors/neutral-70 - :border-width 1) - (and (not dark?) (not neutral?)) (assoc :border-color colors/neutral-30 - :border-width 1)))} + [rn/touchable-opacity + {:on-press on-press + :accessibility-label accessibility-label + :style (merge reaction-styling + (cond-> {:background-color + (if dark? + (if neutral? + colors/neutral-70 + :transparent) + (if neutral? + colors/neutral-30 + :transparent))} + (and dark? (not neutral?)) + (assoc :border-color colors/neutral-70 + :border-width 1) + (and (not dark?) (not neutral?)) + (assoc :border-color colors/neutral-30 + :border-width 1)))} [icons/icon emoji {:no-color true :size 16}] [quo2.text/text {:size :paragraph-2 diff --git a/src/quo2/foundations/colors.cljs b/src/quo2/foundations/colors.cljs index 6b5bd0f969..3346850ee8 100644 --- a/src/quo2/foundations/colors.cljs +++ b/src/quo2/foundations/colors.cljs @@ -45,7 +45,6 @@ ;;Blur (def neutral-5-opa-70 (alpha neutral-5 0.7)) (def neutral-90-opa-70 (alpha neutral-90 0.7)) -(def neutral-95-opa-70 (alpha neutral-95 0.7)) ;;80 with transparency (def neutral-80-opa-5 (alpha neutral-80 0.05)) @@ -60,6 +59,20 @@ (def neutral-80-opa-90 (alpha neutral-80 0.9)) (def neutral-80-opa-95 (alpha neutral-80 0.95)) +;;95 with transparency +(def neutral-95-opa-60 (alpha neutral-95 0.6)) +(def neutral-95-opa-70 (alpha neutral-95 0.7)) +(def neutral-95-opa-80 (alpha neutral-95 0.8)) +(def neutral-95-opa-90 (alpha neutral-95 0.9)) +(def neutral-95-opa-95 (alpha neutral-95 0.95)) + +;;100 with transparency +(def neutral-100-opa-60 (alpha neutral-100 0.6)) +(def neutral-100-opa-70 (alpha neutral-100 0.7)) +(def neutral-100-opa-80 (alpha neutral-100 0.8)) +(def neutral-100-opa-90 (alpha neutral-100 0.9)) +(def neutral-100-opa-95 (alpha neutral-100 0.95)) + ;;;;White ;;Solid diff --git a/src/quo2/screens/navigation/bottom_nav_tab.cljs b/src/quo2/screens/navigation/bottom_nav_tab.cljs index 1315f58f4b..e9aa78bd03 100644 --- a/src/quo2/screens/navigation/bottom_nav_tab.cljs +++ b/src/quo2/screens/navigation/bottom_nav_tab.cljs @@ -2,6 +2,7 @@ (:require [quo.react-native :as rn] [quo.previews.preview :as preview] [reagent.core :as reagent] + [quo2.reanimated :as reanimated] [quo2.components.navigation.bottom-nav-tab :as quo2] [quo2.foundations.colors :as colors])) @@ -36,21 +37,34 @@ :key :counter-label :type :text}]) +(defn get-icon-color [selected? pass-through?] + (cond + selected? colors/white + pass-through? colors/white-opa-40 + :else colors/neutral-50)) + (defn cool-preview [] - (let [state (reagent/atom {:icon :main-icons2/communities - :selected? true - :pass-through? true - :new-notifications? true - :notification-indicator :counter - :counter-label 8 - :preview-label-color colors/white})] - (fn [] - [rn/touchable-without-feedback {:on-press rn/dismiss-keyboard!} - [rn/view {:padding-bottom 150} - [preview/customizer state descriptor] - [rn/view {:padding-vertical 60 - :align-items :center} - [quo2/bottom-nav-tab @state (:value @state)]]]]))) + (let [state (reagent/atom {:icon :main-icons2/communities + :new-notifications? true + :notification-indicator :counter + :counter-label 8 + :preview-label-color colors/white}) + selected? (reagent/cursor state [:selected?]) + pass-through? (reagent/cursor state [:pass-through?])] + [:f> + (fn [] + (let [icon-color-anim (reanimated/use-shared-value colors/white)] + (reanimated/set-shared-value + icon-color-anim + (get-icon-color @selected? @pass-through?)) + [rn/touchable-without-feedback {:on-press rn/dismiss-keyboard!} + [rn/view {:padding-bottom 150} + [preview/customizer state descriptor] + [rn/view {:padding-vertical 60 + :align-items :center} + [quo2/bottom-nav-tab + (merge @state {:icon-color-anim icon-color-anim}) + (:value @state)]]]]))])) (defn preview-bottom-nav-tab [] [rn/view {:background-color colors/neutral-100 diff --git a/src/status_im/core.cljs b/src/status_im/core.cljs index 73fbfc09c9..8ea1ede79f 100644 --- a/src/status_im/core.cljs +++ b/src/status_im/core.cljs @@ -18,6 +18,8 @@ [status-im.utils.logging.core :as utils.logs] [status-im.utils.platform :as platform] [status-im.utils.snoopy :as snoopy] + [status-im.switcher.animation :as animation] + [status-im.async-storage.core :as async-storage] [status-im.utils.universal-links.core :as utils.universal-links])) (set! interop/next-tick js/setTimeout) @@ -39,6 +41,9 @@ (utils.universal-links/initialize) + ;; TODO(parvesh) - Remove while moving functionality to status-go + (async-storage/get-item :selected-stack-id #(animation/selected-stack-id-loaded %)) + ;;DEV (snoopy/subscribe!) (when (and js/goog.DEBUG platform/ios? DevSettings) diff --git a/src/status_im/multiaccounts/login/core.cljs b/src/status_im/multiaccounts/login/core.cljs index 7173ff6250..9fc05cb6be 100644 --- a/src/status_im/multiaccounts/login/core.cljs +++ b/src/status_im/multiaccounts/login/core.cljs @@ -468,7 +468,7 @@ "Decides which root should be initialised depending on user and app state" [db] (if (get db :tos/accepted?) - (re-frame/dispatch [:init-root (if config/new-ui-enabled? :home-stack :chat-stack)]) + (re-frame/dispatch [:init-root (if config/new-ui-enabled? :shell-stack :chat-stack)]) (re-frame/dispatch [:init-root :tos]))) (fx/defn login-only-events @@ -517,7 +517,7 @@ (logging/set-log-level (:log-level multiaccount)) (if config/new-ui-enabled? - (navigation/init-root :home-stack) + (navigation/init-root :shell-stack) ;; if it's a first account, the ToS will be accepted at welcome carousel ;; if not a first account, the ToS might have been accepted by other account logins (if (or first-account? tos-accepted?) diff --git a/src/status_im/navigation2.cljs b/src/status_im/navigation2.cljs index 8307179e2d..01e4457ba1 100644 --- a/src/status_im/navigation2.cljs +++ b/src/status_im/navigation2.cljs @@ -3,14 +3,14 @@ [status-im.reloader :as reloader] [status-im.utils.datetime :as datetime])) -(def parent-stack (atom :home-stack)) +(def parent-stack (atom :shell-stack)) (fx/defn reload-new-ui {:events [:reload-new-ui]} [_] (reloader/reload) {:new-ui/reset-bottom-tabs nil - :dispatch [:init-root :home-stack]}) + :dispatch [:init-root :shell-stack]}) (fx/defn init-root-nav2 {:events [:init-root-nav2]} @@ -27,7 +27,7 @@ [_ modal] {:close-modal-fx-nav2 modal}) -(defn navigate-from-home-stack [go-to-view-id id db] +(defn navigate-from-shell-stack [go-to-view-id id db] (reset! parent-stack go-to-view-id) {:navigate-to-fx-nav2 [go-to-view-id id] :db (assoc-in db [:navigation2/navigation2-stacks id] {:type go-to-view-id @@ -49,8 +49,12 @@ (if from-switcher? (navigate-from-switcher go-to-view-id id db from-home?) (if from-home? - (navigate-from-home-stack go-to-view-id id db) + (navigate-from-shell-stack go-to-view-id id db) ;; TODO(parvesh) - new stacks created from other screens should be stacked on current stack, instead of creating new entry - (navigate-from-home-stack go-to-view-id id db))))) + (navigate-from-shell-stack go-to-view-id id db))))) +(fx/defn change-root-status-bar-style + {:events [:change-root-status-bar-style]} + [_ style] + {:change-root-status-bar-style-fx style}) diff --git a/src/status_im/navigation2/core.cljs b/src/status_im/navigation2/core.cljs index dc413caaa7..61bab6f364 100644 --- a/src/status_im/navigation2/core.cljs +++ b/src/status_im/navigation2/core.cljs @@ -13,8 +13,10 @@ :wallet 2 :browser 3}) -;; (defonce set-navigation-default-options -;; (.setDefaultOptions Navigation (clj->js {:options {:topBar {:visible false}}}))) +(defn change-root-status-bar-style [style] + (.mergeOptions Navigation + "shell-stack" + (clj->js {:statusBar {:style style}}))) ;; TODO (parvesh) - improve open-modal and close-modal (defn open-modal [comp] @@ -55,7 +57,7 @@ (let [{:keys [options]} (get views/screens comp)] (reset! nav2-utils/container-stack-view-id comp) (.push Navigation - (name :home-stack) + "shell-stack" (clj->js {:stack {:id comp :children [{:component {:id comp :name comp @@ -79,3 +81,5 @@ (re-frame/reg-fx :navigate-to-fx-nav2 navigate) (re-frame/reg-fx :navigate-from-switcher-fx navigate-from-switcher) + +(re-frame/reg-fx :change-root-status-bar-style-fx change-root-status-bar-style) diff --git a/src/status_im/navigation2/roots.cljs b/src/status_im/navigation2/roots.cljs index 1e253c586c..7a0ade1fac 100644 --- a/src/status_im/navigation2/roots.cljs +++ b/src/status_im/navigation2/roots.cljs @@ -1,20 +1,19 @@ (ns status-im.navigation2.roots - (:require [quo.theme :as theme] - [quo2.foundations.colors :as colors] + (:require [quo2.foundations.colors :as colors] [status-im.utils.platform :as platform])) (defn status-bar-options [] (if platform/android? - {:navigationBar {:backgroundColor colors/neutral-80} + {:navigationBar {:backgroundColor colors/neutral-100} :statusBar {:backgroundColor :transparent - :style (if (theme/dark?) :light :dark) + :style :light :drawBehind true}} - {:statusBar {:style (if (theme/dark?) :light :dark)}})) + {:statusBar {:style :light}})) (defn roots [] - {:home-stack + {:shell-stack {:root - {:stack {:id :home-stack + {:stack {:id :shell-stack :children [{:component {:name :chat-stack :id :chat-stack :options (merge (status-bar-options) diff --git a/src/status_im/navigation2/screens.cljs b/src/status_im/navigation2/screens.cljs index cbca1d8aed..aab483f402 100644 --- a/src/status_im/navigation2/screens.cljs +++ b/src/status_im/navigation2/screens.cljs @@ -1,15 +1,14 @@ (ns status-im.navigation2.screens (:require [status-im.ui2.screens.chat.view :as chat] - [status-im.switcher.home-stack :as home-stack] - [status-im.navigation2.stack-with-switcher :as stack-with-switcher])) + [status-im.switcher.shell-stack :as shell-stack])) ;; We have to use the home screen name :chat-stack for now, for compatibility with navigation.cljs -(def screens [{:name :chat-stack ;; TODO(parvesh) - rename to home-stack +(def screens [{:name :chat-stack ;; TODO(parvesh) - rename to shell-stack :insets {:top false} - :component home-stack/home}]) + :component shell-stack/shell-stack}]) ;; These screens will overwrite navigation/screens.cljs screens on enabling new UI toggle (def screen-overwrites [{:name :chat :options {:topBar {:visible false}} - :component #(stack-with-switcher/overlap-stack chat/chat :chat)}]) + :component chat/chat}]) diff --git a/src/status_im/navigation2/stack_with_switcher.cljs b/src/status_im/navigation2/stack_with_switcher.cljs deleted file mode 100644 index 886bfdfa75..0000000000 --- a/src/status_im/navigation2/stack_with_switcher.cljs +++ /dev/null @@ -1,10 +0,0 @@ -(ns status-im.navigation2.stack-with-switcher - (:require [quo.react-native :as rn] - [status-im.utils.platform :as platform] - [status-im.switcher.switcher :as switcher])) - -(defn overlap-stack [comp view-id] - [rn/view {:style {:flex 1 - :margin-bottom (if platform/ios? 30 0)}} - [comp] - [switcher/switcher view-id]]) diff --git a/src/status_im/switcher/animation.cljs b/src/status_im/switcher/animation.cljs index b2b3c8d92d..e246dcbab6 100644 --- a/src/status_im/switcher/animation.cljs +++ b/src/status_im/switcher/animation.cljs @@ -1,65 +1,108 @@ (ns status-im.switcher.animation - (:require [quo2.reanimated :as reanimated] + (:require [re-frame.core :as re-frame] + [quo2.reanimated :as reanimated] + [quo2.foundations.colors :as colors] + [status-im.async-storage.core :as async-storage] [status-im.switcher.constants :as constants])) -;;;; Switcher Animations - -;; Component Animations -(defn switcher-touchable-on-press-in - [touchable-scale] - (reanimated/animate-shared-value-with-timing touchable-scale constants/switcher-pressed-scale 300 :easing1)) - -(defn switcher-touchable-on-press-out [switcher-opened? view-id shared-values] - (let [{:keys [width height]} (constants/dimensions) - switcher-bottom-position (constants/switcher-pressed-bottom-position view-id) - switcher-target-radius (Math/hypot - (/ width 2) - (- height constants/switcher-pressed-radius switcher-bottom-position)) - switcher-size (* 2 switcher-target-radius)] - (reanimated/animate-shared-value-with-timing (:button-touchable-scale shared-values) 1 300 :easing1) - (if @switcher-opened? - (do - (reanimated/animate-shared-value-with-timing (:switcher-button-opacity shared-values) 1 300 :easing1) - (reanimated/animate-shared-value-with-timing (:switcher-screen-size shared-values) constants/switcher-pressed-size 300 :linear) - (reanimated/animate-shared-value-with-timing (:switcher-container-scale shared-values) 0.9 300 :linear)) - (do - (reanimated/animate-shared-value-with-timing (:switcher-button-opacity shared-values) 0 300 :easing1) - (reanimated/animate-shared-value-with-timing (:switcher-screen-size shared-values) switcher-size 300 :linear) - (reanimated/animate-shared-value-with-timing (:switcher-container-scale shared-values) 1 300 :linear))) - (swap! switcher-opened? not))) - -;; Derived Values - -(defn switcher-close-button-opacity [switcher-button-opacity] - (.switcherCloseButtonOpacity ^js reanimated/worklet-factory switcher-button-opacity)) - -(defn switcher-screen-radius [switcher-screen-size] - (.switcherScreenRadius ^js reanimated/worklet-factory switcher-screen-size)) - -(defn switcher-screen-bottom-position [switcher-screen-radius view-id] - (.switcherScreenBottomPosition ^js reanimated/worklet-factory - switcher-screen-radius - constants/switcher-pressed-radius - (constants/switcher-pressed-bottom-position view-id))) - -(defn switcher-container-bottom-position [switcher-screen-bottom] - (.switcherContainerBottomPosition ^js reanimated/worklet-factory - switcher-screen-bottom - (+ constants/switcher-container-height-padding - constants/switcher-height-offset))) - - ;;;; Bottom Tabs & Home Stack Animations +(def selected-stack-id (atom nil)) +(def home-stack-open? (atom false)) +(def pass-through? (atom false)) + +(def bottom-nav-tab-width 90) + +(defn selected-stack-id-loaded [stack-id] + (reset! selected-stack-id stack-id) + (reset! home-stack-open? (some? stack-id))) + +(defn calculate-home-stack-position [] + (let [{:keys [width height]} (constants/dimensions) + minimize-scale (/ bottom-nav-tab-width width) + empty-space-half-scale (/ (- 1 minimize-scale) 2) + left-margin (/ (- width (* 4 bottom-nav-tab-width)) 2) + left-empty-space (* empty-space-half-scale width) + top-empty-space (* empty-space-half-scale + (- height (constants/bottom-tabs-container-height)))] + {:left (reduce + (fn [acc stack-id] + (assoc acc stack-id (+ (- left-margin left-empty-space) + (* (.indexOf constants/stacks-ids stack-id) + bottom-nav-tab-width)))) + {:none 0} constants/stacks-ids) + :top (+ top-empty-space (constants/bottom-tabs-container-height)) + :scale minimize-scale})) + +(defn get-shared-values [] + (let [selected-stack-id-sv (reanimated/use-shared-value + ;; passing keywords or nil is not working with reanimated + (name (if @selected-stack-id @selected-stack-id :none))) + ;; Second shared value of selected-stack-id required to make sure stack is still visible while minimizing + selected-stack-id-sv2 (reanimated/use-shared-value + (name (if @selected-stack-id @selected-stack-id :none))) + pass-through-sv (reanimated/use-shared-value @pass-through?) + home-stack-open-sv (reanimated/use-shared-value @home-stack-open?) + animate-home-stack-left (reanimated/use-shared-value (not @home-stack-open?)) + home-stack-position (calculate-home-stack-position)] + (reduce + (fn [acc id] + (let [tabs-icon-color-keyword (get constants/tabs-icon-color-keywords id) + stack-opacity-keyword (get constants/stacks-opacity-keywords id) + stack-pointer-keyword (get constants/stacks-pointer-keywords id)] + (assoc + acc + stack-opacity-keyword (.stackOpacity + ^js reanimated/worklet-factory + (name id) selected-stack-id-sv2) + stack-pointer-keyword (.stackPointer + ^js reanimated/worklet-factory + (name id) selected-stack-id-sv2) + tabs-icon-color-keyword (.bottomTabIconColor + ^js reanimated/worklet-factory + (name id) selected-stack-id-sv pass-through-sv + colors/white colors/neutral-50 colors/white-opa-40)))) + {:selected-stack-id selected-stack-id-sv + :selected-stack-id2 selected-stack-id-sv2 + :pass-through? pass-through-sv + :home-stack-open? home-stack-open-sv + :animate-home-stack-left animate-home-stack-left + :home-stack-left (.homeStackLeft + ^js reanimated/worklet-factory + selected-stack-id-sv2 animate-home-stack-left home-stack-open-sv + (clj->js (:left home-stack-position))) + :home-stack-top (.homeStackTop + ^js reanimated/worklet-factory + home-stack-open-sv (:top home-stack-position)) + :home-stack-opacity (.homeStackOpacity + ^js reanimated/worklet-factory home-stack-open-sv) + :home-stack-pointer (.homeStackPointer + ^js reanimated/worklet-factory home-stack-open-sv) + :home-stack-scale (.homeStackScale + ^js reanimated/worklet-factory home-stack-open-sv + (:scale home-stack-position))} + constants/stacks-ids))) + +;; Animation + +(defn change-tab [shared-values stack-id] + (when-not (colors/dark?) + (js/setTimeout #(re-frame/dispatch [:change-root-status-bar-style :dark]) 300)) + (if @home-stack-open? + (reanimated/set-shared-value (:animate-home-stack-left shared-values) false) + (reset! home-stack-open? true)) + (reset! selected-stack-id stack-id) + (reanimated/set-shared-value (:selected-stack-id2 shared-values) (name stack-id)) + (reanimated/set-shared-value (:selected-stack-id shared-values) (name stack-id)) + (reanimated/set-shared-value (:home-stack-open? shared-values) true) + (async-storage/set-item! :selected-stack-id stack-id)) + +(defn close-home-stack [shared-values] + (re-frame/dispatch [:change-root-status-bar-style :light]) + (reanimated/set-shared-value (:animate-home-stack-left shared-values) true) + (reset! home-stack-open? false) + (reset! selected-stack-id nil) + (reanimated/set-shared-value (:home-stack-open? shared-values) false) + (reanimated/set-shared-value (:selected-stack-id shared-values) "none") + (async-storage/set-item! :selected-stack-id nil)) -(defn bottom-tab-on-press [shared-values selected-stack-id] - (doseq [id constants/stacks-ids] - (let [selected-tab? (= id selected-stack-id) - tab-opacity-shared-value (get shared-values (get constants/tabs-opacity-keywords id)) - stack-opacity-shared-value (get shared-values (get constants/stacks-opacity-keywords id)) - stack-pointer-shared-value (get shared-values (get constants/stacks-pointer-keywords id))] - (reanimated/animate-shared-value-with-timing tab-opacity-shared-value (if selected-tab? 1 0) 300 :easing3) - (reanimated/set-shared-value stack-pointer-shared-value (if selected-tab? "auto" "none")) - (if selected-tab? - (reanimated/animate-shared-value-with-delay stack-opacity-shared-value 1 300 :easing3 150) - (reanimated/animate-shared-value-with-timing stack-opacity-shared-value 0 300 :easing3))))) diff --git a/src/status_im/switcher/bottom_tabs.cljs b/src/status_im/switcher/bottom_tabs.cljs index 263db67800..9ec5114a26 100644 --- a/src/status_im/switcher/bottom_tabs.cljs +++ b/src/status_im/switcher/bottom_tabs.cljs @@ -2,66 +2,58 @@ (:require [quo.react-native :as rn] [reagent.core :as reagent] [re-frame.core :as re-frame] - [quo2.reanimated :as reanimated] [status-im.switcher.styles :as styles] + [status-im.utils.platform :as platform] [status-im.switcher.constants :as constants] [status-im.switcher.animation :as animation] - [quo2.components.icon :as icons])) - -(def selected-stack-id (atom :communities-stack)) + [quo2.components.navigation.bottom-nav-tab :as bottom-nav-tab])) ;; Reagent atoms used for lazily loading home screen tabs -(def load-communities-tab? (reagent/atom true)) +(def load-communities-tab? (reagent/atom false)) (def load-chats-tab? (reagent/atom false)) (def load-wallet-tab? (reagent/atom false)) (def load-browser-tab? (reagent/atom false)) +(defn load-selected-stack [stack-id] + (case stack-id + :communities-stack (reset! load-communities-tab? true) + :chats-stack (reset! load-chats-tab? true) + :wallet-stack (reset! load-wallet-tab? true) + :browser-stack (reset! load-browser-tab? true) + "")) + (re-frame/reg-fx :new-ui/reset-bottom-tabs (fn [] - (reset! selected-stack-id :communities-stack) - (reset! load-communities-tab? true) - (reset! load-chats-tab? false) - (reset! load-wallet-tab? false) - (reset! load-browser-tab? false))) + (let [selected-stack-id @animation/selected-stack-id] + (reset! load-communities-tab? (= selected-stack-id :communities-stack)) + (reset! load-chats-tab? (= selected-stack-id :chats-stack)) + (reset! load-wallet-tab? (= selected-stack-id :wallet-stack)) + (reset! load-browser-tab? (= selected-stack-id :browser-stack))))) (defn bottom-tab-on-press [shared-values stack-id] - (when-not (= stack-id @selected-stack-id) - (reset! selected-stack-id stack-id) - (animation/bottom-tab-on-press shared-values stack-id) - (case stack-id - :communities-stack (reset! load-communities-tab? true) - :chats-stack (reset! load-chats-tab? true) - :wallet-stack (reset! load-wallet-tab? true) - :browser-stack (reset! load-browser-tab? true)))) + (when-not (= stack-id @animation/selected-stack-id) + (let [stack-load-delay (cond + @animation/home-stack-open? 0 + platform/android? 250 + :else 300)] + (animation/change-tab shared-values stack-id) + (js/setTimeout #(load-selected-stack stack-id) stack-load-delay)))) -;; TODO(parvesh) - reimplement tab with counter, once design is complete -(defn bottom-tab [icon stack-id icons-only? shared-values] - [:f> - (fn [] - (let [bottom-tab-original-style {:padding 16}] - (if icons-only? - [rn/touchable-opacity {:active-opacity 1 - :style bottom-tab-original-style - :on-press #(bottom-tab-on-press shared-values stack-id)} - [reanimated/view {:style (reanimated/apply-animations-to-style - {:opacity (get - shared-values - (get constants/tabs-opacity-keywords stack-id))} - {})} - [icons/icon icon (styles/bottom-tab-icon :bottom-tabs-selected-tab)]]] - [rn/view {:style bottom-tab-original-style} - [icons/icon icon (styles/bottom-tab-icon :bottom-tabs-non-selected-tab)]])))]) - -(defn tabs [shared-values icons-only?] - [rn/view {:style (styles/bottom-tabs icons-only?)} - [bottom-tab :main-icons2/communities :communities-stack icons-only? shared-values] - [bottom-tab :main-icons2/messages :chats-stack icons-only? shared-values] - [rn/view {:width 50}] - [bottom-tab :main-icons2/wallet :wallet-stack icons-only? shared-values] - [bottom-tab :main-icons2/browser :browser-stack icons-only? shared-values]]) +(defn bottom-tab [icon stack-id shared-values] + [bottom-nav-tab/bottom-nav-tab + {:icon icon + :icon-color-anim (get + shared-values + (get constants/tabs-icon-color-keywords stack-id)) + :on-press #(bottom-tab-on-press shared-values stack-id) + :accessibility-label (str (name stack-id) "-tab")}]) (defn bottom-tabs [shared-values] - [:<> - [tabs shared-values false] - [tabs shared-values true]]) + (load-selected-stack @animation/selected-stack-id) + [rn/view {:style (styles/bottom-tabs-container false)} + [rn/view {:style (styles/bottom-tabs)} + [bottom-tab :main-icons2/communities :communities-stack shared-values] + [bottom-tab :main-icons2/messages :chats-stack shared-values] + [bottom-tab :main-icons2/wallet :wallet-stack shared-values] + [bottom-tab :main-icons2/browser :browser-stack shared-values]]]) diff --git a/src/status_im/switcher/cards/messaging_card.cljs b/src/status_im/switcher/cards/messaging_card.cljs deleted file mode 100644 index 2d476edf7a..0000000000 --- a/src/status_im/switcher/cards/messaging_card.cljs +++ /dev/null @@ -1,32 +0,0 @@ -(ns status-im.switcher.cards.messaging-card - (:require [quo.react-native :as rn] - [quo2.components.markdown.text :as text] - [status-im.constants :as constants] - [quo2.components.buttons.button :as button] - [status-im.utils.handlers :refer [>evt evt [:chat.ui/navigate-to-chat-nav2 id true])) - -;; TODO - add last message for other content types -(defn last-message [{:keys [content content-type]}] - (cond - (= constants/content-type-text content-type) - [text/text (styles/messaging-card-last-message-text-props) (:text content)])) - -(defn card [{:keys [id toggle-switcher-screen]}] - (let [chat (