Pinned messages in chats
Signed-off-by: Brian Sztamfater <brian@status.im>
This commit is contained in:
parent
440e6d2047
commit
089f42e0e9
Binary file not shown.
After Width: | Height: | Size: 845 B |
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
|
@ -232,6 +232,14 @@
|
|||
{:db (assoc-in db [:chats chat-id] chat)
|
||||
:dispatch [:chat.ui/navigate-to-chat chat-id]}))
|
||||
|
||||
(fx/defn navigate-to-user-pinned-messages
|
||||
"Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data"
|
||||
{:events [:chat.ui/navigate-to-pinned-messages]}
|
||||
[{db :db :as cofx} chat-id]
|
||||
(fx/merge cofx
|
||||
{:db (assoc db :current-chat-id chat-id)}
|
||||
(navigation/navigate-to :chat-pinned-messages nil)))
|
||||
|
||||
(fx/defn start-chat
|
||||
"Start a chat, making sure it exists"
|
||||
{:events [:chat.ui/start-chat]}
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
[status-im.chat.models.message-list :as message-list]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.ethereum.json-rpc :as json-rpc]
|
||||
[clojure.string :as string]))
|
||||
[clojure.string :as string]
|
||||
[status-im.chat.models.pin-message :as models.pin-message]))
|
||||
|
||||
(defn cursor->clock-value
|
||||
[^js cursor]
|
||||
|
@ -115,7 +116,8 @@
|
|||
(when (or first-request cursor)
|
||||
(merge
|
||||
{:db (assoc-in db [:pagination-info chat-id :loading-messages?] true)}
|
||||
{:utils/dispatch-later [{:ms 100 :dispatch [:load-more-reactions cursor chat-id]}]}
|
||||
{:utils/dispatch-later [{:ms 100 :dispatch [:load-more-reactions cursor chat-id]}
|
||||
{:ms 100 :dispatch [::models.pin-message/load-pin-messages chat-id]}]}
|
||||
(data-store.messages/messages-by-chat-id-rpc
|
||||
chat-id
|
||||
cursor
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
(ns status-im.chat.models.pin-message
|
||||
(:require [status-im.chat.models.message-list :as message-list]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.data-store.pin-messages :as data-store.pin-messages]
|
||||
[status-im.utils.fx :as fx]
|
||||
[taoensso.timbre :as log]
|
||||
[re-frame.core :as re-frame]))
|
||||
|
||||
(fx/defn handle-failed-loading-pin-messages
|
||||
{:events [::failed-loading-pin-messages]}
|
||||
[{:keys [db]} current-chat-id _ err]
|
||||
(log/error "failed loading pin messages" current-chat-id err)
|
||||
(when current-chat-id
|
||||
{:db (assoc-in db [:pagination-info current-chat-id :loading-pin-messages?] false)}))
|
||||
|
||||
(fx/defn pin-messages-loaded
|
||||
{:events [::pin-messages-loaded]}
|
||||
[{db :db} chat-id {:keys [cursor pinned-messages]}]
|
||||
(let [all-messages (reduce (fn [acc {:keys [message-id] :as message}]
|
||||
(assoc acc message-id message))
|
||||
{}
|
||||
pinned-messages)
|
||||
messages-id-list (map :message-id pinned-messages)]
|
||||
{:db (-> db
|
||||
(assoc-in [:pagination-info chat-id :loading-pin-messages?] false)
|
||||
(assoc-in [:pin-messages chat-id] all-messages)
|
||||
(assoc-in [:pin-message-lists chat-id] (message-list/add-many nil (vals all-messages)))
|
||||
(assoc-in [:pagination-info chat-id :all-pin-loaded?]
|
||||
(empty? cursor)))}))
|
||||
|
||||
(fx/defn receive-signal
|
||||
[{:keys [db] :as cofx} pin-messages]
|
||||
(let [{:keys [chat-id]} (first pin-messages)]
|
||||
(when (= chat-id (db :current-chat-id))
|
||||
(let [{:keys [chat-id]} (first pin-messages)
|
||||
already-loaded-pin-messages (get-in db [:pin-messages chat-id] {})
|
||||
already-loaded-messages (get-in db [:messages chat-id] {})
|
||||
all-messages (reduce (fn [acc {:keys [message_id pinned from]}]
|
||||
;; Add to or remove from pinned message list, and normalizing pinned-by property
|
||||
(let [current-message (get already-loaded-messages message_id)
|
||||
current-message-pin (merge current-message
|
||||
{:pinned pinned
|
||||
:pinned-by from})]
|
||||
(cond-> acc
|
||||
(nil? pinned)
|
||||
(dissoc message_id)
|
||||
|
||||
(and (some? pinned) (some? current-message))
|
||||
(assoc message_id current-message-pin))))
|
||||
already-loaded-pin-messages
|
||||
pin-messages)]
|
||||
{:db (-> db
|
||||
(assoc-in [:pin-messages chat-id] all-messages)
|
||||
(assoc-in [:pin-message-lists chat-id]
|
||||
(message-list/add-many nil (vals all-messages))))}))))
|
||||
|
||||
(fx/defn load-more-pin-messages
|
||||
[{:keys [db]} chat-id first-request]
|
||||
(let [not-all-loaded? (not (get-in db [:pagination-info chat-id :all-loaded?]))
|
||||
not-loading-pin-messages? (not (get-in db [:pagination-info chat-id :loading-pin-messages?]))]
|
||||
(when not-loading-pin-messages?
|
||||
(fx/merge
|
||||
{:db (assoc-in db [:pagination-info chat-id :loading-pin-messages?] true)}
|
||||
(data-store.pin-messages/pinned-message-by-chat-id-rpc
|
||||
chat-id
|
||||
nil
|
||||
constants/default-number-of-pin-messages
|
||||
#(re-frame/dispatch [::pin-messages-loaded chat-id %])
|
||||
#(re-frame/dispatch [::failed-loading-pin-messages chat-id %]))))))
|
||||
|
||||
(fx/defn send-pin-message
|
||||
"Pin message, rebuild pinned messages list"
|
||||
{:events [::send-pin-message]}
|
||||
[{:keys [db] :as cofx} {:keys [chat-id message-id pinned] :as pin-message}]
|
||||
(let [current-public-key (get-in db [:multiaccount :public-key])
|
||||
message (merge pin-message {:pinned-by current-public-key})]
|
||||
(fx/merge cofx
|
||||
{:db (cond-> db
|
||||
pinned
|
||||
(->
|
||||
(update-in [:pin-message-lists chat-id] message-list/add message)
|
||||
(assoc-in [:pin-messages chat-id message-id] message))
|
||||
(not pinned)
|
||||
(->
|
||||
(update-in [:pin-message-lists chat-id] message-list/remove-message pin-message)
|
||||
(update-in [:pin-messages chat-id] dissoc message-id)))}
|
||||
(data-store.pin-messages/send-pin-message {:chat-id (pin-message :chat-id)
|
||||
:message_id (pin-message :message-id)
|
||||
:pinned (pin-message :pinned)}))))
|
||||
|
||||
(fx/defn load-pin-messages
|
||||
{:events [::load-pin-messages]}
|
||||
[{:keys [db] :as cofx} chat-id]
|
||||
(load-more-pin-messages cofx chat-id true))
|
|
@ -66,6 +66,7 @@
|
|||
(def ^:const min-password-length 6)
|
||||
(def ^:const max-group-chat-participants 20)
|
||||
(def ^:const default-number-of-messages 20)
|
||||
(def ^:const default-number-of-pin-messages 3)
|
||||
|
||||
(def ^:const mailserver-password "status-offline-inbox")
|
||||
|
||||
|
|
|
@ -26,3 +26,8 @@
|
|||
(navigation/navigate-back %))
|
||||
#(when ens-name
|
||||
(contact/name-verified % public-key ens-name)))))
|
||||
|
||||
(fx/defn pinned-messages-pressed
|
||||
{:events [:contact.ui/pinned-messages-pressed]}
|
||||
[cofx public-key]
|
||||
(chat/navigate-to-user-pinned-messages cofx public-key))
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
(assoc :text (:text content)
|
||||
:sticker (:sticker content))
|
||||
:always
|
||||
(clojure.set/rename-keys {:chat-id :chatId
|
||||
(clojure.set/rename-keys {:chat-id :chat_id
|
||||
:whisper-timestamp :whisperTimestamp
|
||||
:community-id :communityId
|
||||
:clock-value :clock})))
|
||||
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
(ns status-im.data-store.pin-messages
|
||||
(:require [clojure.set :as clojure.set]
|
||||
[status-im.ethereum.json-rpc :as json-rpc]
|
||||
[status-im.utils.fx :as fx]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.data-store.messages :as messages]))
|
||||
|
||||
(defn <-rpc [message]
|
||||
(-> message
|
||||
(merge (messages/<-rpc (message :message)))
|
||||
(clojure.set/rename-keys {:pinnedAt :pinned-at
|
||||
:pinnedBy :pinned-by})
|
||||
(dissoc :message)))
|
||||
|
||||
(defn pinned-message-by-chat-id-rpc [chat-id
|
||||
cursor
|
||||
limit
|
||||
on-success
|
||||
on-failure]
|
||||
{::json-rpc/call [{:method (json-rpc/call-ext-method "chatPinnedMessages")
|
||||
:params [chat-id cursor limit]
|
||||
:on-success (fn [result]
|
||||
(let [result (clojure.set/rename-keys result {:pinnedMessages :pinned-messages})]
|
||||
(on-success (update result :pinned-messages #(map <-rpc %)))))
|
||||
:on-failure on-failure}]})
|
||||
|
||||
(fx/defn send-pin-message [cofx pin-message]
|
||||
{::json-rpc/call [{:method (json-rpc/call-ext-method "sendPinMessage")
|
||||
:params [(messages/->rpc pin-message)]
|
||||
:on-success #(log/debug "successfully pinned message" pin-message)
|
||||
:on-failure #(log/error "failed to pin message" % pin-message)}]})
|
|
@ -96,6 +96,8 @@
|
|||
"wakuext_getLinkPreviewData" {}
|
||||
"wakuext_requestCommunityInfoFromMailserver" {}
|
||||
"wakuext_deactivateChat" {}
|
||||
"wakuext_sendPinMessage" {}
|
||||
"wakuext_chatPinnedMessages" {}
|
||||
;;TODO not used anywhere?
|
||||
"wakuext_deleteChat" {}
|
||||
"wakuext_saveContact" {}
|
||||
|
|
|
@ -195,6 +195,8 @@
|
|||
(reg-root-key-sub ::reactions :reactions)
|
||||
(reg-root-key-sub ::message-lists :message-lists)
|
||||
(reg-root-key-sub ::pagination-info :pagination-info)
|
||||
(reg-root-key-sub ::pin-message-lists :pin-message-lists)
|
||||
(reg-root-key-sub ::pin-messages :pin-messages)
|
||||
|
||||
(reg-root-key-sub :tos-accept-next-root :tos-accept-next-root)
|
||||
|
||||
|
@ -874,7 +876,7 @@
|
|||
:chats/current-chat-chat-view
|
||||
:<- [:chats/current-chat]
|
||||
(fn [current-chat]
|
||||
(select-keys current-chat [:chat-id :show-input? :group-chat :admins :invitation-admin :public? :chat-type :color :chat-name :synced-to :synced-from])))
|
||||
(select-keys current-chat [:chat-id :show-input? :group-chat :admins :invitation-admin :public? :chat-type :color :chat-name :synced-to :synced-from :community-id])))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:current-chat/metadata
|
||||
|
@ -910,6 +912,12 @@
|
|||
(fn [messages [_ chat-id]]
|
||||
(get messages chat-id {})))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:chats/pinned
|
||||
:<- [::pin-messages]
|
||||
(fn [pin-messages [_ chat-id]]
|
||||
(get pin-messages chat-id {})))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:chats/message-reactions
|
||||
:<- [:multiaccount/public-key]
|
||||
|
@ -939,6 +947,12 @@
|
|||
(fn [pagination-info [_ chat-id]]
|
||||
(get-in pagination-info [chat-id :loading-messages?])))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:chats/loading-pin-messages?
|
||||
:<- [::pagination-info]
|
||||
(fn [pagination-info [_ chat-id]]
|
||||
(get-in pagination-info [chat-id :loading-pin-messages?])))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:chats/public?
|
||||
:<- [::chats]
|
||||
|
@ -951,14 +965,25 @@
|
|||
(fn [message-lists [_ chat-id]]
|
||||
(get message-lists chat-id)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:chats/pin-message-list
|
||||
:<- [::pin-message-lists]
|
||||
(fn [pin-message-lists [_ chat-id]]
|
||||
(get pin-message-lists chat-id)))
|
||||
|
||||
(defn hydrate-messages
|
||||
"Pull data from messages and add it to the sorted list"
|
||||
[message-list messages]
|
||||
([message-list messages] (hydrate-messages message-list messages {}))
|
||||
([message-list messages pinned-messages]
|
||||
(keep #(if (= :message (% :type))
|
||||
(when-let [message (messages (% :message-id))]
|
||||
(merge message %))
|
||||
(let [pinned-message (get pinned-messages (% :message-id))
|
||||
pinned (if pinned-message true (some? (message :pinned-by)))
|
||||
pinned-by (when pinned (or (message :pinned-by) (pinned-message :pinned-by)))
|
||||
message (assoc message :pinned pinned :pinned-by pinned-by)]
|
||||
(merge message %)))
|
||||
%)
|
||||
message-list))
|
||||
message-list)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:chats/chat-no-messages?
|
||||
|
@ -972,11 +997,12 @@
|
|||
(fn [[_ chat-id] _]
|
||||
[(re-frame/subscribe [:chats/message-list chat-id])
|
||||
(re-frame/subscribe [:chats/chat-messages chat-id])
|
||||
(re-frame/subscribe [:chats/pinned chat-id])
|
||||
(re-frame/subscribe [:chats/loading-messages? chat-id])
|
||||
(re-frame/subscribe [:chats/synced-from chat-id])
|
||||
(re-frame/subscribe [:chats/chat-type chat-id])
|
||||
(re-frame/subscribe [:chats/joined chat-id])])
|
||||
(fn [[message-list messages loading-messages? synced-from chat-type joined] [_ chat-id]]
|
||||
(fn [[message-list messages pin-messages loading-messages? synced-from chat-type joined] [_ chat-id]]
|
||||
;;TODO (perf)
|
||||
(let [message-list-seq (models.message-list/->seq message-list)]
|
||||
; Don't show gaps if that's the case as we are still loading messages
|
||||
|
@ -984,9 +1010,26 @@
|
|||
[]
|
||||
(-> message-list-seq
|
||||
(chat.db/add-datemarks)
|
||||
(hydrate-messages messages)
|
||||
(hydrate-messages messages pin-messages)
|
||||
(chat.db/collapse-gaps chat-id synced-from (datetime/timestamp) chat-type joined loading-messages?))))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:chats/raw-chat-pin-messages-stream
|
||||
(fn [[_ chat-id] _]
|
||||
[(re-frame/subscribe [:chats/pin-message-list chat-id])
|
||||
(re-frame/subscribe [:chats/pinned chat-id])
|
||||
(re-frame/subscribe [:chats/loading-pin-messages? chat-id])
|
||||
(re-frame/subscribe [:chats/synced-from chat-id])])
|
||||
(fn [[pin-message-list messages loading-messages?] [_]]
|
||||
;;TODO (perf)
|
||||
(let [pin-message-list-seq (models.message-list/->seq pin-message-list)]
|
||||
; Don't show gaps if that's the case as we are still loading messages
|
||||
(if (and (empty? pin-message-list-seq) loading-messages?)
|
||||
[]
|
||||
(-> pin-message-list-seq
|
||||
(chat.db/add-datemarks)
|
||||
(hydrate-messages messages))))))
|
||||
|
||||
;;we want to keep data unchanged so react doesn't change component when we leave screen
|
||||
(def memo-chat-messages-stream (atom nil))
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
(ns ^{:doc "Definition of the StatusMessage protocol"}
|
||||
status-im.transport.message.core
|
||||
(:require [status-im.chat.models.message :as models.message]
|
||||
[status-im.chat.models.pin-message :as models.pin-message]
|
||||
[status-im.chat.models :as models.chat]
|
||||
[status-im.chat.models.reactions :as models.reactions]
|
||||
[status-im.contact.core :as models.contact]
|
||||
|
@ -11,6 +12,7 @@
|
|||
[status-im.data-store.chats :as data-store.chats]
|
||||
[status-im.data-store.invitations :as data-store.invitations]
|
||||
[status-im.data-store.activities :as data-store.activities]
|
||||
[status-im.data-store.messages :as data-store.messages]
|
||||
[status-im.group-chats.core :as models.group]
|
||||
[status-im.utils.fx :as fx]
|
||||
[status-im.utils.types :as types]
|
||||
|
@ -38,6 +40,7 @@
|
|||
^js invitations (.-invitations response-js)
|
||||
^js removed-chats (.-removedChats response-js)
|
||||
^js activity-notifications (.-activityCenterNotifications response-js)
|
||||
^js pin-messages (.-pinMessages response-js)
|
||||
sync-handler (when-not process-async process-response)]
|
||||
|
||||
(cond
|
||||
|
@ -87,6 +90,13 @@
|
|||
(process-next response-js sync-handler)
|
||||
(models.communities/handle-communities (types/js->clj communities-clj))))
|
||||
|
||||
(seq pin-messages)
|
||||
(let [pin-messages (types/js->clj pin-messages)]
|
||||
(js-delete response-js "pinMessages")
|
||||
(fx/merge cofx
|
||||
(process-next response-js sync-handler)
|
||||
(models.pin-message/receive-signal (map data-store.messages/<-rpc pin-messages))))
|
||||
|
||||
(seq removed-chats)
|
||||
(let [removed-chats-clj (types/js->clj removed-chats)]
|
||||
(js-delete response-js "removedChats")
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
:blue "#6177E5"
|
||||
:gray "#838C91"
|
||||
:blue-light "#23252F"
|
||||
:red "#FC5F5F"})
|
||||
:red "#FC5F5F"
|
||||
:pin-background "#34232B"})
|
||||
|
||||
(def light {:white "#ffffff"
|
||||
:black "#000000"
|
||||
|
@ -27,7 +28,8 @@
|
|||
:mentioned-background "#def6fc"
|
||||
:mentioned-border "#b8ecf9"
|
||||
:blue-light "#ECEFFC"
|
||||
:red "#ff2d55"})
|
||||
:red "#ff2d55"
|
||||
:pin-background "#FFEECC"})
|
||||
|
||||
(def themes {:dark dark :light light})
|
||||
|
||||
|
@ -79,6 +81,9 @@
|
|||
(def green "#44d058") ;; icon for successful inboud transaction
|
||||
(def green-transparent-10 (alpha green 0.1)) ;; icon for successful inboud transaction
|
||||
|
||||
;; YELLOW
|
||||
(def pin-background (:pin-background light)) ;; Light yellow, used as background for pinned messages
|
||||
|
||||
(def purple "#887af9")
|
||||
(def orange "#FE8F59")
|
||||
|
||||
|
@ -136,5 +141,6 @@
|
|||
(set! gray-transparent-40 (alpha gray 0.4))
|
||||
(set! green-transparent-10 (alpha green 0.1))
|
||||
(set! red-transparent-10 (alpha red 0.1))
|
||||
(set! blue-transparent-10 (alpha blue 0.1)))
|
||||
(set! blue-transparent-10 (alpha blue 0.1))
|
||||
(set! pin-background (:pin-background colors)))
|
||||
(reset! theme type)))
|
||||
|
|
|
@ -22,7 +22,8 @@
|
|||
[reagent.core :as reagent]
|
||||
[status-im.ui.screens.chat.components.reply :as components.reply]
|
||||
[status-im.ui.screens.chat.message.link-preview :as link-preview]
|
||||
[status-im.ui.screens.communities.icon :as communities.icon])
|
||||
[status-im.ui.screens.communities.icon :as communities.icon]
|
||||
[status-im.chat.models.pin-message :as models.pin-message])
|
||||
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
|
||||
|
||||
(defview mention-element [from]
|
||||
|
@ -34,7 +35,8 @@
|
|||
(defn message-timestamp
|
||||
([message]
|
||||
[message-timestamp message false])
|
||||
([{:keys [timestamp-str outgoing content outgoing-status edited-at]} justify-timestamp?]
|
||||
([{:keys [timestamp-str outgoing content outgoing-status pinned edited-at in-popover?]} justify-timestamp?]
|
||||
(when-not in-popover? ;; We keep track if showing this message in a list in pin-limit-popover
|
||||
[react/view (when justify-timestamp?
|
||||
{:align-self :flex-end
|
||||
:position :absolute
|
||||
|
@ -51,24 +53,24 @@
|
|||
:tiny-icons/tiny-pending)
|
||||
{:width 16
|
||||
:height 12
|
||||
:color colors/white
|
||||
:color (if pinned colors/gray colors/white)
|
||||
:accessibility-label (name outgoing-status)}])
|
||||
[react/text {:style (style/message-timestamp-text outgoing)}
|
||||
[react/text {:style (style/message-timestamp-text (and outgoing (not pinned)))}
|
||||
(str
|
||||
timestamp-str
|
||||
(when edited-at edited-at-text))]]))
|
||||
(when edited-at edited-at-text))]])))
|
||||
|
||||
(defview quoted-message
|
||||
[_ {:keys [from parsed-text image]} outgoing current-public-key public?]
|
||||
[_ {:keys [from parsed-text image]} outgoing current-public-key public? pinned]
|
||||
(letsubs [contact-name [:contacts/contact-name-by-identity from]]
|
||||
[react/view {:style (style/quoted-message-container outgoing)}
|
||||
[react/view {:style (style/quoted-message-container (and outgoing (not pinned)))}
|
||||
[react/view {:style style/quoted-message-author-container}
|
||||
[chat.utils/format-reply-author
|
||||
from
|
||||
contact-name
|
||||
current-public-key
|
||||
(partial style/quoted-message-author outgoing)
|
||||
outgoing]]
|
||||
(partial style/quoted-message-author (and outgoing (not pinned)))
|
||||
(and outgoing (not pinned))]]
|
||||
(if (and image
|
||||
;; Disabling images for public-chats
|
||||
(not public?))
|
||||
|
@ -77,11 +79,11 @@
|
|||
:background-color :black
|
||||
:border-radius 4}
|
||||
:source {:uri image}}]
|
||||
[react/text {:style (style/quoted-message-text outgoing)
|
||||
[react/text {:style (style/quoted-message-text (and outgoing (not pinned)))
|
||||
:number-of-lines 5}
|
||||
(components.reply/get-quoted-text-with-mentions parsed-text)])]))
|
||||
|
||||
(defn render-inline [message-text outgoing content-type acc {:keys [type literal destination]}]
|
||||
(defn render-inline [message-text outgoing pinned content-type acc {:keys [type literal destination]}]
|
||||
(case type
|
||||
""
|
||||
(conj acc literal)
|
||||
|
@ -93,22 +95,22 @@
|
|||
literal])
|
||||
|
||||
"emph"
|
||||
(conj acc [react/text-class (style/emph-style outgoing) literal])
|
||||
(conj acc [react/text-class (style/emph-style (and outgoing (not pinned))) literal])
|
||||
|
||||
"strong"
|
||||
(conj acc [react/text-class (style/strong-style outgoing) literal])
|
||||
(conj acc [react/text-class (style/strong-style (and outgoing (not pinned))) literal])
|
||||
|
||||
"strong-emph"
|
||||
(conj acc [quo/text (style/strong-emph-style outgoing) literal])
|
||||
(conj acc [quo/text (style/strong-emph-style (and outgoing (not pinned))) literal])
|
||||
|
||||
"del"
|
||||
(conj acc [react/text-class (style/strikethrough-style outgoing) literal])
|
||||
(conj acc [react/text-class (style/strikethrough-style (and outgoing (not pinned))) literal])
|
||||
|
||||
"link"
|
||||
(conj acc
|
||||
[react/text-class
|
||||
{:style
|
||||
{:color (if outgoing colors/white-persist colors/blue)
|
||||
{:color (if (and outgoing (not pinned)) colors/white-persist colors/blue)
|
||||
:text-decoration-line :underline}
|
||||
:on-press
|
||||
#(when (and (security/safe-link? destination)
|
||||
|
@ -121,14 +123,14 @@
|
|||
(conj acc [react/text-class
|
||||
{:style {:color (cond
|
||||
(= content-type constants/content-type-system-text) colors/black
|
||||
outgoing colors/mention-outgoing
|
||||
(and outgoing (not pinned)) colors/mention-outgoing
|
||||
:else colors/mention-incoming)}
|
||||
:on-press (when-not (= content-type constants/content-type-system-text)
|
||||
#(re-frame/dispatch [:chat.ui/show-profile literal]))}
|
||||
[mention-element literal]])
|
||||
"status-tag"
|
||||
(conj acc [react/text-class
|
||||
{:style {:color (if outgoing colors/white-persist colors/blue)
|
||||
{:style {:color (if (and outgoing (not pinned)) colors/white-persist colors/blue)
|
||||
:text-decoration-line :underline}
|
||||
:on-press
|
||||
#(re-frame/dispatch
|
||||
|
@ -138,19 +140,19 @@
|
|||
|
||||
(conj acc literal)))
|
||||
|
||||
(defn render-block [{:keys [content outgoing content-type]} acc
|
||||
(defn render-block [{:keys [content outgoing content-type pinned in-popover?]} acc
|
||||
{:keys [type ^js literal children]}]
|
||||
(case type
|
||||
|
||||
"paragraph"
|
||||
(conj acc (reduce
|
||||
(fn [acc e] (render-inline (:text content) outgoing content-type acc e))
|
||||
[react/text-class (style/text-style outgoing content-type)]
|
||||
(fn [acc e] (render-inline (:text content) outgoing pinned content-type acc e))
|
||||
[react/text-class (style/text-style (and outgoing (not pinned)) content-type in-popover?)]
|
||||
children))
|
||||
|
||||
"blockquote"
|
||||
(conj acc [react/view (style/blockquote-style outgoing)
|
||||
[react/text-class (style/blockquote-text-style outgoing)
|
||||
(conj acc [react/view (style/blockquote-style (and outgoing (not pinned)))
|
||||
[react/text-class (style/blockquote-text-style (and outgoing (not pinned)))
|
||||
(.substring literal 0 (dec (.-length literal)))]])
|
||||
|
||||
"codeblock"
|
||||
|
@ -165,10 +167,10 @@
|
|||
(defn render-parsed-text [message tree]
|
||||
(reduce (fn [acc e] (render-block message acc e)) [:<>] tree))
|
||||
|
||||
(defn render-parsed-text-with-timestamp [{:keys [timestamp-str outgoing edited-at] :as message} tree]
|
||||
(defn render-parsed-text-with-timestamp [{:keys [timestamp-str outgoing edited-at in-popover?] :as message} tree]
|
||||
(let [elements (render-parsed-text message tree)
|
||||
timestamp [react/text {:style (style/message-timestamp-placeholder)}
|
||||
(str (if outgoing " " " ") timestamp-str (when edited-at edited-at-text))]
|
||||
(str (if (and outgoing (not in-popover?)) " " " ") (when-not in-popover? (str timestamp-str (when edited-at edited-at-text))))]
|
||||
last-element (peek elements)]
|
||||
;; Using `nth` here as slightly faster than `first`, roughly 30%
|
||||
;; It's worth considering pure js structures for this code path as
|
||||
|
@ -204,6 +206,36 @@
|
|||
[react/view style/not-sent-icon
|
||||
[icons/icon :main-icons/warning {:color colors/red}]]]])
|
||||
|
||||
(defn pin-author-name [pinned-by]
|
||||
(let [user-contact @(re-frame/subscribe [:multiaccount/contact])
|
||||
contact-names @(re-frame/subscribe [:contacts/contact-two-names-by-identity pinned-by])]
|
||||
;; We append empty spaces to the name as a workaround to make one-line and multi-line label components show correctly
|
||||
(str " " (if (= pinned-by (user-contact :public-key)) (i18n/label :t/You) (first contact-names)))))
|
||||
|
||||
(def pin-icon-width 9)
|
||||
|
||||
(def pin-icon-height 15)
|
||||
|
||||
(defn pinned-by-indicator [outgoing display-photo? pinned-by]
|
||||
[react/view {:style (style/pin-indicator outgoing display-photo?)
|
||||
:accessibility-label :pinned-by}
|
||||
[react/view {:style (style/pinned-by-text-icon-container)}
|
||||
[react/view {:style (style/pin-icon-container)}
|
||||
[icons/icon :main-icons/pin {:color colors/gray
|
||||
:height pin-icon-height
|
||||
:width pin-icon-width
|
||||
:background-color :red}]]
|
||||
[quo/text {:weight :regular
|
||||
:size :small
|
||||
:color :main
|
||||
:style (style/pinned-by-text)}
|
||||
(i18n/label :t/pinned-by)]]
|
||||
[quo/text {:weight :medium
|
||||
:size :small
|
||||
:color :main
|
||||
:style (style/pin-author-text)}
|
||||
(pin-author-name pinned-by)]])
|
||||
|
||||
(defn message-delivery-status
|
||||
[{:keys [chat-id message-id outgoing-status message-type]}]
|
||||
(when (and (not= constants/message-type-private-group-system-message message-type)
|
||||
|
@ -253,7 +285,7 @@
|
|||
"Author, userpic and delivery wrapper"
|
||||
[{:keys [first-in-group? display-photo? display-username?
|
||||
identicon
|
||||
from outgoing]
|
||||
from outgoing in-popover?]
|
||||
:as message} content {:keys [modal close-modal]}]
|
||||
[react/view {:style (style/message-wrapper message)
|
||||
:pointer-events :box-none
|
||||
|
@ -266,9 +298,10 @@
|
|||
[react/touchable-highlight {:on-press #(do (when modal (close-modal))
|
||||
(re-frame/dispatch [:chat.ui/show-profile from]))}
|
||||
[photos/member-photo from identicon]])])
|
||||
[react/view {:style (style/message-author-wrapper outgoing display-photo?)}
|
||||
[react/view {:style (style/message-author-wrapper outgoing display-photo? in-popover?)}
|
||||
(when display-username?
|
||||
[react/touchable-opacity {:style style/message-author-touchable
|
||||
:disabled in-popover?
|
||||
:on-press #(do (when modal (close-modal))
|
||||
(re-frame/dispatch [:chat.ui/show-profile from]))}
|
||||
[message-author-name from {:modal modal}]])
|
||||
|
@ -290,7 +323,7 @@
|
|||
(when (not= (/ width k) (first @dimensions))
|
||||
(reset! dimensions [(/ width k) image-max-height]))))))
|
||||
|
||||
(defn message-content-image [{:keys [content outgoing] :as message} {:keys [on-long-press]}]
|
||||
(defn message-content-image [{:keys [content outgoing in-popover?] :as message} {:keys [on-long-press]}]
|
||||
(let [dimensions (reagent/atom [image-max-width image-max-height])
|
||||
visible (reagent/atom false)
|
||||
uri (:image content)]
|
||||
|
@ -307,7 +340,8 @@
|
|||
[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 :message-image}
|
||||
[react/image {:style (dissoc style-opts :outgoing)
|
||||
|
@ -335,24 +369,36 @@
|
|||
(def message-height-px 200)
|
||||
(def max-message-height-px 150)
|
||||
|
||||
(defn on-long-press-fn [on-long-press message content]
|
||||
(defn pin-message [{:keys [chat-id pinned] :as message}]
|
||||
(let [pinned-messages @(re-frame/subscribe [:chats/pinned chat-id])]
|
||||
(if (and (not pinned) (> (count pinned-messages) 2))
|
||||
(do
|
||||
(js/setTimeout (fn [] (re-frame/dispatch [:dismiss-keyboard])) 500)
|
||||
(re-frame/dispatch [:show-popover {:view :pin-limit
|
||||
:message message
|
||||
:prevent-closing? true}]))
|
||||
(re-frame/dispatch [::models.pin-message/send-pin-message (assoc message :pinned (not pinned))]))))
|
||||
|
||||
(defn on-long-press-fn [on-long-press {:keys [pinned message-pin-enabled outgoing edit-enabled show-input?] :as message} content]
|
||||
(on-long-press
|
||||
(concat
|
||||
(when (:outgoing message)
|
||||
(when (and outgoing edit-enabled)
|
||||
[{:on-press #(re-frame/dispatch [:chat.ui/edit-message message])
|
||||
:label (i18n/label :t/edit)}])
|
||||
(when (:show-input? message)
|
||||
(when show-input?
|
||||
[{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
|
||||
:label (i18n/label :t/message-reply)}])
|
||||
[{:on-press #(react/copy-to-clipboard
|
||||
(components.reply/get-quoted-text-with-mentions
|
||||
(get content :parsed-text)))
|
||||
:label (i18n/label :t/sharing-copy-to-clipboard)}])))
|
||||
: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))}]))))
|
||||
|
||||
(defn collapsible-text-message [{:keys [mentioned]} _]
|
||||
(let [collapsed? (reagent/atom false)
|
||||
collapsible? (reagent/atom false)]
|
||||
(fn [{:keys [content outgoing current-public-key public?] :as message} on-long-press modal]
|
||||
(fn [{:keys [content outgoing current-public-key public? pinned in-popover?] :as message} on-long-press modal]
|
||||
(let [max-height (when-not (or outgoing modal)
|
||||
(if @collapsible?
|
||||
(if @collapsed? message-height-px nil)
|
||||
|
@ -365,7 +411,8 @@
|
|||
(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)))})
|
||||
(on-long-press-fn on-long-press message content)))
|
||||
:disabled in-popover?})
|
||||
[react/view {:style (style/message-view message)}
|
||||
[react/view {:style (style/message-view-content)
|
||||
:max-height max-height}
|
||||
|
@ -378,13 +425,13 @@
|
|||
(reset! collapsed? true)
|
||||
(reset! collapsible? true))}
|
||||
(when (and (seq response-to) (:quoted-message message))
|
||||
[quoted-message response-to (:quoted-message message) outgoing current-public-key public?])
|
||||
[quoted-message response-to (:quoted-message message) outgoing current-public-key public? pinned])
|
||||
[render-parsed-text-with-timestamp message (:parsed-text content)]])
|
||||
(when-not @collapsed?
|
||||
[message-timestamp message true])
|
||||
(when (and @collapsible? (not modal))
|
||||
(if @collapsed?
|
||||
(let [color (if mentioned colors/mentioned-background colors/blue-light)]
|
||||
(let [color (if pinned colors/pin-background (if mentioned colors/mentioned-background colors/blue-light))]
|
||||
[react/touchable-highlight
|
||||
{:on-press #(swap! collapsed? not)
|
||||
:style {:position :absolute :bottom 0 :left 0 :right 0 :height 72}}
|
||||
|
@ -412,48 +459,53 @@
|
|||
[community-content message])
|
||||
|
||||
(defmethod ->message constants/content-type-status
|
||||
[{:keys [content content-type] :as message}]
|
||||
[{:keys [content content-type pinned] :as message}]
|
||||
[message-content-wrapper message
|
||||
[react/view style/status-container
|
||||
[react/text {:style (style/status-text)}
|
||||
(reduce
|
||||
(fn [acc e] (render-inline (:text content) false content-type acc e))
|
||||
(fn [acc e] (render-inline (:text content) false pinned content-type acc e))
|
||||
[react/text-class {:style (style/status-text)}]
|
||||
(-> content :parsed-text peek :children))]]])
|
||||
|
||||
(defmethod ->message constants/content-type-emoji
|
||||
[{:keys [content current-public-key outgoing public?] :as message} {:keys [on-long-press modal]
|
||||
[{:keys [content current-public-key outgoing public? pinned in-popover? message-pin-enabled] :as message} {:keys [on-long-press modal]
|
||||
:as reaction-picker}]
|
||||
(let [response-to (:response-to content)]
|
||||
[message-content-wrapper message
|
||||
[react/touchable-highlight (when-not modal
|
||||
{:on-press (fn []
|
||||
{:disabled in-popover?
|
||||
:on-press (fn []
|
||||
(react/dismiss-keyboard!))
|
||||
:on-long-press (fn []
|
||||
(on-long-press
|
||||
(concat
|
||||
[{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
|
||||
:label (i18n/label :t/message-reply)}
|
||||
{:on-press #(react/copy-to-clipboard (get content :text))
|
||||
:label (i18n/label :t/sharing-copy-to-clipboard)}]))})
|
||||
:label (i18n/label :t/sharing-copy-to-clipboard)}]
|
||||
(when message-pin-enabled [{:on-press #(pin-message message)
|
||||
:label (if pinned (i18n/label :t/unpin) (i18n/label :t/pin))}]))))})
|
||||
[react/view (style/message-view message)
|
||||
[react/view {:style (style/message-view-content)}
|
||||
[react/view {:style (style/style-message-text outgoing)}
|
||||
(when (and (seq response-to) (:quoted-message message))
|
||||
[quoted-message response-to (:quoted-message message) outgoing current-public-key public?])
|
||||
[quoted-message response-to (:quoted-message message) outgoing current-public-key public? pinned])
|
||||
[react/text {:style (style/emoji-message message)}
|
||||
(:text content)]]
|
||||
[message-timestamp message]]]]
|
||||
reaction-picker]))
|
||||
|
||||
(defmethod ->message constants/content-type-sticker
|
||||
[{:keys [content from outgoing]
|
||||
[{:keys [content from outgoing in-popover?]
|
||||
:as message}
|
||||
{:keys [on-long-press modal]
|
||||
:as reaction-picker}]
|
||||
(let [pack (get-in content [:sticker :pack])]
|
||||
[message-content-wrapper message
|
||||
[react/touchable-highlight (when-not modal
|
||||
{:accessibility-label :sticker-message
|
||||
{:disabled in-popover?
|
||||
:accessibility-label :sticker-message
|
||||
:on-press (fn [_]
|
||||
(when pack
|
||||
(re-frame/dispatch [:stickers/open-sticker-pack pack]))
|
||||
|
@ -469,10 +521,11 @@
|
|||
:source {:uri (contenthash/url (-> content :sticker :hash))}}]]
|
||||
reaction-picker]))
|
||||
|
||||
(defmethod ->message constants/content-type-image [{:keys [content] :as message} {:keys [on-long-press modal]
|
||||
(defmethod ->message constants/content-type-image [{:keys [content in-popover?] :as message} {:keys [on-long-press modal]
|
||||
:as reaction-picker}]
|
||||
[message-content-wrapper message
|
||||
[message-content-image message {:modal modal
|
||||
:disabled in-popover?
|
||||
:on-long-press (fn []
|
||||
(on-long-press
|
||||
[{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
|
||||
|
@ -496,7 +549,8 @@
|
|||
[message-content-wrapper message
|
||||
[unknown-content-type message]])
|
||||
|
||||
(defn chat-message [message space-keeper]
|
||||
(defn chat-message [{:keys [outgoing display-photo? pinned pinned-by] :as message} space-keeper]
|
||||
[:<>
|
||||
[reactions/with-reaction-picker
|
||||
{:message message
|
||||
:reactions @(re-frame/subscribe [:chats/message-reactions (:message-id message) (:chat-id message)])
|
||||
|
@ -513,4 +567,7 @@
|
|||
{:message-id (:message-id message)
|
||||
:emoji-id emoji-id
|
||||
:emoji-reaction-id emoji-reaction-id}]))
|
||||
:render ->message}])
|
||||
:render ->message}]
|
||||
(when pinned
|
||||
[react/view {:style (style/pin-indicator-container outgoing)}
|
||||
[pinned-by-indicator outgoing display-photo? pinned-by]])])
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
(ns status-im.ui.screens.chat.message.pinned-message
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.i18n.i18n :as i18n]
|
||||
[status-im.ui.components.colors :as colors]
|
||||
[status-im.ui.components.react :as react]
|
||||
[quo.core :as quo]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.chat.models.pin-message :as models.pin-message]
|
||||
[status-im.ui.components.list.views :as list]
|
||||
[status-im.ui.components.radio :as radio]
|
||||
[status-im.utils.handlers :refer [<sub]]
|
||||
[status-im.ui.screens.chat.message.message :as message]))
|
||||
|
||||
(def selected-unpin (reagent/atom nil))
|
||||
|
||||
(defn render-pin-fn [{:keys [message-id outgoing] :as message}
|
||||
_
|
||||
_
|
||||
{:keys [group-chat public? current-public-key space-keeper]}]
|
||||
[react/touchable-without-feedback {:style {:width "100%"}
|
||||
:on-press #(reset! selected-unpin message-id)}
|
||||
[react/view {:style {:flex-direction :row
|
||||
:align-items :center
|
||||
:justify-content :space-between
|
||||
:flex 1
|
||||
:padding-right 20}}
|
||||
[message/chat-message
|
||||
(assoc message
|
||||
:group-chat group-chat
|
||||
:public? public?
|
||||
:current-public-key current-public-key
|
||||
:show-input? false
|
||||
:pinned false
|
||||
:display-username? (not outgoing)
|
||||
:display-photo? false
|
||||
:last-in-group? false
|
||||
:in-popover? true)
|
||||
space-keeper]
|
||||
[react/view {:style {:position :absolute
|
||||
:right 18
|
||||
:padding-top 4}}
|
||||
[radio/radio (= @selected-unpin message-id)]]]])
|
||||
|
||||
(def list-key-fn #(or (:message-id %) (:value %)))
|
||||
|
||||
(defn pinned-messages-limit-list [chat-id]
|
||||
(let [pinned-messages @(re-frame/subscribe [:chats/pinned chat-id])]
|
||||
[list/flat-list
|
||||
{:key-fn list-key-fn
|
||||
:data (reverse (vals pinned-messages))
|
||||
:render-data {:chat-id chat-id}
|
||||
:render-fn render-pin-fn
|
||||
:on-scroll-to-index-failed identity
|
||||
:style {:flex-grow 0
|
||||
:border-top-width 1
|
||||
:border-bottom-width 1
|
||||
:border-top-color colors/gray-lighter
|
||||
:border-bottom-color colors/gray-lighter}
|
||||
:content-container-style {:padding-bottom 10
|
||||
:padding-top 10}}]))
|
||||
|
||||
(defn pin-limit-popover []
|
||||
(let [{:keys [message]} (<sub [:popover/popover])]
|
||||
[react/view
|
||||
[react/view {:style {:height 60
|
||||
:justify-content :center}}
|
||||
[react/text {:style {:padding-horizontal 40
|
||||
:text-align :center}}
|
||||
(i18n/label :t/pin-limit-reached)]]
|
||||
[pinned-messages-limit-list (message :chat-id)]
|
||||
[react/view {:flex-direction :row :padding-horizontal 16 :height 60 :justify-content :space-between :align-items :center}
|
||||
[quo/button
|
||||
{:on-press #(do
|
||||
(reset! selected-unpin nil)
|
||||
(re-frame/dispatch [:hide-popover]))
|
||||
:type :secondary}
|
||||
(i18n/label :t/cancel)]
|
||||
[quo/button
|
||||
{:on-press #(do
|
||||
(re-frame/dispatch [::models.pin-message/send-pin-message {:chat-id (message :chat-id)
|
||||
:message-id @selected-unpin
|
||||
:pinned false}])
|
||||
(re-frame/dispatch [::models.pin-message/send-pin-message (assoc message :pinned true)])
|
||||
(re-frame/dispatch [:hide-popover])
|
||||
(reset! selected-unpin nil))
|
||||
:type :secondary
|
||||
:disabled (nil? @selected-unpin)
|
||||
:theme (if (nil? @selected-unpin) :disabled :negative)}
|
||||
(i18n/label :t/unpin)]]]))
|
|
@ -0,0 +1,95 @@
|
|||
(ns status-im.ui.screens.chat.pinned-messages
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.i18n.i18n :as i18n]
|
||||
[status-im.ui.components.connectivity.view :as connectivity]
|
||||
[status-im.ui.components.react :as react]
|
||||
[quo.animated :as animated]
|
||||
[status-im.ui.screens.chat.styles.main :as style]
|
||||
[status-im.ui.screens.chat.components.accessory :as accessory]
|
||||
[status-im.utils.platform :as platform]
|
||||
[quo.react :as quo.react]
|
||||
[status-im.ui.components.topbar :as topbar]
|
||||
[status-im.ui.screens.chat.views :as chat]
|
||||
[status-im.ui.components.list.views :as list]))
|
||||
|
||||
(defn pins-topbar []
|
||||
(let [{:keys [group-chat chat-id chat-name]}
|
||||
@(re-frame/subscribe [:chats/current-chat])
|
||||
pinned-messages @(re-frame/subscribe [:chats/pinned chat-id])
|
||||
[first-name _] (when-not group-chat @(re-frame.core/subscribe [:contacts/contact-two-names-by-identity chat-id]))]
|
||||
[topbar/topbar {:show-border? true
|
||||
:title (if group-chat chat-name first-name)
|
||||
:subtitle (if (= (count pinned-messages) 0)
|
||||
(i18n/label :t/no-pinned-messages)
|
||||
(i18n/label-pluralize (count pinned-messages) :t/pinned-messages-count))}]))
|
||||
|
||||
(defn get-space-keeper-ios [bottom-space panel-space active-panel text-input-ref]
|
||||
(fn [state]
|
||||
;; NOTE: Only iOS now because we use soft input resize screen on android
|
||||
(when platform/ios?
|
||||
(cond
|
||||
(and state
|
||||
(< @bottom-space @panel-space)
|
||||
(not @active-panel))
|
||||
(reset! bottom-space @panel-space)
|
||||
|
||||
(and (not state)
|
||||
(< @panel-space @bottom-space))
|
||||
(do
|
||||
(some-> ^js (quo.react/current-ref text-input-ref) .focus)
|
||||
(reset! panel-space @bottom-space)
|
||||
(reset! bottom-space 0))))))
|
||||
|
||||
(defn pinned-messages-empty []
|
||||
[react/view {:style {:flex 1
|
||||
:align-items :center
|
||||
:justify-content :center}}
|
||||
[react/text {:style style/intro-header-description}
|
||||
(i18n/label :t/pinned-messages-empty)]])
|
||||
|
||||
(defonce messages-list-ref (atom nil))
|
||||
|
||||
(def list-ref #(reset! messages-list-ref %))
|
||||
|
||||
(defn pinned-messages-view [{:keys [chat pan-responder space-keeper]}]
|
||||
(let [{:keys [group-chat chat-id public? community-id admins]} chat
|
||||
pinned-messages @(re-frame/subscribe [:chats/raw-chat-pin-messages-stream chat-id])]
|
||||
(if (= (count pinned-messages) 0)
|
||||
[pinned-messages-empty]
|
||||
;;do not use anonymous functions for handlers
|
||||
[list/flat-list
|
||||
(merge
|
||||
pan-responder
|
||||
{:key-fn chat/list-key-fn
|
||||
:ref list-ref
|
||||
:data (reverse pinned-messages)
|
||||
:render-data (chat/get-render-data {:group-chat group-chat
|
||||
:chat-id chat-id
|
||||
:public? public?
|
||||
:community-id community-id
|
||||
:admins admins
|
||||
:space-keeper space-keeper
|
||||
:show-input? false
|
||||
:edit-enabled false
|
||||
:in-pinned-view? true})
|
||||
:render-fn chat/render-fn
|
||||
:content-container-style {:padding-top 16
|
||||
:padding-bottom 16}})])))
|
||||
|
||||
(defn pinned-messages []
|
||||
(let [bottom-space (reagent/atom 0)
|
||||
panel-space (reagent/atom 52)
|
||||
active-panel (reagent/atom nil)
|
||||
position-y (animated/value 0)
|
||||
pan-state (animated/value 0)
|
||||
text-input-ref (quo.react/create-ref)
|
||||
pan-responder (accessory/create-pan-responder position-y pan-state)
|
||||
space-keeper (get-space-keeper-ios bottom-space panel-space active-panel text-input-ref)
|
||||
chat @(re-frame/subscribe [:chats/current-chat-chat-view])]
|
||||
[:<>
|
||||
[pins-topbar]
|
||||
[connectivity/loading-indicator]
|
||||
[pinned-messages-view {:chat chat
|
||||
:pan-responder pan-responder
|
||||
:space-keeper space-keeper}]]))
|
|
@ -8,7 +8,8 @@
|
|||
[status-im.ui.components.chat-icon.screen :as chat-icon]
|
||||
[status-im.multiaccounts.core :as multiaccounts]
|
||||
[status-im.ui.screens.chat.styles.message.sheets :as sheets.styles]
|
||||
[quo.core :as quo]))
|
||||
[quo.core :as quo]
|
||||
[status-im.chat.models.pin-message :as models.pin-message]))
|
||||
|
||||
(defn hide-sheet-and-dispatch [event]
|
||||
(re-frame/dispatch [:bottom-sheet/hide])
|
||||
|
@ -25,7 +26,9 @@
|
|||
:subtitle (i18n/label :t/view-profile)
|
||||
:accessibility-label :view-chat-details-button
|
||||
:chevron true
|
||||
:on-press #(hide-sheet-and-dispatch [:chat.ui/show-profile chat-id])}]
|
||||
:on-press #(do
|
||||
(hide-sheet-and-dispatch [:chat.ui/show-profile chat-id])
|
||||
(re-frame/dispatch [::models.pin-message/load-pin-messages chat-id]))}]
|
||||
[quo/list-item
|
||||
{:theme :accent
|
||||
:title (i18n/label :t/mark-all-read)
|
||||
|
@ -91,6 +94,12 @@
|
|||
:chevron true
|
||||
:accessibility-label :view-community-channel-details
|
||||
:on-press #(hide-sheet-and-dispatch [:navigate-to :community-channel-details {:chat-id chat-id}])}]
|
||||
[quo/list-item
|
||||
{:theme :accent
|
||||
:title (i18n/label :t/pinned-messages)
|
||||
:icon :main-icons/pin
|
||||
:accessory :text
|
||||
:on-press #(hide-sheet-and-dispatch [:contact.ui/pinned-messages-pressed chat-id])}]
|
||||
[quo/list-item
|
||||
{:theme :accent
|
||||
:title (i18n/label :t/mark-all-read)
|
||||
|
@ -123,7 +132,9 @@
|
|||
:icon [chat-icon/chat-icon-view-chat-sheet
|
||||
chat-id group-chat chat-name color]
|
||||
:chevron true
|
||||
:on-press #(hide-sheet-and-dispatch [:show-group-chat-profile chat-id])}]
|
||||
:on-press #(do
|
||||
(hide-sheet-and-dispatch [:show-group-chat-profile chat-id])
|
||||
(re-frame/dispatch [::models.pin-message/load-pin-messages chat-id]))}]
|
||||
[quo/list-item
|
||||
{:theme :accent
|
||||
:title (i18n/label :t/mark-all-read)
|
||||
|
|
|
@ -6,6 +6,11 @@
|
|||
:align-items :center
|
||||
:flex-direction :row})
|
||||
|
||||
(def pins-name-view
|
||||
{:flex 1
|
||||
:justify-content :center
|
||||
:align-items :center})
|
||||
|
||||
(def chat-name-view
|
||||
{:flex 1
|
||||
:justify-content :center})
|
||||
|
|
|
@ -42,18 +42,18 @@
|
|||
colors/white-transparent-70-persist
|
||||
colors/gray)}))
|
||||
|
||||
(defn message-wrapper [{:keys [outgoing]}]
|
||||
(if outgoing
|
||||
(defn message-wrapper [{:keys [outgoing in-popover?]}]
|
||||
(if (and outgoing (not in-popover?))
|
||||
{:margin-left 96}
|
||||
{:margin-right 52}))
|
||||
|
||||
(defn message-author-wrapper
|
||||
[outgoing display-photo?]
|
||||
(let [align (if outgoing :flex-end :flex-start)]
|
||||
[outgoing display-photo? in-popover?]
|
||||
(let [align (if (and outgoing (not in-popover?)) :flex-end :flex-start)]
|
||||
(merge {:flex-direction :column
|
||||
:flex-shrink 1
|
||||
:align-items align}
|
||||
(if outgoing
|
||||
(if (and outgoing (not in-popover?))
|
||||
{:margin-right 8}
|
||||
(when-not display-photo?
|
||||
{:margin-left 8})))))
|
||||
|
@ -65,6 +65,64 @@
|
|||
{:align-self :flex-start
|
||||
:padding-left 8}))
|
||||
|
||||
(defn pin-indicator [outgoing display-photo?]
|
||||
(merge
|
||||
{:flex-direction :row
|
||||
:border-top-left-radius (if outgoing 12 4)
|
||||
:border-top-right-radius (if outgoing 4 12)
|
||||
:border-bottom-left-radius 12
|
||||
:border-bottom-right-radius 12
|
||||
:padding-left 8
|
||||
:padding-right 10
|
||||
:padding-vertical 5
|
||||
:background-color colors/gray-lighter
|
||||
:justify-content :center
|
||||
:max-width "80%"}
|
||||
(if outgoing
|
||||
{:align-self :flex-end
|
||||
:align-items :flex-end}
|
||||
{:align-self :flex-start
|
||||
:align-items :flex-start})
|
||||
(when display-photo?
|
||||
{:margin-left 44})))
|
||||
|
||||
(defn pin-indicator-container [outgoing]
|
||||
(merge
|
||||
{:margin-top 2
|
||||
:align-items :center
|
||||
:justify-content :center}
|
||||
(if outgoing
|
||||
{:align-self :flex-end
|
||||
:align-items :flex-end
|
||||
:padding-right 8}
|
||||
{:align-self :flex-start
|
||||
:align-items :flex-start
|
||||
:padding-left 8})))
|
||||
|
||||
(defn pinned-by-text-icon-container []
|
||||
{:flex-direction :row
|
||||
:align-items :flex-start
|
||||
:top 5
|
||||
:left 8
|
||||
:position :absolute})
|
||||
|
||||
(defn pin-icon-container []
|
||||
{:flex-direction :row
|
||||
:margin-top 1})
|
||||
|
||||
(defn pin-author-text []
|
||||
{:margin-left 2
|
||||
:margin-right 12
|
||||
:padding-right 0
|
||||
:left 12
|
||||
:flex-direction :row
|
||||
:flex-shrink 1
|
||||
:align-self :flex-start
|
||||
:overflow :hidden})
|
||||
|
||||
(defn pinned-by-text []
|
||||
{:margin-left 5})
|
||||
|
||||
(def message-author-touchable
|
||||
{:margin-left 12
|
||||
:flex-direction :row})
|
||||
|
@ -115,7 +173,7 @@
|
|||
:shadow-offset {:width 0 :height 4}})
|
||||
|
||||
(defn message-view
|
||||
[{:keys [content-type outgoing group-chat last-in-group? mentioned]}]
|
||||
[{:keys [content-type outgoing group-chat last-in-group? mentioned pinned]}]
|
||||
(merge
|
||||
{:border-top-left-radius 16
|
||||
:border-top-right-radius 16
|
||||
|
@ -134,6 +192,7 @@
|
|||
{:border-bottom-left-radius 4})
|
||||
|
||||
(cond
|
||||
pinned {:background-color colors/pin-background}
|
||||
(= content-type constants/content-type-system-text) nil
|
||||
outgoing {:background-color colors/blue}
|
||||
mentioned {:background-color colors/mentioned-background
|
||||
|
@ -214,11 +273,13 @@
|
|||
:text-align :center
|
||||
:font-weight "400"))
|
||||
|
||||
(defn text-style [outgoing content-type]
|
||||
(defn text-style [outgoing content-type in-popover?]
|
||||
(merge
|
||||
(when in-popover? {:number-of-lines 2})
|
||||
(cond
|
||||
(= content-type constants/content-type-system-text) (system-text-style)
|
||||
outgoing (outgoing-text-style)
|
||||
:else (default-text-style)))
|
||||
:else (default-text-style))))
|
||||
|
||||
(defn emph-text-style []
|
||||
(update (default-text-style) :style
|
||||
|
|
|
@ -239,8 +239,8 @@
|
|||
(defn render-fn [{:keys [outgoing type] :as message}
|
||||
idx
|
||||
_
|
||||
{:keys [group-chat public? current-public-key space-keeper chat-id show-input?]}]
|
||||
[react/view {:style (when platform/android? {:scaleY -1})}
|
||||
{:keys [group-chat public? current-public-key space-keeper chat-id show-input? message-pin-enabled edit-enabled in-pinned-view?]}]
|
||||
[react/view {:style (when (and platform/android? (not in-pinned-view?)) {:scaleY -1})}
|
||||
(if (= type :datemark)
|
||||
[message-datemark/chat-datemark (:value message)]
|
||||
(if (= type :gap)
|
||||
|
@ -252,7 +252,9 @@
|
|||
:group-chat group-chat
|
||||
:public? public?
|
||||
:current-public-key current-public-key
|
||||
:show-input? show-input?)
|
||||
:show-input? show-input?
|
||||
:message-pin-enabled message-pin-enabled
|
||||
:edit-enabled edit-enabled)
|
||||
space-keeper]))])
|
||||
|
||||
(def list-key-fn #(or (:message-id %) (:value %)))
|
||||
|
@ -267,10 +269,29 @@
|
|||
(utils/set-timeout #(re-frame/dispatch [:chat.ui/load-more-messages-for-current-chat])
|
||||
(if platform/low-device? 700 200))))
|
||||
|
||||
(defn get-render-data [{:keys [group-chat chat-id public? community-id admins space-keeper show-input? edit-enabled in-pinned-view?]}]
|
||||
(let [current-public-key @(re-frame/subscribe [:multiaccount/public-key])
|
||||
community @(re-frame/subscribe [:communities/community community-id])
|
||||
group-admin? (get admins current-public-key)
|
||||
community-admin? (when community (community :admin))
|
||||
message-pin-enabled (and (not public?)
|
||||
(or (not group-chat)
|
||||
(and group-chat
|
||||
(or group-admin?
|
||||
community-admin?))))]
|
||||
{:group-chat group-chat
|
||||
:public? public?
|
||||
:current-public-key current-public-key
|
||||
:space-keeper space-keeper
|
||||
:chat-id chat-id
|
||||
:show-input? show-input?
|
||||
:message-pin-enabled message-pin-enabled
|
||||
:edit-enabled edit-enabled
|
||||
:in-pinned-view? in-pinned-view?}))
|
||||
|
||||
(defn messages-view [{:keys [chat bottom-space pan-responder space-keeper show-input?]}]
|
||||
(let [{:keys [group-chat chat-id public?]} chat
|
||||
messages @(re-frame/subscribe [:chats/chat-messages-stream chat-id])
|
||||
current-public-key @(re-frame/subscribe [:multiaccount/public-key])]
|
||||
(let [{:keys [group-chat chat-id public? community-id admins]} chat
|
||||
messages @(re-frame/subscribe [:chats/chat-messages-stream chat-id])]
|
||||
;;do not use anonymous functions for handlers
|
||||
[list/flat-list
|
||||
(merge
|
||||
|
@ -280,12 +301,15 @@
|
|||
:header [list-header chat]
|
||||
:footer [list-footer chat]
|
||||
:data messages
|
||||
:render-data {:group-chat group-chat
|
||||
:public? public?
|
||||
:current-public-key current-public-key
|
||||
:space-keeper space-keeper
|
||||
:render-data (get-render-data {:group-chat group-chat
|
||||
:chat-id chat-id
|
||||
:show-input? show-input?}
|
||||
:public? public?
|
||||
:community-id community-id
|
||||
:admins admins
|
||||
:space-keeper space-keeper
|
||||
:show-input? show-input?
|
||||
:edit-enabled true
|
||||
:in-pinned-view? false})
|
||||
:render-fn render-fn
|
||||
:on-viewable-items-changed on-viewable-items-changed
|
||||
:on-end-reached list-on-end-reached
|
||||
|
|
|
@ -19,7 +19,8 @@
|
|||
[status-im.ui.screens.biometric.views :as biometric]
|
||||
[status-im.ui.components.colors :as colors]
|
||||
[status-im.ui.screens.keycard.views :as keycard.views]
|
||||
[status-im.ui.screens.keycard.frozen-card.view :as frozen-card]))
|
||||
[status-im.ui.screens.keycard.frozen-card.view :as frozen-card]
|
||||
[status-im.ui.screens.chat.message.pinned-message :as pinned-message]))
|
||||
|
||||
(defn hide-panel-anim
|
||||
[bottom-anim-value alpha-value window-height]
|
||||
|
@ -173,6 +174,9 @@
|
|||
(= :password-reset-success view)
|
||||
[reset-password.views/reset-success-popover]
|
||||
|
||||
(= :pin-limit view)
|
||||
[pinned-message/pin-limit-popover]
|
||||
|
||||
:else
|
||||
[view])]]]]])))})))
|
||||
|
||||
|
|
|
@ -83,6 +83,16 @@
|
|||
(i18n/label :t/profile-details)]]
|
||||
[render-detail contact]]))
|
||||
|
||||
(defn pin-settings [public-key pin-count]
|
||||
[quo/list-item
|
||||
{:title (i18n/label :t/pinned-messages)
|
||||
:size :small
|
||||
:accessibility-label :profile-nickname-item
|
||||
:accessory :text
|
||||
:accessory-text pin-count
|
||||
:on-press #(re-frame/dispatch [:contact.ui/pinned-messages-pressed public-key])
|
||||
:chevron true}])
|
||||
|
||||
(defn nickname-settings [{:keys [names]}]
|
||||
[quo/list-item
|
||||
{:title (i18n/label :t/nickname)
|
||||
|
@ -162,6 +172,7 @@
|
|||
messages @(re-frame/subscribe [:chats/profile-messages-stream current-chat-id])
|
||||
no-messages? @(re-frame/subscribe [:chats/chat-no-messages? current-chat-id])
|
||||
muted? @(re-frame/subscribe [:chats/muted public-key])
|
||||
pinned-messages @(re-frame/subscribe [:chats/pinned public-key])
|
||||
[first-name second-name] (multiaccounts/contact-two-names contact true)
|
||||
on-share #(re-frame/dispatch [:show-popover (merge
|
||||
{:view :share-chat-key
|
||||
|
@ -188,6 +199,7 @@
|
|||
:subtitle second-name})]
|
||||
[react/view {:height 1 :background-color colors/gray-lighter :margin-top 8}]
|
||||
[nickname-settings contact]
|
||||
[pin-settings public-key (count pinned-messages)]
|
||||
[react/view {:height 1 :background-color colors/gray-lighter}]
|
||||
[react/view {:padding-top 17 :flex-direction :row :align-items :stretch :flex 1}
|
||||
(for [{:keys [label] :as action} (actions contact muted?)
|
||||
|
|
|
@ -182,7 +182,8 @@
|
|||
(defview group-chat-profile []
|
||||
(letsubs [{:keys [admins chat-id joined? chat-name color contacts] :as current-chat} [:chats/current-chat]
|
||||
members [:contacts/current-chat-contacts]
|
||||
current-pk [:multiaccount/public-key]]
|
||||
current-pk [:multiaccount/public-key]
|
||||
pinned-messages [:chats/pinned chat-id]]
|
||||
(when current-chat
|
||||
(let [admin? (get admins current-pk)
|
||||
allow-adding-members? (and admin? joined?
|
||||
|
@ -215,6 +216,13 @@
|
|||
(when (pos? invitations)
|
||||
[components.common/counter {:size 22} invitations]))
|
||||
:on-press #(re-frame/dispatch [:navigate-to :group-chat-invite])}])
|
||||
[quo/list-item
|
||||
{:title (i18n/label :t/pinned-messages)
|
||||
:icon :main-icons/pin
|
||||
:accessory :text
|
||||
:accessory-text (count pinned-messages)
|
||||
:chevron true
|
||||
:on-press #(re-frame/dispatch [:contact.ui/pinned-messages-pressed chat-id])}]
|
||||
(when joined?
|
||||
[quo/list-item
|
||||
{:theme :negative
|
||||
|
|
|
@ -105,7 +105,8 @@
|
|||
[status-im.ui.screens.communities.edit-channel :as edit-channel]
|
||||
[status-im.ui.screens.anonymous-metrics-settings.views :as anonymous-metrics-settings]
|
||||
[status-im.ui.components.colors :as colors]
|
||||
[status-im.ui.components.icons.icons :as icons]))
|
||||
[status-im.ui.components.icons.icons :as icons]
|
||||
[status-im.ui.screens.chat.pinned-messages :as pin-messages]))
|
||||
|
||||
(def components
|
||||
[{:name :chat-toolbar
|
||||
|
@ -212,6 +213,12 @@
|
|||
:right-handler chat/topbar-button
|
||||
:component chat/chat}
|
||||
|
||||
;Pinned messages
|
||||
{:name :chat-pinned-messages
|
||||
;TODO custom subtitle
|
||||
:options {:topBar {:visible false}}
|
||||
:component pin-messages/pinned-messages}
|
||||
|
||||
{:name :group-chat-profile
|
||||
:insets {:top false}
|
||||
;;TODO animated-header
|
||||
|
|
|
@ -1595,5 +1595,16 @@
|
|||
"status-is-open-source": "Status is open-source",
|
||||
"build-yourself": "To use the app without these Terms of Service, you can build your own version",
|
||||
"accept-and-continue": "Accept and continue",
|
||||
"empty-activity-center": "Your chat notifications\nwill appear here"
|
||||
"empty-activity-center": "Your chat notifications\nwill appear here",
|
||||
"pinned-messages": "Pinned messages",
|
||||
"pin": "Pin",
|
||||
"unpin": "Unpin",
|
||||
"no-pinned-messages": "No pinned messages",
|
||||
"pinned-messages-count": {
|
||||
"one": "1 pinned message",
|
||||
"other": "{{count}} pinned messages"
|
||||
},
|
||||
"pinned-messages-empty": "Pinned messages will appear here. To pin a message, press and hold it and tap `Pin`",
|
||||
"pinned-by": "Pinned by",
|
||||
"pin-limit-reached": "Pin limit reached. Unpin a previous message first."
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue