Composer - Fix conflicting touchable and gesture (#17680)

Fix the unreliable swipe behavior of link previews in the chat composer. In many
occasions, we noticed the horizontal scroll wasn't being triggered properly or
not at all. In other occasions, the swipe gesture would incorrectly register a
press on the parent `Touchable` component.

Fixes https://github.com/status-im/status-mobile/issues/16599
This commit is contained in:
Icaro Motta 2023-11-01 13:31:14 -03:00 committed by GitHub
parent 6c2b437e62
commit 972e1eee01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 125 additions and 143 deletions

View File

@ -17,9 +17,15 @@
(def ^:const empty-opacity 0.7)
(def ^:const images-container-height 76)
(def ^:const images-padding-top 12)
(def ^:const images-padding-bottom 8)
(def ^:const images-container-height
(+ actions-container-height images-padding-top images-padding-bottom))
(def ^:const links-container-height 76)
(def ^:const links-padding-top 12)
(def ^:const links-padding-bottom 8)
(def ^:const links-container-height
(+ actions-container-height links-padding-top links-padding-bottom))
(def ^:const reply-container-height 32)

View File

@ -168,20 +168,13 @@
(defn link-previews
[{:keys [sending-links?]}
{:keys [text-value maximized?]}
{:keys [height saved-height last-height]}
{:keys [height saved-height]}
{:keys [link-previews?]}]
(rn/use-effect
(fn []
(if-not @maximized?
(let [value (if link-previews?
constants/links-container-height
(- constants/links-container-height))]
(when (not= @sending-links? link-previews?)
(reanimated/animate height (+ (reanimated/get-shared-value saved-height) value))
(reanimated/set-shared-value saved-height
(+ (reanimated/get-shared-value saved-height) value))
(reanimated/set-shared-value last-height
(+ (reanimated/get-shared-value last-height) value))))
(when (not= @sending-links? link-previews?)
(reanimated/animate height (reanimated/get-shared-value saved-height)))
(let [curr-text @text-value]
(reset! text-value (str @text-value " "))
(js/setTimeout #(reset! text-value curr-text) 100)))
@ -191,7 +184,7 @@
(defn use-images
[{:keys [sending-images? input-ref]}
{:keys [text-value maximized?]}
{:keys [container-opacity height saved-height last-height]}
{:keys [container-opacity height saved-height]}
{:keys [images]}]
(rn/use-effect
(fn []
@ -200,15 +193,8 @@
(when (and (not @sending-images?) (seq images) @input-ref)
(.focus ^js @input-ref))
(if-not @maximized?
(let [value (if (seq images)
constants/images-container-height
(- constants/images-container-height))]
(when (not= @sending-images? (boolean (seq images)))
(reanimated/animate height (+ (reanimated/get-shared-value saved-height) value))
(reanimated/set-shared-value saved-height
(+ (reanimated/get-shared-value saved-height) value))
(reanimated/set-shared-value last-height
(+ (reanimated/get-shared-value last-height) value))))
(when (not= @sending-images? (boolean (seq images)))
(reanimated/animate height (reanimated/get-shared-value saved-height)))
(let [curr-text @text-value]
(reset! text-value (str @text-value " "))
(js/setTimeout #(reset! text-value curr-text) 100)))

View File

@ -59,7 +59,6 @@
[{:keys [input-ref] :as props}
{:keys [gesture-enabled?] :as state}
{:keys [height saved-height last-height opacity background-y container-opacity] :as animations}
{:keys [images link-previews?]}
{:keys [max-height lines] :as dimensions}
keyboard-shown]
(let [expanding? (atom true)
@ -82,7 +81,7 @@
(gesture/on-update
(fn [event]
(let [translation (oops/oget event "translationY")
min-height (utils/get-min-height lines images link-previews?)
min-height (utils/get-min-height lines)
new-height (- (reanimated/get-shared-value saved-height) translation)
bounded-height (utils.number/value-in-range new-height min-height max-height)]
(when keyboard-shown

View File

@ -4,24 +4,27 @@
[react-native.reanimated :as reanimated]
[status-im2.contexts.chat.composer.constants :as constants]))
(defn top-gradient-style
[opacity z-index]
(defn- top-gradient-style
[opacity z-index showing-extra-space?]
(reanimated/apply-animations-to-style
{:opacity opacity}
{:height 80
{:height (when (pos-int? z-index) 80)
:position :absolute
:z-index z-index
:top 0
:top (+ constants/bar-container-height
(if showing-extra-space?
constants/edit-container-height
0))
:left 0
:right 0}))
(defn top-gradient
[opacity z-index]
[opacity z-index showing-extra-space?]
{:colors [(colors/theme-colors colors/white-opa-0 colors/neutral-95-opa-0)
(colors/theme-colors colors/white colors/neutral-95)]
:start {:x 0 :y 1}
:end {:x 0 :y 0}
:style (top-gradient-style opacity z-index)})
:style (top-gradient-style opacity z-index showing-extra-space?)})
(def bottom-gradient-style
{:height constants/line-height

View File

@ -3,22 +3,25 @@
[react-native.core :as rn]
[react-native.linear-gradient :as linear-gradient]
[react-native.reanimated :as reanimated]
[status-im2.contexts.chat.composer.gradients.style :as style]))
[status-im2.contexts.chat.composer.gradients.style :as style]
[utils.re-frame :as rf]))
(defn f-view
[{:keys [input-ref]}
{:keys [gradient-z-index]}
{:keys [gradient-opacity]}
show-bottom-gradient?]
[:<>
[reanimated/linear-gradient (style/top-gradient gradient-opacity @gradient-z-index)]
(when show-bottom-gradient?
[rn/touchable-without-feedback
{:on-press #(when @input-ref (.focus ^js @input-ref))
:accessibility-label :bottom-gradient}
[linear-gradient/linear-gradient (style/bottom-gradient)]])])
(let [showing-extra-space? (boolean (or (rf/sub [:chats/edit-message])
(rf/sub [:chats/reply-message])))]
[:<>
[reanimated/linear-gradient
(style/top-gradient gradient-opacity @gradient-z-index showing-extra-space?)]
(when show-bottom-gradient?
[rn/pressable
{:on-press #(when @input-ref (.focus ^js @input-ref))
:accessibility-label :bottom-gradient}
[linear-gradient/linear-gradient (style/bottom-gradient)]])]))
(defn view
[props state animations show-bottom-gradient?]
[:f> f-view props state animations show-bottom-gradient?])

View File

@ -43,14 +43,12 @@
{:keys [images link-previews? reply]}]
(when-not @recording?
(let [lines (utils/calc-lines (- @content-height constants/extra-content-offset))
min-height (utils/get-min-height lines images link-previews?)
min-height (utils/get-min-height lines)
reopen-height (utils/calc-reopen-height text-value
min-height
max-height
content-height
saved-height
images
link-previews?)]
saved-height)]
(reset! focused? false)
(rf/dispatch [:chat.ui/set-input-focused false])
(reanimated/set-shared-value last-height reopen-height)
@ -72,21 +70,19 @@
[event
{:keys [maximized? lock-layout? text-value]}
{:keys [height saved-height opacity background-y]}
{:keys [images link-previews?]}
{:keys [content-height window-height max-height]}
keyboard-shown]
(when keyboard-shown
(let [event-size (oops/oget event "nativeEvent.contentSize.height")
content-size (+ event-size constants/extra-content-offset)
lines (utils/calc-lines event-size)
content-size (if (or (= lines 1) (empty? @text-value))
constants/input-height
(if (= lines 2) constants/multiline-minimized-height content-size))
new-height (utils.number/value-in-range content-size
constants/input-height
max-height)
bottom-content-height (utils/calc-bottom-content-height images link-previews?)
new-height (min (+ new-height bottom-content-height) max-height)]
(let [event-size (oops/oget event "nativeEvent.contentSize.height")
content-size (+ event-size constants/extra-content-offset)
lines (utils/calc-lines event-size)
content-size (if (or (= lines 1) (empty? @text-value))
constants/input-height
(if (= lines 2) constants/multiline-minimized-height content-size))
new-height (utils.number/value-in-range content-size
constants/input-height
max-height)
new-height (min new-height max-height)]
(reset! content-height content-size)
(when (utils/update-height? content-size height max-height maximized?)
(reanimated/animate height new-height)

View File

@ -1,10 +1,11 @@
(ns status-im2.contexts.chat.composer.images.style
(:require
[quo.foundations.colors :as colors]))
[quo.foundations.colors :as colors]
[status-im2.contexts.chat.composer.constants :as constants]))
(def image-container
{:padding-top 12
:padding-bottom 8
{:padding-top constants/images-padding-top
:padding-bottom constants/images-padding-bottom
:padding-right 12})
(defn remove-photo-container
@ -32,4 +33,3 @@
{:width 56
:height 56
:border-radius 12})

View File

@ -1,13 +1,12 @@
(ns status-im2.contexts.chat.composer.link-preview.style)
(ns status-im2.contexts.chat.composer.link-preview.style
(:require [status-im2.contexts.chat.composer.constants :as constants]))
(def padding-horizontal 20)
(def preview-list-padding-top 12)
(def preview-list-padding-bottom 8)
(def preview-height 56)
(def preview-list
{:padding-top preview-list-padding-top
:padding-bottom preview-list-padding-bottom
{:padding-top constants/links-padding-top
:padding-bottom constants/links-padding-bottom
:margin-horizontal (- padding-horizontal)
;; Keep a high index, otherwise the parent gesture detector used by the
;; composer grabs the initiating gesture event.

View File

@ -31,18 +31,19 @@
(defn- f-view
[suggestions-atom props state animations max-height cursor-pos images link-previews? reply edit]
(let [suggestions (rf/sub [:chat/mention-suggestions])
opacity (reanimated/use-shared-value (if (seq suggestions) 1 0))
size (count suggestions)
data {:keyboard-height @(:kb-height state)
:insets (safe-area/get-insets)
:curr-height (reanimated/get-shared-value (:height animations))
:window-height (:height (rn/get-window))
:images images
:link-previews? link-previews?
:reply reply
:edit edit}
mentions-pos (utils/calc-suggestions-position cursor-pos max-height size state data)]
(let [suggestions (rf/sub [:chat/mention-suggestions])
opacity (reanimated/use-shared-value (if (seq suggestions) 1 0))
size (count suggestions)
data {:keyboard-height @(:kb-height state)
:insets (safe-area/get-insets)
:curr-height (reanimated/get-shared-value (:height animations))
:window-height (:height (rn/get-window))
:images images
:link-previews? link-previews?
:reply reply
:edit edit}
mentions-pos
(utils/calc-suggestions-position cursor-pos max-height size state data images link-previews?)]
(rn/use-effect
(fn []
(if (seq suggestions)

View File

@ -7,6 +7,8 @@
[react-native.reanimated :as reanimated]
[status-im2.contexts.chat.composer.constants :as constants]))
(def border-top-radius 20)
(defn shadow
[focused? theme]
(if platform/ios?
@ -27,8 +29,8 @@
(reanimated/apply-animations-to-style
{:opacity container-opacity}
(merge
{:border-top-left-radius 20
:border-top-right-radius 20
{:border-top-left-radius border-top-radius
:border-top-right-radius border-top-radius
:padding-horizontal 20
:background-color (colors/theme-colors colors/white colors/neutral-95 theme)
:z-index 3
@ -55,7 +57,8 @@
[height max-height]
(reanimated/apply-animations-to-style
{:height height}
{:max-height max-height}))
{:max-height max-height
:z-index 1}))
(defn input-view
[{:keys [recording?]}]
@ -68,19 +71,17 @@
(defn input-text
[{:keys [saved-emoji-kb-extra-height]}
{:keys [focused? maximized?]}
{:keys [link-previews? images]}
{:keys [max-height theme]}]
(merge typography/paragraph-1
{:color (colors/theme-colors :black :white theme)
:text-align-vertical :top
:position (if @saved-emoji-kb-extra-height :relative :absolute)
:top 0
:left 0
:right (when (or focused? platform/ios?) 0)
:max-height (- max-height
(if link-previews? constants/links-container-height 0)
(if (seq images) constants/images-container-height 0))
:padding-bottom (when @maximized? 0)}))
(assoc typography/paragraph-1
:color (colors/theme-colors :black :white theme)
:text-align-vertical :top
:position (if @saved-emoji-kb-extra-height :relative :absolute)
:top 0
:left 0
:right (when (or focused? platform/ios?) 0)
:max-height max-height
:padding-bottom (when @maximized? 0)))
(defn background
[opacity background-y window-height]
(reanimated/apply-animations-to-style
@ -102,8 +103,8 @@
:left 0
:right 0
:bottom 0
:border-top-right-radius 20
:border-top-left-radius 20
:border-top-right-radius border-top-radius
:border-top-left-radius border-top-radius
:overflow :hidden}))
(defn blur-view

View File

@ -57,43 +57,40 @@
(defn calc-top-content-height
[reply? edit?]
(let [height (if reply? constants/reply-container-height 0)
height (if edit? (+ height constants/edit-container-height) height)]
height))
(cond-> 0
reply? (+ constants/reply-container-height)
edit? (+ constants/edit-container-height)))
(defn calc-bottom-content-height
[images link-previews?]
(let [height (if (seq images) constants/images-container-height 0)
height (if link-previews? (+ height constants/links-container-height) height)]
height))
(cond-> 0
(seq images) (+ constants/images-container-height)
link-previews? (+ constants/links-container-height)))
(defn calc-reopen-height
[text-value min-height max-height content-height saved-height images link-previews?]
[text-value min-height max-height content-height saved-height]
(if (empty? @text-value)
min-height
(let [bottom-content-height (calc-bottom-content-height images link-previews?)
input-height (min (+ @content-height bottom-content-height)
(reanimated/get-shared-value saved-height))]
(let [input-height (min @content-height
(reanimated/get-shared-value saved-height))]
(min max-height input-height))))
(defn get-min-height
[lines images link-previews?]
(let [input-height (if (> lines 1)
constants/multiline-minimized-height
constants/input-height)
bottom-content-height (calc-bottom-content-height images link-previews?)]
(+ input-height bottom-content-height)))
[lines]
(if (> lines 1)
constants/multiline-minimized-height
constants/input-height))
(defn calc-max-height
[{:keys [reply edit]} window-height kb-height insets]
(let [margin-top (if platform/ios? (:top insets) (+ 10 (:top insets)))
max-height (- window-height
margin-top
kb-height
constants/bar-container-height
constants/actions-container-height)
max-height (- max-height (calc-top-content-height reply edit))]
max-height))
[{:keys [reply edit images link-previews?]} window-height kb-height insets]
(let [margin-top (if platform/ios? (:top insets) (+ 10 (:top insets)))]
(- window-height
margin-top
kb-height
constants/bar-container-height
constants/actions-container-height
(calc-top-content-height reply edit)
(calc-bottom-content-height images link-previews?))))
(defn empty-input?
[text images link-previews? reply? audio?]
@ -124,21 +121,12 @@
sub-text-lines-in-view (- sub-text-lines scrolled-lines)]
(* sub-text-lines-in-view constants/line-height)))
(defn calc-shell-neg-y
[insets maximized? extra-height]
(let [padding 12
neg-y (if @maximized? -50 0)]
(- (+ constants/bar-container-height
constants/actions-container-height
(:bottom insets)
padding
extra-height
neg-y))))
(defn calc-suggestions-position
[cursor-pos max-height size
{:keys [maximized?]}
{:keys [insets curr-height window-height keyboard-height reply edit]}]
{:keys [insets curr-height window-height keyboard-height reply edit]}
images
link-previews?]
(let [base (+ constants/composer-default-height (:bottom insets) 8)
base (+ base (- curr-height constants/input-height))
base (+ base (calc-top-content-height reply edit))
@ -152,7 +140,8 @@
(+ constants/actions-container-height (:bottom insets))
(+ constants/actions-container-height (:bottom insets) (- max-height cursor-pos) 18))
(if (< (+ base container-height) view-height)
base
(let [bottom-content-height (calc-bottom-content-height images link-previews?)]
(+ base bottom-content-height))
(+ constants/actions-container-height (:bottom insets) (- curr-height cursor-pos) 18)))))
(defn init-props

View File

@ -86,7 +86,7 @@
[sub-view/shell-button state scroll-to-bottom-fn show-floating-scroll-down-button?]
[gesture/gesture-detector
{:gesture
(drag-gesture/drag-gesture props state animations subscriptions dimensions keyboard-shown)}
(drag-gesture/drag-gesture props state animations dimensions keyboard-shown)}
[reanimated/view
{:style (style/sheet-container insets state animations theme)
:on-layout #(handler/layout % state blur-height)}
@ -97,7 +97,9 @@
[edit/view state]])
[reanimated/touchable-opacity
{:active-opacity 1
:on-press (when @(:input-ref props) #(.focus ^js @(:input-ref props)))
:on-press (fn []
(when-let [ref @(:input-ref props)]
(.focus ^js ref)))
:style (style/input-container (:height animations) max-height)
:accessibility-label :message-input-container}
[rn/selectable-text-input
@ -113,7 +115,6 @@
:on-content-size-change #(handler/content-size-change %
state
animations
subscriptions
dimensions
(or keyboard-shown
(:edit subscriptions)))
@ -122,23 +123,21 @@
:on-selection-change #(handler/selection-change % props state)
:on-selection #(selection/on-selection % props state)
:keyboard-appearance (quo.theme/theme-value :light :dark)
:max-height max-height
:max-font-size-multiplier 1
:multiline true
:placeholder (i18n/label :t/type-something)
:placeholder-text-color (colors/theme-colors colors/neutral-30 colors/neutral-50)
:style (style/input-text props
state
subscriptions
{:max-height max-height
:theme theme})
:max-length constants/max-text-size
:accessibility-label :chat-message-input}]]
(when chat-screen-loaded?
[:<>
[gradients/view props state animations show-bottom-gradient?]
[link-preview/view]
[images/images-list]])]
:accessibility-label :chat-message-input}]]]
(when chat-screen-loaded?
[:<>
[gradients/view props state animations show-bottom-gradient?]
[link-preview/view]
[images/images-list]])
[:f> actions/view props state animations window-height insets scroll-to-bottom-fn
subscriptions]]]]]))