Lightbox screen animations (#14954)

* lightbox screen animations
This commit is contained in:
Omar Basem 2023-02-02 15:03:59 +04:00 committed by GitHub
parent 2dad2b67e8
commit eed38fe082
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 278 additions and 210 deletions

View File

@ -103,6 +103,8 @@
(if (fn? ret) ret js/undefined))
(bean/->js deps))))
(def use-callback react/useCallback)
(defn use-effect-once
[effect-fn]
(use-effect effect-fn))

View File

@ -1,6 +1,8 @@
(ns react-native.gesture
(:require ["react-native-gesture-handler" :refer (GestureDetector Gesture gestureHandlerRootHOC)]
[reagent.core :as reagent]))
(:require ["react-native-gesture-handler" :refer
(GestureDetector Gesture gestureHandlerRootHOC TapGestureHandler State)]
[reagent.core :as reagent]
[oops.core :refer [oget]]))
(def gesture-detector (reagent/adapt-react-class GestureDetector))
(def gesture-handler-root-hoc gestureHandlerRootHOC)
@ -12,3 +14,14 @@
(defn on-start [^js pan handler] (.onStart pan handler))
(defn on-end [^js pan handler] (.onEnd pan handler))
(def tap-gesture-handler
(reagent/adapt-react-class TapGestureHandler))
(def states
{:began (oget State "BEGAN")
:active (oget State "ACTIVE")
:cancelled (oget State "CANCELLED")
:end (oget State "END")
:failed (oget State "FAILED")
:undetermined (oget State "UNDETERMINED")})

View File

@ -1,20 +1,23 @@
(ns status-im2.contexts.chat.lightbox.style
(:require [quo2.foundations.colors :as colors]))
(:require [quo2.foundations.colors :as colors]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]))
(defn container-view
[padding-top]
(def container-view
{:background-color :black
:height "100%"
:padding-top padding-top})
:height "100%"})
(defn top-view-container
[top-inset]
{:position :absolute
:left 20
:top (+ 12 top-inset)
:z-index 1
:flex-direction :row
:width "100%"})
[top-inset opacity]
(reanimated/apply-animations-to-style
{:opacity opacity}
{:position :absolute
:left 20
:top (if platform/ios? (+ 12 top-inset) 12)
:z-index 4
:flex-direction :row
:width "100%"}))
(def close-container
{:width 32
@ -26,15 +29,18 @@
(def top-right-buttons
{:position :absolute
:right 20
:right 40
:flex-direction :row})
(defn gradient-container
[insets]
{:width "100%"
;:height (+ (:bottom insets) 65)
:position :absolute
:bottom (:bottom insets)})
[insets opacity]
(reanimated/apply-animations-to-style
{:opacity opacity}
{:width "100%"
:position :absolute
:bottom 0
:padding-bottom (:bottom insets)
:z-index 3}))
(def text-style
{:color colors/white

View File

@ -1,139 +1,191 @@
(ns status-im2.contexts.chat.lightbox.view
(:require [quo2.core :as quo]
[quo2.foundations.colors :as colors]
[react-native.core :as rn]
[react-native.fast-image :as fast-image]
[utils.re-frame :as rf]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[oops.core :as oops]
[status-im2.contexts.chat.lightbox.style :as style]
[utils.datetime :as datetime]
[react-native.linear-gradient :as linear-gradient]))
(:require
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[react-native.core :as rn]
[react-native.fast-image :as fast-image]
[react-native.reanimated :as reanimated]
[utils.re-frame :as rf]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im2.contexts.chat.lightbox.style :as style]
[utils.datetime :as datetime]
[react-native.gesture :as gesture]
[oops.core :refer [oget]]))
(def flat-list-ref (atom nil))
(def small-list-ref (atom nil))
(def small-image-size 40)
(def focused-image-size 56)
(defn toggle-opacity
[opacity-value border-value transparent?]
(let [opacity (reanimated/get-shared-value opacity-value)]
(reanimated/set-shared-value opacity-value (reanimated/with-timing (if (= opacity 1) 0 1)))
(reanimated/set-shared-value border-value (reanimated/with-timing (if (= opacity 1) 0 12)))
(reset! transparent? (not @transparent?))))
(defn image
[message]
(let [shared-element-id (rf/sub [:shared-element-id])
window-width (:width (rn/get-window))
height (* (or (:image-height message) 1000)
(/ window-width (or (:image-width message) 1000)))]
[fast-image/fast-image
{:source {:uri (:image (:content message))}
:style {:width window-width
:height height
:border-radius 12}
:native-ID (when (= shared-element-id (:message-id message)) :shared-element)}]))
[message opacity-value border-value transparent?]
[:f>
(fn []
(let [shared-element-id (rf/sub [:shared-element-id])
width (:width (rn/get-window))
height (* (or (:image-height message) 1000)
(/ width (or (:image-width message) 1000)))]
[gesture/tap-gesture-handler
{:on-handler-state-change (fn [e]
(when (= (oget e "nativeEvent.state") (:active gesture/states))
(toggle-opacity opacity-value border-value transparent?)))}
[rn/view {:style {:flex-direction :row}}
[reanimated/view
{:style (reanimated/apply-animations-to-style
{:border-radius border-value}
{:overflow :hidden})}
[fast-image/fast-image
{:source {:uri (:image (:content message))}
:style {:width width
:height height}
:native-ID (when (= shared-element-id (:message-id message)) :shared-element)}]]
[rn/view {:style {:width 16}}]]]))])
(defn get-item-layout
[_ index]
(let [window-width (:width (rn/get-window))]
#js {:length window-width :offset (* window-width index) :index index}))
#js {:length window-width :offset (* (+ window-width 16) index) :index index}))
(defn get-small-item-layout
[_ index]
#js {:length small-image-size :offset (* (+ small-image-size 8) index) :index index})
(defn on-viewable-items-changed
[e]
(let [changed (-> e (oops/oget :changed) first)]
(.scrollToIndex ^js @small-list-ref #js {:animated true :index (oops/oget changed :index)})
(rf/dispatch [:chat.ui/update-shared-element-id (:message-id (oops/oget changed :item))])))
[e scroll-index]
(let [changed (-> e (oget :changed) first)
index (oget changed :index)]
(reset! scroll-index index)
(.scrollToIndex ^js @small-list-ref #js {:animated true :index index})
(rf/dispatch [:chat.ui/update-shared-element-id (:message-id (oget changed :item))])))
(defn top-view
[{:keys [from timestamp]} insets]
(let [display-name (first (rf/sub [:contacts/contact-two-names-by-identity from]))]
[rn/view
{:style (style/top-view-container (:top insets))}
[rn/touchable-opacity
{:active-opacity 1
:on-press #(rf/dispatch [:navigate-back])
:style style/close-container}
[quo/icon :close {:size 20 :color colors/white}]]
[rn/view {:style {:margin-left 12}}
[quo/text
{:weight :semi-bold
:size :paragraph-1
:style {:color colors/white}} display-name]
[quo/text
{:weight :medium
:size :paragraph-2
:style {:color colors/neutral-40}} (datetime/to-short-str timestamp)]]
[rn/view {:style style/top-right-buttons}
[rn/touchable-opacity
{:active-opacity 1
:on-press #(js/alert "to be implemented")
:style (merge style/close-container {:margin-right 12})}
[quo/icon :share {:size 20 :color colors/white}]]
[rn/touchable-opacity
{:active-opacity 1
:on-press #(js/alert "to be implemented")
:style style/close-container}
[quo/icon :options {:size 20 :color colors/white}]]]]))
[{:keys [from timestamp]} insets opacity-value transparent?]
[:f>
(fn []
(let [display-name (first (rf/sub [:contacts/contact-two-names-by-identity from]))]
[reanimated/view
{:style (style/top-view-container (:top insets) opacity-value)}
[rn/touchable-opacity
{:on-press #(when-not @transparent? (rf/dispatch [:navigate-back]))
:style style/close-container}
[quo/icon :close {:size 20 :color colors/white}]]
[rn/view {:style {:margin-left 12}}
[quo/text
{:weight :semi-bold
:size :paragraph-1
:style {:color colors/white}} display-name]
[quo/text
{:weight :medium
:size :paragraph-2
:style {:color colors/neutral-40}} (datetime/to-short-str timestamp)]]
[rn/view {:style style/top-right-buttons}
[rn/touchable-opacity
{:active-opacity 1
:on-press #(js/alert "to be implemented")
:style (merge style/close-container {:margin-right 12})}
[quo/icon :share {:size 20 :color colors/white}]]
[rn/touchable-opacity
{:active-opacity 1
:on-press #(js/alert "to be implemented")
:style style/close-container}
[quo/icon :options {:size 20 :color colors/white}]]]]))])
(defn small-image
[item]
[fast-image/fast-image
{:source {:uri (:image (:content item))}
:style {:width small-image-size
:height small-image-size
:border-radius 10}}])
[item index scroll-index]
[:f>
(fn []
(let [size (if (= @scroll-index index) focused-image-size small-image-size)
size-value (reanimated/use-shared-value size)]
(reanimated/set-shared-value size-value (reanimated/with-timing size))
[rn/touchable-opacity
{:active-opacity 1
:on-press (fn []
(reset! scroll-index index)
(.scrollToIndex ^js @small-list-ref #js {:animated true :index index})
(.scrollToIndex ^js @flat-list-ref #js {:animated true :index index}))}
[reanimated/fast-image
{:source {:uri (:image (:content item))}
:style (reanimated/apply-animations-to-style {:width size-value
:height size-value}
{:border-radius 10})}]]))])
(defn bottom-view
[messages insets]
(let [text (get-in (first messages) [:content :text])
padding-horizontal (- (/ (:width (rn/get-window)) 2) (/ small-image-size 2))]
[linear-gradient/linear-gradient
{:colors [:black :transparent]
:start {:x 0 :y 1}
:end {:x 0 :y 0}
:style (style/gradient-container insets)}
[rn/text
{:style style/text-style} text]
[rn/flat-list
{:ref #(reset! small-list-ref %)
:key-fn :message-id
:data messages
:render-fn small-image
:horizontal true
:get-item-layout get-small-item-layout
:separator [rn/view {:style {:width 8}}]
:content-container-style {:padding-vertical 12
:padding-horizontal padding-horizontal}}]]))
[messages index scroll-index insets opacity-value]
[:f>
(fn []
(let [text (get-in (first messages) [:content :text])
padding-horizontal (- (/ (:width (rn/get-window)) 2) (/ focused-image-size 2))]
[reanimated/linear-gradient
{:colors [:black :transparent]
:start {:x 0 :y 1}
:end {:x 0 :y 0}
:style (style/gradient-container insets opacity-value)}
[rn/text
{:style style/text-style} text]
[rn/flat-list
{:ref #(reset! small-list-ref %)
:key-fn :message-id
:style {:height 68}
:data messages
:render-fn (fn [item index] [small-image item index scroll-index])
:horizontal true
:get-item-layout get-small-item-layout
:separator [rn/view {:style {:width 8}}]
:initial-scroll-index index
:content-container-style {:padding-vertical 12
:padding-horizontal padding-horizontal
:align-items :center
:justify-content :center}}]]))])
(defn lightbox
[]
(let [{:keys [messages index]} (rf/sub [:get-screen-params])
;; The initial value of data is the image that was pressed (and not the whole album) in order for
;; the transition animation to execute properly, otherwise it would animate towards outside the
;; screen (even if we have `initialScrollIndex` set).
data (reagent/atom [(nth messages index)])]
(reset! data messages)
;; We use setTimeout to enqueue `scrollToIndex` until the `data` has been updated.
(js/setTimeout #(do
(.scrollToIndex ^js @flat-list-ref #js {:animated false :index index})
(.scrollToIndex ^js @small-list-ref #js {:animated false :index index}))
0)
[safe-area/consumer
(fn [insets]
[rn/view
{:style (style/container-view (:top insets))}
[top-view (first messages) insets]
[rn/flat-list
{:ref #(reset! flat-list-ref %)
:key-fn :message-id
:data @data
:render-fn image
:horizontal true
:paging-enabled true
:get-item-layout get-item-layout
:viewability-config {:view-area-coverage-percent-threshold 50}
:on-viewable-items-changed on-viewable-items-changed
:content-container-style {:justify-content :center
:align-items :center}}]
[bottom-view messages insets]])]))
[:f>
(fn []
(let [{:keys [messages index]} (rf/sub [:get-screen-params])
;; The initial value of data is the image that was pressed (and not the whole album) in order
;; for the transition animation to execute properly, otherwise it would animate towards
;; outside the screen (even if we have `initialScrollIndex` set).
data (reagent/atom [(nth messages index)])
scroll-index (reagent/atom index)
transparent? (reagent/atom false)
opacity-value (reanimated/use-shared-value 1)
border-value (reanimated/use-shared-value 12)
window-width (:width (rn/get-window))]
(reset! data messages)
[safe-area/consumer
(fn [insets]
[:f>
(fn []
;; We use setTimeout to enqueue `scrollToIndex` until the `data` has been updated.
(js/setTimeout #(.scrollToIndex ^js @flat-list-ref #js {:animated false :index index}) 0)
[rn/view {:style style/container-view}
[top-view (first messages) insets opacity-value transparent?]
[rn/flat-list
{:ref #(reset! flat-list-ref %)
:key-fn :message-id
:style {:width (+ window-width 16)}
:data @data
:render-fn (fn [item] [image item opacity-value border-value
transparent?])
:horizontal true
:paging-enabled true
:get-item-layout get-item-layout
:viewability-config {:view-area-coverage-percent-threshold 50}
:on-viewable-items-changed (rn/use-callback (fn [e]
(on-viewable-items-changed e
scroll-index)))
:content-container-style {:justify-content :center
:align-items :center}}]
[bottom-view messages index scroll-index insets opacity-value]])])]))])

View File

@ -10,31 +10,6 @@
(def rectangular-style-count 3)
(defn border-tlr
[index]
(when (= index 0) 12))
(defn border-trr
[index count album-style]
(when (or (and (= index 1) (not= count rectangular-style-count))
(and (= index 0) (= count rectangular-style-count) (= album-style :landscape))
(and (= index 1) (= count rectangular-style-count) (= album-style :portrait)))
12))
(defn border-blr
[index count album-style]
(when (or (and (= index 0) (< count rectangular-style-count))
(and (= index 2) (> count rectangular-style-count))
(and (= index 1) (= count rectangular-style-count) (= album-style :landscape))
(and (= index 0) (= count rectangular-style-count) (= album-style :portrait)))
12))
(defn border-brr
[index count]
(when (or (and (= index 1) (< count rectangular-style-count))
(and (= index (- (min count constants/max-album-photos) 1)) (> count 2)))
12))
(defn find-size
[size-arr album-style]
(if (= album-style :landscape)
@ -79,8 +54,7 @@
(when (and (> images-count constants/max-album-photos)
(= index (- constants/max-album-photos 1)))
[rn/view
{:style (merge style/overlay
{:border-bottom-right-radius (border-brr index images-count)})}
{:style style/overlay}
[quo/text
{:weight :bold
:size :heading-2

View File

@ -21,7 +21,12 @@
{:source {:uri uri}
:style style/cover}]
[rn/view {:style {:margin-left 12}}
[quo/text {:weight :medium} title]
[quo/text
{:weight :medium
:ellipsize-mode :tail
:number-of-lines 1
:style {:margin-right 50}}
title]
[quo/text
{:size :paragraph-2
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}}
@ -40,6 +45,10 @@
{:label title
:container-style style/divider}]))
(defn key-fn
[item index]
(str (:title item) index))
(defn album-selector
[]
[:f>
@ -61,4 +70,4 @@
:render-section-header-fn section-header
:style {:margin-top 12}
:content-container-style {:padding-bottom 40}
:key-fn :title}]]))])
:key-fn key-fn}]]))])

View File

@ -29,8 +29,7 @@
:position :absolute
:right 20})
(defn camera-button-container
[]
(def camera-button-container
{:background-color (colors/theme-colors colors/neutral-10 colors/neutral-80)
:width 32
:height 32
@ -82,4 +81,3 @@
:border-radius 8
:top 8
:right 8})

View File

@ -80,15 +80,23 @@
(inc (utils/first-index #(= (:uri item) (:uri %)) @selected))])])
(defn album-title
[photos? selected-album]
[photos? selected-album selected temporary-selected]
[rn/touchable-opacity
{:style (style/title-container)
:active-opacity 1
:accessibility-label :album-title
:on-press #(rf/dispatch (if photos?
[:open-modal :album-selector]
[:navigate-back]))}
[quo/text {:weight :medium} selected-album]
:on-press (fn []
(if photos?
(do
(reset! temporary-selected @selected)
(rf/dispatch [:open-modal :album-selector]))
(rf/dispatch [:navigate-back])))}
[quo/text
{:weight :medium
:ellipsize-mode :tail
:number-of-lines 1
:style {:max-width 150}}
selected-album]
[rn/view {:style (style/chevron-container)}
[quo/icon (if photos? :i/chevron-down :i/chevron-up)
{:color (colors/theme-colors colors/neutral-100 colors/white)}]]])
@ -96,41 +104,47 @@
(defn photo-selector
[]
[:f>
(fn []
(let [selected-images (rf/sub [:chats/sending-image])
selected-album (or (rf/sub [:camera-roll/selected-album]) (i18n/label :t/recent))
selected (reagent/atom [])]
(rn/use-effect
(fn []
(rf/dispatch [:chat.ui/camera-roll-get-photos 20 nil selected-album])
(if selected-images
(reset! selected (vec (vals selected-images)))
(reset! selected [])))
[selected-album])
[safe-area/consumer
(fn [insets]
(let [window-width (:width (rn/get-window))
camera-roll-photos (rf/sub [:camera-roll/photos])
end-cursor (rf/sub [:camera-roll/end-cursor])
loading? (rf/sub [:camera-roll/loading-more])
has-next-page? (rf/sub [:camera-roll/has-next-page])]
[rn/view {:style {:flex 1}}
[rn/view
{:style style/buttons-container}
[album-title true selected-album]
[clear-button selected]]
[rn/flat-list
{:key-fn identity
:render-fn image
:render-data {:window-width window-width :selected selected}
:data camera-roll-photos
:num-columns 3
:content-container-style {:width "100%"
:padding-bottom (+ (:bottom insets) 100)
:padding-top 80}
:on-end-reached #(rf/dispatch [:camera-roll/on-end-reached end-cursor
selected-album loading?
has-next-page?])}]
[bottom-gradient selected-images insets selected]]))]))])
(let [temporary-selected (reagent/atom [])] ; used when switching albums
(fn []
(let [selected (reagent/atom []) ; currently selected
selected-images (rf/sub [:chats/sending-image]) ; already selected and dispatched
selected-album (or (rf/sub [:camera-roll/selected-album]) (i18n/label :t/recent))]
(rn/use-effect
(fn []
(rf/dispatch [:chat.ui/camera-roll-get-photos 20 nil selected-album])
(if (seq selected-images)
(reset! selected (vec (vals selected-images)))
(reset! selected @temporary-selected)))
[selected-album])
[safe-area/consumer
(fn [insets]
(let [window-width (:width (rn/get-window))
camera-roll-photos (rf/sub [:camera-roll/photos])
end-cursor (rf/sub [:camera-roll/end-cursor])
loading? (rf/sub [:camera-roll/loading-more])
has-next-page? (rf/sub [:camera-roll/has-next-page])]
[rn/view {:style {:flex 1}}
[rn/view
{:style style/buttons-container}
(when platform/android?
[rn/touchable-opacity
{:active-opacity 1
:on-press #(rf/dispatch [:navigate-back])
:style style/camera-button-container}
[quo/icon :i/close
{:size 20 :color (colors/theme-colors colors/black colors/white)}]])
[album-title true selected-album selected temporary-selected]
[clear-button selected]]
[rn/flat-list
{:key-fn identity
:render-fn image
:render-data {:window-width window-width :selected selected}
:data camera-roll-photos
:num-columns 3
:content-container-style {:width "100%"
:padding-bottom (+ (:bottom insets) 100)
:padding-top 80}
:on-end-reached #(rf/dispatch [:camera-roll/on-end-reached end-cursor
selected-album loading?
has-next-page?])}]
[bottom-gradient selected-images insets selected]]))])))])

View File

@ -3,7 +3,7 @@
"_comment": "Instead use: scripts/update-status-go.sh <rev>",
"owner": "status-im",
"repo": "status-go",
"version": "v0.126.1",
"commit-sha1": "cb624b0cc14d95e46754210c9153f8a0444d2845",
"src-sha256": "1wrjlzwqj7qzsvh2aba4fjfp29ckx416lkxsk3c7las88ji93wzn"
"version": "v0.128.0",
"commit-sha1": "82596b2b67f82ae6329ad88e34be6240d08c8ff1",
"src-sha256": "17n461i2cglvs66rpqxdxpka4k9ld9ds2ha6scdli2nrfg9jb4q3"
}