diff --git a/src/quo/animated.cljs b/src/quo/animated.cljs index 751ce656611..6402746575f 100644 --- a/src/quo/animated.cljs +++ b/src/quo/animated.cljs @@ -51,7 +51,8 @@ (def bezier (.-bezier ^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-in-out (bezier 0.42 0 0.58 1)}) @@ -182,6 +183,31 @@ (defn snap-point [value velocity 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 [{val :value :keys [snap-points velocity offset state easing duration on-snap] diff --git a/src/quo/react_native.cljs b/src/quo/react_native.cljs index 669a3815d08..3ec796860ab 100644 --- a/src/quo/react_native.cljs +++ b/src/quo/react_native.cljs @@ -8,7 +8,7 @@ (def platform (.-Platform ^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 scroll-view (reagent/adapt-react-class (.-ScrollView ^js rn))) diff --git a/src/status_im/ui/screens/intro/carousel.cljs b/src/status_im/ui/screens/intro/carousel.cljs new file mode 100644 index 00000000000..b061cd55daa --- /dev/null +++ b/src/status_im/ui/screens/intro/carousel.cljs @@ -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)}]])))) diff --git a/src/status_im/ui/screens/intro/styles.cljs b/src/status_im/ui/screens/intro/styles.cljs index 841d8fb31db..2459087ae6b 100644 --- a/src/status_im/ui/screens/intro/styles.cljs +++ b/src/status_im/ui/screens/intro/styles.cljs @@ -1,33 +1,38 @@ (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 {:flex 1 :justify-content :flex-end :margin-bottom 12}) -(defn dot-selector [n] - (let [diameter 6 - interval 10] - {:flex-direction :row - :justify-content :space-between - :align-items :center - :height diameter - :width (+ diameter (* (+ diameter interval) - (dec n)))})) - -(defn dot [color selected?] - {:background-color (if selected? - color - (colors/alpha color 0.2)) - :width 6 - :height 6 - :border-radius 3}) +(defn dot-selector [] + {:flex-direction :row + :justify-content :space-between + :align-items :center}) + +(defn dot-style [active] + {:background-color colors/blue-light + :overflow :hidden + :opacity 1 + :margin-horizontal 2 + :width (animated/mix active dot-size progress-size) + :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 - {:typography :header - :text-align :center - :margin-bottom 16}) + {:margin-bottom 16}) (def wizard-text {:color colors/gray diff --git a/src/status_im/ui/screens/intro/views.cljs b/src/status_im/ui/screens/intro/views.cljs index 6b3bec859fa..1c97d9833de 100644 --- a/src/status_im/ui/screens/intro/views.cljs +++ b/src/status_im/ui/screens/intro/views.cljs @@ -18,93 +18,24 @@ [status-im.utils.identicon :as identicon] [status-im.utils.platform :as platform] [status-im.utils.security :as security] + [status-im.ui.screens.intro.carousel :as carousel] [quo.core :as quo] [status-im.utils.utils :as utils]) (: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 [] (letsubs [{window-height :height} [:dimensions/window] view-id [:view-id]] [react/view {:style styles/intro-view} - [intro-viewer [{:image (resources/get-theme-image :chat) - :title :intro-title1 - :text :intro-text1} - {:image (resources/get-theme-image :wallet) - :title :intro-title2 - :text :intro-text2} - {:image (resources/get-theme-image :browser) - :title :intro-title3 - :text :intro-text3}] window-height view-id] + [carousel/viewer [{:image (resources/get-theme-image :chat) + :title :intro-title1 + :text :intro-text1} + {:image (resources/get-theme-image :wallet) + :title :intro-title2 + :text :intro-text2} + {:image (resources/get-theme-image :browser) + :title :intro-title3 + :text :intro-text3}] window-height view-id] [react/view styles/buttons-container [components.common/button {:button-style (assoc styles/bottom-button :margin-bottom 16) :on-press #(re-frame/dispatch [:multiaccounts.create.ui/intro-wizard])