From b5a96a254a6f6062254878af3d633d4a784b8081 Mon Sep 17 00:00:00 2001 From: Parvesh Monu Date: Tue, 27 Jun 2023 21:02:54 +0530 Subject: [PATCH] Implement Swipe navigation for floating screen (#16390) --- src/js/worklets/shell/floating_screen.js | 42 +++++++++++++++++- src/react_native/gesture.cljs | 4 ++ .../components/floating_screens/view.cljs | 18 +++++--- .../contexts/shell/jump_to/constants.cljs | 5 +++ .../contexts/shell/jump_to/gesture.cljs | 43 +++++++++++++++++++ src/utils/worklets/shell.cljs | 14 +++++- 6 files changed, 117 insertions(+), 9 deletions(-) create mode 100644 src/status_im2/contexts/shell/jump_to/gesture.cljs diff --git a/src/js/worklets/shell/floating_screen.js b/src/js/worklets/shell/floating_screen.js index 2e8958f37c..d3b914a9f3 100644 --- a/src/js/worklets/shell/floating_screen.js +++ b/src/js/worklets/shell/floating_screen.js @@ -1,4 +1,4 @@ -import { useDerivedValue, withTiming, withSequence, withDelay, Easing } from 'react-native-reanimated'; +import { useDerivedValue, withTiming, withSequence, withDelay, Easing, runOnJS } from 'react-native-reanimated'; import * as constants from './constants'; // Derived Values @@ -106,3 +106,43 @@ export function screenBorderRadius(screenState) { } }); } + +export function screenGestureOnUpdate(screenLeft) { + return function (event) { + 'worklet'; + const absoluteX = event.absoluteX; + if (absoluteX !== null) { + screenLeft.value = event.absoluteX; + } + }; +} + +export function screenGestureOnEnd(data) { + return function (event) { + 'worklet'; + + const { screenLeft, screenState, screenWidth, leftVelocity, rightVelocity, screenClosedCallback } = data; + const absoluteX = event.absoluteX ?? 0; + const velocityX = event.velocityX ?? 0; + const closeScreen = velocityX > rightVelocity || (velocityX > leftVelocity && absoluteX >= screenWidth / 2); + + // Velocity (points/sec) = Distance/time + var animationVelocity = (screenWidth * 1000) / constants.SHELL_ANIMATION_TIME; + + if (Math.abs(velocityX) > animationVelocity) { + animationVelocity = velocityX; // Faster fling + } + + const newDistance = closeScreen ? screenWidth - absoluteX : absoluteX; + const animationTime = (newDistance * 1000) / animationVelocity; + + screenLeft.value = withTiming(closeScreen ? screenWidth : 0, { + duration: animationTime, + easing: Easing.bezier(0, 0, 0.58, 1), + }); + + if (closeScreen) { + runOnJS(screenClosedCallback)(animationTime); + } + }; +} diff --git a/src/react_native/gesture.cljs b/src/react_native/gesture.cljs index d86a841ba1..89110879e4 100644 --- a/src/react_native/gesture.cljs +++ b/src/react_native/gesture.cljs @@ -37,6 +37,10 @@ (defn min-distance [gesture dist] (.minDistance ^js gesture dist)) +(defn fail-offset-x [gesture offset] (.failOffsetX ^js gesture offset)) + +(defn hit-slop [gesture settings] (.hitSlop ^js gesture settings)) + (defn number-of-taps [gesture count] (.numberOfTaps ^js gesture count)) (defn enabled [gesture enabled?] (.enabled ^js gesture enabled?)) diff --git a/src/status_im2/contexts/shell/jump_to/components/floating_screens/view.cljs b/src/status_im2/contexts/shell/jump_to/components/floating_screens/view.cljs index fc48d7bd81..f9d3896214 100644 --- a/src/status_im2/contexts/shell/jump_to/components/floating_screens/view.cljs +++ b/src/status_im2/contexts/shell/jump_to/components/floating_screens/view.cljs @@ -1,14 +1,16 @@ (ns status-im2.contexts.shell.jump-to.components.floating-screens.view (:require [utils.re-frame :as rf] [react-native.core :as rn] + [react-native.gesture :as gesture] [react-native.reanimated :as reanimated] + [status-im2.contexts.chat.messages.view :as chat] [status-im2.contexts.shell.jump-to.state :as state] [status-im2.contexts.shell.jump-to.utils :as utils] - [status-im2.contexts.chat.messages.view :as chat] [status-im2.contexts.shell.jump-to.animation :as animation] + [status-im2.contexts.shell.jump-to.gesture :as shell.gesture] [status-im2.contexts.shell.jump-to.constants :as shell.constants] - [status-im2.contexts.shell.jump-to.components.floating-screens.style :as style] - [status-im2.contexts.communities.overview.view :as communities.overview])) + [status-im2.contexts.communities.overview.view :as communities.overview] + [status-im2.contexts.shell.jump-to.components.floating-screens.style :as style])) (def screens-map {shell.constants/community-screen communities.overview/overview @@ -23,10 +25,12 @@ [animation id]) [reanimated/view {:style (style/screen (get @state/shared-values-atom screen-id))} - [rn/view - {:style (style/screen-container (utils/dimensions)) - :key id} - [(get screens-map screen-id) id]]]) + [gesture/gesture-detector + {:gesture (shell.gesture/floating-screen-gesture screen-id)} + [rn/view + {:style (style/screen-container (utils/dimensions)) + :key id} + [(get screens-map screen-id) id]]]]) ;; Currently chat screen and events both depends on current-chat-id, once we remove ;; use of current-chat-id from view then we can keep last chat loaded, for fast navigation diff --git a/src/status_im2/contexts/shell/jump_to/constants.cljs b/src/status_im2/contexts/shell/jump_to/constants.cljs index 927dd049d4..c95a1c0afc 100644 --- a/src/status_im2/contexts/shell/jump_to/constants.cljs +++ b/src/status_im2/contexts/shell/jump_to/constants.cljs @@ -61,3 +61,8 @@ (def ^:const open-screen-with-shell-animation 3) (def ^:const close-screen-without-animation 4) (def ^:const open-screen-without-animation 5) + +;; Floating Screen gesture +(def ^:const gesture-width 20) +(def ^:const gesture-fling-right-velocity 2000) +(def ^:const gesture-fling-left-velocity -1000) diff --git a/src/status_im2/contexts/shell/jump_to/gesture.cljs b/src/status_im2/contexts/shell/jump_to/gesture.cljs new file mode 100644 index 0000000000..90de689b2f --- /dev/null +++ b/src/status_im2/contexts/shell/jump_to/gesture.cljs @@ -0,0 +1,43 @@ +(ns status-im2.contexts.shell.jump-to.gesture + (:require [utils.re-frame :as rf] + [react-native.gesture :as gesture] + [react-native.reanimated :as reanimated] + [utils.worklets.shell :as worklets.shell] + [status-im2.contexts.shell.jump-to.utils :as utils] + [status-im2.contexts.shell.jump-to.state :as state] + [status-im2.contexts.shell.jump-to.constants :as constants])) + +(defn screen-closed-callback + [screen-id] + (fn [animation-time] + (js/setTimeout + (fn [] + (reanimated/set-shared-value + (get-in @state/shared-values-atom [screen-id :screen-state]) + constants/close-screen-without-animation) + (reset! state/floating-screens-state + (assoc @state/floating-screens-state + screen-id + constants/close-screen-without-animation)) + (rf/dispatch [:shell/floating-screen-closed screen-id])) + (or animation-time constants/shell-animation-time)))) + +(defn floating-screen-gesture + [screen-id] + (let [{:keys [screen-left screen-state]} (get @state/shared-values-atom screen-id) + {:keys [width]} (utils/dimensions)] + (-> (gesture/gesture-pan) + (gesture/min-distance 0) + (gesture/max-pointers 1) + (gesture/fail-offset-x -1) + (gesture/hit-slop (clj->js {:left 0 :width constants/gesture-width})) + (gesture/on-update (worklets.shell/floating-screen-gesture-on-update screen-left)) + (gesture/on-end + (worklets.shell/floating-screen-gesture-on-end + {:screen-left screen-left + :screen-state screen-state + :screen-width width + :left-velocity constants/gesture-fling-left-velocity + :right-velocity constants/gesture-fling-right-velocity + :screen-closed-callback (screen-closed-callback screen-id)}))))) + diff --git a/src/utils/worklets/shell.cljs b/src/utils/worklets/shell.cljs index af8c5a67c2..9c122b1d9a 100644 --- a/src/utils/worklets/shell.cljs +++ b/src/utils/worklets/shell.cljs @@ -1,4 +1,6 @@ -(ns utils.worklets.shell) +(ns utils.worklets.shell + (:require [utils.collection] + [camel-snake-kebab.core :as csk])) (def bottom-tabs-worklets (js/require "../src/js/worklets/shell/bottom_tabs.js")) (def home-stack-worklets (js/require "../src/js/worklets/shell/home_stack.js")) @@ -87,3 +89,13 @@ (defn floating-screen-z-index [screen-state] (.screenZIndex ^js floating-screen-worklets screen-state)) + +(defn floating-screen-gesture-on-update + [screen-left] + (.screenGestureOnUpdate ^js floating-screen-worklets screen-left)) + +(defn floating-screen-gesture-on-end + [data] + (.screenGestureOnEnd + ^js floating-screen-worklets + (clj->js (utils.collection/map-keys csk/->camelCaseString data))))