diff --git a/src/js/worklets/chat/messages.js b/src/js/worklets/chat/messages.js index bc08b4d9aa..4babcd3885 100644 --- a/src/js/worklets/chat/messages.js +++ b/src/js/worklets/chat/messages.js @@ -39,7 +39,7 @@ export function interpolateNavigationViewOpacity(props) { }); } -export function messagesListOnScroll(distanceFromListTop, callback) { +export function messagesListOnScroll(distanceFromListTop, chatListScrollY, callback) { return function (event) { 'worklet'; const currentY = event.contentOffset.y; @@ -47,7 +47,8 @@ export function messagesListOnScroll(distanceFromListTop, callback) { const contentSizeY = event.contentSize.height - layoutHeight; const newDistance = contentSizeY - currentY; distanceFromListTop.value = newDistance; - runOnJS(callback)(currentY, layoutHeight, newDistance); + chatListScrollY.value = currentY; + runOnJS(callback)(layoutHeight, newDistance); }; } @@ -64,3 +65,28 @@ export function placeholderZIndex(isCalculationsComplete) { return isCalculationsComplete.value ? 0 : 2; }); } + +export function scrollDownButtonOpacity(chatListScrollY, isComposerFocused, windowHeight) { + return useDerivedValue(function () { + 'worklet'; + if (isComposerFocused.value) { + return 0; + } else { + return chatListScrollY.value > windowHeight * 0.75 ? 1 : 0; + } + }); +} + +export function jumpToButtonOpacity(scrollDownButtonOpacity, isComposerFocused) { + return useDerivedValue(function () { + 'worklet'; + return withTiming(scrollDownButtonOpacity.value == 1 || isComposerFocused.value ? 0 : 1); + }); +} + +export function jumpToButtonPosition(scrollDownButtonOpacity, isComposerFocused) { + return useDerivedValue(function () { + 'worklet'; + return withTiming(scrollDownButtonOpacity.value == 1 || isComposerFocused.value ? 35 : 0); + }); +} diff --git a/src/status_im/common/home/actions/view.cljs b/src/status_im/common/home/actions/view.cljs index dea4ecd648..0eca293252 100644 --- a/src/status_im/common/home/actions/view.cljs +++ b/src/status_im/common/home/actions/view.cljs @@ -95,7 +95,8 @@ :button-text (i18n/label :t/confirm) :close-button-text (i18n/label :t/cancel) :on-press (fn [] - (hide-sheet-and-dispatch [:chat.ui/close-chat chat-id]) + (hide-sheet-and-dispatch [:chat.ui/close-and-remove-chat + chat-id]) (when inside-chat? (rf/dispatch [:navigate-back])))}])}])) diff --git a/src/status_im/contexts/chat/effects.cljs b/src/status_im/contexts/chat/effects.cljs index 57d5dfc28b..b0bdd764ab 100644 --- a/src/status_im/contexts/chat/effects.cljs +++ b/src/status_im/contexts/chat/effects.cljs @@ -1,6 +1,7 @@ (ns status-im.contexts.chat.effects (:require [react-native.async-storage :as async-storage] + [status-im.contexts.chat.messenger.messages.list.state :as chat.state] [status-im.contexts.shell.jump-to.constants :as shell.constants] [utils.re-frame :as rf])) @@ -16,3 +17,9 @@ (when (= stored-key-uid key-uid) (rf/dispatch [:chat/pop-to-root-and-navigate-to-chat chat-id shell.constants/open-screen-without-animation]))))))))) + +(rf/reg-fx :effects.chat/scroll-to-bottom + (fn [] + (some-> ^js @chat.state/messages-list-ref + (.scrollToOffset #js + {:animated true})))) diff --git a/src/status_im/contexts/chat/events.cljs b/src/status_im/contexts/chat/events.cljs index 42fe059c8a..06b1a83707 100644 --- a/src/status_im/contexts/chat/events.cljs +++ b/src/status_im/contexts/chat/events.cljs @@ -281,7 +281,7 @@ (rf/defn close-and-remove-chat "Closes the chat and removes it from chat list while retaining history, producing all necessary effects for that" - {:events [:chat.ui/close-chat]} + {:events [:chat.ui/close-and-remove-chat]} [{:keys [db now] :as cofx} chat-id] (rf/merge cofx {:effects/push-notifications-clear-message-notifications [chat-id] @@ -386,7 +386,7 @@ :confirm-button-text (i18n/label :t/delete) :on-accept #(do (rf/dispatch [:hide-bottom-sheet]) - (rf/dispatch [:chat.ui/close-chat chat-id]))}}) + (rf/dispatch [:chat.ui/close-and-remove-chat chat-id]))}}) (rf/defn navigate-to-user-pinned-messages "Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data" @@ -447,3 +447,8 @@ :params [{:id chat-id}] :on-success #() :on-error #(log/error "failed to fetch messages for chat" chat-id %)}]}) + +(rf/defn scroll-to-bottom + {:events [:chat.ui/scroll-to-bottom]} + [_] + {:effects.chat/scroll-to-bottom nil}) diff --git a/src/status_im/contexts/chat/messenger/composer/actions/view.cljs b/src/status_im/contexts/chat/messenger/composer/actions/view.cljs index f157cca6a8..da7e16a21d 100644 --- a/src/status_im/contexts/chat/messenger/composer/actions/view.cljs +++ b/src/status_im/contexts/chat/messenger/composer/actions/view.cljs @@ -20,8 +20,7 @@ {:keys [text-value focused? maximized?]} {:keys [height saved-height last-height opacity background-y container-opacity]} window-height - edit - scroll-to-bottom-fn] + edit] (reanimated/animate height comp-constants/input-height) (reanimated/set-shared-value saved-height comp-constants/input-height) (reanimated/set-shared-value last-height comp-constants/input-height) @@ -39,11 +38,11 @@ (reset! text-value "") (reset! sending-links? false) (reset! sending-images? false) - (when (and (not (some? edit)) scroll-to-bottom-fn) - (scroll-to-bottom-fn))) + (when-not (some? edit) + (rf/dispatch [:chat.ui/scroll-to-bottom]))) (defn f-send-button - [props state animations window-height images? btn-opacity scroll-to-bottom-fn z-index edit] + [props state animations window-height images? btn-opacity z-index edit] (let [{:keys [text-value]} state customization-color (rf/sub [:profile/customization-color])] (rn/use-effect (fn [] @@ -61,19 +60,17 @@ [reanimated/view {:style (style/send-button btn-opacity @z-index)} [quo/button - {:icon-only? true - :size 32 + {:icon-only? true + :size 32 :customization-color customization-color :accessibility-label :send-message-button - :on-press #(send-message props state animations window-height edit scroll-to-bottom-fn)} + :on-press #(send-message props state animations window-height edit)} :i/arrow-up]])) (defn send-button - [props {:keys [text-value] :as state} animations window-height images? edit btn-opacity - scroll-to-bottom-fn] + [props {:keys [text-value] :as state} animations window-height images? edit btn-opacity] (let [z-index (reagent/atom (if (and (empty? @text-value) (not images?)) 0 1))] - [:f> f-send-button props state animations window-height images? btn-opacity scroll-to-bottom-fn - z-index edit])) + [:f> f-send-button props state animations window-height images? btn-opacity z-index edit])) (defn disabled-audio-button [opacity] @@ -231,7 +228,7 @@ :icon :i/format}]) (defn view - [props state animations window-height insets scroll-to-bottom-fn {:keys [edit images]}] + [props state animations window-height insets {:keys [edit images]}] (let [send-btn-opacity (reanimated/use-shared-value 0) audio-btn-opacity (reanimated/interpolate send-btn-opacity [0 1] [1 0])] [rn/view {:style style/actions-container} @@ -242,8 +239,7 @@ [image-button props animations insets edit] [reaction-button] [format-button]] - [:f> send-button props state animations window-height images edit send-btn-opacity - scroll-to-bottom-fn] + [:f> send-button props state animations window-height images edit send-btn-opacity] (when (and (not edit) (not images)) ;; TODO(alwx): needs to be replaced with an `audio-button` later. See ;; https://github.com/status-im/status-mobile/issues/16084 for more details. diff --git a/src/status_im/contexts/chat/messenger/composer/handlers.cljs b/src/status_im/contexts/chat/messenger/composer/handlers.cljs index 3e7de2b80f..6ae447b3ee 100644 --- a/src/status_im/contexts/chat/messenger/composer/handlers.cljs +++ b/src/status_im/contexts/chat/messenger/composer/handlers.cljs @@ -16,11 +16,11 @@ (defn focus "Animate to the `saved-height`, display background-overlay if needed, and set cursor position" [{:keys [input-ref] :as props} - {:keys [text-value focused? lock-selection? saved-cursor-position]} + {:keys [text-value focused? lock-selection? saved-cursor-position composer-focused?]} {:keys [height saved-height last-height opacity background-y container-opacity] :as animations} - {:keys [max-height] :as dimensions} - show-floating-scroll-down-button?] + {:keys [max-height] :as dimensions}] + (reanimated/set-shared-value composer-focused? true) (reset! focused? true) (rf/dispatch [:chat.ui/set-input-focused true]) (let [last-height-value (reanimated/get-shared-value last-height)] @@ -35,13 +35,12 @@ (when (and (not-empty @text-value) @input-ref) (.setNativeProps ^js @input-ref (clj->js {:selection {:start @saved-cursor-position :end @saved-cursor-position}}))) - (kb/handle-refocus-emoji-kb-ios props animations dimensions) - (reset! show-floating-scroll-down-button? false)) + (kb/handle-refocus-emoji-kb-ios props animations dimensions)) (defn blur "Save the current height, minimize the composer, animate-out the background, and save cursor position" [{:keys [text-value focused? lock-selection? cursor-position saved-cursor-position gradient-z-index - maximized? recording?]} + maximized? recording? composer-focused?]} {:keys [height saved-height last-height gradient-opacity container-opacity opacity background-y]} {:keys [content-height max-height window-height]} {:keys [images link-previews? reply]}] @@ -53,6 +52,7 @@ max-height content-height saved-height)] + (reanimated/set-shared-value composer-focused? false) (reset! focused? false) (rf/dispatch [:chat.ui/set-input-focused false]) (reanimated/set-shared-value last-height reopen-height) diff --git a/src/status_im/contexts/chat/messenger/composer/sub_view.cljs b/src/status_im/contexts/chat/messenger/composer/sub_view.cljs index b0c603116a..f698fe42d5 100644 --- a/src/status_im/contexts/chat/messenger/composer/sub_view.cljs +++ b/src/status_im/contexts/chat/messenger/composer/sub_view.cljs @@ -6,7 +6,8 @@ [react-native.reanimated :as reanimated] [status-im.contexts.chat.messenger.composer.style :as style] [utils.i18n :as i18n] - [utils.re-frame :as rf])) + [utils.re-frame :as rf] + [utils.worklets.chat.messages :as worklets])) (defn bar [theme] @@ -23,33 +24,33 @@ [:f> f-blur-view props]) (defn- f-shell-button - [{:keys [focused?]} scroll-to-bottom-fn show-floating-scroll-down-button?] - (let [customization-color (rf/sub [:profile/customization-color]) - hide-shell? (or @focused? @show-floating-scroll-down-button?) - y-shell (reanimated/use-shared-value (if hide-shell? 35 0)) - opacity (reanimated/use-shared-value (if hide-shell? 0 1))] - (rn/use-effect - (fn [] - (reanimated/animate opacity (if hide-shell? 0 1)) - (reanimated/animate y-shell (if hide-shell? 35 0))) - [@focused? @show-floating-scroll-down-button?]) + [{:keys [composer-focused?]} chat-list-scroll-y window-height] + (let [customization-color (rf/sub [:profile/customization-color]) + scroll-down-button-opacity (worklets/scroll-down-button-opacity + chat-list-scroll-y + composer-focused? + window-height) + jump-to-button-opacity (worklets/jump-to-button-opacity + scroll-down-button-opacity + composer-focused?) + jump-to-button-position (worklets/jump-to-button-position + scroll-down-button-opacity + composer-focused?)] [:<> [reanimated/view - {:style (style/shell-button y-shell opacity)} + {:style (style/shell-button jump-to-button-position jump-to-button-opacity)} [quo/floating-shell-button {:jump-to - {:on-press (fn [] - (rf/dispatch [:shell/navigate-to-jump-to])) + {:on-press #(rf/dispatch [:shell/navigate-to-jump-to]) :customization-color customization-color :label (i18n/label :t/jump-to) :style {:align-self :center}}} {}]] - (when (and (not @focused?) - @show-floating-scroll-down-button?) - [quo/floating-shell-button - {:scroll-to-bottom {:on-press scroll-to-bottom-fn}} - style/scroll-to-bottom-button])])) + [quo/floating-shell-button + {:scroll-to-bottom {:on-press #(rf/dispatch [:chat.ui/scroll-to-bottom])}} + style/scroll-to-bottom-button + scroll-down-button-opacity]])) (defn shell-button - [state scroll-to-bottom-fn show-floating-scroll-down-button?] - [:f> f-shell-button state scroll-to-bottom-fn show-floating-scroll-down-button?]) + [state chat-list-scroll-y window-height] + [:f> f-shell-button state chat-list-scroll-y window-height]) diff --git a/src/status_im/contexts/chat/messenger/composer/utils.cljs b/src/status_im/contexts/chat/messenger/composer/utils.cljs index c816f82f24..8d54fdd389 100644 --- a/src/status_im/contexts/chat/messenger/composer/utils.cljs +++ b/src/status_im/contexts/chat/messenger/composer/utils.cljs @@ -193,7 +193,8 @@ :record-permission? (reagent/atom true) :recording? (reagent/atom false) :first-level? (reagent/atom true) - :menu-items (reagent/atom selection/first-level-menu-items)}) + :menu-items (reagent/atom selection/first-level-menu-items) + :composer-focused? (reanimated/use-shared-value false)}) (defn init-subs [] diff --git a/src/status_im/contexts/chat/messenger/composer/view.cljs b/src/status_im/contexts/chat/messenger/composer/view.cljs index 77bac65505..6d4e074587 100644 --- a/src/status_im/contexts/chat/messenger/composer/view.cljs +++ b/src/status_im/contexts/chat/messenger/composer/view.cljs @@ -30,8 +30,7 @@ (defn sheet-component [{:keys [insets - scroll-to-bottom-fn - show-floating-scroll-down-button? + chat-list-scroll-y window-height blur-height opacity @@ -89,7 +88,7 @@ (:edit subscriptions)] [rn/view {:style style/composer-sheet-and-jump-to-container} - [sub-view/shell-button state scroll-to-bottom-fn show-floating-scroll-down-button?] + [sub-view/shell-button state chat-list-scroll-y window-height] [gesture/gesture-detector {:gesture (drag-gesture/drag-gesture props state animations dimensions keyboard-shown)} @@ -114,42 +113,39 @@ :menu-items @(:menu-items state) :style (style/input-view state)} [rn/text-input - {:ref #(reset! (:input-ref props) %) - :default-value @(:text-value state) - :on-focus - #(handler/focus props state animations dimensions show-floating-scroll-down-button?) - :on-blur #(handler/blur state animations dimensions subscriptions) - :on-content-size-change #(handler/content-size-change % - state - animations - dimensions - (or keyboard-shown - (:edit subscriptions))) - :on-scroll #(handler/scroll % props state animations dimensions) - :on-change-text #(handler/change-text % props state) - :on-selection-change #(handler/selection-change % props state) - :on-selection #(selection/on-selection % props state) - :keyboard-appearance (quo.theme/theme-value :light :dark) + {:ref #(reset! (:input-ref props) %) + :default-value @(:text-value state) + :on-focus #(handler/focus props state animations dimensions) + :on-blur #(handler/blur state animations dimensions subscriptions) + :on-content-size-change #(handler/content-size-change % + state + animations + dimensions + (or keyboard-shown + (:edit subscriptions))) + :on-scroll #(handler/scroll % props state animations dimensions) + :on-change-text #(handler/change-text % props state) + :on-selection-change #(handler/selection-change % props state) + :on-selection #(selection/on-selection % props state) + :keyboard-appearance (quo.theme/theme-value :light :dark) :max-font-size-multiplier 1 - :multiline true - :placeholder (i18n/label :t/type-something) - :placeholder-text-color (colors/theme-colors colors/neutral-40 colors/neutral-50) - :style (style/input-text props - state - {:max-height max-height - :theme theme}) - :max-length constants/max-text-size - :accessibility-label :chat-message-input}]]] + :multiline true + :placeholder (i18n/label :t/type-something) + :placeholder-text-color (colors/theme-colors colors/neutral-40 colors/neutral-50) + :style (style/input-text props + state + {:max-height max-height + :theme theme}) + :max-length constants/max-text-size + :accessibility-label :chat-message-input}]]] [:<> [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]]]]])) + [:f> actions/view props state animations window-height insets subscriptions]]]]])) (defn f-composer - [{:keys [insets scroll-to-bottom-fn show-floating-scroll-down-button? - messages-list-on-layout-finished?]}] + [{:keys [insets chat-list-scroll-y messages-list-on-layout-finished?]}] (let [window-height (:height (rn/get-window)) theme (quo.theme/use-theme-value) opacity (reanimated/use-shared-value 0) @@ -159,8 +155,7 @@ (:bottom insets))) extra-params {:insets insets :window-height window-height - :scroll-to-bottom-fn scroll-to-bottom-fn - :show-floating-scroll-down-button? show-floating-scroll-down-button? + :chat-list-scroll-y chat-list-scroll-y :blur-height blur-height :opacity opacity :background-y background-y diff --git a/src/status_im/contexts/chat/messenger/messages/list/state.cljs b/src/status_im/contexts/chat/messenger/messages/list/state.cljs index d06974e912..8d74349ed3 100644 --- a/src/status_im/contexts/chat/messenger/messages/list/state.cljs +++ b/src/status_im/contexts/chat/messenger/messages/list/state.cljs @@ -1,5 +1,6 @@ (ns status-im.contexts.chat.messenger.messages.list.state) +(defonce messages-list-ref (atom nil)) (defonce first-not-visible-item (atom nil)) (defonce scrolling (atom nil)) diff --git a/src/status_im/contexts/chat/messenger/messages/list/view.cljs b/src/status_im/contexts/chat/messenger/messages/list/view.cljs index 1547c47d36..c36bf7eb7f 100644 --- a/src/status_im/contexts/chat/messenger/messages/list/view.cljs +++ b/src/status_im/contexts/chat/messenger/messages/list/view.cljs @@ -23,37 +23,16 @@ [utils.re-frame :as rf] [utils.worklets.chat.messages :as worklets])) -(defonce ^:const threshold-percentage-to-show-floating-scroll-down-button 75) (defonce ^:const loading-indicator-extra-spacing 250) (defonce ^:const loading-indicator-page-loading-height 100) (defonce ^:const min-message-height 32) -(defonce messages-list-ref (atom nil)) (defn list-key-fn [{:keys [message-id value]}] (or message-id value)) -(defn list-ref [ref] (reset! messages-list-ref ref)) - -(defn scroll-to-bottom - [] - (some-> ^js @messages-list-ref - (.scrollToOffset #js - {:animated true}))) - -(defn on-scroll-fn - [show-floating-scroll-down-button? distance-atom layout-height-atom] - (fn [y layout-height new-distance] - (let [threshold-height (* (/ layout-height 100) - threshold-percentage-to-show-floating-scroll-down-button) - reached-threshold? (> y threshold-height)] - (when (not= layout-height @layout-height-atom) - (reset! layout-height-atom layout-height)) - (reset! distance-atom new-distance) - (when (not= reached-threshold? @show-floating-scroll-down-button?) - (rn/configure-next (:ease-in-ease-out rn/layout-animation-presets)) - (reset! show-floating-scroll-down-button? reached-threshold?))))) +(defn list-ref [ref] (reset! state/messages-list-ref ref)) (defn on-viewable-items-changed [e] - (when @messages-list-ref + (when @state/messages-list-ref (reset! state/first-not-visible-item (when-let [last-visible-element (aget (oops/oget e "viewableItems") (dec (oops/oget e "viewableItems.length")))] @@ -61,7 +40,7 @@ ;; Get first not visible element, if it's a datemark/gap ;; we might add messages on receiving as they do not have ;; a clock value, but usually it will be a message - first-not-visible (aget (oops/oget @messages-list-ref "props.data") (inc index))] + first-not-visible (aget (oops/oget @state/messages-list-ref "props.data") (inc index))] (when (and first-not-visible (= :message (:type first-not-visible))) first-not-visible)))))) @@ -300,9 +279,16 @@ (reanimated/set-shared-value calculations-complete? true)) (js/setTimeout #(reset! messages-list-on-layout-finished? true) 1000))) +(defn on-scroll-fn + [distance-atom layout-height-atom] + (fn [layout-height new-distance] + (when (not= layout-height @layout-height-atom) + (reset! layout-height-atom layout-height)) + (reset! distance-atom new-distance))) + (defn f-messages-list-content [{:keys [insets distance-from-list-top content-height layout-height cover-bg-color distance-atom - show-floating-scroll-down-button? calculations-complete? messages-list-on-layout-finished?]}] + calculations-complete? messages-list-on-layout-finished? chat-list-scroll-y]}] (let [theme (quo.theme/use-theme-value) chat (rf/sub [:chats/current-chat-chat-view]) {:keys [keyboard-shown]} (hooks/use-keyboard) @@ -354,9 +340,8 @@ :on-scroll (reanimated/use-animated-scroll-handler (worklets/messages-list-on-scroll distance-from-list-top - (on-scroll-fn show-floating-scroll-down-button? - distance-atom - layout-height))) + chat-list-scroll-y + (on-scroll-fn distance-atom layout-height))) :style {:background-color (colors/theme-colors colors/white colors/neutral-95 theme)} diff --git a/src/status_im/contexts/chat/messenger/messages/view.cljs b/src/status_im/contexts/chat/messenger/messages/view.cljs index 26a1bc4e50..7726884662 100644 --- a/src/status_im/contexts/chat/messenger/messages/view.cljs +++ b/src/status_im/contexts/chat/messenger/messages/view.cljs @@ -20,9 +20,9 @@ content-height (atom 0) layout-height (atom 0) distance-atom (atom 0) - show-floating-scroll-down-button? (reagent/atom false) messages-list-on-layout-finished? (reagent/atom false) - distance-from-list-top (reanimated/use-shared-value 0)] + distance-from-list-top (reanimated/use-shared-value 0) + chat-list-scroll-y (reanimated/use-shared-value 0)] [rn/keyboard-avoiding-view {:style style/keyboard-avoiding-container :keyboard-vertical-offset (- (:bottom insets))} @@ -36,14 +36,13 @@ :distance-atom distance-atom :calculations-complete? calculations-complete? :distance-from-list-top distance-from-list-top + :chat-list-scroll-y chat-list-scroll-y :messages-list-on-layout-finished? messages-list-on-layout-finished? - :cover-bg-color :turquoise - :show-floating-scroll-down-button? show-floating-scroll-down-button?}] + :cover-bg-color :turquoise}] [composer.view/composer {:insets insets - :scroll-to-bottom-fn list.view/scroll-to-bottom - :messages-list-on-layout-finished? messages-list-on-layout-finished? - :show-floating-scroll-down-button? show-floating-scroll-down-button?}]])) + :chat-list-scroll-y chat-list-scroll-y + :messages-list-on-layout-finished? messages-list-on-layout-finished?}]])) (defn lazy-chat-screen [calculations-complete?] diff --git a/src/utils/worklets/chat/messages.cljs b/src/utils/worklets/chat/messages.cljs index 6635acdc1b..88a6291cd0 100644 --- a/src/utils/worklets/chat/messages.cljs +++ b/src/utils/worklets/chat/messages.cljs @@ -23,9 +23,10 @@ [props] (.interpolateNavigationViewOpacity ^js messages-worklets (clj->js props))) +;;;; Messages List (defn messages-list-on-scroll - [distance-from-list-top callback] - (.messagesListOnScroll ^js messages-worklets distance-from-list-top callback)) + [distance-from-list-top chat-list-scroll-y callback] + (.messagesListOnScroll ^js messages-worklets distance-from-list-top chat-list-scroll-y callback)) ;;;; Placeholder (defn placeholder-opacity @@ -35,3 +36,16 @@ (defn placeholder-z-index [calculations-complete?] (.placeholderZIndex ^js messages-worklets calculations-complete?)) + +;;;; Common +(defn scroll-down-button-opacity + [chat-list-scroll-y composer-focused? window-height] + (.scrollDownButtonOpacity ^js messages-worklets chat-list-scroll-y composer-focused? window-height)) + +(defn jump-to-button-opacity + [scroll-down-button-opacity-sv composer-focused?] + (.jumpToButtonOpacity ^js messages-worklets scroll-down-button-opacity-sv composer-focused?)) + +(defn jump-to-button-position + [scroll-down-button-opacity-sv composer-focused?] + (.jumpToButtonPosition ^js messages-worklets scroll-down-button-opacity-sv composer-focused?))