diff --git a/resources/images/icons2/16x16/connector@2x.png b/resources/images/icons2/16x16/connector@2x.png index 58d7e3b1f4..f69e4b2bb4 100644 Binary files a/resources/images/icons2/16x16/connector@2x.png and b/resources/images/icons2/16x16/connector@2x.png differ diff --git a/resources/images/icons2/16x16/connector@3x.png b/resources/images/icons2/16x16/connector@3x.png index bb1ab6c365..dc1c6b459e 100644 Binary files a/resources/images/icons2/16x16/connector@3x.png and b/resources/images/icons2/16x16/connector@3x.png differ diff --git a/src/status_im/chat/models/input.cljs b/src/status_im/chat/models/input.cljs index c16a0eccd0..4bbf74024c 100644 --- a/src/status_im/chat/models/input.cljs +++ b/src/status_im/chat/models/input.cljs @@ -51,12 +51,6 @@ (let [current-chat-id (or chat-id (:current-chat-id db))] {:db (assoc-in db [:chat/inputs current-chat-id :input-maximized?] maximized?)})) -(rf/defn set-input-refocus - {:events [:chat.ui/set-input-refocus]} - [{db :db} refocus? chat-id] - (let [current-chat-id (or chat-id (:current-chat-id db))] - {:db (assoc-in db [:chat/inputs current-chat-id :input-refocus?] refocus?)})) - (rf/defn select-mention {:events [:chat.ui/select-mention]} [{:keys [db] :as cofx} text-input-ref {:keys [primary-name searched-text match public-key] :as user}] diff --git a/src/status_im2/config.cljs b/src/status_im2/config.cljs index 5ee9c3aedb..c4ae6a4088 100644 --- a/src/status_im2/config.cljs +++ b/src/status_im2/config.cljs @@ -158,3 +158,5 @@ ["enrtree://AOGECG2SPND25EEFMAJ5WF3KSGJNSGV356DSTL2YVLLZWIV6SAYBM@test.waku.nodes.status.im"]}) (def default-kdf-iterations 3200) + +(def ^:const new-composer-enabled? false) diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/actions/view.cljs b/src/status_im2/contexts/chat/bottom_sheet_composer/actions/view.cljs index 3ab7b47294..1b1d18358c 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/actions/view.cljs +++ b/src/status_im2/contexts/chat/bottom_sheet_composer/actions/view.cljs @@ -88,17 +88,13 @@ (defn open-photo-selector [{:keys [input-ref]} - {:keys [focused?]} {:keys [height]} insets] (permissions/request-permissions {:permissions [:read-external-storage :write-external-storage] :on-allowed (fn [] - (when platform/android? - (when @focused? - (rf/dispatch [:chat.ui/set-input-refocus true])) - (when @input-ref - (.blur ^js @input-ref))) + (when (and platform/android? @input-ref) + (.blur ^js @input-ref)) (rf/dispatch [:chat.ui/set-input-content-height (reanimated/get-shared-value height)]) (rf/dispatch [:open-modal :photo-selector {:insets insets}])) @@ -108,9 +104,9 @@ :t/external-storage-denied)))})) (defn image-button - [props state animations insets] + [props animations insets] [quo/button - {:on-press #(open-photo-selector props state animations insets) + {:on-press #(open-photo-selector props animations insets) :icon true :type :outline :size 32 @@ -141,7 +137,7 @@ [rn/view {:style style/actions-container} [rn/view {:style {:flex-direction :row}} [camera-button] - [image-button props state animations insets] + [image-button props animations insets] [reaction-button] [format-button]] [send-button props state animations window-height images?] diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/constants.cljs b/src/status_im2/contexts/chat/bottom_sheet_composer/constants.cljs index 1ac84a87e4..c38f34fc7e 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/constants.cljs +++ b/src/status_im2/contexts/chat/bottom_sheet_composer/constants.cljs @@ -17,6 +17,8 @@ (def ^:const images-container-height 76) +(def ^:const reply-container-height 32) + (def ^:const extra-content-offset (if platform/ios? 6 0)) (def ^:const content-change-threshold 10) diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/effects.cljs b/src/status_im2/contexts/chat/bottom_sheet_composer/effects.cljs index 0c1a7517c8..67de294398 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/effects.cljs +++ b/src/status_im2/contexts/chat/bottom_sheet_composer/effects.cljs @@ -5,7 +5,6 @@ [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.re-frame :as rf] [utils.number :as utils.number])) (defn reenter-screen-effect @@ -29,13 +28,6 @@ (reanimated/set-shared-value saved-height max-height) (reanimated/set-shared-value last-height max-height))) -(defn refocus-effect - [{:keys [input-ref]} - {:keys [input-refocus?]}] - (when (and input-refocus? @input-ref) - (.focus ^js @input-ref) - (rf/dispatch [:chat.ui/set-input-refocus false]))) - (defn layout-effect [{:keys [lock-layout?]}] (when-not @lock-layout? @@ -56,17 +48,29 @@ (reanimated/set-shared-value background-y 0) (reanimated/animate opacity 1))) -(defn images-effect +(defn images-or-reply-effect [{:keys [container-opacity]} - images?] - (when images? - (reanimated/animate container-opacity 1))) + {:keys [replying? sending-images? input-ref]} + images? reply?] + (when (or images? reply?) + (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 empty-effect [{:keys [text-value maximized? focused?]} {:keys [container-opacity]} - images?] - (when (and (empty? @text-value) (not images?) (not @maximized?) (not @focused?)) + images? + reply?] + (when (and (empty? @text-value) (not images?) (not reply?) (not @maximized?) (not @focused?)) (reanimated/animate-delay container-opacity constants/empty-opacity 200))) (defn component-will-unmount @@ -76,17 +80,16 @@ (.remove ^js @keyboard-frame-listener)) (defn initialize - [props state animations {:keys [max-height] :as dimensions} chat-input keyboard-height images?] + [props state animations {:keys [max-height] :as dimensions} chat-input keyboard-height images? reply?] (rn/use-effect (fn [] (maximized-effect state animations dimensions chat-input) - (refocus-effect props chat-input) (reenter-screen-effect state dimensions chat-input) (layout-effect state) (kb-default-height-effect state) (background-effect state animations dimensions chat-input) - (images-effect animations images?) - (empty-effect state animations images?) + (images-or-reply-effect animations props images? reply?) + (empty-effect state animations images? reply?) (kb/add-kb-listeners props state animations dimensions keyboard-height) #(component-will-unmount props)) [max-height])) diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/handlers.cljs b/src/status_im2/contexts/chat/bottom_sheet_composer/handlers.cljs index 4cfac79c76..f46e1961de 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/handlers.cljs +++ b/src/status_im2/contexts/chat/bottom_sheet_composer/handlers.cljs @@ -34,7 +34,8 @@ maximized?]} {:keys [height saved-height last-height gradient-opacity container-opacity opacity background-y]} {:keys [lines content-height max-height window-height]} - images] + images + reply] (let [min-height (utils/get-min-height lines) reopen-height (utils/calc-reopen-height text-value min-height content-height saved-height)] (reset! focused? false) @@ -43,7 +44,7 @@ (reanimated/set-shared-value saved-height min-height) (reanimated/animate opacity 0) (js/setTimeout #(reanimated/set-shared-value background-y (- window-height)) 300) - (when (and (empty? @text-value) (empty? images)) + (when (utils/empty-input? @text-value images reply) (reanimated/animate container-opacity constants/empty-opacity)) (reanimated/animate gradient-opacity 0) (reset! lock-selection? true) diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/reply/style.cljs b/src/status_im2/contexts/chat/bottom_sheet_composer/reply/style.cljs new file mode 100644 index 0000000000..45ff73310c --- /dev/null +++ b/src/status_im2/contexts/chat/bottom_sheet_composer/reply/style.cljs @@ -0,0 +1,41 @@ +(ns status-im2.contexts.chat.bottom-sheet-composer.reply.style) + +(defn reply-content + [pin?] + {:padding-right (when-not pin? 10) + :flex 1 + :flex-direction :row}) + +(defn quoted-message + [pin?] + (merge {:flex-direction :row + :flex 1 + :align-items :center} + (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%"}) + +(def reply-deleted-message + {:text-transform :none + :margin-left 4 + :margin-top 2}) diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/reply/view.cljs b/src/status_im2/contexts/chat/bottom_sheet_composer/reply/view.cljs new file mode 100644 index 0000000000..d00e116adf --- /dev/null +++ b/src/status_im2/contexts/chat/bottom_sheet_composer/reply/view.cljs @@ -0,0 +1,152 @@ +(ns status-im2.contexts.chat.bottom-sheet-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] + [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] + [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 (= (first contact-name) "@") + ;; in case of replies + (= (second contact-name) "@")) + (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}} + [quo/icon :i/sad-face {:size 16}] + [quo/text + {:number-of-lines 1 + :size :label + :weight :regular + :accessibility-label :quoted-message + :style style/reply-deleted-message} + (i18n/label :t/message-deleted)]]) + +(defn reply-from + [{:keys [from contact-name current-public-key]}] + (let [display-name (first (rf/sub [:contacts/contact-two-names-by-identity from])) + contact (rf/sub [:contacts/contact-by-address from]) + photo-path (when-not (empty? (:images contact)) (rf/sub [:chats/photo-path from]))] + [rn/view {:style style/reply-from} + [quo/user-avatar + {:full-name display-name + :profile-picture photo-path + :status-indicator? false + :size :xxxs}] + [quo/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? + [quo/icon :i/connector + {:size 16 + :color (colors/theme-colors colors/neutral-40 colors/neutral-60) + :container-style {:position :absolute :left 0 :bottom -4 :width 16 :height 16}}]) + (if (or deleted? deleted-for-me?) + [rn/view {:style (style/quoted-message pin?)} + [reply-deleted-message]] + [rn/view {:style (style/quoted-message pin?)} + [reply-from + {:from from + :identicon identicon + :contact-name contact-name + :current-public-key current-public-key}] + [quo/text + {:number-of-lines 1 + :size :label + :weight :regular + :accessibility-label :quoted-message + :ellipsize-mode :tail + :style (merge + style/message-text + (when (or (= constant/content-type-image content-type) + (= constant/content-type-sticker content-type) + (= constant/content-type-audio content-type)) + {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}))} + (case (or content-type contentType) + constant/content-type-image (if album-images-count + (i18n/label :t/images-albums-count + {:album-images-count album-images-count}) + (i18n/label :t/image)) + constant/content-type-sticker (i18n/label :t/sticker) + constant/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?)) + [quo/button + {:icon true + :type :outline + :size 24 + :on-press #(rf/dispatch [:chat.ui/cancel-message-reply])} + :i/close]) + (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}])])) + +(defn- f-view + [] + (let [reply (rf/sub [:chats/reply-message]) + 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 false])])) + +(defn view + [] + [:f> f-view]) diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/style.cljs b/src/status_im2/contexts/chat/bottom_sheet_composer/style.cljs index 9358eab4b0..83d9b069fc 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/style.cljs +++ b/src/status_im2/contexts/chat/bottom_sheet_composer/style.cljs @@ -6,16 +6,16 @@ [status-im2.contexts.chat.bottom-sheet-composer.constants :as constants])) (defn shadow - [lines] + [elevation?] (if platform/ios? {:shadow-radius 20 :shadow-opacity (colors/theme-colors 0.1 0.7) :shadow-color colors/neutral-100 :shadow-offset {:width 0 :height (colors/theme-colors -4 -8)}} - {:elevation (if (> lines 1) 10 0)})) + {:elevation (if elevation? 10 0)})) (defn sheet-container - [insets opacity lines] + [insets opacity elevation?] (reanimated/apply-animations-to-style {:opacity opacity} (merge @@ -29,7 +29,7 @@ :background-color (colors/theme-colors colors/white colors/neutral-95) :z-index 3 :padding-bottom (:bottom insets)} - (shadow lines)))) + (shadow elevation?)))) (def bar-container {:height constants/bar-container-height diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/utils.cljs b/src/status_im2/contexts/chat/bottom_sheet_composer/utils.cljs index 985d11ade9..855823f095 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/utils.cljs +++ b/src/status_im2/contexts/chat/bottom_sheet_composer/utils.cljs @@ -60,17 +60,21 @@ (if platform/ios? lines (dec lines)))) (defn calc-max-height - [window-height kb-height insets images] + [window-height kb-height insets images? reply?] (let [margin-top (if platform/ios? (:top insets) (+ 10 (:top insets))) max-height (- window-height margin-top kb-height constants/bar-container-height - constants/actions-container-height)] - (if (seq images) - (- max-height constants/images-container-height) - max-height))) + constants/actions-container-height) + max-height (if images? (- max-height constants/images-container-height) max-height) + max-height (if reply? (- max-height constants/reply-container-height) max-height)] + max-height)) (defn empty-input? - [input-text images] - (and (nil? input-text) (empty? images))) + [text images reply?] + (and (empty? text) (empty? images) (not reply?))) + +(defn android-elevation? + [lines images reply?] + (or (> lines 1) (seq images) reply?)) diff --git a/src/status_im2/contexts/chat/bottom_sheet_composer/view.cljs b/src/status_im2/contexts/chat/bottom_sheet_composer/view.cljs index b4716161a7..2e1963cf92 100644 --- a/src/status_im2/contexts/chat/bottom_sheet_composer/view.cljs +++ b/src/status_im2/contexts/chat/bottom_sheet_composer/view.cljs @@ -9,6 +9,7 @@ [utils.i18n :as i18n] [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] [utils.re-frame :as rf] [status-im2.contexts.chat.bottom-sheet-composer.utils :as utils] [status-im2.contexts.chat.bottom-sheet-composer.constants :as constants] @@ -29,7 +30,9 @@ :keyboard-frame-listener (atom nil) :keyboard-hide-listener (atom nil) :emoji-kb-extra-height (atom nil) - :saved-emoji-kb-extra-height (atom nil)} + :saved-emoji-kb-extra-height (atom nil) + :replying? (atom nil) + :sending-images? (atom nil)} state {:text-value (reagent/atom "") :cursor-position (reagent/atom 0) :saved-cursor-position (reagent/atom 0) @@ -43,6 +46,7 @@ [:f> (fn [] (let [images (rf/sub [:chats/sending-image]) + reply (rf/sub [:chats/reply-message]) {:keys [input-text input-content-height] :as chat-input} (rf/sub [:chats/current-chat-input]) content-height (reagent/atom (or input-content-height @@ -53,7 +57,8 @@ max-height (utils/calc-max-height window-height kb-height insets - images) + (seq images) + reply) lines (utils/calc-lines @content-height) max-lines (utils/calc-lines max-height) initial-height (if (> lines 1) @@ -64,7 +69,8 @@ :container-opacity (reanimated/use-shared-value (if (utils/empty-input? input-text - images) + images + reply) 0.7 1)) :height (reanimated/use-shared-value @@ -83,20 +89,25 @@ :window-height window-height :lines lines :max-lines max-lines} - show-bottom-gradient? (utils/show-bottom-gradient? state dimensions)] + show-bottom-gradient? (utils/show-bottom-gradient? state dimensions) + android-elevation? (utils/android-elevation? lines images reply)] (effects/initialize props state animations dimensions chat-input keyboard-height - (seq images)) + (seq images) + reply) [gesture/gesture-detector {:gesture (drag-gesture/drag-gesture props state animations dimensions keyboard-shown)} [reanimated/view - {:style (style/sheet-container insets (:container-opacity animations) lines) + {:style (style/sheet-container insets + (:container-opacity animations) + android-elevation?) :on-layout #(handler/layout % state blur-height)} [sub-view/bar] + [reply/view] [reanimated/touchable-opacity {:active-opacity 1 :on-press (when @(:input-ref props) #(.focus ^js @(:input-ref props))) @@ -106,7 +117,7 @@ {: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) + :on-blur #(handler/blur state animations dimensions images reply) :on-content-size-change #(handler/content-size-change % state animations 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 index ac06143466..053cfa2e5e 100644 --- a/src/status_im2/contexts/chat/messages/list/new_temp_view.cljs +++ b/src/status_im2/contexts/chat/messages/list/new_temp_view.cljs @@ -150,22 +150,27 @@ {:position :absolute :bottom (+ (:bottom insets) composer.constants/composer-default-height 6)}]]))) +(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> - (fn [] - (let [keyboard-show-listener (atom nil) - keyboard-hide-listener (atom nil) - keyboard-shown (atom false)] - (rn/use-effect - (fn [] - (reset! keyboard-show-listener (.addListener rn/keyboard - "keyboardWillShow" - #(reset! keyboard-shown true))) - (reset! keyboard-hide-listener (.addListener rn/keyboard - "keyboardWillHide" - #(reset! keyboard-shown false))) - (fn [] - (.remove ^js @keyboard-show-listener) - (.remove ^js @keyboard-hide-listener)))) - [messages-list-content chat insets keyboard-shown]))]) + [: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 347b81f2bb..a05d5dae43 100644 --- a/src/status_im2/contexts/chat/messages/view.cljs +++ b/src/status_im2/contexts/chat/messages/view.cljs @@ -4,11 +4,14 @@ [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.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] @@ -75,10 +78,15 @@ :keyboardVerticalOffset (- (:bottom insets))} [page-nav] [pin.banner/banner chat-id] - [messages.list/messages-list chat insets] + (if config/new-composer-enabled? + [messages.list.new/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] - [:f> composer/f-composer chat-id insets])])) + (if config/new-composer-enabled? + [bottom-sheet-composer/bottom-sheet-composer insets] + [:f> composer/f-composer chat-id insets]))])) + (defn chat []