From babcd96fb625aa843d373ced160d94fd6e54bbd9 Mon Sep 17 00:00:00 2001 From: flexsurfer Date: Wed, 11 Sep 2024 18:37:12 +0200 Subject: [PATCH] simplify composer (#20125) --- .../status_im/chat/models/mentions.cljs | 53 ++-- .../composer/actions/image/view.cljs | 54 ++++ .../messenger/composer/actions/style.cljs | 14 +- .../chat/messenger/composer/actions/view.cljs | 242 ++---------------- .../chat/messenger/composer/constants.cljs | 14 - .../chat/messenger/composer/edit/view.cljs | 17 +- .../chat/messenger/composer/effects.cljs | 223 +--------------- .../chat/messenger/composer/events.cljs | 28 +- .../chat/messenger/composer/gesture.cljs | 112 -------- .../chat/messenger/composer/handlers.cljs | 126 +-------- .../chat/messenger/composer/images/view.cljs | 6 +- .../chat/messenger/composer/keyboard.cljs | 87 ------- .../messenger/composer/link_preview/view.cljs | 6 +- .../messenger/composer/mentions/style.cljs | 5 +- .../messenger/composer/mentions/view.cljs | 73 ++---- .../chat/messenger/composer/reply/view.cljs | 13 +- .../chat/messenger/composer/style.cljs | 84 +----- .../chat/messenger/composer/sub_view.cljs | 48 ---- .../chat/messenger/composer/utils.cljs | 180 +------------ .../chat/messenger/composer/view.cljs | 199 +++----------- .../chat/messenger/messages/list/view.cljs | 24 +- .../messenger/messages/navigation/style.cljs | 13 +- .../messages/scroll_to_bottom/style.cljs | 11 + .../messages/scroll_to_bottom/view.cljs | 20 ++ .../chat/messenger/messages/view.cljs | 21 +- src/status_im/subs/chats.cljs | 7 - src/status_im/subs/root.cljs | 2 +- 27 files changed, 279 insertions(+), 1403 deletions(-) create mode 100644 src/status_im/contexts/chat/messenger/composer/actions/image/view.cljs delete mode 100644 src/status_im/contexts/chat/messenger/composer/gesture.cljs delete mode 100644 src/status_im/contexts/chat/messenger/composer/keyboard.cljs delete mode 100644 src/status_im/contexts/chat/messenger/composer/sub_view.cljs create mode 100644 src/status_im/contexts/chat/messenger/messages/scroll_to_bottom/style.cljs create mode 100644 src/status_im/contexts/chat/messenger/messages/scroll_to_bottom/view.cljs diff --git a/src/legacy/status_im/chat/models/mentions.cljs b/src/legacy/status_im/chat/models/mentions.cljs index 720666acd8..f637190fbf 100644 --- a/src/legacy/status_im/chat/models/mentions.cljs +++ b/src/legacy/status_im/chat/models/mentions.cljs @@ -91,14 +91,14 @@ :on-error #(rf/dispatch [:mention/on-error {:method method :params params} %])}]})) + (rf/defn on-to-input-field-success {:events [:mention/on-to-input-field-success]} [{:keys [db]} result] (log/debug "[mentions] on-to-input-field-success" {:result result}) - (let [{:keys [input-segments state chat-id new-text]} (transfer-mention-result result)] - {:db (-> db - (assoc-in [:chats/mentions chat-id :mentions] state) - (assoc-in [:chat/inputs-with-mentions chat-id] input-segments))})) + (let [{:keys [chat-id new-text]} (transfer-mention-result result)] + {:effects/set-input-text-value [(get-in db [:chat/inputs chat-id :input-ref]) new-text] + :dispatch [:chat.ui/set-chat-input-text new-text chat-id]})) (rf/defn on-change-text {:events [:mention/on-change-text]} @@ -118,49 +118,32 @@ {:events [:mention/on-change-text-success]} [{:keys [db]} result] (log/debug "[mentions] on-change-text-success" {:result result}) - (let [{:keys [state chat-id mentionable-users input-segments]} (transfer-mention-result result)] - {:db (-> db - (assoc-in [:chats/mention-suggestions chat-id] mentionable-users) - (assoc-in [:chats/mentions chat-id :mentions] state) - (assoc-in [:chat/inputs-with-mentions chat-id] input-segments))})) + (let [{:keys [chat-id mentionable-users]} (transfer-mention-result result)] + {:db (assoc-in db [:chats/mention-suggestions chat-id] mentionable-users)})) (rf/defn on-select-mention-success {:events [:mention/on-select-mention-success]} - [{:keys [db] :as cofx} result primary-name match searched-text public-key] + [{:keys [db]} result primary-name match searched-text public-key] (log/debug "[mentions] on-select-mention-success" {:result result :primary-name primary-name :match match :searched-text searched-text :public-key public-key}) - (let [{:keys [new-text chat-id state input-segments]} (transfer-mention-result result)] - {:db (-> db - (assoc-in [:chats/mentions chat-id :mentions] state) - (assoc-in [:chat/inputs-with-mentions chat-id] input-segments) - (assoc-in [:chats/mention-suggestions chat-id] nil)) - :dispatch [:chat.ui/set-chat-input-text new-text chat-id]})) - -(rf/defn clear-suggestions - [{:keys [db]}] - (log/debug "[mentions] clear suggestions") - (let [chat-id (:current-chat-id db)] - {:db (update db :chats/mention-suggestions dissoc chat-id)})) + (let [{:keys [new-text chat-id]} (transfer-mention-result result)] + {:db (assoc-in db [:chats/mention-suggestions chat-id] nil) + :effects/set-input-text-value [(get-in db [:chat/inputs (:current-chat-id db) :input-ref]) new-text] + :dispatch [:chat.ui/set-chat-input-text new-text chat-id]})) (rf/defn clear-mentions - [{:keys [db] :as cofx}] - (log/debug "[mentions] clear mentions") + [{:keys [db]}] (let [chat-id (:current-chat-id db)] - (rf/merge - cofx - {:db (-> db - (update-in [:chats/mentions chat-id] dissoc :mentions) - (update :chat/inputs-with-mentions dissoc chat-id)) - :json-rpc/call [{:method "wakuext_chatMentionClearMentions" - :params [chat-id] - :on-success #() - :on-error #(log/error "Error while calling wakuext_chatMentionClearMentions" - {:error %})}]} - (clear-suggestions)))) + {:db (update db :chats/mention-suggestions dissoc chat-id) + :json-rpc/call [{:method "wakuext_chatMentionClearMentions" + :params [chat-id] + :on-success #() + :on-error #(log/error "Error while calling wakuext_chatMentionClearMentions" + {:error %})}]})) (rf/defn select-mention {:events [:chat.ui/select-mention]} diff --git a/src/status_im/contexts/chat/messenger/composer/actions/image/view.cljs b/src/status_im/contexts/chat/messenger/composer/actions/image/view.cljs new file mode 100644 index 0000000000..71780dcf46 --- /dev/null +++ b/src/status_im/contexts/chat/messenger/composer/actions/image/view.cljs @@ -0,0 +1,54 @@ +(ns status-im.contexts.chat.messenger.composer.actions.image.view + (:require [quo.core :as quo] + [react-native.permissions :as permissions] + [react-native.platform :as platform] + [status-im.common.alert.effects :as alert.effects] + [status-im.common.device-permissions :as device-permissions] + [status-im.constants :as constants] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn photo-limit-toast + [] + (rf/dispatch [:toasts/upsert + {:id :random-id + :type :negative + :text (i18n/label :t/hit-photos-limit + {:max-photos constants/max-album-photos})}])) + +(defn go-to-camera + [images-count] + (device-permissions/camera #(if (>= images-count constants/max-album-photos) + (photo-limit-toast) + (rf/dispatch [:navigate-to :camera-screen])))) + +(defn camera-button + [] + (let [images-count (count (vals (rf/sub [:chats/sending-image])))] + [quo/composer-button + {:on-press #(go-to-camera images-count) + :accessibility-label :camera-button + :icon :i/camera + :container-style {:margin-right 12}}])) + +(defn open-photo-selector + [input-ref] + (permissions/request-permissions + {:permissions [(if platform/is-below-android-13? :read-external-storage :read-media-images) + :write-external-storage] + :on-allowed (fn [] + (when (and platform/android? @input-ref) + (.blur ^js @input-ref)) + (when platform/ios? + (rf/dispatch [:alert-banners/hide])) + (rf/dispatch [:photo-selector/navigate-to-photo-selector])) + :on-denied #(alert.effects/show-popup (i18n/label :t/error) + (i18n/label :t/external-storage-denied))})) + +(defn image-button + [input-ref] + [quo/composer-button + {:on-press #(open-photo-selector input-ref) + :accessibility-label :open-images-button + :container-style {:margin-right 12} + :icon :i/image}]) diff --git a/src/status_im/contexts/chat/messenger/composer/actions/style.cljs b/src/status_im/contexts/chat/messenger/composer/actions/style.cljs index 705d734390..d4b872f7f8 100644 --- a/src/status_im/contexts/chat/messenger/composer/actions/style.cljs +++ b/src/status_im/contexts/chat/messenger/composer/actions/style.cljs @@ -7,26 +7,14 @@ {:height constants/actions-container-height :justify-content :space-between :align-items :center - :z-index 2 :flex-direction :row}) (defn send-button - [opacity z-index] + [opacity] (reanimated/apply-animations-to-style {:opacity opacity} {:position :absolute :right 0 - :z-index z-index :padding-vertical 3 :padding-left 2})) -(defn record-audio-container - [] - {:align-items :center - :background-color :transparent - :flex-direction :row - :position :absolute - :left -20 - :right -20 - :bottom 0 - :height constants/composer-default-height}) 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 7962e3fb46..06f5072b52 100644 --- a/src/status_im/contexts/chat/messenger/composer/actions/view.cljs +++ b/src/status_im/contexts/chat/messenger/composer/actions/view.cljs @@ -2,246 +2,60 @@ (:require [quo.core :as quo] [react-native.core :as rn] - [react-native.permissions :as permissions] - [react-native.platform :as platform] [react-native.reanimated :as reanimated] - [reagent.core :as reagent] - [status-im.common.alert.effects :as alert.effects] - [status-im.common.device-permissions :as device-permissions] - [status-im.config :as config] [status-im.constants :as constants] + [status-im.contexts.chat.messenger.composer.actions.image.view :as actions.image] [status-im.contexts.chat.messenger.composer.actions.style :as style] - [status-im.contexts.chat.messenger.composer.constants :as comp-constants] - [utils.i18n :as i18n] [utils.re-frame :as rf])) (defn send-message - "Minimize composer, animate-out background overlay, clear input and flush state" - [{:keys [sending-images? sending-links?]} - {:keys [text-value maximized?]} - {:keys [height saved-height last-height opacity background-y]} - window-height - 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) - (reanimated/animate opacity 0) - (js/setTimeout #(reanimated/set-shared-value background-y - (- window-height)) - 300) + [input-ref edit btn-opacity] + (when @input-ref + (.clear ^js @input-ref)) + (reanimated/animate btn-opacity 0) (rf/dispatch [:chat.ui/send-current-message]) - (rf/dispatch [:chat.ui/set-input-maximized false]) - (rf/dispatch [:chat.ui/set-input-content-height comp-constants/input-height]) (rf/dispatch [:chat.ui/set-chat-input-text nil]) - (reset! maximized? false) - (reset! text-value "") - (reset! sending-links? false) - (reset! sending-images? false) (when-not (some? edit) (rf/dispatch [:chat.ui/scroll-to-bottom]))) -(defn f-send-button - [props state animations window-height images? btn-opacity z-index edit] - (let [{:keys [text-value]} state +(defn send-button + [input-ref edit] + (let [btn-opacity (reanimated/use-shared-value 0) + chat-input (rf/sub [:chats/current-chat-input]) + input-text (:input-text chat-input) + images? (boolean (seq (rf/sub [:chats/sending-image]))) profile-customization-color (rf/sub [:profile/customization-color]) {:keys [chat-id chat-type] chat-color :color} (rf/sub [:chats/current-chat-chat-view]) contact-customization-color (when (= chat-type constants/one-to-one-chat-type) (rf/sub [:contacts/contact-customization-color-by-address - chat-id]))] + chat-id])) + on-press (rn/use-callback #(send-message input-ref edit btn-opacity) [edit])] (rn/use-effect (fn [] ;; Handle send button opacity animation and z-index when input content changes - (if (or (seq @text-value) images?) - (when (or (not= @z-index 1) (not= (reanimated/get-shared-value btn-opacity) 1)) - (reset! z-index 1) + (if (or (seq input-text) images?) + (when (not= (reanimated/get-shared-value btn-opacity) 1) (js/setTimeout #(reanimated/animate btn-opacity 1) 50)) - (when (or (not= @z-index 0) (not= (reanimated/get-shared-value btn-opacity) 0)) - (reanimated/animate btn-opacity 0) - (js/setTimeout #(when (and (empty? @text-value) (not images?)) - (reset! z-index 0)) - 300)))) - [(and (empty? @text-value) (not images?))]) + (when (not= (reanimated/get-shared-value btn-opacity) 0) + (reanimated/animate btn-opacity 0)))) + [(and (empty? input-text) (not images?))]) [reanimated/view - {:style (style/send-button btn-opacity @z-index)} + {:style (style/send-button btn-opacity)} [quo/button {:icon-only? true :size 32 :customization-color (or contact-customization-color chat-color profile-customization-color) :accessibility-label :send-message-button - :on-press #(send-message props state animations window-height edit)} + :on-press on-press} :i/arrow-up]])) -(defn send-button - [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 z-index edit])) - -(defn disabled-audio-button - [opacity] - [reanimated/view {:style (reanimated/apply-animations-to-style {:opacity opacity} {})} - [quo/composer-button - {:on-press (fn [] - (rf/dispatch [:chat.ui/set-input-focused false]) - (rn/dismiss-keyboard!) - (js/alert "to be implemented")) - :icon :i/audio}]]) - -(defn audio-button - [{:keys [record-reset-fn input-ref]} - {:keys [record-permission? recording? gesture-enabled? focused?]}] - (let [audio (rf/sub [:chats/sending-audio])] - [rn/view - {:style (style/record-audio-container) - :pointer-events :box-none} - [quo/record-audio - {:record-audio-permission-granted @record-permission? - :on-init (fn [reset-fn] - (reset! record-reset-fn reset-fn)) - :on-start-recording (fn [] - (rf/dispatch [:chat.ui/set-recording true]) - (reset! recording? true) - (reset! gesture-enabled? false)) - :audio-file audio - :on-lock (fn [] - (rf/dispatch [:chat.ui/set-recording false])) - :on-reviewing-audio (fn [file] - (rf/dispatch [:chat.ui/set-recording false]) - (rf/dispatch [:chat.ui/set-input-audio file])) - :on-send (fn [{:keys [file-path duration]}] - (rf/dispatch [:chat.ui/set-recording false]) - (reset! recording? false) - (reset! gesture-enabled? true) - (rf/dispatch [:chat/send-audio file-path duration]) - (when @focused? - (js/setTimeout #(when @input-ref (.focus ^js @input-ref)) - 300)) - (rf/dispatch [:chat.ui/set-input-audio nil])) - :on-cancel (fn [] - (when @recording? - (rf/dispatch [:chat.ui/set-recording false]) - (reset! recording? false) - (reset! gesture-enabled? true) - (when @focused? - (js/setTimeout #(when @input-ref - (.focus ^js @input-ref)) - 300)) - (rf/dispatch [:chat.ui/set-input-audio nil]))) - :on-check-audio-permissions (fn [] - (permissions/permission-granted? - :record-audio - #(reset! record-permission? %) - #(reset! record-permission? false))) - :on-request-record-audio-permission (fn [] - (rf/dispatch - [:request-permissions - {:permissions [:record-audio] - :on-allowed - #(reset! record-permission? true) - :on-denied - #(js/setTimeout - (fn [] - (alert.effects/show-popup - (i18n/label :t/audio-recorder-error) - (i18n/label - :t/audio-recorder-permissions-error) - nil - {:text (i18n/label :t/settings) - :accessibility-label :settings-button - :onPress (fn [] (permissions/open-settings))})) - 50)}])) - :max-duration-ms constants/audio-max-duration-ms}]])) - -(defn photo-limit-toast - [] - (rf/dispatch [:toasts/upsert - {:id :random-id - :type :negative - :text (i18n/label :t/hit-photos-limit - {:max-photos constants/max-album-photos})}])) - - -(defn go-to-camera - [images-count] - (device-permissions/camera #(if (>= images-count constants/max-album-photos) - (photo-limit-toast) - (rf/dispatch [:navigate-to :camera-screen])))) - -(defn camera-button - [edit] - (let [images-count (count (vals (rf/sub [:chats/sending-image])))] - [quo/composer-button - {:on-press (if edit - #(js/alert "This feature is temporarily unavailable in edit mode.") - #(go-to-camera images-count)) - :accessibility-label :camera-button - :icon :i/camera - :container-style {:margin-right 12}}])) - - -(defn open-photo-selector - [{:keys [input-ref]} - {:keys [height]}] - (permissions/request-permissions - {:permissions [(if platform/is-below-android-13? :read-external-storage :read-media-images) - :write-external-storage] - :on-allowed (fn [] - (when (and platform/android? @input-ref) - (.blur ^js @input-ref)) - (when platform/ios? - (rf/dispatch [:alert-banners/hide])) - (rf/dispatch [:chat.ui/set-input-content-height - (reanimated/get-shared-value height)]) - (rf/dispatch [:photo-selector/navigate-to-photo-selector])) - :on-denied (fn [] - (alert.effects/show-popup (i18n/label :t/error) - (i18n/label - :t/external-storage-denied)))})) - -(defn image-button - [props animations edit] - [quo/composer-button - {:on-press (if edit - #(js/alert "This feature is temporarily unavailable in edit mode.") - #(open-photo-selector props animations)) - :accessibility-label :open-images-button - :container-style {:margin-right 12} - :icon :i/image}]) - -(defn reaction-button - [] - [quo/composer-button - {:icon :i/reaction - :on-press (fn [] - (rf/dispatch [:chat.ui/set-input-focused false]) - (rn/dismiss-keyboard!) - (js/alert "to be implemented")) - :container-style {:margin-right 12}}]) - -(defn format-button - [] - [quo/composer-button - {:on-press (fn [] - (rf/dispatch [:chat.ui/set-input-focused false]) - (rn/dismiss-keyboard!) - (js/alert "to be implemented")) - :icon :i/format}]) - (defn view - [props state animations window-height {:keys [edit images]}] - (let [send-btn-opacity (reanimated/use-shared-value 0) - audio-btn-opacity (reanimated/interpolate send-btn-opacity [0 1] [1 0])] + [input-ref] + (let [edit (rf/sub [:chats/edit-message])] [rn/view {:style style/actions-container} - [rn/view - {:style {:flex-direction :row - :display (if @(:recording? state) :none :flex)}} - [camera-button edit] - [image-button props animations edit] - (when config/show-not-implemented-features? - [reaction-button]) - (when config/show-not-implemented-features? - [format-button])] - [:f> send-button props state animations window-height images edit send-btn-opacity] - (when (and (not edit) (not images) config/show-not-implemented-features?) - ;; TODO(alwx): needs to be replaced with an `audio-button` later. See - ;; https://github.com/status-im/status-mobile/issues/16084 for more details. - [:f> disabled-audio-button audio-btn-opacity])])) + [rn/view {:style {:flex-direction :row}} + (when-not edit + [:<> + [actions.image/camera-button] + [actions.image/image-button input-ref edit]])] + [send-button input-ref edit]])) diff --git a/src/status_im/contexts/chat/messenger/composer/constants.cljs b/src/status_im/contexts/chat/messenger/composer/constants.cljs index 4195575776..b400331f68 100644 --- a/src/status_im/contexts/chat/messenger/composer/constants.cljs +++ b/src/status_im/contexts/chat/messenger/composer/constants.cljs @@ -13,10 +13,6 @@ (def ^:const line-height (if platform/ios? 18 (:line-height typography/paragraph-1))) -(def ^:const multiline-minimized-height (+ input-height line-height)) - -(def ^:const empty-opacity 0.7) - (def ^:const images-padding-top 12) (def ^:const images-padding-bottom 8) (def ^:const images-container-height @@ -33,16 +29,6 @@ (def ^:const mentions-max-height 240) -(def ^:const extra-content-offset (if platform/ios? 6 -8)) - -(def ^:const content-change-threshold 10) - -(def ^:const drag-threshold 30) - -(def ^:const velocity-threshold (if platform/ios? -1000 -500)) - -(def ^:const background-threshold 0.75) - (def ^:const max-text-size 4096) (def ^:const unfurl-debounce-ms diff --git a/src/status_im/contexts/chat/messenger/composer/edit/view.cljs b/src/status_im/contexts/chat/messenger/composer/edit/view.cljs index 40cb0b6839..bcdfd6aa10 100644 --- a/src/status_im/contexts/chat/messenger/composer/edit/view.cljs +++ b/src/status_im/contexts/chat/messenger/composer/edit/view.cljs @@ -7,12 +7,13 @@ [react-native.reanimated :as reanimated] [status-im.contexts.chat.messenger.composer.constants :as constants] [status-im.contexts.chat.messenger.composer.edit.style :as style] + [status-im.contexts.chat.messenger.composer.effects :as effects] [status-im.contexts.chat.messenger.composer.utils :as utils] [utils.i18n :as i18n] [utils.re-frame :as rf])) (defn edit-message - [{:keys [text-value input-ref input-height]}] + [input-ref] (let [theme (quo.theme/use-theme)] [rn/view {:style style/container @@ -32,18 +33,16 @@ {:size 24 :icon-only? true :accessibility-label :edit-cancel-button - :on-press #(utils/cancel-edit-message text-value input-ref input-height) + :on-press #(utils/cancel-edit-message input-ref) :type :outline} :i/close]])) -(defn- f-view - [props] +(defn view + [input-ref] (let [edit (rf/sub [:chats/edit-message]) height (reanimated/use-shared-value (if edit constants/edit-container-height 0))] + (effects/use-edit input-ref edit) (rn/use-effect #(reanimated/animate height (if edit constants/edit-container-height 0)) [edit]) [reanimated/view {:style (reanimated/apply-animations-to-style {:height height} {})} - (when edit [edit-message props])])) - -(defn view - [props] - [:f> f-view props]) + (when edit + [edit-message input-ref])])) diff --git a/src/status_im/contexts/chat/messenger/composer/effects.cljs b/src/status_im/contexts/chat/messenger/composer/effects.cljs index d7627cf1c1..fb08314c69 100644 --- a/src/status_im/contexts/chat/messenger/composer/effects.cljs +++ b/src/status_im/contexts/chat/messenger/composer/effects.cljs @@ -1,225 +1,20 @@ (ns status-im.contexts.chat.messenger.composer.effects (:require - [clojure.string :as string] - [oops.core :as oops] - [react-native.async-storage :as async-storage] [react-native.core :as rn] - [react-native.platform :as platform] - [react-native.reanimated :as reanimated] - [reagent.core :as reagent] - [status-im.contexts.chat.messenger.composer.constants :as constants] - [status-im.contexts.chat.messenger.composer.keyboard :as kb] - [status-im.contexts.chat.messenger.composer.utils :as utils] - [utils.number] - [utils.re-frame :as rf])) - -(defn reenter-screen-effect - [{:keys [text-value saved-cursor-position maximized?]} - {:keys [content-height]} - {:keys [input-content-height input-text input-maximized?]} - {:keys [height]}] - (let [lines (utils/calc-lines input-content-height) - minimized-height (if (or (= lines 1) (empty? input-text)) - constants/input-height - constants/multiline-minimized-height)] - (when (and (empty? @text-value) (not= input-text nil)) - (reset! text-value input-text) - (reset! content-height input-content-height) - (reset! saved-cursor-position (count input-text)) - (reanimated/set-shared-value height minimized-height)) - (when input-maximized? - (reset! maximized? true)))) - -(defn maximized-effect - [{:keys [maximized?]} - {:keys [height saved-height last-height]} - {:keys [max-height]} - {:keys [input-content-height]}] - (when (or @maximized? (>= input-content-height max-height)) - (reanimated/animate height max-height) - (reanimated/set-shared-value saved-height max-height) - (reanimated/set-shared-value last-height max-height))) - -(defn layout-effect - [{:keys [lock-layout?]}] - (when-not @lock-layout? - (js/setTimeout #(reset! lock-layout? true) 500))) - -(defn kb-default-height-effect - [{:keys [kb-default-height kb-height]}] - (when (zero? @kb-default-height) - (async-storage/get-item :kb-default-height - (fn [height] - (reset! kb-default-height (utils.number/parse-int height 0)) - (reset! kb-height (utils.number/parse-int height 0)))))) - -(defn background-effect - [{:keys [maximized?]} - {:keys [opacity background-y]} - {:keys [max-height]} - {:keys [input-content-height]}] - (when (or @maximized? (>= input-content-height (* max-height constants/background-threshold))) - (reanimated/set-shared-value background-y 0) - (reanimated/animate opacity 1))) - -(defn link-preview-effect - [{:keys [text-value]}] - (let [text @text-value] - (when-not (string/blank? text) - (rf/dispatch [:link-preview/unfurl-urls text])))) - -(defn audio-effect - [{:keys [recording? gesture-enabled?]} - audio] - (when (and audio (not @recording?)) - (reset! recording? true) - (reset! gesture-enabled? false))) - -(defn empty-effect - [{:keys [empty-input?]} subscriptions] - (reanimated/set-shared-value - empty-input? - (utils/empty-input? subscriptions))) - -(defn component-will-unmount - [{:keys [keyboard-show-listener keyboard-hide-listener keyboard-frame-listener]}] - (.remove ^js @keyboard-show-listener) - (.remove ^js @keyboard-hide-listener) - (.remove ^js @keyboard-frame-listener)) - -(defn initialize - [props state animations {:keys [max-height] :as dimensions} - {:keys [chat-input audio input-text images link-previews? reply edit] :as subscriptions}] - (rn/use-effect - (fn [] - (maximized-effect state animations dimensions chat-input) - (layout-effect state) - (kb-default-height-effect state) - (background-effect state animations dimensions chat-input) - (link-preview-effect state) - (audio-effect state audio) - (kb/add-kb-listeners props state animations dimensions) - #(component-will-unmount props)) - [max-height]) - (rn/use-effect - (fn [] - (empty-effect animations subscriptions)) - [input-text images link-previews? reply edit audio]) - (rn/use-mount #(reenter-screen-effect state dimensions subscriptions animations))) + [utils.number])) (defn use-edit - [{:keys [input-ref]} - {:keys [text-value saved-cursor-position cursor-position]} - {:keys [edit input-with-mentions]} - chat-screen-layout-calculations-complete?] - (let [mention? (some #(= :mention (first %)) (seq input-with-mentions))] - (rn/use-effect - (fn [] - (let [mention-text (reduce (fn [acc item] - (str acc (second item))) - "" - input-with-mentions) - edit-text (cond - mention? mention-text - ;; NOTE: using text-value for cases when the user - ;; leaves the app with an unfinished edit and re-opens - ;; the chat. - (and (seq @text-value) - (not (reanimated/get-shared-value - chat-screen-layout-calculations-complete?))) - @text-value - :else (get-in edit [:content :text])) - selection-pos (count edit-text) - inject-edit-text (fn [] - (reset! text-value edit-text) - (reset! cursor-position selection-pos) - (reset! saved-cursor-position selection-pos) - (when @input-ref - (.setNativeProps ^js @input-ref - (clj->js {:text edit-text}))))] - - (when (and edit @input-ref) - ;; NOTE: A small setTimeout is necessary to ensure the focus is enqueued and is executed - ;; ASAP. Check https://github.com/software-mansion/react-native-screens/issues/472 - ;; - ;; The nested setTimeout is necessary to avoid both `on-focus` and - ;; `on-content-size-change` handlers triggering the height animation simultaneously, as - ;; this causes a jump in the - ;; UI. This way, `on-focus` will trigger first without changing the height, after which - ;; `on-content-size-change` will animate the height of the input based on the injected - ;; text. - (js/setTimeout #(do (when (reanimated/get-shared-value - chat-screen-layout-calculations-complete?) - (.focus ^js @input-ref)) - (reagent/next-tick inject-edit-text)) - 600)))) - [(:message-id edit)]))) + [input-ref edit] + (rn/use-effect + (fn [] + (when (and edit @input-ref) + (js/setTimeout #(.focus ^js @input-ref) 600))) + [(:message-id edit)])) (defn use-reply - [{:keys [input-ref]} - {:keys [reply]} - chat-screen-layout-calculations-complete?] + [input-ref reply] (rn/use-effect (fn [] - (when (and reply @input-ref (reanimated/get-shared-value chat-screen-layout-calculations-complete?)) + (when (and reply @input-ref) (js/setTimeout #(.focus ^js @input-ref) 600))) [(:message-id reply)])) - -(defn update-input-mention - [{:keys [input-ref]} - {:keys [text-value]} - {:keys [input-text]}] - (rn/use-effect - (fn [] - (when (and input-text (not= @text-value input-text)) - (when @input-ref - (.setNativeProps ^js @input-ref (clj->js {:text input-text}))) - (reset! text-value input-text) - (rf/dispatch [:mention/on-change-text input-text]))) - [input-text])) - -(defn link-previews - [{:keys [sending-links?]} - {:keys [text-value maximized?]} - {:keys [height saved-height]} - {:keys [link-previews?]}] - (rn/use-effect - (fn [] - (if-not @maximized? - (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))) - (reset! sending-links? link-previews?)) - [link-previews?])) - -(defn use-images - [{:keys [sending-images? input-ref]} - {:keys [text-value maximized?]} - {:keys [height saved-height]} - {:keys [images]}] - (rn/use-effect - (fn [] - (when (and (not @sending-images?) (seq images) @input-ref) - (.focus ^js @input-ref)) - (if-not @maximized? - (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))) - (reset! sending-images? (boolean (seq images)))) - [(boolean (seq images))])) - -(defn did-mount - [{:keys [selectable-input-ref input-ref selection-manager]}] - (rn/use-mount - (fn [] - (when platform/android? - (let [selectable-text-input-handle (rn/find-node-handle @selectable-input-ref) - text-input-handle (rn/find-node-handle @input-ref)] - (oops/ocall selection-manager - :setupMenuItems - selectable-text-input-handle - text-input-handle)))))) diff --git a/src/status_im/contexts/chat/messenger/composer/events.cljs b/src/status_im/contexts/chat/messenger/composer/events.cljs index 164dd981ed..236701cc29 100644 --- a/src/status_im/contexts/chat/messenger/composer/events.cljs +++ b/src/status_im/contexts/chat/messenger/composer/events.cljs @@ -21,6 +21,12 @@ (when (empty? new-input) (mentions/clear-mentions))))) +(rf/defn set-chat-input-ref + {:events [:chat/set-input-ref]} + [{:keys [db]} input-ref] + (let [current-chat-id (:current-chat-id db)] + {:db (assoc-in db [:chat/inputs current-chat-id :input-ref] input-ref)})) + (rf/defn set-input-content-height {:events [:chat.ui/set-input-content-height]} [{db :db} content-height chat-id] @@ -69,15 +75,14 @@ [{:keys [db]} message] (let [current-chat-id (:current-chat-id db) text (get-in message [:content :text])] - {:db (-> db - (assoc-in [:chat/inputs current-chat-id :metadata :editing-message] - message) - (assoc-in [:chat/inputs current-chat-id :metadata :responding-to-message] nil) - (update-in [:chat/inputs current-chat-id :metadata] - dissoc - :sending-image)) - :dispatch-n [[:chat.ui/set-chat-input-text nil current-chat-id] - [:mention/to-input-field text current-chat-id]]})) + {:db (-> db + (assoc-in [:chat/inputs current-chat-id :metadata :editing-message] + message) + (assoc-in [:chat/inputs current-chat-id :metadata :responding-to-message] nil) + (update-in [:chat/inputs current-chat-id :metadata] + dissoc + :sending-image)) + :dispatch [:mention/to-input-field text current-chat-id]})) (rf/defn cancel-message-reply "Cancels stage message reply" @@ -260,3 +265,8 @@ (if editing-message (send-edited-message cofx input-text editing-message) (send-messages cofx input-text current-chat-id)))) + +(rf/reg-fx :effects/set-input-text-value + (fn [[input-ref text-value]] + (when (and (not (string/blank? text-value)) input-ref) + (.setNativeProps ^js input-ref (clj->js {:text (emoji/text->emoji text-value)}))))) diff --git a/src/status_im/contexts/chat/messenger/composer/gesture.cljs b/src/status_im/contexts/chat/messenger/composer/gesture.cljs deleted file mode 100644 index d31462f216..0000000000 --- a/src/status_im/contexts/chat/messenger/composer/gesture.cljs +++ /dev/null @@ -1,112 +0,0 @@ -(ns status-im.contexts.chat.messenger.composer.gesture - (:require - [oops.core :as oops] - [react-native.gesture :as gesture] - [react-native.reanimated :as reanimated] - [status-im.contexts.chat.messenger.composer.constants :as constants] - [status-im.contexts.chat.messenger.composer.utils :as utils] - [utils.number] - [utils.re-frame :as rf])) - -(defn set-opacity - [velocity opacity translation expanding? min-height max-height new-height saved-height] - (let [remaining-height (if expanding? - (- max-height (reanimated/get-shared-value saved-height)) - (- (reanimated/get-shared-value saved-height) min-height)) - progress (if (= new-height min-height) 1 (/ translation remaining-height)) - currently-expanding? (neg? velocity) - max-opacity? (and currently-expanding? (= (reanimated/get-shared-value opacity) 1)) - min-opacity? (and (not currently-expanding?) - (= (reanimated/get-shared-value opacity) 0))] - (if (>= translation 0) - (when (and (not expanding?) (not min-opacity?)) - (reanimated/set-shared-value opacity (- 1 progress))) - (when (and expanding? (not max-opacity?)) - (reanimated/set-shared-value opacity (Math/abs progress)))))) - -(defn maximize - [{:keys [maximized?]} - {:keys [height saved-height background-y opacity]} - {:keys [max-height]}] - (reanimated/animate height max-height) - (reanimated/set-shared-value saved-height max-height) - (reanimated/set-shared-value background-y 0) - (reanimated/animate opacity 1) - (js/setTimeout (fn [] - (reset! maximized? true) - (rf/dispatch [:chat.ui/set-input-maximized true])) - 300)) - -(defn minimize - [{:keys [input-ref emoji-kb-extra-height saved-emoji-kb-extra-height]} - {:keys [maximized?]}] - (when @emoji-kb-extra-height - (reset! saved-emoji-kb-extra-height @emoji-kb-extra-height) - (reset! emoji-kb-extra-height nil)) - (reset! maximized? false) - (rf/dispatch [:chat.ui/set-input-maximized false]) - (utils/blur-input input-ref)) - -(defn bounce-back - [{:keys [height saved-height opacity background-y]} - {:keys [window-height]} - starting-opacity] - (reanimated/animate height (reanimated/get-shared-value saved-height)) - (when (zero? starting-opacity) - (reanimated/animate opacity 0) - (reanimated/animate-delay background-y (- window-height) 300))) - -(defn drag-gesture - [{:keys [input-ref] :as props} - {:keys [gesture-enabled?] :as state} - {:keys [height saved-height last-height opacity background-y] :as animations} - {:keys [max-height lines] :as dimensions} - keyboard-shown] - (let [expanding? (atom true) - starting-opacity (reanimated/get-shared-value opacity)] - (-> (gesture/gesture-pan) - (gesture/enabled @gesture-enabled?) - (gesture/on-start (fn [event] - (if-not keyboard-shown - (do ; focus and end - (when (< (oops/oget event "velocityY") constants/velocity-threshold) - (reanimated/set-shared-value last-height max-height) - (maximize state animations dimensions)) - (when @input-ref - (.focus ^js @input-ref)) - (reset! gesture-enabled? false)) - (do ; else, will start gesture - (reanimated/set-shared-value background-y 0) - (reset! expanding? (neg? (oops/oget event "velocityY"))))))) - (gesture/on-update - (fn [event] - (let [translation (oops/oget event "translationY") - 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 - (if (>= new-height min-height) - (do ; expand sheet - (reanimated/set-shared-value height bounded-height) - (set-opacity (oops/oget event "velocityY") - opacity - translation - @expanding? - min-height - max-height - bounded-height - saved-height)) - ; sheet at min-height, collapse keyboard - (utils/blur-input input-ref)))))) - (gesture/on-end (fn [] - (let [diff (- (reanimated/get-shared-value height) - (reanimated/get-shared-value saved-height))] - (if @gesture-enabled? - (if (and @expanding? (>= diff 0)) - (if (> diff constants/drag-threshold) - (maximize state animations dimensions) - (bounce-back animations dimensions starting-opacity)) - (if (> (Math/abs diff) constants/drag-threshold) - (minimize props state) - (bounce-back animations dimensions starting-opacity))) - (reset! gesture-enabled? true)))))))) diff --git a/src/status_im/contexts/chat/messenger/composer/handlers.cljs b/src/status_im/contexts/chat/messenger/composer/handlers.cljs index 1bd54a911c..22a8dad1ca 100644 --- a/src/status_im/contexts/chat/messenger/composer/handlers.cljs +++ b/src/status_im/contexts/chat/messenger/composer/handlers.cljs @@ -3,139 +3,17 @@ [clojure.string :as string] [oops.core :as oops] [react-native.core :as rn] - [react-native.reanimated :as reanimated] - [reagent.core :as reagent] [status-im.contexts.chat.messenger.composer.constants :as constants] - [status-im.contexts.chat.messenger.composer.keyboard :as kb] [status-im.contexts.chat.messenger.composer.selection :as selection] - [status-im.contexts.chat.messenger.composer.utils :as utils] [utils.debounce :as debounce] [utils.number] [utils.re-frame :as rf])) -(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 maximized?]} - {:keys [height saved-height last-height opacity background-y composer-focused?] - :as animations} - {: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) - new-height (min max-height last-height-value)] - (reanimated/animate height new-height) - (reanimated/set-shared-value saved-height new-height) - (when (> last-height-value (* constants/background-threshold max-height)) - (reset! maximized? true) - (reanimated/animate opacity 1) - (reanimated/set-shared-value background-y 0))) - - (js/setTimeout #(reset! lock-selection? false) 300) - (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)) - -(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?]} - {:keys [height saved-height last-height gradient-opacity opacity background-y composer-focused?]} - {:keys [content-height max-height window-height]}] - (when-not @recording? - (let [lines (utils/calc-lines (- @content-height constants/extra-content-offset)) - min-height (utils/get-min-height lines) - reopen-height (utils/calc-reopen-height text-value - min-height - 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) - (reanimated/animate height min-height) - (reanimated/set-shared-value saved-height min-height) - (reanimated/animate opacity 0) - (js/setTimeout #(reanimated/set-shared-value background-y (- window-height)) 300) - (reanimated/animate gradient-opacity 0) - (reset! lock-selection? true) - (reset! saved-cursor-position @cursor-position) - (reset! gradient-z-index (if (= (reanimated/get-shared-value gradient-opacity) 1) -1 0)) - (when (not= reopen-height max-height) - (reset! maximized? false) - (rf/dispatch [:chat.ui/set-input-maximized false]))))) - -(defn content-size-change - "Save new text height, expand composer if possible, show background overlay if needed" - [event - {:keys [maximized? lock-layout? text-value]} - {:keys [height saved-height last-height opacity background-y]} - {: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) - new-height (min new-height max-height)] - (reset! content-height content-size) - (when (utils/update-height? content-size height max-height) - (reanimated/animate height new-height) - (reanimated/set-shared-value last-height new-height) - (reanimated/set-shared-value saved-height new-height)) - (when (= new-height max-height) - (reset! maximized? true) - (rf/dispatch [:chat.ui/set-input-maximized true])) - (if (utils/show-background? max-height new-height maximized?) - (do - (reanimated/set-shared-value background-y 0) - (reanimated/animate opacity 1)) - (when (= (reanimated/get-shared-value opacity) 1) - (reanimated/animate opacity 0) - (js/setTimeout #(reanimated/set-shared-value background-y (- window-height)) 300))) - (rf/dispatch [:chat.ui/set-input-content-height content-size]) - (reset! lock-layout? (> lines 2))))) - -(defn scroll - "Hide or show top gradient while scroll" - [event - {:keys [scroll-y]} - {:keys [gradient-z-index focused?]} - {:keys [gradient-opacity]} - {:keys [lines max-lines]}] - (let [y (oops/oget event "nativeEvent.contentOffset.y")] - (reset! scroll-y y) - (when (utils/show-top-gradient? y lines max-lines gradient-opacity focused?) - (reset! gradient-z-index 1) - (js/setTimeout #(reanimated/animate gradient-opacity 1) 0)) - (when (utils/hide-top-gradient? y gradient-opacity) - (reanimated/animate gradient-opacity 0) - (js/setTimeout #(reset! gradient-z-index 0) 300)))) - (defn change-text "Update `text-value`, update cursor selection, find links, find mentions" - [text - {:keys [input-ref record-reset-fn]} - {:keys [text-value cursor-position recording?]}] - (reset! text-value text) - (reagent/next-tick #(when @input-ref - (.setNativeProps ^js @input-ref - (clj->js {:selection {:start @cursor-position - :end @cursor-position}})))) - (when @recording? - (@record-reset-fn) - (reset! recording? false)) + [text] (rf/dispatch [:chat.ui/set-chat-input-text text]) - (debounce/debounce-and-dispatch [:link-preview/unfurl-urls text] - constants/unfurl-debounce-ms) + (debounce/debounce-and-dispatch [:link-preview/unfurl-urls text] constants/unfurl-debounce-ms) (if (string/ends-with? text "@") (rf/dispatch [:mention/on-change-text text]) (debounce/debounce-and-dispatch [:mention/on-change-text text] 300))) diff --git a/src/status_im/contexts/chat/messenger/composer/images/view.cljs b/src/status_im/contexts/chat/messenger/composer/images/view.cljs index 201a8dcfc3..73e4817351 100644 --- a/src/status_im/contexts/chat/messenger/composer/images/view.cljs +++ b/src/status_im/contexts/chat/messenger/composer/images/view.cljs @@ -26,7 +26,7 @@ [rn/view {:style style/remove-photo-inner-container} [quo/icon :i/clear {:size 20 :color colors/neutral-50 :color-2 colors/white}]]]]) -(defn f-images-list +(defn images-list [] (let [theme (quo.theme/use-theme) images (rf/sub [:chats/sending-image]) @@ -48,7 +48,3 @@ :horizontal true :shows-horizontal-scroll-indicator false :keyboard-should-persist-taps :handled}]])) - -(defn images-list - [] - [:f> f-images-list]) diff --git a/src/status_im/contexts/chat/messenger/composer/keyboard.cljs b/src/status_im/contexts/chat/messenger/composer/keyboard.cljs deleted file mode 100644 index 6b412226e2..0000000000 --- a/src/status_im/contexts/chat/messenger/composer/keyboard.cljs +++ /dev/null @@ -1,87 +0,0 @@ -(ns status-im.contexts.chat.messenger.composer.keyboard - (:require - [oops.core :as oops] - [react-native.async-storage :as async-storage] - [react-native.core :as rn] - [react-native.platform :as platform] - [react-native.reanimated :as reanimated] - [status-im.contexts.chat.messenger.composer.utils :as utils])) - -(defn get-kb-height - [curr-height default-height] - (if (and default-height (< curr-height default-height)) - default-height - curr-height)) - -(defn store-kb-height - [event {:keys [kb-default-height kb-height]}] - (let [height (- (:height (rn/get-window)) - (oops/oget event "endCoordinates.screenY"))] - (reset! kb-height height) - (when (zero? @kb-default-height) - (async-storage/set-item! :kb-default-height (str height))))) - -(defn handle-emoji-kb-ios - "Opening emoji KB on iOS will cause a flicker up and down due to height differences. - This method handles that by adding the extra difference between the keyboards. When the input is - expanded to a point where the added difference will make the composer go beyond the screen causing a flicker, - we're subtracting the difference so it only reaches the allowed max-height. We're not animating these - changes to make it appear seamless during transitions between keyboard types when maximized." - [event - {:keys [emoji-kb-extra-height]} - {:keys [text-value kb-height]} - {:keys [height saved-height]} - {:keys [max-height]}] - (let [start-h (oops/oget event "startCoordinates.height") - end-h (oops/oget event "endCoordinates.height") - diff (- end-h start-h) - max-height-diff (- max-height diff) - curr-text @text-value - bigger-than-default-kb? (> end-h @kb-height) - almost-expanded? (> (reanimated/get-shared-value height) max-height-diff)] - (if (and almost-expanded? bigger-than-default-kb? (pos? diff)) - (do - (reanimated/set-shared-value height (- (reanimated/get-shared-value height) diff)) - (reanimated/set-shared-value saved-height (- (reanimated/get-shared-value saved-height) diff)) - (reset! text-value (str @text-value " ")) - (js/setTimeout #(reset! text-value curr-text) 0) - (reset! emoji-kb-extra-height diff)) - (when @emoji-kb-extra-height - (reanimated/set-shared-value height - (+ (reanimated/get-shared-value height) @emoji-kb-extra-height)) - (reanimated/set-shared-value saved-height - (+ (reanimated/get-shared-value saved-height) - @emoji-kb-extra-height)) - (reset! emoji-kb-extra-height nil))))) - -(defn add-kb-listeners - [{:keys [keyboard-show-listener keyboard-frame-listener keyboard-hide-listener input-ref] :as props} - state animations dimensions] - (reset! keyboard-show-listener (.addListener - rn/keyboard - "keyboardDidShow" - #(store-kb-height % state))) - (reset! keyboard-frame-listener (.addListener - rn/keyboard - "keyboardWillChangeFrame" - #(handle-emoji-kb-ios % props state animations dimensions))) - (reset! keyboard-hide-listener (.addListener rn/keyboard - "keyboardDidHide" - #(when platform/android? - (utils/blur-input input-ref))))) - -(defn handle-refocus-emoji-kb-ios - [{:keys [saved-emoji-kb-extra-height]} - {:keys [height saved-height last-height]} - {:keys [lines max-lines]}] - (when @saved-emoji-kb-extra-height - (js/setTimeout (fn [] - (when (> lines max-lines) - (reanimated/animate height - (+ (reanimated/get-shared-value last-height) - @saved-emoji-kb-extra-height)) - (reanimated/set-shared-value saved-height - (+ (reanimated/get-shared-value last-height) - @saved-emoji-kb-extra-height))) - (reset! saved-emoji-kb-extra-height nil)) - 600))) diff --git a/src/status_im/contexts/chat/messenger/composer/link_preview/view.cljs b/src/status_im/contexts/chat/messenger/composer/link_preview/view.cljs index f2cce2370a..71945d8589 100644 --- a/src/status_im/contexts/chat/messenger/composer/link_preview/view.cljs +++ b/src/status_im/contexts/chat/messenger/composer/link_preview/view.cljs @@ -21,7 +21,7 @@ [previews?]) height)) -(defn f-view +(defn view [] (let [previews (rf/sub [:chats/link-previews-unfurled]) height (use-animated-height (boolean (seq previews)))] @@ -48,7 +48,3 @@ :thumbnail (:data-uri thumbnail) :url url}) previews)}]])) - -(defn view - [] - [:f> f-view]) diff --git a/src/status_im/contexts/chat/messenger/composer/mentions/style.cljs b/src/status_im/contexts/chat/messenger/composer/mentions/style.cljs index 65cc12ff27..d38fb20b1a 100644 --- a/src/status_im/contexts/chat/messenger/composer/mentions/style.cljs +++ b/src/status_im/contexts/chat/messenger/composer/mentions/style.cljs @@ -5,7 +5,6 @@ [react-native.reanimated :as reanimated] [status-im.contexts.chat.messenger.composer.constants :as constants])) - (defn shadow [theme] (if platform/ios? @@ -16,12 +15,12 @@ {:elevation 10})) (defn container - [opacity bottom theme] + [opacity top theme] (reanimated/apply-animations-to-style {:opacity opacity} (merge {:position :absolute - :bottom bottom + :top (- (+ 8 top)) :left 8 :right 8 :border-radius 16 diff --git a/src/status_im/contexts/chat/messenger/composer/mentions/view.cljs b/src/status_im/contexts/chat/messenger/composer/mentions/view.cljs index 4e12d88be1..22c6e23ea6 100644 --- a/src/status_im/contexts/chat/messenger/composer/mentions/view.cljs +++ b/src/status_im/contexts/chat/messenger/composer/mentions/view.cljs @@ -2,70 +2,45 @@ (:require [quo.theme] [react-native.core :as rn] - [react-native.platform :as platform] [react-native.reanimated :as reanimated] [react-native.safe-area :as safe-area] - [reagent.core :as reagent] [status-im.common.contact-list-item.view :as contact-list-item] + [status-im.contexts.chat.messenger.composer.constants :as constants] [status-im.contexts.chat.messenger.composer.mentions.style :as style] - [status-im.contexts.chat.messenger.composer.utils :as utils] + [status-im.contexts.chat.messenger.messages.constants :as messages.constants] [utils.re-frame :as rf])) -(defn update-cursor - [user {:keys [cursor-position input-ref]}] - (when platform/android? - (let [new-cursor-pos (+ (count (:primary-name user)) @cursor-position 1)] - (reset! cursor-position new-cursor-pos) - (reagent/next-tick #(when @input-ref - (.setNativeProps ^js @input-ref - (clj->js {:selection {:start new-cursor-pos - :end - new-cursor-pos}}))))))) - (defn mention-item - [user _ _ render-data] + [user] [contact-list-item/contact-list-item - {:on-press (fn [] - (rf/dispatch [:chat.ui/select-mention user]) - (update-cursor user render-data))} + {:on-press #(rf/dispatch [:chat.ui/select-mention user])} user]) -(defn- f-view - [suggestions-atom props state animations max-height cursor-pos images link-previews? reply edit] - (let [suggestions (rf/sub [:chat/mention-suggestions]) - theme (quo.theme/use-theme) - 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?)] +(defn view + [{:keys [layout-height]}] + (let [suggestions (rf/sub [:chat/mention-suggestions]) + suggestions? (seq suggestions) + theme (quo.theme/use-theme) + opacity (reanimated/use-shared-value (if suggestions? 1 0)) + [suggestions-state set-suggestions-state] (rn/use-state suggestions) + top (min constants/mentions-max-height + (* (count suggestions-state) 56) + (- @layout-height + (+ (safe-area/get-top) + messages.constants/top-bar-height + 5)))] (rn/use-effect (fn [] - (if (seq suggestions) - (reset! suggestions-atom suggestions) - (js/setTimeout #(reset! suggestions-atom suggestions) 300)) - (reanimated/animate opacity (if (seq suggestions) 1 0))) - [(seq suggestions)]) + (if suggestions? + (set-suggestions-state suggestions) + (js/setTimeout #(set-suggestions-state suggestions) 300)) + (reanimated/animate opacity (if suggestions? 1 0))) + [suggestions?]) [reanimated/view - {:style (style/container opacity mentions-pos theme)} + {:style (style/container opacity top theme)} [rn/flat-list {:keyboard-should-persist-taps :always - :data (vals @suggestions-atom) + :data (vals suggestions-state) :key-fn :key :render-fn mention-item - :render-data {:cursor-position (:cursor-position state) - :input-ref (:input-ref props)} :accessibility-label :mentions-list}]])) - -(defn view - [props state animations max-height cursor-pos images link-previews? reply edit] - (let [suggestions-atom (reagent/atom {})] - [:f> f-view suggestions-atom props state animations max-height cursor-pos images link-previews? reply - edit])) diff --git a/src/status_im/contexts/chat/messenger/composer/reply/view.cljs b/src/status_im/contexts/chat/messenger/composer/reply/view.cljs index c6d49e0e35..fb5b7bcad2 100644 --- a/src/status_im/contexts/chat/messenger/composer/reply/view.cljs +++ b/src/status_im/contexts/chat/messenger/composer/reply/view.cljs @@ -9,6 +9,7 @@ [react-native.reanimated :as reanimated] [status-im.constants :as constant] [status-im.contexts.chat.messenger.composer.constants :as constants] + [status-im.contexts.chat.messenger.composer.effects :as effects] [status-im.contexts.chat.messenger.composer.reply.style :as style] [status-im.contexts.chat.messenger.composer.utils :as utils] [utils.ens.stateofus :as stateofus] @@ -164,14 +165,12 @@ :end {:x 0.7 :y 0} :style style/gradient}])])) -(defn- f-view - [recording? input-ref] +(defn view + [input-ref] (let [reply (rf/sub [:chats/reply-message]) height (reanimated/use-shared-value (if reply constants/reply-container-height 0))] + (effects/use-reply input-ref reply) (rn/use-effect #(reanimated/animate height (if reply constants/reply-container-height 0)) [reply]) [reanimated/view {:style (reanimated/apply-animations-to-style {:height height} {})} - (when reply [quoted-message reply true false recording? input-ref])])) - -(defn view - [{:keys [recording?]} input-ref] - [:f> f-view @recording? input-ref]) + (when reply + [quoted-message reply true false false input-ref])])) diff --git a/src/status_im/contexts/chat/messenger/composer/style.cljs b/src/status_im/contexts/chat/messenger/composer/style.cljs index 464d159131..498233333a 100644 --- a/src/status_im/contexts/chat/messenger/composer/style.cljs +++ b/src/status_im/contexts/chat/messenger/composer/style.cljs @@ -2,7 +2,6 @@ (:require [quo.foundations.colors :as colors] [quo.foundations.typography :as typography] - [react-native.platform :as platform] [react-native.reanimated :as reanimated] [status-im.contexts.chat.messenger.composer.constants :as constants] [status-im.contexts.shell.jump-to.constants :as shell.constants] @@ -10,34 +9,6 @@ (def border-top-radius 20) -(defn shadow - [theme] - (when platform/ios? - {:shadow-radius 20 - :shadow-opacity (colors/theme-colors 0.1 0.7 theme) - :shadow-color colors/neutral-100 - :shadow-offset {:width 0 :height (colors/theme-colors -4 -8 theme)}})) - -(def composer-sheet-and-jump-to-container - {:position :absolute - :bottom 0 - :left 0 - :right 0}) - -(defn sheet-container - [insets {:keys [container-opacity composer-elevation]} theme] - (reanimated/apply-animations-to-style - {:opacity container-opacity - :elevation composer-elevation} - (merge - {: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 - :padding-bottom (:bottom insets)} - (shadow theme)))) - (def bar-container {:height constants/bar-container-height :left 0 @@ -54,65 +25,14 @@ :border-radius 100 :background-color (colors/theme-colors colors/neutral-100-opa-5 colors/white-opa-10 theme)}) -(defn input-container - [height max-height] - (reanimated/apply-animations-to-style - {:height height} - {:max-height max-height - :z-index 1})) - -(defn input-view - [{:keys [recording?]}] - {:overflow :hidden - :z-index 1 - :flex 1 - :display (if @recording? :none :flex) - :min-height constants/input-height}) - (defn input-text - [{:keys [saved-emoji-kb-extra-height]} - {:keys [focused? maximized?]} - {:keys [max-height theme]}] + [theme] (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 - {:opacity opacity - :transform [{:translate-y background-y}]} - {:position :absolute - :left 0 - :right 0 - :bottom 0 - :height window-height - :background-color colors/neutral-95-opa-70})) - -(defn blur-container - [composer-default-height {:keys [blur-container-elevation]}] - [{:elevation blur-container-elevation} - {:position :absolute - :left 0 - :right 0 - :bottom 0 - :height composer-default-height - :border-top-right-radius border-top-radius - :border-top-left-radius border-top-radius - :overflow :hidden}]) - -(defn blur-view - [theme] - {:style {:flex 1} - :blur-radius (if platform/ios? 20 10) - :blur-type theme - :blur-amount 20}) + :max-height 150)) (defn shell-button [translate-y opacity] diff --git a/src/status_im/contexts/chat/messenger/composer/sub_view.cljs b/src/status_im/contexts/chat/messenger/composer/sub_view.cljs deleted file mode 100644 index f6049d7297..0000000000 --- a/src/status_im/contexts/chat/messenger/composer/sub_view.cljs +++ /dev/null @@ -1,48 +0,0 @@ -(ns status-im.contexts.chat.messenger.composer.sub-view - (:require - [quo.core :as quo] - [react-native.core :as rn] - [react-native.reanimated :as reanimated] - [status-im.contexts.chat.messenger.composer.style :as style] - [status-im.feature-flags :as ff] - [utils.i18n :as i18n] - [utils.re-frame :as rf] - [utils.worklets.chat.messenger.composer :as worklets])) - -(defn bar - [theme] - [rn/view {:style style/bar-container} - [rn/view {:style (style/bar theme)}]]) - -(defn- f-shell-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?)] - [rn/view {:style (style/shell-button-container)} - (when (ff/enabled? ::ff/shell.jump-to) - [reanimated/view - {:style (style/shell-button jump-to-button-position jump-to-button-opacity)} - [quo/floating-shell-button - {: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}}} - {}]]) - [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 - [shared-values chat-list-scroll-y window-height] - [:f> f-shell-button shared-values 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 734ed72e93..2a019afb13 100644 --- a/src/status_im/contexts/chat/messenger/composer/utils.cljs +++ b/src/status_im/contexts/chat/messenger/composer/utils.cljs @@ -1,52 +1,8 @@ (ns status-im.contexts.chat.messenger.composer.utils (:require - [clojure.string :as string] - [react-native.core :as rn] - [react-native.platform :as platform] - [react-native.reanimated :as reanimated] - [reagent.core :as reagent] [status-im.contexts.chat.messenger.composer.constants :as constants] - [status-im.contexts.chat.messenger.composer.selection :as selection] [utils.number] - [utils.re-frame :as rf] - [utils.worklets.chat.messenger.composer :as worklets])) - -(defn bounded-val - [v min-v max-v] - (max min-v (min v max-v))) - -(defn update-height? - [content-size height max-height] - (let [diff (Math/abs (- content-size (reanimated/get-shared-value height)))] - (and (not= (reanimated/get-shared-value height) max-height) - (> diff constants/content-change-threshold)))) - -(defn show-top-gradient? - [y lines max-lines gradient-opacity focused?] - (and - (> y constants/line-height) - (>= lines max-lines) - (= (reanimated/get-shared-value gradient-opacity) 0) - @focused?)) - -(defn hide-top-gradient? - [y gradient-opacity] - (and - (<= y constants/line-height) - (= (reanimated/get-shared-value gradient-opacity) 1))) - -(defn show-bottom-gradient? - [{:keys [text-value focused?]} {:keys [lines]}] - (and (not-empty @text-value) (not @focused?) (> lines 2))) - -(defn show-background? - [max-height new-height maximized?] - (or @maximized? - (> new-height (* constants/background-threshold max-height)))) - -(defn calc-lines - [height] - (Math/floor (/ height constants/line-height))) + [utils.re-frame :as rf])) (defn calc-top-content-height [reply? edit?] @@ -60,35 +16,6 @@ (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] - (if (empty? @text-value) - min-height - (let [input-height (min @content-height - (reanimated/get-shared-value saved-height))] - (min max-height input-height)))) - -(defn get-min-height - [lines] - (if (> lines 1) - constants/multiline-minimized-height - constants/input-height)) - -(defn calc-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? - [{:keys [input-text images link-previews? reply audio edit]}] - (not (or (not-empty input-text) images link-previews? reply audio edit))) - (defn blur-input [input-ref] (when @input-ref @@ -101,36 +28,17 @@ (rf/dispatch [:chat.ui/cancel-message-reply])) (defn cancel-edit-message - [text-value input-ref input-height] - (reset! text-value "") + [input-ref] ;; NOTE: adding a timeout to assure the input is blurred on the next tick ;; after the `text-value` was cleared. Otherwise the height will be calculated ;; with the old `text-value`, leading to wrong composer height after blur. (js/setTimeout (fn [] - (blur-input input-ref) - (reanimated/set-shared-value input-height constants/input-height)) + (blur-input input-ref)) 100) (.setNativeProps ^js @input-ref (clj->js {:text ""})) - (rf/dispatch [:chat.ui/set-input-content-height constants/input-height]) (rf/dispatch [:chat.ui/cancel-message-edit])) -(defn count-lines - [s] - (-> s - (string/split #"\n" -1) - (butlast) - count)) - -(defn cursor-y-position-relative-to-container - [{:keys [scroll-y]} - {:keys [cursor-position text-value]}] - (let [sub-text (subs @text-value 0 @cursor-position) - sub-text-lines (count-lines sub-text) - scrolled-lines (Math/round (/ @scroll-y constants/line-height)) - sub-text-lines-in-view (- sub-text-lines scrolled-lines)] - (* sub-text-lines-in-view constants/line-height))) - (defn calc-suggestions-position [cursor-pos max-height size {:keys [maximized?]} @@ -153,85 +61,3 @@ (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-non-reactive-state - [] - {:input-ref (atom nil) - :selectable-input-ref (atom nil) - :keyboard-show-listener (atom nil) - :keyboard-frame-listener (atom nil) - :keyboard-hide-listener (atom nil) - :emoji-kb-extra-height (atom nil) - :saved-emoji-kb-extra-height (atom nil) - :sending-images? (atom false) - :sending-links? (atom false) - :record-reset-fn (atom nil) - :scroll-y (atom 0) - :selection-event (atom nil) - :selection-manager (rn/selectable-text-input-manager)}) - -(defn init-reactive-state - [] - {:text-value (reagent/atom "") - :cursor-position (reagent/atom 0) - :saved-cursor-position (reagent/atom 0) - :gradient-z-index (reagent/atom 0) - :kb-default-height (reagent/atom 0) - :kb-height (reagent/atom 0) - :gesture-enabled? (reagent/atom true) - :lock-selection? (reagent/atom true) - :focused? (reagent/atom false) - :lock-layout? (reagent/atom false) - :maximized? (reagent/atom false) - :record-permission? (reagent/atom true) - :recording? (reagent/atom false) - :first-level? (reagent/atom true) - :menu-items (reagent/atom selection/first-level-menu-items)}) - -(defn init-subs - [] - (let [chat-input (rf/sub [:chats/current-chat-input])] - {:images (seq (rf/sub [:chats/sending-image])) - :link-previews? (or (rf/sub [:chats/link-previews?]) - (rf/sub [:chats/status-link-previews?])) - :audio (rf/sub [:chats/sending-audio]) - :reply (rf/sub [:chats/reply-message]) - :edit (rf/sub [:chats/edit-message]) - :input-with-mentions (rf/sub [:chat/input-with-mentions]) - :input-text (:input-text chat-input) - :alert-banners-top-margin (rf/sub [:alert-banners/top-margin]) - :input-content-height (:input-content-height chat-input)})) - -(defn init-shared-values - [] - (let [composer-focused? (reanimated/use-shared-value false) - empty-input-shared-value? (reanimated/use-shared-value true)] - {:composer-focused? composer-focused? - :empty-input? empty-input-shared-value? - :container-opacity (worklets/composer-container-opacity composer-focused? - empty-input-shared-value? - constants/empty-opacity) - :blur-container-elevation (worklets/blur-container-elevation composer-focused? - empty-input-shared-value?) - :composer-elevation (worklets/composer-elevation composer-focused? - empty-input-shared-value?)})) - -(defn init-animations - [lines content-height max-height opacity background-y shared-values] - (let [initial-height (if (> lines 1) - constants/multiline-minimized-height - constants/input-height) - bottom-content-height 0] - (assoc shared-values - :gradient-opacity (reanimated/use-shared-value 0) - :height (reanimated/use-shared-value - initial-height) - :saved-height (reanimated/use-shared-value - initial-height) - :last-height (reanimated/use-shared-value - (utils.number/value-in-range - (+ @content-height bottom-content-height) - constants/input-height - max-height)) - :opacity opacity - :background-y background-y))) diff --git a/src/status_im/contexts/chat/messenger/composer/view.cljs b/src/status_im/contexts/chat/messenger/composer/view.cljs index 9103e14f1d..bf5abc032f 100644 --- a/src/status_im/contexts/chat/messenger/composer/view.cljs +++ b/src/status_im/contexts/chat/messenger/composer/view.cljs @@ -1,183 +1,56 @@ (ns status-im.contexts.chat.messenger.composer.view (:require - [clojure.string :as string] [quo.core :as quo] [quo.foundations.colors :as colors] [quo.theme :as quo.theme] [react-native.core :as rn] - [react-native.gesture :as gesture] - [react-native.hooks :as hooks] - [react-native.platform :as platform] - [react-native.reanimated :as reanimated] - [reagent.core :as reagent] + [react-native.safe-area :as safe-area] [status-im.contexts.chat.messenger.composer.actions.view :as actions] [status-im.contexts.chat.messenger.composer.constants :as constants] [status-im.contexts.chat.messenger.composer.edit.view :as edit] - [status-im.contexts.chat.messenger.composer.effects :as effects] - [status-im.contexts.chat.messenger.composer.gesture :as drag-gesture] - [status-im.contexts.chat.messenger.composer.gradients.view :as gradients] [status-im.contexts.chat.messenger.composer.handlers :as handler] [status-im.contexts.chat.messenger.composer.images.view :as images] [status-im.contexts.chat.messenger.composer.link-preview.view :as link-preview] [status-im.contexts.chat.messenger.composer.mentions.view :as mentions] [status-im.contexts.chat.messenger.composer.reply.view :as reply] - [status-im.contexts.chat.messenger.composer.selection :as selection] [status-im.contexts.chat.messenger.composer.style :as style] - [status-im.contexts.chat.messenger.composer.sub-view :as sub-view] - [status-im.contexts.chat.messenger.composer.utils :as utils] - [status-im.contexts.chat.messenger.messages.contact-requests.bottom-drawer.view :as - contact-requests.bottom-drawer] [utils.i18n :as i18n] [utils.re-frame :as rf])) -(defn sheet-component - [{:keys [insets - chat-list-scroll-y - chat-screen-layout-calculations-complete? - opacity - background-y - theme - window-height]} props state shared-values] - (let [subscriptions (utils/init-subs) - top-margin (if (pos? (:alert-banners-top-margin subscriptions)) - ;; top margin increased to avoid composer overlapping with the - ;; alert banner - (+ (:alert-banners-top-margin subscriptions) 12) - 0) - window-height (- window-height top-margin) - content-height (reagent/atom (or (:input-content-height ; Actual text height - subscriptions) - constants/input-height)) - {:keys [keyboard-shown]} (hooks/use-keyboard) - max-height (utils/calc-max-height subscriptions ; Max allowed height for the - ; composer view - window-height - @(:kb-height state) - insets) - lines (utils/calc-lines (- @content-height constants/extra-content-offset)) ; Current - ; lines - ; count - ;; Maximum number of lines that can be displayed when composer in maximized - max-lines (utils/calc-lines max-height) - animations (utils/init-animations - lines - content-height - max-height - opacity - background-y - shared-values) - dimensions {:content-height content-height - :max-height max-height - :window-height window-height - :lines lines - :max-lines max-lines} - show-bottom-gradient? (utils/show-bottom-gradient? state dimensions) - ;; Cursor position, needed to determine where to display the mentions view - cursor-pos (utils/cursor-y-position-relative-to-container - props - state)] - (effects/did-mount props) - (effects/initialize props - state - animations - dimensions - subscriptions) - (effects/use-edit props state subscriptions chat-screen-layout-calculations-complete?) - (effects/use-reply props subscriptions chat-screen-layout-calculations-complete?) - (effects/update-input-mention props state subscriptions) - (effects/link-previews props state animations subscriptions) - (effects/use-images props state animations subscriptions) - [:<> - [mentions/view props state animations max-height cursor-pos - (:images subscriptions) - (:link-previews? subscriptions) - (:reply subscriptions) - (:edit subscriptions)] - [rn/view - {:style style/composer-sheet-and-jump-to-container} - [sub-view/shell-button shared-values chat-list-scroll-y window-height] - [gesture/gesture-detector - {:gesture - (drag-gesture/drag-gesture props state animations dimensions keyboard-shown)} - [reanimated/view - {:style (style/sheet-container insets animations theme)} - [sub-view/bar theme] - [:<> - [reply/view state (:input-ref props)] - [edit/view - {:text-value (:text-value state) - :input-height (:height animations) - :input-ref (:input-ref props)}]] - [reanimated/touchable-opacity - {:active-opacity 1 - :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 - {:ref #(reset! (:selectable-input-ref props) %) - :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) - :on-blur #(handler/blur state animations dimensions) - :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 theme - :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 theme) - :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 subscriptions]]]]])) +(defn input + [_ _] + (let [default-value (:input-text (rf/sub [:chats/current-chat-input]))] + (fn [set-ref theme] + [rn/text-input + {:ref set-ref + :on-change-text handler/change-text + :keyboard-appearance theme + :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 theme) + :max-length constants/max-text-size + :accessibility-label :chat-message-input + :style (style/input-text theme) + :default-value default-value}]))) -(defn f-composer +(defn view [props] - (let [theme (quo.theme/use-theme) - opacity (reanimated/use-shared-value 0) - window-height (:height (rn/get-window)) - background-y (reanimated/use-shared-value (- window-height)) - composer-default-height (+ constants/composer-default-height (:bottom (:insets props))) - shared-values (utils/init-shared-values) - extra-params (assoc props - :window-height window-height - :opacity opacity - :background-y background-y - :theme theme) - props (utils/init-non-reactive-state) - state (utils/init-reactive-state)] - [rn/view (when platform/ios? {:style {:z-index 1}}) - [reanimated/view {:style (style/background opacity background-y window-height)}] - [reanimated/view {:style (style/blur-container composer-default-height shared-values)} - [quo/blur (style/blur-view theme)]] - [:f> sheet-component extra-params props state shared-values]])) - -(defn composer - [props] - (let [current-chat-id (rf/sub [:chats/current-chat-id]) - able-to-send-message? (rf/sub [:chats/able-to-send-message?])] - (when-not (string/blank? current-chat-id) - (if able-to-send-message? - [:f> f-composer props] - [contact-requests.bottom-drawer/view - {:contact-id current-chat-id}])))) + (let [theme (quo.theme/use-theme) + bottom (safe-area/get-bottom) + input-ref (rn/use-ref-atom nil) + set-ref (rn/use-callback (fn [value] + (rf/dispatch [:chat/set-input-ref value]) + (reset! input-ref value)))] + [rn/view {:style {:margin-bottom bottom}} + [mentions/view props] + [quo/separator] + [rn/view {:style {:padding-horizontal 20 :padding-top 20}} + [:<> + [reply/view input-ref] + [edit/view input-ref]] + [input set-ref theme] + [:<> + [link-preview/view] + [images/images-list]] + [actions/view input-ref]]])) 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 87ed3b431d..e26cdf467d 100644 --- a/src/status_im/contexts/chat/messenger/messages/list/view.cljs +++ b/src/status_im/contexts/chat/messenger/messages/list/view.cljs @@ -26,9 +26,7 @@ [utils.worklets.chat.messenger.messages :as worklets])) (defonce ^:const distance-from-last-message 4) -(defonce ^:const loading-indicator-extra-spacing 250) (defonce ^:const loading-indicator-page-loading-height 100) -(defonce ^:const min-message-height 32) (defn list-key-fn [{:keys [message-id value]}] (or message-id value)) (defn list-ref [ref] (reset! state/messages-list-ref ref)) @@ -336,8 +334,7 @@ (defn list-group-chat-header [{:keys [chat-id invitation-admin]}] - [rn/view - [chat.group/group-chat-footer chat-id invitation-admin]]) + [chat.group/group-chat-footer chat-id invitation-admin]) (defn render-fn [{:keys [type value] :as message-data} _ _ @@ -410,10 +407,8 @@ {:key-fn list-key-fn :ref list-ref :bounces false - :header [:<> - [list-header insets able-to-send-message?] - (when (= (:chat-type chat) constants/private-group-chat-type) - [list-group-chat-header chat])] + :header (when (= (:chat-type chat) constants/private-group-chat-type) + [list-group-chat-header chat]) :footer [list-footer {:theme theme :chat chat @@ -433,15 +428,11 @@ :distance-from-list-top distance-from-list-top}) :on-end-reached #(list-on-end-reached distance-from-list-top) :on-scroll-to-index-failed identity - :scroll-indicator-insets {:top (if (:able-to-send-message? context) - (- composer.constants/composer-default-height 16) - 0) - :right 1} :keyboard-dismiss-mode :interactive :keyboard-should-persist-taps :always - :on-scroll-begin-drag #(do - (rf/dispatch [:chat.ui/set-input-focused false]) - (rn/dismiss-keyboard!)) + :on-scroll-begin-drag (fn [] + (rf/dispatch [:chat.ui/set-input-focused false]) + (rn/dismiss-keyboard!)) :on-momentum-scroll-begin state/start-scrolling :on-momentum-scroll-end state/stop-scrolling :scroll-event-throttle 16 @@ -463,4 +454,5 @@ :chat-screen-layout-calculations-complete? chat-screen-layout-calculations-complete?}) :scroll-enabled (not recording?) - :content-inset-adjustment-behavior :never}]]])) + :content-inset-adjustment-behavior :never + :scroll-indicator-insets {:right 1}}]]])) diff --git a/src/status_im/contexts/chat/messenger/messages/navigation/style.cljs b/src/status_im/contexts/chat/messenger/messages/navigation/style.cljs index 5e70daf6fe..71e2a26f77 100644 --- a/src/status_im/contexts/chat/messenger/messages/navigation/style.cljs +++ b/src/status_im/contexts/chat/messenger/messages/navigation/style.cljs @@ -5,12 +5,13 @@ (defn navigation-view [navigation-view-height pinned-banner-height] - {:top 0 - :left 0 - :right 0 - :position :absolute - :height (+ navigation-view-height pinned-banner-height) - :z-index 1}) + {:top 0 + :left 0 + :right 0 + :position :absolute + :pointer-events :box-none + :height (+ navigation-view-height pinned-banner-height) + :z-index 1}) (defn animated-background-view [background-opacity navigation-view-height] diff --git a/src/status_im/contexts/chat/messenger/messages/scroll_to_bottom/style.cljs b/src/status_im/contexts/chat/messenger/messages/scroll_to_bottom/style.cljs new file mode 100644 index 0000000000..5dee6da667 --- /dev/null +++ b/src/status_im/contexts/chat/messenger/messages/scroll_to_bottom/style.cljs @@ -0,0 +1,11 @@ +(ns status-im.contexts.chat.messenger.messages.scroll-to-bottom.style + (:require [status-im.contexts.shell.jump-to.constants :as shell.constants])) + +(def shell-button-container + {:z-index 1 + :bottom shell.constants/floating-shell-button-height}) + +(def scroll-to-bottom-button + {:position :absolute + :right 0 + :left 0}) diff --git a/src/status_im/contexts/chat/messenger/messages/scroll_to_bottom/view.cljs b/src/status_im/contexts/chat/messenger/messages/scroll_to_bottom/view.cljs new file mode 100644 index 0000000000..9117322765 --- /dev/null +++ b/src/status_im/contexts/chat/messenger/messages/scroll_to_bottom/view.cljs @@ -0,0 +1,20 @@ +(ns status-im.contexts.chat.messenger.messages.scroll-to-bottom.view + (:require + [quo.core :as quo] + [react-native.core :as rn] + [status-im.contexts.chat.messenger.messages.scroll-to-bottom.style :as style] + [utils.re-frame :as rf] + [utils.worklets.chat.messenger.composer :as worklets])) + +(defn button + [{:keys [chat-list-scroll-y]}] + (let [{window-height :height} (rn/get-window) + scroll-down-button-opacity (worklets/scroll-down-button-opacity + chat-list-scroll-y + false + window-height)] + [rn/view {:style style/shell-button-container} + [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]])) diff --git a/src/status_im/contexts/chat/messenger/messages/view.cljs b/src/status_im/contexts/chat/messenger/messages/view.cljs index bdd31c592a..f28d9dabeb 100644 --- a/src/status_im/contexts/chat/messenger/messages/view.cljs +++ b/src/status_im/contexts/chat/messenger/messages/view.cljs @@ -1,19 +1,32 @@ (ns status-im.contexts.chat.messenger.messages.view (:require + [clojure.string :as string] [quo.theme :as quo.theme] [react-native.core :as rn] [react-native.platform :as platform] [react-native.reanimated :as reanimated] [react-native.safe-area :as safe-area] [reagent.core :as reagent] - [status-im.contexts.chat.messenger.composer.view :as composer.view] + [status-im.contexts.chat.messenger.composer.view :as composer] + [status-im.contexts.chat.messenger.messages.contact-requests.bottom-drawer.view :as + contact-requests.bottom-drawer] [status-im.contexts.chat.messenger.messages.list.style :as style] [status-im.contexts.chat.messenger.messages.list.view :as list.view] [status-im.contexts.chat.messenger.messages.navigation.view :as messages.navigation] + [status-im.contexts.chat.messenger.messages.scroll-to-bottom.view :as scroll-to-bottom] [status-im.contexts.chat.messenger.placeholder.view :as placeholder.view] [status-im.feature-flags :as ff] [utils.re-frame :as rf])) +(defn- footer + [props] + (let [current-chat-id (rf/sub [:chats/current-chat-id]) + able-to-send-message? (rf/sub [:chats/able-to-send-message?])] + (when-not (string/blank? current-chat-id) + (if able-to-send-message? + [composer/view props] + [contact-requests.bottom-drawer/view {:contact-id current-chat-id}])))) + (defn- chat-screen [{:keys [insets] :as props}] (let [theme (quo.theme/use-theme) @@ -23,9 +36,11 @@ [rn/keyboard-avoiding-view {:style (style/keyboard-avoiding-container theme) :keyboard-vertical-offset (- (if platform/ios? alert-banners-top-margin 0) (:bottom insets))} - [list.view/messages-list-content props] + [:<> + [list.view/messages-list-content props] + [scroll-to-bottom/button props]] [messages.navigation/view props] - [composer.view/composer props]]))) + [footer props]]))) (defn lazy-chat-screen [chat-screen-layout-calculations-complete? *screen-loaded?*] diff --git a/src/status_im/subs/chats.cljs b/src/status_im/subs/chats.cljs index fa764df158..2a13880f7c 100644 --- a/src/status_im/subs/chats.cljs +++ b/src/status_im/subs/chats.cljs @@ -344,13 +344,6 @@ (fn [[chat-id mentions]] (take 15 (get mentions chat-id)))) -(re-frame/reg-sub - :chat/input-with-mentions - :<- [:chats/current-chat-id] - :<- [:chat/inputs-with-mentions] - (fn [[chat-id cursor]] - (get cursor chat-id))) - (re-frame/reg-sub :chats/link-previews-unfurled :<- [:chat/link-previews] diff --git a/src/status_im/subs/root.cljs b/src/status_im/subs/root.cljs index 8515c2a234..72dfdd62c1 100644 --- a/src/status_im/subs/root.cljs +++ b/src/status_im/subs/root.cljs @@ -110,7 +110,7 @@ (reg-root-key-sub :chat/memberships :chat/memberships) (reg-root-key-sub :group-chat/invitations :group-chat/invitations) (reg-root-key-sub :chats/mention-suggestions :chats/mention-suggestions) -(reg-root-key-sub :chat/inputs-with-mentions :chat/inputs-with-mentions) + (reg-root-key-sub :chats-home-list :chats-home-list) (reg-root-key-sub :chats/recording? :chats/recording?) (reg-root-key-sub :reactions/authors :reactions/authors)