Add autoscroll and animation to intro carousel

Add cancelable loop animation

Set index manually

Increase progress bar size

Signed-off-by: Gheorghe Pinzaru <feross95@gmail.com>
This commit is contained in:
Gheorghe Pinzaru 2020-06-30 14:53:23 +03:00
parent b5fda1234e
commit e290881bf8
No known key found for this signature in database
GPG Key ID: C9A094959935A952
5 changed files with 192 additions and 101 deletions

View File

@ -51,7 +51,8 @@
(def bezier (.-bezier ^js Easing)) (def bezier (.-bezier ^js Easing))
(def linear (.-linear ^js Easing)) (def linear (.-linear ^js Easing))
(def easings {:ease-in (bezier 0.42 0 1 1) (def easings {:linear linear
:ease-in (bezier 0.42 0 1 1)
:ease-out (bezier 0 0 0.58 1) :ease-out (bezier 0 0 0.58 1)
:ease-in-out (bezier 0.42 0 0.58 1)}) :ease-in-out (bezier 0.42 0 0.58 1)})
@ -182,6 +183,31 @@
(defn snap-point [value velocity snap-points] (defn snap-point [value velocity snap-points]
(.snapPoint ^js redash value velocity (to-array snap-points))) (.snapPoint ^js redash value velocity (to-array snap-points)))
(defn cancelable-loop
[{:keys [clock duration finished on-reach]}]
(let [time (value 0)
frame-time (value 0)
position (value 0)
to-value (value 1)
state {:time time
:frameTime frame-time
:finished finished
:position position}
config {:toValue to-value
:duration duration
:easing (:linear easings)}]
(block
[(timing clock state config)
(cond* (and* finished
(eq position to-value))
(call* [] on-reach))
(cond* finished
[(set finished 0)
(set time 0)
(set frame-time 0)
(set position 0)])
position])))
(defn with-easing (defn with-easing
[{val :value [{val :value
:keys [snap-points velocity offset state easing duration on-snap] :keys [snap-points velocity offset state easing duration on-snap]

View File

@ -8,7 +8,7 @@
(def platform (.-Platform ^js rn)) (def platform (.-Platform ^js rn))
(def view (reagent/adapt-react-class (.-View ^js rn))) (def view (reagent/adapt-react-class (.-View ^js rn)))
(def image (reagent/adapt-react-class (.-Image rn)))
(def text (reagent/adapt-react-class (.-Text ^js rn))) (def text (reagent/adapt-react-class (.-Text ^js rn)))
(def scroll-view (reagent/adapt-react-class (.-ScrollView ^js rn))) (def scroll-view (reagent/adapt-react-class (.-ScrollView ^js rn)))

View File

@ -0,0 +1,129 @@
(ns status-im.ui.screens.intro.carousel
(:require [quo.animated :as animated]
[quo.react-native :as rn]
[quo.core :as quo]
[reagent.core :as r]
[status-im.i18n :as i18n]
[status-im.ui.screens.intro.styles :as styles]))
(defn dot []
(let [active (animated/value 0)
active-transition (animated/with-timing-transition active {:duration 100})]
(fn [{:keys [selected progress]}]
[animated/view {:style (styles/dot-style active-transition)}
[animated/code {:exec (animated/set active (if selected 1 0))}]
[animated/view {:style (styles/dot-progress active-transition progress)}]])))
(defn dots-selector [{:keys [n selected progress]}]
[rn/view {:style (styles/dot-selector)}
(for [i (range n)]
^{:key i}
[dot {:progress progress
:selected (= selected i)}])])
(defn viewer [slides window-height _]
(let [scroll-x (r/atom 0)
scroll-view-ref (atom nil)
width (r/atom 0)
height (r/atom 0)
text-height (r/atom 0)
index (r/atom 0)
manual-scroll (atom false)
text-temp-height (atom 0)
text-temp-timer (atom nil)
bottom-margin (if (> window-height 600) 32 16)
progress (animated/value 1)
autoscroll (animated/value 1)
finished (animated/value 0)
clock (animated/clock)
go-next (fn []
(let [x (if (>= @scroll-x (* (dec (count slides))
@width))
0
(+ @scroll-x @width))]
(reset! index (Math/round (/ x @width)))
(some-> ^js @scroll-view-ref (.scrollTo #js {:x x :animated true}))))
code (animated/block
[(animated/cond* (animated/and* (animated/not* (animated/clock-running clock))
autoscroll)
(animated/start-clock clock))
(animated/cond* (animated/and* (animated/clock-running clock)
(animated/not* autoscroll))
[(animated/stop-clock clock)
(animated/set finished 1)])
(animated/set progress (animated/cancelable-loop
{:clock clock
:finished finished
:duration 4000
:on-reach go-next}))])
cancel-animation (fn []
(reset! manual-scroll true)
(animated/set-value autoscroll 0))
restart-animation (fn []
(animated/set-value autoscroll 1))]
(fn [_ _ view-id]
(let [current-screen? (or (nil? view-id) (= view-id :intro))]
[rn/view {:style {:align-items :center
:flex 1
:margin-bottom bottom-margin
:justify-content :flex-end}
:on-layout (fn [^js e]
(when current-screen?
(reset! width (-> e .-nativeEvent .-layout .-width))))}
(when current-screen?
[animated/code {:exec code}])
[rn/scroll-view {:horizontal true
:paging-enabled true
:ref #(reset! scroll-view-ref %)
:shows-vertical-scroll-indicator false
:shows-horizontal-scroll-indicator false
:pinch-gesture-enabled false
:scroll-event-throttle 16
:on-scroll #(let [x (.-nativeEvent.contentOffset.x ^js %)]
(when @manual-scroll
;; NOTE: Will be not synced if velocity is big
(reset! index (Math/round (/ x @width))))
(reset! scroll-x x))
:on-scroll-begin-drag cancel-animation
:on-scroll-end-drag restart-animation
:on-momentum-scroll-end #(reset! manual-scroll false)
:style {:margin-bottom bottom-margin}}
(doall
(for [s slides]
^{:key (:title s)}
[rn/view {:style {:flex 1
:width @width
:justify-content :flex-end
:align-items :center
:padding-horizontal 32}}
(let [size (min @width @height)]
[rn/view {:style {:flex 1}
:on-layout (fn [^js e]
(let [new-height (-> e .-nativeEvent .-layout .-height)]
(when current-screen?
(swap! height #(if (pos? %) (min % new-height) new-height)))))}
[rn/image {:source (:image s)
:resize-mode :contain
:style {:width size
:height size}}]])
[quo/text {:style styles/wizard-title
:align :center
:weight :bold
:size :x-large}
(i18n/label (:title s))]
[quo/text {:style (styles/wizard-text-with-height @text-height)
:on-layout
(fn [^js e]
(let [new-height (-> e .-nativeEvent .-layout .-height)]
(when (and current-screen?
(not= new-height @text-temp-height)
(not (zero? new-height))
(< new-height 200))
(swap! text-temp-height #(if (pos? %) (max % new-height) new-height))
(when @text-temp-timer (js/clearTimeout @text-temp-timer))
(reset! text-temp-timer
(js/setTimeout #(reset! text-height @text-temp-height) 500)))))}
(i18n/label (:text s))]]))]
[dots-selector {:selected @index
:progress progress
:n (count slides)}]]))))

View File

@ -1,33 +1,38 @@
(ns status-im.ui.screens.intro.styles (ns status-im.ui.screens.intro.styles
(:require [status-im.ui.components.colors :as colors])) (:require [status-im.ui.components.colors :as colors]
[quo.animated :as animated]))
(def dot-size 6)
(def progress-size 36)
(def intro-view (def intro-view
{:flex 1 {:flex 1
:justify-content :flex-end :justify-content :flex-end
:margin-bottom 12}) :margin-bottom 12})
(defn dot-selector [n] (defn dot-selector []
(let [diameter 6 {:flex-direction :row
interval 10] :justify-content :space-between
{:flex-direction :row :align-items :center})
:justify-content :space-between
:align-items :center
:height diameter
:width (+ diameter (* (+ diameter interval)
(dec n)))}))
(defn dot [color selected?] (defn dot-style [active]
{:background-color (if selected? {:background-color colors/blue-light
color :overflow :hidden
(colors/alpha color 0.2)) :opacity 1
:width 6 :margin-horizontal 2
:height 6 :width (animated/mix active dot-size progress-size)
:border-radius 3}) :height dot-size
:border-radius 3})
(defn dot-progress [active progress]
{:background-color colors/blue
:height dot-size
:width progress-size
:opacity (animated/mix active 0 1)
:transform [{:translateX (animated/mix progress (- progress-size) 0)}]})
(def wizard-title (def wizard-title
{:typography :header {:margin-bottom 16})
:text-align :center
:margin-bottom 16})
(def wizard-text (def wizard-text
{:color colors/gray {:color colors/gray

View File

@ -18,93 +18,24 @@
[status-im.utils.identicon :as identicon] [status-im.utils.identicon :as identicon]
[status-im.utils.platform :as platform] [status-im.utils.platform :as platform]
[status-im.utils.security :as security] [status-im.utils.security :as security]
[status-im.ui.screens.intro.carousel :as carousel]
[quo.core :as quo] [quo.core :as quo]
[status-im.utils.utils :as utils]) [status-im.utils.utils :as utils])
(:require-macros [status-im.utils.views :refer [defview letsubs]])) (:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn dots-selector [{:keys [n selected color]}]
[react/view {:style (styles/dot-selector n)}
(doall
(for [i (range n)]
^{:key i}
[react/view {:style (styles/dot color (selected i))}]))])
(defn intro-viewer [slides window-height _]
(let [scroll-x (r/atom 0)
scroll-view-ref (atom nil)
width (r/atom 0)
height (r/atom 0)
text-height (r/atom 0)
text-temp-height (atom 0)
text-temp-timer (atom nil)
bottom-margin (if (> window-height 600) 32 16)]
(fn [_ _ view-id]
(let [current-screen? (or (nil? view-id) (= view-id :intro))]
[react/view {:style {:align-items :center
:flex 1
:margin-bottom bottom-margin
:justify-content :flex-end}
:on-layout (fn [^js e]
(when current-screen?
(reset! width (-> e .-nativeEvent .-layout .-width))))}
[react/scroll-view {:horizontal true
:paging-enabled true
:ref #(reset! scroll-view-ref %)
:shows-vertical-scroll-indicator false
:shows-horizontal-scroll-indicator false
:pinch-gesture-enabled false
:on-scroll #(let [^js x (.-nativeEvent.contentOffset.x ^js %)]
(reset! scroll-x x))
:style {:margin-bottom bottom-margin}}
(doall
(for [s slides]
^{:key (:title s)}
[react/view {:style {:flex 1
:width @width
:justify-content :flex-end
:align-items :center
:padding-horizontal 32}}
(let [size (min @width @height)]
[react/view {:style {:flex 1}
:on-layout (fn [^js e]
(let [new-height (-> e .-nativeEvent .-layout .-height)]
(when current-screen?
(swap! height #(if (pos? %) (min % new-height) new-height)))))}
[react/image {:source (:image s)
:resize-mode :contain
:style {:width size
:height size}}]])
[react/i18n-text {:style styles/wizard-title :key (:title s)}]
[react/text {:style (styles/wizard-text-with-height @text-height)
:on-layout
(fn [^js e]
(let [new-height (-> e .-nativeEvent .-layout .-height)]
(when (and current-screen?
(not= new-height @text-temp-height)
(not (zero? new-height))
(< new-height 200))
(swap! text-temp-height #(if (pos? %) (max % new-height) new-height))
(when @text-temp-timer (js/clearTimeout @text-temp-timer))
(reset! text-temp-timer
(js/setTimeout #(reset! text-height @text-temp-height) 500)))))}
(i18n/label (:text s))]]))]
(let [selected (hash-set (quot (int @scroll-x) (int @width)))]
[dots-selector {:selected selected :n (count slides)
:color colors/blue}])]))))
(defview intro [] (defview intro []
(letsubs [{window-height :height} [:dimensions/window] (letsubs [{window-height :height} [:dimensions/window]
view-id [:view-id]] view-id [:view-id]]
[react/view {:style styles/intro-view} [react/view {:style styles/intro-view}
[intro-viewer [{:image (resources/get-theme-image :chat) [carousel/viewer [{:image (resources/get-theme-image :chat)
:title :intro-title1 :title :intro-title1
:text :intro-text1} :text :intro-text1}
{:image (resources/get-theme-image :wallet) {:image (resources/get-theme-image :wallet)
:title :intro-title2 :title :intro-title2
:text :intro-text2} :text :intro-text2}
{:image (resources/get-theme-image :browser) {:image (resources/get-theme-image :browser)
:title :intro-title3 :title :intro-title3
:text :intro-text3}] window-height view-id] :text :intro-text3}] window-height view-id]
[react/view styles/buttons-container [react/view styles/buttons-container
[components.common/button {:button-style (assoc styles/bottom-button :margin-bottom 16) [components.common/button {:button-style (assoc styles/bottom-button :margin-bottom 16)
:on-press #(re-frame/dispatch [:multiaccounts.create.ui/intro-wizard]) :on-press #(re-frame/dispatch [:multiaccounts.create.ui/intro-wizard])