mirror of
https://github.com/status-im/status-mobile.git
synced 2025-01-14 02:35:54 +00:00
feat: drag to dismiss lightbox (#15349)
* feat: drag to dismiss lightbox
This commit is contained in:
parent
8546727f84
commit
f640eb8c8f
10
src/js/worklets/lightbox.js
Normal file
10
src/js/worklets/lightbox.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { useDerivedValue } from 'react-native-reanimated';
|
||||
|
||||
export function infoLayout(input, isTop) {
|
||||
return useDerivedValue(
|
||||
function () {
|
||||
'worklet'
|
||||
return isTop ? input.value : -input.value
|
||||
}
|
||||
);
|
||||
}
|
@ -407,6 +407,7 @@ globalThis.__STATUS_MOBILE_JS_IDENTITY_PROXY__ = new Proxy({}, {get() { return (
|
||||
"../src/js/worklets/bottom_sheet.js" #js {}
|
||||
"../src/js/worklets/record_audio.js" #js {}
|
||||
"../src/js/worklets/scroll_view.js" #js {}
|
||||
"../src/js/worklets/lightbox.js" #js {}
|
||||
"./fleets.js" default-fleets
|
||||
"@walletconnect/client" wallet-connect-client
|
||||
"../translations/ar.json" (js/JSON.parse (slurp "./translations/ar.json"))
|
||||
|
@ -5,7 +5,9 @@
|
||||
RectButton
|
||||
Swipeable
|
||||
TouchableWithoutFeedback
|
||||
gestureHandlerRootHOC)]
|
||||
gestureHandlerRootHOC
|
||||
FlatList)]
|
||||
[react-native.flat-list :as rn-flat-list]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
(def gesture-detector (reagent/adapt-react-class GestureDetector))
|
||||
@ -28,6 +30,8 @@
|
||||
|
||||
(defn on-finalize [gesture handler] (.onFinalize ^js gesture handler))
|
||||
|
||||
(defn max-pointers [gesture count] (.maxPointers ^js gesture count))
|
||||
|
||||
(defn number-of-taps [gesture count] (.numberOfTaps ^js gesture count))
|
||||
|
||||
(defn enabled [gesture enabled?] (.enabled ^js gesture enabled?))
|
||||
@ -63,3 +67,8 @@
|
||||
(fn [& args]
|
||||
(reagent/as-element (apply render-right-actions args)))))]
|
||||
children))
|
||||
|
||||
(def gesture-flat-list (reagent/adapt-react-class FlatList))
|
||||
(defn flat-list
|
||||
[props]
|
||||
[gesture-flat-list (rn-flat-list/base-list-props props)])
|
||||
|
@ -5,26 +5,21 @@
|
||||
[react-native.reanimated :as reanimated]
|
||||
[status-im2.contexts.chat.lightbox.style :as style]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im2.contexts.chat.lightbox.animations :as anim]))
|
||||
|
||||
(def small-image-size 40)
|
||||
|
||||
(def focused-image-size 56)
|
||||
|
||||
(def small-list-height 80)
|
||||
[status-im2.contexts.chat.lightbox.animations :as anim]
|
||||
[status-im2.contexts.chat.lightbox.constants :as c]))
|
||||
|
||||
(defn get-small-item-layout
|
||||
[_ index]
|
||||
#js
|
||||
{:length small-image-size
|
||||
:offset (* (+ small-image-size 8) index)
|
||||
{:length c/small-image-size
|
||||
:offset (* (+ c/small-image-size 8) index)
|
||||
:index index})
|
||||
|
||||
(defn small-image
|
||||
[item index _ {:keys [scroll-index atoms]}]
|
||||
[:f>
|
||||
(fn []
|
||||
(let [size (if (= @scroll-index index) focused-image-size small-image-size)
|
||||
(let [size (if (= @scroll-index index) c/focused-image-size c/small-image-size)
|
||||
size-value (anim/use-val size)
|
||||
{:keys [scroll-index-lock? small-list-ref
|
||||
flat-list-ref]} atoms]
|
||||
@ -51,22 +46,22 @@
|
||||
{:border-radius 10})}]]))])
|
||||
|
||||
(defn bottom-view
|
||||
[messages index scroll-index insets animations item-width atoms]
|
||||
[messages index scroll-index insets animations derived item-width atoms]
|
||||
[:f>
|
||||
(fn []
|
||||
(let [text (get-in (first messages) [:content :text])
|
||||
padding-horizontal (- (/ item-width 2) (/ focused-image-size 2))]
|
||||
padding-horizontal (- (/ item-width 2) (/ c/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 animations)}
|
||||
:style (style/gradient-container insets animations derived)}
|
||||
(when (not= text "placeholder")
|
||||
[rn/text {:style style/text-style} text])
|
||||
[rn/flat-list
|
||||
{:ref #(reset! (:small-list-ref atoms) %)
|
||||
:key-fn :message-id
|
||||
:style {:height small-list-height}
|
||||
:style {:height c/small-list-height}
|
||||
:data messages
|
||||
:render-fn small-image
|
||||
:render-data {:scroll-index scroll-index
|
||||
|
13
src/status_im2/contexts/chat/lightbox/constants.cljs
Normal file
13
src/status_im2/contexts/chat/lightbox/constants.cljs
Normal file
@ -0,0 +1,13 @@
|
||||
(ns status-im2.contexts.chat.lightbox.constants)
|
||||
|
||||
(def ^:const small-image-size 40)
|
||||
|
||||
(def ^:const focused-extra-size 16)
|
||||
|
||||
(def ^:const focused-image-size (+ small-image-size focused-extra-size))
|
||||
|
||||
(def ^:const small-list-height 80)
|
||||
|
||||
(def ^:const small-list-padding-vertical 12)
|
||||
|
||||
(def ^:const top-view-height 56)
|
@ -1,13 +1,23 @@
|
||||
(ns status-im2.contexts.chat.lightbox.style
|
||||
(:require [quo2.foundations.colors :as colors]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.reanimated :as reanimated]))
|
||||
[react-native.reanimated :as reanimated]
|
||||
[status-im2.contexts.chat.lightbox.constants :as c]))
|
||||
|
||||
;;;; VIEW
|
||||
(defn image
|
||||
[width height]
|
||||
{:flex-direction :row
|
||||
:width width
|
||||
:height height
|
||||
:align-items :center
|
||||
:justify-content :center})
|
||||
|
||||
;;;; TOP-VIEW
|
||||
(defn top-view-container
|
||||
[top-inset {:keys [opacity rotate top-view-y top-view-x top-view-width top-view-bg top-layout]}
|
||||
window-width
|
||||
bg-color]
|
||||
[top-inset window-width bg-color landscape?
|
||||
{:keys [opacity rotate top-view-y top-view-x top-view-width top-view-bg]}
|
||||
{:keys [top-layout]}]
|
||||
(reanimated/apply-animations-to-style
|
||||
(if platform/ios?
|
||||
{:transform [{:translateY top-layout}
|
||||
@ -21,9 +31,8 @@
|
||||
:opacity opacity})
|
||||
{:position :absolute
|
||||
:padding-horizontal 20
|
||||
:top (if platform/ios? top-inset 0)
|
||||
;; height defined in top_view.cljs, but can't import due to circular dependency
|
||||
:height 56
|
||||
:top (if (or platform/ios? (not landscape?)) top-inset 0)
|
||||
:height c/top-view-height
|
||||
:z-index 4
|
||||
:flex-direction :row
|
||||
:justify-content :space-between
|
||||
@ -31,6 +40,14 @@
|
||||
:background-color (when platform/android? bg-color)
|
||||
:align-items :center}))
|
||||
|
||||
(defn top-gradient
|
||||
[insets]
|
||||
{:position :absolute
|
||||
:height (+ c/top-view-height (:top insets) 0)
|
||||
:top (- (:top insets))
|
||||
:left 0
|
||||
:right 0})
|
||||
|
||||
(def close-container
|
||||
{:width 32
|
||||
:height 32
|
||||
@ -44,18 +61,20 @@
|
||||
|
||||
;;;; BOTTOM-VIEW
|
||||
(defn gradient-container
|
||||
[insets {:keys [opacity bottom-layout]}]
|
||||
[insets {:keys [opacity]} {:keys [bottom-layout]}]
|
||||
(reanimated/apply-animations-to-style
|
||||
{:transform [{:translateY bottom-layout}]
|
||||
:opacity opacity}
|
||||
{:position :absolute
|
||||
:bottom 0
|
||||
:padding-bottom (:bottom insets)
|
||||
:padding-bottom (if platform/ios?
|
||||
(:bottom insets)
|
||||
(+ (:bottom insets) c/small-list-padding-vertical c/focused-extra-size))
|
||||
:z-index 3}))
|
||||
|
||||
(defn content-container
|
||||
[padding-horizontal]
|
||||
{:padding-vertical 12
|
||||
{:padding-vertical c/small-list-padding-vertical
|
||||
:padding-horizontal padding-horizontal
|
||||
:align-items :center
|
||||
:justify-content :center})
|
||||
|
@ -9,14 +9,13 @@
|
||||
[status-im2.contexts.chat.lightbox.animations :as anim]
|
||||
[status-im2.contexts.chat.lightbox.style :as style]
|
||||
[utils.datetime :as datetime]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
(def ^:const top-view-height 56)
|
||||
[utils.re-frame :as rf]
|
||||
[status-im2.contexts.chat.lightbox.constants :as c]))
|
||||
|
||||
(defn animate-rotation
|
||||
[result screen-width screen-height insets-atom
|
||||
[result screen-width screen-height insets
|
||||
{:keys [rotate top-view-y top-view-x top-view-width top-view-bg]}]
|
||||
(let [top-x (+ (/ top-view-height 2) (:top insets-atom))]
|
||||
(let [top-x (+ (/ c/top-view-height 2) (:top insets))]
|
||||
(cond
|
||||
(= result orientation/landscape-left)
|
||||
(do
|
||||
@ -41,22 +40,30 @@
|
||||
(anim/animate top-view-bg colors/neutral-100-opa-0)))))
|
||||
|
||||
(defn top-view
|
||||
[{:keys [from timestamp]} insets index animations landscape? screen-width]
|
||||
[{:keys [from timestamp]} insets index animations derived landscape? screen-width]
|
||||
[:f>
|
||||
(fn []
|
||||
(let [display-name (first (rf/sub [:contacts/contact-two-names-by-identity from]))
|
||||
bg-color (if landscape? colors/neutral-100-opa-70 colors/neutral-100-opa-0)]
|
||||
(let [display-name (first (rf/sub [:contacts/contact-two-names-by-identity
|
||||
from]))
|
||||
bg-color (if landscape?
|
||||
colors/neutral-100-opa-70
|
||||
colors/neutral-100-opa-0)
|
||||
{:keys [background-color opacity]} animations]
|
||||
[reanimated/view
|
||||
{:style (style/top-view-container (:top insets) animations screen-width bg-color)}
|
||||
{:style
|
||||
(style/top-view-container (:top insets) screen-width bg-color landscape? animations derived)}
|
||||
[reanimated/linear-gradient
|
||||
{:colors [(colors/alpha "#000000" 0.8) :transparent]
|
||||
:start {:x 0 :y 0}
|
||||
:end {:x 0 :y 1}
|
||||
:style (style/top-gradient insets)}]
|
||||
[rn/view
|
||||
{:style {:flex-direction :row
|
||||
:align-items :center}}
|
||||
[rn/touchable-opacity
|
||||
{:on-press (fn []
|
||||
(when platform/ios?
|
||||
(anim/animate (:background-color animations)
|
||||
(reanimated/with-timing "rgba(0,0,0,0)")))
|
||||
(anim/animate (:opacity animations) 0)
|
||||
(anim/animate background-color :transparent)
|
||||
(anim/animate opacity 0)
|
||||
(rf/dispatch (if platform/ios?
|
||||
[:chat.ui/exit-lightbox-signal @index]
|
||||
[:navigate-back])))
|
||||
|
@ -3,25 +3,34 @@
|
||||
[clojure.string :as string]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
[react-native.navigation :as navigation]
|
||||
[react-native.orientation :as orientation]
|
||||
[react-native.platform :as platform]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[status-im2.contexts.chat.lightbox.animations :as anim]
|
||||
[status-im2.contexts.chat.lightbox.style :as style]
|
||||
[utils.re-frame :as rf]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[reagent.core :as reagent]
|
||||
[react-native.gesture :as gesture]
|
||||
[status-im2.contexts.chat.lightbox.zoomable-image.view :as zoomable-image]
|
||||
[status-im2.contexts.chat.lightbox.top-view :as top-view]
|
||||
[status-im2.contexts.chat.lightbox.bottom-view :as bottom-view]
|
||||
[utils.worklets.lightbox :as worklet]
|
||||
[oops.core :refer [oget]]))
|
||||
|
||||
(def seperator-width 16)
|
||||
(def ^:const seperator-width 16)
|
||||
|
||||
(def ^:const drag-threshold 100)
|
||||
|
||||
(defn toggle-opacity
|
||||
[opacity-value border-value transparent? index {:keys [small-list-ref]}]
|
||||
(let [opacity (reanimated/get-shared-value opacity-value)]
|
||||
[index {:keys [opacity-value border-value transparent? atoms]} portrait?]
|
||||
(let [{:keys [small-list-ref]} atoms
|
||||
opacity (reanimated/get-shared-value opacity-value)]
|
||||
(if (= opacity 1)
|
||||
(do
|
||||
(when platform/ios?
|
||||
;; status-bar issue: https://github.com/status-im/status-mobile/issues/15343
|
||||
(js/setTimeout #(navigation/merge-options "lightbox" {:statusBar {:visible false}}) 75))
|
||||
(anim/animate opacity-value 0)
|
||||
(js/setTimeout #(reset! transparent? (not @transparent?)) 400))
|
||||
(do
|
||||
@ -29,17 +38,19 @@
|
||||
(js/setTimeout #(anim/animate opacity-value 1) 50)
|
||||
(js/setTimeout #(when @small-list-ref
|
||||
(.scrollToIndex ^js @small-list-ref #js {:animated false :index index}))
|
||||
100)))
|
||||
100)
|
||||
(when (and platform/ios? portrait?)
|
||||
(js/setTimeout #(navigation/merge-options "lightbox" {:statusBar {:visible true}}) 150))))
|
||||
(anim/animate border-value (if (= opacity 1) 0 12))))
|
||||
|
||||
(defn handle-orientation
|
||||
[result index window animations {:keys [flat-list-ref insets-atom]}]
|
||||
[result index window-width window-height animations insets {:keys [flat-list-ref]}]
|
||||
(let [screen-width (if (or platform/ios? (= result orientation/portrait))
|
||||
(:width window)
|
||||
(:height window))
|
||||
window-width
|
||||
window-height)
|
||||
screen-height (if (or platform/ios? (= result orientation/portrait))
|
||||
(:height window)
|
||||
(:width window))
|
||||
window-height
|
||||
window-width)
|
||||
landscape? (string/includes? result orientation/landscape)
|
||||
item-width (if (and landscape? platform/ios?) screen-height screen-width)
|
||||
timeout (if platform/ios? 50 100)]
|
||||
@ -56,7 +67,7 @@
|
||||
#js {:animated false :offset (* (+ item-width seperator-width) @index)}))
|
||||
timeout)
|
||||
(when platform/ios?
|
||||
(top-view/animate-rotation result screen-width screen-height @insets-atom animations))))
|
||||
(top-view/animate-rotation result screen-width screen-height insets animations))))
|
||||
|
||||
(defn get-item-layout
|
||||
[_ index item-width]
|
||||
@ -73,77 +84,104 @@
|
||||
(rf/dispatch [:chat.ui/update-shared-element-id (:message-id (oget changed :item))]))))
|
||||
|
||||
(defn image
|
||||
[message index _ {:keys [opacity-value border-value transparent? width height atoms]}]
|
||||
[:f>
|
||||
(fn []
|
||||
[rn/view
|
||||
{:style {:flex-direction :row
|
||||
:width (+ width seperator-width)
|
||||
:height height
|
||||
:align-items :center
|
||||
:justify-content :center}}
|
||||
[zoomable-image/zoomable-image message index border-value
|
||||
#(toggle-opacity opacity-value border-value transparent? index atoms)]
|
||||
[rn/view {:style {:width seperator-width}}]])])
|
||||
[message index _ {:keys [screen-width screen-height] :as args}]
|
||||
[rn/view
|
||||
{:style (style/image (+ screen-width seperator-width) screen-height)}
|
||||
[zoomable-image/zoomable-image message index args
|
||||
#(toggle-opacity index args %)]
|
||||
[rn/view {:style {:width seperator-width}}]])
|
||||
|
||||
;; using `safe-area/consumer` in this component in iOS causes unnecessary re-renders and weird behaviour
|
||||
;; using `use-safe-area` on Android crashes the app with error `rendered fewer hooks than expected`
|
||||
(defn container-view
|
||||
[children]
|
||||
(if platform/ios?
|
||||
[:f> children]
|
||||
[safe-area/consumer children]))
|
||||
(defn drag-gesture
|
||||
[{:keys [pan-x pan-y background-color opacity layout]} x? set-full-height?]
|
||||
(->
|
||||
(gesture/gesture-pan)
|
||||
(gesture/enabled true)
|
||||
(gesture/max-pointers 1)
|
||||
(gesture/on-start #(reset! set-full-height? false))
|
||||
(gesture/on-update (fn [e]
|
||||
(let [translation (if x? (oget e "translationX") (oget e "translationY"))
|
||||
progress (Math/abs (/ translation drag-threshold))]
|
||||
(anim/set-val (if x? pan-x pan-y) translation)
|
||||
(anim/set-val opacity (- 1 progress))
|
||||
(anim/set-val layout (* progress -20)))))
|
||||
(gesture/on-end (fn [e]
|
||||
(if (> (Math/abs (if x? (oget e "translationX") (oget e "translationY")))
|
||||
drag-threshold)
|
||||
(do
|
||||
(anim/animate background-color "rgba(0,0,0,0)")
|
||||
(anim/animate opacity 0)
|
||||
(rf/dispatch [:navigate-back]))
|
||||
(do
|
||||
#(reset! set-full-height? true)
|
||||
(anim/animate (if x? pan-x pan-y) 0)
|
||||
(anim/animate opacity 1)
|
||||
(anim/animate layout 0)))))))
|
||||
|
||||
(defn lightbox
|
||||
[]
|
||||
[:f>
|
||||
(fn []
|
||||
(let [{:keys [messages index]} (rf/sub [:get-screen-params])
|
||||
atoms {:flat-list-ref (atom nil)
|
||||
:small-list-ref (atom nil)
|
||||
:scroll-index-lock? (atom true)
|
||||
:insets-atom (atom nil)}
|
||||
;; we get `insets` from `screen-params` because trying to consume it from
|
||||
;; lightbox screen causes lots of problems
|
||||
(let [{:keys [messages index insets]} (rf/sub [:get-screen-params])
|
||||
atoms {:flat-list-ref (atom nil)
|
||||
:small-list-ref (atom nil)
|
||||
:scroll-index-lock? (atom true)}
|
||||
;; 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)
|
||||
window (rf/sub [:dimensions/window])
|
||||
animations {:background-color (anim/use-val "rgba(0,0,0,0)")
|
||||
:border (anim/use-val (if platform/ios? 0 12))
|
||||
:opacity (anim/use-val 0)
|
||||
:rotate (anim/use-val "0deg")
|
||||
:top-layout (anim/use-val -10)
|
||||
:bottom-layout (anim/use-val 10)
|
||||
:top-view-y (anim/use-val 0)
|
||||
:top-view-x (anim/use-val 0)
|
||||
:top-view-width (anim/use-val (:width window))
|
||||
:top-view-bg (anim/use-val colors/neutral-100-opa-0)}
|
||||
|
||||
callback (fn [e]
|
||||
(on-viewable-items-changed e scroll-index atoms))
|
||||
insets-ios (when platform/ios? (safe-area/use-safe-area))]
|
||||
data (reagent/atom [(nth messages index)])
|
||||
scroll-index (reagent/atom index)
|
||||
transparent? (reagent/atom false)
|
||||
set-full-height? (reagent/atom false)
|
||||
window (rf/sub [:dimensions/window])
|
||||
window-width (:width window)
|
||||
window-height (:height window)
|
||||
window-height (if platform/android?
|
||||
(+ window-height (:top insets))
|
||||
window-height)
|
||||
animations {:background-color (anim/use-val "rgba(0,0,0,0)")
|
||||
:border (anim/use-val (if platform/ios? 0 12))
|
||||
:opacity (anim/use-val 0)
|
||||
:rotate (anim/use-val "0deg")
|
||||
:layout (anim/use-val -10)
|
||||
:top-view-y (anim/use-val 0)
|
||||
:top-view-x (anim/use-val 0)
|
||||
:top-view-width (anim/use-val window-width)
|
||||
:top-view-bg (anim/use-val colors/neutral-100-opa-0)
|
||||
:pan-y (anim/use-val 0)
|
||||
:pan-x (anim/use-val 0)}
|
||||
derived {:top-layout (worklet/info-layout (:layout animations)
|
||||
true)
|
||||
:bottom-layout (worklet/info-layout (:layout animations)
|
||||
false)}
|
||||
callback (fn [e]
|
||||
(on-viewable-items-changed e scroll-index atoms))]
|
||||
(anim/animate (:background-color animations) "rgba(0,0,0,1)")
|
||||
(reset! data messages)
|
||||
(orientation/use-device-orientation-change
|
||||
(fn [result]
|
||||
(if platform/ios?
|
||||
(handle-orientation result scroll-index window animations atoms)
|
||||
(handle-orientation result scroll-index window-width window-height animations insets atoms)
|
||||
;; `use-device-orientation-change` will always be called on Android, so need to check
|
||||
(orientation/get-auto-rotate-state
|
||||
(fn [enabled?]
|
||||
;; RNN does not support landscape-right
|
||||
(when (and enabled? (not= result orientation/landscape-right))
|
||||
(handle-orientation result scroll-index window animations atoms)))))))
|
||||
(handle-orientation result
|
||||
scroll-index
|
||||
window-width
|
||||
window-height
|
||||
animations
|
||||
insets
|
||||
atoms)))))))
|
||||
(rn/use-effect (fn []
|
||||
(when @(:flat-list-ref atoms)
|
||||
(.scrollToIndex ^js @(:flat-list-ref atoms)
|
||||
#js {:animated false :index index}))
|
||||
(js/setTimeout (fn []
|
||||
(anim/animate (:opacity animations) 1)
|
||||
(anim/animate (:top-layout animations) 0)
|
||||
(anim/animate (:bottom-layout animations) 0)
|
||||
(anim/animate (:layout animations) 0)
|
||||
(anim/animate (:border animations) 12))
|
||||
(if platform/ios? 250 100))
|
||||
(js/setTimeout #(reset! (:scroll-index-lock? atoms) false) 300)
|
||||
@ -151,53 +189,57 @@
|
||||
(rf/dispatch [:chat.ui/zoom-out-signal nil])
|
||||
(when platform/android?
|
||||
(rf/dispatch [:chat.ui/lightbox-scale 1])))))
|
||||
[container-view
|
||||
(fn [insets-android]
|
||||
(let [insets (if platform/ios? insets-ios insets-android)
|
||||
curr-orientation (or (rf/sub [:lightbox/orientation]) orientation/portrait)
|
||||
[:f>
|
||||
(fn []
|
||||
(let [curr-orientation (or (rf/sub [:lightbox/orientation]) orientation/portrait)
|
||||
landscape? (string/includes? curr-orientation orientation/landscape)
|
||||
horizontal? (or platform/android? (not landscape?))
|
||||
inverted? (and platform/ios? (= curr-orientation orientation/landscape-right))
|
||||
screen-width (if (or platform/ios? (= curr-orientation orientation/portrait))
|
||||
(:width window)
|
||||
(:height window))
|
||||
window-width
|
||||
window-height)
|
||||
screen-height (if (or platform/ios? (= curr-orientation orientation/portrait))
|
||||
(:height window)
|
||||
(:width window))
|
||||
window-height
|
||||
window-width)
|
||||
item-width (if (and landscape? platform/ios?) screen-height screen-width)]
|
||||
(reset! (:insets-atom atoms) insets)
|
||||
[reanimated/view
|
||||
{:style (if platform/ios?
|
||||
(reanimated/apply-animations-to-style {:background-color (:background-color
|
||||
animations)}
|
||||
{})
|
||||
{:background-color :black})}
|
||||
{:style (reanimated/apply-animations-to-style {:background-color (:background-color
|
||||
animations)}
|
||||
{:height screen-height})}
|
||||
(when-not @transparent?
|
||||
[top-view/top-view (first messages) insets scroll-index animations landscape?
|
||||
[top-view/top-view (first messages) insets scroll-index animations derived landscape?
|
||||
screen-width])
|
||||
[rn/flat-list
|
||||
{:ref #(reset! (:flat-list-ref atoms) %)
|
||||
:key-fn :message-id
|
||||
:style {:width (+ screen-width seperator-width)}
|
||||
:data @data
|
||||
:render-fn image
|
||||
:render-data {:opacity-value (:opacity animations)
|
||||
:border-value (:border animations)
|
||||
:transparent? transparent?
|
||||
:height screen-height
|
||||
:width screen-width
|
||||
:atoms atoms}
|
||||
:horizontal horizontal?
|
||||
:inverted inverted?
|
||||
:paging-enabled true
|
||||
:get-item-layout (fn [_ index] (get-item-layout _ index item-width))
|
||||
:viewability-config {:view-area-coverage-percent-threshold 50
|
||||
:wait-for-interaction true}
|
||||
:shows-vertical-scroll-indicator false
|
||||
:shows-horizontal-scroll-indicator false
|
||||
:on-viewable-items-changed callback
|
||||
:content-container-style {:justify-content :center
|
||||
:align-items :center}}]
|
||||
[gesture/gesture-detector
|
||||
{:gesture (drag-gesture animations (and landscape? platform/ios?) set-full-height?)}
|
||||
[reanimated/view
|
||||
{:style (reanimated/apply-animations-to-style
|
||||
{:transform [{:translateY (:pan-y animations)}
|
||||
{:translateX (:pan-x animations)}]}
|
||||
{})}
|
||||
[gesture/flat-list
|
||||
{:ref #(reset! (:flat-list-ref atoms) %)
|
||||
:key-fn :message-id
|
||||
:style {:width (+ screen-width seperator-width)}
|
||||
:data @data
|
||||
:render-fn image
|
||||
:render-data {:opacity-value (:opacity animations)
|
||||
:border-value (:border animations)
|
||||
:transparent? transparent?
|
||||
:set-full-height? set-full-height?
|
||||
:screen-height screen-height
|
||||
:screen-width screen-width
|
||||
:window-height window-height
|
||||
:window-width window-width
|
||||
:atoms atoms}
|
||||
:horizontal horizontal?
|
||||
:inverted inverted?
|
||||
:paging-enabled true
|
||||
:get-item-layout (fn [_ index] (get-item-layout _ index item-width))
|
||||
:viewability-config {:view-area-coverage-percent-threshold 50
|
||||
:wait-for-interaction true}
|
||||
:shows-vertical-scroll-indicator false
|
||||
:shows-horizontal-scroll-indicator false
|
||||
:on-viewable-items-changed callback}]]]
|
||||
(when (and (not @transparent?) (not landscape?))
|
||||
[bottom-view/bottom-view messages index scroll-index insets animations
|
||||
[bottom-view/bottom-view messages index scroll-index insets animations derived
|
||||
item-width atoms])]))]))])
|
||||
|
@ -37,10 +37,10 @@
|
||||
{:keys [x-threshold-scale y-threshold-scale]}
|
||||
{:keys [scale saved-scale] :as animations}
|
||||
{:keys [pan-x-enabled? pan-y-enabled?] :as props}]
|
||||
(anim/animate scale value (if exit? 100 c/default-duration))
|
||||
(anim/set-val saved-scale value)
|
||||
(when (= value c/min-scale)
|
||||
(reset-values exit? animations props))
|
||||
(anim/animate scale value (if exit? 100 c/default-duration))
|
||||
(anim/set-val saved-scale value)
|
||||
(reset! pan-x-enabled? (> value x-threshold-scale))
|
||||
(reset! pan-y-enabled? (> value y-threshold-scale)))
|
||||
|
||||
@ -51,24 +51,37 @@
|
||||
{:keys [landscape-scale-val x-threshold-scale y-threshold-scale]}
|
||||
{:keys [rotate rotate-scale scale] :as animations}
|
||||
{:keys [pan-x-enabled? pan-y-enabled?]}]
|
||||
(let [duration (when focused? c/default-duration)]
|
||||
(if focused?
|
||||
(cond
|
||||
(= curr-orientation orientation/landscape-left)
|
||||
(do
|
||||
(anim/animate rotate "90deg" duration)
|
||||
(anim/animate rotate-scale landscape-scale-val duration))
|
||||
(anim/animate rotate "90deg")
|
||||
(anim/animate rotate-scale landscape-scale-val))
|
||||
(= curr-orientation orientation/landscape-right)
|
||||
(do
|
||||
(anim/animate rotate "-90deg" duration)
|
||||
(anim/animate rotate-scale landscape-scale-val duration))
|
||||
(anim/animate rotate "-90deg")
|
||||
(anim/animate rotate-scale landscape-scale-val))
|
||||
(= curr-orientation orientation/portrait)
|
||||
(do
|
||||
(anim/animate rotate c/init-rotation duration)
|
||||
(anim/animate rotate-scale c/min-scale duration)))
|
||||
(center-x animations false)
|
||||
(center-y animations false)
|
||||
(reset! pan-x-enabled? (> (anim/get-val scale) x-threshold-scale))
|
||||
(reset! pan-y-enabled? (> (anim/get-val scale) y-threshold-scale))))
|
||||
(anim/animate rotate c/init-rotation)
|
||||
(anim/animate rotate-scale c/min-scale)))
|
||||
(cond
|
||||
(= curr-orientation orientation/landscape-left)
|
||||
(do
|
||||
(anim/set-val rotate "90deg")
|
||||
(anim/set-val rotate-scale landscape-scale-val))
|
||||
(= curr-orientation orientation/landscape-right)
|
||||
(do
|
||||
(anim/set-val rotate "-90deg")
|
||||
(anim/set-val rotate-scale landscape-scale-val))
|
||||
(= curr-orientation orientation/portrait)
|
||||
(do
|
||||
(anim/set-val rotate c/init-rotation)
|
||||
(anim/set-val rotate-scale c/min-scale))))
|
||||
(center-x animations false)
|
||||
(center-y animations false)
|
||||
(reset! pan-x-enabled? (> (anim/get-val scale) x-threshold-scale))
|
||||
(reset! pan-y-enabled? (> (anim/get-val scale) y-threshold-scale)))
|
||||
|
||||
;; On ios, when attempting to navigate back while zoomed in, the shared-element transition animation
|
||||
;; doesn't execute properly, so we need to zoom out first
|
||||
@ -94,14 +107,9 @@
|
||||
"Calculates all required dimensions. Dimensions calculations are different on iOS and Android because landscape
|
||||
mode is implemented differently.On Android, we just need to resize the content, and the OS takes care of the
|
||||
animations. On iOS, we need to animate the content ourselves in code"
|
||||
[pixels-width pixels-height curr-orientation]
|
||||
(let [window (rf/sub [:dimensions/window])
|
||||
landscape? (string/includes? curr-orientation orientation/landscape)
|
||||
portrait? (= curr-orientation orientation/portrait)
|
||||
window-width (:width window)
|
||||
window-height (:height window)
|
||||
screen-width (if (or platform/ios? portrait?) window-width window-height)
|
||||
screen-height (if (or platform/ios? portrait?) window-height window-width)
|
||||
[pixels-width pixels-height curr-orientation
|
||||
{:keys [window-width screen-width screen-height]}]
|
||||
(let [landscape? (string/includes? curr-orientation orientation/landscape)
|
||||
portrait-image-width window-width
|
||||
portrait-image-height (* pixels-height (/ window-width pixels-width))
|
||||
landscape-image-width (* pixels-width (/ window-width pixels-height))
|
||||
|
@ -22,7 +22,9 @@
|
||||
(defn double-tap-gesture
|
||||
[{:keys [width height screen-width screen-height y-threshold-scale x-threshold-scale]}
|
||||
{:keys [scale pan-x pan-x-start pan-y pan-y-start]}
|
||||
rescale]
|
||||
rescale
|
||||
transparent?
|
||||
toggle-opacity]
|
||||
(->
|
||||
(gesture/gesture-tap)
|
||||
(gesture/number-of-taps 2)
|
||||
@ -37,8 +39,13 @@
|
||||
(when (> c/double-tap-scale y-threshold-scale)
|
||||
(anim/animate pan-y translate-y)
|
||||
(anim/set-val pan-y-start translate-y))
|
||||
(rescale c/double-tap-scale))
|
||||
(rescale c/min-scale))))))
|
||||
(rescale c/double-tap-scale)
|
||||
(when (not @transparent?)
|
||||
(toggle-opacity)))
|
||||
(do
|
||||
(rescale c/min-scale)
|
||||
(when @transparent?
|
||||
(toggle-opacity))))))))
|
||||
|
||||
;; not using on-finalize because on-finalize gets called always regardless the gesture executed or not
|
||||
(defn finalize-pinch
|
||||
@ -74,7 +81,9 @@
|
||||
{:keys [saved-scale scale pinch-x pinch-y pinch-x-start pinch-y-start pinch-x-max pinch-y-max]
|
||||
:as animations}
|
||||
{:keys [focal-x focal-y] :as props}
|
||||
rescale]
|
||||
rescale
|
||||
transparent?
|
||||
toggle-opacity]
|
||||
(->
|
||||
(gesture/gesture-pinch)
|
||||
(gesture/on-begin (fn [e]
|
||||
@ -82,6 +91,8 @@
|
||||
(reset! focal-x (oget e "focalX"))
|
||||
(reset! focal-y (utils/get-focal (oget e "focalY") height screen-height)))))
|
||||
(gesture/on-start (fn [e]
|
||||
(when (and (= (anim/get-val saved-scale) c/min-scale) (not @transparent?))
|
||||
(toggle-opacity))
|
||||
(when platform/android?
|
||||
(reset! focal-x (utils/get-focal (oget e "focalX") width screen-width))
|
||||
(reset! focal-y (utils/get-focal (oget e "focalY") height screen-height)))))
|
||||
@ -101,7 +112,10 @@
|
||||
(fn []
|
||||
(cond
|
||||
(< (anim/get-val scale) c/min-scale)
|
||||
(rescale c/min-scale)
|
||||
(do
|
||||
(when @transparent?
|
||||
(toggle-opacity))
|
||||
(rescale c/min-scale))
|
||||
(> (anim/get-val scale) c/max-scale)
|
||||
(do
|
||||
(anim/animate pinch-x (anim/get-val pinch-x-max))
|
||||
@ -191,64 +205,74 @@
|
||||
(anim/animate-decay pan-y-start velocity [lower-bound upper-bound]))))))))
|
||||
|
||||
(defn zoomable-image
|
||||
[{:keys [image-width image-height content message-id]} index border-radius on-tap]
|
||||
(let [set-full-height? (reagent/atom false)]
|
||||
[:f>
|
||||
(fn []
|
||||
(let [shared-element-id (rf/sub [:shared-element-id])
|
||||
exit-lightbox-signal (rf/sub [:lightbox/exit-signal])
|
||||
zoom-out-signal (rf/sub [:lightbox/zoom-out-signal])
|
||||
focused? (= shared-element-id message-id)
|
||||
curr-orientation (or (rf/sub [:lightbox/orientation]) orientation/portrait)
|
||||
dimensions (utils/get-dimensions image-width image-height curr-orientation)
|
||||
animations {:scale (anim/use-val c/min-scale)
|
||||
:saved-scale (anim/use-val c/min-scale)
|
||||
:pan-x-start (anim/use-val c/init-offset)
|
||||
:pan-x (anim/use-val c/init-offset)
|
||||
:pan-y-start (anim/use-val c/init-offset)
|
||||
:pan-y (anim/use-val c/init-offset)
|
||||
:pinch-x-start (anim/use-val c/init-offset)
|
||||
:pinch-x (anim/use-val c/init-offset)
|
||||
:pinch-y-start (anim/use-val c/init-offset)
|
||||
:pinch-y (anim/use-val c/init-offset)
|
||||
:pinch-x-max (anim/use-val js/Infinity)
|
||||
:pinch-y-max (anim/use-val js/Infinity)
|
||||
:rotate (anim/use-val c/init-rotation)
|
||||
:rotate-scale (anim/use-val c/min-scale)}
|
||||
props {:pan-x-enabled? (reagent/atom false)
|
||||
:pan-y-enabled? (reagent/atom false)
|
||||
:focal-x (reagent/atom nil)
|
||||
:focal-y (reagent/atom nil)}
|
||||
rescale (fn [value exit?]
|
||||
(utils/rescale-image value exit? dimensions animations props))]
|
||||
(rn/use-effect-once (fn []
|
||||
(js/setTimeout #(reset! set-full-height? true) 500)
|
||||
js/undefined))
|
||||
(when platform/ios?
|
||||
(utils/handle-orientation-change curr-orientation focused? dimensions animations props)
|
||||
(utils/handle-exit-lightbox-signal exit-lightbox-signal
|
||||
index
|
||||
(anim/get-val (:scale animations))
|
||||
rescale
|
||||
set-full-height?))
|
||||
(utils/handle-zoom-out-signal zoom-out-signal index (anim/get-val (:scale animations)) rescale)
|
||||
[:f>
|
||||
(fn []
|
||||
(let [tap (tap-gesture on-tap)
|
||||
double-tap (double-tap-gesture dimensions animations rescale)
|
||||
pinch (pinch-gesture dimensions animations props rescale)
|
||||
pan-x (pan-x-gesture dimensions animations props rescale)
|
||||
pan-y (pan-y-gesture dimensions animations props rescale)
|
||||
composed-gestures (gesture/exclusive
|
||||
(gesture/simultaneous pinch pan-x pan-y)
|
||||
(gesture/exclusive double-tap tap))]
|
||||
[gesture/gesture-detector {:gesture composed-gestures}
|
||||
[reanimated/view
|
||||
{:style (style/container dimensions
|
||||
animations
|
||||
@set-full-height?
|
||||
(= curr-orientation orientation/portrait))}
|
||||
[reanimated/fast-image
|
||||
{:source {:uri (:image content)}
|
||||
:native-ID (when focused? :shared-element)
|
||||
:style (style/image dimensions animations border-radius)}]]]))]))]))
|
||||
[{:keys [image-width image-height content message-id]} index args on-tap]
|
||||
[:f>
|
||||
(fn []
|
||||
(let [{:keys [transparent? set-full-height?]} args
|
||||
shared-element-id (rf/sub [:shared-element-id])
|
||||
exit-lightbox-signal (rf/sub [:lightbox/exit-signal])
|
||||
zoom-out-signal (rf/sub [:lightbox/zoom-out-signal])
|
||||
focused? (= shared-element-id message-id)
|
||||
curr-orientation (or (rf/sub [:lightbox/orientation])
|
||||
orientation/portrait)
|
||||
portrait? (= curr-orientation orientation/portrait)
|
||||
dimensions (utils/get-dimensions image-width
|
||||
image-height
|
||||
curr-orientation
|
||||
args)
|
||||
animations {:scale (anim/use-val c/min-scale)
|
||||
:saved-scale (anim/use-val c/min-scale)
|
||||
:pan-x-start (anim/use-val c/init-offset)
|
||||
:pan-x (anim/use-val c/init-offset)
|
||||
:pan-y-start (anim/use-val c/init-offset)
|
||||
:pan-y (anim/use-val c/init-offset)
|
||||
:pinch-x-start (anim/use-val c/init-offset)
|
||||
:pinch-x (anim/use-val c/init-offset)
|
||||
:pinch-y-start (anim/use-val c/init-offset)
|
||||
:pinch-y (anim/use-val c/init-offset)
|
||||
:pinch-x-max (anim/use-val js/Infinity)
|
||||
:pinch-y-max (anim/use-val js/Infinity)
|
||||
:rotate (anim/use-val c/init-rotation)
|
||||
:rotate-scale (anim/use-val c/min-scale)}
|
||||
props {:pan-x-enabled? (reagent/atom false)
|
||||
:pan-y-enabled? (reagent/atom false)
|
||||
:focal-x (reagent/atom nil)
|
||||
:focal-y (reagent/atom nil)}
|
||||
rescale (fn [value exit?]
|
||||
(utils/rescale-image value
|
||||
exit?
|
||||
dimensions
|
||||
animations
|
||||
props))]
|
||||
(rn/use-effect (fn []
|
||||
(js/setTimeout #(reset! set-full-height? true) 500)))
|
||||
(when platform/ios?
|
||||
(utils/handle-orientation-change curr-orientation focused? dimensions animations props)
|
||||
(utils/handle-exit-lightbox-signal exit-lightbox-signal
|
||||
index
|
||||
(anim/get-val (:scale animations))
|
||||
rescale
|
||||
set-full-height?))
|
||||
(utils/handle-zoom-out-signal zoom-out-signal index (anim/get-val (:scale animations)) rescale)
|
||||
[:f>
|
||||
(fn []
|
||||
(let [tap (tap-gesture #(on-tap portrait?))
|
||||
double-tap
|
||||
(double-tap-gesture dimensions animations rescale transparent? #(on-tap portrait?))
|
||||
pinch
|
||||
(pinch-gesture dimensions animations props rescale transparent? #(on-tap portrait?))
|
||||
pan-x (pan-x-gesture dimensions animations props rescale)
|
||||
pan-y (pan-y-gesture dimensions animations props rescale)
|
||||
composed-gestures (gesture/exclusive
|
||||
(gesture/simultaneous pinch pan-x pan-y)
|
||||
(gesture/exclusive double-tap tap))]
|
||||
[gesture/gesture-detector {:gesture composed-gestures}
|
||||
[reanimated/view
|
||||
{:style (style/container dimensions
|
||||
animations
|
||||
@set-full-height?
|
||||
(= curr-orientation orientation/portrait))}
|
||||
[reanimated/fast-image
|
||||
{:source {:uri (:image content)}
|
||||
:native-ID (when focused? :shared-element)
|
||||
:style (style/image dimensions animations (:border-value args))}]]]))]))])
|
||||
|
@ -3,6 +3,7 @@
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
[react-native.fast-image :as fast-image]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[status-im2.contexts.chat.messages.content.album.style :as style]
|
||||
[status-im2.constants :as constants]
|
||||
[status-im2.contexts.chat.messages.content.image.view :as image]
|
||||
@ -19,57 +20,63 @@
|
||||
|
||||
(defn album-message
|
||||
[{:keys [albumize?] :as message} context on-long-press]
|
||||
(let [shared-element-id (rf/sub [:shared-element-id])
|
||||
first-image (first (:album message))
|
||||
album-style (if (> (:image-width first-image) (:image-height first-image))
|
||||
:landscape
|
||||
:portrait)
|
||||
images-count (count (:album message))
|
||||
;; album images are always square, except when we have 3 images, then they must be rectangular
|
||||
;; (portrait or landscape)
|
||||
portrait? (and (= images-count rectangular-style-count) (= album-style :portrait))
|
||||
text (:text (:content first-image))]
|
||||
(if (and albumize? (> images-count 1))
|
||||
[:<>
|
||||
(when (not= text "placeholder")
|
||||
[rn/view {:style {:margin-bottom 10}} [text/text-content first-image context]])
|
||||
[rn/view
|
||||
{:style (style/album-container portrait?)}
|
||||
(map-indexed
|
||||
(fn [index item]
|
||||
(let [images-size-key (if (< images-count constants/max-album-photos) images-count :default)
|
||||
size (get-in constants/album-image-sizes [images-size-key index])
|
||||
dimensions (if (not= images-count rectangular-style-count)
|
||||
{:width size :height size}
|
||||
(find-size size album-style))]
|
||||
[rn/touchable-opacity
|
||||
{:key (:message-id item)
|
||||
:active-opacity 1
|
||||
:on-long-press #(on-long-press message context)
|
||||
:on-press (fn []
|
||||
(rf/dispatch [:chat.ui/update-shared-element-id (:message-id item)])
|
||||
(js/setTimeout #(rf/dispatch [:navigate-to :lightbox
|
||||
{:messages (:album message)
|
||||
:index index}])
|
||||
100))}
|
||||
[fast-image/fast-image
|
||||
{:style (style/image dimensions index portrait? images-count)
|
||||
:source {:uri (:image (:content item))}
|
||||
:native-ID (when (and (= shared-element-id (:message-id item))
|
||||
(< index constants/max-album-photos))
|
||||
:shared-element)}]
|
||||
(when (and (> images-count constants/max-album-photos)
|
||||
(= index (- constants/max-album-photos 1)))
|
||||
[rn/view
|
||||
{:style style/overlay}
|
||||
[quo/text
|
||||
{:weight :bold
|
||||
:size :heading-2
|
||||
:style {:color colors/white}}
|
||||
(str "+" (- images-count (dec constants/max-album-photos)))]])]))
|
||||
(:album message))]]
|
||||
[:<>
|
||||
(map-indexed
|
||||
(fn [index item]
|
||||
[image/image-message index item context #(on-long-press message context)])
|
||||
(:album message))])))
|
||||
[:f>
|
||||
(fn []
|
||||
(let [insets (safe-area/use-safe-area)
|
||||
shared-element-id (rf/sub [:shared-element-id])
|
||||
first-image (first (:album message))
|
||||
album-style (if (> (:image-width first-image) (:image-height first-image))
|
||||
:landscape
|
||||
:portrait)
|
||||
images-count (count (:album message))
|
||||
;; album images are always square, except when we have 3 images, then they must be rectangular
|
||||
;; (portrait or landscape)
|
||||
portrait? (and (= images-count rectangular-style-count) (= album-style :portrait))
|
||||
text (:text (:content first-image))]
|
||||
(if (and albumize? (> images-count 1))
|
||||
[:<>
|
||||
(when (not= text "placeholder")
|
||||
[rn/view {:style {:margin-bottom 10}} [text/text-content first-image context]])
|
||||
[rn/view
|
||||
{:style (style/album-container portrait?)}
|
||||
(map-indexed
|
||||
(fn [index item]
|
||||
(let [images-size-key (if (< images-count constants/max-album-photos)
|
||||
images-count
|
||||
:default)
|
||||
size (get-in constants/album-image-sizes [images-size-key index])
|
||||
dimensions (if (not= images-count rectangular-style-count)
|
||||
{:width size :height size}
|
||||
(find-size size album-style))]
|
||||
[rn/touchable-opacity
|
||||
{:key (:message-id item)
|
||||
:active-opacity 1
|
||||
:on-long-press #(on-long-press message context)
|
||||
:on-press (fn []
|
||||
(rf/dispatch [:chat.ui/update-shared-element-id (:message-id item)])
|
||||
(js/setTimeout #(rf/dispatch [:navigate-to :lightbox
|
||||
{:messages (:album message)
|
||||
:index index
|
||||
:insets insets}])
|
||||
100))}
|
||||
[fast-image/fast-image
|
||||
{:style (style/image dimensions index portrait? images-count)
|
||||
:source {:uri (:image (:content item))}
|
||||
:native-ID (when (and (= shared-element-id (:message-id item))
|
||||
(< index constants/max-album-photos))
|
||||
:shared-element)}]
|
||||
(when (and (> images-count constants/max-album-photos)
|
||||
(= index (- constants/max-album-photos 1)))
|
||||
[rn/view
|
||||
{:style style/overlay}
|
||||
[quo/text
|
||||
{:weight :bold
|
||||
:size :heading-2
|
||||
:style {:color colors/white}}
|
||||
(str "+" (- images-count (dec constants/max-album-photos)))]])]))
|
||||
(:album message))]]
|
||||
[:<>
|
||||
(map-indexed
|
||||
(fn [index item]
|
||||
[image/image-message index item context #(on-long-press message context)])
|
||||
(:album message))])))])
|
||||
|
@ -2,6 +2,7 @@
|
||||
(:require
|
||||
[react-native.core :as rn]
|
||||
[react-native.fast-image :as fast-image]
|
||||
[react-native.safe-area :as safe-area]
|
||||
[status-im2.constants :as constants]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im2.contexts.chat.messages.content.text.view :as text]))
|
||||
@ -14,23 +15,28 @@
|
||||
|
||||
(defn image-message
|
||||
[index {:keys [content image-width image-height message-id] :as message} context on-long-press]
|
||||
(let [dimensions (calculate-dimensions (or image-width 1000) (or image-height 1000))
|
||||
text (:text content)]
|
||||
(fn []
|
||||
(let [shared-element-id (rf/sub [:shared-element-id])]
|
||||
[rn/touchable-opacity
|
||||
{:active-opacity 1
|
||||
:key message-id
|
||||
:style {:margin-top (when (> index 0) 10)}
|
||||
:on-long-press on-long-press
|
||||
:on-press (fn []
|
||||
(rf/dispatch [:chat.ui/update-shared-element-id message-id])
|
||||
(js/setTimeout #(rf/dispatch [:navigate-to :lightbox
|
||||
{:messages [message] :index 0}])
|
||||
100))}
|
||||
(when (and (not= text "placeholder") (= index 0))
|
||||
[rn/view {:style {:margin-bottom 10}} [text/text-content message context]])
|
||||
[fast-image/fast-image
|
||||
{:source {:uri (:image content)}
|
||||
:style (merge dimensions {:border-radius 12})
|
||||
:native-ID (when (= shared-element-id message-id) :shared-element)}]]))))
|
||||
[:f>
|
||||
(fn []
|
||||
(let [insets (safe-area/use-safe-area)
|
||||
dimensions (calculate-dimensions (or image-width 1000) (or image-height 1000))
|
||||
text (:text content)]
|
||||
(fn []
|
||||
(let [shared-element-id (rf/sub [:shared-element-id])]
|
||||
[rn/touchable-opacity
|
||||
{:active-opacity 1
|
||||
:key message-id
|
||||
:style {:margin-top (when (pos? index) 10)}
|
||||
:on-long-press on-long-press
|
||||
:on-press (fn []
|
||||
(rf/dispatch [:chat.ui/update-shared-element-id message-id])
|
||||
(js/setTimeout #(rf/dispatch [:navigate-to :lightbox
|
||||
{:messages (:album message)
|
||||
:index index
|
||||
:insets insets}])
|
||||
100))}
|
||||
(when (and (not= text "placeholder") (= index 0))
|
||||
[rn/view {:style {:margin-bottom 10}} [text/text-content message context]])
|
||||
[fast-image/fast-image
|
||||
{:source {:uri (:image content)}
|
||||
:style (merge dimensions {:border-radius 12})
|
||||
:native-ID (when (= shared-element-id message-id) :shared-element)}]]))))])
|
||||
|
@ -61,9 +61,11 @@
|
||||
{:name :lightbox
|
||||
:insets {:top false :bottom false}
|
||||
:options {:topBar {:visible false}
|
||||
:statusBar {:backgroundColor colors/black
|
||||
:statusBar {:backgroundColor :transparent
|
||||
:style :light
|
||||
:animate true}
|
||||
:animate true
|
||||
:drawBehind true
|
||||
:translucent true}
|
||||
:navigationBar {:backgroundColor colors/black}
|
||||
:layout {:componentBackgroundColor :transparent
|
||||
:backgroundColor :transparent}
|
||||
|
7
src/utils/worklets/lightbox.cljs
Normal file
7
src/utils/worklets/lightbox.cljs
Normal file
@ -0,0 +1,7 @@
|
||||
(ns utils.worklets.lightbox)
|
||||
|
||||
(def ^:private layout-worklets (js/require "../src/js/worklets/lightbox.js"))
|
||||
|
||||
(defn info-layout
|
||||
[input top?]
|
||||
(.infoLayout ^js layout-worklets input top?))
|
Loading…
x
Reference in New Issue
Block a user