From 00fbb0dee6af2ef6e900a720841980f458484c2b Mon Sep 17 00:00:00 2001 From: flexsurfer Date: Mon, 17 Oct 2022 15:44:48 +0200 Subject: [PATCH] Introduce new ui2 namespace, move chat to new namespace and remove new UI toggle from UI (#14156) * enable new ui by default, introduce new ui2 namespace, move chat to the new namespace --- src/quo/components/bottom_sheet/style.cljs | 9 +- src/quo/components/bottom_sheet/view.cljs | 8 +- src/quo/components/list/item.cljs | 59 +- src/status_im/chat/models/input.cljs | 13 +- src/status_im/core.cljs | 3 - src/status_im/multiaccounts/login/core.cljs | 15 +- src/status_im/multiaccounts/logout/core.cljs | 1 + src/status_im/navigation2.cljs | 16 +- src/status_im/navigation2/screens.cljs | 9 +- src/status_im/subs/home.cljs | 2 +- src/status_im/transport/message/core.cljs | 2 +- src/status_im/ui/components/topnav.cljs | 2 +- .../ui/screens/advanced_settings/views.cljs | 11 +- .../ui/screens/appearance/views.cljs | 2 +- .../ui/screens/chat/components/edit.cljs | 44 +- .../ui/screens/chat/components/input.cljs | 334 +------- .../ui/screens/chat/components/reply.cljs | 97 +-- .../ui/screens/chat/components/style.cljs | 79 +- .../ui/screens/chat/message/message.cljs | 619 +++++++------- .../ui/screens/chat/message/message_old.cljs | 702 ---------------- .../screens/chat/message/pinned_message.cljs | 2 +- .../ui/screens/chat/pinned_messages.cljs | 2 +- src/status_im/ui/screens/chat/views.cljs | 248 +----- src/status_im/ui/screens/ens/views.cljs | 2 +- src/status_im/ui/screens/home/views.cljs | 6 +- .../ui/screens/profile/group_chat/views.cljs | 2 +- .../profile/visibility_status/utils.cljs | 29 +- .../profile/visibility_status/views.cljs | 5 +- src/status_im/ui/screens/screens.cljs | 6 +- src/status_im/ui/screens/status/views.cljs | 2 +- src/status_im/ui/screens/views.cljs | 2 +- .../ui2/screens/chat/components/reply.cljs | 89 ++ .../ui2/screens/chat/composer/input.cljs | 178 ++++ .../ui2/screens/chat/composer/mentions.cljs | 56 ++ .../ui2/screens/chat/composer/reply.cljs | 21 + .../ui2/screens/chat/composer/style.cljs | 82 ++ .../ui2/screens/chat/composer/view.cljs | 194 +++++ .../ui2/screens/chat/messages/message.cljs | 773 ++++++++++++++++++ .../ui2/screens/chat/messages/view.cljs | 191 +++++ src/status_im/ui2/screens/chat/view.cljs | 85 ++ src/status_im/utils/config.cljs | 22 +- src/status_im/utils/image_server.cljs | 15 +- 42 files changed, 2117 insertions(+), 1922 deletions(-) delete mode 100644 src/status_im/ui/screens/chat/message/message_old.cljs create mode 100644 src/status_im/ui2/screens/chat/components/reply.cljs create mode 100644 src/status_im/ui2/screens/chat/composer/input.cljs create mode 100644 src/status_im/ui2/screens/chat/composer/mentions.cljs create mode 100644 src/status_im/ui2/screens/chat/composer/reply.cljs create mode 100644 src/status_im/ui2/screens/chat/composer/style.cljs create mode 100644 src/status_im/ui2/screens/chat/composer/view.cljs create mode 100644 src/status_im/ui2/screens/chat/messages/message.cljs create mode 100644 src/status_im/ui2/screens/chat/messages/view.cljs create mode 100644 src/status_im/ui2/screens/chat/view.cljs diff --git a/src/quo/components/bottom_sheet/style.cljs b/src/quo/components/bottom_sheet/style.cljs index fcf7c0571e..49a7c442ef 100644 --- a/src/quo/components/bottom_sheet/style.cljs +++ b/src/quo/components/bottom_sheet/style.cljs @@ -1,6 +1,5 @@ (ns quo.components.bottom-sheet.style (:require [quo.design-system.colors :as colors] - [quo2.foundations.colors :as quo2.colors] [quo.design-system.spacing :as spacing])) (def border-radius 16) @@ -25,12 +24,8 @@ :bottom 0}) (defn content-container - [window-height new-ui?] - {:background-color (if new-ui? - (quo2.colors/theme-colors - quo2.colors/white - quo2.colors/neutral-90) - (:ui-background @colors/theme)) + [window-height] + {:background-color (:ui-background @colors/theme) :border-top-left-radius border-radius :border-top-right-radius border-radius :height (* window-height 2)}) diff --git a/src/quo/components/bottom_sheet/view.cljs b/src/quo/components/bottom_sheet/view.cljs index f128d5500a..68b9fc4595 100644 --- a/src/quo/components/bottom_sheet/view.cljs +++ b/src/quo/components/bottom_sheet/view.cljs @@ -8,8 +8,7 @@ [quo.components.safe-area :as safe-area] [quo.components.bottom-sheet.style :as styles] [quo.gesture-handler :as gesture-handler] - [quo.design-system.colors :as colors] - [status-im.utils.config :as config])) + [quo.design-system.colors :as colors])) (def opacity-coeff 0.8) (def close-duration 150) @@ -102,8 +101,7 @@ {:inputRange [(animated/multiply open-snap-point opacity-coeff) 0] :outputRange [1 0] :extrapolate (:clamp animated/extrapolate)}))) - []) - new-ui? @config/new-ui-enabled?] + [])] (animated/code! (fn [] (animated/cond* (animated/and* (animated/eq master-state (:end gesture-handler/states)) @@ -187,7 +185,7 @@ (when platform/ios? {:opacity opacity :background-color (:backdrop @colors/theme)}))}]] - [animated/view {:style (merge (styles/content-container window-height new-ui?) + [animated/view {:style (merge (styles/content-container window-height) {:transform [{:translateY (if (= sheet-height max-height) (animated/add translate-y keyboard-height-android-delta) translate-y)} diff --git a/src/quo/components/list/item.cljs b/src/quo/components/list/item.cljs index 9eec038758..1bfebb5117 100644 --- a/src/quo/components/list/item.cljs +++ b/src/quo/components/list/item.cljs @@ -8,25 +8,21 @@ [quo.components.text :as text] [quo.components.controls.view :as controls] [quo.components.tooltip :as tooltip] - ;; FIXME: [status-im.ui.components.icons.icons :as icons] - [quo2.foundations.colors :as quo2.colors] - [quo2.components.icon :as quo.icons] - [status-im.utils.config :as config] [quo.components.animated.pressable :as animated])) (defn themes [theme] (case theme - :main {:icon-color (:icon-04 @colors/theme) - :icon-bg-color (:interactive-02 @colors/theme) - :active-background (:interactive-02 @colors/theme) - :passive-background (:ui-background @colors/theme) - :text-color (:text-01 @colors/theme)} - :accent {:icon-color (:icon-04 @colors/theme) - :icon-bg-color (:interactive-02 @colors/theme) - :active-background (:interactive-02 @colors/theme) - :passive-background (:ui-background @colors/theme) - :text-color (:text-04 @colors/theme)} + :main {:icon-color (:icon-04 @colors/theme) + :icon-bg-color (:interactive-02 @colors/theme) + :active-background (:interactive-02 @colors/theme) + :passive-background (:ui-background @colors/theme) + :text-color (:text-01 @colors/theme)} + :accent {:icon-color (:icon-04 @colors/theme) + :icon-bg-color (:interactive-02 @colors/theme) + :active-background (:interactive-02 @colors/theme) + :passive-background (:ui-background @colors/theme) + :text-color (:text-04 @colors/theme)} :negative {:icon-color (:negative-01 @colors/theme) :icon-bg-color (:negative-02 @colors/theme) :active-background (:negative-02 @colors/theme) @@ -41,17 +37,7 @@ :icon-bg-color (:ui-01 @colors/theme) :active-background (:ui-01 @colors/theme) :passive-background (:ui-background @colors/theme) - :text-color (:text-02 @colors/theme)} - :light {:icon-color quo2.colors/neutral-50 - :icon-bg-color quo2.colors/white - :text-color quo2.colors/neutral-100 - :active-background quo2.colors/neutral-10 - :passive-background quo2.colors/white} - :dark {:icon-color quo2.colors/neutral-40 - :icon-bg-color quo2.colors/neutral-90 - :text-color quo2.colors/white - :active-background quo2.colors/neutral-70 - :passive-background quo2.colors/neutral-90})) + :text-color (:text-02 @colors/theme)})) (defn size->icon-size [size] (case size @@ -80,26 +66,19 @@ (defn icon-column [{:keys [icon icon-bg-color icon-color size icon-container-style]}] (when icon - (let [icon-size (size->icon-size size) - new-ui? @config/new-ui-enabled?] + (let [icon-size (size->icon-size size)] [rn/view {:style (or icon-container-style (:tiny spacing/padding-horizontal))} (cond (vector? icon) icon (keyword? icon) - (if new-ui? - [quo.icons/icon icon {:container-style {:align-items :center - :justify-content :center} - :color icon-color - :size 20 - :resize-mode :center}] - [rn/view {:style {:width icon-size - :height icon-size - :align-items :center - :justify-content :center - :border-radius (/ icon-size 2) - :background-color icon-bg-color}} - [icons/icon icon {:color icon-color}]]))]))) + [rn/view {:style {:width icon-size + :height icon-size + :align-items :center + :justify-content :center + :border-radius (/ icon-size 2) + :background-color icon-bg-color}} + [icons/icon icon {:color icon-color}]])]))) (defn title-column [{:keys [title text-color subtitle subtitle-max-lines subtitle-secondary diff --git a/src/status_im/chat/models/input.cljs b/src/status_im/chat/models/input.cljs index 9168bbd4b5..6392556799 100644 --- a/src/status_im/chat/models/input.cljs +++ b/src/status_im/chat/models/input.cljs @@ -281,12 +281,13 @@ {:events [:contacts/send-contact-request]} [{:keys [db] :as cofx} public-key message] (fx/merge cofx - {:chat.ui/clear-inputs nil - ::json-rpc/call [{:method "wakuext_sendContactRequest" - :js-response true - :params [{:id public-key :message message}] - :on-error #(log/warn "failed to send a contact request" %) - :on-success #(re-frame/dispatch [:transport/message-sent %])}]} + {:chat.ui/clear-inputs nil + :chat.ui/clear-inputs-old nil + ::json-rpc/call [{:method "wakuext_sendContactRequest" + :js-response true + :params [{:id public-key :message message}] + :on-error #(log/warn "failed to send a contact request" %) + :on-success #(re-frame/dispatch [:transport/message-sent %])}]} (mentions/clear-mentions) (mentions/clear-cursor) (clean-input (:current-chat-id db)) diff --git a/src/status_im/core.cljs b/src/status_im/core.cljs index 112b1d06de..73fbfc09c9 100644 --- a/src/status_im/core.cljs +++ b/src/status_im/core.cljs @@ -5,7 +5,6 @@ [re-frame.core :as re-frame] [re-frame.interop :as interop] [reagent.impl.batching :as batching] - [status-im.async-storage.core :as async-storage] status-im.events [status-im.i18n.i18n :as i18n] [status-im.native-module.core :as status] @@ -40,8 +39,6 @@ (utils.universal-links/initialize) - (async-storage/get-item :new-ui-enabled? #(reset! config/new-ui-enabled? %)) - ;;DEV (snoopy/subscribe!) (when (and js/goog.DEBUG platform/ios? DevSettings) diff --git a/src/status_im/multiaccounts/login/core.cljs b/src/status_im/multiaccounts/login/core.cljs index bba88824c2..7173ff6250 100644 --- a/src/status_im/multiaccounts/login/core.cljs +++ b/src/status_im/multiaccounts/login/core.cljs @@ -468,7 +468,7 @@ "Decides which root should be initialised depending on user and app state" [db] (if (get db :tos/accepted?) - (re-frame/dispatch [:init-root (if @config/new-ui-enabled? :home-stack :chat-stack)]) + (re-frame/dispatch [:init-root (if config/new-ui-enabled? :home-stack :chat-stack)]) (re-frame/dispatch [:init-root :tos]))) (fx/defn login-only-events @@ -515,11 +515,14 @@ (multiaccounts/switch-preview-privacy-mode-flag) (link-preview/request-link-preview-whitelist) (logging/set-log-level (:log-level multiaccount)) - ;; if it's a first account, the ToS will be accepted at welcome carousel - ;; if not a first account, the ToS might have been accepted by other account logins - (if (or first-account? tos-accepted?) - (navigation/init-root :onboarding-notification) - (navigation/init-root :tos))))) + + (if config/new-ui-enabled? + (navigation/init-root :home-stack) + ;; if it's a first account, the ToS will be accepted at welcome carousel + ;; if not a first account, the ToS might have been accepted by other account logins + (if (or first-account? tos-accepted?) + (navigation/init-root :onboarding-notification) + (navigation/init-root :tos)))))) (defn- keycard-setup? [cofx] (boolean (get-in cofx [:db :keycard :flow]))) diff --git a/src/status_im/multiaccounts/logout/core.cljs b/src/status_im/multiaccounts/logout/core.cljs index 0a63ff475f..81bab21553 100644 --- a/src/status_im/multiaccounts/logout/core.cljs +++ b/src/status_im/multiaccounts/logout/core.cljs @@ -16,6 +16,7 @@ (fx/merge cofx {:init-root-fx :progress :chat.ui/clear-inputs nil + :chat.ui/clear-inputs-old nil :new-ui/reset-bottom-tabs nil :hide-popover nil ::logout nil diff --git a/src/status_im/navigation2.cljs b/src/status_im/navigation2.cljs index 80eb8018b2..8307179e2d 100644 --- a/src/status_im/navigation2.cljs +++ b/src/status_im/navigation2.cljs @@ -1,21 +1,10 @@ (ns status-im.navigation2 (:require [status-im.utils.fx :as fx] [status-im.reloader :as reloader] - [status-im.utils.config :as config] - [status-im.utils.datetime :as datetime] - [status-im.async-storage.core :as async-storage])) + [status-im.utils.datetime :as datetime])) (def parent-stack (atom :home-stack)) -(fx/defn toggle-new-ui - {:events [:toggle-new-ui]} - [_] - (swap! config/new-ui-enabled? not) - (reloader/reload) - {:new-ui/reset-bottom-tabs nil - :dispatch [:init-root (if @config/new-ui-enabled? :home-stack :chat-stack)] - ::async-storage/set! {:new-ui-enabled? @config/new-ui-enabled?}}) - (fx/defn reload-new-ui {:events [:reload-new-ui]} [_] @@ -54,9 +43,8 @@ (fx/defn navigate-to-nav2 {:events [:navigate-to-nav2]} - [{:keys [db] :as cofx} go-to-view-id id screen-params from-switcher?] + [{:keys [db]} go-to-view-id id _ from-switcher?] (let [view-id (:view-id db) - stacks (:navigation2/navigation2-stacks db) from-home? (= view-id :chat-stack)] (if from-switcher? (navigate-from-switcher go-to-view-id id db from-home?) diff --git a/src/status_im/navigation2/screens.cljs b/src/status_im/navigation2/screens.cljs index 173bc0fb2a..cbca1d8aed 100644 --- a/src/status_im/navigation2/screens.cljs +++ b/src/status_im/navigation2/screens.cljs @@ -1,14 +1,15 @@ (ns status-im.navigation2.screens - (:require [status-im.ui.screens.chat.views :as chat] + (:require [status-im.ui2.screens.chat.view :as chat] [status-im.switcher.home-stack :as home-stack] [status-im.navigation2.stack-with-switcher :as stack-with-switcher])) ;; We have to use the home screen name :chat-stack for now, for compatibility with navigation.cljs -(def screens [{:name :chat-stack ;; TODO(parvesh) - rename to home-stack +(def screens [{:name :chat-stack ;; TODO(parvesh) - rename to home-stack :insets {:top false} :component home-stack/home}]) ;; These screens will overwrite navigation/screens.cljs screens on enabling new UI toggle (def screen-overwrites - [{:name :chat - :component #(stack-with-switcher/overlap-stack chat/chat :chat)}]) + [{:name :chat + :options {:topBar {:visible false}} + :component #(stack-with-switcher/overlap-stack chat/chat :chat)}]) diff --git a/src/status_im/subs/home.cljs b/src/status_im/subs/home.cljs index 81048ac96e..b3eba3fe90 100644 --- a/src/status_im/subs/home.cljs +++ b/src/status_im/subs/home.cljs @@ -13,7 +13,7 @@ :<- [:home-items-show-number] (fn [[search-filter filtered-chats communities view-id home-items-show-number]] (if (or (= view-id :home) - (and @config/new-ui-enabled? (= view-id :chat-stack))) + (and config/new-ui-enabled? (= view-id :chat-stack))) (let [communities-count (count communities) chats-count (count filtered-chats) ;; If we have both communities & chats we want to display diff --git a/src/status_im/transport/message/core.cljs b/src/status_im/transport/message/core.cljs index 41728dc131..81473f2541 100644 --- a/src/status_im/transport/message/core.cljs +++ b/src/status_im/transport/message/core.cljs @@ -72,7 +72,7 @@ (do (js-delete response-js "activityCenterNotifications") (fx/merge cofx - (if (and @config/new-ui-enabled? @config/new-activity-center-enabled?) + (if config/new-activity-center-enabled? (->> activity-notifications types/js->clj (map data-store.activities/<-rpc) diff --git a/src/status_im/ui/components/topnav.cljs b/src/status_im/ui/components/topnav.cljs index 0b245ee586..598aa0eb1a 100644 --- a/src/status_im/ui/components/topnav.cljs +++ b/src/status_im/ui/components/topnav.cljs @@ -39,7 +39,7 @@ :accessibility-label :notifications-button :on-press #(do (re-frame/dispatch [:mark-all-activity-center-notifications-as-read]) - (if (and @config/new-ui-enabled? @config/new-activity-center-enabled?) + (if config/new-activity-center-enabled? (re-frame/dispatch [:navigate-to :activity-center]) (re-frame/dispatch [:navigate-to :notifications-center])))} :main-icons2/notifications] diff --git a/src/status_im/ui/screens/advanced_settings/views.cljs b/src/status_im/ui/screens/advanced_settings/views.cljs index f3b51904bb..92473c5acd 100644 --- a/src/status_im/ui/screens/advanced_settings/views.cljs +++ b/src/status_im/ui/screens/advanced_settings/views.cljs @@ -14,7 +14,6 @@ wakuv2-flag current-fleet webview-debug - new-ui-enabled? mutual-contact-requests-enabled?]}] (keep identity @@ -116,14 +115,7 @@ #(re-frame/dispatch [:multiaccounts.ui/switch-mutual-contact-requests-enabled (not mutual-contact-requests-enabled?)]) :accessory :switch - :active mutual-contact-requests-enabled?} - {:size :small - :title (i18n/label :t/new-ui) - :accessibility-label :new-ui-toggle - :container-margin-bottom 8 - :on-press #(re-frame/dispatch [:toggle-new-ui]) - :accessory :switch - :active new-ui-enabled?}])) + :active mutual-contact-requests-enabled?}])) (defn- flat-list-data [options] (normal-mode-settings-data options)) @@ -154,7 +146,6 @@ :wakuv2-flag wakuv2-flag :waku-bloom-filter-mode waku-bloom-filter-mode :webview-debug webview-debug - :new-ui-enabled? @config/new-ui-enabled? :mutual-contact-requests-enabled? mutual-contact-requests-enabled?}) :key-fn (fn [_ i] (str i)) :render-fn render-item}])) diff --git a/src/status_im/ui/screens/appearance/views.cljs b/src/status_im/ui/screens/appearance/views.cljs index da7259534f..38e79e34f6 100644 --- a/src/status_im/ui/screens/appearance/views.cljs +++ b/src/status_im/ui/screens/appearance/views.cljs @@ -12,7 +12,7 @@ [react/touchable-highlight {:on-press (fn [] (re-frame/dispatch [:multiaccounts.ui/appearance-switched theme]) - (re-frame/dispatch (if @config/new-ui-enabled? [:reload-new-ui] [:init-root :chat-stack])) + (re-frame/dispatch (if config/new-ui-enabled? [:reload-new-ui] [:init-root :chat-stack])) (re-frame/dispatch [:navigate-change-tab :profile]) (js/setTimeout #(re-frame/dispatch [:navigate-to :appearance]) 1000))} [react/view (merge {:align-items :center :padding 8 :border-radius 20} diff --git a/src/status_im/ui/screens/chat/components/edit.cljs b/src/status_im/ui/screens/chat/components/edit.cljs index 9ab7566000..ffabf68cd4 100644 --- a/src/status_im/ui/screens/chat/components/edit.cljs +++ b/src/status_im/ui/screens/chat/components/edit.cljs @@ -7,19 +7,16 @@ [quo.components.animated.pressable :as pressable] [status-im.ui.components.icons.icons :as icons] [status-im.ui.screens.chat.components.style :as styles] - [re-frame.core :as re-frame] - [quo2.foundations.colors :as quo2.colors :refer [theme-colors]] - [quo2.components.buttons.button :as quo2.button] - [quo2.components.markdown.text :as quo2.text])) + [re-frame.core :as re-frame])) (defn input-focus [text-input-ref] (some-> ^js (quo.react/current-ref text-input-ref) .focus)) -(defn edit-message-old [] +(defn edit-message [] [rn/view {:style {:flex-direction :row}} [rn/view {} [icons/icon :tiny-icons/tiny-edit {:container-style {:margin-top 5}}]] - [rn/view {:style (styles/reply-content-old)} + [rn/view {:style (styles/reply-content)} [quo/text {:weight :medium :number-of-lines 1} (i18n/label :t/editing-message)]] @@ -29,24 +26,6 @@ [icons/icon :main-icons/close-circle {:container-style (styles/close-button) :color (:icon-02 @quo.colors/theme)}]]]]) -(defn edit-message [] - [rn/view {:style {:flex-direction :row :height 24}} - [rn/view {:style (styles/reply-content false)} - [icons/icon :main-icons/edit-connector {:color (theme-colors quo2.colors/neutral-40 quo2.colors/neutral-60) - :container-style {:position :absolute :left 10 :bottom -4 :width 16 :height 16}}] - [rn/view {:style {:position :absolute :left 36 :right 54 :top 3 :flex-direction :row :align-items :center}} - [quo2.text/text {:weight :medium - :size :paragraph-2} - (i18n/label :t/editing-message)]]] - [quo2.button/button {:width 24 - :size 24 - :type :outline - :accessibility-label :reply-cancel-button - :on-press #(re-frame/dispatch [:chat.ui/cancel-message-edit])} - [icons/icon :main-icons/close {:width 16 - :height 16 - :color (theme-colors quo2.colors/neutral-100 quo2.colors/neutral-40)}]]]) - (defn focus-input-on-edit [edit had-edit text-input-ref] ;;when we show edit we focus input (when-not (= edit @had-edit) @@ -54,13 +33,6 @@ (when edit (js/setTimeout #(input-focus text-input-ref) 250)))) -(defn edit-message-wrapper-old [edit] - [rn/view {:style {:padding-horizontal 15 - :border-top-width 1 - :border-top-color (:ui-01 @quo.colors/theme) - :padding-vertical 8}} - [edit-message-old edit]]) - (defn edit-message-wrapper [edit] [rn/view {:style {:padding-horizontal 15 :border-top-width 1 @@ -68,18 +40,10 @@ :padding-vertical 8}} [edit-message edit]]) -(defn edit-message-auto-focus-wrapper-old [text-input-ref] - (let [had-edit (atom nil)] - (fn [] - (let [edit @(re-frame/subscribe [:chats/edit-message])] - (focus-input-on-edit edit had-edit text-input-ref) - (when edit - [edit-message-wrapper-old]))))) - (defn edit-message-auto-focus-wrapper [text-input-ref] (let [had-edit (atom nil)] (fn [] (let [edit @(re-frame/subscribe [:chats/edit-message])] (focus-input-on-edit edit had-edit text-input-ref) (when edit - [edit-message-wrapper]))))) + [edit-message-wrapper]))))) \ No newline at end of file diff --git a/src/status_im/ui/screens/chat/components/input.cljs b/src/status_im/ui/screens/chat/components/input.cljs index f49c87d7f9..d8668432b0 100644 --- a/src/status_im/ui/screens/chat/components/input.cljs +++ b/src/status_im/ui/screens/chat/components/input.cljs @@ -1,15 +1,12 @@ (ns status-im.ui.screens.chat.components.input (:require [status-im.ui.components.icons.icons :as icons] [quo.react-native :as rn] - [oops.core :refer [oget]] [quo.react :as quo.react] [quo.platform :as platform] [quo.components.text :as text] [quo.design-system.colors :as colors] - [quo2.foundations.colors :as quo2.colors] [status-im.ui.screens.chat.components.style :as styles] [status-im.utils.fx :as fx] - [status-im.utils.handlers :refer [evt]] [status-im.ui.screens.chat.components.reply :as reply] [status-im.multiaccounts.core :as multiaccounts] [status-im.chat.constants :as chat.constants] @@ -22,11 +19,7 @@ [quo.components.list.item :as list-item] [status-im.ui.screens.chat.photos :as photos] [reagent.core :as reagent] - [clojure.string :as string] - [quo2.components.buttons.button :as quo2.button] - [quo2.reanimated :as reanimated] - [quo2.gesture :as gesture] - [quo.components.safe-area :as safe-area])) + [clojure.string :as string])) (defn input-focus [text-input-ref] (some-> ^js (quo.react/current-ref text-input-ref) .focus)) @@ -90,15 +83,6 @@ :accessibility-label :send-message-button :color (styles/send-icon-color)}])]]) -(defn send-button-old [on-send contact-request] - [rn/touchable-opacity {:on-press-in on-send} - [rn/view {:style (styles/send-message-button)} - (when-not contact-request - [icons/icon :main-icons/arrow-up - {:container-style (styles/send-message-container contact-request) - :accessibility-label :send-message-button - :color (styles/send-icon-color)}])]]) - (defn on-selection-change [timeout-id last-text-change mentionable-users args] (let [selection (.-selection ^js (.-nativeEvent ^js args)) start (.-start selection) @@ -132,7 +116,7 @@ (defonce chat-input-key (reagent/atom 1)) (re-frame/reg-fx - :chat.ui/clear-inputs + :chat.ui/clear-inputs-old (fn [] (reset! input-texts {}) (reset! mentions-enabled {}) @@ -263,7 +247,7 @@ (when platform/android? (re-frame/dispatch [::mentions/calculate-suggestions mentionable-users])))) -(defn text-input-old [{:keys [set-active-panel refs chat-id sending-image]}] +(defn text-input [{:keys [set-active-panel refs chat-id sending-image]}] (let [cooldown-enabled? @(re-frame/subscribe [:chats/current-chat-cooldown-enabled?]) mentionable-users @(re-frame/subscribe [:chats/mentionable-users]) timeout-id (atom nil) @@ -272,7 +256,7 @@ contact-request @(re-frame/subscribe [:chats/sending-contact-request])] [rn/text-input - {:style (styles/text-input-old contact-request) + {:style (styles/text-input contact-request) :ref (:text-input-ref refs) :max-font-size-multiplier 1 :accessibility-label :chat-message-input @@ -302,83 +286,43 @@ text]) (get @input-texts chat-id))])) -(defn text-input [{:keys [set-active-panel refs chat-id sending-image on-content-size-change]}] - (let [cooldown-enabled? @(re-frame/subscribe [:chats/current-chat-cooldown-enabled?]) - mentionable-users @(re-frame/subscribe [:chats/mentionable-users]) - timeout-id (atom nil) - last-text-change (atom nil) - mentions-enabled (get @mentions-enabled chat-id)] - - [rn/text-input - {:style (styles/text-input) - :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 - :on-focus #(set-active-panel nil) - :max-length chat.constants/max-text-size - :placeholder-text-color (:text-02 @colors/theme) - :placeholder (if cooldown-enabled? - (i18n/label :cooldown/text-input-disabled) - (i18n/label :t/type-a-message)) - :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 mentionable-users) - :on-change (partial on-change last-text-change timeout-id mentionable-users refs chat-id sending-image) - :on-text-input (partial on-text-input mentionable-users chat-id)} - (if mentions-enabled - (for [[idx [type text]] (map-indexed - (fn [idx item] - [idx item]) - @(re-frame/subscribe [:chat/input-with-mentions]))] - ^{:key (str idx "_" type "_" text)} - [rn/text (when (= type :mention) {:style {:color quo2.colors/primary-50}}) text]) - (get @input-texts chat-id))])) - (defn mention-item [[public-key {:keys [alias name nickname] :as user}] _ _ text-input-ref] (let [ens-name? (not= alias name)] - [rn/touchable-opacity - {:on-press #(re-frame/dispatch [:chat.ui/select-mention text-input-ref user])} + [list-item/list-item + (cond-> {:icon [photos/member-photo public-key] + :size :small + :text-size :small + :title + [text/text + {:weight :medium + :ellipsize-mode :tail + :number-of-lines 1 + :size :small} + (if nickname + nickname + name) + (when nickname + [text/text + {:weight :regular + :color :secondary + :ellipsize-mode :tail + :size :small} + " " + (when ens-name? + "@") + name])] + :title-text-weight :medium + :on-press + (fn [] + (re-frame/dispatch [:chat.ui/select-mention text-input-ref user]))} - [list-item/list-item - (cond-> {:icon [photos/member-photo public-key] - :size :small - :text-size :small - :title - [text/text - {:weight :medium - :ellipsize-mode :tail - :number-of-lines 1 - :size :small} - (if nickname - nickname - name) - (when nickname - [text/text - {:weight :regular - :color :secondary - :ellipsize-mode :tail - :size :small} - " " - (when ens-name? - "@") - name])] - :title-text-weight :medium} - - ens-name? - (assoc :subtitle alias))]])) + ens-name? + (assoc :subtitle alias))])) (def chat-toolbar-height (reagent/atom nil)) -(defn autocomplete-mentions-old [text-input-ref bottom] +(defn autocomplete-mentions [text-input-ref bottom] (let [suggestions @(re-frame/subscribe [:chat/mention-suggestions])] (when (seq suggestions) (let [height (+ 16 (* 52 (min 4.5 (count suggestions))))] @@ -396,22 +340,6 @@ :render-data text-input-ref :render-fn mention-item}]]])))) -(defn autocomplete-mentions [suggestions] - [:f> - (fn [] - (let [animation (reanimated/use-shared-value 0)] - (quo.react/effect! #(do - (reanimated/set-shared-value animation (reanimated/with-timing (if (seq suggestions) 0 200))))) - [reanimated/view {:style (reanimated/apply-animations-to-style - {:transform [{:translateY animation}]} - {:bottom 0 :position :absolute :z-index 5 :max-height 180})} - [list/flat-list - {:keyboardShouldPersistTaps :always - :data suggestions - :key-fn first - :render-fn mention-item - :content-container-style {:padding-bottom 12}}]]))]) - (defn on-chat-toolbar-layout [^js ev] (reset! chat-toolbar-height (-> ev .-nativeEvent .-layout .-height))) @@ -434,7 +362,7 @@ :active active-panel :set-active set-active-panel}])]) -(defn chat-toolbar-old [{:keys [chat-id]}] +(defn chat-toolbar [{:keys [chat-id]}] (let [actions-ref (quo.react/create-ref) send-ref (quo.react/create-ref) sticker-ref (quo.react/create-ref) @@ -457,15 +385,15 @@ [rn/view {:style (styles/input-container contact-request)} [send-image] [rn/view {:style styles/input-row} - [text-input-old {:chat-id chat-id - :sending-image sending-image - :refs refs - :set-active-panel set-active-panel}] + [text-input {:chat-id chat-id + :sending-image sending-image + :refs refs + :set-active-panel set-active-panel}] ;;SEND button [rn/view {:ref send-ref :style (when-not show-send {:width 0 :right -100})} (when send - [send-button-old #(do (clear-input chat-id refs) - (re-frame/dispatch [:chat.ui/send-current-message])) + [send-button #(do (clear-input chat-id refs) + (re-frame/dispatch [:chat.ui/send-current-message])) contact-request])] ;;STICKERS and AUDIO buttons @@ -483,182 +411,4 @@ :accessibility-label :show-audio-message-icon :active active-panel :input-focus #(input-focus text-input-ref) - :set-active set-active-panel}])])]]])))) - -(defn calculate-y [context keyboard-shown min-y max-y added-value] - (if keyboard-shown - (if (= (:state @context) :max) - max-y - (if (< (:y @context) max-y) - (+ (:y @context) added-value) - (do - (swap! context assoc :state :max) - max-y))) - (do - (swap! context assoc :state :min) - min-y))) - -(defn calculate-y-with-mentions [y max-y max-height chat-id suggestions reply] - (let [input-text (:input-text (get ( (gesture/gesture-pan) - (gesture/on-start - (fn [_] - (if keyboard-shown - (swap! context assoc :pan-y (reanimated/get-shared-value translate-y)) - (input-focus text-input-ref)))) - (gesture/on-update - (fn [evt] - (when keyboard-shown - (swap! context assoc :dy (- (.-translationY evt) (:pdy @context))) - (swap! context assoc :pdy (.-translationY evt)) - (reanimated/set-shared-value - translate-y - (max (min (+ (.-translationY evt) (:pan-y @context)) (- min-y)) (- max-y)))))) - (gesture/on-end - (fn [_] - (when keyboard-shown - (if (< (:dy @context) 0) - (do - (swap! context assoc :state :max) - (input-focus text-input-ref) - (reanimated/set-shared-value translate-y (reanimated/with-timing (- max-y))) - (reanimated/set-shared-value shared-height (reanimated/with-timing max-height)) - (reanimated/set-shared-value bg-opacity (reanimated/with-timing 1))) - (do - (swap! context assoc :state :min) - (reanimated/set-shared-value translate-y (reanimated/with-timing (- min-y))) - (reanimated/set-shared-value shared-height (reanimated/with-timing min-y)) - (reanimated/set-shared-value bg-opacity (reanimated/with-timing 0)) - (re-frame/dispatch [:dismiss-keyboard])))))))) - -(defn get-input-content-change [context translate-y shared-height max-height bg-opacity keyboard-shown min-y max-y] - (fn [evt] - (if (:clear @context) - (do - (swap! context dissoc :clear) - (swap! context assoc :state :min) - (swap! context assoc :y min-y) - (reanimated/set-shared-value translate-y (reanimated/with-timing (- min-y))) - (reanimated/set-shared-value shared-height (reanimated/with-timing min-y)) - (reanimated/set-shared-value bg-opacity (reanimated/with-timing 0))) - (when (not= (:state @context) :max) - (let [new-y (+ min-y (- (max (oget evt "nativeEvent" "contentSize" "height") 22) 22))] - (if (< new-y max-y) - (do - (if (> (- max-y new-y) 120) - (do - (swap! context assoc :state :custom-chat-available) - (reanimated/set-shared-value bg-opacity (reanimated/with-timing 0))) - (do - (reanimated/set-shared-value bg-opacity (reanimated/with-timing 1)) - (swap! context assoc :state :custom-chat-unavailable))) - (swap! context assoc :y new-y) - (when keyboard-shown - (reanimated/set-shared-value - translate-y - (reanimated/with-timing (- new-y))) - (reanimated/set-shared-value - shared-height - (reanimated/with-timing (min new-y max-height))))) - (do - (swap! context assoc :state :max) - (swap! context assoc :y max-y) - (when keyboard-shown - (reanimated/set-shared-value bg-opacity (reanimated/with-timing 1)) - (reanimated/set-shared-value - translate-y - (reanimated/with-timing (- max-y))))))))))) - -(defn chat-input-bottom-sheet [chat-id] - [safe-area/consumer - (fn [insets] - (let [min-y 112 - context (atom {:y min-y ;current y value - :min-y min-y ;minimum y value - :dy 0 ;used for gesture - :pdy 0 ;used for gesture - :state :min ;:min, :custom-chat-available, :custom-chat-unavailable, :max - :clear false}) - keyboard-was-shown (atom false) - text-input-ref (quo.react/create-ref) - send-ref (quo.react/create-ref) - refs {:send-ref send-ref - :text-input-ref text-input-ref}] - (fn [] - [:f> - (fn [] - (let [reply ( keyboard-height 0) keyboard-height 360) (:top insets)) ; 360 - default height - max-height (- max-y 56 (:bottom insets)) ; 56 - top-bar height - added-value (if (and (not (seq suggestions)) reply) 38 0) ; increased height of input box needed when reply - min-y (+ min-y (when reply 38)) - y (get-y-value context keyboard-shown min-y max-y added-value max-height chat-id suggestions reply) - translate-y (reanimated/use-shared-value 0) - shared-height (reanimated/use-shared-value min-y) - bg-opacity (reanimated/use-shared-value 0) - input-content-change (get-input-content-change context translate-y shared-height max-height - bg-opacity keyboard-shown min-y max-y) - bottom-sheet-gesture (get-bottom-sheet-gesture context translate-y (:text-input-ref refs) keyboard-shown - min-y max-y shared-height max-height bg-opacity)] - (quo.react/effect! #(do - (when (and @keyboard-was-shown (not keyboard-shown)) - (swap! context assoc :state :min)) - (reset! keyboard-was-shown keyboard-shown) - (if (#{:max :custom-chat-unavailable} (:state @context)) - (reanimated/set-shared-value bg-opacity (reanimated/with-timing 1)) - (reanimated/set-shared-value bg-opacity (reanimated/with-timing 0))) - (reanimated/set-shared-value translate-y (reanimated/with-timing (- y))) - (reanimated/set-shared-value shared-height (reanimated/with-timing (min y max-height))))) - [reanimated/view {:style (reanimated/apply-animations-to-style - {:height shared-height} - {})} - ;;INPUT MESSAGE bottom sheet - [gesture/gesture-detector {:gesture bottom-sheet-gesture} - [reanimated/view {:style (reanimated/apply-animations-to-style - {:transform [{:translateY translate-y}]} - (styles/new-input-bottom-sheet window-height))} - ;handle - [rn/view {:style (styles/new-bottom-sheet-handle)}] - [reply/reply-message-auto-focus-wrapper (:text-input-ref refs) reply] - [rn/view {:style {:height (- max-y 80 added-value)}} - [text-input {:chat-id chat-id - :on-content-size-change input-content-change - :sending-image false - :refs refs - :set-active-panel #()}]]]] - ;CONTROLS - (when-not (seq suggestions) - [rn/view {:style (styles/new-bottom-sheet-controls insets)} - [quo2.button/button {:icon true :type :outline :size 32} :main-icons2/image] - [rn/view {:width 12}] - [quo2.button/button {:icon true :type :outline :size 32} :main-icons2/reaction] - [rn/view {:flex 1}] - ;;SEND button - [rn/view {:ref send-ref :style (when-not (seq (get @input-texts chat-id)) {:width 0 :right -100})} - [quo2.button/button {:icon true :size 32 :accessibility-label :send-message-button - :on-press #(do (swap! context assoc :clear true) - (clear-input chat-id refs) - (>evt [:chat.ui/send-current-message]))} - :main-icons2/arrow-up]]]) - ;black background - [reanimated/view {:style (reanimated/apply-animations-to-style - {:opacity bg-opacity} - (styles/new-bottom-sheet-background window-height))}] - [autocomplete-mentions suggestions]]))])))]) + :set-active set-active-panel}])])]]])))) \ No newline at end of file diff --git a/src/status_im/ui/screens/chat/components/reply.cljs b/src/status_im/ui/screens/chat/components/reply.cljs index 524ddaf00d..e158f72dc5 100644 --- a/src/status_im/ui/screens/chat/components/reply.cljs +++ b/src/status_im/ui/screens/chat/components/reply.cljs @@ -9,27 +9,13 @@ [status-im.ethereum.stateofus :as stateofus] [status-im.ui.screens.chat.components.style :as styles] [re-frame.core :as re-frame] - [clojure.string :as string] - [quo2.foundations.colors :as quo2.colors :refer [theme-colors]] - [quo2.components.buttons.button :as quo2.button] - [quo2.components.markdown.text :as quo2.text] - [status-im.ui.screens.chat.photos :as photos] - [status-im.constants :as constants])) + [clojure.string :as string])) (def ^:private reply-symbol "↪ ") (defn input-focus [text-input-ref] (some-> ^js (quo.react/current-ref text-input-ref) .focus)) -(defn format-author-old [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)] - (i18n/label :replying-to {:author author}))) - (defn format-author [contact-name] (let [author (if (or (= (aget contact-name 0) "@") ;; in case of replies @@ -37,17 +23,12 @@ (or (stateofus/username contact-name) (subs contact-name 0 81)) contact-name)] - author)) - -(defn format-reply-author-old [from username current-public-key] - (or (and (= from current-public-key) - (str reply-symbol (i18n/label :t/You))) - (str reply-symbol (format-author-old username)))) + (i18n/label :replying-to {:author author}))) (defn format-reply-author [from username current-public-key] (or (and (= from current-public-key) - (i18n/label :t/You)) - (format-author username))) + (str reply-symbol (i18n/label :t/You))) + (str reply-symbol (format-author username)))) (defn get-quoted-text-with-mentions [parsed-text] (string/join @@ -66,67 +47,25 @@ literal)) parsed-text))) -(defn reply-message-old [{:keys [from]}] +(defn reply-message [{:keys [from]}] (let [contact-name @(re-frame/subscribe [:contacts/contact-name-by-identity from]) current-public-key @(re-frame/subscribe [:multiaccount/public-key])] [rn/view {:style {:flex-direction :row}} - [rn/view {:style (styles/reply-content-old)} + [rn/view {:style (styles/reply-content)} [quo/text {:weight :medium :number-of-lines 1 :style {:line-height 18}} - (format-reply-author-old from contact-name current-public-key)]] + (format-reply-author from contact-name current-public-key)]] [rn/view [pressable/pressable {:on-press #(re-frame/dispatch [:chat.ui/cancel-message-reply]) :accessibility-label :cancel-message-reply} [icons/icon :main-icons/close-circle {:container-style (styles/close-button) :color (:icon-02 @quo.colors/theme)}]]]])) -; This component is also used for quoted pinned message as the UI is very similar -(defn reply-message [{:keys [from identicon content-type contentType parsed-text content]} in-chat-input? pin?] - (let [contact-name @(re-frame/subscribe [:contacts/contact-name-by-identity from]) - current-public-key @(re-frame/subscribe [:multiaccount/public-key]) - content-type (or content-type contentType)] - [rn/view {:style {:flex-direction :row :height (when-not pin? 24)}} - [rn/view {:style (styles/reply-content pin?)} - (when-not pin? [icons/icon :main-icons/connector {:color (theme-colors quo2.colors/neutral-40 quo2.colors/neutral-60) - :container-style {:position :absolute :left 10 :bottom -4 :width 16 :height 16}}]) - [rn/view {:style (styles/quoted-message pin?)} - [photos/member-photo from identicon 16] - [quo2.text/text {:weight :semi-bold - :size :paragraph-2 - :number-of-lines 1 - :style {:margin-left 4}} - (format-reply-author from contact-name current-public-key)] - [quo2.text/text {:number-of-lines 1 - :size :label - :weight :regular - :style (merge {:ellipsize-mode :tail - :text-transform :none - :margin-left 4 - :margin-top 2} - (when (or (= constants/content-type-image content-type) - (= constants/content-type-sticker content-type) - (= constants/content-type-audio content-type)) - {:color (theme-colors quo2.colors/neutral-50 quo2.colors/neutral-40)}))} - (case (or content-type contentType) - constants/content-type-image "Image" - constants/content-type-sticker "Sticker" - constants/content-type-audio "Audio" - (get-quoted-text-with-mentions (or parsed-text (:parsed-text content))))]]] - (when in-chat-input? - [quo2.button/button {:width 24 - :size 24 - :type :outline - :accessibility-label :reply-cancel-button - :on-press #(re-frame/dispatch [:chat.ui/cancel-message-reply])} - [icons/icon :main-icons/close {:width 16 - :height 16 - :color (theme-colors quo2.colors/neutral-100 quo2.colors/neutral-40)}]])])) - (defn send-image [images] [rn/view {:style (styles/reply-container-image)} [rn/scroll-view {:horizontal true - :style (styles/reply-content false)} + :style (styles/reply-content)} (for [{:keys [uri]} (vals images)] ^{:key uri} [rn/image {:source {:uri uri} @@ -148,29 +87,17 @@ ;; A setTimeout of 0 is necessary to ensure the statement is enqueued and will get executed ASAP. (js/setTimeout #(input-focus text-input-ref) 0)))) -(defn reply-message-wrapper-old [reply] +(defn reply-message-wrapper [reply] [rn/view {:style {:padding-horizontal 15 :border-top-width 1 :border-top-color (:ui-01 @quo.colors/theme) :padding-vertical 8}} - [reply-message-old reply]]) + [reply-message reply]]) -(defn reply-message-wrapper [reply] - [rn/view {:style {:padding-horizontal 15 - :padding-vertical 8}} - [reply-message reply true]]) - -(defn reply-message-auto-focus-wrapper-old [text-input-ref] +(defn reply-message-auto-focus-wrapper [text-input-ref] (let [had-reply (atom nil)] (fn [] (let [reply @(re-frame/subscribe [:chats/reply-message])] (focus-input-on-reply reply had-reply text-input-ref) (when reply - [reply-message-wrapper-old reply]))))) - -(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 - [reply-message-wrapper reply])))) + [reply-message-wrapper reply]))))) diff --git a/src/status_im/ui/screens/chat/components/style.cljs b/src/status_im/ui/screens/chat/components/style.cljs index e6b61d1405..5033d060c0 100644 --- a/src/status_im/ui/screens/chat/components/style.cljs +++ b/src/status_im/ui/screens/chat/components/style.cljs @@ -1,9 +1,7 @@ (ns status-im.ui.screens.chat.components.style (:require [quo.platform :as platform] [quo.design-system.colors :as colors] - [quo.design-system.typography :as typography] - [quo2.foundations.colors :as quo2.colors] - [quo2.foundations.typography :as quo2.typography])) + [quo.design-system.typography :as typography])) (defn toolbar [] {:min-height 52 @@ -37,7 +35,7 @@ (when platform/ios? {:padding-top 2}))) -(defn text-input-old [contact-request] +(defn text-input [contact-request] (merge typography/font-regular typography/base {:flex 1 @@ -52,21 +50,6 @@ {:padding-top (if contact-request 10 2) :padding-bottom (if contact-request 5 6)}))) -(defn text-input [] - (merge quo2.typography/font-regular - quo2.typography/paragraph-1 - {:flex 1 - :min-height 34 - :margin 0 - :flex-shrink 1 - :color (:text-01 @colors/theme) - :margin-horizontal 20} - (if platform/android? - {:padding-vertical 8 - :text-align-vertical :top} - {:margin-top 8 - :margin-bottom 8}))) - (defn actions-wrapper [show-send] (merge (when show-send {:width 0 :left -88}) @@ -103,16 +86,11 @@ (defn reply-container [] {:flex-direction :row}) -(defn reply-content-old [] +(defn reply-content [] {:padding-vertical 6 :padding-horizontal 10 :flex 1}) -(defn reply-content [pin?] - {:padding-horizontal (when-not pin? 10) - :flex 1 - :flex-direction :row}) - (defn quoted-message [pin?] (merge {:flex-direction :row :align-items :center @@ -152,53 +130,4 @@ :background-color (colors/get-color :ui-background) :border-top-width 1 :border-top-color (colors/get-color :ui-01) - :z-index 3}) - -(defn new-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 (quo2.colors/theme-colors quo2.colors/white quo2.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 2}))) - -(defn new-bottom-sheet-handle [] - {:width 32 - :height 4 - :background-color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white) - :opacity 0.05 - :border-radius 100 - :align-self :center - :margin-top 8}) - -(defn new-bottom-sheet-controls [insets] - {:flex-direction :row - :padding-horizontal 20 - :elevation 2 - :z-index 2 - :position :absolute - :background-color (quo2.colors/theme-colors quo2.colors/white quo2.colors/neutral-90) - ;these 3 props play together, we need this magic to hide message text in the safe area - :padding-top 10 - :padding-bottom (+ 12 (:bottom insets)) - :bottom (- 2 (:bottom insets))}) - -(defn new-bottom-sheet-background [window-height] - {:pointerEvents :none - :position :absolute - :left 0 - :right 0 - :bottom 0 - :height window-height - :background-color quo2.colors/neutral-95-opa-70 - :z-index 1}) + :z-index 3}) \ No newline at end of file diff --git a/src/status_im/ui/screens/chat/message/message.cljs b/src/status_im/ui/screens/chat/message/message.cljs index 3299d35bdb..d98afc0322 100644 --- a/src/status_im/ui/screens/chat/message/message.cljs +++ b/src/status_im/ui/screens/chat/message/message.cljs @@ -1,40 +1,32 @@ (ns status-im.ui.screens.chat.message.message - (:require [quo.core :as quo] - [quo.design-system.colors :as colors] - [re-frame.core :as re-frame] - [reagent.core :as reagent] - [status-im.chat.models.delete-message-for-me] - [status-im.chat.models.images :as images] - [status-im.chat.models.pin-message :as models.pin-message] - [status-im.chat.models.reactions :as models.reactions] + (:require [re-frame.core :as re-frame] [status-im.constants :as constants] [status-im.i18n.i18n :as i18n] [status-im.react-native.resources :as resources] - [status-im.ui.components.animation :as animation] - [status-im.ui.components.fast-image :as fast-image] + [quo.design-system.colors :as colors] [status-im.ui.components.icons.icons :as icons] [status-im.ui.components.react :as react] - [status-im.ui.screens.chat.bottom-sheets.context-drawer :as message-context-drawer] - [status-im.ui.screens.chat.components.reply :as components.reply] - [status-im.ui.screens.chat.image.preview.views :as preview] - [status-im.ui.screens.chat.message.audio :as message.audio] + [status-im.ui.screens.chat.message.audio-old :as message.audio] + [status-im.chat.models.reactions :as models.reactions] [status-im.ui.screens.chat.message.command :as message.command] - [status-im.ui.screens.chat.message.gap :as message.gap] - [status-im.ui.screens.chat.message.link-preview :as link-preview] - [status-im.ui.screens.chat.message.reactions :as reactions] - [status-im.ui.screens.chat.message.reactions-row :as reaction-row] [status-im.ui.screens.chat.photos :as photos] [status-im.ui.screens.chat.sheets :as sheets] - [status-im.ui.screens.chat.styles.message.message :as style] + [status-im.ui.screens.chat.message.gap :as message.gap] + [status-im.ui.screens.chat.styles.message.message-old :as style] [status-im.ui.screens.chat.utils :as chat.utils] - [status-im.ui.screens.chat.styles.photos :as photos.style] - [status-im.ui.screens.communities.icon :as communities.icon] - [status-im.utils.handlers :refer [>evt]] - [status-im.utils.config :as config] [status-im.utils.security :as security] - [quo2.foundations.typography :as typography] - [quo2.foundations.colors :as quo2.colors]) - + [status-im.ui.screens.chat.message.reactions-old :as reactions] + [status-im.ui.screens.chat.image.preview.views :as preview] + [quo.core :as quo] + [status-im.utils.config :as config] + [reagent.core :as reagent] + [status-im.ui.screens.chat.components.reply :as components.reply] + [status-im.ui.screens.chat.message.link-preview :as link-preview] + [status-im.ui.screens.communities.icon :as communities.icon] + [status-im.ui.components.animation :as animation] + [status-im.chat.models.images :as images] + [status-im.chat.models.pin-message :as models.pin-message] + [status-im.ui.components.fast-image :as fast-image]) (:require-macros [status-im.utils.views :refer [defview letsubs]])) (defn message-timestamp-anim @@ -81,13 +73,13 @@ :height 12 :color (if pinned colors/gray colors/white) :accessibility-label (name outgoing-status)}]) - (when edited-at [react/text {:style (style/message-status-text)} edited-at-text])])) + (when edited-at [react/text {:style (style/message-status-text (and outgoing (not pinned)))} edited-at-text])])) (defn message-timestamp - [{:keys [timestamp-str in-popover?]} show-timestamp?] + [{:keys [timestamp-str in-popover?] :as message} show-timestamp?] (when-not in-popover? ;; We keep track if showing this message in a list in pin-limit-popover (let [anim-opacity (animation/create-value 0)] - [react/animated-view {:style (style/message-timestamp-wrapper) :opacity anim-opacity} + [react/animated-view {:style (style/message-timestamp-wrapper message) :opacity anim-opacity} (when @show-timestamp? (message-timestamp-anim anim-opacity show-timestamp?)) [react/text {:style (style/message-timestamp-text) @@ -95,14 +87,29 @@ timestamp-str]]))) (defview quoted-message - [_ reply pin?] - [react/view {:style (when-not pin? (style/quoted-message-container))} - [components.reply/reply-message reply false pin?]]) + [_ {:keys [from parsed-text image]} outgoing current-public-key public? pinned] + (letsubs [contact-name [:contacts/contact-name-by-identity from]] + [react/view {:style (style/quoted-message-container (and outgoing (not pinned)))} + [react/view {:style style/quoted-message-author-container} + [chat.utils/format-reply-author + from + contact-name + current-public-key + (partial style/quoted-message-author (and outgoing (not pinned))) + (and outgoing (not pinned))]] + (if (and image + ;; Disabling images for public-chats + (not public?)) + [fast-image/fast-image {:style {:width 56 + :height 56 + :background-color :black + :border-radius 4} + :source {:uri image}}] + [react/text {:style (style/quoted-message-text (and outgoing (not pinned))) + :number-of-lines 5} + (components.reply/get-quoted-text-with-mentions parsed-text)])])) -(defn system-text? [content-type] - (= content-type constants/content-type-system-text)) - -(defn render-inline [message-text content-type acc {:keys [type literal destination]}] +(defn render-inline [message-text outgoing pinned content-type acc {:keys [type literal destination]}] (case type "" (conj acc literal) @@ -114,22 +121,22 @@ literal]) "emph" - (conj acc [react/text-class (style/emph-style) literal]) + (conj acc [react/text-class (style/emph-style (and outgoing (not pinned))) literal]) "strong" - (conj acc [react/text-class (style/strong-style) literal]) + (conj acc [react/text-class (style/strong-style (and outgoing (not pinned))) literal]) "strong-emph" - (conj acc [quo/text (style/strong-emph-style) literal]) + (conj acc [quo/text (style/strong-emph-style (and outgoing (not pinned))) literal]) "del" - (conj acc [react/text-class (style/strikethrough-style) literal]) + (conj acc [react/text-class (style/strikethrough-style (and outgoing (not pinned))) literal]) "link" (conj acc [react/text-class {:style - {:color colors/blue + {:color (if (and outgoing (not pinned)) colors/white-persist colors/blue) :text-decoration-line :underline} :on-press #(when (and (security/safe-link? destination) @@ -139,17 +146,17 @@ destination]) "mention" - (conj acc - [react/view {:style {:background-color quo2.colors/primary-50-opa-10 :border-radius 6 :padding-horizontal 3}} - [react/text-class - {:style (merge {:color (if (system-text? content-type) colors/black quo2.colors/primary-50)} - (if (system-text? content-type) typography/font-regular typography/font-medium)) - :on-press (when-not (system-text? content-type) - #(>evt [:chat.ui/show-profile literal]))} - [mention-element literal]]]) + (conj acc [react/text-class + {:style {:color (cond + (= content-type constants/content-type-system-text) colors/black + (and outgoing (not pinned)) colors/mention-outgoing + :else colors/mention-incoming)} + :on-press (when-not (= content-type constants/content-type-system-text) + #(re-frame/dispatch [:chat.ui/show-profile literal]))} + [mention-element literal]]) "status-tag" (conj acc [react/text-class - {:style {:color colors/blue + {:style {:color (if (and outgoing (not pinned)) colors/white-persist colors/blue) :text-decoration-line :underline} :on-press #(re-frame/dispatch @@ -159,19 +166,19 @@ (conj acc literal))) -(defn render-block [{:keys [content content-type in-popover?]} acc +(defn render-block [{:keys [content outgoing content-type pinned in-popover?]} acc {:keys [type ^js literal children]}] (case type "paragraph" (conj acc (reduce - (fn [acc e] (render-inline (:text content) content-type acc e)) - [react/text-class (style/text-style content-type in-popover?)] + (fn [acc e] (render-inline (:text content) outgoing pinned content-type acc e)) + [react/text-class (style/text-style (and outgoing (not pinned)) content-type in-popover?)] children)) "blockquote" - (conj acc [react/view (style/blockquote-style) - [react/text-class (style/blockquote-text-style) + (conj acc [react/view (style/blockquote-style (and outgoing (not pinned))) + [react/text-class (style/blockquote-text-style (and outgoing (not pinned))) (.substring literal 0 (.-length literal))]]) "codeblock" @@ -186,10 +193,10 @@ (defn render-parsed-text [message tree] (reduce (fn [acc e] (render-block message acc e)) [:<>] tree)) -(defn render-parsed-text-with-message-status [{:keys [edited-at in-popover?] :as message} tree] +(defn render-parsed-text-with-message-status [{:keys [outgoing edited-at in-popover?] :as message} tree] (let [elements (render-parsed-text message tree) message-status [react/text {:style (style/message-status-placeholder)} - (str (if (not in-popover?) " " " ") (when (and (not in-popover?) edited-at) edited-at-text))] + (str (if (and outgoing (not in-popover?)) " " " ") (when (and (not in-popover?) edited-at) edited-at-text))] last-element (peek elements)] ;; Using `nth` here as slightly faster than `first`, roughly 30% ;; It's worth considering pure js structures for this code path as @@ -201,10 +208,10 @@ (conj elements message-status)))) (defn unknown-content-type - [{:keys [content-type content] :as message}] + [{:keys [outgoing content-type content] :as message}] [react/view (style/message-view message) [react/text - {:style {:color colors/white-persist}} + {:style {:color (if outgoing colors/white-persist colors/black)}} (if (seq (:text content)) (:text content) (str "Unhandled content-type " content-type))]]) @@ -229,22 +236,28 @@ (let [user-contact @(re-frame/subscribe [:multiaccount/contact]) contact-names @(re-frame/subscribe [:contacts/contact-two-names-by-identity pinned-by])] ;; We append empty spaces to the name as a workaround to make one-line and multi-line label components show correctly - (str " " (if (= pinned-by (user-contact :public-key)) (i18n/label :t/You) (first contact-names))))) + (str " " (if (= pinned-by (user-contact :public-key)) (i18n/label :t/You) (first contact-names))))) -(def pin-icon-width 10) +(def pin-icon-width 9) (def pin-icon-height 15) -(defn pin-icon [] - [icons/icon :main-icons/pin16 {:color (:text-04 @colors/theme) - :height pin-icon-height - :width pin-icon-width}]) - -(defn pinned-by-indicator [pinned-by] - [react/view {:style (style/pin-indicator) +(defn pinned-by-indicator [outgoing display-photo? pinned-by] + [react/view {:style (style/pin-indicator outgoing display-photo?) :accessibility-label :pinned-by} - [pin-icon] - [quo/text {:size :small + [react/view {:style (style/pinned-by-text-icon-container)} + [react/view {:style (style/pin-icon-container)} + [icons/icon :main-icons/pin {:color colors/gray + :height pin-icon-height + :width pin-icon-width + :background-color :red}]] + [quo/text {:weight :regular + :size :small + :color :main + :style (style/pinned-by-text)} + (i18n/label :t/pinned-by)]] + [quo/text {:weight :medium + :size :small :color :main :style (style/pin-author-text)} (pin-author-name pinned-by)]]) @@ -257,11 +270,11 @@ (defview message-author-name [from opts] (letsubs [contact-with-names [:contacts/contact-by-identity from]] - (chat.utils/format-author contact-with-names opts))) + (chat.utils/format-author-old contact-with-names opts))) (defview message-my-name [opts] (letsubs [contact-with-names [:multiaccount/contact]] - (chat.utils/format-author contact-with-names opts))) + (chat.utils/format-author-old contact-with-names opts))) (defview community-content [{:keys [community-id] :as message}] (letsubs [{:keys [name description verified] :as community} [:communities/community community-id] @@ -296,50 +309,34 @@ (defn message-content-wrapper "Author, userpic and delivery wrapper" - [{:keys [last-in-group? + [{:keys [first-in-group? display-photo? display-username? identicon - from in-popover? timestamp-str - deleted-for-me? pinned] + from outgoing in-popover?] :as message} content {:keys [modal close-modal]}] - (let [response-to (:response-to (:content message))] - [react/view {:style (style/message-wrapper message) - :pointer-events :box-none - :accessibility-label :chat-item} - (when (and (seq response-to) (:quoted-message message)) - [quoted-message response-to (:quoted-message message)]) - [react/view {:style (style/message-body) - :pointer-events :box-none} - [react/view (style/message-author-userpic) - (when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned) + [react/view {:style (style/message-wrapper message) + :pointer-events :box-none + :accessibility-label :chat-item} + [react/view {:style (style/message-body message) + :pointer-events :box-none} + (when display-photo? + [react/view (style/message-author-userpic outgoing) + (when first-in-group? [react/touchable-highlight {:on-press #(do (when modal (close-modal)) (re-frame/dispatch [:chat.ui/show-profile from]))} - [photos/member-photo from identicon]])] - [react/view {:style (style/message-author-wrapper)} - (when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned) - [react/view {:style {:flex-direction :row :align-items :center}} - [react/touchable-opacity {:style style/message-author-touchable - :disabled in-popover? - :on-press #(do (when modal (close-modal)) - (re-frame/dispatch [:chat.ui/show-profile from]))} - [message-author-name from {:modal modal}]] - [react/text - {:style (merge - {:padding-left 5 - :margin-top 2} - (style/message-timestamp-text)) - :accessibility-label :message-timestamp} - timestamp-str]]) - ;; MESSAGE CONTENT - ;; TODO(yqrashawn): wait for system message component to display deleted for me UI - (if deleted-for-me? - [react/view {:style {:border-width 2 - :border-color :red}} - content] - content) - [link-preview/link-preview-wrapper (:links (:content message)) false false]]] - ; delivery status - [react/view (style/delivery-status) - [message-delivery-status message]]])) + [photos/member-photo from identicon]])]) + [react/view {:style (style/message-author-wrapper outgoing display-photo? in-popover?)} + (when display-username? + [react/touchable-opacity {:style style/message-author-touchable + :disabled in-popover? + :on-press #(do (when modal (close-modal)) + (re-frame/dispatch [:chat.ui/show-profile from]))} + [message-author-name from {:modal modal}]]) + ;;MESSAGE CONTENT + content + [link-preview/link-preview-wrapper (:links (:content message)) outgoing false]]] + ; delivery status + [react/view (style/delivery-status outgoing) + [message-delivery-status message]]]) (def image-max-width 260) (def image-max-height 192) @@ -356,13 +353,13 @@ (swap! dimensions assoc :loaded true))))) (defn message-content-image - [{:keys [content]} _] + [{:keys [content outgoing in-popover?] :as message} + {:keys [on-long-press]}] (let [dimensions (reagent/atom {:width image-max-width :height image-max-height :loaded false}) visible (reagent/atom false) uri (:image content)] - (fn [{:keys [in-popover?] :as message} - {:keys [on-long-press]}] - (let [style-opts {:outgoing false + (fn [] + (let [style-opts {:outgoing outgoing :opacity (if (:loaded @dimensions) 1 0) :width (:width @dimensions) :height (:height @dimensions)}] @@ -374,7 +371,7 @@ [react/touchable-highlight {:on-press (fn [] (reset! visible true) (react/dismiss-keyboard!)) - :on-long-press @on-long-press + :on-long-press on-long-press :disabled in-popover?} [react/view {:style (style/image-message style-opts) :accessibility-label :image-message} @@ -408,6 +405,9 @@ [react/view (style/message-view-content) [render-parsed-text message (:parsed-text content)]]]]]) +(def message-height-px 200) +(def max-message-height-px 150) + (defn pin-message [{:keys [chat-id pinned] :as message}] (let [pinned-messages @(re-frame/subscribe [:chats/pinned chat-id])] (if (and (not pinned) (> (count pinned-messages) 2)) @@ -422,154 +422,135 @@ (on-long-press (concat (when (and outgoing edit-enabled) - [{:type :main - :on-press #(re-frame/dispatch [:chat.ui/edit-message message]) - :label (i18n/label :t/edit-message) - :icon :main-icons/edit-context20 + [{:on-press #(re-frame/dispatch [:chat.ui/edit-message message]) + :label (i18n/label :t/edit) :id :edit}]) (when show-input? - [{:type :main - :on-press #(re-frame/dispatch [:chat.ui/reply-to-message message]) + [{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message]) :label (i18n/label :t/message-reply) - :icon :main-icons/reply-context20 :id :reply}]) - [{:type :main - :on-press #(react/copy-to-clipboard + [{:on-press #(react/copy-to-clipboard (components.reply/get-quoted-text-with-mentions (get content :parsed-text))) - :label (i18n/label :t/copy-text) - :icon :main-icons/copy-context20 + :label (i18n/label :t/sharing-copy-to-clipboard) :id :copy}] (when message-pin-enabled - [{:type :main - :on-press #(pin-message message) - :label (i18n/label (if pinned :t/unpin-from-chat :t/pin-to-chat)) - :icon :main-icons/pin-context20 + [{:on-press #(pin-message message) + :label (if pinned (i18n/label :t/unpin) (i18n/label :t/pin)) :id (if pinned :unpin :pin)}]) - [{:type :danger - :on-press #(re-frame/dispatch - [:chat.ui/delete-message-for-me message - config/delete-message-for-me-undo-time-limit-ms]) - :label (i18n/label :t/delete-for-me) - :icon :main-icons/delete-context20 - :id :delete-for-me}] (when (and outgoing config/delete-message-enabled?) - [{:type :danger - :on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message]) - :label (i18n/label :t/delete-for-everyone) - :icon :main-icons/delete-context20 - :id :delete-for-all}])))) + [{:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message]) + :label (i18n/label :t/delete) + :id :delete}])))) -(defn collapsible-text-message [_ _] +(defn collapsible-text-message [{:keys [mentioned]} _] (let [collapsed? (reagent/atom false) + collapsible? (reagent/atom false) show-timestamp? (reagent/atom false)] - (fn [{:keys [content in-popover?] :as message} on-long-press modal ref] - (let [on-long-press (fn [] - (if @collapsed? - (do (reset! collapsed? false) - (js/setTimeout #(on-long-press-fn on-long-press message content) 200)) - (on-long-press-fn on-long-press message content)))] - (reset! ref on-long-press) + (fn [{:keys [content outgoing current-public-key public? pinned in-popover?] :as message} on-long-press modal] + (let [max-height (when-not (or outgoing modal) + (if @collapsible? + (if @collapsed? message-height-px nil) + message-height-px))] [react/touchable-highlight (when-not modal {:on-press (fn [_] (react/dismiss-keyboard!) (reset! show-timestamp? true)) :delay-long-press 100 - :on-long-press on-long-press + :on-long-press (fn [] + (if @collapsed? + (do (reset! collapsed? false) + (js/setTimeout #(on-long-press-fn on-long-press message content) 200)) + (on-long-press-fn on-long-press message content))) :disabled in-popover?}) - [react/view style/message-view-wrapper + [react/view (style/message-view-wrapper outgoing) [message-timestamp message show-timestamp?] [react/view {:style (style/message-view message)} - [react/view {:style (style/message-view-content)} - [react/view - [render-parsed-text-with-message-status message (:parsed-text content)]]]]]])))) + [react/view {:style (style/message-view-content) + :max-height max-height} + (let [response-to (:response-to content)] + [react/view {:on-layout + #(when (and (> (.-nativeEvent.layout.height ^js %) max-message-height-px) + (not @collapsible?) + (not outgoing) + (not modal)) + (reset! collapsed? true) + (reset! collapsible? true))} + (when (and (seq response-to) (:quoted-message message)) + [quoted-message response-to (:quoted-message message) outgoing current-public-key public? pinned]) + [render-parsed-text-with-message-status message (:parsed-text content)]]) + (when-not @collapsible? [message-status message]) + (when (and @collapsible? (not modal)) + (if @collapsed? + (let [color (if pinned colors/pin-background (if mentioned colors/mentioned-background colors/blue-light))] + [react/touchable-highlight + {:on-press #(swap! collapsed? not) + :style {:position :absolute :bottom 0 :left 0 :right 0 :height 72}} + [react/linear-gradient {:colors [(str color "00") color] + :start {:x 0 :y 0} :end {:x 0 :y 0.9}} + [react/view {:height 72 :align-self :center :justify-content :flex-end + :padding-bottom 10} + [react/view (style/collapse-button) + [icons/icon :main-icons/dropdown + {:color colors/white}]]]]]) + [react/touchable-highlight {:on-press #(swap! collapsed? not) + :style {:align-self :center :margin 5}} + [react/view (style/collapse-button) + [icons/icon :main-icons/dropdown-up + {:color colors/white}]]]))]]]])))) (defmethod ->message constants/content-type-text - [message {:keys [on-long-press modal ref] :as reaction-picker}] + [message {:keys [on-long-press modal] :as reaction-picker}] [message-content-wrapper message - [collapsible-text-message message on-long-press modal ref] + [collapsible-text-message message on-long-press modal] reaction-picker]) -(defmethod ->message constants/content-type-pin [{:keys [from in-popover? timestamp-str] :as message} {:keys [modal close-modal]}] - (let [response-to (:response-to (:content message))] - [react/view {:style (merge {:flex-direction :row :margin-vertical 8} (style/message-wrapper message))} - [react/view {:style {:width photos.style/default-size - :height photos.style/default-size - :margin-horizontal 8 - :border-radius photos.style/default-size - :justify-content :center - :align-items :center - :background-color quo2.colors/primary-50-opa-10}} - [pin-icon]] - [react/view - [react/view {:style {:flex-direction :row :align-items :center}} - [react/touchable-opacity {:style style/message-author-touchable - :disabled in-popover? - :on-press #(do (when modal (close-modal)) - (re-frame/dispatch [:chat.ui/show-profile from]))} - [message-author-name from {:modal modal}]] - [react/text {:style {:font-size 13}} (str " " (i18n/label :pinned-a-message))] - [react/text - {:style (merge - {:padding-left 5 - :margin-top 2} - (style/message-timestamp-text)) - :accessibility-label :message-timestamp} - timestamp-str]] - [quoted-message response-to (:quoted-message message) true]]])) - (defmethod ->message constants/content-type-community [message] [community-content message]) (defmethod ->message constants/content-type-status - [{:keys [content content-type] :as message}] + [{:keys [content content-type pinned] :as message}] [message-content-wrapper message [react/view style/status-container [react/text {:style (style/status-text)} (reduce - (fn [acc e] (render-inline (:text content) content-type acc e)) + (fn [acc e] (render-inline (:text content) false pinned content-type acc e)) [react/text-class {:style (style/status-text)}] (-> content :parsed-text peek :children))]]]) (defmethod ->message constants/content-type-emoji [] (let [show-timestamp? (reagent/atom false)] - (fn [{:keys [content pinned in-popover? message-pin-enabled] :as message} - {:keys [on-long-press modal ref] + (fn [{:keys [content current-public-key outgoing public? pinned in-popover? message-pin-enabled] :as message} + {:keys [on-long-press modal] :as reaction-picker}] - (let [on-long-press (fn [] - (on-long-press - (concat - [{:type :main - :on-press #(re-frame/dispatch [:chat.ui/reply-to-message message]) - :id :reply - :icon :main-icons/reply-context20 - :label (i18n/label :t/message-reply)} - {:type :main - :on-press #(react/copy-to-clipboard (get content :text)) - :id :copy - :icon :main-icons/copy-context20 - :label (i18n/label :t/copy-text)}] - (when message-pin-enabled [{:type :main - :on-press #(pin-message message) - :id :pin - :icon :main-icons/pin-context20 - :label (if pinned (i18n/label :t/unpin) (i18n/label :t/pin))}]))))] - (reset! ref on-long-press) + (let [response-to (:response-to content)] [message-content-wrapper message [react/touchable-highlight (when-not modal - {:disabled in-popover? - :on-press (fn [] - (react/dismiss-keyboard!) - (reset! show-timestamp? true)) + {:disabled in-popover? + :on-press (fn [] + (react/dismiss-keyboard!) + (reset! show-timestamp? true)) :delay-long-press 100 - :on-long-press on-long-press}) - [react/view style/message-view-wrapper + :on-long-press (fn [] + (on-long-press + (concat + [{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message]) + :id :reply + :label (i18n/label :t/message-reply)} + {:on-press #(react/copy-to-clipboard (get content :text)) + :id :copy + :label (i18n/label :t/sharing-copy-to-clipboard)}] + (when message-pin-enabled [{:on-press #(pin-message message) + :label (if pinned (i18n/label :t/unpin) (i18n/label :t/pin))}]))))}) + [react/view (style/message-view-wrapper outgoing) [message-timestamp message show-timestamp?] [react/view (style/message-view message) [react/view {:style (style/message-view-content)} - [react/view {:style (style/style-message-text)} + [react/view {:style (style/style-message-text outgoing)} + (when (and (seq response-to) (:quoted-message message)) + [quoted-message response-to (:quoted-message message) outgoing current-public-key public? pinned]) [react/text {:style (style/emoji-message message)} (:text content)]] [message-status message]]]]] @@ -578,18 +559,9 @@ (defmethod ->message constants/content-type-sticker [{:keys [content from outgoing in-popover?] :as message} - {:keys [on-long-press modal ref] + {:keys [on-long-press modal] :as reaction-picker}] - (let [pack (get-in content [:sticker :pack]) - on-long-press (fn [] - (on-long-press - (when-not outgoing - [{:type :main - :icon :main-icons/stickers-context20 - :on-press #(when pack - (re-frame/dispatch [:chat.ui/show-profile from])) - :label (i18n/label :t/see-sticker-set)}])))] - (reset! ref on-long-press) + (let [pack (get-in content [:sticker :pack])] [message-content-wrapper message [react/touchable-highlight (when-not modal {:disabled in-popover? @@ -598,97 +570,66 @@ (when pack (re-frame/dispatch [:stickers/open-sticker-pack (str pack)])) (react/dismiss-keyboard!)) - :delay-long-press 100 - :on-long-press on-long-press}) + :delay-long-press 100 + :on-long-press (fn [] + (on-long-press + (when-not outgoing + [{:on-press #(when pack + (re-frame/dispatch [:chat.ui/show-profile from])) + :label (i18n/label :t/view-details)}])))}) [fast-image/fast-image {:style {:margin 10 :width 140 :height 140} :source {:uri (str (-> content :sticker :url) "&download=true")}}]] reaction-picker])) (defmethod ->message constants/content-type-image [{:keys [content in-popover? outgoing] :as message} - {:keys [on-long-press modal ref] + {:keys [on-long-press modal] :as reaction-picker}] - (let [on-long-press (fn [] - (on-long-press - (concat [{:type :main - :on-press #(re-frame/dispatch [:chat.ui/reply-to-message message]) - :id :reply - :icon :main-icons/reply-context20 - :label (i18n/label :t/message-reply)} - {:type :main - :on-press #(re-frame/dispatch [:chat.ui/save-image-to-gallery (:image content)]) - :id :save - :icon :main-icons/save-context20 - :label (i18n/label :t/save-image-library)} - {:type :main - :on-press #(images/download-image-http - (get-in message [:content :image]) preview/share) - :id :share - :icon :main-icons/share-context20 - :label (i18n/label :t/share-image)}] - [{:type :danger - :on-press #(re-frame/dispatch - [:chat.ui/delete-message-for-me message - config/delete-message-for-me-undo-time-limit-ms]) - :label (i18n/label :t/delete-for-me) - :icon :main-icons/delete-context20 - :id :delete-for-me}] - (when (and outgoing config/delete-message-enabled?) - [{:type :danger - :on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message]) - :label (i18n/label :t/delete-for-everyone) - :icon :main-icons/delete-context20 - :id :delete}]))))] - (reset! ref on-long-press) - [message-content-wrapper message - [message-content-image message - {:modal modal - :disabled in-popover? - :delay-long-press 100 - :on-long-press ref}] - reaction-picker])) + [message-content-wrapper message + [message-content-image message + {:modal modal + :disabled in-popover? + :delay-long-press 100 + :on-long-press (fn [] + (on-long-press + (concat [{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message]) + :id :reply + :label (i18n/label :t/message-reply)} + {:on-press #(re-frame/dispatch [:chat.ui/save-image-to-gallery (:image content)]) + :id :save + :label (i18n/label :t/save)} + {:on-press #(images/download-image-http + (get-in message [:content :image]) preview/share) + :id :share + :label (i18n/label :t/share)}] + (when (and outgoing config/delete-message-enabled?) + [{:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message]) + :label (i18n/label :t/delete) + :id :delete}]))))}] + reaction-picker]) (defmethod ->message constants/content-type-audio [] (let [show-timestamp? (reagent/atom false)] - (fn [{:keys [outgoing pinned] :as message} - {:keys [on-long-press modal ref] + (fn [{:keys [outgoing] :as message} + {:keys [on-long-press modal] :as reaction-picker}] - (let [on-long-press (fn [] (on-long-press [{:type :main - :on-press #(re-frame/dispatch [:chat.ui/reply-to-message message]) - :label (i18n/label :t/message-reply) - :icon :main-icons/reply-context20 - :id :reply} - {:type :main - :on-press #(pin-message message) - :label (i18n/label (if pinned :t/unpin-from-chat :t/pin-to-chat)) - :icon :main-icons/pin-context20 - :id (if pinned :unpin :pin)} - {:type :danger - :on-press #(re-frame/dispatch - [:chat.ui/delete-message-for-me message - config/delete-message-for-me-undo-time-limit-ms]) - :label (i18n/label :t/delete-for-me) - :icon :main-icons/delete-context20 - :id :delete-for-me} - (when (and outgoing config/delete-message-enabled?) - {:type :danger - :on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message]) - :label (i18n/label :t/delete-for-everyone) - :icon :main-icons/delete-context20 - :id :delete})]))] - (reset! ref on-long-press) - [message-content-wrapper message - [react/touchable-highlight - (when-not modal - {:on-long-press on-long-press - :on-press (fn [] - (reset! show-timestamp? true))}) - [react/view style/message-view-wrapper - [message-timestamp message show-timestamp?] - [react/view {:style (style/message-view message) :accessibility-label :audio-message} - [react/view {:style (style/message-view-content)} - [message.audio/message-content message] [message-status message]]]]] - reaction-picker])))) + [message-content-wrapper message + [react/touchable-highlight + (when-not modal + {:on-long-press + (fn [] (on-long-press (if (and outgoing config/delete-message-enabled?) + [{:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message]) + :label (i18n/label :t/delete) + :id :delete}] + []))) + :on-press (fn [] + (reset! show-timestamp? true))}) + [react/view (style/message-view-wrapper (:outgoing message)) + [message-timestamp message show-timestamp?] + [react/view {:style (style/message-view message) :accessibility-label :audio-message} + [react/view {:style (style/message-view-content)} + [message.audio/message-content message] [message-status message]]]]] + reaction-picker]))) (defn contact-request-status-pending [] [react/view {:style {:flex-direction :row}} @@ -718,8 +659,8 @@ constants/contact-request-message-state-declined [contact-request-status-declined])]) (defmethod ->message constants/content-type-contact-request - [message _] - [react/view {:style (style/content-type-contact-request)} + [{:keys [outgoing] :as message} _] + [react/view {:style (style/content-type-contact-request outgoing)} [react/image {:source (resources/get-image :hand-wave) :style {:width 112 :height 97}}] @@ -737,39 +678,25 @@ [message-content-wrapper message [unknown-content-type message]]) -(defn chat-message [{:keys [pinned pinned-by mentioned] :as message}] - (let [reactions @(re-frame/subscribe [:chats/message-reactions (:message-id message) (:chat-id message)]) - own-reactions (reduce (fn [acc {:keys [emoji-id own]}] - (if own (conj acc emoji-id) acc)) - [] reactions) - send-emoji (fn [{:keys [emoji-id]}] - (re-frame/dispatch [::models.reactions/send-emoji-reaction - {:message-id (:message-id message) - :emoji-id emoji-id}])) - retract-emoji (fn [{:keys [emoji-id emoji-reaction-id]}] - (re-frame/dispatch [::models.reactions/send-emoji-reaction-retraction - {:message-id (:message-id message) - :emoji-id emoji-id - :emoji-reaction-id emoji-reaction-id}])) - on-emoji-press (fn [emoji-id] - (let [active ((set own-reactions) emoji-id)] - (if active - (retract-emoji {:emoji-id emoji-id - :emoji-reaction-id (reactions/extract-id reactions emoji-id)}) - (send-emoji {:emoji-id emoji-id})))) - on-open-drawer (fn [actions] - (re-frame/dispatch [:bottom-sheet/show-sheet - {:content (message-context-drawer/message-options - actions - (into #{} (js->clj own-reactions)) - #(on-emoji-press %))}])) - on-long-press (atom nil)] - [react/view {:style (merge (when (or mentioned pinned) {:background-color quo2.colors/primary-50-opa-5 :border-radius 16 :margin-bottom 4}) {:margin-horizontal 8})} - (when pinned - [react/view {:style (style/pin-indicator-container)} - [pinned-by-indicator pinned-by]]) - [->message message {:ref on-long-press - :modal false - :on-long-press on-open-drawer}] - [reaction-row/message-reactions message reactions nil on-emoji-press on-long-press] ;; TODO: pass on-open-drawer function - ])) +(defn chat-message [{:keys [outgoing display-photo? pinned pinned-by] :as message} space-keeper] + [:<> + [reactions/with-reaction-picker + {:message message + :reactions @(re-frame/subscribe [:chats/message-reactions (:message-id message) (:chat-id message)]) + :picker-on-open (fn [] + (space-keeper true)) + :picker-on-close (fn [] + (space-keeper false)) + :send-emoji (fn [{:keys [emoji-id]}] + (re-frame/dispatch [::models.reactions/send-emoji-reaction + {:message-id (:message-id message) + :emoji-id emoji-id}])) + :retract-emoji (fn [{:keys [emoji-id emoji-reaction-id]}] + (re-frame/dispatch [::models.reactions/send-emoji-reaction-retraction + {:message-id (:message-id message) + :emoji-id emoji-id + :emoji-reaction-id emoji-reaction-id}])) + :render ->message}] + (when pinned + [react/view {:style (style/pin-indicator-container outgoing)} + [pinned-by-indicator outgoing display-photo? pinned-by]])]) \ No newline at end of file diff --git a/src/status_im/ui/screens/chat/message/message_old.cljs b/src/status_im/ui/screens/chat/message/message_old.cljs deleted file mode 100644 index a5f0d8227e..0000000000 --- a/src/status_im/ui/screens/chat/message/message_old.cljs +++ /dev/null @@ -1,702 +0,0 @@ -(ns status-im.ui.screens.chat.message.message-old - (:require [re-frame.core :as re-frame] - [status-im.constants :as constants] - [status-im.i18n.i18n :as i18n] - [status-im.react-native.resources :as resources] - [quo.design-system.colors :as colors] - [status-im.ui.components.icons.icons :as icons] - [status-im.ui.components.react :as react] - [status-im.ui.screens.chat.message.audio-old :as message.audio] - [status-im.chat.models.reactions :as models.reactions] - [status-im.ui.screens.chat.message.command :as message.command] - [status-im.ui.screens.chat.photos :as photos] - [status-im.ui.screens.chat.sheets :as sheets] - [status-im.ui.screens.chat.message.gap :as message.gap] - [status-im.ui.screens.chat.styles.message.message-old :as style] - [status-im.ui.screens.chat.utils :as chat.utils] - [status-im.utils.security :as security] - [status-im.ui.screens.chat.message.reactions-old :as reactions] - [status-im.ui.screens.chat.image.preview.views :as preview] - [quo.core :as quo] - [status-im.utils.config :as config] - [reagent.core :as reagent] - [status-im.ui.screens.chat.components.reply :as components.reply] - [status-im.ui.screens.chat.message.link-preview :as link-preview] - [status-im.ui.screens.communities.icon :as communities.icon] - [status-im.ui.components.animation :as animation] - [status-im.chat.models.images :as images] - [status-im.chat.models.pin-message :as models.pin-message] - [status-im.ui.components.fast-image :as fast-image]) - (:require-macros [status-im.utils.views :refer [defview letsubs]])) - -(defn message-timestamp-anim - [anim-opacity show-timestamp?] - (animation/start - (animation/anim-sequence - [(animation/timing - anim-opacity - {:toValue 1 - :duration 100 - :easing (.-ease ^js animation/easing) - :useNativeDriver true}) - (animation/timing - anim-opacity - {:toValue 0 - :delay 2000 - :duration 100 - :easing (.-ease ^js animation/easing) - :useNativeDriver true})]) #(reset! show-timestamp? false))) - -(defview mention-element [from] - (letsubs [contact-name [:contacts/contact-name-by-identity from]] - contact-name)) - -(def edited-at-text (str " ⌫ " (i18n/label :t/edited))) - -(defn message-status [{:keys [outgoing content outgoing-status pinned edited-at in-popover?]}] - (when-not in-popover? ;; We keep track if showing this message in a list in pin-limit-popover - [react/view - {:align-self :flex-end - :position :absolute - :bottom 9 ; 6 Bubble bottom, 3 message baseline - (if (:rtl? content) :left :right) 0 - :flex-direction :row - :align-items :flex-end} - (when outgoing - [icons/icon (case outgoing-status - :sending :tiny-icons/tiny-pending - :sent :tiny-icons/tiny-sent - :not-sent :tiny-icons/tiny-warning - :delivered :tiny-icons/tiny-delivered - :tiny-icons/tiny-pending) - {:width 16 - :height 12 - :color (if pinned colors/gray colors/white) - :accessibility-label (name outgoing-status)}]) - (when edited-at [react/text {:style (style/message-status-text (and outgoing (not pinned)))} edited-at-text])])) - -(defn message-timestamp - [{:keys [timestamp-str in-popover?] :as message} show-timestamp?] - (when-not in-popover? ;; We keep track if showing this message in a list in pin-limit-popover - (let [anim-opacity (animation/create-value 0)] - [react/animated-view {:style (style/message-timestamp-wrapper message) :opacity anim-opacity} - (when @show-timestamp? (message-timestamp-anim anim-opacity show-timestamp?)) - [react/text - {:style (style/message-timestamp-text) - :accessibility-label :message-timestamp} - timestamp-str]]))) - -(defview quoted-message - [_ {:keys [from parsed-text image]} outgoing current-public-key public? pinned] - (letsubs [contact-name [:contacts/contact-name-by-identity from]] - [react/view {:style (style/quoted-message-container (and outgoing (not pinned)))} - [react/view {:style style/quoted-message-author-container} - [chat.utils/format-reply-author - from - contact-name - current-public-key - (partial style/quoted-message-author (and outgoing (not pinned))) - (and outgoing (not pinned))]] - (if (and image - ;; Disabling images for public-chats - (not public?)) - [fast-image/fast-image {:style {:width 56 - :height 56 - :background-color :black - :border-radius 4} - :source {:uri image}}] - [react/text {:style (style/quoted-message-text (and outgoing (not pinned))) - :number-of-lines 5} - (components.reply/get-quoted-text-with-mentions parsed-text)])])) - -(defn render-inline [message-text outgoing pinned content-type acc {:keys [type literal destination]}] - (case type - "" - (conj acc literal) - - "code" - (conj acc [quo/text {:max-font-size-multiplier react/max-font-size-multiplier - :style (style/inline-code-style) - :monospace true} - literal]) - - "emph" - (conj acc [react/text-class (style/emph-style (and outgoing (not pinned))) literal]) - - "strong" - (conj acc [react/text-class (style/strong-style (and outgoing (not pinned))) literal]) - - "strong-emph" - (conj acc [quo/text (style/strong-emph-style (and outgoing (not pinned))) literal]) - - "del" - (conj acc [react/text-class (style/strikethrough-style (and outgoing (not pinned))) literal]) - - "link" - (conj acc - [react/text-class - {:style - {:color (if (and outgoing (not pinned)) colors/white-persist colors/blue) - :text-decoration-line :underline} - :on-press - #(when (and (security/safe-link? destination) - (security/safe-link-text? message-text)) - (re-frame/dispatch - [:browser.ui/message-link-pressed destination]))} - destination]) - - "mention" - (conj acc [react/text-class - {:style {:color (cond - (= content-type constants/content-type-system-text) colors/black - (and outgoing (not pinned)) colors/mention-outgoing - :else colors/mention-incoming)} - :on-press (when-not (= content-type constants/content-type-system-text) - #(re-frame/dispatch [:chat.ui/show-profile literal]))} - [mention-element literal]]) - "status-tag" - (conj acc [react/text-class - {:style {:color (if (and outgoing (not pinned)) colors/white-persist colors/blue) - :text-decoration-line :underline} - :on-press - #(re-frame/dispatch - [:chat.ui/start-public-chat literal])} - "#" - literal]) - - (conj acc literal))) - -(defn render-block [{:keys [content outgoing content-type pinned in-popover?]} acc - {:keys [type ^js literal children]}] - (case type - - "paragraph" - (conj acc (reduce - (fn [acc e] (render-inline (:text content) outgoing pinned content-type acc e)) - [react/text-class (style/text-style (and outgoing (not pinned)) content-type in-popover?)] - children)) - - "blockquote" - (conj acc [react/view (style/blockquote-style (and outgoing (not pinned))) - [react/text-class (style/blockquote-text-style (and outgoing (not pinned))) - (.substring literal 0 (.-length literal))]]) - - "codeblock" - (conj acc [react/view {:style style/codeblock-style} - [quo/text {:max-font-size-multiplier react/max-font-size-multiplier - :style style/codeblock-text-style - :monospace true} - (.substring literal 0 (dec (.-length literal)))]]) - - acc)) - -(defn render-parsed-text [message tree] - (reduce (fn [acc e] (render-block message acc e)) [:<>] tree)) - -(defn render-parsed-text-with-message-status [{:keys [outgoing edited-at in-popover?] :as message} tree] - (let [elements (render-parsed-text message tree) - message-status [react/text {:style (style/message-status-placeholder)} - (str (if (and outgoing (not in-popover?)) " " " ") (when (and (not in-popover?) edited-at) edited-at-text))] - last-element (peek elements)] - ;; Using `nth` here as slightly faster than `first`, roughly 30% - ;; It's worth considering pure js structures for this code path as - ;; it's perfomance critical - (if (= react/text-class (nth last-element 0)) - ;; Append message status to last text - (conj (pop elements) (conj last-element message-status)) - ;; Append message status to new block - (conj elements message-status)))) - -(defn unknown-content-type - [{:keys [outgoing content-type content] :as message}] - [react/view (style/message-view message) - [react/text - {:style {:color (if outgoing colors/white-persist colors/black)}} - (if (seq (:text content)) - (:text content) - (str "Unhandled content-type " content-type))]]) - -(defn message-not-sent-text - [chat-id message-id] - [react/touchable-highlight - {:on-press - (fn [] - (re-frame/dispatch - [:bottom-sheet/show-sheet - {:content (sheets/options chat-id message-id) - :content-height 200}]) - (react/dismiss-keyboard!))} - [react/view style/not-sent-view - [react/text {:style style/not-sent-text} - (i18n/label :t/status-not-sent-tap)] - [react/view style/not-sent-icon - [icons/icon :main-icons/warning {:color colors/red}]]]]) - -(defn pin-author-name [pinned-by] - (let [user-contact @(re-frame/subscribe [:multiaccount/contact]) - contact-names @(re-frame/subscribe [:contacts/contact-two-names-by-identity pinned-by])] - ;; We append empty spaces to the name as a workaround to make one-line and multi-line label components show correctly - (str " " (if (= pinned-by (user-contact :public-key)) (i18n/label :t/You) (first contact-names))))) - -(def pin-icon-width 9) - -(def pin-icon-height 15) - -(defn pinned-by-indicator [outgoing display-photo? pinned-by] - [react/view {:style (style/pin-indicator outgoing display-photo?) - :accessibility-label :pinned-by} - [react/view {:style (style/pinned-by-text-icon-container)} - [react/view {:style (style/pin-icon-container)} - [icons/icon :main-icons/pin {:color colors/gray - :height pin-icon-height - :width pin-icon-width - :background-color :red}]] - [quo/text {:weight :regular - :size :small - :color :main - :style (style/pinned-by-text)} - (i18n/label :t/pinned-by)]] - [quo/text {:weight :medium - :size :small - :color :main - :style (style/pin-author-text)} - (pin-author-name pinned-by)]]) - -(defn message-delivery-status - [{:keys [chat-id message-id outgoing-status message-type]}] - (when (and (not= constants/message-type-private-group-system-message message-type) - (= outgoing-status :not-sent)) - [message-not-sent-text chat-id message-id])) - -(defview message-author-name [from opts] - (letsubs [contact-with-names [:contacts/contact-by-identity from]] - (chat.utils/format-author-old contact-with-names opts))) - -(defview message-my-name [opts] - (letsubs [contact-with-names [:multiaccount/contact]] - (chat.utils/format-author-old contact-with-names opts))) - -(defview community-content [{:keys [community-id] :as message}] - (letsubs [{:keys [name description verified] :as community} [:communities/community community-id] - communities-enabled? [:communities/enabled?]] - (when (and communities-enabled? community) - [react/view {:style (assoc (style/message-wrapper message) - :margin-vertical 10 - :margin-left 8 - :width 271)} - (when verified - [react/view (style/community-verified) - [react/text {:style {:font-size 13 - :color colors/blue}} (i18n/label :t/communities-verified)]]) - [react/view (style/community-message verified) - [react/view {:width 62 - :padding-left 14} - (if (= community-id constants/status-community-id) - [react/image {:source (resources/get-image :status-logo) - :style {:width 40 - :height 40}}] - [communities.icon/community-icon community])] - [react/view {:padding-right 14 :flex 1} - [react/text {:style {:font-weight "700" :font-size 17}} - name] - [react/text description]]] - [react/view (style/community-view-button) - [react/touchable-highlight {:on-press #(re-frame/dispatch [:navigate-to - :community - {:community-id (:id community)}])} - [react/text {:style {:text-align :center - :color colors/blue}} (i18n/label :t/view)]]]]))) - -(defn message-content-wrapper - "Author, userpic and delivery wrapper" - [{:keys [first-in-group? display-photo? display-username? - identicon - from outgoing in-popover?] - :as message} content {:keys [modal close-modal]}] - [react/view {:style (style/message-wrapper message) - :pointer-events :box-none - :accessibility-label :chat-item} - [react/view {:style (style/message-body message) - :pointer-events :box-none} - (when display-photo? - [react/view (style/message-author-userpic outgoing) - (when first-in-group? - [react/touchable-highlight {:on-press #(do (when modal (close-modal)) - (re-frame/dispatch [:chat.ui/show-profile from]))} - [photos/member-photo from identicon]])]) - [react/view {:style (style/message-author-wrapper outgoing display-photo? in-popover?)} - (when display-username? - [react/touchable-opacity {:style style/message-author-touchable - :disabled in-popover? - :on-press #(do (when modal (close-modal)) - (re-frame/dispatch [:chat.ui/show-profile from]))} - [message-author-name from {:modal modal}]]) - ;;MESSAGE CONTENT - content - [link-preview/link-preview-wrapper (:links (:content message)) outgoing false]]] - ; delivery status - [react/view (style/delivery-status outgoing) - [message-delivery-status message]]]) - -(def image-max-width 260) -(def image-max-height 192) - -(defn image-set-size [dimensions] - (fn [evt] - (let [width (.-width (.-nativeEvent evt)) - height (.-height (.-nativeEvent evt))] - (if (< width height) - ;; if width less than the height we reduce width proportionally to height - (let [k (/ height image-max-height)] - (when (not= (/ width k) (first @dimensions)) - (reset! dimensions {:width (/ width k) :height image-max-height :loaded true}))) - (swap! dimensions assoc :loaded true))))) - -(defn message-content-image - [{:keys [content outgoing in-popover?] :as message} - {:keys [on-long-press]}] - (let [dimensions (reagent/atom {:width image-max-width :height image-max-height :loaded false}) - visible (reagent/atom false) - uri (:image content)] - (fn [] - (let [style-opts {:outgoing outgoing - :opacity (if (:loaded @dimensions) 1 0) - :width (:width @dimensions) - :height (:height @dimensions)}] - [:<> - [preview/preview-image {:message message - :visible @visible - :on-close #(do (reset! visible false) - (reagent/flush))}] - [react/touchable-highlight {:on-press (fn [] - (reset! visible true) - (react/dismiss-keyboard!)) - :on-long-press on-long-press - :disabled in-popover?} - [react/view {:style (style/image-message style-opts) - :accessibility-label :image-message} - (when (or (:error @dimensions) (not (:loaded @dimensions))) - [react/view - (merge (dissoc style-opts :opacity) - {:flex 1 :align-items :center :justify-content :center :position :absolute}) - (if (:error @dimensions) - [icons/icon :main-icons/cancel] - [react/activity-indicator {:animating true}])]) - [fast-image/fast-image {:style (dissoc style-opts :outgoing) - :on-load (image-set-size dimensions) - :on-error #(swap! dimensions assoc :error true) - :source {:uri uri}}] - [react/view {:style (style/image-message-border style-opts)}]]]])))) - -(defmulti ->message :content-type) - -(defmethod ->message constants/content-type-command - [message] - [message.command/command-content message-content-wrapper message]) - -(defmethod ->message constants/content-type-gap - [message] - [message.gap/gap message]) - -(defmethod ->message constants/content-type-system-text [{:keys [content] :as message}] - [react/view {:accessibility-label :chat-item} - [react/view (style/system-message-body message) - [react/view (style/message-view message) - [react/view (style/message-view-content) - [render-parsed-text message (:parsed-text content)]]]]]) - -(def message-height-px 200) -(def max-message-height-px 150) - -(defn pin-message [{:keys [chat-id pinned] :as message}] - (let [pinned-messages @(re-frame/subscribe [:chats/pinned chat-id])] - (if (and (not pinned) (> (count pinned-messages) 2)) - (do - (js/setTimeout (fn [] (re-frame/dispatch [:dismiss-keyboard])) 500) - (re-frame/dispatch [:show-popover {:view :pin-limit - :message message - :prevent-closing? true}])) - (re-frame/dispatch [::models.pin-message/send-pin-message (assoc message :pinned (not pinned))])))) - -(defn on-long-press-fn [on-long-press {:keys [pinned message-pin-enabled outgoing edit-enabled show-input?] :as message} content] - (on-long-press - (concat - (when (and outgoing edit-enabled) - [{:on-press #(re-frame/dispatch [:chat.ui/edit-message message]) - :label (i18n/label :t/edit) - :id :edit}]) - (when show-input? - [{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message]) - :label (i18n/label :t/message-reply) - :id :reply}]) - [{:on-press #(react/copy-to-clipboard - (components.reply/get-quoted-text-with-mentions - (get content :parsed-text))) - :label (i18n/label :t/sharing-copy-to-clipboard) - :id :copy}] - (when message-pin-enabled - [{:on-press #(pin-message message) - :label (if pinned (i18n/label :t/unpin) (i18n/label :t/pin)) - :id (if pinned :unpin :pin)}]) - (when (and outgoing config/delete-message-enabled?) - [{:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message]) - :label (i18n/label :t/delete) - :id :delete}])))) - -(defn collapsible-text-message [{:keys [mentioned]} _] - (let [collapsed? (reagent/atom false) - collapsible? (reagent/atom false) - show-timestamp? (reagent/atom false)] - (fn [{:keys [content outgoing current-public-key public? pinned in-popover?] :as message} on-long-press modal] - (let [max-height (when-not (or outgoing modal) - (if @collapsible? - (if @collapsed? message-height-px nil) - message-height-px))] - [react/touchable-highlight - (when-not modal - {:on-press (fn [_] - (react/dismiss-keyboard!) - (reset! show-timestamp? true)) - :delay-long-press 100 - :on-long-press (fn [] - (if @collapsed? - (do (reset! collapsed? false) - (js/setTimeout #(on-long-press-fn on-long-press message content) 200)) - (on-long-press-fn on-long-press message content))) - :disabled in-popover?}) - [react/view (style/message-view-wrapper outgoing) - [message-timestamp message show-timestamp?] - [react/view {:style (style/message-view message)} - [react/view {:style (style/message-view-content) - :max-height max-height} - (let [response-to (:response-to content)] - [react/view {:on-layout - #(when (and (> (.-nativeEvent.layout.height ^js %) max-message-height-px) - (not @collapsible?) - (not outgoing) - (not modal)) - (reset! collapsed? true) - (reset! collapsible? true))} - (when (and (seq response-to) (:quoted-message message)) - [quoted-message response-to (:quoted-message message) outgoing current-public-key public? pinned]) - [render-parsed-text-with-message-status message (:parsed-text content)]]) - (when-not @collapsible? [message-status message]) - (when (and @collapsible? (not modal)) - (if @collapsed? - (let [color (if pinned colors/pin-background (if mentioned colors/mentioned-background colors/blue-light))] - [react/touchable-highlight - {:on-press #(swap! collapsed? not) - :style {:position :absolute :bottom 0 :left 0 :right 0 :height 72}} - [react/linear-gradient {:colors [(str color "00") color] - :start {:x 0 :y 0} :end {:x 0 :y 0.9}} - [react/view {:height 72 :align-self :center :justify-content :flex-end - :padding-bottom 10} - [react/view (style/collapse-button) - [icons/icon :main-icons/dropdown - {:color colors/white}]]]]]) - [react/touchable-highlight {:on-press #(swap! collapsed? not) - :style {:align-self :center :margin 5}} - [react/view (style/collapse-button) - [icons/icon :main-icons/dropdown-up - {:color colors/white}]]]))]]]])))) - -(defmethod ->message constants/content-type-text - [message {:keys [on-long-press modal] :as reaction-picker}] - [message-content-wrapper message - [collapsible-text-message message on-long-press modal] - reaction-picker]) - -(defmethod ->message constants/content-type-community - [message] - [community-content message]) - -(defmethod ->message constants/content-type-status - [{:keys [content content-type pinned] :as message}] - [message-content-wrapper message - [react/view style/status-container - [react/text {:style (style/status-text)} - (reduce - (fn [acc e] (render-inline (:text content) false pinned content-type acc e)) - [react/text-class {:style (style/status-text)}] - (-> content :parsed-text peek :children))]]]) - -(defmethod ->message constants/content-type-emoji [] - (let [show-timestamp? (reagent/atom false)] - (fn [{:keys [content current-public-key outgoing public? pinned in-popover? message-pin-enabled] :as message} - {:keys [on-long-press modal] - :as reaction-picker}] - (let [response-to (:response-to content)] - [message-content-wrapper message - [react/touchable-highlight (when-not modal - {:disabled in-popover? - :on-press (fn [] - (react/dismiss-keyboard!) - (reset! show-timestamp? true)) - :delay-long-press 100 - :on-long-press (fn [] - (on-long-press - (concat - [{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message]) - :id :reply - :label (i18n/label :t/message-reply)} - {:on-press #(react/copy-to-clipboard (get content :text)) - :id :copy - :label (i18n/label :t/sharing-copy-to-clipboard)}] - (when message-pin-enabled [{:on-press #(pin-message message) - :label (if pinned (i18n/label :t/unpin) (i18n/label :t/pin))}]))))}) - [react/view (style/message-view-wrapper outgoing) - [message-timestamp message show-timestamp?] - [react/view (style/message-view message) - [react/view {:style (style/message-view-content)} - [react/view {:style (style/style-message-text outgoing)} - (when (and (seq response-to) (:quoted-message message)) - [quoted-message response-to (:quoted-message message) outgoing current-public-key public? pinned]) - [react/text {:style (style/emoji-message message)} - (:text content)]] - [message-status message]]]]] - reaction-picker])))) - -(defmethod ->message constants/content-type-sticker - [{:keys [content from outgoing in-popover?] - :as message} - {:keys [on-long-press modal] - :as reaction-picker}] - (let [pack (get-in content [:sticker :pack])] - [message-content-wrapper message - [react/touchable-highlight (when-not modal - {:disabled in-popover? - :accessibility-label :sticker-message - :on-press (fn [_] - (when pack - (re-frame/dispatch [:stickers/open-sticker-pack (str pack)])) - (react/dismiss-keyboard!)) - :delay-long-press 100 - :on-long-press (fn [] - (on-long-press - (when-not outgoing - [{:on-press #(when pack - (re-frame/dispatch [:chat.ui/show-profile from])) - :label (i18n/label :t/view-details)}])))}) - [fast-image/fast-image {:style {:margin 10 :width 140 :height 140} - :source {:uri (str (-> content :sticker :url) "&download=true")}}]] - reaction-picker])) - -(defmethod ->message constants/content-type-image - [{:keys [content in-popover? outgoing] :as message} - {:keys [on-long-press modal] - :as reaction-picker}] - [message-content-wrapper message - [message-content-image message - {:modal modal - :disabled in-popover? - :delay-long-press 100 - :on-long-press (fn [] - (on-long-press - (concat [{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message]) - :id :reply - :label (i18n/label :t/message-reply)} - {:on-press #(re-frame/dispatch [:chat.ui/save-image-to-gallery (:image content)]) - :id :save - :label (i18n/label :t/save)} - {:on-press #(images/download-image-http - (get-in message [:content :image]) preview/share) - :id :share - :label (i18n/label :t/share)}] - (when (and outgoing config/delete-message-enabled?) - [{:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message]) - :label (i18n/label :t/delete) - :id :delete}]))))}] - reaction-picker]) - -(defmethod ->message constants/content-type-audio [] - (let [show-timestamp? (reagent/atom false)] - (fn [{:keys [outgoing] :as message} - {:keys [on-long-press modal] - :as reaction-picker}] - [message-content-wrapper message - [react/touchable-highlight - (when-not modal - {:on-long-press - (fn [] (on-long-press (if (and outgoing config/delete-message-enabled?) - [{:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message]) - :label (i18n/label :t/delete) - :id :delete}] - []))) - :on-press (fn [] - (reset! show-timestamp? true))}) - [react/view (style/message-view-wrapper (:outgoing message)) - [message-timestamp message show-timestamp?] - [react/view {:style (style/message-view message) :accessibility-label :audio-message} - [react/view {:style (style/message-view-content)} - [message.audio/message-content message] [message-status message]]]]] - reaction-picker]))) - -(defn contact-request-status-pending [] - [react/view {:style {:flex-direction :row}} - [quo/text {:style {:margin-right 5.27} - :weight :medium - :color :secondary} - (i18n/label :t/contact-request-pending)] - [react/activity-indicator {:animating true - :size :small - :color colors/gray}]]) - -(defn contact-request-status-accepted [] - [quo/text {:style {:color colors/green} - :weight :medium} - (i18n/label :t/contact-request-accepted)]) - -(defn contact-request-status-declined [] - [quo/text {:style {:color colors/red} - :weight :medium} - (i18n/label :t/contact-request-declined)]) - -(defn contact-request-status-label [state] - [react/view {:style (style/contact-request-status-label state)} - (case state - constants/contact-request-message-state-pending [contact-request-status-pending] - constants/contact-request-message-state-accepted [contact-request-status-accepted] - constants/contact-request-message-state-declined [contact-request-status-declined])]) - -(defmethod ->message constants/content-type-contact-request - [{:keys [outgoing] :as message} _] - [react/view {:style (style/content-type-contact-request outgoing)} - [react/image {:source (resources/get-image :hand-wave) - :style {:width 112 - :height 97}}] - [quo/text {:style {:margin-top 6} - :weight :bold - :size :large} - (i18n/label :t/contact-request)] - [react/view {:style {:padding-horizontal 16}} - [quo/text {:style {:margin-top 2 - :margin-bottom 14}} - (get-in message [:content :text])]] - [contact-request-status-label (:contact-request-state message)]]) - -(defmethod ->message :default [message] - [message-content-wrapper message - [unknown-content-type message]]) - -(defn chat-message [{:keys [outgoing display-photo? pinned pinned-by] :as message} space-keeper] - [:<> - [reactions/with-reaction-picker - {:message message - :reactions @(re-frame/subscribe [:chats/message-reactions (:message-id message) (:chat-id message)]) - :picker-on-open (fn [] - (space-keeper true)) - :picker-on-close (fn [] - (space-keeper false)) - :send-emoji (fn [{:keys [emoji-id]}] - (re-frame/dispatch [::models.reactions/send-emoji-reaction - {:message-id (:message-id message) - :emoji-id emoji-id}])) - :retract-emoji (fn [{:keys [emoji-id emoji-reaction-id]}] - (re-frame/dispatch [::models.reactions/send-emoji-reaction-retraction - {:message-id (:message-id message) - :emoji-id emoji-id - :emoji-reaction-id emoji-reaction-id}])) - :render ->message}] - (when pinned - [react/view {:style (style/pin-indicator-container outgoing)} - [pinned-by-indicator outgoing display-photo? pinned-by]])]) \ No newline at end of file diff --git a/src/status_im/ui/screens/chat/message/pinned_message.cljs b/src/status_im/ui/screens/chat/message/pinned_message.cljs index ba2b7ab9b2..d2a3d59c5f 100644 --- a/src/status_im/ui/screens/chat/message/pinned_message.cljs +++ b/src/status_im/ui/screens/chat/message/pinned_message.cljs @@ -8,7 +8,7 @@ [status-im.chat.models.pin-message :as models.pin-message] [status-im.ui.components.list.views :as list] [status-im.utils.handlers :refer [ ^js @messages-list-ref (.scrollToOffset #js {:y 0 :animated true}))) - -(defn floating-scroll-down-button [show-input?] - [rn/touchable-without-feedback - {:on-press scroll-to-bottom} - [rn/view {:style {:position :absolute - :bottom (if show-input? 126 12) - :right 12 - :height 24 - :width 24 - :align-items :center - :justify-content :center - :border-radius (/ 24 2) - :background-color (quo2.colors/theme-colors quo2.colors/neutral-80-opa-70 quo2.colors/white-opa-70)}} - [icons/icon - :main-icons/arrow-down {:color (quo2.colors/theme-colors quo2.colors/white quo2.colors/neutral-100) - :width 12 - :height 12}]]]) - -(defn on-scroll [^js ev] - (let [y (-> ev .-nativeEvent .-contentOffset .-y) - layout-height (-> ev .-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 messages-view [{:keys [chat - bottom-space - pan-responder - mutual-contact-requests-enabled? - show-input?]}] - (let [{:keys [group-chat chat-type chat-id public? community-id admins]} chat - - messages @(re-frame/subscribe [:chats/raw-chat-messages-stream chat-id]) - one-to-one? (= chat-type constants/one-to-one-chat-type) - contact-added? (when one-to-one? @(re-frame/subscribe [:contacts/contact-added? chat-id])) - should-send-contact-request? - (and - mutual-contact-requests-enabled? - one-to-one? - (not contact-added?))] - [:<> - ;;do not use anonymous functions for handlers - [list/flat-list - (merge - pan-responder - {:key-fn list-key-fn - :ref list-ref - :header [list-header chat] - :footer [list-footer chat] - :data (when-not should-send-contact-request? - messages) - :render-data (get-render-data {:group-chat group-chat - :chat-id chat-id - :public? public? - :community-id community-id - :admins admins - :show-input? show-input? - :edit-enabled true - :in-pinned-view? false}) - :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 16) - :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})})] - (when @show-floating-scroll-down-button - [floating-scroll-down-button show-input?])])) - -(defn back-button [] - [quo2.button/button {:type :grey - :size 32 - :width 32 - :accessibility-label "back-button" - :on-press #(re-frame/dispatch [:navigate-back])} - [icons/icon :main-icons/arrow-left {:color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)}]]) - -(defn search-button [] - [quo2.button/button {:type :grey - :size 32 - :width 32 - :accessibility-label "search-button"} - [icons/icon :main-icons/search {:color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)}]]) - -(defn topbar-content [] - (let [window-width @(re-frame/subscribe [:dimensions/window-width]) - {:keys [group-chat chat-id] :as chat-info} @(re-frame/subscribe [:chats/current-chat])] - [react/view {:flex-direction :row :align-items :center :height 56} - [react/touchable-highlight {:on-press #(when-not group-chat - (debounce/dispatch-and-chill [:chat.ui/show-profile chat-id] 1000)) - :style {:flex 1 :margin-left 12 :width (- window-width 120)}} - [toolbar-content/toolbar-content-view-inner chat-info]]])) - (defn navigate-back-handler [] (when (and (not @navigation.state/curr-modal) (= (get @re-frame.db/app-db :view-id) :chat)) (react/hw-back-remove-listener navigate-back-handler) (re-frame/dispatch [:close-chat]) (re-frame/dispatch [:navigate-back]))) -(defn topbar-content-old [] +(defn topbar-content [] (let [window-width @(re-frame/subscribe [:dimensions/window-width]) {:keys [group-chat chat-id] :as chat-info} @(re-frame/subscribe [:chats/current-chat])] [react/touchable-highlight {:on-press #(when-not group-chat @@ -546,7 +398,7 @@ :style {:flex 1 :width (- window-width 120)}} [toolbar-content/toolbar-content-view-inner chat-info]])) -(defn topbar-old [] +(defn topbar [] ;;we don't use topbar component, because we want chat view as simple (fast) as possible [react/view {:height 56} [react/touchable-highlight {:on-press-in navigate-back-handler @@ -555,10 +407,9 @@ :padding-left 16}} [icons/icon :main-icons/arrow-left {:color colors/black}]] [react/view {:flex 1 :left 52 :right 52 :top 0 :bottom 0 :position :absolute} - [topbar-content-old]] + [topbar-content]] [react/touchable-highlight {:on-press-in #(re-frame/dispatch [:bottom-sheet/show-sheet - {:content (fn [] - [sheets/current-chat-actions]) + {:content (fn [] [sheets/current-chat-actions]) :height 256}]) :accessibility-label :chat-menu-button :style {:right 0 :top 0 :bottom 0 :position :absolute @@ -566,7 +417,7 @@ :padding-right 16}} [icons/icon :main-icons/more {:color colors/black}]]]) -(defn chat-render-old [] +(defn chat-render [] (let [bottom-space (reagent/atom 0) panel-space (reagent/atom 52) active-panel (reagent/atom nil) @@ -585,24 +436,24 @@ mutual-contact-requests-enabled? @(re-frame/subscribe [:mutual-contact-requests/enabled?]) max-bottom-space (max @bottom-space @panel-space)] [:<> - [topbar-old] + [topbar] [connectivity/loading-indicator] (when chat-id (if group-chat [invitation-requests chat-id admins] - (when-not mutual-contact-requests-enabled? [add-contact-bar-old chat-id]))) + (when-not mutual-contact-requests-enabled? [add-contact-bar chat-id]))) ;;MESSAGES LIST - [messages-view-old {:chat chat - :bottom-space max-bottom-space - :pan-responder pan-responder - :mutual-contact-requests-enabled? mutual-contact-requests-enabled? - :space-keeper space-keeper - :show-input? show-input?}] + [messages-view {:chat chat + :bottom-space max-bottom-space + :pan-responder pan-responder + :mutual-contact-requests-enabled? mutual-contact-requests-enabled? + :space-keeper space-keeper + :show-input? show-input?}] (when (and group-chat invitation-admin) [accessory/view {:y position-y :on-update-inset on-update} [invitation-bar chat-id]]) - [components/autocomplete-mentions-old text-input-ref max-bottom-space] + [components/autocomplete-mentions text-input-ref max-bottom-space] (when show-input? ;; NOTE: this only accepts two children [accessory/view {:y position-y @@ -611,12 +462,12 @@ :on-close on-close :on-update-inset on-update} [react/view - [edit/edit-message-auto-focus-wrapper-old text-input-ref] - [reply/reply-message-auto-focus-wrapper-old text-input-ref] + [edit/edit-message-auto-focus-wrapper text-input-ref] + [reply/reply-message-auto-focus-wrapper text-input-ref] ;; We set the key so we can force a re-render as ;; it does not rely on ratom but just atoms ^{:key (str @components/chat-input-key "chat-input")} - [components/chat-toolbar-old + [components/chat-toolbar {:chat-id chat-id :active-panel @active-panel :set-active-panel set-active-panel @@ -624,47 +475,10 @@ [contact-request/contact-request-message-auto-focus-wrapper text-input-ref]] [bottom-sheet @active-panel]])])))) -(defn chat-render [] - (let [{:keys [chat-id show-input? group-chat admins] :as chat} - ;;we want to react only on these fields, do not use full chat map here - @(re-frame/subscribe [:chats/current-chat-chat-view]) - mutual-contact-requests-enabled? @(re-frame/subscribe [:mutual-contact-requests/enabled?])] - [react/keyboard-avoiding-view-new {:style {:flex 1} - :ignore-offset false} - ;; It is better to not use topbar component because of performance - [topbar/topbar {:navigation :none - :left-component [react/view {:flex-direction :row :margin-left 16} - [back-button]] - :title-component [topbar-content] - :right-component [react/view {:flex-direction :row :margin-right 16} - [search-button]] - :border-bottom false - :new-ui? true}] - [connectivity/loading-indicator] - (when chat-id - (if group-chat - [invitation-requests chat-id admins] - (when-not mutual-contact-requests-enabled? [add-contact-bar chat-id]))) - ;;MESSAGES LIST - [messages-view {:chat chat - :mutual-contact-requests-enabled? mutual-contact-requests-enabled? - :show-input? show-input?}] - ;;INPUT COMPONENT - (when show-input? - [components/chat-input-bottom-sheet chat-id])])) - -(defn chat-old [] +(defn chat [] (reagent/create-class {:component-did-mount (fn [] (react/hw-back-remove-listener navigate-back-handler) (react/hw-back-add-listener navigate-back-handler)) :component-will-unmount (fn [] (react/hw-back-remove-listener navigate-back-handler)) - :reagent-render chat-render-old})) - -(defn chat [] - (reagent/create-class - {:component-did-mount (fn [] - (react/hw-back-remove-listener navigate-back-handler) - (react/hw-back-add-listener navigate-back-handler)) - :component-will-unmount (fn [] (react/hw-back-remove-listener navigate-back-handler)) - :reagent-render chat-render})) + :reagent-render chat-render})) \ No newline at end of file diff --git a/src/status_im/ui/screens/ens/views.cljs b/src/status_im/ui/screens/ens/views.cljs index e983bf22d0..9ebf5f86f0 100644 --- a/src/status_im/ui/screens/ens/views.cljs +++ b/src/status_im/ui/screens/ens/views.cljs @@ -17,7 +17,7 @@ [status-im.ui.components.topbar :as topbar] [status-im.ui.screens.chat.utils :as chat.utils] [status-im.ui.components.toolbar :as toolbar] - [status-im.ui.screens.chat.message.message-old :as message] + [status-im.ui.screens.chat.message.message :as message] [status-im.ui.screens.chat.photos :as photos] [status-im.ui.screens.profile.components.views :as profile.components] [status-im.utils.debounce :as debounce] diff --git a/src/status_im/ui/screens/home/views.cljs b/src/status_im/ui/screens/home/views.cljs index b5146e3f9c..e1852cde1b 100644 --- a/src/status_im/ui/screens/home/views.cljs +++ b/src/status_im/ui/screens/home/views.cljs @@ -129,7 +129,7 @@ home-item {:on-press (fn [] (re-frame/dispatch [:dismiss-keyboard]) - (if (and @config/new-ui-enabled? platform/android?) + (if (and config/new-ui-enabled? platform/android?) (re-frame/dispatch [:chat.ui/navigate-to-chat-nav2 chat-id]) (re-frame/dispatch [:chat.ui/navigate-to-chat chat-id])) (re-frame/dispatch [:search/home-filter-changed nil]) @@ -146,7 +146,7 @@ home-item {:on-press (fn [] (re-frame/dispatch [:dismiss-keyboard]) - (if (and @config/new-ui-enabled? platform/android?) + (if (and config/new-ui-enabled? platform/android?) (re-frame/dispatch [:chat.ui/navigate-to-chat-nav2 chat-id]) (re-frame/dispatch [:chat.ui/navigate-to-chat chat-id])) (re-frame/dispatch [:search/home-filter-changed nil]) @@ -256,7 +256,7 @@ :accessibility-label :notifications-button :on-press #(do (re-frame/dispatch [:mark-all-activity-center-notifications-as-read]) - (if (and @config/new-ui-enabled? @config/new-activity-center-enabled?) + (if config/new-activity-center-enabled? (re-frame/dispatch [:navigate-to :activity-center]) (re-frame/dispatch [:navigate-to :notifications-center])))} [icons/icon :main-icons/notification2 {:color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)}]] diff --git a/src/status_im/ui/screens/profile/group_chat/views.cljs b/src/status_im/ui/screens/profile/group_chat/views.cljs index 4795e1a974..c7be5bb798 100644 --- a/src/status_im/ui/screens/profile/group_chat/views.cljs +++ b/src/status_im/ui/screens/profile/group_chat/views.cljs @@ -13,7 +13,7 @@ [status-im.ui.screens.profile.components.styles :as profile.components.styles] [status-im.ui.components.topbar :as topbar] [status-im.ui.components.common.common :as components.common] - [status-im.ui.screens.chat.message.message-old :as message] + [status-im.ui.screens.chat.message.message :as message] [status-im.ui.screens.chat.photos :as photos] [status-im.ui.screens.chat.utils :as chat.utils] [status-im.utils.debounce :as debounce]) diff --git a/src/status_im/ui/screens/profile/visibility_status/utils.cljs b/src/status_im/ui/screens/profile/visibility_status/utils.cljs index 331dfd9747..106f59e08a 100644 --- a/src/status_im/ui/screens/profile/visibility_status/utils.cljs +++ b/src/status_im/ui/screens/profile/visibility_status/utils.cljs @@ -5,7 +5,6 @@ [status-im.constants :as constants] [status-im.i18n.i18n :as i18n] [status-im.ui.screens.profile.visibility-status.styles :as styles] - [status-im.utils.config :as config] [status-im.utils.datetime :as datetime] [status-im.utils.handlers :refer [ (when automatic? [rn/view {:style (styles/visibility-status-profile-dot diff --git a/src/status_im/ui/screens/screens.cljs b/src/status_im/ui/screens/screens.cljs index a42c29c7c7..c03083ec93 100644 --- a/src/status_im/ui/screens/screens.cljs +++ b/src/status_im/ui/screens/screens.cljs @@ -226,7 +226,7 @@ :hardwareBackButton {:dismissModalOnPress false :popStackOnPress false} :topBar {:visible false}} - :component chat/chat-old} + :component chat/chat} ;Pinned messages {:name :chat-pinned-messages @@ -905,9 +905,9 @@ quo.preview/screens) (when config/quo-preview-enabled? quo.preview/main-screens) - (when (or config/quo-preview-enabled? @config/new-ui-enabled?) + (when config/quo-preview-enabled? quo2.preview/screens) - (when @config/new-ui-enabled? + (when config/new-ui-enabled? navigation2.screens/screen-overwrites) (when config/quo-preview-enabled? quo2.preview/main-screens))) diff --git a/src/status_im/ui/screens/status/views.cljs b/src/status_im/ui/screens/status/views.cljs index 9cbe62b7eb..6faa7624f0 100644 --- a/src/status_im/ui/screens/status/views.cljs +++ b/src/status_im/ui/screens/status/views.cljs @@ -1,5 +1,5 @@ (ns status-im.ui.screens.status.views - (:require [status-im.ui.screens.chat.message.message-old :as message] + (:require [status-im.ui.screens.chat.message.message :as message] [status-im.ui.components.react :as react] [quo.design-system.colors :as colors] [status-im.utils.datetime :as datetime] diff --git a/src/status_im/ui/screens/views.cljs b/src/status_im/ui/screens/views.cljs index 26d17c451c..ec837d3a34 100644 --- a/src/status_im/ui/screens/views.cljs +++ b/src/status_im/ui/screens/views.cljs @@ -59,7 +59,7 @@ (reagent.core/reactify-component (fn [] (let [{:keys [component insets]} (get - (if (or js/goog.DEBUG @config/new-ui-enabled?) + (if js/goog.DEBUG (get-screens) screens) (keyword key))] diff --git a/src/status_im/ui2/screens/chat/components/reply.cljs b/src/status_im/ui2/screens/chat/components/reply.cljs new file mode 100644 index 0000000000..7112fac1aa --- /dev/null +++ b/src/status_im/ui2/screens/chat/components/reply.cljs @@ -0,0 +1,89 @@ +(ns status-im.ui2.screens.chat.components.reply + (:require [quo2.foundations.colors :as quo2.colors] + [status-im.ui.components.icons.icons :as icons] + [quo.react-native :as rn] + [status-im.constants :as constants] + [status-im.utils.handlers :refer [evt]] + [quo2.components.markdown.text :as quo2.text] + [status-im.ui.screens.chat.photos :as photos] + [quo2.components.buttons.button :as quo2.button] + [clojure.string :as string] + [status-im.ethereum.stateofus :as stateofus] + [status-im.i18n.i18n :as i18n] + [status-im.ui2.screens.chat.composer.style :as styles])) + +(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") + (evt [:chat.ui/cancel-message-reply])} + ;;TODO quo2 icon should be used + [icons/icon :main-icons/close {:width 16 + :height 16 + :color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/neutral-40)}]])])) \ No newline at end of file diff --git a/src/status_im/ui2/screens/chat/composer/input.cljs b/src/status_im/ui2/screens/chat/composer/input.cljs new file mode 100644 index 0000000000..f839d7a2d2 --- /dev/null +++ b/src/status_im/ui2/screens/chat/composer/input.cljs @@ -0,0 +1,178 @@ +(ns status-im.ui2.screens.chat.composer.input + (:require [quo.react-native :as rn] + [status-im.i18n.i18n :as i18n] + [status-im.utils.handlers :refer [evt]] + [quo.design-system.colors :as colors] + [status-im.utils.utils :as utils.utils] + [status-im.utils.platform :as platform] + [clojure.string :as string] + [reagent.core :as reagent] + [status-im.chat.constants :as chat.constants] + [status-im.ui2.screens.chat.composer.style :as style] + [re-frame.core :as re-frame] + [status-im.chat.models.mentions :as mentions] + [quo.react])) + +(defonce input-texts (atom {})) +(defonce mentions-enabled (reagent/atom {})) +(defonce chat-input-key (reagent/atom 1)) + +(re-frame/reg-fx + :chat.ui/clear-inputs + (fn [] + (reset! input-texts {}) + (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]}] + (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}) + (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]}] + (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}) + (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)) + +(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 + (>evt [:chat.ui/set-chat-input-text val])) + +(defn on-selection-change [timeout-id last-text-change mentionable-users 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 + (utils.utils/set-timeout + #(>evt [::mentions/on-selection-change + {:start start + :end end} + mentionable-users]) + 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)))) + (>evt [::mentions/on-selection-change + {:start start + :end end} + mentionable-users])))) + +(defn on-change [last-text-change timeout-id mentionable-users 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) (seq text)) + (show-send refs)) + + (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) + (utils.utils/clear-timeout @timeout-id)) + (when platform/android? + (reset! last-text-change (js/Date.now))) + + (on-text-change text chat-id) + ;; NOTE(rasom): on iOS `on-change` is dispatched after `on-text-input`, + ;; that's why mention suggestions are calculated on `on-change` + (when platform/ios? + (>evt [::mentions/calculate-suggestions mentionable-users])))) + +(defn on-text-input [mentionable-users chat-id args] + (let [native-event (.-nativeEvent ^js args) + text (.-text ^js native-event) + previous-text (.-previousText ^js native-event) + range (.-range ^js native-event) + start (.-start ^js range) + end (.-end ^js range)] + (when (and (not (get @mentions-enabled chat-id)) (string/index-of text "@")) + (swap! mentions-enabled assoc chat-id true)) + + (>evt + [::mentions/on-text-input + {:new-text text + :previous-text previous-text + :start start + :end end}]) + ;; NOTE(rasom): on Android `on-text-input` is dispatched after + ;; `on-change`, that's why mention suggestions are calculated + ;; on `on-change` + (when platform/android? + (>evt [::mentions/calculate-suggestions mentionable-users])))) + +(defn text-input [{:keys [set-active-panel refs chat-id sending-image on-content-size-change]}] + (let [cooldown-enabled? (evt]])) + +(defn mention-item + [[public-key {:keys [alias name nickname] :as user}] _ _ text-input-ref] + (let [ens-name? (not= alias name)] + [rn/touchable-opacity {:on-press #(>evt [:chat.ui/select-mention text-input-ref user]) :style {:border-width 1 :border-color :red}} + ;;TODO quo2 item should be used + [list-item/list-item + (cond-> {:icon [photos/member-photo public-key] + :size :small + :text-size :small + :title + [text/text + {:weight :medium + :ellipsize-mode :tail + :number-of-lines 1 + :size :small} + (if nickname + nickname + name) + (when nickname + [text/text + {:weight :regular + :color :secondary + :ellipsize-mode :tail + :size :small} + " " + (when ens-name? + "@") + name])] + :title-text-weight :medium} + ens-name? + (assoc :subtitle alias))]])) + +(defn autocomplete-mentions [suggestions] + [:f> + (fn [] + (let [animation (reanimated/use-shared-value 0)] + (quo.react/effect! #(reanimated/set-shared-value animation (reanimated/with-timing (if (seq suggestions) 0 200)))) + [reanimated/view {:style (reanimated/apply-animations-to-style + {:transform [{:translateY animation}]} + {:bottom 0 :position :absolute :z-index 5 :max-height 180})} + [list/flat-list + {:keyboardShouldPersistTaps :always + :data suggestions + :key-fn first + :render-fn mention-item + :content-container-style {:padding-bottom 12}}]]))]) \ No newline at end of file diff --git a/src/status_im/ui2/screens/chat/composer/reply.cljs b/src/status_im/ui2/screens/chat/composer/reply.cljs new file mode 100644 index 0000000000..617ba48809 --- /dev/null +++ b/src/status_im/ui2/screens/chat/composer/reply.cljs @@ -0,0 +1,21 @@ +(ns status-im.ui2.screens.chat.composer.reply + (:require [quo.react-native :as rn] + [status-im.ui2.screens.chat.composer.input :as input] + [status-im.ui2.screens.chat.components.reply :as reply])) + +(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 {:padding-horizontal 15 + :padding-vertical 8}} + [reply/reply-message reply true]])))) \ No newline at end of file diff --git a/src/status_im/ui2/screens/chat/composer/style.cljs b/src/status_im/ui2/screens/chat/composer/style.cljs new file mode 100644 index 0000000000..ea69475491 --- /dev/null +++ b/src/status_im/ui2/screens/chat/composer/style.cljs @@ -0,0 +1,82 @@ +(ns status-im.ui2.screens.chat.composer.style + (:require [quo2.foundations.typography :as quo2.typography] + [quo.design-system.colors :as colors] + [status-im.utils.platform :as platform] + [quo2.foundations.colors :as quo2.colors])) + +(defn text-input [] + (merge quo2.typography/font-regular + quo2.typography/paragraph-1 + {:flex 1 + :min-height 34 + :margin 0 + :flex-shrink 1 + :color (:text-01 @colors/theme) + :margin-horizontal 20} + (if platform/android? + {:padding-vertical 8 + :text-align-vertical :top} + {:margin-top 8 + :margin-bottom 8}))) + +(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 (quo2.colors/theme-colors quo2.colors/white quo2.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 2}))) + +(defn bottom-sheet-handle [] + {:width 32 + :height 4 + :background-color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white) + :opacity 0.05 + :border-radius 100 + :align-self :center + :margin-top 8}) + +(defn bottom-sheet-controls [insets] + {:flex-direction :row + :padding-horizontal 20 + :elevation 2 + :z-index 3 + :position :absolute + :background-color (quo2.colors/theme-colors quo2.colors/white quo2.colors/neutral-90) + ;these 3 props play together, we need this magic to hide message text in the safe area + :padding-top 10 + :padding-bottom (+ 12 (:bottom insets)) + :bottom (- 2 (:bottom insets))}) + +(defn bottom-sheet-background [window-height] + {:pointerEvents :none + :position :absolute + :left 0 + :right 0 + :bottom 0 + :height window-height + :background-color quo2.colors/neutral-95-opa-70 + :z-index 1}) + +(defn reply-content [pin?] + {:padding-horizontal (when-not pin? 10) + :flex 1 + :flex-direction :row}) + +(defn quoted-message [pin?] + (merge {:flex-direction :row + :align-items :center + :width "45%"} + (when-not pin? {:position :absolute + :left 34 + :top 3}))) \ No newline at end of file diff --git a/src/status_im/ui2/screens/chat/composer/view.cljs b/src/status_im/ui2/screens/chat/composer/view.cljs new file mode 100644 index 0000000000..3c0aae6b17 --- /dev/null +++ b/src/status_im/ui2/screens/chat/composer/view.cljs @@ -0,0 +1,194 @@ +(ns status-im.ui2.screens.chat.composer.view + (:require [quo2.gesture :as gesture] + [quo2.reanimated :as reanimated] + [re-frame.core :as re-frame] + [quo.components.safe-area :as safe-area] + [quo.react-native :as rn] + [status-im.ui2.screens.chat.composer.style :as styles] + [status-im.ui2.screens.chat.composer.reply :as reply] + [quo2.components.buttons.button :as quo2.button] + [status-im.utils.handlers :refer [ (gesture/gesture-pan) + (gesture/on-start + (fn [_] + (if keyboard-shown + (swap! context assoc :pan-y (reanimated/get-shared-value translate-y)) + (input/input-focus text-input-ref)))) + (gesture/on-update + (fn [evt] + (when keyboard-shown + (swap! context assoc :dy (- (.-translationY evt) (:pdy @context))) + (swap! context assoc :pdy (.-translationY evt)) + (reanimated/set-shared-value + translate-y + (max (min (+ (.-translationY evt) (:pan-y @context)) (- min-y)) (- max-y)))))) + (gesture/on-end + (fn [_] + (when keyboard-shown + (if (< (:dy @context) 0) + (do + (swap! context assoc :state :max) + (input/input-focus text-input-ref) + (reanimated/set-shared-value translate-y (reanimated/with-timing (- max-y))) + (reanimated/set-shared-value shared-height (reanimated/with-timing max-height)) + (reanimated/set-shared-value bg-opacity (reanimated/with-timing 1))) + (do + (swap! context assoc :state :min) + (reanimated/set-shared-value translate-y (reanimated/with-timing (- min-y))) + (reanimated/set-shared-value shared-height (reanimated/with-timing min-y)) + (reanimated/set-shared-value bg-opacity (reanimated/with-timing 0)) + (re-frame/dispatch [:dismiss-keyboard])))))))) + +(defn get-input-content-change [context translate-y shared-height max-height bg-opacity keyboard-shown min-y max-y] + (fn [evt] + (if (:clear @context) + (do + (swap! context dissoc :clear) + (swap! context assoc :state :min) + (swap! context assoc :y min-y) + (reanimated/set-shared-value translate-y (reanimated/with-timing (- min-y))) + (reanimated/set-shared-value shared-height (reanimated/with-timing min-y)) + (reanimated/set-shared-value bg-opacity (reanimated/with-timing 0))) + (when (not= (:state @context) :max) + (let [new-y (+ min-y (- (max (oget evt "nativeEvent" "contentSize" "height") 22) 22))] + (if (< new-y max-y) + (do + (if (> (- max-y new-y) 120) + (do + (swap! context assoc :state :custom-chat-available) + (reanimated/set-shared-value bg-opacity (reanimated/with-timing 0))) + (do + (reanimated/set-shared-value bg-opacity (reanimated/with-timing 1)) + (swap! context assoc :state :custom-chat-unavailable))) + (swap! context assoc :y new-y) + (when keyboard-shown + (reanimated/set-shared-value + translate-y + (reanimated/with-timing (- new-y))) + (reanimated/set-shared-value + shared-height + (reanimated/with-timing (min new-y max-height))))) + (do + (swap! context assoc :state :max) + (swap! context assoc :y max-y) + (when keyboard-shown + (reanimated/set-shared-value bg-opacity (reanimated/with-timing 1)) + (reanimated/set-shared-value + translate-y + (reanimated/with-timing (- max-y))))))))))) + +(defn composer [chat-id] + [safe-area/consumer + (fn [insets] + (let [min-y 112 + context (atom {:y min-y ;current y value + :min-y min-y ;minimum y value + :dy 0 ;used for gesture + :pdy 0 ;used for gesture + :state :min ;:min, :custom-chat-available, :custom-chat-unavailable, :max + :clear false}) + keyboard-was-shown (atom false) + text-input-ref (quo.react/create-ref) + send-ref (quo.react/create-ref) + refs {:send-ref send-ref + :text-input-ref text-input-ref}] + (fn [] + [:f> + (fn [] + (let [reply ( keyboard-height 0) keyboard-height 360) (:top insets)) ; 360 - default height + max-height (- max-y 56 (:bottom insets)) ; 56 - top-bar height + added-value (if (and (not (seq suggestions)) reply) 38 0) ; increased height of input box needed when reply + min-y (+ min-y (when reply 38)) + y (get-y-value context keyboard-shown min-y max-y added-value max-height chat-id suggestions reply) + translate-y (reanimated/use-shared-value 0) + shared-height (reanimated/use-shared-value min-y) + bg-opacity (reanimated/use-shared-value 0) + + input-content-change (get-input-content-change context translate-y shared-height max-height + bg-opacity keyboard-shown min-y max-y) + bottom-sheet-gesture (get-bottom-sheet-gesture context translate-y (:text-input-ref refs) keyboard-shown + min-y max-y shared-height max-height bg-opacity)] + (quo.react/effect! #(do + (when (and @keyboard-was-shown (not keyboard-shown)) + (swap! context assoc :state :min)) + (reset! keyboard-was-shown keyboard-shown) + (if (#{:max :custom-chat-unavailable} (:state @context)) + (reanimated/set-shared-value bg-opacity (reanimated/with-timing 1)) + (reanimated/set-shared-value bg-opacity (reanimated/with-timing 0))) + (reanimated/set-shared-value translate-y (reanimated/with-timing (- y))) + (reanimated/set-shared-value shared-height (reanimated/with-timing (min y max-height))))) + [reanimated/view {:style (reanimated/apply-animations-to-style + {:height shared-height} + {})} + ;;INPUT MESSAGE bottom sheet + [gesture/gesture-detector {:gesture bottom-sheet-gesture} + [reanimated/view {:style (reanimated/apply-animations-to-style + {:transform [{:translateY translate-y}]} + (styles/input-bottom-sheet window-height))} + ;handle + [rn/view {:style (styles/bottom-sheet-handle)}] + [reply/reply-message-auto-focus-wrapper (:text-input-ref refs) reply] + [rn/view {:style {:height (- max-y 80 added-value)}} + [input/text-input {:chat-id chat-id + :on-content-size-change input-content-change + :sending-image false + :refs refs + :set-active-panel #()}]]]] + ;CONTROLS + (when-not (seq suggestions) + [rn/view {:style (styles/bottom-sheet-controls insets)} + [quo2.button/button {:icon true :type :outline :size 32} :main-icons2/image] + [rn/view {:width 12}] + [quo2.button/button {:icon true :type :outline :size 32} :main-icons2/reaction] + [rn/view {:flex 1}] + ;;SEND button + [rn/view {:ref send-ref :style (when-not (seq (get @input/input-texts chat-id)) {:width 0 :right -100})} + [quo2.button/button {:icon true :size 32 :accessibility-label :send-message-button + :on-press #(do (swap! context assoc :clear true) + (input/clear-input chat-id refs) + (re-frame/dispatch [:chat.ui/send-current-message]))} + :main-icons2/arrow-up]]]) + ;black background + [reanimated/view {:style (reanimated/apply-animations-to-style + {:opacity bg-opacity} + (styles/bottom-sheet-background window-height))}] + [mentions/autocomplete-mentions suggestions]]))])))]) \ No newline at end of file diff --git a/src/status_im/ui2/screens/chat/messages/message.cljs b/src/status_im/ui2/screens/chat/messages/message.cljs new file mode 100644 index 0000000000..1d750790d6 --- /dev/null +++ b/src/status_im/ui2/screens/chat/messages/message.cljs @@ -0,0 +1,773 @@ +(ns status-im.ui2.screens.chat.messages.message + (:require [quo.core :as quo] + [quo.design-system.colors :as colors] + [re-frame.core :as re-frame] + [reagent.core :as reagent] + [status-im.chat.models.delete-message-for-me] + [status-im.chat.models.images :as images] + [status-im.chat.models.pin-message :as models.pin-message] + [status-im.chat.models.reactions :as models.reactions] + [status-im.constants :as constants] + [status-im.i18n.i18n :as i18n] + [status-im.react-native.resources :as resources] + [status-im.ui.components.animation :as animation] + [status-im.ui.components.fast-image :as fast-image] + [status-im.ui.components.icons.icons :as icons] + [status-im.ui.components.react :as react] + [status-im.ui.screens.chat.bottom-sheets.context-drawer :as message-context-drawer] + [status-im.ui.screens.chat.image.preview.views :as preview] + [status-im.ui.screens.chat.message.audio :as message.audio] + [status-im.ui.screens.chat.message.command :as message.command] + [status-im.ui.screens.chat.message.gap :as message.gap] + [status-im.ui.screens.chat.message.link-preview :as link-preview] + [status-im.ui.screens.chat.message.reactions :as reactions] + [status-im.ui.screens.chat.message.reactions-row :as reaction-row] + [status-im.ui.screens.chat.photos :as photos] + [status-im.ui.screens.chat.sheets :as sheets] + [status-im.ui.screens.chat.styles.message.message :as style] + [status-im.ui.screens.chat.utils :as chat.utils] + [status-im.ui.screens.chat.styles.photos :as photos.style] + [status-im.ui.screens.communities.icon :as communities.icon] + [status-im.utils.handlers :refer [>evt]] + [status-im.utils.config :as config] + [status-im.utils.security :as security] + [quo2.foundations.typography :as typography] + [quo2.foundations.colors :as quo2.colors] + [status-im.ui2.screens.chat.components.reply :as reply]) + (:require-macros [status-im.utils.views :refer [defview letsubs]])) + +(defn message-timestamp-anim + [anim-opacity show-timestamp?] + (animation/start + (animation/anim-sequence + [(animation/timing + anim-opacity + {:toValue 1 + :duration 100 + :easing (.-ease ^js animation/easing) + :useNativeDriver true}) + (animation/timing + anim-opacity + {:toValue 0 + :delay 2000 + :duration 100 + :easing (.-ease ^js animation/easing) + :useNativeDriver true})]) #(reset! show-timestamp? false))) + +(defview mention-element [from] + (letsubs [contact-name [:contacts/contact-name-by-identity from]] + contact-name)) + +(def edited-at-text (str " ⌫ " (i18n/label :t/edited))) + +(defn message-status [{:keys [outgoing content outgoing-status pinned edited-at in-popover?]}] + (when-not in-popover? ;; We keep track if showing this message in a list in pin-limit-popover + [react/view + {:align-self :flex-end + :position :absolute + :bottom 9 ; 6 Bubble bottom, 3 message baseline + (if (:rtl? content) :left :right) 0 + :flex-direction :row + :align-items :flex-end} + (when outgoing + [icons/icon (case outgoing-status + :sending :tiny-icons/tiny-pending + :sent :tiny-icons/tiny-sent + :not-sent :tiny-icons/tiny-warning + :delivered :tiny-icons/tiny-delivered + :tiny-icons/tiny-pending) + {:width 16 + :height 12 + :color (if pinned colors/gray colors/white) + :accessibility-label (name outgoing-status)}]) + (when edited-at [react/text {:style (style/message-status-text)} edited-at-text])])) + +(defn message-timestamp + [{:keys [timestamp-str in-popover?]} show-timestamp?] + (when-not in-popover? ;; We keep track if showing this message in a list in pin-limit-popover + (let [anim-opacity (animation/create-value 0)] + [react/animated-view {:style (style/message-timestamp-wrapper) :opacity anim-opacity} + (when @show-timestamp? (message-timestamp-anim anim-opacity show-timestamp?)) + [react/text + {:style (style/message-timestamp-text) + :accessibility-label :message-timestamp} + timestamp-str]]))) + +(defview quoted-message + [_ reply pin?] + [react/view {:style (when-not pin? (style/quoted-message-container))} + [reply/reply-message reply false pin?]]) + +(defn system-text? [content-type] + (= content-type constants/content-type-system-text)) + +(defn render-inline [message-text content-type acc {:keys [type literal destination]}] + (case type + "" + (conj acc literal) + + "code" + (conj acc [quo/text {:max-font-size-multiplier react/max-font-size-multiplier + :style (style/inline-code-style) + :monospace true} + literal]) + + "emph" + (conj acc [react/text-class (style/emph-style) literal]) + + "strong" + (conj acc [react/text-class (style/strong-style) literal]) + + "strong-emph" + (conj acc [quo/text (style/strong-emph-style) literal]) + + "del" + (conj acc [react/text-class (style/strikethrough-style) literal]) + + "link" + (conj acc + [react/text-class + {:style + {:color colors/blue + :text-decoration-line :underline} + :on-press + #(when (and (security/safe-link? destination) + (security/safe-link-text? message-text)) + (re-frame/dispatch + [:browser.ui/message-link-pressed destination]))} + destination]) + + "mention" + (conj acc + [react/view {:style {:background-color quo2.colors/primary-50-opa-10 :border-radius 6 :padding-horizontal 3}} + [react/text-class + {:style (merge {:color (if (system-text? content-type) colors/black quo2.colors/primary-50)} + (if (system-text? content-type) typography/font-regular typography/font-medium)) + :on-press (when-not (system-text? content-type) + #(>evt [:chat.ui/show-profile literal]))} + [mention-element literal]]]) + "status-tag" + (conj acc [react/text-class + {:style {:color colors/blue + :text-decoration-line :underline} + :on-press + #(re-frame/dispatch + [:chat.ui/start-public-chat literal])} + "#" + literal]) + + (conj acc literal))) + +(defn render-block [{:keys [content content-type in-popover?]} acc + {:keys [type ^js literal children]}] + (case type + + "paragraph" + (conj acc (reduce + (fn [acc e] (render-inline (:text content) content-type acc e)) + [react/text-class (style/text-style content-type in-popover?)] + children)) + + "blockquote" + (conj acc [react/view (style/blockquote-style) + [react/text-class (style/blockquote-text-style) + (.substring literal 0 (.-length literal))]]) + + "codeblock" + (conj acc [react/view {:style style/codeblock-style} + [quo/text {:max-font-size-multiplier react/max-font-size-multiplier + :style style/codeblock-text-style + :monospace true} + (.substring literal 0 (dec (.-length literal)))]]) + + acc)) + +(defn render-parsed-text [message tree] + (reduce (fn [acc e] (render-block message acc e)) [:<>] tree)) + +(defn render-parsed-text-with-message-status [{:keys [edited-at in-popover?] :as message} tree] + (let [elements (render-parsed-text message tree) + message-status [react/text {:style (style/message-status-placeholder)} + (str (if (not in-popover?) " " " ") (when (and (not in-popover?) edited-at) edited-at-text))] + last-element (peek elements)] + ;; Using `nth` here as slightly faster than `first`, roughly 30% + ;; It's worth considering pure js structures for this code path as + ;; it's perfomance critical + (if (= react/text-class (nth last-element 0)) + ;; Append message status to last text + (conj (pop elements) (conj last-element message-status)) + ;; Append message status to new block + (conj elements message-status)))) + +(defn unknown-content-type + [{:keys [content-type content] :as message}] + [react/view (style/message-view message) + [react/text + {:style {:color colors/white-persist}} + (if (seq (:text content)) + (:text content) + (str "Unhandled content-type " content-type))]]) + +(defn message-not-sent-text + [chat-id message-id] + [react/touchable-highlight + {:on-press + (fn [] + (re-frame/dispatch + [:bottom-sheet/show-sheet + {:content (sheets/options chat-id message-id) + :content-height 200}]) + (react/dismiss-keyboard!))} + [react/view style/not-sent-view + [react/text {:style style/not-sent-text} + (i18n/label :t/status-not-sent-tap)] + [react/view style/not-sent-icon + [icons/icon :main-icons/warning {:color colors/red}]]]]) + +(defn pin-author-name [pinned-by] + (let [user-contact @(re-frame/subscribe [:multiaccount/contact]) + contact-names @(re-frame/subscribe [:contacts/contact-two-names-by-identity pinned-by])] + ;; We append empty spaces to the name as a workaround to make one-line and multi-line label components show correctly + (str " " (if (= pinned-by (user-contact :public-key)) (i18n/label :t/You) (first contact-names))))) + +(def pin-icon-width 10) + +(def pin-icon-height 15) + +(defn pin-icon [] + [icons/icon :main-icons/pin16 {:color (:text-04 @colors/theme) + :height pin-icon-height + :width pin-icon-width}]) + +(defn pinned-by-indicator [pinned-by] + [react/view {:style (style/pin-indicator) + :accessibility-label :pinned-by} + [pin-icon] + [quo/text {:size :small + :color :main + :style (style/pin-author-text)} + (pin-author-name pinned-by)]]) + +(defn message-delivery-status + [{:keys [chat-id message-id outgoing-status message-type]}] + (when (and (not= constants/message-type-private-group-system-message message-type) + (= outgoing-status :not-sent)) + [message-not-sent-text chat-id message-id])) + +(defview message-author-name [from opts] + (letsubs [contact-with-names [:contacts/contact-by-identity from]] + (chat.utils/format-author contact-with-names opts))) + +(defview message-my-name [opts] + (letsubs [contact-with-names [:multiaccount/contact]] + (chat.utils/format-author contact-with-names opts))) + +(defview community-content [{:keys [community-id] :as message}] + (letsubs [{:keys [name description verified] :as community} [:communities/community community-id] + communities-enabled? [:communities/enabled?]] + (when (and communities-enabled? community) + [react/view {:style (assoc (style/message-wrapper message) + :margin-vertical 10 + :margin-left 8 + :width 271)} + (when verified + [react/view (style/community-verified) + [react/text {:style {:font-size 13 + :color colors/blue}} (i18n/label :t/communities-verified)]]) + [react/view (style/community-message verified) + [react/view {:width 62 + :padding-left 14} + (if (= community-id constants/status-community-id) + [react/image {:source (resources/get-image :status-logo) + :style {:width 40 + :height 40}}] + [communities.icon/community-icon community])] + [react/view {:padding-right 14 :flex 1} + [react/text {:style {:font-weight "700" :font-size 17}} + name] + [react/text description]]] + [react/view (style/community-view-button) + [react/touchable-highlight {:on-press #(re-frame/dispatch [:navigate-to + :community + {:community-id (:id community)}])} + [react/text {:style {:text-align :center + :color colors/blue}} (i18n/label :t/view)]]]]))) + +(defn message-content-wrapper + "Author, userpic and delivery wrapper" + [{:keys [last-in-group? + identicon + from in-popover? timestamp-str + deleted-for-me? pinned] + :as message} content {:keys [modal close-modal]}] + (let [response-to (:response-to (:content message))] + [react/view {:style (style/message-wrapper message) + :pointer-events :box-none + :accessibility-label :chat-item} + (when (and (seq response-to) (:quoted-message message)) + [quoted-message response-to (:quoted-message message)]) + [react/view {:style (style/message-body) + :pointer-events :box-none} + [react/view (style/message-author-userpic) + (when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned) + [react/touchable-highlight {:on-press #(do (when modal (close-modal)) + (re-frame/dispatch [:chat.ui/show-profile from]))} + [photos/member-photo from identicon]])] + [react/view {:style (style/message-author-wrapper)} + (when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned) + [react/view {:style {:flex-direction :row :align-items :center}} + [react/touchable-opacity {:style style/message-author-touchable + :disabled in-popover? + :on-press #(do (when modal (close-modal)) + (re-frame/dispatch [:chat.ui/show-profile from]))} + [message-author-name from {:modal modal}]] + [react/text + {:style (merge + {:padding-left 5 + :margin-top 2} + (style/message-timestamp-text)) + :accessibility-label :message-timestamp} + timestamp-str]]) + ;; MESSAGE CONTENT + ;; TODO(yqrashawn): wait for system message component to display deleted for me UI + (if deleted-for-me? + [react/view {:style {:border-width 2 + :border-color :red}} + content] + content) + [link-preview/link-preview-wrapper (:links (:content message)) false false]]] + ; delivery status + [react/view (style/delivery-status) + [message-delivery-status message]]])) + +(def image-max-width 260) +(def image-max-height 192) + +(defn image-set-size [dimensions] + (fn [evt] + (let [width (.-width (.-nativeEvent evt)) + height (.-height (.-nativeEvent evt))] + (if (< width height) + ;; if width less than the height we reduce width proportionally to height + (let [k (/ height image-max-height)] + (when (not= (/ width k) (first @dimensions)) + (reset! dimensions {:width (/ width k) :height image-max-height :loaded true}))) + (swap! dimensions assoc :loaded true))))) + +(defn message-content-image + [{:keys [content]} _] + (let [dimensions (reagent/atom {:width image-max-width :height image-max-height :loaded false}) + visible (reagent/atom false) + uri (:image content)] + (fn [{:keys [in-popover?] :as message} + {:keys [on-long-press]}] + (let [style-opts {:outgoing false + :opacity (if (:loaded @dimensions) 1 0) + :width (:width @dimensions) + :height (:height @dimensions)}] + [:<> + [preview/preview-image {:message message + :visible @visible + :on-close #(do (reset! visible false) + (reagent/flush))}] + [react/touchable-highlight {:on-press (fn [] + (reset! visible true) + (react/dismiss-keyboard!)) + :on-long-press @on-long-press + :disabled in-popover?} + [react/view {:style (style/image-message style-opts) + :accessibility-label :image-message} + (when (or (:error @dimensions) (not (:loaded @dimensions))) + [react/view + (merge (dissoc style-opts :opacity) + {:flex 1 :align-items :center :justify-content :center :position :absolute}) + (if (:error @dimensions) + [icons/icon :main-icons/cancel] + [react/activity-indicator {:animating true}])]) + [fast-image/fast-image {:style (dissoc style-opts :outgoing) + :on-load (image-set-size dimensions) + :on-error #(swap! dimensions assoc :error true) + :source {:uri uri}}] + [react/view {:style (style/image-message-border style-opts)}]]]])))) + +(defmulti ->message :content-type) + +(defmethod ->message constants/content-type-command + [message] + [message.command/command-content message-content-wrapper message]) + +(defmethod ->message constants/content-type-gap + [message] + [message.gap/gap message]) + +(defmethod ->message constants/content-type-system-text [{:keys [content] :as message}] + [react/view {:accessibility-label :chat-item} + [react/view (style/system-message-body message) + [react/view (style/message-view message) + [react/view (style/message-view-content) + [render-parsed-text message (:parsed-text content)]]]]]) + +(defn pin-message [{:keys [chat-id pinned] :as message}] + (let [pinned-messages @(re-frame/subscribe [:chats/pinned chat-id])] + (if (and (not pinned) (> (count pinned-messages) 2)) + (do + (js/setTimeout (fn [] (re-frame/dispatch [:dismiss-keyboard])) 500) + (re-frame/dispatch [:show-popover {:view :pin-limit + :message message + :prevent-closing? true}])) + (re-frame/dispatch [::models.pin-message/send-pin-message (assoc message :pinned (not pinned))])))) + +(defn on-long-press-fn [on-long-press {:keys [pinned message-pin-enabled outgoing edit-enabled show-input?] :as message} content] + (on-long-press + (concat + (when (and outgoing edit-enabled) + [{:type :main + :on-press #(re-frame/dispatch [:chat.ui/edit-message message]) + :label (i18n/label :t/edit-message) + :icon :main-icons/edit-context20 + :id :edit}]) + (when show-input? + [{:type :main + :on-press #(re-frame/dispatch [:chat.ui/reply-to-message message]) + :label (i18n/label :t/message-reply) + :icon :main-icons/reply-context20 + :id :reply}]) + [{:type :main + :on-press #(react/copy-to-clipboard + (reply/get-quoted-text-with-mentions + (get content :parsed-text))) + :label (i18n/label :t/copy-text) + :icon :main-icons/copy-context20 + :id :copy}] + (when message-pin-enabled + [{:type :main + :on-press #(pin-message message) + :label (i18n/label (if pinned :t/unpin-from-chat :t/pin-to-chat)) + :icon :main-icons/pin-context20 + :id (if pinned :unpin :pin)}]) + [{:type :danger + :on-press #(re-frame/dispatch + [:chat.ui/delete-message-for-me message + config/delete-message-for-me-undo-time-limit-ms]) + :label (i18n/label :t/delete-for-me) + :icon :main-icons/delete-context20 + :id :delete-for-me}] + (when (and outgoing config/delete-message-enabled?) + [{:type :danger + :on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message]) + :label (i18n/label :t/delete-for-everyone) + :icon :main-icons/delete-context20 + :id :delete-for-all}])))) + +(defn collapsible-text-message [_ _] + (let [collapsed? (reagent/atom false) + show-timestamp? (reagent/atom false)] + (fn [{:keys [content in-popover?] :as message} on-long-press modal ref] + (let [on-long-press (fn [] + (if @collapsed? + (do (reset! collapsed? false) + (js/setTimeout #(on-long-press-fn on-long-press message content) 200)) + (on-long-press-fn on-long-press message content)))] + (reset! ref on-long-press) + [react/touchable-highlight + (when-not modal + {:on-press (fn [_] + (react/dismiss-keyboard!) + (reset! show-timestamp? true)) + :delay-long-press 100 + :on-long-press on-long-press + :disabled in-popover?}) + [react/view style/message-view-wrapper + [message-timestamp message show-timestamp?] + [react/view {:style (style/message-view message)} + [react/view {:style (style/message-view-content)} + [react/view + [render-parsed-text-with-message-status message (:parsed-text content)]]]]]])))) + +(defmethod ->message constants/content-type-text + [message {:keys [on-long-press modal ref] :as reaction-picker}] + [message-content-wrapper message + [collapsible-text-message message on-long-press modal ref] + reaction-picker]) + +(defmethod ->message constants/content-type-pin [{:keys [from in-popover? timestamp-str] :as message} {:keys [modal close-modal]}] + (let [response-to (:response-to (:content message))] + [react/view {:style (merge {:flex-direction :row :margin-vertical 8} (style/message-wrapper message))} + [react/view {:style {:width photos.style/default-size + :height photos.style/default-size + :margin-horizontal 8 + :border-radius photos.style/default-size + :justify-content :center + :align-items :center + :background-color quo2.colors/primary-50-opa-10}} + [pin-icon]] + [react/view + [react/view {:style {:flex-direction :row :align-items :center}} + [react/touchable-opacity {:style style/message-author-touchable + :disabled in-popover? + :on-press #(do (when modal (close-modal)) + (re-frame/dispatch [:chat.ui/show-profile from]))} + [message-author-name from {:modal modal}]] + [react/text {:style {:font-size 13}} (str " " (i18n/label :pinned-a-message))] + [react/text + {:style (merge + {:padding-left 5 + :margin-top 2} + (style/message-timestamp-text)) + :accessibility-label :message-timestamp} + timestamp-str]] + [quoted-message response-to (:quoted-message message) true]]])) + +(defmethod ->message constants/content-type-community + [message] + [community-content message]) + +(defmethod ->message constants/content-type-status + [{:keys [content content-type] :as message}] + [message-content-wrapper message + [react/view style/status-container + [react/text {:style (style/status-text)} + (reduce + (fn [acc e] (render-inline (:text content) content-type acc e)) + [react/text-class {:style (style/status-text)}] + (-> content :parsed-text peek :children))]]]) + +(defmethod ->message constants/content-type-emoji [] + (let [show-timestamp? (reagent/atom false)] + (fn [{:keys [content pinned in-popover? message-pin-enabled] :as message} + {:keys [on-long-press modal ref] + :as reaction-picker}] + (let [on-long-press (fn [] + (on-long-press + (concat + [{:type :main + :on-press #(re-frame/dispatch [:chat.ui/reply-to-message message]) + :id :reply + :icon :main-icons/reply-context20 + :label (i18n/label :t/message-reply)} + {:type :main + :on-press #(react/copy-to-clipboard (get content :text)) + :id :copy + :icon :main-icons/copy-context20 + :label (i18n/label :t/copy-text)}] + (when message-pin-enabled [{:type :main + :on-press #(pin-message message) + :id :pin + :icon :main-icons/pin-context20 + :label (if pinned (i18n/label :t/unpin) (i18n/label :t/pin))}]))))] + (reset! ref on-long-press) + [message-content-wrapper message + [react/touchable-highlight (when-not modal + {:disabled in-popover? + :on-press (fn [] + (react/dismiss-keyboard!) + (reset! show-timestamp? true)) + :delay-long-press 100 + :on-long-press on-long-press}) + [react/view style/message-view-wrapper + [message-timestamp message show-timestamp?] + [react/view (style/message-view message) + [react/view {:style (style/message-view-content)} + [react/view {:style (style/style-message-text)} + [react/text {:style (style/emoji-message message)} + (:text content)]] + [message-status message]]]]] + reaction-picker])))) + +(defmethod ->message constants/content-type-sticker + [{:keys [content from outgoing in-popover?] + :as message} + {:keys [on-long-press modal ref] + :as reaction-picker}] + (let [pack (get-in content [:sticker :pack]) + on-long-press (fn [] + (on-long-press + (when-not outgoing + [{:type :main + :icon :main-icons/stickers-context20 + :on-press #(when pack + (re-frame/dispatch [:chat.ui/show-profile from])) + :label (i18n/label :t/see-sticker-set)}])))] + (reset! ref on-long-press) + [message-content-wrapper message + [react/touchable-highlight (when-not modal + {:disabled in-popover? + :accessibility-label :sticker-message + :on-press (fn [_] + (when pack + (re-frame/dispatch [:stickers/open-sticker-pack (str pack)])) + (react/dismiss-keyboard!)) + :delay-long-press 100 + :on-long-press on-long-press}) + [fast-image/fast-image {:style {:margin 10 :width 140 :height 140} + :source {:uri (str (-> content :sticker :url) "&download=true")}}]] + reaction-picker])) + +(defmethod ->message constants/content-type-image + [{:keys [content in-popover? outgoing] :as message} + {:keys [on-long-press modal ref] + :as reaction-picker}] + (let [on-long-press (fn [] + (on-long-press + (concat [{:type :main + :on-press #(re-frame/dispatch [:chat.ui/reply-to-message message]) + :id :reply + :icon :main-icons/reply-context20 + :label (i18n/label :t/message-reply)} + {:type :main + :on-press #(re-frame/dispatch [:chat.ui/save-image-to-gallery (:image content)]) + :id :save + :icon :main-icons/save-context20 + :label (i18n/label :t/save-image-library)} + {:type :main + :on-press #(images/download-image-http + (get-in message [:content :image]) preview/share) + :id :share + :icon :main-icons/share-context20 + :label (i18n/label :t/share-image)}] + [{:type :danger + :on-press #(re-frame/dispatch + [:chat.ui/delete-message-for-me message + config/delete-message-for-me-undo-time-limit-ms]) + :label (i18n/label :t/delete-for-me) + :icon :main-icons/delete-context20 + :id :delete-for-me}] + (when (and outgoing config/delete-message-enabled?) + [{:type :danger + :on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message]) + :label (i18n/label :t/delete-for-everyone) + :icon :main-icons/delete-context20 + :id :delete}]))))] + (reset! ref on-long-press) + [message-content-wrapper message + [message-content-image message + {:modal modal + :disabled in-popover? + :delay-long-press 100 + :on-long-press ref}] + reaction-picker])) + +(defmethod ->message constants/content-type-audio [] + (let [show-timestamp? (reagent/atom false)] + (fn [{:keys [outgoing pinned] :as message} + {:keys [on-long-press modal ref] + :as reaction-picker}] + (let [on-long-press (fn [] (on-long-press [{:type :main + :on-press #(re-frame/dispatch [:chat.ui/reply-to-message message]) + :label (i18n/label :t/message-reply) + :icon :main-icons/reply-context20 + :id :reply} + {:type :main + :on-press #(pin-message message) + :label (i18n/label (if pinned :t/unpin-from-chat :t/pin-to-chat)) + :icon :main-icons/pin-context20 + :id (if pinned :unpin :pin)} + {:type :danger + :on-press #(re-frame/dispatch + [:chat.ui/delete-message-for-me message + config/delete-message-for-me-undo-time-limit-ms]) + :label (i18n/label :t/delete-for-me) + :icon :main-icons/delete-context20 + :id :delete-for-me} + (when (and outgoing config/delete-message-enabled?) + {:type :danger + :on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message]) + :label (i18n/label :t/delete-for-everyone) + :icon :main-icons/delete-context20 + :id :delete})]))] + (reset! ref on-long-press) + [message-content-wrapper message + [react/touchable-highlight + (when-not modal + {:on-long-press on-long-press + :on-press (fn [] + (reset! show-timestamp? true))}) + [react/view style/message-view-wrapper + [message-timestamp message show-timestamp?] + [react/view {:style (style/message-view message) :accessibility-label :audio-message} + [react/view {:style (style/message-view-content)} + [message.audio/message-content message] [message-status message]]]]] + reaction-picker])))) + +(defn contact-request-status-pending [] + [react/view {:style {:flex-direction :row}} + [quo/text {:style {:margin-right 5.27} + :weight :medium + :color :secondary} + (i18n/label :t/contact-request-pending)] + [react/activity-indicator {:animating true + :size :small + :color colors/gray}]]) + +(defn contact-request-status-accepted [] + [quo/text {:style {:color colors/green} + :weight :medium} + (i18n/label :t/contact-request-accepted)]) + +(defn contact-request-status-declined [] + [quo/text {:style {:color colors/red} + :weight :medium} + (i18n/label :t/contact-request-declined)]) + +(defn contact-request-status-label [state] + [react/view {:style (style/contact-request-status-label state)} + (case state + constants/contact-request-message-state-pending [contact-request-status-pending] + constants/contact-request-message-state-accepted [contact-request-status-accepted] + constants/contact-request-message-state-declined [contact-request-status-declined])]) + +(defmethod ->message constants/content-type-contact-request + [message _] + [react/view {:style (style/content-type-contact-request)} + [react/image {:source (resources/get-image :hand-wave) + :style {:width 112 + :height 97}}] + [quo/text {:style {:margin-top 6} + :weight :bold + :size :large} + (i18n/label :t/contact-request)] + [react/view {:style {:padding-horizontal 16}} + [quo/text {:style {:margin-top 2 + :margin-bottom 14}} + (get-in message [:content :text])]] + [contact-request-status-label (:contact-request-state message)]]) + +(defmethod ->message :default [message] + [message-content-wrapper message + [unknown-content-type message]]) + +(defn chat-message [{:keys [pinned pinned-by mentioned] :as message}] + (let [reactions @(re-frame/subscribe [:chats/message-reactions (:message-id message) (:chat-id message)]) + own-reactions (reduce (fn [acc {:keys [emoji-id own]}] + (if own (conj acc emoji-id) acc)) + [] reactions) + send-emoji (fn [{:keys [emoji-id]}] + (re-frame/dispatch [::models.reactions/send-emoji-reaction + {:message-id (:message-id message) + :emoji-id emoji-id}])) + retract-emoji (fn [{:keys [emoji-id emoji-reaction-id]}] + (re-frame/dispatch [::models.reactions/send-emoji-reaction-retraction + {:message-id (:message-id message) + :emoji-id emoji-id + :emoji-reaction-id emoji-reaction-id}])) + on-emoji-press (fn [emoji-id] + (let [active ((set own-reactions) emoji-id)] + (if active + (retract-emoji {:emoji-id emoji-id + :emoji-reaction-id (reactions/extract-id reactions emoji-id)}) + (send-emoji {:emoji-id emoji-id})))) + on-open-drawer (fn [actions] + (re-frame/dispatch [:bottom-sheet/show-sheet + {:content (message-context-drawer/message-options + actions + (into #{} (js->clj own-reactions)) + #(on-emoji-press %))}])) + on-long-press (atom nil)] + [react/view {:style (merge (when (or mentioned pinned) {:background-color quo2.colors/primary-50-opa-5 :border-radius 16 :margin-bottom 4}) {:margin-horizontal 8})} + (when pinned + [react/view {:style (style/pin-indicator-container)} + [pinned-by-indicator pinned-by]]) + [->message message {:ref on-long-press + :modal false + :on-long-press on-open-drawer}] + [reaction-row/message-reactions message reactions nil on-emoji-press on-long-press]])) ;; TODO: pass on-open-drawer function \ No newline at end of file diff --git a/src/status_im/ui2/screens/chat/messages/view.cljs b/src/status_im/ui2/screens/chat/messages/view.cljs new file mode 100644 index 0000000000..31abe83ad4 --- /dev/null +++ b/src/status_im/ui2/screens/chat/messages/view.cljs @@ -0,0 +1,191 @@ +(ns status-im.ui2.screens.chat.messages.view + (:require [reagent.core :as reagent] + [quo.react-native :as rn] + [quo2.foundations.colors :as quo2.colors] + [status-im.constants :as constants] + [status-im.utils.handlers :refer [evt]] + [status-im.ui.components.list.views :as list] + [status-im.ui2.screens.chat.messages.message :as message] + [status-im.ui.screens.chat.group :as chat.group] + [status-im.ui.screens.chat.message.datemark :as message-datemark] + [status-im.ui.screens.chat.message.gap :as gap] + [status-im.utils.utils :as utils] + [status-im.utils.platform :as platform] + [status-im.ui.screens.chat.state :as state] + [status-im.ui.components.icons.icons :as icons])) + +(defonce ^:const threshold-percentage-to-show-floating-scroll-down-button 75) + +(defonce show-floating-scroll-down-button (reagent/atom false)) +(defonce messages-list-ref (atom nil)) + +(def list-key-fn #(or (:message-id %) (:value %))) +(def list-ref #(reset! messages-list-ref %)) + +(defn scroll-to-bottom [] + (some-> ^js @messages-list-ref (.scrollToOffset #js {:y 0 :animated true}))) + +(defn floating-scroll-down-button [show-input?] + [rn/touchable-without-feedback + {:on-press scroll-to-bottom} + [rn/view {:style {:position :absolute + :bottom (if show-input? 126 12) + :right 12 + :height 24 + :width 24 + :align-items :center + :justify-content :center + :border-radius (/ 24 2) + :background-color (quo2.colors/theme-colors quo2.colors/neutral-80-opa-70 quo2.colors/white-opa-70)}} + ;;TODO icon from quo2 should be used instead! + [icons/icon + :main-icons/arrow-down {:color (quo2.colors/theme-colors quo2.colors/white quo2.colors/neutral-100) + :width 12 + :height 12}]]]) + +(defn on-scroll [^js ev] + (let [y (-> ev .-nativeEvent .-contentOffset .-y) + layout-height (-> ev .-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 chat-intro-header-container + [] + ;;not implemented + [rn/view]) + +(defn list-footer [{:keys [chat-id] :as chat}] + (let [loading-messages? (evt [:chat.ui/load-more-messages-for-current-chat]) + (utils/set-timeout #(>evt [:chat.ui/load-more-messages-for-current-chat]) + (if platform/low-device? 700 200)))) + +(defn get-render-data [{:keys [group-chat chat-id public? community-id admins space-keeper show-input? edit-enabled in-pinned-view?]}] + (let [current-public-key ( + ;;do not use anonymous functions for handlers + [list/flat-list + (merge + pan-responder + {:key-fn list-key-fn + :ref list-ref + :header [list-header chat] + :footer [list-footer chat] + :data (when-not should-send-contact-request? + messages) + :render-data (get-render-data {:group-chat group-chat + :chat-id chat-id + :public? public? + :community-id community-id + :admins admins + :show-input? show-input? + :edit-enabled true + :in-pinned-view? false}) + :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 16) + :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})})] + (when @show-floating-scroll-down-button + [floating-scroll-down-button show-input?])])) \ No newline at end of file diff --git a/src/status_im/ui2/screens/chat/view.cljs b/src/status_im/ui2/screens/chat/view.cljs new file mode 100644 index 0000000000..a28ee3a778 --- /dev/null +++ b/src/status_im/ui2/screens/chat/view.cljs @@ -0,0 +1,85 @@ +(ns status-im.ui2.screens.chat.view + (:require [reagent.core :as reagent] + [status-im.ui.components.connectivity.view :as connectivity] + [status-im.ui.components.topbar :as topbar] + [status-im.ui2.screens.chat.composer.view :as composer] + [status-im.utils.debounce :as debounce] + [quo.react-native :as rn] + [quo2.components.buttons.button :as quo2.button] + [quo2.foundations.colors :as quo2.colors] + [status-im.ui.components.react :as react] + [status-im.navigation.state :as navigation.state] + [status-im.ui2.screens.chat.messages.view :as messages] + [status-im.utils.handlers :refer [evt]] + [status-im.ui.components.icons.icons :as icons] + [re-frame.db])) + +(defn topbar-content [] + (let [window-width (evt [:navigate-back])} + [icons/icon :main-icons/arrow-left {:color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)}]]) + +(defn search-button [] + [quo2.button/button {:type :grey + :size 32 + :width 32 + :accessibility-label "search-button"} + [icons/icon :main-icons/search {:color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)}]]) + +(defn navigate-back-handler [] + (when (and (not @navigation.state/curr-modal) (= (get @re-frame.db/app-db :view-id) :chat)) + (react/hw-back-remove-listener navigate-back-handler) + (>evt [:close-chat]) + (>evt [:navigate-back]))) + +(defn chat-render [] + (let [{:keys [chat-id show-input?] :as chat} + ;;we want to react only on these fields, do not use full chat map here + (