Shell & Bottom Tabs Migration (#14099)

* Shell & Bottom Tabs Migration

* Added accessibility ids for elements in the new UI
This commit is contained in:
Parvesh Monu 2022-10-24 18:35:06 +05:30 committed by GitHub
parent 38c95804ae
commit 0ff6fb25f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 546 additions and 671 deletions

View File

@ -1,4 +1,4 @@
import { useDerivedValue } from 'react-native-reanimated'; import { useDerivedValue, withTiming, withSequence, withDelay, Easing } from 'react-native-reanimated';
// Generic Worklets // Generic Worklets
@ -32,38 +32,99 @@ export function applyAnimationsToStyle(animations, style) {
// Switcher Worklets // Switcher Worklets
export function switcherCloseButtonOpacity (switcherButtonOpacity) { export function stackOpacity (stackId, selectedStackId) {
return useDerivedValue( return useDerivedValue(
function () { function () {
'worklet' 'worklet'
return 1 - switcherButtonOpacity.value; return selectedStackId.value == stackId ? 1 : 0;
} }
); );
} }
export function switcherScreenRadius (switcherScreenSize) { export function stackPointer (stackId, selectedStackId) {
return useDerivedValue( return useDerivedValue(
function () { function () {
'worklet' '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( return useDerivedValue(
function () { function () {
'worklet' '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( return useDerivedValue(
function () { function () {
'worklet' '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);
} }
); );
} }

View File

@ -56,6 +56,8 @@
{:keyboardVerticalOffset (+ 44 (:status-bar-height @navigation-const))})] {:keyboardVerticalOffset (+ 44 (:status-bar-height @navigation-const))})]
(reagent/children this)))) (reagent/children this))))
(def status-bar (.-StatusBar ^js rn))
(def keyboard (.-Keyboard ^js rn)) (def keyboard (.-Keyboard ^js rn))
(def dismiss-keyboard! #(.dismiss ^js keyboard)) (def dismiss-keyboard! #(.dismiss ^js keyboard))

View File

@ -62,9 +62,10 @@
(let [pressed? (reagent/atom false)] (let [pressed? (reagent/atom false)]
(fn [{:keys [type on-press count customization-color style]}] (fn [{:keys [type on-press count customization-color style]}]
[rn/touchable-without-feedback [rn/touchable-without-feedback
{:on-press-in #(reset! pressed? true) {:on-press-in #(reset! pressed? true)
:on-press-out #(reset! pressed? false) :on-press-out #(reset! pressed? false)
:on-press on-press} :on-press on-press
:accessibility-label type}
[rn/view {:style (merge [rn/view {:style (merge
{:flex-direction :row {:flex-direction :row
:height 24 :height 24

View File

@ -1,71 +1,77 @@
(ns quo2.components.navigation.bottom-nav-tab (ns quo2.components.navigation.bottom-nav-tab
(:require [quo.react-native :as rn] (:require [quo.react-native :as rn]
[reagent.core :as reagent] [quo2.reanimated :as reanimated]
[quo2.foundations.colors :as colors] [quo2.foundations.colors :as colors]
[quo2.components.icon :as icon] [quo2.components.icons.icons :as icons]
[quo2.components.counter.counter :as counter])) [quo2.components.counter.counter :as counter]))
(defn toggle-background-color [background-color press-out? pass-through?] (defn toggle-background-color [background-color press-out? pass-through?]
(let [color (cond (reanimated/set-shared-value
press-out? nil background-color
pass-through? colors/white-opa-5 (cond
:else colors/neutral-70)] press-out? "transparent"
(reset! background-color color))) pass-through? colors/white-opa-5
:else colors/neutral-70)))
(defn bottom-nav-tab (defn bottom-nav-tab
"[bottom-nav-tab opts] "[bottom-nav-tab opts]
opts opts
{:icon :main-icons2/communities {:icon :main-icons2/communities
:selected? true/false
:new-notifications? true/false :new-notifications? true/false
:notification-indicator :unread-dot/:counter :notification-indicator :unread-dot/:counter
:counter-label number :counter-label number
:on-press bottom-tab on-press function :on-press bottom-tab on-press function
:pass-through? true/false :pass-through? true/false
:icon-color-anim reanimated shared value
" "
[_] [{:keys [icon new-notifications? notification-indicator counter-label
(let [background-color (reagent/atom nil)] on-press pass-through? icon-color-anim accessibility-label]}]
(fn [{:keys [icon selected? new-notifications? notification-indicator counter-label on-press pass-through?]}] [:f>
[rn/touchable-without-feedback (fn []
{:on-press on-press (let [icon-animated-style (reanimated/apply-animations-to-style
:on-press-in #(toggle-background-color background-color false pass-through?) {:tint-color icon-color-anim}
:on-press-out #(toggle-background-color background-color true pass-through?)} {:width 24
[rn/view {:style {:width 90 :height 24})
:height 40 background-color (reanimated/use-shared-value "transparent")
:background-color @background-color background-animated-style (reanimated/apply-animations-to-style
:border-radius 10}} {:background-color background-color}
[rn/hole-view {:style {:padding-left 33 {:width 90
:padding-top 8} :height 40
:key new-notifications? ;; Key is required to force removal of holes :border-radius 10})]
:holes (cond [rn/touchable-without-feedback
(not new-notifications?) ;; No new notifications, remove holes {: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) (= notification-indicator :unread-dot)
[{:x 50 :y 5 :width 10 :height 10 :borderRadius 5}] [{:x 50 :y 5 :width 10 :height 10 :borderRadius 5}]
:else :else
[{:x 47 :y 1 :width 18 :height 18 :borderRadius 7}])} [{:x 47 :y 1 :width 18 :height 18 :borderRadius 7}])}
[icon/icon [reanimated/image
icon {:style icon-animated-style
{:size 24 :source (icons/icon-source (keyword (str icon 24)))}]]
:color (cond (when new-notifications?
selected? colors/white (if (= notification-indicator :counter)
pass-through? colors/white-opa-40 [counter/counter {:outline false
:else colors/neutral-50)}]] :override-text-color colors/white
(when new-notifications? :override-bg-color colors/primary-50
(if (= notification-indicator :counter) :style {:position :absolute
[counter/counter {:outline false :left 48
:override-text-color colors/white :top 2}}
:override-bg-color colors/primary-50 counter-label]
:style {:position :absolute [rn/view {:style {:width 8
:left 48 :height 8
:top 2}} :border-radius 4
counter-label] :top 6
[rn/view {:style {:width 8 :left 51
:height 8 :position :absolute
:border-radius 4 :background-color colors/primary-50}}]))]]))])
:top 6
:left 51
:position :absolute
:background-color colors/primary-50}}]))]])))

View File

@ -44,8 +44,7 @@
open-scanner show-qr open-activity-center style avatar counter-label]}] open-scanner show-qr open-activity-center style avatar counter-label]}]
(let [button-common-props (get-button-common-props type)] (let [button-common-props (get-button-common-props type)]
[rn/view {:style (merge [rn/view {:style (merge
{:height 56 {:height 56}
:flex 1}
style)} style)}
;; Left Section ;; Left Section
[rn/touchable-without-feedback {:on-press open-profile} [rn/touchable-without-feedback {:on-press open-profile}

View File

@ -16,14 +16,16 @@
(defn open-reactions-menu (defn open-reactions-menu
[{:keys [on-press]}] [{:keys [on-press]}]
(let [dark? (theme/dark?)] (let [dark? (theme/dark?)]
[rn/touchable-opacity {:on-press on-press [rn/touchable-opacity
:style (merge reaction-styling {:on-press on-press
:accessibility-label :emoji-reaction-add
:style (merge reaction-styling
{:padding-horizontal 9 {:padding-horizontal 9
:border-width 1 :border-width 1
:margin-top 5 :margin-top 5
:border-color (if dark? :border-color (if dark?
colors/neutral-70 colors/neutral-70
colors/neutral-30)})} colors/neutral-30)})}
[icons/icon :main-icons2/add [icons/icon :main-icons2/add
{:size 20 {:size 20
:color (if dark? :color (if dark?
@ -32,26 +34,30 @@
(defn reaction (defn reaction
"Add your emoji as a param here" "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?) (let [dark? (theme/dark?)
text-color (if dark? colors/white text-color (if dark? colors/white
colors/neutral-100) colors/neutral-100)
numeric-value (int clicks) numeric-value (int clicks)
clicks-positive? (pos? numeric-value)] clicks-positive? (pos? numeric-value)]
[rn/touchable-opacity {:on-press on-press [rn/touchable-opacity
:style (merge reaction-styling {:on-press on-press
(cond-> {:background-color :accessibility-label accessibility-label
(if dark? :style (merge reaction-styling
(if neutral? (cond-> {:background-color
colors/neutral-70 (if dark?
:transparent) (if neutral?
(if neutral? colors/neutral-70
colors/neutral-30 :transparent)
:transparent))} (if neutral?
(and dark? (not neutral?)) (assoc :border-color colors/neutral-70 colors/neutral-30
:border-width 1) :transparent))}
(and (not dark?) (not neutral?)) (assoc :border-color colors/neutral-30 (and dark? (not neutral?))
:border-width 1)))} (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 [icons/icon emoji {:no-color true
:size 16}] :size 16}]
[quo2.text/text {:size :paragraph-2 [quo2.text/text {:size :paragraph-2

View File

@ -45,7 +45,6 @@
;;Blur ;;Blur
(def neutral-5-opa-70 (alpha neutral-5 0.7)) (def neutral-5-opa-70 (alpha neutral-5 0.7))
(def neutral-90-opa-70 (alpha neutral-90 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 ;;80 with transparency
(def neutral-80-opa-5 (alpha neutral-80 0.05)) (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-90 (alpha neutral-80 0.9))
(def neutral-80-opa-95 (alpha neutral-80 0.95)) (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 ;;;;White
;;Solid ;;Solid

View File

@ -2,6 +2,7 @@
(:require [quo.react-native :as rn] (:require [quo.react-native :as rn]
[quo.previews.preview :as preview] [quo.previews.preview :as preview]
[reagent.core :as reagent] [reagent.core :as reagent]
[quo2.reanimated :as reanimated]
[quo2.components.navigation.bottom-nav-tab :as quo2] [quo2.components.navigation.bottom-nav-tab :as quo2]
[quo2.foundations.colors :as colors])) [quo2.foundations.colors :as colors]))
@ -36,21 +37,34 @@
:key :counter-label :key :counter-label
:type :text}]) :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 [] (defn cool-preview []
(let [state (reagent/atom {:icon :main-icons2/communities (let [state (reagent/atom {:icon :main-icons2/communities
:selected? true :new-notifications? true
:pass-through? true :notification-indicator :counter
:new-notifications? true :counter-label 8
:notification-indicator :counter :preview-label-color colors/white})
:counter-label 8 selected? (reagent/cursor state [:selected?])
:preview-label-color colors/white})] pass-through? (reagent/cursor state [:pass-through?])]
(fn [] [:f>
[rn/touchable-without-feedback {:on-press rn/dismiss-keyboard!} (fn []
[rn/view {:padding-bottom 150} (let [icon-color-anim (reanimated/use-shared-value colors/white)]
[preview/customizer state descriptor] (reanimated/set-shared-value
[rn/view {:padding-vertical 60 icon-color-anim
:align-items :center} (get-icon-color @selected? @pass-through?))
[quo2/bottom-nav-tab @state (:value @state)]]]]))) [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 [] (defn preview-bottom-nav-tab []
[rn/view {:background-color colors/neutral-100 [rn/view {:background-color colors/neutral-100

View File

@ -18,6 +18,8 @@
[status-im.utils.logging.core :as utils.logs] [status-im.utils.logging.core :as utils.logs]
[status-im.utils.platform :as platform] [status-im.utils.platform :as platform]
[status-im.utils.snoopy :as snoopy] [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])) [status-im.utils.universal-links.core :as utils.universal-links]))
(set! interop/next-tick js/setTimeout) (set! interop/next-tick js/setTimeout)
@ -39,6 +41,9 @@
(utils.universal-links/initialize) (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 ;;DEV
(snoopy/subscribe!) (snoopy/subscribe!)
(when (and js/goog.DEBUG platform/ios? DevSettings) (when (and js/goog.DEBUG platform/ios? DevSettings)

View File

@ -468,7 +468,7 @@
"Decides which root should be initialised depending on user and app state" "Decides which root should be initialised depending on user and app state"
[db] [db]
(if (get db :tos/accepted?) (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]))) (re-frame/dispatch [:init-root :tos])))
(fx/defn login-only-events (fx/defn login-only-events
@ -517,7 +517,7 @@
(logging/set-log-level (:log-level multiaccount)) (logging/set-log-level (:log-level multiaccount))
(if config/new-ui-enabled? (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 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 not a first account, the ToS might have been accepted by other account logins
(if (or first-account? tos-accepted?) (if (or first-account? tos-accepted?)

View File

@ -3,14 +3,14 @@
[status-im.reloader :as reloader] [status-im.reloader :as reloader]
[status-im.utils.datetime :as datetime])) [status-im.utils.datetime :as datetime]))
(def parent-stack (atom :home-stack)) (def parent-stack (atom :shell-stack))
(fx/defn reload-new-ui (fx/defn reload-new-ui
{:events [:reload-new-ui]} {:events [:reload-new-ui]}
[_] [_]
(reloader/reload) (reloader/reload)
{:new-ui/reset-bottom-tabs nil {:new-ui/reset-bottom-tabs nil
:dispatch [:init-root :home-stack]}) :dispatch [:init-root :shell-stack]})
(fx/defn init-root-nav2 (fx/defn init-root-nav2
{:events [:init-root-nav2]} {:events [:init-root-nav2]}
@ -27,7 +27,7 @@
[_ modal] [_ modal]
{:close-modal-fx-nav2 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) (reset! parent-stack go-to-view-id)
{:navigate-to-fx-nav2 [go-to-view-id id] {:navigate-to-fx-nav2 [go-to-view-id id]
:db (assoc-in db [:navigation2/navigation2-stacks id] {:type go-to-view-id :db (assoc-in db [:navigation2/navigation2-stacks id] {:type go-to-view-id
@ -49,8 +49,12 @@
(if from-switcher? (if from-switcher?
(navigate-from-switcher go-to-view-id id db from-home?) (navigate-from-switcher go-to-view-id id db from-home?)
(if 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 ;; 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})

View File

@ -13,8 +13,10 @@
:wallet 2 :wallet 2
:browser 3}) :browser 3})
;; (defonce set-navigation-default-options (defn change-root-status-bar-style [style]
;; (.setDefaultOptions Navigation (clj->js {:options {:topBar {:visible false}}}))) (.mergeOptions Navigation
"shell-stack"
(clj->js {:statusBar {:style style}})))
;; TODO (parvesh) - improve open-modal and close-modal ;; TODO (parvesh) - improve open-modal and close-modal
(defn open-modal [comp] (defn open-modal [comp]
@ -55,7 +57,7 @@
(let [{:keys [options]} (get views/screens comp)] (let [{:keys [options]} (get views/screens comp)]
(reset! nav2-utils/container-stack-view-id comp) (reset! nav2-utils/container-stack-view-id comp)
(.push Navigation (.push Navigation
(name :home-stack) "shell-stack"
(clj->js {:stack {:id comp (clj->js {:stack {:id comp
:children [{:component {:id comp :children [{:component {:id comp
:name comp :name comp
@ -79,3 +81,5 @@
(re-frame/reg-fx :navigate-to-fx-nav2 navigate) (re-frame/reg-fx :navigate-to-fx-nav2 navigate)
(re-frame/reg-fx :navigate-from-switcher-fx navigate-from-switcher) (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)

View File

@ -1,20 +1,19 @@
(ns status-im.navigation2.roots (ns status-im.navigation2.roots
(:require [quo.theme :as theme] (:require [quo2.foundations.colors :as colors]
[quo2.foundations.colors :as colors]
[status-im.utils.platform :as platform])) [status-im.utils.platform :as platform]))
(defn status-bar-options [] (defn status-bar-options []
(if platform/android? (if platform/android?
{:navigationBar {:backgroundColor colors/neutral-80} {:navigationBar {:backgroundColor colors/neutral-100}
:statusBar {:backgroundColor :transparent :statusBar {:backgroundColor :transparent
:style (if (theme/dark?) :light :dark) :style :light
:drawBehind true}} :drawBehind true}}
{:statusBar {:style (if (theme/dark?) :light :dark)}})) {:statusBar {:style :light}}))
(defn roots [] (defn roots []
{:home-stack {:shell-stack
{:root {:root
{:stack {:id :home-stack {:stack {:id :shell-stack
:children [{:component {:name :chat-stack :children [{:component {:name :chat-stack
:id :chat-stack :id :chat-stack
:options (merge (status-bar-options) :options (merge (status-bar-options)

View File

@ -1,15 +1,14 @@
(ns status-im.navigation2.screens (ns status-im.navigation2.screens
(:require [status-im.ui2.screens.chat.view :as chat] (:require [status-im.ui2.screens.chat.view :as chat]
[status-im.switcher.home-stack :as home-stack] [status-im.switcher.shell-stack :as shell-stack]))
[status-im.navigation2.stack-with-switcher :as stack-with-switcher]))
;; We have to use the home screen name :chat-stack for now, for compatibility with navigation.cljs ;; 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} :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 ;; These screens will overwrite navigation/screens.cljs screens on enabling new UI toggle
(def screen-overwrites (def screen-overwrites
[{:name :chat [{:name :chat
:options {:topBar {:visible false}} :options {:topBar {:visible false}}
:component #(stack-with-switcher/overlap-stack chat/chat :chat)}]) :component chat/chat}])

View File

@ -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]])

View File

@ -1,65 +1,108 @@
(ns status-im.switcher.animation (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])) [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 ;;;; 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)))))

View File

@ -2,66 +2,58 @@
(:require [quo.react-native :as rn] (:require [quo.react-native :as rn]
[reagent.core :as reagent] [reagent.core :as reagent]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[quo2.reanimated :as reanimated]
[status-im.switcher.styles :as styles] [status-im.switcher.styles :as styles]
[status-im.utils.platform :as platform]
[status-im.switcher.constants :as constants] [status-im.switcher.constants :as constants]
[status-im.switcher.animation :as animation] [status-im.switcher.animation :as animation]
[quo2.components.icon :as icons])) [quo2.components.navigation.bottom-nav-tab :as bottom-nav-tab]))
(def selected-stack-id (atom :communities-stack))
;; Reagent atoms used for lazily loading home screen tabs ;; 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-chats-tab? (reagent/atom false))
(def load-wallet-tab? (reagent/atom false)) (def load-wallet-tab? (reagent/atom false))
(def load-browser-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 (re-frame/reg-fx
:new-ui/reset-bottom-tabs :new-ui/reset-bottom-tabs
(fn [] (fn []
(reset! selected-stack-id :communities-stack) (let [selected-stack-id @animation/selected-stack-id]
(reset! load-communities-tab? true) (reset! load-communities-tab? (= selected-stack-id :communities-stack))
(reset! load-chats-tab? false) (reset! load-chats-tab? (= selected-stack-id :chats-stack))
(reset! load-wallet-tab? false) (reset! load-wallet-tab? (= selected-stack-id :wallet-stack))
(reset! load-browser-tab? false))) (reset! load-browser-tab? (= selected-stack-id :browser-stack)))))
(defn bottom-tab-on-press [shared-values stack-id] (defn bottom-tab-on-press [shared-values stack-id]
(when-not (= stack-id @selected-stack-id) (when-not (= stack-id @animation/selected-stack-id)
(reset! selected-stack-id stack-id) (let [stack-load-delay (cond
(animation/bottom-tab-on-press shared-values stack-id) @animation/home-stack-open? 0
(case stack-id platform/android? 250
:communities-stack (reset! load-communities-tab? true) :else 300)]
:chats-stack (reset! load-chats-tab? true) (animation/change-tab shared-values stack-id)
:wallet-stack (reset! load-wallet-tab? true) (js/setTimeout #(load-selected-stack stack-id) stack-load-delay))))
:browser-stack (reset! load-browser-tab? true))))
;; TODO(parvesh) - reimplement tab with counter, once design is complete (defn bottom-tab [icon stack-id shared-values]
(defn bottom-tab [icon stack-id icons-only? shared-values] [bottom-nav-tab/bottom-nav-tab
[:f> {:icon icon
(fn [] :icon-color-anim (get
(let [bottom-tab-original-style {:padding 16}] shared-values
(if icons-only? (get constants/tabs-icon-color-keywords stack-id))
[rn/touchable-opacity {:active-opacity 1 :on-press #(bottom-tab-on-press shared-values stack-id)
:style bottom-tab-original-style :accessibility-label (str (name stack-id) "-tab")}])
: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-tabs [shared-values] (defn bottom-tabs [shared-values]
[:<> (load-selected-stack @animation/selected-stack-id)
[tabs shared-values false] [rn/view {:style (styles/bottom-tabs-container false)}
[tabs shared-values true]]) [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]]])

View File

@ -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 <sub]]
[status-im.switcher.cards.styles :as styles]))
;; TODO - Add switcher close animation (fade) while opening screen from cards
;; currently dealy is added to avoid default circular animation
(defn on-press [id toggle-switcher-screen]
(js/setTimeout toggle-switcher-screen 100)
(>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 (<sub [:chats/chat id])]
[rn/touchable-without-feedback {:on-press #(on-press id toggle-switcher-screen)}
[rn/view {:style (styles/messaging-card-main-container)}
[rn/view {:style (styles/messaging-card-secondary-container)}
[text/text (styles/messaging-card-title-props) (:alias chat)]
[text/text (styles/messaging-card-subtitle-props) "Message"]
[rn/view {:style (styles/messaging-card-details-container)}
[last-message (:last-message chat)]]]
[rn/view {:style (styles/messaging-card-avatar-container)}]
[button/button (styles/messaging-card-close-button-props) :main-icons/close]]]))

View File

@ -1,103 +0,0 @@
(ns status-im.switcher.cards.styles
(:require [quo.theme :as theme]
[quo2.foundations.colors :as colors]))
(def themes
{:light {:messaging-card-container-background-color "#26A69A"
:messaging-card-secondary-container-background-color colors/white
:messaging-card-title-color colors/neutral-100
:messaging-card-subtitle-color colors/neutral-50
:messaging-card-last-message-text-color colors/neutral-100
:messaging-card-close-button-bg-color colors/white-opa-50
:messaging-card-close-button-icon-color colors/neutral-100}
:dark {:messaging-card-container-background-color "#26A69A"
:messaging-card-secondary-container-background-color colors/neutral-90
:messaging-card-title-color colors/white
:messaging-card-subtitle-color colors/neutral-40
:messaging-card-last-message-text-color colors/white
:messaging-card-close-button-bg-color colors/neutral-80-opa-60
:messaging-card-close-button-icon-color colors/white}})
(defn get-color [key]
(get-in themes [(theme/get-theme) key]))
;; Messaging Card
(defn messaging-card-main-container []
{:width 160
:height 172
:border-radius 16
:margin 8
:background-color (get-color :messaging-card-container-background-color)})
(defn messaging-card-secondary-container []
{:width 160
:height 132
:background-color (get-color :messaging-card-secondary-container-background-color)
:border-radius 16
:position :absolute
:bottom 0})
(defn messaging-card-title []
{:position :absolute
:top 32
:margin-horizontal 12
:color (get-color :messaging-card-title-color)})
(defn messaging-card-title-props []
{:size :paragraph-1
:weight :semi-bold
:number-of-lines 1
:ellipsize-mode :tail
:style (messaging-card-title)})
(defn messaging-card-subtitle []
{:position :absolute
:top 54
:margin-horizontal 12
:color (get-color :messaging-card-subtitle-color)})
(defn messaging-card-subtitle-props []
{:size :paragraph-2
:weight :medium
:style (messaging-card-subtitle)})
(defn messaging-card-details-container []
{:position :absolute
:bottom 12
:margin-horizontal 12
:width 136
:height 36})
(defn messaging-card-last-message-text []
{:color (get-color :messaging-card-last-message-text-color)})
(defn messaging-card-last-message-text-props []
{:size :paragraph-2
:weight :regular
:number-of-lines 2
:ellipsize-mode :tail
:style (messaging-card-last-message-text)})
(defn messaging-card-close-button []
{:position :absolute
:right 8
:top 8
:background-color (get-color :messaging-card-close-button-bg-color)
:icon-color (get-color :messaging-card-icon-color)})
(defn messaging-card-close-button-props []
{:size 24
:type :grey
:icon true
:on-press #(print "close pressed")
:style (messaging-card-close-button)})
(defn messaging-card-avatar-container []
{:width 48
:height 48
:border-radius 24
:position :absolute
:left 12
:top 16
:background-color :pink})

View File

@ -1,57 +1,26 @@
(ns status-im.switcher.constants (ns status-im.switcher.constants
(:require [quo.react-native :as rn] (:require [quo.react-native :as rn]
[reagent.core :as reagent]
[status-im.utils.handlers :refer [<sub]] [status-im.utils.handlers :refer [<sub]]
[status-im.utils.platform :as platform])) [status-im.utils.platform :as platform]))
;; For translucent status bar(android), dimensions/window also includes status bar's height, (defn bottom-tabs-container-height []
;; this offset is used for correctly calculating switcher position (if platform/android? 57 82))
(def switcher-height-offset
(if platform/android? (:status-bar-height @rn/navigation-const) 0))
;; extra height of switcher container for show/peek hidden cards while opening animation (defn bottom-tabs-extended-container-height []
(def switcher-container-height-padding 100) (if platform/android? 90 120))
(def switcher-button-radius 24) (def status-bar-offset
(if platform/android? (.-currentHeight ^js rn/status-bar) 0))
(def switcher-button-size
(* switcher-button-radius 2))
(def switcher-pressed-scale 0.9)
(def switcher-pressed-radius
(* switcher-pressed-scale switcher-button-radius))
(def switcher-pressed-size
(* 2 switcher-pressed-radius))
(def switcher-bottom-positions
{:android
{:home-stack 15
:chat 140}
:ios
{:home-stack 40
:chat 140}})
(defn switcher-bottom-position [view-id]
(get-in
switcher-bottom-positions
[(keyword platform/os) view-id]))
(defn switcher-pressed-bottom-position [view-id]
(+
(get-in
switcher-bottom-positions
[(keyword platform/os) view-id])
(- switcher-button-radius switcher-pressed-radius)))
;; TODO(parvesh) - use different height for android and ios(Confirm from Design)
(defn bottom-tabs-height []
(if platform/android? 55 80))
;; status bar height is not included in : the dimensions/window for devices with a notch
;; https://github.com/facebook/react-native/issues/23693#issuecomment-662860819
(defn dimensions [] (defn dimensions []
(let [{:keys [width height]} (<sub [:dimensions/window])] (let [{:keys [width height]} (<sub [:dimensions/window])]
{:width width {:width width
:height (+ height switcher-height-offset)})) :height (if (> status-bar-offset 28)
(+ height status-bar-offset)
height)}))
(def stacks-ids [:communities-stack :chats-stack :wallet-stack :browser-stack]) (def stacks-ids [:communities-stack :chats-stack :wallet-stack :browser-stack])
@ -67,8 +36,10 @@
:wallet-stack :wallet-stack-pointer :wallet-stack :wallet-stack-pointer
:browser-stack :browser-stack-pointer}) :browser-stack :browser-stack-pointer})
(def tabs-opacity-keywords (def tabs-icon-color-keywords
{:communities-stack :communities-tab-opacity {:communities-stack :communities-tab-icon-color
:chats-stack :chats-tab-opacity :chats-stack :chats-tab-icon-opacity
:wallet-stack :wallet-tab-opacity :wallet-stack :wallet-tab-icon-opacity
:browser-stack :browser-tab-opacity}) :browser-stack :browser-tab-icon-opacity})
(def pass-through? (reagent/atom false))

View File

@ -1,33 +1,36 @@
(ns status-im.switcher.home-stack (ns status-im.switcher.home-stack
(:require [quo2.reanimated :as reanimated] (:require [quo2.reanimated :as reanimated]
[status-im.utils.platform :as platform] [status-im.switcher.styles :as styles]
[status-im.switcher.switcher :as switcher] [status-im.switcher.animation :as animation]
[status-im.ui2.screens.chat.home :as chat.home]
[status-im.switcher.constants :as constants] [status-im.switcher.constants :as constants]
[status-im.ui2.screens.chat.home :as chat.home]
[status-im.switcher.bottom-tabs :as bottom-tabs] [status-im.switcher.bottom-tabs :as bottom-tabs]
[status-im.ui.screens.profile.user.views :as profile.user] [status-im.ui.screens.profile.user.views :as profile.user]
[status-im.ui.screens.communities.communities-list-redesign :as communities] [status-im.ui.screens.wallet.accounts.views :as wallet.accounts]
[status-im.ui.screens.wallet.accounts.views :as wallet.accounts])) [quo2.components.navigation.floating-shell-button :as floating-shell-button]
[status-im.ui.screens.communities.communities-list-redesign :as communities]))
(defn load-stack? [stack-id] (defn load-stack? [stack-id]
(case stack-id (case stack-id
:communities-stack @bottom-tabs/load-communities-tab? :communities-stack @bottom-tabs/load-communities-tab?
:chats-stack @bottom-tabs/load-chats-tab? :chats-stack @bottom-tabs/load-chats-tab?
:browser-stack @bottom-tabs/load-browser-tab? :browser-stack @bottom-tabs/load-browser-tab?
:wallet-stack @bottom-tabs/load-wallet-tab?)) :wallet-stack @bottom-tabs/load-wallet-tab?))
(defn stack-view [stack-id shared-values] (defn stack-view [stack-id shared-values]
(when (load-stack? stack-id) (when (load-stack? stack-id)
[:f> [:f>
(fn [] (fn []
[reanimated/view {:style (reanimated/apply-animations-to-style [reanimated/view
{:opacity (get shared-values (get constants/stacks-opacity-keywords stack-id)) {:style (reanimated/apply-animations-to-style
:pointer-events (get shared-values (get constants/stacks-pointer-keywords stack-id))} {:opacity (get shared-values (get constants/stacks-opacity-keywords stack-id))
{:top 0 :pointer-events (get shared-values (get constants/stacks-pointer-keywords stack-id))}
:bottom (if platform/ios? 79 54) {:position :absolute
:left 0 :top 0
:right 0 :bottom 0
:position :absolute})} :left 0
:right 0
:accessibility-label stack-id})}
(case stack-id (case stack-id
:communities-stack [communities/communities-list] :communities-stack [communities/communities-list]
:chats-stack [chat.home/home] :chats-stack [chat.home/home]
@ -35,29 +38,22 @@
:browser-stack [profile.user/my-profile])])])) :browser-stack [profile.user/my-profile])])]))
(defn home-stack [shared-values] (defn home-stack [shared-values]
[:<>
[stack-view :communities-stack shared-values]
[stack-view :chats-stack shared-values]
[stack-view :browser-stack shared-values]
[stack-view :wallet-stack shared-values]])
(defn home []
[:f> [:f>
(fn [] (fn []
(let [selected-stack-id @bottom-tabs/selected-stack-id (let [home-stack-original-style (styles/home-stack)
shared-values (reduce (fn [acc id] home-stack-animated-style (reanimated/apply-animations-to-style
(let [selected-tab? (= id selected-stack-id) {:top (:home-stack-top shared-values)
tab-opacity-keyword (get constants/tabs-opacity-keywords id) :left (:home-stack-left shared-values)
stack-opacity-keyword (get constants/stacks-opacity-keywords id) :opacity (:home-stack-opacity shared-values)
stack-pointer-keyword (get constants/stacks-pointer-keywords id)] :pointer-events (:home-stack-pointer shared-values)
(assoc :transform [{:scale (:home-stack-scale shared-values)}]}
acc home-stack-original-style)]
tab-opacity-keyword (reanimated/use-shared-value (if selected-tab? 1 0)) [reanimated/view {:style home-stack-animated-style}
stack-opacity-keyword (reanimated/use-shared-value (if selected-tab? 1 0)) [stack-view :communities-stack shared-values]
stack-pointer-keyword (reanimated/use-shared-value (if selected-tab? "auto" "none"))))) [stack-view :chats-stack shared-values]
{} [stack-view :browser-stack shared-values]
constants/stacks-ids)] [stack-view :wallet-stack shared-values]
[:<> [floating-shell-button/floating-shell-button
[home-stack shared-values] {:jump-to {:on-press #(animation/close-home-stack shared-values)}}
[bottom-tabs/bottom-tabs shared-values] {:position :absolute
[switcher/switcher :home-stack]]))]) :bottom 12}]]))])

View File

@ -0,0 +1,54 @@
(ns status-im.switcher.shell
(:require [quo.react-native :as rn]
[status-im.i18n.i18n :as i18n]
[quo2.foundations.colors :as colors]
[quo2.components.markdown.text :as text]
[quo.components.safe-area :as safe-area]
[quo2.components.navigation.top-nav :as top-nav]))
(defn placeholder []
[rn/view {:style {:position :absolute
:top 0
:left 0
:right 0
:bottom -1
:justify-content :center
:align-items :center
:accessibility-label :shell-placeholder-view}}
[rn/view {:style {:margin-top 12
:width 80
:height 80
:border-radius 16
:background-color colors/neutral-90}}]
[text/text {:size :heading-2
:weight :semi-bold
:style {:margin-top 20
:color colors/white}}
(i18n/label :t/shell-placeholder-title)]
[text/text {:size :paragraph-1
:weight :regular
:align :center
:style {:margin-top 8
:color colors/white}}
(i18n/label :t/shell-placeholder-subtitle)]])
(defn shell []
[safe-area/consumer
(fn [insets]
[rn/view {:style {:top 0
:left 0
:right 0
:bottom -1
:position :absolute
:background-color colors/neutral-100}}
[top-nav/top-nav {:type :shell
:style {:margin-top (:top insets)}}]
[placeholder]
[rn/scroll-view {:style {:padding-horizontal 20
:flex-direction :row}}
[text/text {:size :heading-1
:weight :semi-bold
:style {:color colors/white
:margin-top 12}}
(i18n/label :t/jump-to)]]])])

View File

@ -0,0 +1,14 @@
(ns status-im.switcher.shell-stack
(:require [status-im.switcher.shell :as shell]
[status-im.switcher.animation :as animation]
[status-im.switcher.home-stack :as home-stack]
[status-im.switcher.bottom-tabs :as bottom-tabs]))
(defn shell-stack []
[:f>
(fn []
(let [shared-values (animation/get-shared-values)]
[:<>
[shell/shell]
[bottom-tabs/bottom-tabs shared-values]
[home-stack/home-stack shared-values]]))])

View File

@ -1,89 +1,35 @@
(ns status-im.switcher.styles (ns status-im.switcher.styles
(:require [quo.theme :as theme] (:require [quo2.foundations.colors :as colors]
[quo2.foundations.colors :as colors]
[status-im.utils.platform :as platform] [status-im.utils.platform :as platform]
[status-im.switcher.constants :as constants])) [status-im.switcher.constants :as constants]))
(def themes
{:light {:bottom-tabs-bg-color colors/neutral-80
:bottom-tabs-on-scroll-bg-color colors/neutral-80-opa-80
:bottom-tabs-non-selected-tab colors/neutral-50
:bottom-tabs-selected-tab colors/white
:switcher-close-button-bg-color colors/white}
:dark {:bottom-tabs-bg-color colors/neutral-80
:bottom-tabs-on-scroll-bg-color colors/neutral-80-opa-80
:bottom-tabs-non-selected-tab colors/neutral-50
:bottom-tabs-selected-tab colors/white
:switcher-close-button-bg-color colors/white}})
(defn get-color [key]
(get-in themes [(theme/get-theme) key]))
;; Bottom Tabs ;; Bottom Tabs
(defn bottom-tab-icon [tab-state] (defn bottom-tabs-container [pass-through?]
{:size 24 {:background-color (if pass-through? colors/neutral-100-opa-70 colors/neutral-100)
:color (get-color tab-state)}) :flex 1
:align-items :center
:flex-direction :column
:height (constants/bottom-tabs-container-height)
:position :absolute
:bottom -1
:right 0
:left 0
:accessibility-label :bottom-tabs-container})
(defn bottom-tabs [icons-only?] (defn bottom-tabs []
{:background-color (if icons-only? nil (get-color :bottom-tabs-bg-color)) {:flex-direction :row
:flex-direction :row :position :absolute
:flex 1 :bottom (if platform/android? 8 34)
:justify-content :space-between :flex 1
:height (constants/bottom-tabs-height) :accessibility-label :bottom-tabs})
:position :absolute
:bottom -1
:right 0
:left 0
:padding-horizontal 16})
;; Switcher ;; Home Stack
(defn switcher-button [] (defn home-stack []
{:width constants/switcher-button-size
:height constants/switcher-button-size
:z-index 2})
(defn merge-switcher-button-common-styles [style]
(merge
{:width constants/switcher-button-size
:height constants/switcher-button-size
:border-radius constants/switcher-button-radius
:position :absolute
:z-index 2
:align-items :center
:align-self :center
:justify-content :center}
style))
(defn switcher-button-touchable [view-id]
(merge-switcher-button-common-styles
{:bottom (constants/switcher-bottom-position view-id)}))
(defn switcher-close-button []
(merge-switcher-button-common-styles
{:backgroundColor (get-color :switcher-close-button-bg-color)}))
(defn switcher-screen []
(cond-> (merge-switcher-button-common-styles
{:background-color colors/neutral-80-opa-80
:z-index 1
:overflow :hidden})
platform/android? (dissoc :background-color)
true (dissoc :justify-content)))
(defn switcher-blur-background []
(let [{:keys [width height]} (constants/dimensions)] (let [{:keys [width height]} (constants/dimensions)]
{:style {:width width {:border-bottom-left-radius 20
:height (+ height constants/switcher-container-height-padding)} :border-bottom-right-radius 20
:blur-amount 17 :background-color (colors/theme-colors colors/neutral-5 colors/neutral-95)
:overlay-color colors/neutral-80-opa-80})) :overflow :hidden
:position :absolute
(defn switcher-screen-container [] :width width
(let [{:keys [width height]} (constants/dimensions)] :height (- height (constants/bottom-tabs-container-height))}))
{:width width
:height (+ height constants/switcher-container-height-padding)
:align-items :center
:position :absolute}))
(defn switcher-switch-screen []
{:margin-top 40
:align-items :center})

View File

@ -1,80 +0,0 @@
(ns status-im.switcher.switcher
(:require [reagent.core :as reagent]
[quo2.reanimated :as reanimated]
[quo2.foundations.colors :as colors]
[status-im.switcher.styles :as styles]
[status-im.ui.components.react :as react]
[status-im.switcher.constants :as constants]
[status-im.switcher.animation :as animation]
[status-im.ui.components.icons.icons :as icons]
[status-im.react-native.resources :as resources]
[status-im.switcher.switcher-container :as switcher-container]
[quo.react-native :as rn]))
(defn switcher-button [view-id toggle-switcher-screen-fn shared-values]
[:f>
(fn []
(let [touchable-original-style (styles/switcher-button-touchable view-id)
close-button-original-style (styles/switcher-close-button)
switcher-button-original-style (styles/switcher-button)
touchable-animated-style (reanimated/apply-animations-to-style
{:transform [{:scale (:button-touchable-scale shared-values)}]}
touchable-original-style)
close-button-animated-style (reanimated/apply-animations-to-style
{:opacity (:close-button-opacity shared-values)}
close-button-original-style)
switcher-button-animated-style (reanimated/apply-animations-to-style
{:opacity (:switcher-button-opacity shared-values)}
switcher-button-original-style)]
[reanimated/touchable-opacity {:active-opacity 1
:on-press-in #(animation/switcher-touchable-on-press-in
(:button-touchable-scale shared-values))
:on-press-out toggle-switcher-screen-fn
:style touchable-animated-style}
[reanimated/view {:style close-button-animated-style}
[icons/icon :main-icons/close {:color colors/neutral-100}]]
[reanimated/image {:source (resources/get-image :switcher)
:style switcher-button-animated-style}]]))])
(defn switcher-screen [toggle-switcher-screen-fn shared-values]
[:f>
(fn []
(let [switcher-screen-original-style (styles/switcher-screen)
switcher-container-original-style (styles/switcher-screen-container)
switcher-screen-animated-style (reanimated/apply-animations-to-style
{:width (:switcher-screen-size shared-values)
:height (:switcher-screen-size shared-values)
:bottom (:switcher-screen-bottom shared-values)
:border-radius (:switcher-screen-radius shared-values)}
switcher-screen-original-style)
switcher-container-animated-style (reanimated/apply-animations-to-style
{:bottom (:switcher-container-bottom shared-values)
:transform [{:scale (:switcher-container-scale shared-values)}]}
switcher-container-original-style)]
[reanimated/view {:style switcher-screen-animated-style}
[react/blur-view (styles/switcher-blur-background)]
[reanimated/view {:style switcher-container-animated-style}
[switcher-container/tabs toggle-switcher-screen-fn]]]))])
(defn switcher [view-id]
[:f>
(fn []
(let [switcher-opened? (reagent/atom false)
switcher-button-opacity (reanimated/use-shared-value 1)
switcher-screen-size (reanimated/use-shared-value constants/switcher-pressed-size)
switcher-screen-radius (animation/switcher-screen-radius switcher-screen-size)
switcher-screen-bottom (animation/switcher-screen-bottom-position switcher-screen-radius view-id)
shared-values {:switcher-button-opacity switcher-button-opacity
:switcher-screen-size switcher-screen-size
:switcher-screen-radius switcher-screen-radius
:switcher-screen-bottom switcher-screen-bottom
:button-touchable-scale (reanimated/use-shared-value 1)
:switcher-container-scale (reanimated/use-shared-value 0.9)
:close-button-opacity (animation/switcher-close-button-opacity switcher-button-opacity)
:switcher-container-bottom (animation/switcher-container-bottom-position switcher-screen-bottom)}
toggle-switcher-screen-fn #(animation/switcher-touchable-on-press-out switcher-opened? view-id shared-values)
{:keys [keyboard-shown]} (rn/use-keyboard)]
(when-not keyboard-shown
[:<>
[switcher-screen toggle-switcher-screen-fn shared-values]
[switcher-button view-id toggle-switcher-screen-fn shared-values]])))])

View File

@ -1,31 +0,0 @@
(ns status-im.switcher.switcher-container
(:require [quo.react-native :as rn]
[status-im.switcher.cards.messaging-card :as messaging-card]
[status-im.switcher.styles :as styles]
[status-im.utils.handlers :refer [<sub]]))
;; TODO - use something like this to avoid multiple renders etc.
;; (defn switch-screen [toggle-switcher-screen]
;; (let [cards (<sub [:navigation2/switcher-cards])
;; new-cards (reduce (fn [acc card]
;; (conj acc (assoc card :toggle-switcher-screen toggle-switcher-screen)))
;; () cards)]
;; (fn []
;; [rn/view {:style (styles/switcher-switch-screen)}
;; [rn/flat-list {:width 352
;; :data new-cards
;; :render-fn messaging-card/card
;; :num-columns 2
;; :key-fn str}]])))
(defn switch-screen [toggle-switcher-screen]
(let [cards (<sub [:navigation2/switcher-cards toggle-switcher-screen])]
[rn/view {:style (styles/switcher-switch-screen)}
[rn/flat-list {:width 352
:data cards
:render-fn messaging-card/card
:num-columns 2
:key-fn str}]]))
(defn tabs [toggle-switcher-screen]
[switch-screen toggle-switcher-screen])

View File

@ -1,6 +0,0 @@
(ns status-im.switcher.utils
(:require [re-frame.core :as re-frame]))
(def switcher-container-view-id (atom nil))
(re-frame/reg-fx :switcher-container-view-id #(reset! switcher-container-view-id %))

View File

@ -10,13 +10,17 @@
(defn message-reactions [{:keys [content-type]} reactions timeline on-emoji-press on-open] (defn message-reactions [{:keys [content-type]} reactions timeline on-emoji-press on-open]
(when (seq reactions) (when (seq reactions)
[rn/view {:style (styles/reactions-row timeline (if (= content-type constants/content-type-text) text-reaction-margin-top default-reaction-margin-top))} [rn/view {:style (styles/reactions-row
timeline
(if (= content-type constants/content-type-text)
text-reaction-margin-top default-reaction-margin-top))}
(for [{:keys [own emoji-id quantity] :as emoji-reaction} reactions] (for [{:keys [own emoji-id quantity] :as emoji-reaction} reactions]
^{:key (str emoji-reaction)} ^{:key (str emoji-reaction)}
[rn/view {:style {:margin-right 6 :margin-top 5}} [rn/view {:style {:margin-right 6 :margin-top 5}}
[quo2.reaction/reaction {:emoji (get constants/reactions emoji-id) [quo2.reaction/reaction {:emoji (get constants/reactions emoji-id)
:neutral? own :neutral? own
:clicks quantity :clicks quantity
:on-press #(on-emoji-press emoji-id)}]]) :on-press #(on-emoji-press emoji-id)
:accessibility-label (str "emoji-reaction-" emoji-id)}]])
;; on-press won't work until we integrate Message Context Drawer ;; on-press won't work until we integrate Message Context Drawer
[quo2.reaction/open-reactions-menu (when @on-open {:on-press @on-open})]])) [quo2.reaction/open-reactions-menu (when @on-open {:on-press @on-open})]]))

View File

@ -30,7 +30,7 @@
:type :grey :type :grey
:icon true :icon true
:icon-no-color true :icon-no-color true
:accessibility-label :reply-cancel-button :accessibility-label (str "emoji-picker-" id)
:on-press #(do :on-press #(do
(send-emoji id) (send-emoji id)
(re-frame/dispatch [:bottom-sheet/hide]))} (re-frame/dispatch [:bottom-sheet/hide]))}
@ -73,4 +73,4 @@
:icon (:icon action) :icon (:icon action)
:on-press #(do :on-press #(do
(when on-press (on-press)) (when on-press (on-press))
(re-frame/dispatch [:bottom-sheet/hide]))}]))]]))) (re-frame/dispatch [:bottom-sheet/hide]))}]))]])))

View File

@ -48,7 +48,9 @@
(let [contact-name (<sub [:contacts/contact-name-by-identity from]) (let [contact-name (<sub [:contacts/contact-name-by-identity from])
current-public-key (<sub [:multiaccount/public-key]) current-public-key (<sub [:multiaccount/public-key])
content-type (or content-type contentType)] content-type (or content-type contentType)]
[rn/view {:style {:flex-direction :row :height (when-not pin? 24)}} [rn/view {:style {:flex-direction :row
:height (when-not pin? 24)
:accessibility-label :reply-message}}
[rn/view {:style (styles/reply-content pin?)} [rn/view {:style (styles/reply-content pin?)}
(when-not pin? (when-not pin?
;;TODO quo2 icon should be used ;;TODO quo2 icon should be used

View File

@ -148,7 +148,7 @@
(multiaccounts/displayed-photo row)]}])) (multiaccounts/displayed-photo row)]}]))
(defn chat-list-key-fn [item] (defn chat-list-key-fn [item]
(or (:chat-id item) (:public-key item))) (or (:chat-id item) (:public-key item) (:id item)))
(defn get-item-layout [_ index] (defn get-item-layout [_ index]
#js {:length 64 :offset (* 64 index) :index index}) #js {:length 64 :offset (* 64 index) :index index})

View File

@ -1822,5 +1822,7 @@
"membership": "Membership", "membership": "Membership",
"jump-to": "Jump to", "jump-to": "Jump to",
"blank-messages-text": "Your messages will be here", "blank-messages-text": "Your messages will be here",
"groups": "Groups" "groups": "Groups",
"shell-placeholder-title": "Your apps will run here",
"shell-placeholder-subtitle": "Open tabs of your communities, messages,\nwallet account and browser windows"
} }