-
Notifications
You must be signed in to change notification settings - Fork 987
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement onboarding slide animation #15489
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { useDerivedValue, withTiming, Easing } from 'react-native-reanimated'; | ||
|
||
const slideAnimationDuration = 300; | ||
|
||
const easeOut = { | ||
duration: slideAnimationDuration, | ||
easing: Easing.bezier(0, 0, 0.58, 1), | ||
} | ||
|
||
// Derived Values | ||
export function dynamicProgressBarWidth(staticProgressBarWidth, progress) { | ||
return useDerivedValue( | ||
function () { | ||
'worklet' | ||
return staticProgressBarWidth * (progress.value || 0) / 100; | ||
} | ||
); | ||
} | ||
|
||
export function carouselLeftPosition(windowWidth, progress) { | ||
return useDerivedValue( | ||
function () { | ||
'worklet' | ||
const progressValue = progress.value; | ||
switch (true) { | ||
case (progressValue < 25): | ||
return 0; | ||
case (progressValue === 25): | ||
return withTiming(-windowWidth, easeOut); | ||
case (progressValue < 50): | ||
return -windowWidth; | ||
case (progressValue === 50): | ||
return withTiming(-2 * windowWidth, easeOut); | ||
case (progressValue < 75): | ||
return -2 * windowWidth; | ||
case (progressValue === 75): | ||
return withTiming(-3 * windowWidth, easeOut); | ||
case (progressValue < 100): | ||
return -3 * windowWidth; | ||
case (progressValue === 100): | ||
return withTiming(-4 * windowWidth, easeOut); | ||
default: | ||
return 0; | ||
} | ||
}); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -127,6 +127,7 @@ | |
|
||
;;Solid | ||
(def black "#000000") | ||
(def onboarding-header-black "#000716") | ||
|
||
;;;;Primary | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,22 @@ | ||
(ns status-im2.contexts.onboarding.common.background.view | ||
(:require [react-native.core :as rn] | ||
[quo2.foundations.colors :as colors] | ||
[status-im2.common.resources :as resources] | ||
[react-native.linear-gradient :as linear-gradient] | ||
[status-im2.contexts.onboarding.common.background.style :as style])) | ||
[react-native.blur :as blur] | ||
[status-im2.contexts.onboarding.common.carousel.view :as carousel] | ||
[status-im2.contexts.onboarding.common.background.style :as style] | ||
[status-im2.contexts.onboarding.common.carousel.animation :as carousel.animation])) | ||
|
||
(defn view | ||
[dark-overlay?] | ||
[rn/view | ||
{:style style/background-container} | ||
[rn/image | ||
{:blur-radius (if dark-overlay? 13 0) | ||
:style {:height "100%" | ||
:width "100%"} | ||
;; Todo - get background image from sub using carousel index on landing page | ||
:source (resources/get-image :onboarding-bg-1)}] | ||
[linear-gradient/linear-gradient | ||
{:colors [(if dark-overlay? (colors/custom-color :yin 50) "#000716") | ||
(if dark-overlay? (colors/custom-color :yin 50 0) "#000716")] | ||
:start {:x 0 :y 0} | ||
:end {:x 0 :y 1} | ||
:style (style/background-gradient-overlay dark-overlay?)}] | ||
(when dark-overlay? | ||
[:f> | ||
(fn [] | ||
(carousel.animation/initialize-animation) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is the carousel here? 🤔 Should we not create a separate background component for this use case? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah you are right , it was not before. I understand now the other pages are using this for where the background image has stopped and the section being shown. Nicely done :D There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thank you There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi @briansztamfater, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what about useLayoutEffect? 🤔 https://react.dev/reference/react/useLayoutEffect There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just curious, how this will be different then calling function before view? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not 100% if this applies here, but useLayoutEffect works similar to useEffect except it fires before the screen is painted. It's probably safer to initialise this way in React if possible. But it's not a must have, just worth exploring :) |
||
[rn/view | ||
{:style style/background-blur-overlay}])]) | ||
{:style style/background-container} | ||
[carousel/view dark-overlay?] | ||
(when dark-overlay? | ||
[blur/view | ||
{:style style/background-blur-overlay | ||
:blur-amount 30 | ||
:blur-radius 25 | ||
:blur-type :transparent | ||
:overlay-color :transparent}])])]) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
(ns status-im2.contexts.onboarding.common.carousel.animation | ||
(:require | ||
[react-native.reanimated :as reanimated] | ||
[utils.worklets.onboarding-carousel :as worklets.onboarding-carousel])) | ||
|
||
(def progress (atom nil)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder is it possible to avoid these global atoms and instead have them scoped to the onboarding stack? 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe, we can initialize them on top of root and pass as params to other stacks. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It sounds complicated to pass shared values as params to navigated screens There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I believe we want to avoid global atoms as much as possible I understand there are exceptions and perhaps it's needed/ not so much of an issue in this case cc: @ilmotta @ulisesmac There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for cc'ing me @J-Son89. Let's be real, global state is often not a big issue since most of our global atoms or ratoms are used only in the scope of a single namespace, at least that's how I feel. Practically speaking, it's passable 🤷♂️ But having said that, I still think it's weird... In one hand Clojure(Script) promotes pure code as much as possible and controlled, local state. On the other hand, ClojureScript and Reagent allow us to define state used for views outside view components, something that's a no-no in React itself. That is to say, even raw React code feels more functional than our own ClojureScript code with global vars. It's a discussion without a clear answer to me. As usual, almost all global state is created in the name of convenience, so this is not enough to justify, at least to me. Given this discussion come and go every now and then, maybe it's about time we write something in the guidelines if we can all agree on something. Perhaps the discussion should be made outside this PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah I think it would be nice to discuss this. As far as I see it one big drawback is this code becomes a lot less reusable. In this case we have other carousels in the new designs and so ideally we could use this component/code (or at least a good chunk of it) to create these carousels. Anyway if that is the case we can also let the developer who will work on these aim to refactor when they go to use this code. Ideally we can do as much as possible to enable that from the initial point code is added to the code base. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great point, reusability is one big reason to favor local state, because there's no state to synchronize between components in the first place. |
||
(def paused (atom nil)) | ||
|
||
(def ^:const progress-bar-animation-delay 300) | ||
(def ^:const progress-bar-animation-duration 4000) | ||
|
||
(defn slide-animation | ||
[progress-percentage & [delay duration]] | ||
(reanimated/with-delay | ||
(or delay progress-bar-animation-delay) | ||
(reanimated/with-timing | ||
progress-percentage | ||
(js-obj "duration" (or duration progress-bar-animation-duration) | ||
"easing" (:linear reanimated/easings))))) | ||
|
||
(defn animate-progress | ||
[progress paused] | ||
(reanimated/set-shared-value | ||
progress | ||
(reanimated/with-pause | ||
(reanimated/with-repeat | ||
(reanimated/with-sequence | ||
(slide-animation 25) | ||
(slide-animation 50) | ||
(slide-animation 75) | ||
(slide-animation 100) | ||
(slide-animation 0 300 0)) | ||
-1) | ||
paused))) | ||
|
||
(defn initialize-animation | ||
[] | ||
(when-not @progress | ||
(reset! progress (reanimated/use-shared-value 0)) | ||
(reset! paused (reanimated/use-shared-value false)) | ||
(animate-progress @progress @paused))) | ||
|
||
;; Derived Values | ||
(defn carousel-left-position | ||
[window-width] | ||
(worklets.onboarding-carousel/carousel-left-position window-width @progress)) | ||
|
||
(defn dynamic-progress-bar-width | ||
[progress-bar-width] | ||
(worklets.onboarding-carousel/dynamic-progress-bar-width progress-bar-width @progress)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
(ns status-im2.contexts.onboarding.common.carousel.style | ||
(:require | ||
[quo2.foundations.colors :as colors] | ||
[react-native.reanimated :as reanimated])) | ||
|
||
(defn header-container | ||
[status-bar-height content-width index] | ||
{:position :absolute | ||
:top 0 | ||
:left (* content-width index) | ||
:padding-top (+ 30 status-bar-height) | ||
:width content-width | ||
:height (+ 96 status-bar-height) | ||
:flex-direction :row | ||
:background-color colors/onboarding-header-black}) | ||
|
||
(defn header-text-view | ||
[window-width] | ||
{:flex-direction :column | ||
:width window-width | ||
:padding-left 20}) | ||
|
||
(def carousel-text | ||
{:color colors/white}) | ||
|
||
(def carousel-sub-text | ||
{:color colors/white | ||
:margin-top 2}) | ||
|
||
(defn background-image | ||
[content-width] | ||
{:resize-mode :stretch | ||
:width content-width}) | ||
|
||
(defn progress-bar-item | ||
[static? end?] | ||
{:height 2 | ||
:flex 1 | ||
:background-color (if static? colors/white-opa-10 colors/white) | ||
:margin-right (if end? 0 8) | ||
:border-radius 4}) | ||
|
||
(defn progress-bar | ||
[width] | ||
{:position :absolute | ||
:top 0 | ||
:width width | ||
:flex-direction :row}) | ||
|
||
(defn dynamic-progress-bar | ||
[width] | ||
(reanimated/apply-animations-to-style | ||
{:width width} | ||
{:height 2 | ||
:border-radius 4 | ||
:overflow :hidden})) | ||
|
||
(defn progress-bar-container | ||
[progress-bar-width status-bar-height] | ||
{:position :absolute | ||
:width progress-bar-width | ||
:margin-left 20 | ||
:top (+ 12 status-bar-height)}) | ||
|
||
(defn carousel-container | ||
[left] | ||
(reanimated/apply-animations-to-style | ||
{:left left} | ||
{:position :absolute | ||
:right 0 | ||
:top 0 | ||
:bottom 0 | ||
:flex-direction :row})) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd recommend to put that functional component in a different function: