feat: implement new message context drawer

Signed-off-by: Brian Sztamfater <brian@status.im>
This commit is contained in:
Brian Sztamfater 2022-08-28 20:57:48 -03:00
parent 1fb71bdfc8
commit 5fa203c6f2
No known key found for this signature in database
GPG Key ID: 59EB921E0706B48F
42 changed files with 330 additions and 138 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 808 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 769 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1016 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 902 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 514 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1010 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1010 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 838 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 672 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 501 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,43 @@
(ns quo2.components.list-items.menu-item
(:require [quo.react-native :as rn]
[quo2.foundations.colors :as colors :refer [theme-colors]]
[quo2.components.markdown.text :as text]
[status-im.ui.components.icons.icons :as icons]))
(defn themes [type]
(case type
:main {:icon-color (theme-colors colors/neutral-50 colors/neutral-10)
:background (theme-colors colors/white colors/neutral-90)
:text-color (theme-colors colors/black colors/white)}
:danger {:icon-color (theme-colors colors/danger-50 colors/danger-60)
:background (theme-colors colors/white colors/neutral-90)
:text-color (theme-colors colors/danger-50 colors/danger-60)}))
(defn menu-item
[{:keys [type title accessibility-label icon on-press]
:or {type :main}}]
(let [{:keys [icon-color text-color background]} (themes type)]
[rn/touchable-opacity
(merge {:accessibility-label accessibility-label
:style {:background-color background
:height 48
:flex-direction :row
:align-items :center}}
(when on-press
{:on-press on-press}))
[rn/view {:style {:flex-direction :row
:flex-grow 0
:flex-shrink 1
:padding-horizontal 20}}
[rn/view {:style {:width 20
:height 20
:align-items :center
:justify-content :center
:margin-right 12}}
[icons/icon icon {:color icon-color}]]
[text/text {:weight :medium
:style {:color text-color}
:ellipsize-mode :tail
:number-of-lines 1
:size :paragraph-1}
title]]]))

View File

@ -0,0 +1,78 @@
(ns status-im.ui.screens.chat.bottom-sheets.context-drawer
(:require [quo.react-native :as rn]
[status-im.ui.components.react :as react]
[re-frame.core :as re-frame]
[status-im.ui.screens.wallet.components.views :as components]
[status-im.constants :as constants]
[quo2.components.buttons.button :as quo2.button]
[quo2.foundations.colors :as quo2.colors]
[quo2.components.list-items.menu-item :as quo2.menu-item]))
(defn message-options [actions own-reactions send-emoji]
(fn []
(let [main-actions (filter #(= (:type %) :main) actions)
danger-actions (filter #(= (:type %) :danger) actions)
admin-actions (filter #(= (:type %) :admin) actions)]
[react/view {:flex 1}
[react/view {:style {:width "100%"
:flex-direction :row
:justify-content :space-between
:padding-horizontal 30
:padding-top 5
:padding-bottom 15}}
(doall
(for [[id resource] constants/reactions-old
:let [active (own-reactions id)]]
^{:key id}
[quo2.button/button (merge
{:width 40
:size 40
:type :grey
:accessibility-label :reply-cancel-button
:on-press #(do
(send-emoji id)
(re-frame/dispatch [:bottom-sheet/hide]))}
(when active {:style {:background-color quo2.colors/neutral-70}}))
[rn/image {:source resource
:style {:height 20
:width 20}}]]))]
[rn/view {:style {:padding-horizontal 8}}
(for [action main-actions]
^{:key (:id action)}
(let [on-press (:on-press action)]
[quo2.menu-item/menu-item
{:type :main
:title (:label action)
:accessibility-label (:label action)
:icon (:icon action)
:on-press #(do
(when on-press (on-press))
(re-frame/dispatch [:bottom-sheet/hide]))}]))
(when-not (empty? danger-actions)
[rn/view {:style {:padding-vertical 8}}
[components/separator]])
(for [action danger-actions]
^{:key (:id action)}
(let [on-press (:on-press action)]
[quo2.menu-item/menu-item
{:type :danger
:title (:label action)
:accessibility-label (:label action)
:icon (:icon action)
:on-press #(do
(when on-press (on-press))
(re-frame/dispatch [:bottom-sheet/hide]))}]))
(when-not (empty? admin-actions)
[rn/view {:style {:padding-vertical 8}}
[components/separator]])
(for [action admin-actions]
^{:key (:id action)}
(let [on-press (:on-press action)]
[quo2.menu-item/menu-item
{:type :danger
:title (:label action)
:accessibility-label (:label action)
:icon (:icon action)
:on-press #(do
(when on-press (on-press))
(re-frame/dispatch [:bottom-sheet/hide]))}]))]])))

View File

@ -26,7 +26,9 @@
[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])
[status-im.ui.components.fast-image :as fast-image]
[status-im.ui.screens.chat.bottom-sheets.context-drawer :as message-context-drawer]
[status-im.ui.screens.chat.message.reactions-row :as reaction-row])
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn message-timestamp-anim
@ -344,12 +346,12 @@
(swap! dimensions assoc :loaded true)))))
(defn message-content-image
[{:keys [content in-popover?] :as message}
{:keys [on-long-press]}]
[{:keys [content]} _]
(let [dimensions (reagent/atom {:width image-max-width :height image-max-height :loaded false})
visible (reagent/atom false)
uri (:image content)]
(fn []
(fn [{:keys [in-popover?] :as message}
{:keys [on-long-press]}]
(let [style-opts {:outgoing false
:opacity (if (:loaded @dimensions) 1 0)
:width (:width @dimensions)
@ -362,7 +364,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}
@ -410,54 +412,66 @@
(on-long-press
(concat
(when (and outgoing edit-enabled)
[{:on-press #(re-frame/dispatch [:chat.ui/edit-message message])
:label (i18n/label :t/edit)
[{: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?
[{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
[{: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}])
[{:on-press #(react/copy-to-clipboard
[{:type :main
: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)
:label (i18n/label :t/copy-text)
:icon :main-icons/copy-context20
:id :copy}]
(when message-pin-enabled
[{:on-press #(pin-message message)
:label (if pinned (i18n/label :t/unpin) (i18n/label :t/pin))
[{: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)}])
(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}]))))
[{: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]
[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
[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)]]]]]])))
(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] :as reaction-picker}]
[message {:keys [on-long-press modal ref] :as reaction-picker}]
[message-content-wrapper message
[collapsible-text-message message on-long-press modal]
[collapsible-text-message message on-long-press modal ref]
reaction-picker])
(defmethod ->message constants/content-type-community
@ -477,42 +491,60 @@
(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]
{:keys [on-long-press modal ref]
:as reaction-picker}]
[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
[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])))
(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]
{:keys [on-long-press modal ref]
:as reaction-picker}]
(let [pack (get-in content [:sticker :pack])]
(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?
@ -521,66 +553,83 @@
(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)}])))})
: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]
{:keys [on-long-press modal ref]
: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])
(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)}]
(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] :as message}
{:keys [on-long-press modal]
(fn [{:keys [outgoing pinned] :as message}
{:keys [on-long-press modal ref]
: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
[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])))
(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)}
(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}}
@ -630,22 +679,37 @@
[unknown-content-type message]])
(defn chat-message [{:keys [display-photo? pinned pinned-by] :as message}]
[:<>
[reactions/with-reaction-picker
{:message message
:reactions @(re-frame/subscribe [:chats/message-reactions (:message-id message) (:chat-id message)])
:picker-on-open (fn [])
:picker-on-close (fn [])
: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)}
[pinned-by-indicator display-photo? pinned-by]])])
(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)]
[:<>
[->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
(when pinned
[react/view {:style (style/pin-indicator-container)}
[pinned-by-indicator display-photo? pinned-by]])]))

View File

@ -19,7 +19,7 @@
:width width
:height height})))))
(defn- extract-id [reactions id]
(defn extract-id [reactions id]
(->> reactions
(filter (fn [{:keys [emoji-id]}] (= emoji-id id)))
first

View File

@ -19,4 +19,4 @@
:clicks quantity
:on-press #(on-emoji-press emoji-id)}]])
;; on-press won't work until we integrate Message Context Drawer
[quo2.reaction/open-reactions-menu {:on-press on-open}]]))
[quo2.reaction/open-reactions-menu (when @on-open {:on-press @on-open})]]))

View File

@ -1790,6 +1790,13 @@
"link-to-community": "Link to community",
"external-link": "External link",
"code-snippet": "Code snippet",
"kicked": "Kicked"
"kicked": "Kicked",
"delete-for-everyone": "Delete for everyone",
"pin-to-chat": "Pin to the chat",
"unpin-from-chat": "Unpin from the chat",
"copy-text": "Copy text",
"edit-message": "Edit message",
"save-image-library": "Save image to library",
"share-image": "Share image",
"see-sticker-set": "See the full sticker set"
}