move messages to status-im2 (#14573)

* move messages to status-im2
This commit is contained in:
flexsurfer 2022-12-23 15:33:54 +01:00 committed by GitHub
parent a8d392e4de
commit a39e0f6fbd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 767 additions and 867 deletions

View File

@ -4,18 +4,15 @@
[quo2.foundations.colors :as colors] [quo2.foundations.colors :as colors]
[react-native.core :as rn])) [react-native.core :as rn]))
(defn date (defn date [value]
[value] [rn/view {:margin-vertical 8
[rn/view :padding-right 20
{:margin-vertical 16 :padding-left 60}
:padding-right 8 [text/text {:weight :medium
:padding-left 62} :accessibility-label :divider-date-text
[text/text :size :label
{:weight :medium :style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)
:accessibility-label :divider-date-text :text-transform :capitalize
:size :label :margin-bottom 4}}
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)
:text-transform :capitalize
:margin-bottom 4}}
value] value]
[separator/separator]]) [separator/separator]])

View File

@ -4,7 +4,6 @@
(def container (def container
{:flex 1 {:flex 1
:flex-wrap :wrap :flex-wrap :wrap
:height 18
:flex-direction :row :flex-direction :row
:align-items :center}) :align-items :center})
@ -40,4 +39,4 @@
(defn time-text (defn time-text
[ens?] [ens?]
{:color colors/neutral-50 {:color colors/neutral-50
:margin-left (if ens? 8 4)}) :margin-left (if ens? 8 4)})

View File

@ -95,5 +95,6 @@
[text/text [text/text
{:monospace true {:monospace true
:size :paragraph-2 :size :paragraph-2
:accessibility-label :message-timestamp
:style (style/time-text ens?)} :style (style/time-text ens?)}
time-str]]))]) time-str]]))])

View File

@ -146,12 +146,11 @@
[rn/view [rn/view
{:style (assoc centrify-style {:style (assoc centrify-style
:flex-direction :row :flex-direction :row
:flex 1
:justify-content :flex-end)} :justify-content :flex-end)}
(let [last-icon-index (-> right-section-buttons count dec)] (let [last-icon-index (-> right-section-buttons count dec)]
(map-indexed (fn [index {:keys [icon on-press type] :or {type :grey}}] (map-indexed (fn [index {:keys [icon on-press type style] :or {type :grey}}]
^{:key index} ^{:key index}
[rn/view {:style {:margin-right (if (not= index last-icon-index) 8 0)}} [rn/view {:style (merge {:margin-right (if (not= index last-icon-index) 8 0)} style)}
[button/button {:on-press on-press :icon true :type type :size 32} [button/button {:on-press on-press :icon true :type type :size 32}
icon]]) icon]])
right-section-buttons))]) right-section-buttons))])

View File

@ -1,31 +1,18 @@
(ns quo2.components.reactions.reaction (ns quo2.components.reactions.reaction
(:require [quo2.components.icon :as icons] (:require [quo2.components.icon :as icons]
[quo2.components.markdown.text :as quo2.text] [quo2.components.markdown.text :as text]
[quo2.components.reactions.style :as style]
[quo2.foundations.colors :as colors] [quo2.foundations.colors :as colors]
[quo2.theme :as theme] [quo2.theme :as theme]
[react-native.core :as rn])) [react-native.core :as rn]))
(def reaction-styling (defn add-reaction
{:flex-direction :row
:justify-content :center
:align-items :center
:padding-horizontal 8
:border-radius 8
:height 24})
(defn open-reactions-menu
[{:keys [on-press]}] [{:keys [on-press]}]
(let [dark? (theme/dark?)] (let [dark? (theme/dark?)]
[rn/touchable-opacity [rn/touchable-opacity
{:on-press on-press {:on-press on-press
:accessibility-label :emoji-reaction-add :accessibility-label :emoji-reaction-add
:style (merge reaction-styling :style (style/add-reaction)}
{:padding-horizontal 9
:border-width 1
:margin-top 5
:border-color (if dark?
colors/neutral-70
colors/neutral-30)})}
[icons/icon :i/add [icons/icon :i/add
{:size 20 {:size 20
:color (if dark? :color (if dark?
@ -35,41 +22,17 @@
(defn reaction (defn reaction
"Add your emoji as a param here" "Add your emoji as a param here"
[{:keys [emoji clicks neutral? on-press accessibility-label]}] [{:keys [emoji clicks neutral? on-press accessibility-label]}]
(let [dark? (theme/dark?) (let [numeric-value (int clicks)]
text-color (if dark?
colors/white
colors/neutral-100)
numeric-value (int clicks)
clicks-positive? (pos? numeric-value)]
[rn/touchable-opacity [rn/touchable-opacity
{:on-press on-press {:on-press on-press
:accessibility-label accessibility-label :accessibility-label accessibility-label
:style (merge reaction-styling :style (style/reaction neutral?)}
(cond-> [icons/icon emoji {:no-color true
{:background-color :size 16}]
(if dark? [text/text {:size :paragraph-2
(if neutral? :weight :semi-bold
colors/neutral-70 :flex-direction :row
:transparent) :align-items :center
(if neutral? :justify-content :center}
colors/neutral-30 (when (pos? numeric-value)
:transparent))} (str " " numeric-value))]]))
(and dark? (not neutral?))
(assoc :border-color colors/neutral-70
:border-width 1)
(and (not dark?) (not neutral?))
(assoc :border-color colors/neutral-30
:border-width 1)))}
[icons/icon emoji
{:no-color true
:size 16}]
[quo2.text/text
{:size :paragraph-2
:weight :semi-bold
:color text-color
:flex-direction :row
:align-items :center
:justify-content :center}
(if clicks-positive?
(str " " numeric-value)
"")]]))

View File

@ -0,0 +1,31 @@
(ns quo2.components.reactions.style
(:require [quo2.foundations.colors :as colors]))
(def reaction-styling
{:flex-direction :row
:justify-content :center
:align-items :center
:padding-horizontal 8
:border-radius 8
:height 24})
(defn add-reaction []
(merge reaction-styling
{:padding-horizontal 9
:border-width 1
:border-color (colors/theme-colors colors/neutral-30 colors/neutral-70)}))
(defn reaction [neutral?]
(merge reaction-styling
(cond-> {:background-color (colors/theme-colors (if neutral?
colors/neutral-30
:transparent)
(if neutral?
colors/neutral-70
:transparent))}
(and (colors/dark?) (not neutral?))
(assoc :border-color colors/neutral-70
:border-width 1)
(and (not (colors/dark?)) (not neutral?))
(assoc :border-color colors/neutral-30
:border-width 1))))

View File

@ -29,6 +29,7 @@
quo2.components.list-items.preview-list quo2.components.list-items.preview-list
quo2.components.loaders.skeleton quo2.components.loaders.skeleton
quo2.components.markdown.text quo2.components.markdown.text
quo2.components.messages.author.view
quo2.components.messages.gap quo2.components.messages.gap
quo2.components.messages.system-message quo2.components.messages.system-message
quo2.components.navigation.floating-shell-button quo2.components.navigation.floating-shell-button
@ -64,6 +65,7 @@
(def gap quo2.components.messages.gap/gap) (def gap quo2.components.messages.gap/gap)
(def system-message quo2.components.messages.system-message/system-message) (def system-message quo2.components.messages.system-message/system-message)
(def reaction quo2.components.reactions.reaction/reaction) (def reaction quo2.components.reactions.reaction/reaction)
(def add-reaction quo2.components.reactions.reaction/add-reaction)
(def tags quo2.components.tags.tags/tags) (def tags quo2.components.tags.tags/tags)
(def user-avatar-tag quo2.components.tags.context-tags/user-avatar-tag) (def user-avatar-tag quo2.components.tags.context-tags/user-avatar-tag)
(def context-tag quo2.components.tags.context-tags/context-tag) (def context-tag quo2.components.tags.context-tags/context-tag)
@ -77,6 +79,7 @@
(def disclaimer quo2.components.selectors.disclaimer/disclaimer) (def disclaimer quo2.components.selectors.disclaimer/disclaimer)
(def checkbox quo2.components.selectors.selectors/checkbox) (def checkbox quo2.components.selectors.selectors/checkbox)
(def skeleton quo2.components.loaders.skeleton/skeleton) (def skeleton quo2.components.loaders.skeleton/skeleton)
(def author quo2.components.messages.author.view/author)
;;;; AVATAR ;;;; AVATAR
(def account-avatar quo2.components.avatars.account-avatar/account-avatar) (def account-avatar quo2.components.avatars.account-avatar/account-avatar)

View File

@ -23,7 +23,8 @@
[status-im.utils.universal-links.utils :as links] [status-im.utils.universal-links.utils :as links]
[status-im2.navigation.events :as navigation] [status-im2.navigation.events :as navigation]
[taoensso.timbre :as log] [taoensso.timbre :as log]
[utils.debounce :as debounce])) [utils.debounce :as debounce]
[utils.security.core :as security]))
(fx/defn update-browser-option (fx/defn update-browser-option
[{:keys [db]} option-key option-value] [{:keys [db]} option-key option-value]
@ -226,9 +227,10 @@
(fx/defn handle-message-link (fx/defn handle-message-link
{:events [:browser.ui/message-link-pressed]} {:events [:browser.ui/message-link-pressed]}
[_ link] [_ link]
(if (links/universal-link? link) (when (security/safe-link? link)
{:dispatch [:universal-links/handle-url link]} (if (links/universal-link? link)
{:browser/show-browser-selection link})) {:dispatch [:universal-links/handle-url link]}
{:browser/show-browser-selection link})))
(fx/defn update-browser-on-nav-change (fx/defn update-browser-on-nav-change
[cofx url error?] [cofx url error?]

View File

@ -16,8 +16,8 @@
[status-im.utils.fx :as fx] [status-im.utils.fx :as fx]
[status-im.utils.types :as types] [status-im.utils.types :as types]
[status-im.utils.utils :as utils] [status-im.utils.utils :as utils]
[status-im2.contexts.chat.messages.message.delete-message-for-me.events :as delete-for-me] [status-im2.contexts.chat.messages.delete-message-for-me.events :as delete-for-me]
[status-im2.contexts.chat.messages.message.delete-message.events :as delete-message] [status-im2.contexts.chat.messages.delete-message.events :as delete-message]
[status-im2.navigation.events :as navigation] [status-im2.navigation.events :as navigation]
[taoensso.timbre :as log])) [taoensso.timbre :as log]))

View File

@ -600,7 +600,7 @@
{:keys [new-text at-idxs start end] :as state} {:keys [new-text at-idxs start end] :as state}
(get-in db [:chats/mentions chat-id :mentions]) (get-in db [:chats/mentions chat-id :mentions])
new-text (or new-text text)] new-text (or new-text text)]
(log/info "[mentions] calculate suggestions" (log/debug "[mentions] calculate suggestions"
"state" "state"
state) state)
(if-not (seq at-idxs) (if-not (seq at-idxs)

View File

@ -13,7 +13,7 @@
[status-im.utils.gfycat.core :as gfycat] [status-im.utils.gfycat.core :as gfycat]
[status-im.utils.platform :as platform] [status-im.utils.platform :as platform]
[status-im.utils.types :as types] [status-im.utils.types :as types]
[status-im2.contexts.chat.messages.message.delete-message.events :as delete-message] [status-im2.contexts.chat.messages.delete-message.events :as delete-message]
[taoensso.timbre :as log])) [taoensso.timbre :as log]))
(defn- message-loaded? (defn- message-loaded?

View File

@ -73,13 +73,13 @@
(fx/defn send-emoji-reaction (fx/defn send-emoji-reaction
{:events [::send-emoji-reaction]} {:events [:models.reactions/send-emoji-reaction]}
[{{:keys [current-chat-id]} :db :as cofx} reaction] [{{:keys [current-chat-id]} :db :as cofx} reaction]
(message.protocol/send-reaction cofx (message.protocol/send-reaction cofx
(update reaction :chat-id #(or % current-chat-id)))) (update reaction :chat-id #(or % current-chat-id))))
(fx/defn send-retract-emoji-reaction (fx/defn send-retract-emoji-reaction
{:events [::send-emoji-reaction-retraction]} {:events [:models.reactions/send-emoji-reaction-retraction]}
[{{:keys [current-chat-id]} :db :as cofx} reaction] [{{:keys [current-chat-id]} :db :as cofx} reaction]
(message.protocol/send-retract-reaction cofx (message.protocol/send-retract-reaction cofx
(update reaction :chat-id #(or % current-chat-id)))) (update reaction :chat-id #(or % current-chat-id))))

View File

@ -4,7 +4,6 @@
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[reagent.core :as reagent] [reagent.core :as reagent]
[status-im.chat.models.images :as images] [status-im.chat.models.images :as images]
[status-im.chat.models.reactions :as models.reactions]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.i18n.i18n :as i18n] [status-im.i18n.i18n :as i18n]
[status-im.react-native.resources :as resources] [status-im.react-native.resources :as resources]
@ -827,11 +826,11 @@
:picker-on-close (fn [] :picker-on-close (fn []
(space-keeper false)) (space-keeper false))
:send-emoji (fn [{:keys [emoji-id]}] :send-emoji (fn [{:keys [emoji-id]}]
(re-frame/dispatch [::models.reactions/send-emoji-reaction (re-frame/dispatch [:models.reactions/send-emoji-reaction
{:message-id (:message-id message) {:message-id (:message-id message)
:emoji-id emoji-id}])) :emoji-id emoji-id}]))
:retract-emoji (fn [{:keys [emoji-id emoji-reaction-id]}] :retract-emoji (fn [{:keys [emoji-id emoji-reaction-id]}]
(re-frame/dispatch [::models.reactions/send-emoji-reaction-retraction (re-frame/dispatch [:models.reactions/send-emoji-reaction-retraction
{:message-id (:message-id message) {:message-id (:message-id message)
:emoji-id emoji-id :emoji-id emoji-id
:emoji-reaction-id emoji-reaction-id}])) :emoji-reaction-id emoji-reaction-id}]))

View File

@ -1,30 +0,0 @@
(ns status-im.ui.screens.chat.message.reactions-row
(:require [quo.react-native :as rn]
[quo2.components.reactions.reaction :as quo2.reaction]
[status-im.constants :as constants]
[status-im.ui.screens.chat.message.styles :as styles]))
(def default-reaction-margin-top 5)
(def text-reaction-margin-top -3)
(defn message-reactions
[{:keys [content-type]} reactions timeline on-emoji-press on-open]
(when (seq reactions)
[rn/view
{:style (styles/reactions-row
timeline
(if (= content-type constants/content-type-text)
text-reaction-margin-top
default-reaction-margin-top))}
(for [{:keys [own emoji-id quantity] :as emoji-reaction} reactions]
^{:key (str emoji-reaction)}
[rn/view {:style {:margin-right 6 :margin-top 5}}
[quo2.reaction/reaction
{:emoji (get constants/reactions emoji-id)
:neutral? own
:clicks quantity
:on-press #(on-emoji-press emoji-id)
:accessibility-label (str "emoji-reaction-" emoji-id)}]])
;; on-press won't work until we integrate Message Context Drawer
[quo2.reaction/open-reactions-menu (when @on-open {:on-press @on-open})]]))

View File

@ -1,7 +1,6 @@
(ns status-im.ui.screens.chat.styles.message.message (ns status-im.ui.screens.chat.styles.message.message
(:require [quo.design-system.colors :as colors] (:require [quo.design-system.colors :as colors]
[quo2.foundations.colors :as quo2.colors] [quo2.foundations.colors :as quo2.colors]
[quo2.foundations.typography :as typography]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.screens.chat.styles.photos :as photos])) [status-im.ui.screens.chat.styles.photos :as photos]))
@ -51,7 +50,9 @@
[] []
{:font-size 10 {:font-size 10
:line-height 10 :line-height 10
:color colors/gray}) :border-color :red
:border-width 1
:color :red})
(defn audio-message-timestamp-text (defn audio-message-timestamp-text
[] []
@ -104,14 +105,7 @@
{:flex-direction :row {:flex-direction :row
:margin-top 1}) :margin-top 1})
(defn pin-author-text (defn pinned-by-text []
[]
(merge typography/font-medium
{:color quo2.colors/primary-50
:bottom 2}))
(defn pinned-by-text
[]
{:margin-left 5}) {:margin-left 5})
(def message-author-touchable (def message-author-touchable

View File

@ -3,7 +3,6 @@
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[reagent.core :as reagent] [reagent.core :as reagent]
[status-im.chat.models :as chat] [status-im.chat.models :as chat]
[status-im.chat.models.reactions :as models.reactions]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.i18n.i18n :as i18n] [status-im.i18n.i18n :as i18n]
[status-im.ui.components.fast-image :as fast-image] [status-im.ui.components.fast-image :as fast-image]
@ -174,12 +173,12 @@
:picker-on-open (fn []) :picker-on-open (fn [])
:picker-on-close (fn []) :picker-on-close (fn [])
:send-emoji (fn [{:keys [emoji-id]}] :send-emoji (fn [{:keys [emoji-id]}]
(re-frame/dispatch [::models.reactions/send-emoji-reaction (re-frame/dispatch [:models.reactions/send-emoji-reaction
{:message-id (:message-id message) {:message-id (:message-id message)
:chat-id chat-id :chat-id chat-id
:emoji-id emoji-id}])) :emoji-id emoji-id}]))
:retract-emoji (fn [{:keys [emoji-id emoji-reaction-id]}] :retract-emoji (fn [{:keys [emoji-id emoji-reaction-id]}]
(re-frame/dispatch [::models.reactions/send-emoji-reaction-retraction (re-frame/dispatch [:models.reactions/send-emoji-reaction-retraction
{:message-id (:message-id message) {:message-id (:message-id message)
:chat-id chat-id :chat-id chat-id
:emoji-id emoji-id :emoji-id emoji-id

View File

@ -1,80 +0,0 @@
(ns status-im.ui2.screens.chat.components.reaction-drawer
(:require [quo.react-native :as rn]
[quo2.components.buttons.button :as quo2.button]
[quo2.components.list-items.menu-item :as quo2.menu-item]
[quo2.components.separator :as quo2.separator]
[quo2.foundations.colors :as colors]
[re-frame.core :as re-frame]
[status-im.constants :as constants]))
(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)]
[rn/view
[rn/view
{:style {:width "100%"
:flex-direction :row
:justify-content :space-between
:padding-horizontal 30
:padding-top 5
:padding-bottom 15}}
(doall
(for [[id icon] constants/reactions
:let [active (own-reactions id)]]
;;TODO reactions selector should be used
;;https://www.figma.com/file/WQZcp6S0EnzxdTL4taoKDv/Design-System?node-id=9961%3A166549
;; not implemented yet
^{:key id}
[quo2.button/button
(merge
{:size 40
:type :grey
:icon true
:icon-no-color true
:accessibility-label (str "emoji-picker-" id)
:on-press #(do
(send-emoji id)
(re-frame/dispatch [:bottom-sheet/hide]))}
(when active {:style {:background-color colors/neutral-10}}))
icon]))]
[rn/view {:style {:padding-horizontal 8}}
(for [action main-actions]
(let [on-press (:on-press action)]
^{:key (:id 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)
[quo2.separator/separator])
(for [action danger-actions]
(let [on-press (:on-press action)]
^{:key (:id 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)
[quo2.separator/separator])
(for [action admin-actions]
(let [on-press (:on-press action)]
^{:key (:id 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

@ -20,7 +20,8 @@
[status-im.utils.utils :as utils] [status-im.utils.utils :as utils]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[status-im2.contexts.chat.messages.list.view :refer [scroll-to-bottom]] [status-im2.contexts.chat.messages.list.view :refer [scroll-to-bottom]]
[status-im.utils.platform :as platform])) [status-im.utils.platform :as platform]
[status-im2.common.not-implemented :as not-implemented]))
(defn calculate-y (defn calculate-y
[context min-y max-y added-value chat-id set-bg-opacity] [context min-y max-y added-value chat-id set-bg-opacity]
@ -301,10 +302,11 @@
:type :outline :type :outline
:size 32} :i/image] :size 32} :i/image]
[rn/view {:width 12}] [rn/view {:width 12}]
[quo2.button/button [not-implemented/not-implemented
{:icon true [quo2.button/button
:type :outline {:icon true
:size 32} :i/reaction] :type :outline
:size 32} :i/reaction]]
[rn/view {:flex 1}] [rn/view {:flex 1}]
;;SEND button ;;SEND button
[rn/view {:ref send-ref [rn/view {:ref send-ref

View File

@ -1,21 +1,16 @@
(ns status-im.ui2.screens.chat.messages.message (ns status-im.ui2.screens.chat.messages.message
(:require [quo.core :as quo] (:require [quo.design-system.colors :as quo.colors]
[quo.design-system.colors :as quo.colors]
[quo.react-native :as rn] [quo.react-native :as rn]
[quo2.components.avatars.user-avatar :as user-avatar] [quo2.components.avatars.user-avatar :as user-avatar]
[quo2.components.icon :as icons] [quo2.components.icon :as icons]
[quo2.components.markdown.text :as text] [quo2.components.markdown.text :as text]
[quo2.components.messages.system-message :as system-message]
[quo2.foundations.colors :as colors] [quo2.foundations.colors :as colors]
[quo2.foundations.typography :as typography] [quo2.foundations.typography :as typography]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[reagent.core :as reagent] [reagent.core :as reagent]
[status-im.chat.models.images :as images]
[status-im.chat.models.reactions :as models.reactions]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.i18n.i18n :as i18n] [status-im.i18n.i18n :as i18n]
[status-im.react-native.resources :as resources] [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.fast-image :as fast-image]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.screens.chat.image.preview.views :as preview] [status-im.ui.screens.chat.image.preview.views :as preview]
@ -23,93 +18,39 @@
[status-im.ui.screens.chat.message.command :as message.command] [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.gap :as message.gap]
[status-im.ui.screens.chat.message.link-preview :as link-preview] [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.sheets :as sheets] [status-im.ui.screens.chat.sheets :as sheets]
[status-im.ui.screens.chat.styles.message.message :as style] [status-im.ui.screens.chat.styles.message.message :as style]
[status-im.ui.screens.chat.styles.photos :as photos.style]
[status-im.ui.screens.chat.utils :as chat.utils] [status-im.ui.screens.chat.utils :as chat.utils]
[status-im.ui.screens.communities.icon :as communities.icon] [status-im.ui.screens.communities.icon :as communities.icon]
[status-im.ui2.screens.chat.components.reaction-drawer :as reaction-drawer]
[status-im.ui2.screens.chat.components.reply :as components.reply] [status-im.ui2.screens.chat.components.reply :as components.reply]
[status-im.utils.config :as config] [status-im.utils.config :as config]
[status-im.utils.datetime :as time] [status-im.utils.datetime :as time]
[status-im.utils.utils :as utils] [status-im.utils.utils :as utils]
[status-im2.contexts.chat.home.chat-list-item.view :as home.chat-list-item] [status-im2.contexts.chat.home.chat-list-item.view :as home.chat-list-item]
[status-im2.contexts.chat.messages.message.delete-message-for-me.events] [status-im2.contexts.chat.messages.delete-message-for-me.events]
[status-im2.contexts.chat.messages.message.delete-message.events] [status-im2.contexts.chat.messages.delete-message.events]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[utils.security.core :as security]) [quo2.core :as quo])
(:require-macros [status-im.utils.views :refer [defview letsubs]])) (:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defview mention-element
[from]
(letsubs [contact-name [:contacts/contact-name-by-identity from]]
contact-name))
(def edited-at-text (str " ⌫ " (i18n/label :t/edited))) (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
[rn/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 quo.colors/gray quo.colors/white)
:accessibility-label (name outgoing-status)}])
(when edited-at [rn/text {:style (style/message-status-text)} edited-at-text])]))
(defn message-timestamp
[{:keys [timestamp-str in-popover?]}]
(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)]
[rn/animated-view {:style (style/message-timestamp-wrapper) :opacity anim-opacity}
[rn/text
{:style (style/message-timestamp-text)
:accessibility-label :message-timestamp}
timestamp-str]])))
(defn quoted-message
[{:keys [message-id chat-id]} reply pin?]
(let [{:keys [deleted? deleted-for-me?]} (get @(re-frame/subscribe [:chats/chat-messages chat-id])
message-id)
reply (assoc reply
:deleted? deleted?
:deleted-for-me? deleted-for-me?)]
[rn/view {:style (when-not pin? (style/quoted-message-container))}
[components.reply/reply-message reply false pin?]]))
(defn system-text? (defn system-text?
[content-type] [content-type]
(= content-type constants/content-type-system-text)) (= content-type constants/content-type-system-text))
(defn mention-element
[from]
(rf/sub [:contacts/contact-name-by-identity from]))
(defn render-inline (defn render-inline
[message-text content-type acc {:keys [type literal destination]}] [_message-text content-type acc {:keys [type literal destination]}]
(case type (case type
"" ""
(conj acc literal) (conj acc literal)
"code" "code"
(conj acc (conj acc [rn/text literal])
[quo/text
{:max-font-size-multiplier react/max-font-size-multiplier
:style (style/inline-code-style)
:monospace true}
literal])
"emph" "emph"
(conj acc [rn/text (style/emph-style) literal]) (conj acc [rn/text (style/emph-style) literal])
@ -126,14 +67,9 @@
"link" "link"
(conj acc (conj acc
[rn/text [rn/text
{:style {:style {:color :blue
{:color quo.colors/blue :text-decoration-line :underline}
:text-decoration-line :underline} :on-press #(rf/dispatch [:browser.ui/message-link-pressed destination])}
:on-press
#(when (and (security/safe-link? destination)
(security/safe-link-text? message-text))
(re-frame/dispatch
[:browser.ui/message-link-pressed destination]))}
destination]) destination])
"mention" "mention"
@ -150,16 +86,15 @@
"status-tag" "status-tag"
(conj acc (conj acc
[rn/text [rn/text
{:style {:color quo.colors/blue {:style {:color :blue
:text-decoration-line :underline} :text-decoration-line :underline}
:on-press :on-press #(rf/dispatch [:chat.ui/start-public-chat literal])}
#(re-frame/dispatch
[:chat.ui/start-public-chat literal])}
"#" "#"
literal]) literal])
(conj acc literal))) (conj acc literal)))
;; TEXT
(defn render-block (defn render-block
[{:keys [content content-type in-popover?]} acc [{:keys [content content-type in-popover?]} acc
{:keys [type ^js literal children]}] {:keys [type ^js literal children]}]
@ -181,42 +116,37 @@
"codeblock" "codeblock"
(conj acc (conj acc
[rn/view {:style style/codeblock-style} [rn/view {:style style/codeblock-style}
[quo/text [rn/text (.substring literal 0 (dec (.-length literal)))]])
{:max-font-size-multiplier react/max-font-size-multiplier
:style style/codeblock-text-style
:monospace true}
(.substring literal 0 (dec (.-length literal)))]])
acc)) acc))
(defn render-parsed-text (defn render-parsed-text
[message tree] [{:keys [content] :as message-data}]
(reduce (fn [acc e] (render-block message acc e)) [:<>] tree)) (reduce (fn [acc e]
(render-block message-data acc e))
[:<>]
(:parsed-text content)))
(defn render-parsed-text-with-message-status (defn message-status
[{:keys [edited-at in-popover?] :as message} tree] [{:keys [outgoing-status edited-at]}]
(let [elements (render-parsed-text message tree) (when (or edited-at outgoing-status)
message-status [rn/text {:style (style/message-status-placeholder)} [rn/view {:flex-direction :row}
(str (if (not in-popover?) " " " ") [rn/text {:style (style/message-status-text)}
(when (and (not in-popover?) edited-at) edited-at-text))] (str "["
last-element (peek elements)] (if edited-at
;; Using `nth` here as slightly faster than `first`, roughly 30% "edited"
;; It's worth considering pure js structures for this code path as (or outgoing-status ""))
;; it's perfomance critical " DEBUG]")]]))
(if (= rn/text (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 (defn quoted-message
[{:keys [content-type content] :as message}] [{:keys [message-id chat-id]} reply pin?]
[rn/view (style/message-view message) (let [{:keys [deleted? deleted-for-me?]} (get @(re-frame/subscribe [:chats/chat-messages chat-id])
[rn/text message-id)
{:style {:color quo.colors/white-persist}} reply (assoc reply
(if (seq (:text content)) :deleted? deleted?
(:text content) :deleted-for-me? deleted-for-me?)]
(str "Unhandled content-type " content-type))]]) [rn/view {:style (when-not pin? (style/quoted-message-container))}
[components.reply/reply-message reply false pin?]]))
(defn message-not-sent-text (defn message-not-sent-text
[chat-id message-id] [chat-id message-id]
@ -234,33 +164,6 @@
[rn/view style/not-sent-icon [rn/view style/not-sent-icon
[icons/icon :i/warning {:color quo.colors/red}]]]]) [icons/icon :i/warning {:color quo.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)))))
(defn pin-icon
[color size]
[icons/icon :i/pin
{:color color
:height size
:width size}])
(defn pinned-by-indicator
[pinned-by]
[rn/view
{:style (style/pin-indicator)
:accessibility-label :pinned-by}
[pin-icon colors/primary-50 16]
[quo/text
{:size :small
:color :main
:style (style/pin-author-text)}
(pin-author-name pinned-by)]])
(defn message-delivery-status (defn message-delivery-status
[{:keys [chat-id message-id outgoing-status message-type]}] [{:keys [chat-id message-id outgoing-status message-type]}]
(when (and (not= constants/message-type-private-group-system-message message-type) (when (and (not= constants/message-type-private-group-system-message message-type)
@ -278,44 +181,6 @@
(letsubs [contact-with-names [:multiaccount/contact]] (letsubs [contact-with-names [:multiaccount/contact]]
(chat.utils/format-author contact-with-names opts nil))) (chat.utils/format-author contact-with-names opts nil)))
(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)
[rn/view
{:style (assoc (style/message-wrapper message)
:margin-vertical 10
:margin-left 8
:width 271)}
(when verified
[rn/view (style/community-verified)
[rn/text
{:style {:font-size 13
:color quo.colors/blue}} (i18n/label :t/communities-verified)]])
[rn/view (style/community-message verified)
[rn/view
{:width 62
:padding-left 14}
(if (= community-id constants/status-community-id)
[rn/image
{:source (resources/get-image :status-logo)
:style {:width 40
:height 40}}]
[communities.icon/community-icon community])]
[rn/view {:padding-right 14 :flex 1}
[rn/text {:style {:font-weight "700" :font-size 17}}
name]
[rn/text description]]]
[rn/view (style/community-view-button)
[rn/touchable-opacity
{:on-press #(re-frame/dispatch
[:communities/navigate-to-community
{:community-id (:id community)}])}
[rn/text
{:style {:text-align :center
:color quo.colors/blue}} (i18n/label :t/view)]]]])))
(defn display-name-view (defn display-name-view
[display-name contact timestamp show-key?] [display-name contact timestamp show-key?]
[rn/view {:style {:flex-direction :row}} [rn/view {:style {:flex-direction :row}}
@ -343,55 +208,43 @@
(defn message-content-wrapper (defn message-content-wrapper
"Author, userpic and delivery wrapper" "Author, userpic and delivery wrapper"
[{:keys [last-in-group? timestamp-str timestamp deleted? deleted-undoable-till [{:keys [last-in-group? timestamp pinned from chat-id]
deleted-for-me? deleted-for-me-undoable-till pinned from chat-id]
:as message} content] :as message} content]
(let [response-to (:response-to (:content message)) (let [response-to (:response-to (:content message))
display-name (first (rf/sub [:contacts/contact-two-names-by-identity from])) display-name (first (rf/sub [:contacts/contact-two-names-by-identity from]))
contact (rf/sub [:contacts/contact-by-address from]) contact (rf/sub [:contacts/contact-by-address from])
photo-path (when-not (empty? (:images contact)) (rf/sub [:chats/photo-path from])) photo-path (when-not (empty? (:images contact)) (rf/sub [:chats/photo-path from]))
online? (rf/sub [:visibility-status-updates/online? from])] online? (rf/sub [:visibility-status-updates/online? from])]
(if (or deleted? deleted-for-me?) [rn/view
[system-message/system-message {:style (style/message-wrapper message)
{:type :deleted :pointer-events :box-none
:label (if deleted? :message-deleted :message-deleted-for-you) :accessibility-label :chat-item}
:labels {:pinned-a-message (i18n/label :pinned-a-message) (when (and (seq response-to) (:quoted-message message))
:message-deleted (i18n/label :message-deleted-for-everyone) [quoted-message {:message-id response-to :chat-id chat-id} (:quoted-message message)])
:message-deleted-for-you (i18n/label :message-deleted-for-you) [rn/view
:added (i18n/label :added)} {:style (style/message-body)
:timestamp-str timestamp-str :pointer-events :box-none}
:non-pressable? true ;; AVATAR
:animate-landing? (if (or deleted-undoable-till deleted-for-me-undoable-till) [rn/view {:style {:width 40}}
true (when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned)
false)}] [react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/show-profile from])}
[rn/view [user-avatar/user-avatar
{:style (style/message-wrapper message) {:full-name display-name
:pointer-events :box-none :profile-picture photo-path
:accessibility-label :chat-item} :status-indicator? true
(when (and (seq response-to) (:quoted-message message)) :online? online?
[quoted-message {:message-id response-to :chat-id chat-id} (:quoted-message message)]) :size :small
[rn/view :ring? false}]])]
{:style (style/message-body) [rn/view {:style (style/message-author-wrapper)}
:pointer-events :box-none} ;; AUTHOR NAME
[rn/view {:style {:width 40}} (when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned)
(when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned) [display-name-view display-name contact timestamp true])
[react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/show-profile from])} ;; MESSAGE CONTENT
[user-avatar/user-avatar content
{:full-name display-name [link-preview/link-preview-wrapper (:links (:content message)) false false]]]
:profile-picture photo-path ;; delivery status
:status-indicator? true [rn/view (style/delivery-status)
:online? online? [message-delivery-status message]]]))
:size :small
:ring? false}]])]
[rn/view {:style (style/message-author-wrapper)}
(when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned)
[display-name-view display-name contact timestamp true])
;; MESSAGE CONTENT
content
[link-preview/link-preview-wrapper (:links (:content message)) false false]]]
;; delivery status
[rn/view (style/delivery-status)
[message-delivery-status message]]])))
(def image-max-width 260) (def image-max-width 260)
(def image-max-height 192) (def image-max-height 192)
@ -408,46 +261,6 @@
(reset! dimensions {:width (/ width k) :height image-max-height :loaded true}))) (reset! dimensions {:width (/ width k) :height image-max-height :loaded true})))
(swap! dimensions assoc :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))}]
[rn/touchable-opacity
{:on-press (fn []
(reset! visible true)
(rn/dismiss-keyboard!))
:on-long-press @on-long-press
:disabled in-popover?}
[rn/view
{:style (style/image-message style-opts)
:accessibility-label :image-message}
(when (or (:error @dimensions) (not (:loaded @dimensions)))
[rn/view
(merge (dissoc style-opts :opacity)
{:flex 1 :align-items :center :justify-content :center :position :absolute})
(if (:error @dimensions)
[icons/icon :i/cancel]
[rn/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}}]
[rn/view {:style (style/image-message-border style-opts)}]]]]))))
(defmulti ->message :content-type) (defmulti ->message :content-type)
(defmethod ->message constants/content-type-command (defmethod ->message constants/content-type-command
@ -519,220 +332,73 @@
:icon :i/delete :icon :i/delete
:id :delete-for-all}])))) :id :delete-for-all}]))))
(defn collapsible-text-message ;; STATUS ? whats that ?
[_ _]
(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)
[rn/touchable-opacity
(when-not modal
{:delay-long-press 100
:on-long-press on-long-press
:disabled in-popover?})
[rn/view style/message-view-wrapper
[message-timestamp message show-timestamp?]
[rn/view {:style (style/message-view message)}
[rn/view {:style (style/message-view-content)}
[rn/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-community
[message]
[community-content message])
(defmethod ->message constants/content-type-status (defmethod ->message constants/content-type-status
[{:keys [content content-type] :as message}] [{:keys [content content-type]}]
[message-content-wrapper message [rn/view style/status-container
[rn/view style/status-container [rn/text {:style (style/status-text)}
[rn/text {:style (style/status-text)} (reduce
(reduce (fn [acc e] (render-inline (:text content) content-type acc e))
(fn [acc e] (render-inline (:text content) content-type acc e)) [rn/text {:style (style/status-text)}]
[rn/text {:style (style/status-text)}] (-> content :parsed-text peek :children))]])
(-> content :parsed-text peek :children))]]])
(defmethod ->message constants/content-type-emoji ;; EMOJI
(defn emoji
[] []
(let [show-timestamp? (reagent/atom false)] (fn [{:keys [content] :as message}]
(fn [{:keys [content pinned in-popover? message-pin-enabled] :as message} [rn/view (style/message-view message)
{:keys [on-long-press modal ref] [rn/view {:style (style/message-view-content)}
:as reaction-picker}] [rn/view {:style (style/style-message-text)}
(let [on-long-press (fn [] [rn/text {:style (style/emoji-message message)}
(on-long-press (:text content)]]]]))
(concat
[{:type :main
:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
:id :reply
:icon :i/reply
:label (i18n/label :t/message-reply)}
{:type :main
:on-press #(react/copy-to-clipboard (get content :text))
:id :copy
:icon :i/copy
:label (i18n/label :t/copy-text)}]
(when message-pin-enabled
[{:type :main
:on-press #(pin-message message)
:id :pin
:icon :i/pin
:label (if pinned (i18n/label :t/unpin) (i18n/label :t/pin))}]))))]
(reset! ref on-long-press)
[message-content-wrapper message
[rn/touchable-opacity
(when-not modal
{:disabled in-popover?
:on-press (fn []
(rn/dismiss-keyboard!)
(reset! show-timestamp? true))
:delay-long-press 100
:on-long-press on-long-press})
[rn/view style/message-view-wrapper
[message-timestamp message show-timestamp?]
[rn/view (style/message-view message)
[rn/view {:style (style/message-view-content)}
[rn/view {:style (style/style-message-text)}
[rn/text {:style (style/emoji-message message)}
(:text content)]]
[message-status message]]]]]
reaction-picker]))))
(defmethod ->message constants/content-type-sticker ;; STICKER
[{:keys [content from outgoing in-popover?] (defn sticker
:as message} [{:keys [content]}]
{:keys [on-long-press modal ref] [fast-image/fast-image
:as reaction-picker}] {:style {:margin 10 :width 140 :height 140}
(let [pack (get-in content [:sticker :pack]) :source {:uri (str (-> content :sticker :url) "&download=true")}}])
on-long-press (fn []
(on-long-press
(when-not outgoing
[{:type :main
:icon :i/stickers
: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
[rn/touchable-opacity
(when-not modal
{:disabled in-popover?
:accessibility-label :sticker-message
:on-press (fn [_]
(when pack
(re-frame/dispatch [:stickers/open-sticker-pack (str pack)]))
(rn/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 ;;IMAGE
[{:keys [content in-popover? outgoing] :as message} (defn message-content-image
{:keys [on-long-press modal ref] [{:keys [content]}]
:as reaction-picker}] (let [dimensions (reagent/atom {:width image-max-width :height image-max-height :loaded false})
(let [on-long-press visible (reagent/atom false)
(fn [] uri (:image content)]
(on-long-press (fn [message]
(concat [{:type :main (let [style-opts {:outgoing false
:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message]) :opacity (if (:loaded @dimensions) 1 0)
:id :reply :width (:width @dimensions)
:icon :i/reply :height (:height @dimensions)}]
:label (i18n/label :t/message-reply)} [:<>
{:type :main [preview/preview-image
:on-press #(re-frame/dispatch [:chat.ui/save-image-to-gallery (:image content)]) {:message message
:id :save :visible @visible
:icon :i/save :on-close #(do (reset! visible false)
:label (i18n/label :t/save-image-library)} (reagent/flush))}]
{:type :main [rn/view
:on-press #(images/download-image-http {:style (style/image-message style-opts)
(get-in message [:content :image]) :accessibility-label :image-message}
preview/share) (when (or (:error @dimensions) (not (:loaded @dimensions)))
:id :share [rn/view
:icon :i/share (merge (dissoc style-opts :opacity)
:label (i18n/label :t/share-image)}] {:flex 1 :align-items :center :justify-content :center :position :absolute})
[{:type :danger (if (:error @dimensions)
:on-press #(re-frame/dispatch [icons/icon :i/cancel]
[:chat.ui/delete-message-for-me message [rn/activity-indicator {:animating true}])])
config/delete-message-undo-time-limit-ms]) [fast-image/fast-image
:label (i18n/label :t/delete-for-me) {:style (dissoc style-opts :outgoing)
:icon :i/delete :on-load (image-set-size dimensions)
:id :delete-for-me}] :on-error #(swap! dimensions assoc :error true)
(when (and outgoing config/delete-message-enabled?) :source {:uri uri}}]
[{:type :danger [rn/view {:style (style/image-message-border style-opts)}]]]))))
:on-press #(re-frame/dispatch [:chat.ui/delete-message
message
config/delete-message-undo-time-limit-ms])
:label (i18n/label :t/delete-for-everyone)
:icon :i/delete
: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 ;; AUDIO
[] (defn audio
(let [show-timestamp? (reagent/atom false)] [message]
(fn [{:keys [outgoing pinned] :as message} [rn/view {:style (style/message-view message) :accessibility-label :audio-message}
{:keys [on-long-press modal ref] [rn/view {:style (style/message-view-content)}
:as reaction-picker}] [message.audio/message-content message]]])
(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 :i/reply
:id :reply}
{:type :main
:on-press #(pin-message message)
:label (i18n/label (if pinned :t/unpin-from-chat :t/pin-to-chat))
:icon :i/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 :i/delete
:id :delete-for-me}
(when (and outgoing config/delete-message-enabled?)
{:type :danger
:on-press #(re-frame/dispatch [:chat.ui/delete-message
message
config/delete-message-undo-time-limit-ms])
:label (i18n/label :t/delete-for-everyone)
:icon :i/delete
:id :delete})]))]
(reset! ref on-long-press)
[message-content-wrapper message
[rn/touchable-opacity
(when-not modal
{:on-long-press on-long-press
:on-press (fn []
(reset! show-timestamp? true))})
[rn/view style/message-view-wrapper
[message-timestamp message show-timestamp?]
[rn/view {:style (style/message-view message) :accessibility-label :audio-message}
[rn/view {:style (style/message-view-content)}
[message.audio/message-content message] [message-status message]]]]]
reaction-picker]))))
(defn contact-request-status-pending (defn contact-request-status-pending
[] []
@ -769,7 +435,10 @@
constants/contact-request-message-state-accepted [contact-request-status-accepted] constants/contact-request-message-state-accepted [contact-request-status-accepted]
constants/contact-request-message-state-declined [contact-request-status-declined])]) constants/contact-request-message-state-declined [contact-request-status-declined])])
(defmethod ->message constants/content-type-contact-request ;;;; SYSTEM
;; CONTACT REQUEST (like system message ? ) no wrapper
(defn system-contact-request
[message _] [message _]
[rn/view {:style (style/content-type-contact-request)} [rn/view {:style (style/content-type-contact-request)}
[rn/image [rn/image
@ -788,120 +457,45 @@
(get-in message [:content :text])]] (get-in message [:content :text])]]
[contact-request-status-label (:contact-request-state message)]]) [contact-request-status-label (:contact-request-state message)]])
(defmethod ->message :default (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)
[rn/view
{:style (assoc (style/message-wrapper message)
:margin-vertical 10
:margin-left 8
:width 271)}
(when verified
[rn/view (style/community-verified)
[rn/text
{:style {:font-size 13
:color quo.colors/blue}} (i18n/label :t/communities-verified)]])
[rn/view (style/community-message verified)
[rn/view
{:width 62
:padding-left 14}
(if (= community-id constants/status-community-id)
[rn/image
{:source (resources/get-image :status-logo)
:style {:width 40
:height 40}}]
[communities.icon/community-icon community])]
[rn/view {:padding-right 14 :flex 1}
[rn/text {:style {:font-weight "700" :font-size 17}}
name]
[rn/text description]]]
[rn/view (style/community-view-button)
[rn/touchable-opacity
{:on-press #(re-frame/dispatch
[:communities/navigate-to-community
{:community-id (:id community)}])}
[rn/text
{:style {:text-align :center
:color quo.colors/blue}} (i18n/label :t/view)]]]])))
;; COMMUNITY (like system ? ) no wrapper
(defn community
[message] [message]
[message-content-wrapper message [community-content message])
[unknown-content-type message]])
(defn chat-message
[{:keys [pinned pinned-by mentioned in-pinned-view? last-in-group? deleted? deleted-for-me?]
: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 (reaction-drawer/message-options
actions
(into #{} (js->clj own-reactions))
#(on-emoji-press %))}]))
on-long-press (atom nil)]
[rn/view
{:style (merge (when (and (not in-pinned-view?) (or mentioned pinned))
{:background-color colors/primary-50-opa-5
:border-radius 16
:margin-bottom 4})
(when (or mentioned pinned last-in-group?) {:margin-top 8})
{:margin-horizontal 8})}
(when (and pinned (not (or deleted? deleted-for-me?)))
[rn/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}]
(when-not (or deleted? deleted-for-me?)
[reaction-row/message-reactions message reactions nil on-emoji-press on-long-press])])) ;; TODO: pass on-open-drawer function
(defn message-render-fn
[{:keys [outgoing whisper-timestamp] :as message}
_
{:keys [group-chat public? community? current-public-key show-input? edit-enabled]}]
[chat-message
(assoc message
:incoming-group (and group-chat (not outgoing))
:group-chat group-chat
:public? public?
:community? community?
:current-public-key current-public-key
:show-input? show-input?
:message-pin-enabled true
:in-pinned-view? true
:pinned true
:timestamp-str (time/timestamp->time whisper-timestamp)
:edit-enabled edit-enabled)])
(defn pin-system-message
[{:keys [from in-popover? timestamp-str chat-id] :as message} {:keys [modal close-modal]}]
(let [response-to (:response-to (:content message))]
[rn/touchable-opacity
{:on-press #(rf/dispatch [:bottom-sheet/show-sheet :pinned-messages-list chat-id])
:active-opacity 1
:style (merge {:flex-direction :row :margin-vertical 8} (style/message-wrapper message))}
[rn/view
{:style {:width photos.style/default-size
:height photos.style/default-size
:margin-right 16
:border-radius photos.style/default-size
:justify-content :center
:align-items :center
:background-color colors/primary-50-opa-10}
:accessibility-label :content-type-pin-icon}
[pin-icon colors/primary-50 16]]
[rn/view
[rn/view {:style {:flex-direction :row :align-items :center}}
[rn/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} 20]]
[rn/text {:style {:font-size 13}} (str " " (i18n/label :t/pinned-a-message))]
[rn/text
{:style (merge
{:padding-left 5
:margin-top 2}
(style/message-timestamp-text))
:accessibility-label :message-timestamp}
timestamp-str]]
[quoted-message {:message-id response-to :chat-id chat-id} (:quoted-message message) true]]]))
(defmethod ->message constants/content-type-system-text
[{:keys [content quoted-message] :as message}]
(if quoted-message
[pin-system-message message]
[rn/view {:accessibility-label :chat-item}
[rn/view (style/system-message-body message)
[rn/view (style/message-view message)
[rn/view (style/message-view-content)
[render-parsed-text message (:parsed-text content)]]]]]))

View File

@ -152,8 +152,9 @@
(def wallet-connect-project-id "87815d72a81d739d2a7ce15c2cfdefb3") (def wallet-connect-project-id "87815d72a81d739d2a7ce15c2cfdefb3")
;; NOTE moved to status-im2.common.constants
(def delete-message-undo-time-limit-ms 4000) (def delete-message-undo-time-limit-ms 4000)
(def delete-message-for-me-undo-time-limit-ms 4000) (def delete-message-for-me-undo-time-limit-ms 4000)
;;TODO for development only should be removed in status 2.0 ;; NOTE for development only should be removed in status 2.0
(def new-ui-enabled? true) (def new-ui-enabled? true)

View File

@ -195,3 +195,6 @@
(def ^:const community-member-role-all 1) (def ^:const community-member-role-all 1)
(def ^:const community-member-role-manage-users 2) (def ^:const community-member-role-manage-users 2)
(def ^:const community-member-role-moderator 3) (def ^:const community-member-role-moderator 3)
(def ^:const delete-message-undo-time-limit-ms 4000)
(def ^:const delete-message-for-me-undo-time-limit-ms 4000)

View File

@ -0,0 +1,6 @@
(ns status-im2.common.not-implemented
(:require [react-native.core :as rn]))
(defn not-implemented [content]
[rn/view {:border-color :red :border-width 1}
content])

View File

@ -0,0 +1,15 @@
(ns status-im2.contexts.chat.messages.content.deleted.view
(:require [quo2.core :as quo]
[i18n.i18n :as i18n]))
(defn deleted-message [{:keys [deleted? deleted-undoable-till timestamp-str deleted-for-me-undoable-till]}]
[quo/system-message
{:type :deleted
:label (if deleted? :message-deleted :message-deleted-for-you)
:labels {:pinned-a-message (i18n/label :t/pinned-a-message)
:message-deleted (i18n/label :t/message-deleted-for-everyone)
:message-deleted-for-you (i18n/label :t/message-deleted-for-you)
:added (i18n/label :t/added)}
:timestamp-str timestamp-str
:non-pressable? true
:animate-landing? (or deleted-undoable-till deleted-for-me-undoable-till)}])

View File

@ -0,0 +1,14 @@
(ns status-im2.contexts.chat.messages.content.pin.style
(:require [quo2.foundations.colors :as colors]))
(def pin-indicator-container
{:margin-top 4
:margin-left 54
:justify-content :center
:align-self :flex-start
:align-items :flex-start
:flex-direction :row})
(def pin-author-text
{:color colors/primary-50
:bottom 2})

View File

@ -0,0 +1,52 @@
(ns status-im2.contexts.chat.messages.content.pin.view
(:require [react-native.core :as rn]
[status-im2.contexts.chat.messages.content.pin.style :as style]
[quo2.core :as quo]
[quo2.foundations.colors :as colors]
[utils.re-frame :as rf]
[i18n.i18n :as i18n]
[status-im.ui2.screens.chat.messages.message :as old-message]
[status-im.ui.screens.chat.styles.message.message :as old-style]))
(defn pinned-by-view [pinned-by]
(let [{:keys [public-key]} (rf/sub [:multiaccount/contact])
contact-names (rf/sub [:contacts/contact-two-names-by-identity pinned-by])
author-name (if (= pinned-by public-key) (i18n/label :t/You) (first contact-names))]
[rn/view {:style style/pin-indicator-container
:accessibility-label :pinned-by}
[quo/icon :i/pin {:color colors/primary-50 :size 16}]
[quo/text {:size :label
:weight :medium
:style style/pin-author-text}
author-name]]))
(defn system-message [{:keys [from in-popover? timestamp-str chat-id] :as message}]
(let [response-to (:response-to (:content message))
default-size 36]
[rn/touchable-opacity {:on-press #(rf/dispatch [:bottom-sheet/show-sheet :pinned-messages-list chat-id])
:active-opacity 1
:style (merge {:flex-direction :row :margin-vertical 8} (old-style/message-wrapper message))}
[rn/view {:style {:width default-size
:height default-size
:margin-right 16
:border-radius default-size
:justify-content :center
:align-items :center
:background-color colors/primary-50-opa-10}
:accessibility-label :content-type-pin-icon}
[quo/icon :i/pin {:color colors/primary-50 :size 16}]]
[rn/view
[rn/view {:style {:flex-direction :row :align-items :center}}
[rn/touchable-opacity {:style old-style/message-author-touchable
:disabled in-popover?
:on-press #(rf/dispatch [:chat.ui/show-profile from])}
[old-message/message-author-name from {} 20]]
[rn/text {:style {:font-size 13}} (str " " (i18n/label :t/pinned-a-message))]
[rn/text
{:style (merge
{:padding-left 5
:margin-top 2}
(old-style/message-timestamp-text))
:accessibility-label :message-timestamp}
timestamp-str]]
[old-message/quoted-message {:message-id response-to :chat-id chat-id} (:quoted-message message) true]]]))

View File

@ -0,0 +1,28 @@
(ns status-im2.contexts.chat.messages.content.reactions.view
(:require [status-im2.common.constants :as constants]
[quo2.core :as quo]
[react-native.core :as rn]
[utils.re-frame :as rf]
[status-im2.contexts.chat.messages.drawers.view :as drawers]))
(defn message-reactions-row [chat-id message-id]
(let [reactions (rf/sub [:chats/message-reactions message-id chat-id])]
(when (seq reactions)
[rn/view {:margin-left 52 :margin-bottom 12 :flex-direction :row}
(for [{:keys [own emoji-id quantity emoji-reaction-id] :as emoji-reaction} reactions]
^{:key (str emoji-reaction)}
[rn/view {:style {:margin-right 6}}
[quo/reaction {:emoji (get constants/reactions emoji-id)
:neutral? own
:clicks quantity
:on-press (if own
#(rf/dispatch [:models.reactions/send-emoji-reaction-retraction
{:message-id message-id
:emoji-id emoji-id
:emoji-reaction-id emoji-reaction-id}])
#(rf/dispatch [:models.reactions/send-emoji-reaction
{:message-id message-id
:emoji-id emoji-id}]))
:accessibility-label (str "emoji-reaction-" emoji-id)}]])
[quo/add-reaction {:on-press #(rf/dispatch [:bottom-sheet/show-sheet
{:content (fn [] [drawers/reactions chat-id message-id])}])}]])))

View File

@ -0,0 +1,5 @@
(ns status-im2.contexts.chat.messages.content.status.view
(:require [status-im.ui2.screens.chat.messages.message :as old-message]))
(defn status [message-data]
[old-message/message-status message-data])

View File

@ -0,0 +1,10 @@
(ns status-im2.contexts.chat.messages.content.style
(:require [quo2.foundations.colors :as colors]))
(defn message-container [in-pinned-view? pinned mentioned last-in-group?]
(merge (when (and (not in-pinned-view?) (or mentioned pinned))
{:background-color colors/primary-50-opa-5
:margin-bottom 4})
(when (or mentioned pinned last-in-group?)
{:margin-top 8})
{:border-radius 16}))

View File

@ -0,0 +1,7 @@
(ns status-im2.contexts.chat.messages.content.system.text.view
(:require [react-native.core :as rn]
[status-im.ui2.screens.chat.messages.message :as old-message]))
(defn text-content [message-data]
[rn/view {:accessibility-label :chat-item}
[old-message/render-parsed-text message-data]])

View File

@ -0,0 +1 @@
(ns status-im2.contexts.chat.messages.content.text.style)

View File

@ -0,0 +1,5 @@
(ns status-im2.contexts.chat.messages.content.text.view
(:require [status-im.ui2.screens.chat.messages.message :as old-message]))
(defn text-content [message-data]
[old-message/render-parsed-text message-data])

View File

@ -0,0 +1,9 @@
(ns status-im2.contexts.chat.messages.content.unknown.view
(:require [react-native.core :as rn]))
(defn unknown-content
[{:keys [content-type content]}]
[rn/text
(if (seq (:text content))
(:text content)
(str "Unhandled content-type " content-type))])

View File

@ -0,0 +1,107 @@
(ns status-im2.contexts.chat.messages.content.view
(:require [react-native.core :as rn]
[quo2.foundations.colors :as colors]
[status-im2.contexts.chat.messages.content.style :as style]
[status-im2.contexts.chat.messages.content.pin.view :as pin]
[status-im2.common.constants :as constants]
[status-im2.contexts.chat.messages.content.unknown.view :as content.unknown]
[status-im2.contexts.chat.messages.content.text.view :as content.text]
[status-im2.contexts.chat.messages.drawers.view :as drawers]
[status-im2.contexts.chat.messages.content.reactions.view :as reactions]
[status-im2.contexts.chat.messages.content.status.view :as status]
[status-im2.contexts.chat.messages.content.system.text.view :as system.text]
[quo2.core :as quo]
[utils.re-frame :as rf]
[status-im.utils.datetime :as time]
[status-im.ui2.screens.chat.messages.message :as old-message]
[status-im2.common.not-implemented :as not-implemented]))
(defn avatar
[{:keys [response-to last-in-group? pinned quoted-message from]}]
[rn/touchable-without-feedback {:on-press #(rf/dispatch [:chat.ui/show-profile from])}
[rn/view {:padding-top 2 :width 32}
(when (or (and (seq response-to) quoted-message) last-in-group? pinned)
(let [display-name (first (rf/sub [:contacts/contact-two-names-by-identity from]))
contact (rf/sub [:contacts/contact-by-address from])
photo-path (when-not (empty? (:images contact)) (rf/sub [:chats/photo-path from]))
online? (rf/sub [:visibility-status-updates/online? from])]
[quo/user-avatar
{:full-name display-name
:profile-picture photo-path
:status-indicator? true
:online? online?
:size :small
:ring? false}]))]])
(defn author
[{:keys [response-to last-in-group? pinned quoted-message from timestamp]}]
(when (or (and (seq response-to) quoted-message) last-in-group? pinned)
(let [display-name (first (rf/sub [:contacts/contact-two-names-by-identity from]))
{:keys [ens-verified added?]} (rf/sub [:contacts/contact-by-address from])]
[quo/author
{:profile-name display-name
:chat-key from
:time-str (time/timestamp->time timestamp)
:contact? added?
:verified? ens-verified}])))
(defn system-message-content
[{:keys [content-type quoted-message] :as message-data}]
(if quoted-message
[not-implemented/not-implemented [pin/system-message message-data]]
(case content-type
constants/content-type-system-text [not-implemented/not-implemented [system.text/text-content message-data]]
constants/content-type-community [not-implemented/not-implemented [old-message/community message-data]]
constants/content-type-contact-request [not-implemented/not-implemented [old-message/system-contact-request message-data]])))
(defn user-message-content
[{:keys [content-type quoted-message content] :as message-data}
{:keys [chat-id] :as context}]
(let [response-to (:response-to content)]
[rn/touchable-highlight
{:underlay-color (colors/theme-colors colors/neutral-5 colors/neutral-90)
:style {:border-radius 16}
:on-press #()
:on-long-press #(rf/dispatch [:bottom-sheet/show-sheet
{:content (drawers/reactions-and-actions message-data context)}])}
[rn/view {:padding-vertical 8}
(when (and (seq response-to) quoted-message)
[old-message/quoted-message {:message-id response-to :chat-id chat-id} quoted-message])
[rn/view {:padding-horizontal 12 :flex-direction :row}
[avatar message-data]
[rn/view {:margin-left 8}
[author message-data]
(case content-type
constants/content-type-text [not-implemented/not-implemented [content.text/text-content message-data]]
constants/content-type-emoji [not-implemented/not-implemented [old-message/emoji message-data]]
constants/content-type-sticker [not-implemented/not-implemented [old-message/sticker message-data]]
constants/content-type-image [not-implemented/not-implemented [old-message/message-content-image message-data]]
constants/content-type-audio [not-implemented/not-implemented [old-message/audio message-data]]
[not-implemented/not-implemented [content.unknown/unknown-content message-data]])
[status/status message-data]]]]]))
(defn message-with-reactions
[{:keys [pinned pinned-by mentioned in-pinned-view? content-type
last-in-group? message-id]
:as message-data}
{:keys [chat-id] :as context}]
[rn/view
{:style (style/message-container in-pinned-view? pinned mentioned last-in-group?)
:accessibility-label :chat-item}
(when pinned
[pin/pinned-by-view pinned-by])
(if (#{constants/content-type-system-text constants/content-type-community
constants/content-type-contact-request}
content-type)
[system-message-content message-data]
[user-message-content message-data context])
[reactions/message-reactions-row chat-id message-id]])

View File

@ -1,4 +1,4 @@
(ns status-im2.contexts.chat.messages.message.delete-message.events (ns status-im2.contexts.chat.messages.delete-message.events
(:require [status-im.chat.models.message-list :as message-list] (:require [status-im.chat.models.message-list :as message-list]
[status-im.utils.datetime :as datetime] [status-im.utils.datetime :as datetime]
[taoensso.timbre :as log] [taoensso.timbre :as log]

View File

@ -1,7 +1,7 @@
(ns status-im2.contexts.chat.messages.message.delete-message.events-test (ns status-im2.contexts.chat.messages.delete-message.events-test
(:require [cljs.test :refer-macros [deftest is testing]] (:require [cljs.test :refer-macros [deftest is testing]]
[status-im.utils.datetime :as datetime] [status-im.utils.datetime :as datetime]
[status-im2.contexts.chat.messages.message.delete-message.events :as delete-message])) [status-im2.contexts.chat.messages.delete-message.events :as delete-message]))
(def mid "message-id") (def mid "message-id")
(def cid "chat-id") (def cid "chat-id")

View File

@ -1,4 +1,4 @@
(ns status-im2.contexts.chat.messages.message.delete-message-for-me.events (ns status-im2.contexts.chat.messages.delete-message-for-me.events
(:require [status-im.chat.models.message-list :as message-list] (:require [status-im.chat.models.message-list :as message-list]
[status-im.utils.datetime :as datetime] [status-im.utils.datetime :as datetime]
[taoensso.timbre :as log] [taoensso.timbre :as log]

View File

@ -1,8 +1,7 @@
(ns status-im2.contexts.chat.messages.message.delete-message-for-me.events-test (ns status-im2.contexts.chat.messages.delete-message-for-me.events-test
(:require [cljs.test :refer-macros [deftest is testing]] (:require [cljs.test :refer-macros [deftest is testing]]
[status-im.utils.datetime :as datetime] [status-im.utils.datetime :as datetime]
[status-im2.contexts.chat.messages.message.delete-message-for-me.events :as [status-im2.contexts.chat.messages.delete-message-for-me.events :as delete-message-for-me]))
delete-message-for-me]))
(def mid "message-id") (def mid "message-id")
(def cid "chat-id") (def cid "chat-id")

View File

@ -0,0 +1,166 @@
(ns status-im2.contexts.chat.messages.drawers.view
(:require [react-native.core :as rn]
[status-im2.common.constants :as constants]
[utils.re-frame :as rf]
[quo2.core :as quo]
[i18n.i18n :as i18n]
[status-im2.setup.config :as config]
[status-im.ui.components.react :as react]
[status-im.ui2.screens.chat.components.reply :as components.reply]
[status-im2.common.not-implemented :as not-implemented]))
(defn pin-message
[{:keys [chat-id pinned] :as message-data}]
(let [pinned-messages (rf/sub [:chats/pinned chat-id])]
(if (and (not pinned) (> (count pinned-messages) 2))
(do
(js/setTimeout (fn [] (rf/dispatch [:dismiss-keyboard])) 500)
(rf/dispatch [:pin-message/show-pin-limit-modal chat-id]))
(rf/dispatch [:pin-message/send-pin-message (assoc message-data :pinned (not pinned))]))))
(defn get-actions
[{:keys [outgoing content pinned] :as message-data}
{:keys [edit-enabled show-input? can-delete-message-for-everyone? community? message-pin-enabled]}]
(concat
(when (and outgoing edit-enabled)
[{:type :main
:on-press #(rf/dispatch [:chat.ui/edit-message message-data])
:label (i18n/label :t/edit-message)
:icon :i/edit
:id :edit}])
(when show-input?
[{:type :main
:on-press #(rf/dispatch [:chat.ui/reply-to-message message-data])
:label (i18n/label :t/message-reply)
:icon :i/reply
:id :reply}])
[{:type :main
:on-press #(react/copy-to-clipboard
(components.reply/get-quoted-text-with-mentions
(get content :parsed-text)))
:label (i18n/label :t/copy-text)
:icon :i/copy
:id :copy}]
(when message-pin-enabled
[{:type :main
:on-press #(pin-message message-data)
:label (i18n/label (if pinned
(if community? :t/unpin-from-channel :t/unpin-from-chat)
(if community? :t/pin-to-channel :t/pin-to-chat)))
:icon :i/pin
:id (if pinned :unpin :pin)}])
(when-not pinned
[{:type :danger
:on-press #(rf/dispatch [:chat.ui/delete-message-for-me message-data
constants/delete-message-for-me-undo-time-limit-ms])
:label (i18n/label :t/delete-for-me)
:icon :i/delete
:id :delete-for-me}])
(when (and (or outgoing can-delete-message-for-everyone?) config/delete-message-enabled?)
[{:type :danger
:on-press #(rf/dispatch [:chat.ui/delete-message message-data
constants/delete-message-undo-time-limit-ms])
:label (i18n/label :t/delete-for-everyone)
:icon :i/delete
:id :delete-for-all}])))
(defn extract-id
[reactions id]
(->> reactions
(filter (fn [{:keys [emoji-id]}] (= emoji-id id)))
first
:emoji-reaction-id))
(defn reactions
[chat-id message-id]
(let [reactions (rf/sub [:chats/message-reactions message-id chat-id])
own-reactions (reduce (fn [acc {:keys [emoji-id own emoji-reaction-id]}]
(if own
(assoc acc emoji-id emoji-reaction-id)
acc))
{}
reactions)]
[rn/view
{:style {:flex-direction :row
:justify-content :space-between
:padding-horizontal 30
:padding-top 5
:padding-bottom 15}}
(doall
(for [[id icon] constants/reactions]
(let [emoji-reaction-id (get own-reactions id)]
^{:key id}
[not-implemented/not-implemented
[quo/button
(merge
{:size 40
:type (if emoji-reaction-id :grey :ghost)
:icon true
:icon-no-color true
:accessibility-label (str "emoji-picker-" id)
:on-press (fn []
(if emoji-reaction-id
(rf/dispatch [:models.reactions/send-emoji-reaction-retraction
{:message-id message-id
:emoji-id id
:emoji-reaction-id emoji-reaction-id}])
(rf/dispatch [:models.reactions/send-emoji-reaction
{:message-id message-id
:emoji-id id}]))
(rf/dispatch [:bottom-sheet/hide]))})
icon]])))]))
(defn reactions-and-actions
[{:keys [message-id] :as message-data} {:keys [chat-id] :as context}]
(fn []
(let [actions (get-actions message-data context)
main-actions (filter #(= (:type %) :main) actions)
danger-actions (filter #(= (:type %) :danger) actions)
admin-actions (filter #(= (:type %) :admin) actions)]
[:<>
;; REACTIONS
[reactions chat-id message-id]
;; MAIN ACTIONS
[rn/view {:style {:padding-horizontal 8}}
(for [action main-actions]
(let [on-press (:on-press action)]
^{:key (:id action)}
[quo/menu-item
{:type :main
:title (:label action)
:accessibility-label (:label action)
:icon (:icon action)
:on-press #(do
(when on-press (on-press))
(rf/dispatch [:bottom-sheet/hide]))}]))
(when-not (empty? danger-actions)
[quo/separator])
;; DANGER ACTIONS
(for [action danger-actions]
(let [on-press (:on-press action)]
^{:key (:id action)}
[quo/menu-item
{:type :danger
:title (:label action)
:accessibility-label (:label action)
:icon (:icon action)
:on-press #(do
(when on-press (on-press))
(rf/dispatch [:bottom-sheet/hide]))}]))
(when-not (empty? admin-actions)
[quo/separator])
;; ADMIN ACTIONS
(for [action admin-actions]
(let [on-press (:on-press action)]
^{:key (:id action)}
[quo/menu-item
{:type :danger
:title (:label action)
:accessibility-label (:label action)
:icon (:icon action)
:on-press #(do
(when on-press (on-press))
(rf/dispatch [:bottom-sheet/hide]))}]))]])))

View File

@ -8,11 +8,13 @@
[react-native.platform :as platform] [react-native.platform :as platform]
[reagent.core :as reagent] [reagent.core :as reagent]
[status-im.ui.screens.chat.group :as chat.group] [status-im.ui.screens.chat.group :as chat.group]
[status-im.ui.screens.chat.message.gap :as gap]
[status-im.ui.screens.chat.state :as state] [status-im.ui.screens.chat.state :as state]
[status-im.ui2.screens.chat.messages.message :as message] [status-im2.contexts.chat.messages.content.view :as message]
[status-im2.common.constants :as constants] ;;TODO move to status-im2 [status-im2.common.constants :as constants]
[utils.re-frame :as rf])) [utils.re-frame :as rf]
[status-im2.contexts.chat.messages.content.deleted.view :as content.deleted]
[status-im.ui.screens.chat.message.gap :as message.gap]
[status-im2.common.not-implemented :as not-implemented]))
(defonce messages-list-ref (atom nil)) (defonce messages-list-ref (atom nil))
@ -112,39 +114,23 @@
[rn/view {:style (when platform/android? {:scaleY -1})} [rn/view {:style (when platform/android? {:scaleY -1})}
[chat.group/group-chat-footer chat-id invitation-admin]])) [chat.group/group-chat-footer chat-id invitation-admin]]))
(defn render-fn (defn render-fn [{:keys [type value deleted? deleted-for-me? content-type] :as message-data} _ _ context]
[{:keys [outgoing type] :as message} [rn/view {:style (when platform/android? {:scaleY -1})}
idx
_
{:keys [group-chat public? community? current-public-key
chat-id show-input? message-pin-enabled edit-enabled in-pinned-view?
can-delete-message-for-everyone?]}]
[rn/view {:style (when (and platform/android? (not in-pinned-view?)) {:scaleY -1})}
(if (= type :datemark) (if (= type :datemark)
[quo/divider-date (:value message)] [quo/divider-date value]
(if (= type :gap) (if (= content-type constants/content-type-gap)
;; TODO (flexsurfer) new gap functionality is not implemented yet [not-implemented/not-implemented
[gap/gap message idx messages-list-ref false chat-id] [message.gap/gap message-data]]
; message content [rn/view {:padding-horizontal 8}
[message/chat-message (if (or deleted? deleted-for-me?)
(assoc message [content.deleted/deleted-message message-data]
:incoming-group (and group-chat (not outgoing)) [message/message-with-reactions message-data context])]))])
:group-chat group-chat
:public? public?
:community? community?
:current-public-key current-public-key
:show-input? show-input?
:message-pin-enabled message-pin-enabled
:edit-enabled edit-enabled
:can-delete-message-for-everyone? can-delete-message-for-everyone?)]))])
(defn messages-list (defn messages-list [{:keys [chat
[{:keys [chat pan-responder
bottom-space show-input?]}]
pan-responder
mutual-contact-requests-enabled?
show-input?]}]
(let [{:keys [group-chat chat-type chat-id public? community-id admins]} chat (let [{:keys [group-chat chat-type chat-id public? community-id admins]} chat
mutual-contact-requests-enabled? (rf/sub [:mutual-contact-requests/enabled?])
messages (rf/sub [:chats/raw-chat-messages-stream chat-id]) messages (rf/sub [:chats/raw-chat-messages-stream chat-id])
one-to-one? (= chat-type constants/one-to-one-chat-type) one-to-one? (= chat-type constants/one-to-one-chat-type)
contact-added? (when one-to-one? (rf/sub [:contacts/contact-added? chat-id])) contact-added? (when one-to-one? (rf/sub [:contacts/contact-added? chat-id]))
@ -152,8 +138,9 @@
(and (and
mutual-contact-requests-enabled? mutual-contact-requests-enabled?
one-to-one? one-to-one?
(not contact-added?))] (not contact-added?))
[rn/view {:style {:flex 1}} bottom-space 15]
[:<>
;;DO NOT use anonymous functions for handlers ;;DO NOT use anonymous functions for handlers
[rn/flat-list [rn/flat-list
(merge (merge

View File

@ -1,18 +1,36 @@
(ns status-im2.contexts.chat.messages.pin.list.view (ns status-im2.contexts.chat.messages.pin.list.view
(:require [i18n.i18n :as i18n] (:require [i18n.i18n :as i18n]
[quo2.core :as quo] [quo2.core :as quo]
[quo2.foundations.colors :as colors] ;; TODO move to status-im2 [quo2.foundations.colors :as colors]
[react-native.core :as rn] [react-native.core :as rn]
[status-im.ui2.screens.chat.messages.message :as old-message] [status-im2.contexts.chat.messages.content.view :as message]
[utils.re-frame :as rf])) [utils.re-frame :as rf]
[status-im.utils.datetime :as time]))
(def list-key-fn #(or (:message-id %) (:value %))) (def list-key-fn #(or (:message-id %) (:value %)))
(defn pinned-messages-list (defn message-render-fn
[chat-id] [{:keys [whisper-timestamp] :as message}
_
{:keys [group-chat public? community? current-public-key show-input? edit-enabled]}]
;; TODO (flexsurfer) probably we don't want reactions here
[message/message-with-reactions
message
{:group-chat group-chat
:public? public?
:community? community?
:current-public-key current-public-key
:show-input? show-input?
:message-pin-enabled true
:in-pinned-view? true
:pinned true
:timestamp-str (time/timestamp->time whisper-timestamp)
:edit-enabled edit-enabled}])
(defn pinned-messages-list [chat-id]
(let [pinned-messages (vec (vals (rf/sub [:chats/pinned chat-id]))) (let [pinned-messages (vec (vals (rf/sub [:chats/pinned chat-id])))
current-chat (rf/sub [:chats/current-chat]) current-chat (rf/sub [:chats/current-chat])
community (rf/sub [:communities/community (:community-id current-chat)])] community (rf/sub [:communities/community (:community-id current-chat)])]
[rn/view {:accessibility-label :pinned-messages-list} [rn/view {:accessibility-label :pinned-messages-list}
;; TODO (flexsurfer) this should be a component in quo2 ;; TODO (flexsurfer) this should be a component in quo2
;; https://github.com/status-im/status-mobile/issues/14529 ;; https://github.com/status-im/status-mobile/issues/14529
@ -44,7 +62,7 @@
(if (> (count pinned-messages) 0) (if (> (count pinned-messages) 0)
[rn/flat-list [rn/flat-list
{:data pinned-messages {:data pinned-messages
:render-fn old-message/message-render-fn :render-fn message-render-fn
:key-fn list-key-fn :key-fn list-key-fn
:separator quo/separator}] :separator quo/separator}]
[rn/view [rn/view

View File

@ -10,7 +10,8 @@
[status-im2.contexts.chat.messages.pin.banner.view :as pin.banner] ;;TODO move to status-im2 [status-im2.contexts.chat.messages.pin.banner.view :as pin.banner] ;;TODO move to status-im2
[status-im2.navigation.state :as navigation.state] [status-im2.navigation.state :as navigation.state]
[utils.debounce :as debounce] [utils.debounce :as debounce]
[utils.re-frame :as rf])) [utils.re-frame :as rf]
[status-im2.common.not-implemented :as not-implemented]))
(defn navigate-back-handler (defn navigate-back-handler
[] []
@ -51,28 +52,21 @@
:accessibility-label :back-button} :accessibility-label :back-button}
:right-section-buttons :right-section-buttons
[{:on-press #() ;; TODO not implemented [{:on-press #()
:style {:border-width 1 :border-color :red}
:icon :i/options :icon :i/options
:accessibility-label :options-button}]}])) :accessibility-label :options-button}]}]))
(defn chat-render (defn chat-render []
[] (let [;;NOTE: we want to react only on these fields, do not use full chat map here
(let [;;we want to react only on these fields, do not use full chat map here {:keys [chat-id show-input?] :as chat} (rf/sub [:chats/current-chat-chat-view])]
{:keys [chat-id show-input?] :as chat} (rf/sub [:chats/current-chat-chat-view])
mutual-contact-requests-enabled? (rf/sub [:mutual-contact-requests/enabled?])]
[rn/keyboard-avoiding-view {:style {:flex 1}} [rn/keyboard-avoiding-view {:style {:flex 1}}
[page-nav] [page-nav]
;; TODO (flexsurfer) this should be in-app notification component in quo2 [not-implemented/not-implemented
;; https://github.com/status-im/status-mobile/issues/14527 [pin-limit-popover/pin-limit-popover chat-id]]
[pin-limit-popover/pin-limit-popover chat-id] [not-implemented/not-implemented
[pin.banner/banner chat-id] [pin.banner/banner chat-id]]
;;MESSAGES LIST [messages.list/messages-list {:chat chat :show-input? show-input?}]
[messages.list/messages-list
{:chat chat
:mutual-contact-requests-enabled? mutual-contact-requests-enabled?
:show-input? show-input?
:bottom-space 15}]
;;INPUT COMPOSER
(when show-input? (when show-input?
[composer/composer chat-id])])) [composer/composer chat-id])]))

View File

@ -37,7 +37,7 @@
{:padding-vertical 60 {:padding-vertical 60
:align-items :center} :align-items :center}
[quo2.reaction/reaction @state] [quo2.reaction/reaction @state]
[quo2.reaction/open-reactions-menu @state]]]]))) [quo2.reaction/add-reaction @state]]]])))
(defn preview-react (defn preview-react
[] []

View File

@ -171,7 +171,7 @@ class ChatElementByText(Text):
def username(self): def username(self):
class Username(Text): class Username(Text):
def __init__(self, driver, parent_locator: str): def __init__(self, driver, parent_locator: str):
super().__init__(driver, prefix=parent_locator, xpath="/android.widget.TextView[1]") super().__init__(driver, prefix=parent_locator, xpath="/android.view.ViewGroup/android.widget.TextView[1]")
return Username(self.driver, self.locator) return Username(self.driver, self.locator)