diff --git a/src/status_im/ui/screens/chat/message/audio.cljs b/src/status_im/ui/screens/chat/message/audio.cljs index 6b09b0a534..1c76ec24aa 100644 --- a/src/status_im/ui/screens/chat/message/audio.cljs +++ b/src/status_im/ui/screens/chat/message/audio.cljs @@ -181,7 +181,7 @@ :message-id @current-player-message-id})) nil) -(defview message-content [{:keys [audio audio-duration-ms message-id outgoing]} timestamp-view] +(defview message-content [{:keys [audio audio-duration-ms message-id outgoing]}] (letsubs [state (reagent/atom nil) progress (reagent/atom 0) progress-anim (anim/create-value 0) @@ -223,5 +223,4 @@ (#{:playing :paused :seeking} (:general @state)) @progress :else (:duration @state)) s (quot time 1000)] - (gstring/format "%02d:%02d" (quot s 60) (mod s 60)))] - timestamp-view]])))) + (gstring/format "%02d:%02d" (quot s 60) (mod s 60)))]]])))) diff --git a/src/status_im/ui/screens/chat/message/message.cljs b/src/status_im/ui/screens/chat/message/message.cljs index 0c0bf42369..56d52b9139 100644 --- a/src/status_im/ui/screens/chat/message/message.cljs +++ b/src/status_im/ui/screens/chat/message/message.cljs @@ -24,42 +24,64 @@ [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.pin-message :as models.pin-message]) (: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 - ([message] - [message-timestamp message false]) - ([{:keys [timestamp-str outgoing content outgoing-status pinned edited-at in-popover?]} justify-timestamp?] - (when-not in-popover? ;; We keep track if showing this message in a list in pin-limit-popover - [react/view (when justify-timestamp? - {: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 (and outgoing justify-timestamp?) - [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)}]) - [react/text {:style (style/message-timestamp-text (and outgoing (not pinned)))} - (str - timestamp-str - (when edited-at edited-at-text))]]))) + [{: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)} + timestamp-str]]))) (defview quoted-message [_ {:keys [from parsed-text image]} outgoing current-public-key public? pinned] @@ -168,19 +190,19 @@ (defn render-parsed-text [message tree] (reduce (fn [acc e] (render-block message acc e)) [:<>] tree)) -(defn render-parsed-text-with-timestamp [{:keys [timestamp-str outgoing 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) - timestamp [react/text {:style (style/message-timestamp-placeholder)} - (str (if (and outgoing (not in-popover?)) " " " ") (when-not in-popover? (str timestamp-str (when edited-at edited-at-text))))] + 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 timestamp to last text - (conj (pop elements) (conj last-element timestamp)) - ;; Append timestamp to new block - (conj elements timestamp)))) + ;; 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}] @@ -409,7 +431,8 @@ (defn collapsible-text-message [{:keys [mentioned]} _] (let [collapsed? (reagent/atom false) - collapsible? (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? @@ -418,7 +441,8 @@ [react/touchable-highlight (when-not modal {:on-press (fn [_] - (react/dismiss-keyboard!)) + (react/dismiss-keyboard!) + (reset! show-timestamp? true)) :delay-long-press 100 :on-long-press (fn [] (if @collapsed? @@ -426,40 +450,41 @@ (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 (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-timestamp message (:parsed-text content)]]) - (when-not @collapsed? - [message-timestamp message true]) - (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}]]]))]]])))) + [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}] @@ -484,33 +509,37 @@ (defmethod ->message constants/content-type-emoji [{: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!)) - :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 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-timestamp message]]]] - reaction-picker])) + (let [response-to (:response-to content) + show-timestamp? (reagent/atom false)] + (fn [] [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?] @@ -556,14 +585,19 @@ (defmethod ->message constants/content-type-audio [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 []))}) - [react/view {:style (style/message-view message) :accessibility-label :audio-message} - [react/view {:style (style/message-view-content)} - [message.audio/message-content message [message-timestamp message false]]]]] - reaction-picker]) + (let [show-timestamp? (reagent/atom false)] + (fn [] [message-content-wrapper message + [react/touchable-highlight (when-not modal + {:on-long-press + (fn [] (on-long-press [])) + :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]))) (defmethod ->message :default [message] [message-content-wrapper message diff --git a/src/status_im/ui/screens/chat/styles/message/audio.cljs b/src/status_im/ui/screens/chat/styles/message/audio.cljs index 8fb4003ad9..0a941c34e1 100644 --- a/src/status_im/ui/screens/chat/styles/message/audio.cljs +++ b/src/status_im/ui/screens/chat/styles/message/audio.cljs @@ -43,5 +43,5 @@ :justify-content :space-between}) (defn timestamp [outgoing] - (merge (message.style/message-timestamp-text outgoing) + (merge (message.style/audio-message-timestamp-text outgoing) {:margin-left 40})) \ No newline at end of file diff --git a/src/status_im/ui/screens/chat/styles/message/message.cljs b/src/status_im/ui/screens/chat/styles/message/message.cljs index b21cee5d3d..80dcdf2849 100644 --- a/src/status_im/ui/screens/chat/styles/message/message.cljs +++ b/src/status_im/ui/screens/chat/styles/message/message.cljs @@ -29,11 +29,33 @@ (def message-timestamp {:font-size 10}) -(defn message-timestamp-placeholder +(defn message-status-placeholder [] (merge message-timestamp {:opacity 0 :color "rgba(0,0,0,0)"})) -(defn message-timestamp-text +(defn message-timestamp-wrapper [{:keys [last-in-group? outgoing group-chat]}] + {:justify-content :center + (if outgoing :margin-right :margin-left) 12 ;; horizontal margin is only required at the adjust side of the message. + :margin-top (if (and last-in-group? + (or outgoing + (not group-chat))) + 16 + 0) ;; Add gap between message groups + :opacity 0}) + +(defn message-timestamp-text [] + (merge message-timestamp + {:color colors/gray + :text-align :center})) + +(defn message-status-text [outgoing] + {:font-size 10 + :line-height 10 + :color (if outgoing + colors/white-transparent-70-persist + colors/gray)}) + +(defn audio-message-timestamp-text [outgoing] (merge message-timestamp {:line-height 10 @@ -44,7 +66,7 @@ (defn message-wrapper [{:keys [outgoing in-popover?]}] (if (and outgoing (not in-popover?)) {:margin-left 96} - {:margin-right 52})) + {:margin-right 96})) (defn message-author-wrapper [outgoing display-photo? in-popover?] @@ -156,10 +178,10 @@ :padding-left 3}) (defn emoji-message - [{:keys [incoming-group]}] + [{:keys [incoming-group outgoing]}] {:font-size 28 :line-height 34 ;TODO: Smaller crops the icon on the top - :margin-right 12 + :margin-right (if outgoing 18 0) ;; Margin to display outgoing message status :margin-top (if incoming-group 4 0)}) (defn collapse-button [] @@ -171,6 +193,10 @@ :shadow-color (:shadow-01 @colors/theme) :shadow-offset {:width 0 :height 4}}) +(defn message-view-wrapper [outgoing] + {:align-self :flex-end + :flex-direction (if outgoing :row :row-reverse)}) + (defn message-view [{:keys [content-type outgoing group-chat last-in-group? mentioned pinned]}] (merge