From 341497e98debc31729e96d409beb13607c9963a4 Mon Sep 17 00:00:00 2001 From: Omar Basem Date: Tue, 9 May 2023 21:03:19 +0400 Subject: [PATCH] =?UTF-8?q?feat:=20new=20composer=20complete=20?= =?UTF-8?q?=F0=9F=8E=89=20(#15818)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: new composer --- src/react_native/core.cljs | 7 +- src/status_im/chat/models/input.cljs | 13 +- src/status_im/chat/models/mentions.cljs | 28 +- .../screens/chat/components/edit/style.cljs | 25 -- .../screens/chat/components/edit/view.cljs | 33 -- .../screens/chat/components/reply/style.cljs | 37 -- .../screens/chat/components/reply/view.cljs | 142 ------- .../ui2/screens/chat/composer/edit/style.cljs | 5 - .../ui2/screens/chat/composer/edit/view.cljs | 23 -- .../screens/chat/composer/images/style.cljs | 11 - .../screens/chat/composer/images/view.cljs | 35 -- .../ui2/screens/chat/composer/input.cljs | 383 ------------------ .../ui2/screens/chat/composer/mentions.cljs | 2 - .../ui2/screens/chat/composer/reply.cljs | 37 -- .../ui2/screens/chat/messages/message.cljs | 6 +- src/status_im2/config.cljs | 2 - src/status_im2/constants.cljs | 1 - .../bottom_sheet_composer/gradients/view.cljs | 23 -- .../bottom_sheet_composer/images/view.cljs | 41 -- .../chat/bottom_sheet_composer/sub_view.cljs | 18 - .../chat/bottom_sheet_composer/view.cljs | 172 -------- .../actions/style.cljs | 4 +- .../actions/view.cljs | 83 ++-- .../constants.cljs | 14 +- .../edit/style.cljs | 2 +- .../edit/view.cljs | 24 +- .../effects.cljs | 114 ++++-- .../gesture.cljs | 47 ++- .../gradients/style.cljs | 6 +- .../chat/composer/gradients/view.cljs | 24 ++ .../handlers.cljs | 40 +- .../images/style.cljs | 2 +- .../contexts/chat/composer/images/view.cljs | 47 +++ .../keyboard.cljs | 2 +- .../mentions/style.cljs | 4 +- .../mentions/view.cljs | 8 +- .../reply/style.cljs | 9 +- .../reply/view.cljs | 15 +- .../contexts/chat/composer/selection.cljs | 109 +++++ .../style.cljs | 33 +- .../contexts/chat/composer/sub_view.cljs | 19 + .../utils.cljs | 84 +++- .../contexts/chat/composer/view.cljs | 144 +++++++ .../messages/composer/controls/style.cljs | 28 -- .../chat/messages/composer/controls/view.cljs | 127 ------ .../chat/messages/composer/mentions/view.cljs | 35 -- .../chat/messages/composer/style.cljs | 42 -- .../contexts/chat/messages/composer/view.cljs | 241 ----------- .../chat/messages/content/pin/view.cljs | 3 +- .../contexts/chat/messages/content/view.cljs | 50 +-- .../contexts/chat/messages/drawers/view.cljs | 4 +- .../chat/messages/list/new_temp_view.cljs | 211 ---------- .../contexts/chat/messages/list/view.cljs | 154 +++++-- .../contexts/chat/messages/view.cljs | 13 +- 54 files changed, 821 insertions(+), 1965 deletions(-) delete mode 100644 src/status_im/ui2/screens/chat/components/edit/style.cljs delete mode 100644 src/status_im/ui2/screens/chat/components/edit/view.cljs delete mode 100644 src/status_im/ui2/screens/chat/components/reply/style.cljs delete mode 100644 src/status_im/ui2/screens/chat/components/reply/view.cljs delete mode 100644 src/status_im/ui2/screens/chat/composer/edit/style.cljs delete mode 100644 src/status_im/ui2/screens/chat/composer/edit/view.cljs delete mode 100644 src/status_im/ui2/screens/chat/composer/images/style.cljs delete mode 100644 src/status_im/ui2/screens/chat/composer/images/view.cljs delete mode 100644 src/status_im/ui2/screens/chat/composer/input.cljs delete mode 100644 src/status_im/ui2/screens/chat/composer/mentions.cljs delete mode 100644 src/status_im/ui2/screens/chat/composer/reply.cljs delete mode 100644 src/status_im2/contexts/chat/bottom_sheet_composer/gradients/view.cljs delete mode 100644 src/status_im2/contexts/chat/bottom_sheet_composer/images/view.cljs delete mode 100644 src/status_im2/contexts/chat/bottom_sheet_composer/sub_view.cljs delete mode 100644 src/status_im2/contexts/chat/bottom_sheet_composer/view.cljs rename src/status_im2/contexts/chat/{bottom_sheet_composer => composer}/actions/style.cljs (86%) rename src/status_im2/contexts/chat/{bottom_sheet_composer => composer}/actions/view.cljs (75%) rename src/status_im2/contexts/chat/{bottom_sheet_composer => composer}/constants.cljs (67%) rename src/status_im2/contexts/chat/{bottom_sheet_composer => composer}/edit/style.cljs (86%) rename src/status_im2/contexts/chat/{bottom_sheet_composer => composer}/edit/view.cljs (71%) rename src/status_im2/contexts/chat/{bottom_sheet_composer => composer}/effects.cljs (51%) rename src/status_im2/contexts/chat/{bottom_sheet_composer => composer}/gesture.cljs (75%) rename src/status_im2/contexts/chat/{bottom_sheet_composer => composer}/gradients/style.cljs (84%) create mode 100644 src/status_im2/contexts/chat/composer/gradients/view.cljs rename src/status_im2/contexts/chat/{bottom_sheet_composer => composer}/handlers.cljs (77%) rename src/status_im2/contexts/chat/{bottom_sheet_composer => composer}/images/style.cljs (88%) create mode 100644 src/status_im2/contexts/chat/composer/images/view.cljs rename src/status_im2/contexts/chat/{bottom_sheet_composer => composer}/keyboard.cljs (98%) rename src/status_im2/contexts/chat/{bottom_sheet_composer => composer}/mentions/style.cljs (84%) rename src/status_im2/contexts/chat/{bottom_sheet_composer => composer}/mentions/view.cljs (92%) rename src/status_im2/contexts/chat/{bottom_sheet_composer => composer}/reply/style.cljs (72%) rename src/status_im2/contexts/chat/{bottom_sheet_composer => composer}/reply/view.cljs (92%) create mode 100644 src/status_im2/contexts/chat/composer/selection.cljs rename src/status_im2/contexts/chat/{bottom_sheet_composer => composer}/style.cljs (78%) create mode 100644 src/status_im2/contexts/chat/composer/sub_view.cljs rename src/status_im2/contexts/chat/{bottom_sheet_composer => composer}/utils.cljs (63%) create mode 100644 src/status_im2/contexts/chat/composer/view.cljs delete mode 100644 src/status_im2/contexts/chat/messages/composer/controls/style.cljs delete mode 100644 src/status_im2/contexts/chat/messages/composer/controls/view.cljs delete mode 100644 src/status_im2/contexts/chat/messages/composer/mentions/view.cljs delete mode 100644 src/status_im2/contexts/chat/messages/composer/style.cljs delete mode 100644 src/status_im2/contexts/chat/messages/composer/view.cljs delete mode 100644 src/status_im2/contexts/chat/messages/list/new_temp_view.cljs diff --git a/src/react_native/core.cljs b/src/react_native/core.cljs index 982e6d1a9b..03b2332aa5 100644 --- a/src/react_native/core.cljs +++ b/src/react_native/core.cljs @@ -130,6 +130,9 @@ (when (exists? (.-NativeModules ^js react-native)) (.-RNSelectableTextInputManager ^js (.-NativeModules ^js react-native)))) +;; TODO: iOS native implementation https://github.com/status-im/status-mobile/issues/14137 (defonce selectable-text-input - (reagent/adapt-react-class - (.requireNativeComponent ^js react-native "RNSelectableTextInput"))) + (if platform/android? + (reagent/adapt-react-class + (.requireNativeComponent ^js react-native "RNSelectableTextInput")) + view)) diff --git a/src/status_im/chat/models/input.cljs b/src/status_im/chat/models/input.cljs index 50399cb387..71c5db4b8b 100644 --- a/src/status_im/chat/models/input.cljs +++ b/src/status_im/chat/models/input.cljs @@ -6,7 +6,6 @@ [status-im.chat.models.mentions :as mentions] [status-im.chat.models.message :as chat.message] [status-im.chat.models.message-content :as message-content] - [status-im2.config :as config] [status-im2.constants :as constants] [utils.re-frame :as rf] [utils.i18n :as i18n] @@ -35,9 +34,12 @@ "Set input text for current-chat. Takes db and input text and cofx as arguments and returns new fx. Always clear all validation messages." {:events [:chat.ui/set-chat-input-text]} - [{db :db} new-input chat-id] + [{:keys [db] :as cofx} new-input chat-id] (let [current-chat-id (or chat-id (:current-chat-id db))] - {:db (assoc-in db [:chat/inputs current-chat-id :input-text] (text->emoji new-input))})) + (rf/merge cofx + {:db (assoc-in db [:chat/inputs current-chat-id :input-text] (text->emoji new-input))} + (when (empty? new-input) + (mentions/clear-mentions))))) (rf/defn set-input-content-height {:events [:chat.ui/set-input-content-height]} @@ -111,8 +113,7 @@ (update-in [:chat/inputs current-chat-id :metadata] dissoc :sending-image)) - :dispatch (when-not config/new-composer-enabled? - [:mention/to-input-field text current-chat-id])})) + :dispatch [:mention/to-input-field text current-chat-id]})) (rf/defn show-contact-request-input "Sets reference to previous chat message and focuses on input" @@ -184,8 +185,6 @@ [{:keys [db] :as cofx}] (let [current-chat-id (:current-chat-id db)] (rf/merge cofx - (when-not config/new-composer-enabled? - {:set-text-input-value [current-chat-id ""]}) (clean-input current-chat-id) (mentions/clear-mentions)))) diff --git a/src/status_im/chat/models/mentions.cljs b/src/status_im/chat/models/mentions.cljs index 50580b074f..a295681f10 100644 --- a/src/status_im/chat/models/mentions.cljs +++ b/src/status_im/chat/models/mentions.cljs @@ -3,7 +3,6 @@ [quo.react :as react] [quo.react-native :as rn] [re-frame.core :as re-frame] - [status-im2.config :as config] [utils.re-frame :as rf] [taoensso.timbre :as log] [native-module.core :as native-module])) @@ -99,10 +98,9 @@ [{: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)] - {:set-text-input-value [chat-id new-text] - :db (-> db - (assoc-in [:chats/mentions chat-id :mentions] state) - (assoc-in [:chat/inputs-with-mentions chat-id] input-segments))})) + {:db (-> db + (assoc-in [:chats/mentions chat-id :mentions] state) + (assoc-in [:chat/inputs-with-mentions chat-id] input-segments))})) (rf/defn on-change-text {:events [:mention/on-change-text]} @@ -163,25 +161,11 @@ :match match :searched-text searched-text :public-key public-key}) - (let [{:keys [new-text state chat-id]} (transfer-mention-result result) - {:keys [at-sign-idx]} state - cursor (+ at-sign-idx (count primary-name) 2)] + (let [{:keys [new-text chat-id]} (transfer-mention-result result)] (rf/merge cofx - (let [common {:db (-> db - (assoc-in [:chats/mention-suggestions chat-id] nil)) - :dispatch [:chat.ui/set-chat-input-text new-text chat-id]} - extra (if (not config/new-composer-enabled?) - ;; NOTE(rasom): Some keyboards do not react on selection property passed to - ;; text input (specifically Samsung keyboard with predictive text set on). - ;; In this case, if the user continues typing after the programmatic change, - ;; the new text is added to the last known cursor position before - ;; programmatic change. By calling `reset-text-input-cursor` we force the - ;; keyboard's cursor position to be changed before the next input. - {:set-text-input-value [chat-id new-text text-input-ref] - :reset-text-input-cursor (reset-text-input-cursor text-input-ref cursor)} - {})] - (merge common extra)) + {:db (assoc-in db [:chats/mention-suggestions chat-id] nil) + :dispatch [:chat.ui/set-chat-input-text new-text chat-id]} (recheck-at-idxs public-key)))) (rf/defn clear-suggestions diff --git a/src/status_im/ui2/screens/chat/components/edit/style.cljs b/src/status_im/ui2/screens/chat/components/edit/style.cljs deleted file mode 100644 index 4f336579b8..0000000000 --- a/src/status_im/ui2/screens/chat/components/edit/style.cljs +++ /dev/null @@ -1,25 +0,0 @@ -(ns status-im.ui2.screens.chat.components.edit.style) - -(def container - {:flex-direction :row - :height 24}) - -(def content-container - {:padding-horizontal 10 - :flex 1 - :flex-direction :row}) - -(def icon-container - {:position :absolute - :left 10 - :bottom -4 - :width 16 - :height 16}) - -(def text-container - {:position :absolute - :left 36 - :right 54 - :top 3 - :flex-direction :row - :align-items :center}) diff --git a/src/status_im/ui2/screens/chat/components/edit/view.cljs b/src/status_im/ui2/screens/chat/components/edit/view.cljs deleted file mode 100644 index 0369831308..0000000000 --- a/src/status_im/ui2/screens/chat/components/edit/view.cljs +++ /dev/null @@ -1,33 +0,0 @@ -(ns status-im.ui2.screens.chat.components.edit.view - (:require [utils.i18n :as i18n] - [quo2.core :as quo] - [quo2.foundations.colors :as colors] - [re-frame.core :as rf] - [react-native.core :as rn] - [status-im.ui2.screens.chat.components.edit.style :as style])) - -(defn edit-message - [on-cancel] - [rn/view - {:style style/container - :accessibility-label :edit-message} - [rn/view {:style style/content-container} - [quo/icon :i/connector - {:size 16 - :color (colors/theme-colors colors/neutral-40 colors/neutral-60) - :container-style style/icon-container}] - [rn/view {:style style/text-container} - [quo/text - {:weight :medium - :size :paragraph-2} - (i18n/label :t/editing-message)]]] - [quo/button - {:width 24 - :size 24 - :accessibility-label :reply-cancel-button - :on-press #(do (on-cancel) - (rf/dispatch [:chat.ui/cancel-message-edit])) - :type :outline} - [quo/icon :i/close - {:size 16 - :color (colors/theme-colors colors/neutral-100 colors/neutral-40)}]]]) diff --git a/src/status_im/ui2/screens/chat/components/reply/style.cljs b/src/status_im/ui2/screens/chat/components/reply/style.cljs deleted file mode 100644 index 06614a8542..0000000000 --- a/src/status_im/ui2/screens/chat/components/reply/style.cljs +++ /dev/null @@ -1,37 +0,0 @@ -(ns status-im.ui2.screens.chat.components.reply.style) - -(defn reply-content - [pin?] - {:padding-horizontal (when-not pin? 10) - :flex 1 - :flex-direction :row}) - -(defn quoted-message - [pin? in-chat-input?] - (merge {:flex-direction :row - :flex 1 - :align-items :center - :width (if in-chat-input? "100%" "45%")} - (when-not pin? - {:left 22 - :margin-right 22}))) - -(def reply-from - {:flex-direction :row - :align-items :center}) - -(def message-author-text - {:margin-left 4}) - -(def message-text - {:text-transform :none - :margin-left 4 - :margin-top 2 - :flex 1}) - -(def gradient - {:position :absolute - :right 0 - :top 0 - :bottom 0 - :width "50%"}) diff --git a/src/status_im/ui2/screens/chat/components/reply/view.cljs b/src/status_im/ui2/screens/chat/components/reply/view.cljs deleted file mode 100644 index a5886276ad..0000000000 --- a/src/status_im/ui2/screens/chat/components/reply/view.cljs +++ /dev/null @@ -1,142 +0,0 @@ -(ns status-im.ui2.screens.chat.components.reply.view - (:require [clojure.string :as string] - [utils.i18n :as i18n] - [quo.react-native :as rn] - [quo2.components.buttons.button :as quo2.button] - [quo2.components.icon :as quo2.icon] - [quo2.components.markdown.text :as quo2.text] - [quo2.foundations.colors :as colors] - [status-im2.constants :as constants] - [status-im.ethereum.stateofus :as stateofus] - [status-im.ui.components.icons.icons :as icons] - [status-im2.contexts.chat.messages.avatar.view :as avatar] - [utils.re-frame :as rf] - [status-im.ui2.screens.chat.components.reply.style :as style] - [react-native.linear-gradient :as linear-gradient])) - -(defn get-quoted-text-with-mentions - [parsed-text] - (string/join - (mapv (fn [{:keys [type literal children]}] - (cond - (= type "paragraph") - (get-quoted-text-with-mentions children) - - (= type "mention") - (rf/sub [:messages/resolve-mention literal]) - - (seq children) - (get-quoted-text-with-mentions children) - - :else - literal)) - parsed-text))) - -(defn format-author - [contact-name] - (let [author (if (or (= (aget contact-name 0) "@") - ;; in case of replies - (= (aget contact-name 1) "@")) - (or (stateofus/username contact-name) - (subs contact-name 0 81)) - contact-name)] - author)) - -(defn format-reply-author - [from username current-public-key] - (or (and (= from current-public-key) - (i18n/label :t/You)) - (when username (format-author username)))) - -(defn reply-deleted-message - [] - [rn/view - {:style {:flex-direction :row - :align-items :center}} - [quo2.icon/icon :sad-face {:size 16}] - [quo2.text/text - {:number-of-lines 1 - :size :label - :weight :regular - :accessibility-label :quoted-message - :style {:text-transform :none - :margin-left 4 - :margin-top 2}} - (i18n/label :t/message-deleted)]]) - -(defn reply-from - [{:keys [from contact-name current-public-key]}] - [rn/view {:style style/reply-from} - [avatar/avatar from :xxxs] - [quo2.text/text - {:weight :semi-bold - :size :paragraph-2 - :number-of-lines 1 - :style style/message-author-text} - (format-reply-author from contact-name current-public-key)]]) - -(defn reply-message - [{:keys [from identicon content-type contentType parsed-text content deleted? deleted-for-me? - album-images-count]} - in-chat-input? pin? recording-audio?] - (let [contact-name (rf/sub [:contacts/contact-name-by-identity from]) - current-public-key (rf/sub [:multiaccount/public-key]) - content-type (or content-type contentType)] - [rn/view - {:style {:flex-direction :row - :height (when-not pin? 24) - :accessibility-label :reply-message}} - [rn/view {:style (style/reply-content pin?)} - (when-not pin? - ;;TODO quo2 icon should be used - [icons/icon :main-icons/connector - {:color (colors/theme-colors colors/neutral-40 colors/neutral-60) - :container-style {:position :absolute :left 10 :bottom -4 :width 16 :height 16}}]) - (if (or deleted? deleted-for-me?) - [rn/view {:style (style/quoted-message pin? in-chat-input?)} - [reply-deleted-message]] - [rn/view {:style (style/quoted-message pin? in-chat-input?)} - [reply-from - {:from from - :identicon identicon - :contact-name contact-name - :current-public-key current-public-key}] - [quo2.text/text - {:number-of-lines 1 - :size :label - :weight :regular - :accessibility-label :quoted-message - :ellipsize-mode :tail - :style (merge - style/message-text - (when (or (= constants/content-type-image content-type) - (= constants/content-type-sticker content-type) - (= constants/content-type-audio content-type)) - {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}))} - (case (or content-type contentType) - constants/content-type-image (if album-images-count - (i18n/label :t/images-albums-count - {:album-images-count album-images-count}) - (i18n/label :t/image)) - constants/content-type-sticker (i18n/label :t/sticker) - constants/content-type-audio (i18n/label :t/audio) - (get-quoted-text-with-mentions (or parsed-text (:parsed-text content))))]])] - (when (and in-chat-input? (not recording-audio?)) - [quo2.button/button - {:width 24 - :size 24 - :type :outline - :accessibility-label :reply-cancel-button - :on-press #(rf/dispatch [:chat.ui/cancel-message-reply])} - ;;TODO quo2 icon should be used - [icons/icon :main-icons/close - {:width 16 - :height 16 - :color (colors/theme-colors colors/neutral-100 colors/neutral-40)}]]) - (when (and in-chat-input? recording-audio?) - [linear-gradient/linear-gradient - {:colors [(colors/theme-colors colors/white-opa-0 colors/neutral-90-opa-0) - (colors/theme-colors colors/white colors/neutral-90)] - :start {:x 0 :y 0} - :end {:x 0.7 :y 0} - :style style/gradient}])])) diff --git a/src/status_im/ui2/screens/chat/composer/edit/style.cljs b/src/status_im/ui2/screens/chat/composer/edit/style.cljs deleted file mode 100644 index 27d3282d8a..0000000000 --- a/src/status_im/ui2/screens/chat/composer/edit/style.cljs +++ /dev/null @@ -1,5 +0,0 @@ -(ns status-im.ui2.screens.chat.composer.edit.style) - -(def container - {:padding-horizontal 15 - :padding-vertical 8}) diff --git a/src/status_im/ui2/screens/chat/composer/edit/view.cljs b/src/status_im/ui2/screens/chat/composer/edit/view.cljs deleted file mode 100644 index 66de86ab3a..0000000000 --- a/src/status_im/ui2/screens/chat/composer/edit/view.cljs +++ /dev/null @@ -1,23 +0,0 @@ -(ns status-im.ui2.screens.chat.composer.edit.view - (:require [react-native.core :as rn] - [status-im.ui2.screens.chat.components.edit.view :as edit] - [status-im.ui2.screens.chat.composer.edit.style :as style] - [status-im.ui2.screens.chat.composer.input :as input])) - -(defn- focus-input-on-edit - [edit had-edit text-input-ref] - ;;when we show edit we focus input - (when-not (= edit @had-edit) - (reset! had-edit edit) - (when edit - ;; A setTimeout of 0 is necessary to ensure the statement is enqueued and will get executed ASAP. - (js/setTimeout #(input/input-focus text-input-ref) 0)))) - -(defn edit-message-auto-focus-wrapper - [text-input-ref _] - (let [had-edit (atom nil)] - (fn [_ edit on-cancel] - (focus-input-on-edit edit had-edit text-input-ref) - (when edit - [rn/view {:style style/container} - [edit/edit-message on-cancel]])))) diff --git a/src/status_im/ui2/screens/chat/composer/images/style.cljs b/src/status_im/ui2/screens/chat/composer/images/style.cljs deleted file mode 100644 index 0cd3e64809..0000000000 --- a/src/status_im/ui2/screens/chat/composer/images/style.cljs +++ /dev/null @@ -1,11 +0,0 @@ -(ns status-im.ui2.screens.chat.composer.images.style) - -(def remove-photo-container - {:position :absolute - :top 5 - :right 5}) - -(def small-image - {:width 56 - :height 56 - :border-radius 8}) diff --git a/src/status_im/ui2/screens/chat/composer/images/view.cljs b/src/status_im/ui2/screens/chat/composer/images/view.cljs deleted file mode 100644 index db09530bf6..0000000000 --- a/src/status_im/ui2/screens/chat/composer/images/view.cljs +++ /dev/null @@ -1,35 +0,0 @@ -(ns status-im.ui2.screens.chat.composer.images.view - (:require [quo2.core :as quo] - [quo2.foundations.colors :as colors] - [re-frame.core :as rf] - [react-native.core :as rn] - [status-im.ui2.screens.chat.composer.images.style :as style])) - -(defn image - [item] - ;; there is a bug on Android https://github.com/facebook/react-native/issues/12534 - ;; so we need some magic here with paddings so close button isn't cut - [rn/view {:padding-top 12 :padding-bottom 8 :padding-right 12} - [rn/image - {:source {:uri (:resized-uri (val item))} - :style style/small-image}] - [rn/touchable-opacity - {:on-press (fn [] (rf/dispatch [:chat.ui/image-unselected (val item)])) - :style style/remove-photo-container - :hit-slop {:right 5 - :left 5 - :top 10 - :bottom 10}} - [quo/icon :i/clear - {:size 20 - :color (colors/theme-colors colors/neutral-50 colors/neutral-60)}]]]) - -(defn images-list - [images] - (when (seq images) - [rn/flat-list - {:key-fn first - :render-fn image - :data images - :horizontal true - :keyboard-should-persist-taps :handled}])) diff --git a/src/status_im/ui2/screens/chat/composer/input.cljs b/src/status_im/ui2/screens/chat/composer/input.cljs deleted file mode 100644 index a1473813bb..0000000000 --- a/src/status_im/ui2/screens/chat/composer/input.cljs +++ /dev/null @@ -1,383 +0,0 @@ -(ns status-im.ui2.screens.chat.composer.input - (:require [clojure.string :as string] - [oops.core :as oops] - [quo2.foundations.colors :as colors] - [re-frame.core :as re-frame] - [reagent.core :as reagent] - [status-im2.constants :as chat.constants] - [utils.i18n :as i18n] - [utils.re-frame :as rf] - [utils.transforms :as transforms] - [quo2.foundations.typography :as typography] - [react-native.background-timer :as background-timer] - [react-native.platform :as platform] - [react-native.core :as rn] - [react-native.clipboard :as clipboard] - [quo.react :as quo.react])) - -(defonce input-texts (atom {})) -(defonce input-text-content-heights (atom {})) -(defonce mentions-enabled? (reagent/atom {})) -(defonce chat-input-key (reagent/atom 1)) -(defonce text-input-ref (reagent/atom nil)) -(defonce recording-audio? (reagent/atom false)) -(defonce reviewing-audio? (reagent/atom false)) -(defonce reviewing-audio-filepath (atom {})) -(defonce record-audio-permission-granted (reagent/atom false)) -(defonce record-audio-reset-fn (atom nil)) - -(declare selectable-text-input) - -(re-frame/reg-fx - :chat.ui/clear-inputs - (fn [] - (reset! input-texts {}) - (reset! input-text-content-heights {}) - (reset! mentions-enabled? {}) - (reset! chat-input-key 1))) - -(defn input-focus - [text-input-ref] - (some-> ^js (quo.react/current-ref text-input-ref) - .focus)) - -(defn show-send - [{:keys [actions-ref send-ref sticker-ref record-ref]} chat-id] - (when (and (or @recording-audio? - (get @reviewing-audio-filepath chat-id)) - @record-audio-reset-fn) - (@record-audio-reset-fn) - (swap! reviewing-audio-filepath dissoc chat-id)) - (when actions-ref - (quo.react/set-native-props actions-ref #js {:width 0 :left -88})) - (quo.react/set-native-props send-ref #js {:width nil :right nil}) - (quo.react/set-native-props record-ref #js {:right nil :left -1000}) - (when sticker-ref - (quo.react/set-native-props sticker-ref #js {:width 0 :right -100}))) - -(defn hide-send - [{:keys [actions-ref send-ref sticker-ref record-ref]}] - (when actions-ref - (quo.react/set-native-props actions-ref #js {:width nil :left nil})) - (quo.react/set-native-props send-ref #js {:width 0 :right -100}) - (quo.react/set-native-props record-ref #js {:left 0 :right 0}) - (when sticker-ref - (quo.react/set-native-props sticker-ref #js {:width nil :right nil}))) - -(defn reset-input - [refs chat-id] - (some-> ^js (quo.react/current-ref (:text-input-ref refs)) - .clear) - (swap! mentions-enabled? update :render not) - (swap! input-texts dissoc chat-id) - (swap! input-text-content-heights dissoc chat-id)) - -(defn clear-input - [chat-id refs] - (hide-send refs) - (if (get @mentions-enabled? chat-id) - (do - (swap! mentions-enabled? dissoc chat-id) - ;;we need this timeout, because if we clear text input and first index was a mention object with - ;;blue color, - ;;after clearing text will be typed with this blue color, so we render white text first and then - ;;clear it - (js/setTimeout #(reset-input refs chat-id) 50)) - (reset-input refs chat-id))) - -(defn on-text-change - [val chat-id] - (swap! input-texts assoc chat-id val) - ;;we still store it in app-db for mentions, we don't have reactions in views - (rf/dispatch [:chat.ui/set-chat-input-text val])) - -(defn on-selection-change - [timeout-id last-text-change args] - (let [selection (.-selection ^js (.-nativeEvent ^js args)) - start (.-start selection) - end (.-end selection)] - ;; NOTE(rasom): on iOS we do not dispatch this event immediately - ;; because it is needed only in case if selection is changed without - ;; typing. Timeout might be canceled on `on-change`. - (when platform/ios? - (reset! - timeout-id - (background-timer/set-timeout - #(rf/dispatch [:mention/on-selection-change - {:start start - :end end}]) - 50))) - ;; NOTE(rasom): on Android we dispatch event only in case if there - ;; was no text changes during last 50ms. `on-selection-change` is - ;; dispatched after `on-change`, that's why there is no another way - ;; to know whether selection was changed without typing. - (when (and platform/android? - (or (not @last-text-change) - (< 50 (- (js/Date.now) @last-text-change)))) - (rf/dispatch [:mention/on-selection-change - {:start start - :end end}])))) - -(defn on-change - [last-text-change timeout-id refs chat-id sending-image args] - (let [text (.-text ^js (.-nativeEvent ^js args)) - prev-text (get @input-texts chat-id)] - (when (and (seq prev-text) (empty? text) (not sending-image)) - (hide-send refs)) - (when (and (empty? prev-text) (or (seq text) sending-image)) - (show-send refs chat-id) - (reset! recording-audio? false) - (swap! reviewing-audio-filepath dissoc chat-id)) - - (when (and (not (get @mentions-enabled? chat-id)) (string/index-of text "@")) - (swap! mentions-enabled? assoc chat-id true)) - - ;; NOTE(rasom): on iOS `on-selection-change` is canceled in case if it - ;; happens during typing because it is not needed for mention - ;; suggestions calculation - (when (and platform/ios? @timeout-id) - (background-timer/clear-timeout @timeout-id)) - (when platform/android? - (reset! last-text-change (js/Date.now))) - - (on-text-change text chat-id) - - (rf/dispatch [:mention/on-change-text text]))) - -(defn on-text-input - [chat-id args] - (let [native-event (.-nativeEvent ^js args) - text (.-text ^js native-event)] - (when (and (not (get @mentions-enabled? chat-id)) (string/index-of text "@")) - (swap! mentions-enabled? assoc chat-id true)))) - -(defn text-input-style - [chat-id] - (merge typography/font-regular - typography/paragraph-1 - {:flex 1 - :min-height 34 - :margin 0 - :flex-shrink 1 - :color (colors/theme-colors colors/neutral-100 colors/white) - :margin-horizontal 20} - (if platform/android? - {:padding-vertical 8 - :text-align-vertical :top} - {:margin-top 8 - :margin-bottom 8}) - (when (or @recording-audio? (get @reviewing-audio-filepath chat-id)) - {:display :none}))) - -(defn text-input - [{:keys [refs chat-id sending-image on-content-size-change]}] - (let [cooldown-enabled? (rf/sub [:chats/current-chat-cooldown-enabled?]) - timeout-id (reagent/atom nil) - last-text-change (reagent/atom nil) - mentions-enabled? (get @mentions-enabled? chat-id) - props - {:style (text-input-style chat-id) - :ref (:text-input-ref refs) - :max-font-size-multiplier 1 - :accessibility-label :chat-message-input - :text-align-vertical :center - :multiline true - :editable (not cooldown-enabled?) - :blur-on-submit false - :auto-focus false - :max-length chat.constants/max-text-size - :placeholder-text-color (colors/theme-colors colors/neutral-40 colors/white-opa-30) - :placeholder (if cooldown-enabled? - (i18n/label :cooldown/text-input-disabled) - (i18n/label :t/type-a-message)) - :default-value (get @input-texts chat-id) - :underline-color-android :transparent - :auto-capitalize :sentences - :auto-correct false - :spell-check false - :on-content-size-change on-content-size-change - :on-selection-change (partial on-selection-change - timeout-id - last-text-change) - :on-change - (partial on-change last-text-change timeout-id refs chat-id sending-image) - :on-text-input (partial on-text-input chat-id)} - input-with-mentions (rf/sub [:chat/input-with-mentions]) - children (fn [] - (if mentions-enabled? - (map-indexed - (fn [index [mention-type text]] - ^{:key (str index "_" mention-type "_" text)} - [rn/text (when (= mention-type :mention) {:style {:color colors/primary-50}}) - text]) - input-with-mentions) - (get @input-texts chat-id)))] - (reset! text-input-ref (:text-input-ref refs)) - ;when ios implementation for selectable-text-input is ready, we need remove this condition and use - ;selectable-text-input directly. - (if platform/android? - [selectable-text-input chat-id props children] - [rn/text-input props - children]))) - -(declare first-level-menu-items second-level-menu-items) - -(defn update-input-text - [{:keys [text-input chat-id]} text] - (on-text-change text chat-id) - (.setNativeProps ^js text-input (clj->js {:text text}))) - -(re-frame/reg-fx - :set-text-input-value - (fn [[chat-id text local-text-input-ref]] - (when local-text-input-ref - (reset! text-input-ref local-text-input-ref)) - (on-text-change text chat-id) - (if platform/ios? - (.setNativeProps ^js (quo.react/current-ref @text-input-ref) (clj->js {:text text})) - (if (string/blank? text) - (.clear ^js (quo.react/current-ref @text-input-ref)) - (.setNativeProps ^js (quo.react/current-ref @text-input-ref) (clj->js {:text text})))))) - -(defn calculate-input-text - [{:keys [full-text selection-start selection-end]} content] - (let [head (subs full-text 0 selection-start) - tail (subs full-text selection-end)] - (str head content tail))) - -(defn update-selection - [text-input-handle selection-start selection-end] - ;to avoid something disgusting like this - ;https://lightrun.com/answers/facebook-react-native-textinput-controlled-selection-broken-on-both-ios-and-android - ;use native invoke instead! do not use setNativeProps! e.g. (.setNativeProps ^js text-input (clj->js - ;{:selection {:start selection-start :end selection-end}})) - (let [manager (rn/selectable-text-input-manager)] - (oops/ocall manager :setSelection text-input-handle selection-start selection-end))) - -(def first-level-menus - {:cut (fn [{:keys [content] :as params}] - (let [new-text (calculate-input-text params "")] - (clipboard/set-string content) - (update-input-text params new-text))) - - :copy-to-clipboard (fn [{:keys [content]}] - (clipboard/set-string content)) - - :paste (fn [params] - (let [callback (fn [paste-content] - (let [content (string/trim paste-content) - new-text (calculate-input-text params content)] - (update-input-text params new-text)))] - (clipboard/get-string callback))) - - :biu (fn [{:keys [first-level text-input-handle menu-items selection-start - selection-end]}] - (reset! first-level false) - (reset! menu-items second-level-menu-items) - (update-selection text-input-handle selection-start selection-end))}) - -(def first-level-menu-items (map i18n/label (keys first-level-menus))) - -(defn reset-to-first-level-menu - [first-level menu-items] - (reset! first-level true) - (reset! menu-items first-level-menu-items)) - -(defn append-markdown-char - [{:keys [first-level menu-items content selection-start selection-end text-input-handle - selection-event] - :as params} wrap-chars] - (let [content (str wrap-chars content wrap-chars) - new-text (calculate-input-text params content) - len-wrap-chars (count wrap-chars) - selection-start (+ selection-start len-wrap-chars) - selection-end (+ selection-end len-wrap-chars)] - ;don't update selection directly here, process it within on-selection-change instead - ;so that we can avoid java.lang.IndexOutOfBoundsException: setSpan.. - (reset! selection-event {:start selection-start - :end selection-end - :text-input-handle text-input-handle}) - (update-input-text params new-text) - (reset-to-first-level-menu first-level menu-items))) - -(def second-level-menus - {:bold #(append-markdown-char % "**") - - :italic #(append-markdown-char % "*") - - :strikethrough #(append-markdown-char % "~~")}) - -(def second-level-menu-items (map i18n/label (keys second-level-menus))) - -(defn on-menu-item-touched - [{:keys [first-level event-type] :as params}] - (let [menus (if @first-level first-level-menus second-level-menus) - menu-item-key (nth (keys menus) event-type) - action (get menus menu-item-key)] - (action params))) - -(defn selectable-text-input - [_ _ _] - (let [text-input-ref (reagent/atom nil) - menu-items (reagent/atom first-level-menu-items) - first-level (reagent/atom true) - selection-event (atom nil) - manager (rn/selectable-text-input-manager)] - (reagent/create-class - {:component-did-mount - (fn [this] - (when @text-input-ref - (let [selectable-text-input-handle (rn/find-node-handle this) - text-input-handle (rn/find-node-handle @text-input-ref)] - (oops/ocall manager :setupMenuItems selectable-text-input-handle text-input-handle)))) - - :component-did-update (fn [_ _ _ _] - (when (not @first-level) - (let [text-input-handle (rn/find-node-handle @text-input-ref)] - (oops/ocall manager :startActionMode text-input-handle)))) - - :reagent-render - (fn [chat-id {:keys [style ref on-selection-change] :as props} children] - (let [ref #(do (reset! text-input-ref %) - (when ref - (quo.react/set-ref-val! ref %))) - on-selection-change (fn [args] - (let [selection (.-selection ^js (.-nativeEvent ^js args)) - start (.-start selection) - end (.-end selection) - no-selection (<= (- end start) 0)] - (when (and no-selection (not @first-level)) - (oops/ocall manager :hideLastActionMode) - (reset-to-first-level-menu first-level menu-items))) - (when on-selection-change - (on-selection-change args)) - (when @selection-event - (let [{:keys [start end text-input-handle]} @selection-event] - (update-selection text-input-handle start end) - (reset! selection-event nil)))) - on-selection - (fn [^js event] - (let [native-event (.-nativeEvent event) - native-event (transforms/js->clj native-event) - {:keys [eventType content selectionStart - selectionEnd]} native-event - full-text (:input-text (rf/sub [:chats/current-chat-input]))] - (on-menu-item-touched {:first-level first-level - :event-type eventType - :content content - :selection-start selectionStart - :selection-end selectionEnd - :text-input @text-input-ref - :text-input-handle (rn/find-node-handle @text-input-ref) - :full-text full-text - :menu-items menu-items - :chat-id chat-id - :selection-event selection-event}))) - props (merge props - {:ref ref - :style (dissoc style :margin-horizontal) - :on-selection-change on-selection-change - :on-selection on-selection})] - [rn/selectable-text-input {:menuItems @menu-items :style style} - [rn/text-input props - children]]))}))) diff --git a/src/status_im/ui2/screens/chat/composer/mentions.cljs b/src/status_im/ui2/screens/chat/composer/mentions.cljs deleted file mode 100644 index c16336dc21..0000000000 --- a/src/status_im/ui2/screens/chat/composer/mentions.cljs +++ /dev/null @@ -1,2 +0,0 @@ -(ns status-im.ui2.screens.chat.composer.mentions) - diff --git a/src/status_im/ui2/screens/chat/composer/reply.cljs b/src/status_im/ui2/screens/chat/composer/reply.cljs deleted file mode 100644 index 3e16d64d9e..0000000000 --- a/src/status_im/ui2/screens/chat/composer/reply.cljs +++ /dev/null @@ -1,37 +0,0 @@ -(ns status-im.ui2.screens.chat.composer.reply - (:require [quo.react-native :as rn] - [status-im.ui2.screens.chat.components.reply.view :as reply] - [status-im.ui2.screens.chat.composer.input :as input])) - -(defn focus-input-on-reply - [reply had-reply text-input-ref] - ;;when we show reply we focus input - (when-not (= reply @had-reply) - (reset! had-reply reply) - (when reply - ;; A setTimeout of 0 is necessary to ensure the statement is enqueued and will get executed ASAP. - (js/setTimeout #(input/input-focus text-input-ref) 0)))) - -(defn reply-message-auto-focus-wrapper - [text-input-ref _] - (let [had-reply (atom nil)] - (fn [_ reply] - (focus-input-on-reply reply had-reply text-input-ref) - (when reply - [rn/view - {:style (merge - {:padding-horizontal 15 - :padding-vertical 8} - (when @input/recording-audio? - {:position :absolute - :top 12 - :left 0 - :right 0 - ;;When recording an audio and replying at the same time, - ;;text input is overlapped by the reply component but - ;;text input still have priority over touches, so we need - ;;to force the reply component to receive the touches in this - ;;scenario, thus we increase its z-index - :z-index 1}))} - [reply/reply-message reply true false - (and @input/recording-audio? (not @input/reviewing-audio?))]])))) diff --git a/src/status_im/ui2/screens/chat/messages/message.cljs b/src/status_im/ui2/screens/chat/messages/message.cljs index 3fc65ed5d1..c1b362b74c 100644 --- a/src/status_im/ui2/screens/chat/messages/message.cljs +++ b/src/status_im/ui2/screens/chat/messages/message.cljs @@ -18,7 +18,6 @@ [status-im.ui.screens.chat.styles.message.message :as style] [status-im.ui.screens.chat.utils :as chat.utils] [status-im.ui.screens.communities.icon :as communities.icon] - [status-im.ui2.screens.chat.components.reply.view :as components.reply] [status-im.utils.utils :as utils] [status-im2.constants :as constants] [status-im2.contexts.chat.home.chat-list-item.view :as home.chat-list-item] @@ -144,9 +143,8 @@ (:parsed-text content)))) (defn quoted-message - [quoted-message pin?] - [rn/view {:style (when-not pin? (style/quoted-message-container))} - [components.reply/reply-message quoted-message false pin?]]) + [_ pin?] + [rn/view {:style (when-not pin? (style/quoted-message-container))}]) (defn message-not-sent-text [chat-id message-id] diff --git a/src/status_im2/config.cljs b/src/status_im2/config.cljs index c4ae6a4088..5ee9c3aedb 100644 --- a/src/status_im2/config.cljs +++ b/src/status_im2/config.cljs @@ -158,5 +158,3 @@ ["enrtree://AOGECG2SPND25EEFMAJ5WF3KSGJNSGV356DSTL2YVLLZWIV6SAYBM@test.waku.nodes.status.im"]}) (def default-kdf-iterations 3200) - -(def ^:const new-composer-enabled? false) diff --git a/src/status_im2/constants.cljs b/src/status_im2/constants.cljs index 23f07dc890..1a04df1cb4 100644 --- a/src/status_im2/constants.cljs +++ b/src/status_im2/constants.cljs @@ -263,7 +263,6 @@ 2 5000 3 10000}) -(def ^:const max-text-size 4096) ;; any message that comes after this amount of ms will be grouped separately (def ^:const group-ms 300000) diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/gradients/view.cljs b/src/status_im2/contexts/chat/bottom_sheet_composer/gradients/view.cljs deleted file mode 100644 index 5a61debef8..0000000000 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/gradients/view.cljs +++ /dev/null @@ -1,23 +0,0 @@ -(ns status-im2.contexts.chat.bottom-sheet-composer.gradients.view - (:require - [react-native.core :as rn] - [react-native.linear-gradient :as linear-gradient] - [react-native.reanimated :as reanimated] - [status-im2.contexts.chat.bottom-sheet-composer.gradients.style :as style])) - - -(defn view - [{:keys [input-ref]} - {:keys [gradient-z-index]} - {:keys [gradient-opacity]} - show-bottom-gradient?] - [:f> - (fn [] - [:<> - [reanimated/linear-gradient (style/top-gradient gradient-opacity @gradient-z-index)] - (when show-bottom-gradient? - [rn/touchable-without-feedback - {:on-press #(when @input-ref (.focus ^js @input-ref)) - :accessibility-label :bottom-gradient} - [linear-gradient/linear-gradient (style/bottom-gradient)]])])]) - diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/images/view.cljs b/src/status_im2/contexts/chat/bottom_sheet_composer/images/view.cljs deleted file mode 100644 index 0b6773fcc5..0000000000 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/images/view.cljs +++ /dev/null @@ -1,41 +0,0 @@ -(ns status-im2.contexts.chat.bottom-sheet-composer.images.view - (:require [quo2.core :as quo] - [quo2.foundations.colors :as colors] - [react-native.core :as rn] - [react-native.reanimated :as reanimated] - [status-im2.contexts.chat.bottom-sheet-composer.images.style :as style] - [utils.re-frame :as rf] - [status-im2.contexts.chat.bottom-sheet-composer.constants :as constants])) - -(defn image - [item] - [rn/view style/image-container - [rn/image - {:source {:uri (:resized-uri (val item))} - :style style/small-image}] - [rn/touchable-opacity - {:on-press #(rf/dispatch [:chat.ui/image-unselected (val item)]) - :style style/remove-photo-container - :hit-slop {:right 5 - :left 5 - :top 10 - :bottom 10}} - [quo/icon :i/close {:color colors/white :size 12}]]]) - -(defn images-list - [] - [:f> - (fn [] - (let [images (rf/sub [:chats/sending-image]) - height (reanimated/use-shared-value (if (seq images) constants/images-container-height 0))] - (rn/use-effect (fn [] - (reanimated/animate height - (if (seq images) constants/images-container-height 0))) - [images]) - [reanimated/view {:style (reanimated/apply-animations-to-style {:height height} {})} - [rn/flat-list - {:key-fn first - :render-fn image - :data images - :horizontal true - :keyboard-should-persist-taps :handled}]]))]) diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/sub_view.cljs b/src/status_im2/contexts/chat/bottom_sheet_composer/sub_view.cljs deleted file mode 100644 index c6736f2210..0000000000 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/sub_view.cljs +++ /dev/null @@ -1,18 +0,0 @@ -(ns status-im2.contexts.chat.bottom-sheet-composer.sub-view - (:require - [react-native.blur :as blur] - [react-native.core :as rn] - [react-native.reanimated :as reanimated] - [status-im2.contexts.chat.bottom-sheet-composer.style :as style])) - -(defn bar - [] - [rn/view {:style style/bar-container} - [rn/view {:style (style/bar)}]]) - -(defn blur-view - [layout-height] - [:f> - (fn [] - [reanimated/view {:style (style/blur-container layout-height)} - [blur/view (style/blur-view)]])]) diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/view.cljs b/src/status_im2/contexts/chat/bottom_sheet_composer/view.cljs deleted file mode 100644 index d05722a7b3..0000000000 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/view.cljs +++ /dev/null @@ -1,172 +0,0 @@ -(ns status-im2.contexts.chat.bottom-sheet-composer.view - (:require - [quo2.foundations.colors :as colors] - [react-native.core :as rn] - [react-native.gesture :as gesture] - [react-native.hooks :as hooks] - [react-native.reanimated :as reanimated] - [reagent.core :as reagent] - [utils.i18n :as i18n] - [utils.re-frame :as rf] - [status-im2.contexts.chat.bottom-sheet-composer.style :as style] - [status-im2.contexts.chat.bottom-sheet-composer.images.view :as images] - [status-im2.contexts.chat.bottom-sheet-composer.reply.view :as reply] - [status-im2.contexts.chat.bottom-sheet-composer.edit.view :as edit] - [status-im2.contexts.chat.bottom-sheet-composer.mentions.view :as mentions] - [status-im2.contexts.chat.bottom-sheet-composer.utils :as utils] - [status-im2.contexts.chat.bottom-sheet-composer.constants :as constants] - [status-im2.contexts.chat.bottom-sheet-composer.actions.view :as actions] - [status-im2.contexts.chat.bottom-sheet-composer.keyboard :as kb] - [status-im2.contexts.chat.bottom-sheet-composer.sub-view :as sub-view] - [status-im2.contexts.chat.bottom-sheet-composer.effects :as effects] - [status-im2.contexts.chat.bottom-sheet-composer.gesture :as drag-gesture] - [status-im2.contexts.chat.bottom-sheet-composer.handlers :as handler] - [status-im2.contexts.chat.bottom-sheet-composer.gradients.view :as gradients])) - -(defn sheet - [insets window-height blur-height opacity background-y] - [:f> - (fn [] - (let [props {: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) - :replying? (atom nil) - :sending-images? (atom nil) - :editing? (atom nil) - :record-permission? (atom nil) - :record-reset-fn (atom nil) - :scroll-y (atom 0)} - 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 nil) - :gesture-enabled? (reagent/atom true) - :lock-selection? (reagent/atom true) - :focused? (reagent/atom false) - :lock-layout? (reagent/atom false) - :maximized? (reagent/atom false) - :recording? (reagent/atom false)}] - [:f> - (fn [] - (let [images (rf/sub [:chats/sending-image]) - audio (rf/sub [:chats/sending-audio]) - reply (rf/sub [:chats/reply-message]) - edit (rf/sub [:chats/edit-message]) - {:keys [input-text input-content-height] - :as chat-input} (rf/sub [:chats/current-chat-input]) - content-height (reagent/atom (or input-content-height - constants/input-height)) - {:keys [keyboard-shown keyboard-height]} (hooks/use-keyboard) - kb-height (kb/get-kb-height keyboard-height - @(:kb-default-height state)) - max-height (utils/calc-max-height window-height - kb-height - insets - (boolean (seq images)) - reply - edit) - lines (utils/calc-lines @content-height) - max-lines (utils/calc-lines max-height) - initial-height (if (> lines 1) - constants/multiline-minimized-height - constants/input-height) - animations {:gradient-opacity (reanimated/use-shared-value - 0) - :container-opacity (reanimated/use-shared-value - (if (utils/empty-input? - input-text - images - reply - audio) - 0.7 - 1)) - :height (reanimated/use-shared-value - initial-height) - :saved-height (reanimated/use-shared-value - initial-height) - :last-height (reanimated/use-shared-value - (utils/bounded-val - @content-height - constants/input-height - max-height)) - :opacity opacity - :background-y background-y} - 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-pos (utils/cursor-y-position-relative-to-container - props - state)] - (effects/initialize props - state - animations - dimensions - chat-input - keyboard-height - (boolean (seq images)) - reply - edit - audio) - (utils/update-input props state input-text) - [:<> - [mentions/view props state animations max-height cursor-pos] - [gesture/gesture-detector - {:gesture (drag-gesture/drag-gesture props state animations dimensions keyboard-shown)} - [reanimated/view - {:style (style/sheet-container insets state animations) - :on-layout #(handler/layout % state blur-height)} - [sub-view/bar] - [reply/view state] - [edit/view edit #(utils/cancel-edit-message state animations)] - [reanimated/touchable-opacity - {:active-opacity 1 - :on-press (when @(:input-ref props) #(.focus ^js @(:input-ref props))) - :style (style/input-container (:height animations) max-height) - :accessibility-label :message-input-container} - [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 images reply) - :on-content-size-change #(handler/content-size-change % - state - animations - dimensions - (or keyboard-shown edit)) - :on-scroll #(handler/scroll % props state animations dimensions) - :on-change-text #(handler/change-text % props state) - :on-selection-change #(handler/selection-change % state) - :max-height max-height - :max-font-size-multiplier 1 - :multiline true - :placeholder (i18n/label :t/type-something) - :placeholder-text-color (colors/theme-colors colors/neutral-40 colors/neutral-50) - :style (style/input props state) - :accessibility-label :chat-message-input}] - [gradients/view props state animations show-bottom-gradient?]] - [images/images-list] - [actions/view props state animations window-height insets - (boolean (seq images))]]]]))]))]) - -(defn f-bottom-sheet-composer - [insets] - (let [window-height (rf/sub [:dimensions/window-height]) - opacity (reanimated/use-shared-value 0) - background-y (reanimated/use-shared-value (- window-height)) - blur-height (reanimated/use-shared-value (+ constants/composer-default-height - (:bottom insets)))] - [rn/view - [reanimated/view {:style (style/background opacity background-y window-height)}] - [sub-view/blur-view blur-height] - [sheet insets window-height blur-height opacity background-y]])) - -(defn bottom-sheet-composer - [insets] - [:f> f-bottom-sheet-composer insets]) diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/actions/style.cljs b/src/status_im2/contexts/chat/composer/actions/style.cljs similarity index 86% rename from src/status_im2/contexts/chat/bottom_sheet_composer/actions/style.cljs rename to src/status_im2/contexts/chat/composer/actions/style.cljs index 3c0698fcd3..41f32efed1 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/actions/style.cljs +++ b/src/status_im2/contexts/chat/composer/actions/style.cljs @@ -1,8 +1,8 @@ -(ns status-im2.contexts.chat.bottom-sheet-composer.actions.style +(ns status-im2.contexts.chat.composer.actions.style (:require [quo2.foundations.colors :as colors] [react-native.reanimated :as reanimated] - [status-im2.contexts.chat.bottom-sheet-composer.constants :as constants])) + [status-im2.contexts.chat.composer.constants :as constants])) (def actions-container {:height constants/actions-container-height diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/actions/view.cljs b/src/status_im2/contexts/chat/composer/actions/view.cljs similarity index 75% rename from src/status_im2/contexts/chat/bottom_sheet_composer/actions/view.cljs rename to src/status_im2/contexts/chat/composer/actions/view.cljs index d065638d9c..e5c3c3d67f 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/actions/view.cljs +++ b/src/status_im2/contexts/chat/composer/actions/view.cljs @@ -1,4 +1,4 @@ -(ns status-im2.contexts.chat.bottom-sheet-composer.actions.view +(ns status-im2.contexts.chat.composer.actions.view (:require [quo2.core :as quo] [react-native.core :as rn] @@ -7,11 +7,11 @@ [react-native.reanimated :as reanimated] [reagent.core :as reagent] [status-im2.common.alert.events :as alert] - [status-im2.contexts.chat.bottom-sheet-composer.constants :as constants] + [status-im2.contexts.chat.composer.constants :as constants] [status-im2.contexts.chat.messages.list.view :as messages.list] [utils.i18n :as i18n] [utils.re-frame :as rf] - [status-im2.contexts.chat.bottom-sheet-composer.actions.style :as style])) + [status-im2.contexts.chat.composer.actions.style :as style])) (defn send-message [{:keys [text-value focused? maximized?]} @@ -34,45 +34,45 @@ (reset! text-value "") (messages.list/scroll-to-bottom)) -(defn send-button +(defn f-send-button [{:keys [text-value] :as state} - animations - window-height - images?] - [:f> - (fn [] - (let [btn-opacity (reanimated/use-shared-value 0) - z-index (reagent/atom 0)] - [:f> - (fn [] - (rn/use-effect (fn [] - (if (or (not-empty @text-value) images?) - (when-not (= @z-index 1) - (reset! z-index 1) - (js/setTimeout #(reanimated/animate btn-opacity 1) 50)) - (when-not (= @z-index 0) - (reanimated/animate btn-opacity 0) - (js/setTimeout #(reset! z-index 0) 300)))) - [(and (empty? @text-value) (not images?))]) - [reanimated/view - {:style (style/send-button btn-opacity @z-index)} - [quo/button - {:icon true - :size 32 - :accessibility-label :send-message-button - :on-press #(send-message state animations window-height)} - :i/arrow-up]])]))]) + animations window-height images? + btn-opacity z-index] + (rn/use-effect (fn [] + (if (or (seq @text-value) images?) + (when (or (not= @z-index 1) (not= (reanimated/get-shared-value btn-opacity) 1)) + (reset! z-index 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?))]) + [reanimated/view + {:style (style/send-button btn-opacity @z-index)} + [quo/button + {:icon true + :size 32 + :accessibility-label :send-message-button + :on-press #(send-message state animations window-height)} + :i/arrow-up]]) + +(defn send-button + [state animations window-height images?] + (let [btn-opacity (reanimated/use-shared-value 0) + z-index (reagent/atom 0)] + [:f> f-send-button state animations window-height images? btn-opacity z-index])) (defn audio-button - [{:keys [record-permission? record-reset-fn]} - {:keys [recording? gesture-enabled?]} + [{:keys [record-reset-fn]} + {:keys [record-permission? recording? gesture-enabled? focused?]} {:keys [container-opacity]}] (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? + {:record-audio-permission-granted @record-permission? :on-init (fn [reset-fn] (reset! record-reset-fn reset-fn)) :on-start-recording (fn [] @@ -86,15 +86,17 @@ (reset! recording? false) (reset! gesture-enabled? true) (rf/dispatch [:chat/send-audio file-path duration]) - (reanimated/animate container-opacity - constants/empty-opacity) + (when-not @focused? + (reanimated/animate container-opacity + constants/empty-opacity)) (rf/dispatch [:chat.ui/set-input-audio nil])) :on-cancel (fn [] (when @recording? (reset! recording? false) (reset! gesture-enabled? true) - (reanimated/animate container-opacity - constants/empty-opacity) + (when-not @focused? + (reanimated/animate container-opacity + constants/empty-opacity)) (rf/dispatch [:chat.ui/set-input-audio nil]))) :on-check-audio-permissions (fn [] (permissions/permission-granted? @@ -174,7 +176,7 @@ :i/format]) (defn view - [props state animations window-height insets images? audio] + [props state animations window-height insets edit? images?] [rn/view {:style style/actions-container} [rn/view {:style {:flex-direction :row @@ -183,5 +185,6 @@ [image-button props animations insets] [reaction-button] [format-button]] - [send-button state animations window-height images?] - [audio-button props state animations audio]]) + [:f> send-button state animations window-height images?] + (when (and (not edit?) (not images?)) + [audio-button props state animations])]) diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/constants.cljs b/src/status_im2/contexts/chat/composer/constants.cljs similarity index 67% rename from src/status_im2/contexts/chat/bottom_sheet_composer/constants.cljs rename to src/status_im2/contexts/chat/composer/constants.cljs index a25f144fa4..c9e482a7c2 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/constants.cljs +++ b/src/status_im2/contexts/chat/composer/constants.cljs @@ -1,17 +1,19 @@ -(ns status-im2.contexts.chat.bottom-sheet-composer.constants +(ns status-im2.contexts.chat.composer.constants (:require [quo2.foundations.typography :as typography] [react-native.platform :as platform])) (def ^:const bar-container-height 20) -(def ^:const input-height (if platform/ios? 32 42)) +(def ^:const input-height 32) (def ^:const actions-container-height 56) (def ^:const composer-default-height (+ bar-container-height input-height actions-container-height)) -(def ^:const multiline-minimized-height (+ input-height 18)) +(def ^:const line-height (:line-height typography/paragraph-1)) + +(def ^:const multiline-minimized-height (+ input-height (if platform/ios? 18 line-height))) (def ^:const empty-opacity 0.7) @@ -23,14 +25,14 @@ (def ^:const mentions-max-height 240) -(def ^:const extra-content-offset (if platform/ios? 6 0)) +(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 -1000) +(def ^:const velocity-threshold (if platform/ios? -1000 -500)) (def ^:const background-threshold 0.75) -(def ^:const line-height (:line-height typography/paragraph-1)) +(def ^:const max-text-size 4096) diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/edit/style.cljs b/src/status_im2/contexts/chat/composer/edit/style.cljs similarity index 86% rename from src/status_im2/contexts/chat/bottom_sheet_composer/edit/style.cljs rename to src/status_im2/contexts/chat/composer/edit/style.cljs index 8e89a12a69..c5c2f5f12e 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/edit/style.cljs +++ b/src/status_im2/contexts/chat/composer/edit/style.cljs @@ -1,4 +1,4 @@ -(ns status-im2.contexts.chat.bottom-sheet-composer.edit.style) +(ns status-im2.contexts.chat.composer.edit.style) (def container {:flex-direction :row diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/edit/view.cljs b/src/status_im2/contexts/chat/composer/edit/view.cljs similarity index 71% rename from src/status_im2/contexts/chat/bottom_sheet_composer/edit/view.cljs rename to src/status_im2/contexts/chat/composer/edit/view.cljs index 5c0818ce3b..86b34048e4 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/edit/view.cljs +++ b/src/status_im2/contexts/chat/composer/edit/view.cljs @@ -1,16 +1,17 @@ -(ns status-im2.contexts.chat.bottom-sheet-composer.edit.view +(ns status-im2.contexts.chat.composer.edit.view (:require [react-native.reanimated :as reanimated] - [status-im2.contexts.chat.bottom-sheet-composer.constants :as constants] + [status-im2.contexts.chat.composer.constants :as constants] + [status-im2.contexts.chat.composer.utils :as utils] [utils.i18n :as i18n] [quo2.core :as quo] [quo2.foundations.colors :as colors] - [re-frame.core :as rf] [react-native.core :as rn] - [status-im2.contexts.chat.bottom-sheet-composer.edit.style :as style])) + [status-im2.contexts.chat.composer.edit.style :as style] + [utils.re-frame :as rf])) (defn edit-message - [cancel] + [state] [rn/view {:style style/container :accessibility-label :edit-message} @@ -30,7 +31,7 @@ :size 24 :accessibility-label :edit-cancel-button :on-press (fn [] - (cancel) + (utils/cancel-edit-message state) (rf/dispatch [:chat.ui/cancel-message-edit])) :type :outline} [quo/icon :i/close @@ -38,12 +39,13 @@ :color (colors/theme-colors colors/neutral-100 colors/neutral-40)}]]]) (defn- f-view - [edit cancel] - (let [height (reanimated/use-shared-value (if edit constants/edit-container-height 0))] + [state] + (let [edit (rf/sub [:chats/edit-message]) + height (reanimated/use-shared-value (if edit constants/edit-container-height 0))] (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 cancel])])) + (when edit [edit-message state])])) (defn view - [edit cancel] - [:f> f-view edit cancel]) + [state] + [:f> f-view state]) diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/effects.cljs b/src/status_im2/contexts/chat/composer/effects.cljs similarity index 51% rename from src/status_im2/contexts/chat/bottom_sheet_composer/effects.cljs rename to src/status_im2/contexts/chat/composer/effects.cljs index ec982b6ef8..4628fedf32 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/effects.cljs +++ b/src/status_im2/contexts/chat/composer/effects.cljs @@ -1,11 +1,14 @@ -(ns status-im2.contexts.chat.bottom-sheet-composer.effects +(ns status-im2.contexts.chat.composer.effects (:require + [react-native.platform :as platform] [status-im.async-storage.core :as async-storage] [react-native.core :as rn] [react-native.reanimated :as reanimated] - [status-im2.contexts.chat.bottom-sheet-composer.constants :as constants] - [status-im2.contexts.chat.bottom-sheet-composer.keyboard :as kb] - [utils.number :as utils.number])) + [status-im2.contexts.chat.composer.constants :as constants] + [status-im2.contexts.chat.composer.keyboard :as kb] + [utils.number :as utils.number] + [oops.core :as oops] + [utils.re-frame :as rf])) (defn reenter-screen-effect [{:keys [text-value saved-cursor-position maximized?]} @@ -48,35 +51,17 @@ (reanimated/set-shared-value background-y 0) (reanimated/animate opacity 1))) -(defn images-or-reply-effect - [{:keys [container-opacity]} - {:keys [replying? sending-images? input-ref]} - images? reply?] - (when (or images? reply?) +(defn images-effect + [{:keys [sending-images? input-ref]} + {:keys [container-opacity]} + images?] + (when images? (reanimated/animate container-opacity 1)) (when (and (not @sending-images?) images? @input-ref) (.focus ^js @input-ref) (reset! sending-images? true)) - (when (and (not @replying?) reply? @input-ref) - (.focus ^js @input-ref) - (reset! replying? true)) (when-not images? - (reset! sending-images? false)) - (when-not reply? - (reset! replying? false))) - -(defn edit-effect - [{:keys [text-value saved-cursor-position]} - {:keys [editing? input-ref]} - edit] - (let [edit-text (get-in edit [:content :text])] - (when (and (not @editing?) edit @input-ref) - (.focus ^js @input-ref) - (reset! editing? true) - (reset! text-value edit-text) - (reset! saved-cursor-position (count edit-text))) - (when-not edit-text - (reset! editing? false)))) + (reset! sending-images? false))) (defn audio-effect [{:keys [recording? gesture-enabled?]} @@ -105,7 +90,7 @@ (defn initialize [props state animations {:keys [max-height] :as dimensions} chat-input keyboard-height images? reply? - edit audio] + audio] (rn/use-effect (fn [] (maximized-effect state animations dimensions chat-input) @@ -113,10 +98,77 @@ (layout-effect state) (kb-default-height-effect state) (background-effect state animations dimensions chat-input) - (images-or-reply-effect animations props images? reply?) - (edit-effect state props edit) + (images-effect props animations images?) (audio-effect state animations audio) (empty-effect state animations images? reply? audio) (kb/add-kb-listeners props state animations dimensions keyboard-height) #(component-will-unmount props)) [max-height])) + +(defn edit + [{:keys [input-ref]} + {:keys [text-value saved-cursor-position]} + edit] + (rn/use-effect + (fn [] + (let [edit-text (get-in edit [:content :text])] + (when (and edit @input-ref) + (.focus ^js @input-ref) + (.setNativeProps ^js @input-ref (clj->js {:text edit-text})) + (reset! text-value edit-text) + (reset! saved-cursor-position (count edit-text))))) + [(:message-id edit)])) + +(defn reply + [{:keys [input-ref]} + {:keys [container-opacity]} + reply] + (rn/use-effect + (fn [] + (when reply + (reanimated/animate container-opacity 1)) + (when (and reply @input-ref) + (.focus ^js @input-ref))) + [(:message-id reply)])) + +(defn edit-mentions + [{:keys [input-ref]} {:keys [text-value cursor-position]} input-with-mentions] + (rn/use-effect (fn [] + (let [input-text (reduce (fn [acc item] + (str acc (second item))) + "" + input-with-mentions)] + (reset! text-value input-text) + (reset! cursor-position (count input-text)) + (js/setTimeout #(when @input-ref + (.setNativeProps ^js @input-ref + (clj->js {:selection {:start (count input-text) + :end (count + input-text)}}))) + 300))) + [(some #(= :mention (first %)) (seq input-with-mentions))])) + +(defn update-input-mention + [{:keys [input-ref]} + {:keys [text-value]} + 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 did-mount + [{:keys [selectable-input-ref input-ref selection-manager]}] + (rn/use-effect + (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_im2/contexts/chat/bottom_sheet_composer/gesture.cljs b/src/status_im2/contexts/chat/composer/gesture.cljs similarity index 75% rename from src/status_im2/contexts/chat/bottom_sheet_composer/gesture.cljs rename to src/status_im2/contexts/chat/composer/gesture.cljs index 05b1a047ce..747d1527cd 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/gesture.cljs +++ b/src/status_im2/contexts/chat/composer/gesture.cljs @@ -1,10 +1,10 @@ -(ns status-im2.contexts.chat.bottom-sheet-composer.gesture +(ns status-im2.contexts.chat.composer.gesture (:require [react-native.gesture :as gesture] [react-native.reanimated :as reanimated] [oops.core :as oops] - [status-im2.contexts.chat.bottom-sheet-composer.constants :as constants] - [status-im2.contexts.chat.bottom-sheet-composer.utils :as utils] + [status-im2.contexts.chat.composer.constants :as constants] + [status-im2.contexts.chat.composer.utils :as utils] [utils.re-frame :as rf])) (defn set-opacity @@ -35,10 +35,12 @@ (rf/dispatch [:chat.ui/set-input-maximized true])) (defn minimize - [{:keys [input-ref emoji-kb-extra-height saved-emoji-kb-extra-height]}] + [{: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]) (when @input-ref (.blur ^js @input-ref))) @@ -74,21 +76,26 @@ (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) - new-height (utils/bounded-val new-height min-height max-height)] - (when keyboard-shown - (reanimated/set-shared-value height new-height) - (set-opacity (oops/oget event "velocityY") - opacity - translation - @expanding? - min-height - max-height - new-height - saved-height))))) + (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/bounded-val 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)) + (when @input-ref ; sheet at min-height, collapse keyboard + (.blur ^js @input-ref))))))) (gesture/on-end (fn [] (let [diff (- (reanimated/get-shared-value height) (reanimated/get-shared-value saved-height))] @@ -98,6 +105,6 @@ (maximize state animations dimensions) (bounce-back animations dimensions starting-opacity)) (if (> (Math/abs diff) constants/drag-threshold) - (minimize props) + (minimize props state) (bounce-back animations dimensions starting-opacity))) (reset! gesture-enabled? true)))))))) diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/gradients/style.cljs b/src/status_im2/contexts/chat/composer/gradients/style.cljs similarity index 84% rename from src/status_im2/contexts/chat/bottom_sheet_composer/gradients/style.cljs rename to src/status_im2/contexts/chat/composer/gradients/style.cljs index 0bcdfe5690..5c1d543a3b 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/gradients/style.cljs +++ b/src/status_im2/contexts/chat/composer/gradients/style.cljs @@ -1,8 +1,7 @@ -(ns status-im2.contexts.chat.bottom-sheet-composer.gradients.style +(ns status-im2.contexts.chat.composer.gradients.style (:require [quo2.foundations.colors :as colors] [quo2.foundations.typography :as typography] - [react-native.platform :as platform] [react-native.reanimated :as reanimated])) (defn top-gradient-style @@ -25,7 +24,7 @@ :style (top-gradient-style opacity z-index)}) (def bottom-gradient-style - {:height (if platform/ios? (:line-height typography/paragraph-1) 32) + {:height (:line-height typography/paragraph-1) :position :absolute :bottom 0 :left 0 @@ -39,4 +38,3 @@ :start {:x 0 :y 1} :end {:x 0 :y 0} :style bottom-gradient-style}) - diff --git a/src/status_im2/contexts/chat/composer/gradients/view.cljs b/src/status_im2/contexts/chat/composer/gradients/view.cljs new file mode 100644 index 0000000000..383956ed85 --- /dev/null +++ b/src/status_im2/contexts/chat/composer/gradients/view.cljs @@ -0,0 +1,24 @@ +(ns status-im2.contexts.chat.composer.gradients.view + (:require + [react-native.core :as rn] + [react-native.linear-gradient :as linear-gradient] + [react-native.reanimated :as reanimated] + [status-im2.contexts.chat.composer.gradients.style :as style])) + + +(defn f-view + [{:keys [input-ref]} + {:keys [gradient-z-index]} + {:keys [gradient-opacity]} + show-bottom-gradient?] + [:<> + [reanimated/linear-gradient (style/top-gradient gradient-opacity @gradient-z-index)] + (when show-bottom-gradient? + [rn/touchable-without-feedback + {:on-press #(when @input-ref (.focus ^js @input-ref)) + :accessibility-label :bottom-gradient} + [linear-gradient/linear-gradient (style/bottom-gradient)]])]) +(defn view + [props state animations show-bottom-gradient?] + [:f> f-view props state animations show-bottom-gradient?]) + diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/handlers.cljs b/src/status_im2/contexts/chat/composer/handlers.cljs similarity index 77% rename from src/status_im2/contexts/chat/bottom_sheet_composer/handlers.cljs rename to src/status_im2/contexts/chat/composer/handlers.cljs index 36b4f616f5..f696d0c51f 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/handlers.cljs +++ b/src/status_im2/contexts/chat/composer/handlers.cljs @@ -1,11 +1,13 @@ -(ns status-im2.contexts.chat.bottom-sheet-composer.handlers +(ns status-im2.contexts.chat.composer.handlers (:require + [react-native.core :as rn] [react-native.reanimated :as reanimated] [reagent.core :as reagent] [oops.core :as oops] - [status-im2.contexts.chat.bottom-sheet-composer.constants :as constants] - [status-im2.contexts.chat.bottom-sheet-composer.keyboard :as kb] - [status-im2.contexts.chat.bottom-sheet-composer.utils :as utils] + [status-im2.contexts.chat.composer.constants :as constants] + [status-im2.contexts.chat.composer.keyboard :as kb] + [status-im2.contexts.chat.composer.utils :as utils] + [status-im2.contexts.chat.composer.selection :as selection] [utils.re-frame :as rf])) (defn focus @@ -54,7 +56,8 @@ (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)))) + (reset! maximized? false) + (rf/dispatch [:chat.ui/set-input-maximized false])))) (defn content-size-change [event @@ -113,13 +116,26 @@ (rf/dispatch [:mention/on-change-text text])) (defn selection-change - [event {:keys [lock-selection? cursor-position]}] - (when-not @lock-selection? - (reset! cursor-position (oops/oget event "nativeEvent.selection.end")))) + [event + {:keys [input-ref selection-event selection-manager]} + {:keys [lock-selection? cursor-position first-level? menu-items]}] + (let [start (oops/oget event "nativeEvent.selection.start") + end (oops/oget event "nativeEvent.selection.end") + selection? (not= start end) + text-input-handle (rn/find-node-handle @input-ref)] + (when-not @lock-selection? + (reset! cursor-position end)) + (when (and selection? (not @first-level?)) + (js/setTimeout #(oops/ocall selection-manager :startActionMode text-input-handle) 500)) + (when (and (not selection?) (not @first-level?)) + (oops/ocall selection-manager :hideLastActionMode) + (selection/reset-to-first-level-menu first-level? menu-items)) + (when @selection-event + (let [{:keys [start end text-input-handle]} @selection-event] + (selection/update-selection text-input-handle start end) + (reset! selection-event nil))))) (defn layout - [event - {:keys [lock-layout?]} - blur-height] - (when (utils/update-blur-height? event lock-layout? blur-height) + [event state blur-height] + (when (utils/update-blur-height? event state blur-height) (reanimated/set-shared-value blur-height (oops/oget event "nativeEvent.layout.height")))) diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/images/style.cljs b/src/status_im2/contexts/chat/composer/images/style.cljs similarity index 88% rename from src/status_im2/contexts/chat/bottom_sheet_composer/images/style.cljs rename to src/status_im2/contexts/chat/composer/images/style.cljs index 5859347eed..cb5e13e60a 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/images/style.cljs +++ b/src/status_im2/contexts/chat/composer/images/style.cljs @@ -1,4 +1,4 @@ -(ns status-im2.contexts.chat.bottom-sheet-composer.images.style +(ns status-im2.contexts.chat.composer.images.style (:require [quo2.foundations.colors :as colors])) (def image-container diff --git a/src/status_im2/contexts/chat/composer/images/view.cljs b/src/status_im2/contexts/chat/composer/images/view.cljs new file mode 100644 index 0000000000..89e3a90c6f --- /dev/null +++ b/src/status_im2/contexts/chat/composer/images/view.cljs @@ -0,0 +1,47 @@ +(ns status-im2.contexts.chat.composer.images.view + (:require [quo2.core :as quo] + [quo2.foundations.colors :as colors] + [react-native.core :as rn] + [react-native.gesture :as gesture] + [react-native.reanimated :as reanimated] + [status-im2.contexts.chat.composer.images.style :as style] + [utils.re-frame :as rf] + [status-im2.contexts.chat.composer.constants :as constants])) + +(defn image + [item] + [rn/view style/image-container + [rn/image + {:source {:uri (:resized-uri (val item))} + :style style/small-image}] + [rn/touchable-opacity + {:on-press #(rf/dispatch [:chat.ui/image-unselected (val item)]) + :style style/remove-photo-container + :hit-slop {:right 5 + :left 5 + :top 10 + :bottom 10}} + [quo/icon :i/close {:color colors/white :size 12}]]]) + +(defn f-images-list + [] + (let [images (rf/sub [:chats/sending-image]) + height (reanimated/use-shared-value (if (seq images) constants/images-container-height 0))] + (rn/use-effect (fn [] + (reanimated/animate height + (if (seq images) constants/images-container-height 0))) + [images]) + [reanimated/view + {:style (reanimated/apply-animations-to-style {:height height} {:margin-horizontal -20})} + [gesture/flat-list + {:key-fn first + :render-fn image + :data images + :content-container-style {:padding-horizontal 20} + :horizontal true + :shows-horizontal-scroll-indicator false + :keyboard-should-persist-taps :handled}]])) + +(defn images-list + [] + [:f> f-images-list]) diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/keyboard.cljs b/src/status_im2/contexts/chat/composer/keyboard.cljs similarity index 98% rename from src/status_im2/contexts/chat/bottom_sheet_composer/keyboard.cljs rename to src/status_im2/contexts/chat/composer/keyboard.cljs index 81db283fa5..8cbc978cde 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/keyboard.cljs +++ b/src/status_im2/contexts/chat/composer/keyboard.cljs @@ -1,4 +1,4 @@ -(ns status-im2.contexts.chat.bottom-sheet-composer.keyboard +(ns status-im2.contexts.chat.composer.keyboard (:require [oops.core :as oops] [status-im.async-storage.core :as async-storage] [react-native.core :as rn] diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/mentions/style.cljs b/src/status_im2/contexts/chat/composer/mentions/style.cljs similarity index 84% rename from src/status_im2/contexts/chat/bottom_sheet_composer/mentions/style.cljs rename to src/status_im2/contexts/chat/composer/mentions/style.cljs index e3cc73a9d9..b836ae13dd 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/mentions/style.cljs +++ b/src/status_im2/contexts/chat/composer/mentions/style.cljs @@ -1,8 +1,8 @@ -(ns status-im2.contexts.chat.bottom-sheet-composer.mentions.style +(ns status-im2.contexts.chat.composer.mentions.style (:require [quo2.foundations.colors :as colors] [react-native.platform :as platform] [react-native.reanimated :as reanimated] - [status-im2.contexts.chat.bottom-sheet-composer.constants :as constants])) + [status-im2.contexts.chat.composer.constants :as constants])) (defn shadow diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/mentions/view.cljs b/src/status_im2/contexts/chat/composer/mentions/view.cljs similarity index 92% rename from src/status_im2/contexts/chat/bottom_sheet_composer/mentions/view.cljs rename to src/status_im2/contexts/chat/composer/mentions/view.cljs index 62f5403571..ade578c8d1 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/mentions/view.cljs +++ b/src/status_im2/contexts/chat/composer/mentions/view.cljs @@ -1,20 +1,20 @@ -(ns status-im2.contexts.chat.bottom-sheet-composer.mentions.view +(ns status-im2.contexts.chat.composer.mentions.view (:require [react-native.hooks :as hooks] [react-native.platform :as platform] [react-native.safe-area :as safe-area] [reagent.core :as reagent] - [status-im2.contexts.chat.bottom-sheet-composer.utils :as utils] + [status-im2.contexts.chat.composer.utils :as utils] [utils.re-frame :as rf] [react-native.core :as rn] [react-native.reanimated :as reanimated] [status-im2.common.contact-list-item.view :as contact-list-item] - [status-im2.contexts.chat.bottom-sheet-composer.mentions.style :as style])) + [status-im2.contexts.chat.composer.mentions.style :as style])) (defn update-cursor [user {:keys [cursor-position input-ref]}] (when platform/android? - (let [new-cursor-pos (+ (count (:primary-name user)) @cursor-position)] + (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 diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/reply/style.cljs b/src/status_im2/contexts/chat/composer/reply/style.cljs similarity index 72% rename from src/status_im2/contexts/chat/bottom_sheet_composer/reply/style.cljs rename to src/status_im2/contexts/chat/composer/reply/style.cljs index 45ff73310c..7ceb087597 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/reply/style.cljs +++ b/src/status_im2/contexts/chat/composer/reply/style.cljs @@ -1,5 +1,12 @@ -(ns status-im2.contexts.chat.bottom-sheet-composer.reply.style) +(ns status-im2.contexts.chat.composer.reply.style) + +(defn container + [pin? in-chat-input?] + {:flex-direction :row + :height (when-not pin? 24) + :margin-left (when (and (not in-chat-input?) (not pin?)) 26) + :margin-bottom (when (and (not in-chat-input?) (not pin?)) 8)}) (defn reply-content [pin?] {:padding-right (when-not pin? 10) diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/reply/view.cljs b/src/status_im2/contexts/chat/composer/reply/view.cljs similarity index 92% rename from src/status_im2/contexts/chat/bottom_sheet_composer/reply/view.cljs rename to src/status_im2/contexts/chat/composer/reply/view.cljs index afca72f86d..91f33e86d1 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/reply/view.cljs +++ b/src/status_im2/contexts/chat/composer/reply/view.cljs @@ -1,15 +1,15 @@ -(ns status-im2.contexts.chat.bottom-sheet-composer.reply.view +(ns status-im2.contexts.chat.composer.reply.view (:require [clojure.string :as string] [react-native.core :as rn] [react-native.reanimated :as reanimated] - [status-im2.contexts.chat.bottom-sheet-composer.constants :as constants] + [status-im2.contexts.chat.composer.constants :as constants] [utils.i18n :as i18n] [quo2.core :as quo] [quo2.foundations.colors :as colors] [status-im2.constants :as constant] [status-im.ethereum.stateofus :as stateofus] [utils.re-frame :as rf] - [status-im2.contexts.chat.bottom-sheet-composer.reply.style :as style] + [status-im2.contexts.chat.composer.reply.style :as style] [react-native.linear-gradient :as linear-gradient])) (defn get-quoted-text-with-mentions @@ -78,7 +78,7 @@ :style style/message-author-text} (format-reply-author from contact-name current-public-key)]])) -(defn reply-message +(defn quoted-message [{:keys [from identicon content-type contentType parsed-text content deleted? deleted-for-me? album-images-count]} in-chat-input? pin? recording-audio?] @@ -86,9 +86,8 @@ current-public-key (rf/sub [:multiaccount/public-key]) content-type (or content-type contentType)] [rn/view - {:style {:flex-direction :row - :height (when-not pin? 24) - :accessibility-label :reply-message}} + {:style (style/container pin? in-chat-input?) + :accessibility-label :reply-message} [rn/view {:style (style/reply-content pin?)} (when-not pin? [quo/icon :i/connector @@ -148,7 +147,7 @@ height (reanimated/use-shared-value (if reply constants/reply-container-height 0))] (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 [reply-message reply true false recording?])])) + (when reply [quoted-message reply true false recording?])])) (defn view [{:keys [recording?]}] diff --git a/src/status_im2/contexts/chat/composer/selection.cljs b/src/status_im2/contexts/chat/composer/selection.cljs new file mode 100644 index 0000000000..6178cb2ee7 --- /dev/null +++ b/src/status_im2/contexts/chat/composer/selection.cljs @@ -0,0 +1,109 @@ +(ns status-im2.contexts.chat.composer.selection + (:require + [clojure.string :as string] + [react-native.clipboard :as clipboard] + [react-native.core :as rn] + [oops.core :as oops] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(declare first-level-menu-items second-level-menu-items) + +(defn update-input-text + [{:keys [text-input]} text] + (rf/dispatch [:chat.ui/set-chat-input-text text]) + (.setNativeProps ^js text-input (clj->js {:text text}))) + +(defn calculate-input-text + [{:keys [full-text selection-start selection-end]} content] + (let [head (subs full-text 0 selection-start) + tail (subs full-text selection-end)] + (str head content tail))) + +(defn update-selection + [text-input-handle selection-start selection-end] + ;; to avoid something like this + ;; https://lightrun.com/answers/facebook-react-native-textinput-controlled-selection-broken-on-both-ios-and-android + ;; use native invoke instead! do not use setNativeProps! e.g. (.setNativeProps ^js text-input (clj->js + ;; {:selection {:start selection-start :end selection-end}})) + (let [manager (rn/selectable-text-input-manager)] + (oops/ocall manager :setSelection text-input-handle selection-start selection-end))) + +(defn reset-to-first-level-menu + [first-level? menu-items] + (reset! first-level? true) + (reset! menu-items first-level-menu-items)) + +(defn append-markdown-char + [{:keys [first-level? menu-items content selection-start selection-end text-input-handle + selection-event] + :as params} wrap-chars] + (let [content (str wrap-chars content wrap-chars) + new-text (calculate-input-text params content) + len-wrap-chars (count wrap-chars) + selection-start (+ selection-start len-wrap-chars) + selection-end (+ selection-end len-wrap-chars)] + ;; don't update selection directly here, process it within on-selection-change instead + ;; so that we can avoid java.lang.IndexOutOfBoundsException: setSpan.. + (reset! selection-event {:start selection-start + :end selection-end + :text-input-handle text-input-handle}) + (update-input-text params new-text) + (reset-to-first-level-menu first-level? menu-items))) + +(def first-level-menus + {:cut (fn [{:keys [content] :as params}] + (let [new-text (calculate-input-text params "")] + (clipboard/set-string content) + (update-input-text params new-text))) + + :copy-to-clipboard (fn [{:keys [content]}] + (clipboard/set-string content)) + + :paste (fn [params] + (let [callback (fn [paste-content] + (let [content (string/trim paste-content) + new-text (calculate-input-text params content)] + (update-input-text params new-text)))] + (clipboard/get-string callback))) + + :biu (fn [{:keys [first-level? text-input-handle menu-items selection-start + selection-end]}] + (reset! first-level? false) + (reset! menu-items second-level-menu-items) + (update-selection text-input-handle selection-start selection-end))}) + +(def first-level-menu-items (map i18n/label (keys first-level-menus))) +(def second-level-menus + {:bold #(append-markdown-char % "**") + + :italic #(append-markdown-char % "*") + + :strikethrough #(append-markdown-char % "~~")}) + +(def second-level-menu-items (map i18n/label (keys second-level-menus))) + +(defn on-menu-item-touched + [{:keys [first-level? event-type] :as params}] + (let [menus (if @first-level? first-level-menus second-level-menus) + menu-item-key (nth (keys menus) event-type) + action (get menus menu-item-key)] + (action params))) + +(defn on-selection + [event + {:keys [input-ref selection-event]} + {:keys [first-level? menu-items]}] + (let [{:strs [eventType content selectionStart + selectionEnd]} (js->clj (oops/oget event "nativeEvent")) + full-text (:input-text (rf/sub [:chats/current-chat-input]))] + (on-menu-item-touched {:first-level? first-level? + :event-type eventType + :content content + :selection-start selectionStart + :selection-end selectionEnd + :text-input @input-ref + :text-input-handle (rn/find-node-handle @input-ref) + :full-text full-text + :menu-items menu-items + :selection-event selection-event}))) diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/style.cljs b/src/status_im2/contexts/chat/composer/style.cljs similarity index 78% rename from src/status_im2/contexts/chat/bottom_sheet_composer/style.cljs rename to src/status_im2/contexts/chat/composer/style.cljs index e5f06f305a..6211de0ae5 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/style.cljs +++ b/src/status_im2/contexts/chat/composer/style.cljs @@ -1,9 +1,9 @@ -(ns status-im2.contexts.chat.bottom-sheet-composer.style +(ns status-im2.contexts.chat.composer.style (:require [quo2.foundations.colors :as colors] [quo2.foundations.typography :as typography] [react-native.platform :as platform] [react-native.reanimated :as reanimated] - [status-im2.contexts.chat.bottom-sheet-composer.constants :as constants])) + [status-im2.contexts.chat.composer.constants :as constants])) (defn shadow [focused?] @@ -54,20 +54,23 @@ {:max-height max-height :overflow :hidden})) -(defn input +(defn input-view [{:keys [saved-emoji-kb-extra-height]} {:keys [focused? recording?]}] + {:z-index 1 + :position (if @saved-emoji-kb-extra-height :relative :absolute) + :top 0 + :left 0 + :right (when (or focused? platform/ios?) 0) + :flex 1 + :display (if @recording? :none :flex) + :min-height constants/input-height}) + +(defn input-text + [] (merge typography/paragraph-1 - {:min-height constants/input-height - :color (colors/theme-colors :black :white) - :text-align-vertical :top - :flex 1 - :z-index 1 - :position (if @saved-emoji-kb-extra-height :relative :absolute) - :top 0 - :left 0 - :right (when (or focused? platform/ios?) 0) - :display (if @recording? :none :flex)})) + {:color (colors/theme-colors :black :white) + :text-align-vertical :top})) (defn background [opacity background-y window-height] (reanimated/apply-animations-to-style @@ -82,11 +85,11 @@ :z-index 1})) (defn blur-container - [height] + [height focused?] (reanimated/apply-animations-to-style {:height height} {:position :absolute - :elevation 10 + :elevation (if-not @focused? 10 0) :left 0 :right 0 :bottom 0 diff --git a/src/status_im2/contexts/chat/composer/sub_view.cljs b/src/status_im2/contexts/chat/composer/sub_view.cljs new file mode 100644 index 0000000000..bf656f5bde --- /dev/null +++ b/src/status_im2/contexts/chat/composer/sub_view.cljs @@ -0,0 +1,19 @@ +(ns status-im2.contexts.chat.composer.sub-view + (:require + [react-native.blur :as blur] + [react-native.core :as rn] + [react-native.reanimated :as reanimated] + [status-im2.contexts.chat.composer.style :as style])) + +(defn bar + [] + [rn/view {:style style/bar-container} + [rn/view {:style (style/bar)}]]) + +(defn f-blur-view + [layout-height focused?] + [reanimated/view {:style (style/blur-container layout-height focused?)} + [blur/view (style/blur-view)]]) +(defn blur-view + [layout-height focused?] + [:f> f-blur-view layout-height focused?]) diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/utils.cljs b/src/status_im2/contexts/chat/composer/utils.cljs similarity index 63% rename from src/status_im2/contexts/chat/bottom_sheet_composer/utils.cljs rename to src/status_im2/contexts/chat/composer/utils.cljs index 8e56bcb452..8f4cef1db3 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/utils.cljs +++ b/src/status_im2/contexts/chat/composer/utils.cljs @@ -1,10 +1,13 @@ -(ns status-im2.contexts.chat.bottom-sheet-composer.utils +(ns status-im2.contexts.chat.composer.utils (:require [clojure.string :as string] [oops.core :as oops] + [react-native.core :as rn] [react-native.platform :as platform] [react-native.reanimated :as reanimated] - [status-im2.contexts.chat.bottom-sheet-composer.constants :as constants] + [reagent.core :as reagent] + [status-im2.contexts.chat.composer.constants :as constants] + [status-im2.contexts.chat.composer.selection :as selection] [utils.re-frame :as rf])) (defn bounded-val @@ -52,8 +55,9 @@ (> new-height (* constants/background-threshold max-height)))) (defn update-blur-height? - [event lock-layout? layout-height] + [event {:keys [lock-layout? focused?]} layout-height] (or (not @lock-layout?) + (not @focused?) (> (reanimated/get-shared-value layout-height) (oops/oget event "nativeEvent.layout.height")))) (defn calc-lines @@ -83,24 +87,10 @@ (or (> lines 1) (seq images) reply? edit?)) (defn cancel-edit-message - [{:keys [text-value maximized?]} - {:keys [height saved-height last-height]}] - (when-not @maximized? - (reanimated/animate height constants/input-height) - (reanimated/set-shared-value saved-height constants/input-height) - (reanimated/set-shared-value last-height constants/input-height)) + [{:keys [text-value]}] (reset! text-value "") (rf/dispatch [:chat.ui/set-input-content-height constants/input-height])) -(defn update-input - [{:keys [input-ref]} - {:keys [text-value]} - input-text] - (when (and input-text (not= @text-value input-text)) - (reset! text-value input-text) - (when @input-ref - (.setNativeProps ^js @input-ref (clj->js {:text input-text}))))) - (defn count-lines [s] (-> s @@ -141,3 +131,61 @@ (if (< (+ base container-height) view-height) base (+ constants/actions-container-height (:bottom insets) (- curr-height cursor-pos) 18))))) + + +(defn init-props + [] + {: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) + :record-reset-fn (atom nil) + :scroll-y (atom 0) + :selection-event (atom nil) + :selection-manager (rn/selectable-text-input-manager)}) + +(defn init-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 nil) + :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-animations + [lines input-text images reply audio content-height max-height opacity background-y] + (let [initial-height (if (> lines 1) + constants/multiline-minimized-height + constants/input-height)] + {:gradient-opacity (reanimated/use-shared-value 0) + :container-opacity (reanimated/use-shared-value + (if (empty-input? + input-text + images + reply + audio) + 0.7 + 1)) + :height (reanimated/use-shared-value + initial-height) + :saved-height (reanimated/use-shared-value + initial-height) + :last-height (reanimated/use-shared-value + (bounded-val + @content-height + constants/input-height + max-height)) + :opacity opacity + :background-y background-y})) diff --git a/src/status_im2/contexts/chat/composer/view.cljs b/src/status_im2/contexts/chat/composer/view.cljs new file mode 100644 index 0000000000..ecc13f7c9a --- /dev/null +++ b/src/status_im2/contexts/chat/composer/view.cljs @@ -0,0 +1,144 @@ +(ns status-im2.contexts.chat.composer.view + (:require + [quo2.foundations.colors :as colors] + [react-native.core :as rn] + [react-native.gesture :as gesture] + [react-native.hooks :as hooks] + [react-native.reanimated :as reanimated] + [reagent.core :as reagent] + [utils.i18n :as i18n] + [utils.re-frame :as rf] + [status-im2.contexts.chat.composer.style :as style] + [status-im2.contexts.chat.composer.images.view :as images] + [status-im2.contexts.chat.composer.reply.view :as reply] + [status-im2.contexts.chat.composer.edit.view :as edit] + [status-im2.contexts.chat.composer.mentions.view :as mentions] + [status-im2.contexts.chat.composer.utils :as utils] + [status-im2.contexts.chat.composer.constants :as constants] + [status-im2.contexts.chat.composer.actions.view :as actions] + [status-im2.contexts.chat.composer.keyboard :as kb] + [status-im2.contexts.chat.composer.sub-view :as sub-view] + [status-im2.contexts.chat.composer.effects :as effects] + [status-im2.contexts.chat.composer.gesture :as drag-gesture] + [status-im2.contexts.chat.composer.handlers :as handler] + [status-im2.contexts.chat.composer.gradients.view :as gradients] + [status-im2.contexts.chat.composer.selection :as selection])) + +(defn sheet-component + [{:keys [insets window-height blur-height opacity background-y]} props state] + (let [images (rf/sub [:chats/sending-image]) + 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]) + {:keys [input-text input-content-height] + :as chat-input} (rf/sub [:chats/current-chat-input]) + content-height (reagent/atom (or input-content-height + constants/input-height)) + {:keys [keyboard-shown keyboard-height]} (hooks/use-keyboard) + kb-height (kb/get-kb-height keyboard-height + @(:kb-default-height state)) + max-height (utils/calc-max-height window-height + kb-height + insets + (boolean (seq images)) + reply + edit) + lines (utils/calc-lines @content-height) + max-lines (utils/calc-lines max-height) + animations (utils/init-animations + lines + input-text + images + reply + audio + content-height + max-height + opacity + background-y) + 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-pos (utils/cursor-y-position-relative-to-container props + state)] + (effects/did-mount props) + (effects/initialize props + state + animations + dimensions + chat-input + keyboard-height + (boolean (seq images)) + reply + audio) + (effects/edit props state edit) + (effects/reply props animations reply) + (effects/update-input-mention props state input-text) + (effects/edit-mentions props state input-with-mentions) + [:<> + [mentions/view props state animations max-height cursor-pos] + [gesture/gesture-detector + {:gesture (drag-gesture/drag-gesture props state animations dimensions keyboard-shown)} + [reanimated/view + {:style (style/sheet-container insets state animations) + :on-layout #(handler/layout % state blur-height)} + [sub-view/bar] + [reply/view state] + [edit/view state] + [reanimated/touchable-opacity + {:active-opacity 1 + :on-press (when @(:input-ref props) #(.focus ^js @(:input-ref props))) + :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 props 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 images reply) + :on-content-size-change #(handler/content-size-change % + state + animations + dimensions + (or keyboard-shown edit)) + :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) + :max-height max-height + :max-font-size-multiplier 1 + :multiline true + :placeholder (i18n/label :t/type-something) + :placeholder-text-color (colors/theme-colors colors/neutral-40 colors/neutral-50) + :style (style/input-text) + :max-length constants/max-text-size + :accessibility-label :chat-message-input}]] + [gradients/view props state animations show-bottom-gradient?]] + [images/images-list] + [actions/view props state animations window-height insets edit + (boolean (seq images))]]]])) + +(defn composer + [insets] + (let [window-height (rf/sub [:dimensions/window-height]) + opacity (reanimated/use-shared-value 0) + background-y (reanimated/use-shared-value (- window-height)) + blur-height (reanimated/use-shared-value (+ constants/composer-default-height + (:bottom insets))) + extra-params {:insets insets + :window-height window-height + :blur-height blur-height + :opacity opacity + :background-y background-y} + props (utils/init-props) + state (utils/init-state)] + [rn/view + [reanimated/view {:style (style/background opacity background-y window-height)}] + [sub-view/blur-view blur-height (:focused? state)] + [:f> sheet-component extra-params props state]])) diff --git a/src/status_im2/contexts/chat/messages/composer/controls/style.cljs b/src/status_im2/contexts/chat/messages/composer/controls/style.cljs deleted file mode 100644 index fbd0d407db..0000000000 --- a/src/status_im2/contexts/chat/messages/composer/controls/style.cljs +++ /dev/null @@ -1,28 +0,0 @@ -(ns status-im2.contexts.chat.messages.composer.controls.style - (:require [quo2.foundations.colors :as colors])) - -(defn controls - [bottom-inset] - {:padding-horizontal 20 - :z-index 3 - :position :absolute - :background-color (colors/theme-colors colors/white colors/neutral-90) - :padding-bottom (+ 12 bottom-inset) - :bottom 0 - :left 0 - :right 0}) - -(def buttons-container - {:flex-direction :row - :margin-top 12 - :min-height 32}) - -(defn record-audio-container - [bottom-inset] - {:align-items :center - :background-color :transparent - :flex-direction :row - :position :absolute - :left 0 - :right 0 - :bottom (- bottom-inset 7)}) diff --git a/src/status_im2/contexts/chat/messages/composer/controls/view.cljs b/src/status_im2/contexts/chat/messages/composer/controls/view.cljs deleted file mode 100644 index e711c3350f..0000000000 --- a/src/status_im2/contexts/chat/messages/composer/controls/view.cljs +++ /dev/null @@ -1,127 +0,0 @@ -(ns status-im2.contexts.chat.messages.composer.controls.view - (:require [react-native.core :as rn] - [react-native.background-timer :as background-timer] - [utils.re-frame :as rf] - [utils.i18n :as i18n] - [quo2.core :as quo] - [status-im2.common.not-implemented :as not-implemented] - [status-im2.contexts.chat.messages.composer.controls.style :as style] - [status-im2.contexts.chat.messages.list.view :as messages.list] - [status-im.ui2.screens.chat.composer.images.view :as composer-images] - [status-im.utils.utils :as utils-old] - [status-im.ui2.screens.chat.composer.input :as input] - [status-im2.common.alert.events :as alert] - [react-native.permissions :as permissions] - [quo.react :as quo.react])) - -(defn send-button - [send-ref {:keys [chat-id images]} on-send] - [rn/view - {:ref send-ref - :style (when-not (or (seq (get @input/input-texts chat-id)) (seq images)) - {:width 0 - :right -100})} - [quo/button - {:icon true - :size 32 - :accessibility-label :send-message-button - :on-press (fn [] - (on-send) - (messages.list/scroll-to-bottom) - (rf/dispatch [:chat.ui/send-current-message]))} - :i/arrow-up]]) - -(defn reactions-button - [] - [not-implemented/not-implemented - [quo/button - {:icon true - :type :outline - :size 32} :i/reaction]]) - -(defn image-button - [insets] - [quo/button - {:on-press (fn [] - (permissions/request-permissions - {:permissions [:read-external-storage :write-external-storage] - :on-allowed #(rf/dispatch [:open-modal :photo-selector {:insets insets}]) - :on-denied (fn [] - (background-timer/set-timeout - #(utils-old/show-popup (i18n/label :t/error) - (i18n/label - :t/external-storage-denied)) - 50))})) - :icon true - :type :outline - :size 32} - :i/image]) - -(defn record-audio - [record-ref chat-id bottom-inset] - [rn/view - {:ref record-ref - :style (style/record-audio-container bottom-inset) - :pointer-events :box-none} - [quo/record-audio - {:record-audio-permission-granted @input/record-audio-permission-granted - :on-init (fn [init-fn] - (reset! input/record-audio-reset-fn init-fn) - (reset! input/recording-audio? - (some? (get @input/reviewing-audio-filepath chat-id))) - (when (seq (get @input/input-texts chat-id)) - (js/setTimeout #(quo.react/set-native-props - record-ref - #js {:right nil :left -1000})))) - :on-start-recording #(reset! input/recording-audio? true) - :audio-file (get @input/reviewing-audio-filepath chat-id) - :on-reviewing-audio (fn [audio-file] - (swap! input/reviewing-audio-filepath assoc - chat-id - audio-file) - (reset! input/reviewing-audio? true)) - :on-send (fn - [{:keys [file-path duration]}] - (rf/dispatch [:chat/send-audio file-path duration]) - (reset! input/recording-audio? false) - (reset! input/reviewing-audio? false) - (swap! input/reviewing-audio-filepath dissoc chat-id)) - :on-cancel (fn [] - (reset! input/recording-audio? false) - (reset! input/reviewing-audio? false) - (swap! input/reviewing-audio-filepath dissoc chat-id)) - :on-check-audio-permissions (fn [] - (permissions/permission-granted? - :record-audio - #(reset! input/record-audio-permission-granted %) - #(reset! input/record-audio-permission-granted false))) - :on-request-record-audio-permission (fn [] - (rf/dispatch - [:request-permissions - {:permissions [:record-audio] - :on-allowed - #(reset! input/record-audio-permission-granted true) - :on-denied - #(js/setTimeout - (fn [] - (alert/show-popup - (i18n/label :t/audio-recorder-error) - (i18n/label - :t/audio-recorder-permissions-error))) - 50)}]))}]]) - -(defn view - [send-ref record-ref params bottom-inset chat-id images edit on-send] - [rn/view {:style (style/controls bottom-inset)} - [composer-images/images-list images] - [rn/view {:style style/buttons-container} - (when (and (not @input/recording-audio?) - (nil? (get @input/reviewing-audio-filepath chat-id))) - [:<> - [image-button {:bottom bottom-inset}] - [rn/view {:width 12}] - [reactions-button] - [rn/view {:flex 1}] - [send-button send-ref params on-send]])] - (when (and (not edit) (not (seq images))) - [record-audio record-ref chat-id bottom-inset])]) diff --git a/src/status_im2/contexts/chat/messages/composer/mentions/view.cljs b/src/status_im2/contexts/chat/messages/composer/mentions/view.cljs deleted file mode 100644 index bc0188e15a..0000000000 --- a/src/status_im2/contexts/chat/messages/composer/mentions/view.cljs +++ /dev/null @@ -1,35 +0,0 @@ -(ns status-im2.contexts.chat.messages.composer.mentions.view - (:require [utils.re-frame :as rf] - [react-native.core :as rn] - [react-native.reanimated :as reanimated] - [status-im2.common.contact-list-item.view :as contact-list-item])) - -(defn mention-item - [user _ _ text-input-ref] - [contact-list-item/contact-list-item - {:on-press #(rf/dispatch [:chat.ui/select-mention text-input-ref user])} user]) - -(defn f-mentions - [{:keys [refs suggestions max-y]} bottom-inset] - (let [translate-y (reanimated/use-shared-value 0)] - (rn/use-effect - (fn [] - (reanimated/set-shared-value translate-y - (reanimated/with-timing (if (seq suggestions) 0 200))))) - [reanimated/view - {:style (reanimated/apply-animations-to-style - {:transform [{:translateY translate-y}]} - {:bottom (or bottom-inset 0) - :position :absolute - :left 0 - :right 0 - :z-index 5 - :elevation 5 - :max-height (/ max-y 2)})} - [rn/flat-list - {:keyboard-should-persist-taps :always - :data (vals suggestions) - :key-fn :key - :render-fn mention-item - :render-data (:text-input-ref refs) - :accessibility-label :mentions-list}]])) diff --git a/src/status_im2/contexts/chat/messages/composer/style.cljs b/src/status_im2/contexts/chat/messages/composer/style.cljs deleted file mode 100644 index d8e92b4877..0000000000 --- a/src/status_im2/contexts/chat/messages/composer/style.cljs +++ /dev/null @@ -1,42 +0,0 @@ -(ns status-im2.contexts.chat.messages.composer.style - (:require [quo2.foundations.colors :as colors] - [status-im.utils.platform :as platform])) - -(defn input-bottom-sheet - [window-height] - (merge {:border-top-left-radius 20 - :border-top-right-radius 20 - :position :absolute - :left 0 - :right 0 - :bottom (- window-height) - :height window-height - :flex 1 - :background-color (colors/theme-colors colors/white colors/neutral-90) - :z-index 2} - (if platform/ios? - {:shadow-radius 16 - :shadow-opacity 1 - :shadow-color "rgba(9, 16, 28, 0.04)" - :shadow-offset {:width 0 :height -2}} - {:elevation 4}))) - -(defn bottom-sheet-handle - [] - {:width 32 - :height 4 - :background-color (colors/theme-colors colors/neutral-100 colors/white) - :opacity 0.05 - :border-radius 100 - :align-self :center - :margin-top 8}) - -(defn bottom-sheet-background - [window-height] - {:position :absolute - :left 0 - :right 0 - :bottom 0 - :height window-height - :background-color colors/neutral-95-opa-70 - :z-index 1}) diff --git a/src/status_im2/contexts/chat/messages/composer/view.cljs b/src/status_im2/contexts/chat/messages/composer/view.cljs deleted file mode 100644 index 7eb8cfd018..0000000000 --- a/src/status_im2/contexts/chat/messages/composer/view.cljs +++ /dev/null @@ -1,241 +0,0 @@ -(ns status-im2.contexts.chat.messages.composer.view - (:require [clojure.string :as string] - [oops.core :refer [oget]] - [react-native.core :as rn] - [react-native.hooks :as hooks] - [react-native.gesture :as gesture] - [react-native.reanimated :as reanimated] - [react-native.background-timer :as background-timer] - [utils.re-frame :as rf] - [status-im2.contexts.chat.messages.composer.style :as style] - [status-im2.contexts.chat.messages.composer.controls.view :as controls] - [status-im2.contexts.chat.messages.composer.mentions.view :as mentions] - [status-im.ui2.screens.chat.composer.edit.view :as edit] - [status-im.ui2.screens.chat.composer.input :as input] - [status-im.ui2.screens.chat.composer.reply :as reply] - [quo.react :refer [set-native-props]])) - -(def initial-content-height (atom nil)) -(def keyboard-hiding? (atom false)) - -(defn minimize - [{:keys [min-y set-bg-opacity set-translate-y set-parent-height refs chat-id]}] - (set-bg-opacity 0) - (set-translate-y (- min-y)) - (set-parent-height min-y) - (when-not (seq (get @input/input-texts chat-id)) - (set-native-props (:record-ref refs) #js {:right 0 :left 0}))) - -(defn maximize - [{:keys [max-y set-bg-opacity set-translate-y set-parent-height max-parent-height refs]}] - (set-bg-opacity 1) - (set-translate-y (- max-y)) - (set-parent-height max-parent-height) - (set-native-props (:record-ref refs) #js {:right nil :left -1000})) - -(defn clean-and-minimize - [{:keys [chat-id refs] :as params}] - (input/clear-input chat-id refs) - (minimize params)) - -(def gesture-values (atom {})) - -(defn get-bottom-sheet-gesture - [{:keys [translate-y refs keyboard-shown min-y max-y] :as params}] - (let [{:keys [text-input-ref]} refs] - (-> (gesture/gesture-pan) - (gesture/on-start - (fn [_] - (if (and keyboard-shown (not @input/recording-audio?)) - (swap! gesture-values assoc :pan-y (reanimated/get-shared-value translate-y)) - (input/input-focus text-input-ref)))) - (gesture/on-update - (fn [evt] - (when (and keyboard-shown (not @input/recording-audio?)) - (let [tY (oget evt "translationY")] - (swap! gesture-values assoc :dy (- tY (:pdy @gesture-values))) - (swap! gesture-values assoc :pdy tY) - (reanimated/set-shared-value - translate-y - (max (min (+ tY (:pan-y @gesture-values)) (- min-y)) (- max-y))))))) - (gesture/on-end - (fn [_] - (when (and keyboard-shown (not @input/recording-audio?)) - (if (< (:dy @gesture-values) 0) - (maximize params) - (do - (reset! keyboard-hiding? true) - (minimize params) - (background-timer/set-timeout #(rf/dispatch [:dismiss-keyboard]) 200))))))))) - -(defn update-y - [{:keys [max-parent-height set-bg-opacity keyboard-shown max-y set-translate-y set-parent-height - min-y chat-id] - :as params}] - (let [content-height (get @input/input-text-content-heights chat-id) - new-y (+ min-y - (if (and content-height @initial-content-height) - (- content-height - @initial-content-height) - 0))] - (if (< new-y max-y) - (when keyboard-shown - (if (> new-y max-parent-height) - (set-bg-opacity 1) - (set-bg-opacity 0)) - (set-translate-y (- new-y)) - (set-parent-height (min new-y max-parent-height))) - (if keyboard-shown - (maximize params) - (set-bg-opacity 0))))) - -(defn get-input-content-change - [{:keys [chat-id] :as params}] - (fn [evt] - (let [new-height (oget evt "nativeEvent.contentSize.height")] - (swap! input/input-text-content-heights assoc chat-id new-height) - (if @initial-content-height - (do - ;;on Android when last symbol removed, height value is wrong, like 300px - (when (string/blank? (get @input/input-texts chat-id)) - (swap! input/input-text-content-heights assoc chat-id @initial-content-height)) - (update-y params)) - (reset! initial-content-height new-height))))) - -(defn effect! - [{:keys [keyboard-shown reply edit suggestions images] :as params}] - (rn/use-effect - (fn [] - (when (or (not @keyboard-hiding?) - (and @keyboard-hiding? (not keyboard-shown))) - (reset! keyboard-hiding? false) - (if (not keyboard-shown) - (minimize params) - (update-y params)))) - [reply edit suggestions images])) - -(defn prepare-params - [[refs window-height translate-y bg-opacity bg-bottom min-y max-y parent-height - max-parent-height chat-id suggestions reply edit images keyboard-shown]] - {:chat-id chat-id - :translate-y translate-y - :min-y min-y - :max-y max-y - :max-parent-height max-parent-height - :parent-height parent-height - :suggestions suggestions - :reply reply - :edit edit - :images images - :refs refs - :keyboard-shown keyboard-shown - :bg-opacity bg-opacity - :bg-bottom bg-bottom - :window-height window-height - :set-bg-opacity (fn [value] - (reanimated/set-shared-value bg-bottom (if (= value 1) 0 (- window-height))) - (reanimated/set-shared-value bg-opacity (reanimated/with-timing value))) - :set-translate-y (fn [value] - (reanimated/set-shared-value translate-y (reanimated/with-timing value))) - :set-parent-height (fn [value] - (reanimated/set-shared-value parent-height (reanimated/with-timing value)))}) - -;; IMPORTANT! PLEASE READ BEFORE MAKING CHANGES - -;; 1) insets are not stable so we can't rely on the first values and we have to use it from render -;; function, so effect will be triggered a few times, for example first time it will be 0 and second -;; time 38 on ios, and 400 and 0 on Android -;; 2) for keyboard we use hooks, for some reason it triggers effect a few times with the same values, -;; also it changes value only when keyboard completely closed or opened, that's why its not visually -;; smooth, there is a small visible delay between keyboard and sheet animations -;; 3) when we minimize sheet and hide keyboard in `sheet-gesture` function, keyboard hook triggers -;; effect and there is a visual glitch when we have more than one line of text, so we use -;; `keyboard-hiding?` flag -;; 4) we store input content height in `input/input-text-content-heights` atom , we need it when chat -;; screen is reopened -(defn f-composer - [_ _] - (let [text-input-ref (rn/create-ref) - send-ref (rn/create-ref) - record-ref (rn/create-ref) - refs {:send-ref send-ref - :text-input-ref text-input-ref - :record-ref record-ref}] - (fn [chat-id insets] - (let [reply (rf/sub [:chats/reply-message]) - edit (rf/sub [:chats/edit-message]) - suggestions (rf/sub [:chat/mention-suggestions]) - images (rf/sub [:chats/sending-image]) - - bottom-inset (max 20 (:bottom insets)) - {window-height :height} (rn/get-window) - {:keys [keyboard-shown keyboard-height]} (hooks/use-keyboard) - translate-y (reanimated/use-shared-value 0) - bg-opacity (reanimated/use-shared-value 0) - bg-bottom (reanimated/use-shared-value (- window-height)) - - suggestions? (and (seq suggestions) - keyboard-shown - (not @keyboard-hiding?)) - - max-y (- window-height - (- (if (> keyboard-height 0) - keyboard-height - 360) - bottom-inset) - 46) - - min-y (+ 108 - bottom-inset - (if suggestions? - (min (/ max-y 2) - (+ 16 - (* 46 (dec (count suggestions))))) - (+ 0 - (when (and - (or edit reply) - (not @input/recording-audio?)) - 38) - (when (seq images) 80)))) - - parent-height (reanimated/use-shared-value min-y) - max-parent-height (Math/abs (- max-y 110 bottom-inset)) - - params - (prepare-params - [refs window-height translate-y bg-opacity bg-bottom min-y max-y parent-height - max-parent-height chat-id suggestions reply edit images keyboard-shown]) - - input-content-change (get-input-content-change params) - bottom-sheet-gesture (get-bottom-sheet-gesture params)] - (effect! params) - [reanimated/view - {:style (reanimated/apply-animations-to-style - {:height parent-height} - {})} - ;;;;input - [gesture/gesture-detector {:gesture bottom-sheet-gesture} - [reanimated/view - {:style (reanimated/apply-animations-to-style - {:transform [{:translateY translate-y}]} - (style/input-bottom-sheet window-height))} - [rn/view {:style (style/bottom-sheet-handle)}] - [edit/edit-message-auto-focus-wrapper text-input-ref edit #(clean-and-minimize params)] - [reply/reply-message-auto-focus-wrapper text-input-ref reply] - [rn/view - {:style {:height (- max-y (- min-y 38))}} - [input/text-input - {:chat-id chat-id - :on-content-size-change input-content-change - :sending-image (seq images) - :refs refs}]]]] - (if suggestions? - [:f> mentions/f-mentions (select-keys params [:refs :suggestions :max-y]) bottom-inset] - [controls/view send-ref record-ref params bottom-inset chat-id images - edit #(clean-and-minimize params)]) - ;;;;black background - [reanimated/view - {:style (reanimated/apply-animations-to-style - {:opacity bg-opacity - :transform [{:translateY bg-bottom}]} - (style/bottom-sheet-background window-height))}]])))) diff --git a/src/status_im2/contexts/chat/messages/content/pin/view.cljs b/src/status_im2/contexts/chat/messages/content/pin/view.cljs index cfe1460efb..f6efbeeda4 100644 --- a/src/status_im2/contexts/chat/messages/content/pin/view.cljs +++ b/src/status_im2/contexts/chat/messages/content/pin/view.cljs @@ -1,5 +1,6 @@ (ns status-im2.contexts.chat.messages.content.pin.view (:require [react-native.core :as rn] + [status-im2.contexts.chat.composer.reply.view :as reply] [status-im2.contexts.chat.messages.content.pin.style :as style] [quo2.core :as quo] [quo2.foundations.colors :as colors] @@ -53,4 +54,4 @@ (old-style/message-timestamp-text)) :accessibility-label :message-timestamp} timestamp-str]] - [old-message/quoted-message quoted-message true]]]) + [reply/quoted-message quoted-message false true]]]) diff --git a/src/status_im2/contexts/chat/messages/content/view.cljs b/src/status_im2/contexts/chat/messages/content/view.cljs index 2b91f135b6..0539743838 100644 --- a/src/status_im2/contexts/chat/messages/content/view.cljs +++ b/src/status_im2/contexts/chat/messages/content/view.cljs @@ -1,27 +1,29 @@ (ns status-im2.contexts.chat.messages.content.view - (:require [react-native.core :as rn] - [quo2.foundations.colors :as colors] - [react-native.platform :as platform] - [status-im2.contexts.chat.messages.content.style :as style] - [status-im2.contexts.chat.messages.content.pin.view :as pin] - [status-im2.constants :as constants] - [status-im2.contexts.chat.messages.content.unknown.view :as content.unknown] - [status-im2.contexts.chat.messages.content.text.view :as content.text] - [status-im2.contexts.chat.messages.drawers.view :as drawers] - [status-im2.contexts.chat.messages.content.reactions.view :as reactions] - [status-im2.contexts.chat.messages.content.status.view :as status] - [status-im2.contexts.chat.messages.content.system.text.view :as system.text] - [status-im2.contexts.chat.messages.content.album.view :as album] - [status-im2.contexts.chat.messages.avatar.view :as avatar] - [status-im2.contexts.chat.messages.content.image.view :as image] - [status-im2.contexts.chat.messages.content.audio.view :as audio] - [quo2.core :as quo] - [utils.re-frame :as rf] - [status-im.ui2.screens.chat.messages.message :as old-message] - [status-im2.common.not-implemented :as not-implemented] - [utils.datetime :as datetime] - [reagent.core :as reagent] - [utils.address :as address])) + (:require + [react-native.core :as rn] + [quo2.foundations.colors :as colors] + [react-native.platform :as platform] + [status-im2.contexts.chat.messages.content.style :as style] + [status-im2.contexts.chat.messages.content.pin.view :as pin] + [status-im2.constants :as constants] + [status-im2.contexts.chat.messages.content.unknown.view :as content.unknown] + [status-im2.contexts.chat.messages.content.text.view :as content.text] + [status-im2.contexts.chat.messages.drawers.view :as drawers] + [status-im2.contexts.chat.messages.content.reactions.view :as reactions] + [status-im2.contexts.chat.messages.content.status.view :as status] + [status-im2.contexts.chat.messages.content.system.text.view :as system.text] + [status-im2.contexts.chat.messages.content.album.view :as album] + [status-im2.contexts.chat.messages.avatar.view :as avatar] + [status-im2.contexts.chat.messages.content.image.view :as image] + [status-im2.contexts.chat.messages.content.audio.view :as audio] + [quo2.core :as quo] + [utils.re-frame :as rf] + [status-im.ui2.screens.chat.messages.message :as old-message] + [status-im2.contexts.chat.composer.reply.view :as reply] + [status-im2.common.not-implemented :as not-implemented] + [utils.datetime :as datetime] + [reagent.core :as reagent] + [utils.address :as address])) (def delivery-state-showing-time-ms 3000) @@ -109,7 +111,7 @@ :on-long-press #(on-long-press message-data context)} [rn/view {:style {:padding-vertical 8}} (when (and (seq response-to) quoted-message) - [old-message/quoted-message quoted-message]) + [reply/quoted-message quoted-message]) [rn/view {:style {:padding-horizontal 12 :flex-direction :row}} diff --git a/src/status_im2/contexts/chat/messages/drawers/view.cljs b/src/status_im2/contexts/chat/messages/drawers/view.cljs index e61b31b201..3d346e8bee 100644 --- a/src/status_im2/contexts/chat/messages/drawers/view.cljs +++ b/src/status_im2/contexts/chat/messages/drawers/view.cljs @@ -2,7 +2,7 @@ (:require [quo2.core :as quo] [react-native.core :as rn] [status-im.ui.components.react :as react] - [status-im.ui2.screens.chat.components.reply.view :as components.reply] + [status-im2.contexts.chat.composer.reply.view :as reply] [status-im2.common.not-implemented :as not-implemented] [status-im2.constants :as constants] [utils.i18n :as i18n] @@ -47,7 +47,7 @@ (not= content-type constants/content-type-audio)) [{:type :main :on-press #(react/copy-to-clipboard - (components.reply/get-quoted-text-with-mentions + (reply/get-quoted-text-with-mentions (get content :parsed-text))) :label (i18n/label :t/copy-text) :icon :i/copy diff --git a/src/status_im2/contexts/chat/messages/list/new_temp_view.cljs b/src/status_im2/contexts/chat/messages/list/new_temp_view.cljs deleted file mode 100644 index f71e4a142d..0000000000 --- a/src/status_im2/contexts/chat/messages/list/new_temp_view.cljs +++ /dev/null @@ -1,211 +0,0 @@ -(ns status-im2.contexts.chat.messages.list.new-temp-view - (:require [oops.core :as oops] - [quo2.core :as quo] - [react-native.background-timer :as background-timer] - [react-native.core :as rn] - [react-native.platform :as platform] - [react-native.reanimated :as reanimated] - [reagent.core :as reagent] - [status-im.ui.screens.chat.group :as chat.group] - [status-im.ui.screens.chat.message.gap :as message.gap] - [status-im2.common.not-implemented :as not-implemented] - [status-im2.constants :as constants] - [status-im2.contexts.chat.bottom-sheet-composer.utils :as utils] - [status-im2.contexts.chat.messages.content.deleted.view :as content.deleted] - [status-im2.contexts.chat.messages.content.view :as message] - [status-im2.contexts.chat.messages.list.state :as state] - [utils.i18n :as i18n] - [utils.re-frame :as rf] - [status-im2.contexts.chat.bottom-sheet-composer.constants :as composer.constants])) - -(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 {:y 0 :animated true}))) - -(defonce ^:const threshold-percentage-to-show-floating-scroll-down-button 75) -(defonce show-floating-scroll-down-button (reagent/atom false)) - -(defn on-scroll - [evt] - (let [y (oops/oget evt "nativeEvent.contentOffset.y") - layout-height (oops/oget evt "nativeEvent.layoutMeasurement.height") - threshold-height (* (/ layout-height 100) - threshold-percentage-to-show-floating-scroll-down-button) - reached-threshold? (> y threshold-height)] - (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 on-viewable-items-changed - [evt] - (when @messages-list-ref - (reset! state/first-not-visible-item - (when-let [last-visible-element (aget (oops/oget evt "viewableItems") - (dec (oops/oget evt "viewableItems.length")))] - (let [index (oops/oget last-visible-element "index") - ;; Get first not visible element, if it's a datemark/gap - ;; we might unnecessarely add messages on receiving as - ;; they do not have a clock value, but most of the times - ;; it will be a message - first-not-visible (aget (oops/oget @messages-list-ref "props.data") (inc index))] - (when (and first-not-visible - (= :message (:type first-not-visible))) - first-not-visible)))))) - -;;TODO this is not really working in pair with inserting new messages because we stop inserting new -;;messages -;;if they outside the viewarea, but we load more here because end is reached,so its slowdown UI because -;;we -;;load and render 20 messages more, but we can't prevent this , because otherwise :on-end-reached will -;;work wrong -(defn list-on-end-reached - [] - (if @state/scrolling - (rf/dispatch [:chat.ui/load-more-messages-for-current-chat]) - (background-timer/set-timeout #(rf/dispatch [:chat.ui/load-more-messages-for-current-chat]) - (if platform/low-device? 700 200)))) - -(defonce messages-view-height (reagent/atom 0)) - -(defn on-messages-view-layout - [evt] - (reset! messages-view-height (oops/oget evt "nativeEvent.layout.height"))) - -(defn list-footer - [{:keys [chat-id]}] - (let [loading-messages? (rf/sub [:chats/loading-messages? chat-id]) - all-loaded? (rf/sub [:chats/all-loaded? chat-id])] - (when (or loading-messages? (not chat-id) (not all-loaded?)) - [rn/view {:style (when platform/android? {:scaleY -1})} - [quo/skeleton @messages-view-height]]))) - -(defn list-header - [{:keys [chat-id chat-type invitation-admin]}] - (when (= chat-type constants/private-group-chat-type) - [rn/view {:style (when platform/android? {:scaleY -1})} - [chat.group/group-chat-footer chat-id invitation-admin]])) - -(defn render-fn - [{:keys [type value deleted? deleted-for-me? content-type] :as message-data} _ _ - {:keys [context keyboard-shown]}] - [rn/view {:style (when platform/android? {:scaleY -1})} - (if (= type :datemark) - [quo/divider-date value] - (if (= content-type constants/content-type-gap) - [not-implemented/not-implemented - [message.gap/gap message-data]] - [rn/view {:padding-horizontal 8} - (if (or deleted? deleted-for-me?) - [content.deleted/deleted-message message-data context] - [message/message-with-reactions message-data context keyboard-shown])]))]) - -(defn calc-shell-position - [y] - (let [{:keys [input-content-height focused?]} (rf/sub [:chats/current-chat-input]) - reply (rf/sub [:chats/reply-message]) - edit (rf/sub [:chats/edit-message]) - lines (utils/calc-lines input-content-height) - base (if (or reply edit) - (- composer.constants/edit-container-height) - 0)] - (if (not focused?) - (if (> lines 1) (+ -18 base) base) - (if (> lines 12) - (reanimated/get-shared-value y) - (if (> lines 1) (- (- input-content-height composer.constants/input-height base)) base))))) - -(defn shell-button - [insets] - (let [y (reanimated/use-shared-value 0) - shell-position (calc-shell-position y)] - (rn/use-effect (fn [] - (reanimated/animate y shell-position)) - [shell-position]) - [reanimated/view - {:style (reanimated/apply-animations-to-style - {:transform [{:translate-y y}]} - {:bottom (+ composer.constants/composer-default-height (:bottom insets) 6) - :position :absolute - :left 0 - :right 0})} - [quo/floating-shell-button - (merge {:jump-to - {:on-press #(do - (rf/dispatch [:chat/close true]) - (rf/dispatch [:shell/navigate-to-jump-to])) - :label (i18n/label :t/jump-to) - :style {:align-self :center}}} - (when @show-floating-scroll-down-button - {:scroll-to-bottom {:on-press scroll-to-bottom}})) - {}]])) - - -(defn messages-list-content - [{:keys [chat-id] :as chat} insets keyboard-shown] - (fn [] - (let [context (rf/sub [:chats/current-chat-message-list-view-context]) - messages (rf/sub [:chats/raw-chat-messages-stream chat-id])] - [rn/view - {:style {:flex 1}} - ;; NOTE: DO NOT use anonymous functions for handlers - [rn/flat-list - {:key-fn list-key-fn - :ref list-ref - :header [list-header chat] - :footer [list-footer chat] - :data messages - :render-data {:context context - :keyboard-shown keyboard-shown} - :render-fn render-fn - :on-viewable-items-changed on-viewable-items-changed - :on-end-reached list-on-end-reached - :on-scroll-to-index-failed identity ; don't remove this - :content-container-style {:padding-top (+ composer.constants/composer-default-height - (:bottom insets) - 32) - :padding-bottom 16} - :scroll-indicator-insets {:top (+ composer.constants/composer-default-height - (:bottom insets))} - :keyboard-dismiss-mode :interactive - :keyboard-should-persist-taps :handled - :on-momentum-scroll-begin state/start-scrolling - :on-momentum-scroll-end state/stop-scrolling - :scroll-event-throttle 16 - :on-scroll on-scroll - ;; TODO https://github.com/facebook/react-native/issues/30034 - :inverted (when platform/ios? true) - :style (when platform/android? {:scaleY -1}) - :on-layout on-messages-view-layout}] - [:f> shell-button insets]]))) - - -(defn use-keyboard-visibility - [] - (let [show-listener (atom nil) - hide-listener (atom nil) - shown? (atom nil)] - (rn/use-effect - (fn [] - (reset! show-listener - (.addListener rn/keyboard "keyboardWillShow" #(reset! shown? true))) - (reset! hide-listener - (.addListener rn/keyboard "keyboardWillHide" #(reset! shown? false))) - (fn [] - (.remove ^js @show-listener) - (.remove ^js @hide-listener)))) - {:shown? shown?})) - -(defn- f-messages-list - [chat insets] - (let [{keyboard-shown? :shown?} (use-keyboard-visibility)] - [messages-list-content chat insets keyboard-shown?])) - -(defn messages-list - [chat insets] - [:f> f-messages-list chat insets]) diff --git a/src/status_im2/contexts/chat/messages/list/view.cljs b/src/status_im2/contexts/chat/messages/list/view.cljs index adaa2276e5..333348e092 100644 --- a/src/status_im2/contexts/chat/messages/list/view.cljs +++ b/src/status_im2/contexts/chat/messages/list/view.cljs @@ -4,16 +4,19 @@ [react-native.background-timer :as background-timer] [react-native.core :as rn] [react-native.platform :as platform] + [react-native.reanimated :as reanimated] [reagent.core :as reagent] [status-im.ui.screens.chat.group :as chat.group] [status-im.ui.screens.chat.message.gap :as message.gap] [status-im2.common.not-implemented :as not-implemented] [status-im2.constants :as constants] + [status-im2.contexts.chat.composer.utils :as utils] [status-im2.contexts.chat.messages.content.deleted.view :as content.deleted] [status-im2.contexts.chat.messages.content.view :as message] [status-im2.contexts.chat.messages.list.state :as state] [utils.i18n :as i18n] - [utils.re-frame :as rf])) + [utils.re-frame :as rf] + [status-im2.contexts.chat.composer.constants :as composer.constants])) (defonce messages-list-ref (atom nil)) @@ -90,7 +93,7 @@ (defn render-fn [{:keys [type value deleted? deleted-for-me? content-type] :as message-data} _ _ - {:keys [keyboard-shown context]}] + {:keys [context keyboard-shown]}] [rn/view {:style (when platform/android? {:scaleY -1})} (if (= type :datemark) [quo/divider-date value] @@ -102,47 +105,114 @@ [content.deleted/deleted-message message-data context] [message/message-with-reactions message-data context keyboard-shown])]))]) -(defn messages-list - [{:keys [chat-id] :as chat}] - (let [context (rf/sub [:chats/current-chat-message-list-view-context]) - messages (rf/sub [:chats/raw-chat-messages-stream chat-id]) - keyboard-shown (atom false) - bottom-space 15] - [rn/view - {:style {:flex 1}} - ;; NOTE: DO NOT use anonymous functions for handlers - [rn/flat-list - {:key-fn list-key-fn - :ref list-ref - :header [list-header chat] - :footer [list-footer chat] - :data messages - :render-data {:context context - :keyboard-shown keyboard-shown} - :render-fn render-fn - :on-viewable-items-changed on-viewable-items-changed - :on-end-reached list-on-end-reached - :on-scroll-to-index-failed identity ; don't remove this - :content-container-style {:padding-top (+ bottom-space 32) - :padding-bottom 16} - :scroll-indicator-insets {:top bottom-space} ; iOS only - :keyboard-dismiss-mode :interactive - :keyboard-should-persist-taps :handled - :onMomentumScrollBegin state/start-scrolling - :onMomentumScrollEnd state/stop-scrolling - :scrollEventThrottle 16 - :on-scroll on-scroll - ;; TODO https://github.com/facebook/react-native/issues/30034 - :inverted (when platform/ios? true) - :style (when platform/android? {:scaleY -1}) - :on-layout on-messages-view-layout}] +(defn calc-shell-position + [curr-pos input-content-height focused? reply edit images] + (let [lines (utils/calc-lines input-content-height) + base (if reply (- composer.constants/reply-container-height) 0) + base (if edit (- composer.constants/edit-container-height) base) + base (if (seq images) (- composer.constants/images-container-height) base)] + (if (not focused?) + (if (> lines 1) (+ (- composer.constants/multiline-minimized-height) base) base) + (if (> lines 12) + curr-pos + (if (> lines 1) (- (- input-content-height composer.constants/input-height base)) base))))) + +(def memoized-calc-shell-position (memoize calc-shell-position)) + +(defn shell-button + [insets] + (let [y (reanimated/use-shared-value 0) + {:keys [input-content-height focused?]} (rf/sub [:chats/current-chat-input]) + reply (rf/sub [:chats/reply-message]) + edit (rf/sub [:chats/edit-message]) + images (rf/sub [:chats/sending-image]) + curr-pos (reanimated/get-shared-value y) + shell-position + (memoized-calc-shell-position curr-pos input-content-height focused? reply edit images)] + (rn/use-effect (fn [] + (reanimated/animate y shell-position)) + [shell-position]) + [reanimated/view + {:style (reanimated/apply-animations-to-style + {:transform [{:translate-y y}]} + {:bottom (+ composer.constants/composer-default-height (:bottom insets) 6) + :position :absolute + :left 0 + :right 0})} [quo/floating-shell-button (merge {:jump-to - {:on-press #(do - (rf/dispatch [:chat/close true]) - (rf/dispatch [:shell/navigate-to-jump-to])) - :label (i18n/label :t/jump-to)}} + {:on-press (fn [] + (rf/dispatch [:chat/close true]) + (rf/dispatch [:shell/navigate-to-jump-to])) + :label (i18n/label :t/jump-to) + :style {:align-self :center}}} (when @show-floating-scroll-down-button {:scroll-to-bottom {:on-press scroll-to-bottom}})) - {:position :absolute - :bottom 6}]])) + {}]])) + +(defn messages-list-content + [{:keys [chat-id] :as chat} insets keyboard-shown] + (fn [] + (let [context (rf/sub [:chats/current-chat-message-list-view-context]) + messages (rf/sub [:chats/raw-chat-messages-stream chat-id])] + [rn/view + {:style {:flex 1}} + ;; NOTE: DO NOT use anonymous functions for handlers + [rn/flat-list + {:key-fn list-key-fn + :ref list-ref + :header [list-header chat] + :footer [list-footer chat] + :data messages + :render-data {:context context + :keyboard-shown keyboard-shown} + :render-fn render-fn + :on-viewable-items-changed on-viewable-items-changed + :on-end-reached list-on-end-reached + :on-scroll-to-index-failed identity ; don't remove this + :content-container-style {:padding-top (+ composer.constants/composer-default-height + (:bottom insets) + 32) + :padding-bottom 16} + :scroll-indicator-insets {:top (+ composer.constants/composer-default-height + (:bottom insets))} + :keyboard-dismiss-mode :interactive + :keyboard-should-persist-taps :handled + :on-momentum-scroll-begin state/start-scrolling + :on-momentum-scroll-end state/stop-scrolling + :scroll-event-throttle 16 + :on-scroll on-scroll + ;; TODO https://github.com/facebook/react-native/issues/30034 + :inverted (when platform/ios? true) + :style (when platform/android? {:scaleY -1}) + :on-layout on-messages-view-layout}] + [:f> shell-button insets]]))) + +;; This should be replaced with keyboard hook. It has to do with flat-list probably. The keyboard-shown +;; value +;; updates in the parent component, but does not get passed to the children. +;; When using listeners and resetting the value on an atom it works. +(defn use-keyboard-visibility + [] + (let [show-listener (atom nil) + hide-listener (atom nil) + shown? (atom nil)] + (rn/use-effect + (fn [] + (reset! show-listener + (.addListener rn/keyboard "keyboardWillShow" #(reset! shown? true))) + (reset! hide-listener + (.addListener rn/keyboard "keyboardWillHide" #(reset! shown? false))) + (fn [] + (.remove ^js @show-listener) + (.remove ^js @hide-listener)))) + {:shown? shown?})) + +(defn- f-messages-list + [chat insets] + (let [{keyboard-shown? :shown?} (use-keyboard-visibility)] + [messages-list-content chat insets keyboard-shown?])) + +(defn messages-list + [chat insets] + [:f> f-messages-list chat insets]) diff --git a/src/status_im2/contexts/chat/messages/view.cljs b/src/status_im2/contexts/chat/messages/view.cljs index a05d5dae43..809df97393 100644 --- a/src/status_im2/contexts/chat/messages/view.cljs +++ b/src/status_im2/contexts/chat/messages/view.cljs @@ -4,14 +4,11 @@ [react-native.core :as rn] [react-native.safe-area :as safe-area] [reagent.core :as reagent] - [status-im2.config :as config] [status-im2.constants :as constants] - [status-im2.contexts.chat.bottom-sheet-composer.view :as bottom-sheet-composer] - [status-im2.contexts.chat.messages.composer.view :as composer] + [status-im2.contexts.chat.composer.view :as composer] [status-im2.contexts.chat.messages.contact-requests.bottom-drawer :as contact-requests.bottom-drawer] [status-im2.contexts.chat.messages.list.view :as messages.list] - [status-im2.contexts.chat.messages.list.new-temp-view :as messages.list.new] [status-im2.contexts.chat.messages.pin.banner.view :as pin.banner] [status-im2.navigation.state :as navigation.state] [utils.debounce :as debounce] @@ -78,14 +75,10 @@ :keyboardVerticalOffset (- (:bottom insets))} [page-nav] [pin.banner/banner chat-id] - (if config/new-composer-enabled? - [messages.list.new/messages-list chat insets] - [messages.list/messages-list chat insets]) + [messages.list/messages-list chat insets] (if-not able-to-send-message? [contact-requests.bottom-drawer/view chat-id contact-request-state group-chat] - (if config/new-composer-enabled? - [bottom-sheet-composer/bottom-sheet-composer insets] - [:f> composer/f-composer chat-id insets]))])) + [:f> composer/composer insets])])) (defn chat