Refactored message data-model and view
This commit is contained in:
parent
df21ec8c3a
commit
eb8d0a8a79
|
@ -3,7 +3,6 @@
|
|||
(def command-char "/")
|
||||
(def spacing-char " ")
|
||||
(def arg-wrapping-char "\"")
|
||||
(def bot-char "@")
|
||||
|
||||
(def input-height 56)
|
||||
(def max-input-height 66)
|
||||
|
|
|
@ -40,16 +40,6 @@
|
|||
(fn [cofx _]
|
||||
(assoc cofx :get-stored-messages msg-store/get-by-chat-id)))
|
||||
|
||||
(re-frame/reg-cofx
|
||||
:get-last-stored-message
|
||||
(fn [cofx _]
|
||||
(assoc cofx :get-last-stored-message msg-store/get-last-message)))
|
||||
|
||||
(re-frame/reg-cofx
|
||||
:get-message-previews
|
||||
(fn [cofx _]
|
||||
(assoc cofx :message-previews (msg-store/get-previews))))
|
||||
|
||||
(re-frame/reg-cofx
|
||||
:all-stored-chats
|
||||
(fn [cofx _]
|
||||
|
@ -120,38 +110,25 @@
|
|||
:show-emoji? false
|
||||
:bottom-info details})))
|
||||
|
||||
(def index-messages (partial into {} (map (juxt :message-id identity))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:load-more-messages
|
||||
[(re-frame/inject-cofx :get-stored-messages)]
|
||||
(fn [{{:keys [current-chat-id loading-allowed] :as db} :db
|
||||
get-stored-messages :get-stored-messages} _]
|
||||
(let [all-loaded? (get-in db [:chats current-chat-id :all-loaded?])]
|
||||
(if (and loading-allowed (not all-loaded?))
|
||||
(let [messages-path [:chats current-chat-id :messages]
|
||||
messages (get-in db messages-path)
|
||||
chat-messages (filter #(= current-chat-id (:chat-id %)) messages)
|
||||
new-messages (get-stored-messages current-chat-id (count chat-messages))
|
||||
all-loaded? (> const/default-number-of-messages (count new-messages))]
|
||||
{:db (-> db
|
||||
(assoc :loading-allowed false)
|
||||
(update-in messages-path concat new-messages)
|
||||
(assoc-in [:chats current-chat-id :all-loaded?] all-loaded?))
|
||||
;; we permit loading more messages again after 400ms
|
||||
:dispatch-later [{:ms 400 :dispatch [:set :loading-allowed true]}]})
|
||||
{:db db}))))
|
||||
(fn [{{:keys [current-chat-id] :as db} :db get-stored-messages :get-stored-messages} _]
|
||||
(when-not (get-in db [:chats current-chat-id :all-loaded?])
|
||||
(let [loaded-count (count (get-in db [:chats current-chat-id :messages]))
|
||||
new-messages (get-stored-messages current-chat-id loaded-count)]
|
||||
{:db (-> db
|
||||
(update-in [:chats current-chat-id :messages] merge (index-messages new-messages))
|
||||
(assoc-in [:chats current-chat-id :all-loaded?]
|
||||
(> const/default-number-of-messages (count new-messages))))}))))
|
||||
|
||||
(handlers/register-handler-db
|
||||
:set-message-shown
|
||||
[re-frame/trim-v]
|
||||
(fn [db [{:keys [chat-id message-id]}]]
|
||||
(update-in db
|
||||
[:chats chat-id :messages]
|
||||
(fn [messages]
|
||||
(map (fn [message]
|
||||
(if (= message-id (:message-id message))
|
||||
(assoc message :new? false)
|
||||
message))
|
||||
messages)))))
|
||||
(update-in db [:chats chat-id :messages message-id] assoc :new? false)))
|
||||
|
||||
(defn init-console-chat
|
||||
[{:keys [chats] :accounts/keys [current-account-id] :as db}]
|
||||
|
@ -165,7 +142,7 @@
|
|||
:save-all-contacts [sign-up/console-contact]}
|
||||
|
||||
(not current-account-id)
|
||||
(update :dispatch-n concat sign-up/intro-events))))
|
||||
(update :dispatch-n conj sign-up/intro-event))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:init-console-chat
|
||||
|
@ -175,37 +152,36 @@
|
|||
(handlers/register-handler-fx
|
||||
:initialize-chats
|
||||
[(re-frame/inject-cofx :all-stored-chats)
|
||||
(re-frame/inject-cofx :get-stored-messages)
|
||||
(re-frame/inject-cofx :stored-unviewed-messages)
|
||||
(re-frame/inject-cofx :get-stored-unanswered-requests)
|
||||
(re-frame/inject-cofx :get-last-stored-message)
|
||||
(re-frame/inject-cofx :get-message-previews)]
|
||||
(re-frame/inject-cofx :get-stored-unanswered-requests)]
|
||||
(fn [{:keys [db
|
||||
all-stored-chats
|
||||
stored-unanswered-requests
|
||||
stored-unviewed-messages
|
||||
get-last-stored-message
|
||||
message-previews]} _]
|
||||
(let [{:accounts/keys [account-creation?] :contacts/keys [contacts]} db
|
||||
new-db (unviewed-messages-model/load-unviewed-messages db stored-unviewed-messages)
|
||||
event [:load-default-contacts!]]
|
||||
get-stored-messages
|
||||
stored-unviewed-messages]} _]
|
||||
(let [{:accounts/keys [account-creation?]} db
|
||||
load-default-contacts-event [:load-default-contacts!]]
|
||||
(if account-creation?
|
||||
{:db new-db
|
||||
:dispatch event}
|
||||
(let [chat->message-id->request (reduce (fn [acc {:keys [chat-id message-id] :as request}]
|
||||
{:db db
|
||||
:dispatch load-default-contacts-event}
|
||||
(let [chat->unviewed-messages (unviewed-messages-model/index-unviewed-messages stored-unviewed-messages)
|
||||
chat->message-id->request (reduce (fn [acc {:keys [chat-id message-id] :as request}]
|
||||
(assoc-in acc [chat-id message-id] request))
|
||||
{}
|
||||
stored-unanswered-requests)
|
||||
chats (->> all-stored-chats
|
||||
(map (fn [{:keys [chat-id] :as chat}]
|
||||
[chat-id (assoc chat
|
||||
:last-message (get-last-stored-message chat-id)
|
||||
:requests (get chat->message-id->request chat-id))]))
|
||||
(into {}))]
|
||||
(-> new-db
|
||||
(assoc-in [:message-data :preview] message-previews)
|
||||
chats (reduce (fn [acc {:keys [chat-id] :as chat}]
|
||||
(assoc acc chat-id
|
||||
(assoc chat
|
||||
:unviewed-messages (get chat->unviewed-messages chat-id)
|
||||
:requests (get chat->message-id->request chat-id)
|
||||
:messages (index-messages (get-stored-messages chat-id)))))
|
||||
{}
|
||||
all-stored-chats)]
|
||||
(-> db
|
||||
(assoc :chats chats)
|
||||
init-console-chat
|
||||
(update :dispatch-n conj event)))))))
|
||||
(update :dispatch-n conj load-default-contacts-event)))))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:send-seen!
|
||||
|
@ -214,7 +190,9 @@
|
|||
(let [{:keys [web3 current-public-key chats]
|
||||
:contacts/keys [contacts]} db
|
||||
{:keys [group-chat public?]} (get chats chat-id)]
|
||||
(cond-> {:db (unviewed-messages-model/remove-unviewed-messages db chat-id)
|
||||
(cond-> {:db (-> db
|
||||
(unviewed-messages-model/remove-unviewed-message chat-id message-id)
|
||||
(assoc-in [:chats chat-id :messages message-id :message-status] :seen))
|
||||
:update-message {:message-id message-id
|
||||
:message-status :seen}}
|
||||
(and (not (get-in contacts [chat-id] :dapp?))
|
||||
|
@ -256,7 +234,7 @@
|
|||
|
||||
(defn preload-chat-data
|
||||
"Takes coeffects map and chat-id, returns effects necessary when navigating to chat"
|
||||
[{:keys [db get-stored-messages]} chat-id]
|
||||
[{:keys [db]} chat-id]
|
||||
(let [messages (get-in db [:chats chat-id :messages])
|
||||
chat-loaded-event (get-in db [:chats chat-id :chat-loaded-event])
|
||||
jail-loaded? (get-in db [:contacts/contacts chat-id :jail-loaded?])]
|
||||
|
@ -266,9 +244,6 @@
|
|||
(model/set-chat-ui-props {:validation-messages nil})
|
||||
(update-in [:chats chat-id] dissoc :chat-loaded-event))}
|
||||
|
||||
(empty? messages)
|
||||
(assoc-in [:db :chats chat-id :messages] (get-stored-messages chat-id))
|
||||
|
||||
chat-loaded-event
|
||||
(assoc :dispatch chat-loaded-event))))
|
||||
|
||||
|
@ -301,14 +276,13 @@
|
|||
|
||||
(handlers/register-handler-fx
|
||||
:navigate-to-chat
|
||||
[(re-frame/inject-cofx :get-stored-messages) re-frame/trim-v]
|
||||
[re-frame/trim-v]
|
||||
(fn [cofx [chat-id {:keys [navigation-replace?]}]]
|
||||
(navigate-to-chat cofx chat-id navigation-replace?)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:start-chat
|
||||
[(re-frame/inject-cofx :get-stored-messages)
|
||||
re-frame/trim-v]
|
||||
[re-frame/trim-v]
|
||||
(fn [{:keys [db] :as cofx} [contact-id {:keys [navigation-replace?]}]]
|
||||
(when (not= (:current-public-key db) contact-id) ; don't allow to open chat with yourself
|
||||
(if (get (:chats db) contact-id)
|
||||
|
|
|
@ -11,13 +11,12 @@
|
|||
|
||||
(defn- generate-context
|
||||
"Generates context for jail call"
|
||||
[{:keys [chats] :accounts/keys [current-account-id]} chat-id to group-id]
|
||||
[current-account-id chat-id to group-id]
|
||||
(merge {:platform platform/platform
|
||||
:from current-account-id
|
||||
:to to
|
||||
:chat {:chat-id chat-id
|
||||
:group-chat (or (get-in chats [chat-id :group-chat])
|
||||
(not (nil? group-id)))}}
|
||||
:group-chat (not (nil? group-id))}}
|
||||
i18n/delimeters))
|
||||
|
||||
(defn request-command-message-data
|
||||
|
@ -25,52 +24,56 @@
|
|||
[db
|
||||
{{command-name :command
|
||||
content-command-name :content-command
|
||||
:keys [content-command-scope-bitmask scope-bitmask params type bot]} :content
|
||||
:keys [chat-id jail-id group-id] :as message}
|
||||
data-type]
|
||||
(let [{:keys [chats]
|
||||
:accounts/keys [current-account-id]
|
||||
:keys [content-command-scope-bitmask bot scope-bitmask params type]} :content
|
||||
:keys [chat-id group-id jail-id] :as message}
|
||||
{:keys [data-type proceed-event-creator cache-data?] :as opts}]
|
||||
(let [{:accounts/keys [current-account-id]
|
||||
:contacts/keys [contacts]} db
|
||||
jail-id (or bot jail-id chat-id)
|
||||
jail-command-name (or content-command-name command-name)]
|
||||
(if (get-in contacts [jail-id :jail-loaded?])
|
||||
(let [path [(if (= :response (keyword type)) :responses :commands)
|
||||
[jail-command-name
|
||||
(or scope-bitmask content-command-scope-bitmask)]
|
||||
(or content-command-scope-bitmask scope-bitmask)]
|
||||
data-type]
|
||||
to (get-in contacts [chat-id :address])
|
||||
jail-params {:parameters params
|
||||
:context (generate-context db chat-id to group-id)}]
|
||||
:context (generate-context current-account-id chat-id to group-id)}]
|
||||
{:call-jail {:jail-id jail-id
|
||||
:path path
|
||||
:params jail-params
|
||||
:callback-events-creator (fn [jail-response]
|
||||
[[::jail-command-data-response
|
||||
jail-response message data-type]])}})
|
||||
jail-response message opts]])}})
|
||||
{:db (update-in db [:contacts/contacts jail-id :jail-loaded-events]
|
||||
conj [:request-command-message-data message data-type])})))
|
||||
conj [:request-command-message-data message opts])})))
|
||||
|
||||
;;;; Handlers
|
||||
|
||||
(handlers/register-handler-fx
|
||||
::jail-command-data-response
|
||||
[re-frame/trim-v]
|
||||
(fn [{:keys [db]} [{{:keys [returned]} :result} {:keys [message-id on-requested]} data-type]]
|
||||
(cond-> {}
|
||||
returned
|
||||
(assoc :db (assoc-in db [:message-data data-type message-id] returned))
|
||||
(and returned
|
||||
(= :preview data-type))
|
||||
(assoc :update-message {:message-id message-id
|
||||
:preview (prn-str returned)})
|
||||
on-requested
|
||||
(assoc :dispatch (on-requested returned)))))
|
||||
(fn [{:keys [db]} [{{:keys [returned]} :result}
|
||||
{:keys [message-id chat-id]}
|
||||
{:keys [data-type proceed-event-creator cache-data?]}]]
|
||||
(let [existing-message (get-in db [:chats chat-id :messages message-id])]
|
||||
(cond-> {}
|
||||
|
||||
(and cache-data? existing-message returned)
|
||||
(as-> fx
|
||||
(let [updated-message (assoc-in existing-message [:content data-type] returned)]
|
||||
(assoc fx
|
||||
:db (assoc-in db [:chats chat-id :messages message-id] updated-message)
|
||||
:update-message (select-keys updated-message [:message-id :content]))))
|
||||
|
||||
proceed-event-creator
|
||||
(assoc :dispatch (proceed-event-creator returned))))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:request-command-message-data
|
||||
[re-frame/trim-v (re-frame/inject-cofx :get-local-storage-data)]
|
||||
(fn [{:keys [db]} [message data-type]]
|
||||
(request-command-message-data db message data-type)))
|
||||
(fn [{:keys [db]} [message opts]]
|
||||
(request-command-message-data db message opts)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:execute-command-immediately
|
||||
|
@ -82,18 +85,3 @@
|
|||
[:read-external-storage]
|
||||
#(re-frame/dispatch [:initialize-geth])]}
|
||||
(log/debug "ignoring command: " command-name))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:request-command-preview
|
||||
[re-frame/trim-v (re-frame/inject-cofx :get-stored-message)]
|
||||
(fn [{:keys [db get-stored-message]} [{:keys [message-id] :as message}]]
|
||||
(let [previews (get-in db [:message-data :preview])]
|
||||
(when-not (contains? previews message-id)
|
||||
(let [{serialized-preview :preview} (get-stored-message message-id)]
|
||||
;; if preview is already cached in db, do not request it from jail
|
||||
;; and write it directly to message-data path
|
||||
(if serialized-preview
|
||||
{:db (assoc-in db
|
||||
[:message-data :preview message-id]
|
||||
(reader/read-string serialized-preview))}
|
||||
(request-command-message-data db message :preview)))))))
|
||||
|
|
|
@ -270,10 +270,11 @@
|
|||
:content {:command (:name command)
|
||||
:scope-bitmask (:scope-bitmask command)
|
||||
:params params
|
||||
:type (:type command)}
|
||||
:on-requested (fn [jail-response]
|
||||
(event-after-creator command-message jail-response))}]
|
||||
(commands-events/request-command-message-data db request-data data-type)))
|
||||
:type (:type command)}}]
|
||||
(commands-events/request-command-message-data db request-data
|
||||
{:data-type data-type
|
||||
:proceed-event-creator (partial event-after-creator
|
||||
command-message)})))
|
||||
|
||||
(defn proceed-command
|
||||
"Proceed with command processing by setting up execution chain of events:
|
||||
|
@ -429,10 +430,13 @@
|
|||
(animation-events/choose-predefined-expandable-height :result-box :max))
|
||||
::dismiss-keyboard nil}
|
||||
;; regular command message, we need to fetch preview before sending the command message
|
||||
(request-command-data db (merge params-template
|
||||
{:data-type :preview
|
||||
:event-after-creator (fn [command-message _]
|
||||
[::send-command command-message])})))))
|
||||
(request-command-data
|
||||
db
|
||||
(merge params-template
|
||||
{:data-type :preview
|
||||
:event-after-creator (fn [command-message returned]
|
||||
[::send-command (assoc-in command-message
|
||||
[:command :preview] returned)])})))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:send-current-message
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
[status-im.chat.models :as model]
|
||||
[status-im.chat.models.commands :as commands-model]
|
||||
[status-im.chat.models.unviewed-messages :as unviewed-messages-model]
|
||||
[status-im.chat.events.commands :as commands-events]
|
||||
[status-im.chat.events.requests :as requests-events]
|
||||
[status-im.data-store.chats :as chat-store]
|
||||
[status-im.data-store.messages :as msg-store]))
|
||||
|
@ -43,35 +44,36 @@
|
|||
contacts)]
|
||||
(:ref (get available-commands-responses response-name))))
|
||||
|
||||
(defn- add-message-to-db
|
||||
[db {:keys [message-id] :as message} chat-id]
|
||||
(-> db
|
||||
(chat-utils/add-message-to-db chat-id chat-id message (:new? message))
|
||||
(unviewed-messages-model/add-unviewed-message chat-id message-id)))
|
||||
|
||||
(defn add-message
|
||||
[{:keys [db message-exists? get-last-stored-message pop-up-chat?
|
||||
get-last-clock-value now random-id] :as cofx}
|
||||
{:keys [from group-id chat-id content-type content
|
||||
message-id timestamp clock-value]
|
||||
[{:keys [db message-exists? pop-up-chat? get-last-clock-value now] :as cofx}
|
||||
{:keys [from group-id chat-id content-type content message-id timestamp clock-value]
|
||||
:as message
|
||||
:or {clock-value 0}}]
|
||||
(let [{:keys [access-scope->commands-responses] :contacts/keys [contacts]} db
|
||||
chat-identifier (or group-id chat-id from)
|
||||
current-account (get-current-account db)]
|
||||
{:keys [public-key] :as current-account} (get-current-account db)
|
||||
chat-identifier (or group-id chat-id from)]
|
||||
;; proceed with adding message if message is not already stored in realm,
|
||||
;; it's not from current user (outgoing message) and it's for relevant chat
|
||||
;; (either current active chat or new chat not existing yet)
|
||||
(if (and (not (message-exists? message-id))
|
||||
(not= from (:public-key current-account))
|
||||
(pop-up-chat? chat-identifier))
|
||||
(let [group-chat? (not (nil? group-id))
|
||||
chat-exists? (get-in db [:chats chat-identifier])
|
||||
fx (if chat-exists?
|
||||
(when (and (not (message-exists? message-id))
|
||||
(not= from public-key)
|
||||
(pop-up-chat? chat-identifier))
|
||||
(let [fx (if (get-in db [:chats chat-identifier])
|
||||
(model/upsert-chat cofx {:chat-id chat-identifier
|
||||
:group-chat group-chat?})
|
||||
:group-chat (boolean group-id)})
|
||||
(model/add-chat cofx chat-identifier))
|
||||
command-request? (= content-type const/content-type-command-request)
|
||||
command (:command content)
|
||||
enriched-message (cond-> (assoc (chat-utils/check-author-direction
|
||||
(get-last-stored-message chat-identifier)
|
||||
message)
|
||||
:chat-id chat-identifier
|
||||
:timestamp (or timestamp now)
|
||||
enriched-message (cond-> (assoc message
|
||||
:chat-id chat-identifier
|
||||
:timestamp (or timestamp now)
|
||||
:show? true
|
||||
:clock-value (clocks/receive
|
||||
clock-value
|
||||
(get-last-clock-value chat-identifier)))
|
||||
|
@ -81,49 +83,50 @@
|
|||
current-account
|
||||
(get-in fx [:db :chats chat-identifier])
|
||||
contacts
|
||||
command)))
|
||||
update-db-fx #(-> %
|
||||
(chat-utils/add-message-to-db chat-identifier chat-identifier enriched-message
|
||||
(:new? enriched-message))
|
||||
(unviewed-messages-model/add-unviewed-message chat-identifier message-id)
|
||||
(assoc-in [:chats chat-identifier :last-message] enriched-message))]
|
||||
command)))]
|
||||
(cond-> (-> fx
|
||||
(update :db update-db-fx)
|
||||
(update :db add-message-to-db enriched-message chat-identifier)
|
||||
(assoc :save-message (dissoc enriched-message :new?)))
|
||||
|
||||
command
|
||||
(update :dispatch-n concat [[:request-command-message-data enriched-message :short-preview]
|
||||
[:request-command-preview enriched-message]])
|
||||
|
||||
command-request?
|
||||
(requests-events/add-request chat-identifier enriched-message)))
|
||||
{:db db})))
|
||||
(requests-events/add-request chat-identifier enriched-message))))))
|
||||
|
||||
(def ^:private receive-interceptors
|
||||
[(re-frame/inject-cofx :message-exists?) (re-frame/inject-cofx :get-last-stored-message)
|
||||
(re-frame/inject-cofx :pop-up-chat?) (re-frame/inject-cofx :get-last-clock-value)
|
||||
(re-frame/inject-cofx :random-id) (re-frame/inject-cofx :get-stored-chat) re-frame/trim-v])
|
||||
[(re-frame/inject-cofx :message-exists?) (re-frame/inject-cofx :pop-up-chat?)
|
||||
(re-frame/inject-cofx :get-last-clock-value) (re-frame/inject-cofx :get-stored-chat)
|
||||
re-frame/trim-v])
|
||||
|
||||
;; we need this internal event without jail checking, otherwise no response for the jail
|
||||
;; call to generate preview would result to infinite loop of `:received-message` events
|
||||
(handlers/register-handler-fx
|
||||
:received-protocol-message!
|
||||
receive-interceptors
|
||||
(fn [cofx [{:keys [from to payload]}]]
|
||||
(add-message cofx (merge payload
|
||||
{:from from
|
||||
:to to
|
||||
:chat-id from}))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:received-message
|
||||
::received-message
|
||||
receive-interceptors
|
||||
(fn [cofx [message]]
|
||||
(add-message cofx message)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:received-message
|
||||
receive-interceptors
|
||||
(fn [{:keys [db] :as cofx} [{:keys [content] :as message}]]
|
||||
(if (:command content)
|
||||
;; we are dealing with received command message, we can't add it right away,
|
||||
;; we first need to fetch preview and add it only after we already have the preview.
|
||||
;; note that `request-command-message-data` implicitly wait till jail is ready and
|
||||
;; call is made only after that
|
||||
(commands-events/request-command-message-data
|
||||
db message
|
||||
{:data-type :preview
|
||||
:proceed-event-creator (fn [preview]
|
||||
[::received-message
|
||||
(assoc-in message [:content :preview] preview)])})
|
||||
;; regular non command message, we can add it right away
|
||||
(add-message cofx message))))
|
||||
|
||||
;; TODO janherich: get rid of this special case once they hacky app start-up sequence is refactored
|
||||
(handlers/register-handler-fx
|
||||
:received-message-when-commands-loaded
|
||||
receive-interceptors
|
||||
(fn [{:keys [db] :as cofx} [chat-id message]]
|
||||
(fn [{:keys [db] :as cofx} [{:keys [chat-id] :as message}]]
|
||||
(if (and (:status-node-started? db)
|
||||
(get-in db [:contacts/contacts chat-id :jail-loaded?]))
|
||||
(add-message cofx message)
|
||||
{:dispatch-later [{:ms 400 :dispatch [:received-message-when-commands-loaded chat-id message]}]})))
|
||||
{:dispatch-later [{:ms 400 :dispatch [:received-message-when-commands-loaded message]}]})))
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
|
||||
(defn- message-seen [{:keys [db] :as fx} message-id]
|
||||
(-> fx
|
||||
(assoc-in [:db :message-data :statuses message-id :status] :seen)
|
||||
(assoc-in [:db :chats const/console-chat-id :messages message-id :message-status] :seen)
|
||||
(assoc :update-message {:message-id message-id
|
||||
:message-status :seen})))
|
||||
|
||||
|
@ -90,7 +90,8 @@
|
|||
(message-seen message-id))))
|
||||
|
||||
(defn- extract-last-phone-number [chats]
|
||||
(let [phone-message (->> (get-in chats ["console" :messages])
|
||||
(let [phone-message (->> (get-in chats [const/console-chat-id :messages])
|
||||
(map second)
|
||||
(some (fn [{:keys [type content] :as message}]
|
||||
(when (and (= type :response)
|
||||
(= (:command content) "phone"))
|
||||
|
|
|
@ -40,6 +40,8 @@
|
|||
:content-command (:name command)
|
||||
:content-command-scope-bitmask (:scope-bitmask command)
|
||||
:content-command-ref (:ref command)
|
||||
:preview (:preview command)
|
||||
:short-preview (:short-preview command)
|
||||
:bot (or (:bot command)
|
||||
(:owner-id command)))]
|
||||
{:message-id id
|
||||
|
@ -90,12 +92,11 @@
|
|||
hidden-params (->> (:params command)
|
||||
(filter :hidden)
|
||||
(map :name))
|
||||
command' (->> (prepare-command current-public-key chat-id clock-value request content)
|
||||
(cu/check-author-direction db chat-id))]
|
||||
command' (prepare-command current-public-key chat-id clock-value request content)]
|
||||
(dispatch [:update-message-overhead! chat-id network-status])
|
||||
(dispatch [:set-chat-ui-props {:sending-in-progress? false}])
|
||||
(dispatch [::send-command! add-to-chat-id (assoc params :command command') hidden-params])
|
||||
(when (cu/console? chat-id)
|
||||
(when (= console-chat-id chat-id)
|
||||
(dispatch [:console-respond-command params]))))))
|
||||
|
||||
(register-handler ::send-command!
|
||||
|
@ -104,7 +105,8 @@
|
|||
(dispatch [::add-command add-to-chat-id params])
|
||||
(dispatch [::save-command! add-to-chat-id params hidden-params])
|
||||
(dispatch [::dispatch-responded-requests! params])
|
||||
(dispatch [::send-command-protocol! params]))))
|
||||
(dispatch [::send-command-protocol! (update-in params [:command :content]
|
||||
dissoc :preview :short-preview)]))))
|
||||
|
||||
(register-handler ::add-command
|
||||
(after (fn [_ [_ _ {:keys [handler]}]]
|
||||
|
@ -115,11 +117,9 @@
|
|||
(register-handler ::save-command!
|
||||
(u/side-effect!
|
||||
(fn [db [_ chat-id {:keys [command]} hidden-params]]
|
||||
(let [preview (get-in db [:message-data :preview (:message-id command)])
|
||||
command (cond-> (-> command
|
||||
(let [command (cond-> (-> command
|
||||
(update-in [:content :params] #(apply dissoc % hidden-params))
|
||||
(dissoc :to-message :has-handler :raw-input))
|
||||
preview (assoc :preview (pr-str preview)))]
|
||||
(dissoc :to-message :has-handler :raw-input)))]
|
||||
(dispatch [:upsert-chat! {:chat-id chat-id}])
|
||||
(messages/save chat-id command)))))
|
||||
|
||||
|
@ -168,17 +168,15 @@
|
|||
(fn [{:keys [network-status] :as db} [_ {:keys [chat-id identity message] :as params}]]
|
||||
(let [{:keys [group-chat public?]} (get-in db [:chats chat-id])
|
||||
clock-value (messages/get-last-clock-value chat-id)
|
||||
message' (cu/check-author-direction
|
||||
db chat-id
|
||||
{:message-id (random/id)
|
||||
:chat-id chat-id
|
||||
:content message
|
||||
:from identity
|
||||
:content-type text-content-type
|
||||
:outgoing true
|
||||
:timestamp (datetime/now-ms)
|
||||
:clock-value (clocks/send clock-value)
|
||||
:show? true})
|
||||
message' {:message-id (random/id)
|
||||
:chat-id chat-id
|
||||
:content message
|
||||
:from identity
|
||||
:content-type text-content-type
|
||||
:outgoing true
|
||||
:timestamp (datetime/now-ms)
|
||||
:clock-value (clocks/send clock-value)
|
||||
:show? true}
|
||||
message'' (cond-> message'
|
||||
(and group-chat public?)
|
||||
(assoc :group-id chat-id :message-type :public-group-user-message)
|
||||
|
|
|
@ -24,16 +24,13 @@
|
|||
original)))))
|
||||
|
||||
(defn text-ends-with-space? [text]
|
||||
(and (not (nil? text))
|
||||
(str/ends-with? text const/spacing-char)))
|
||||
(and text (str/ends-with? text const/spacing-char)))
|
||||
|
||||
(defn starts-as-command?
|
||||
"Returns true if `text` may be treated as a command.
|
||||
To make sure that text is command we need to use `possible-chat-actions` function."
|
||||
[text]
|
||||
(and (not (nil? text))
|
||||
(or (str/starts-with? text const/bot-char)
|
||||
(str/starts-with? text const/command-char))))
|
||||
(and text (str/starts-with? text const/command-char)))
|
||||
|
||||
(defn split-command-args
|
||||
"Returns a list of command's arguments including the command's name.
|
||||
|
|
|
@ -1,18 +1,13 @@
|
|||
(ns status-im.chat.models.unviewed-messages)
|
||||
|
||||
(defn load-unviewed-messages [db raw-unviewed-messages]
|
||||
(assoc db :unviewed-messages
|
||||
(->> raw-unviewed-messages
|
||||
(group-by :chat-id)
|
||||
(map (fn [[id messages]]
|
||||
[id {:messages-ids (map :message-id messages)
|
||||
:count (count messages)}]))
|
||||
(into {}))))
|
||||
(defn index-unviewed-messages [unviewed-messages]
|
||||
(into {}
|
||||
(map (fn [[chat-id messages]]
|
||||
[chat-id (into #{} (map :message-id) messages)]))
|
||||
(group-by :chat-id unviewed-messages)))
|
||||
|
||||
(defn add-unviewed-message [db chat-id message-id]
|
||||
(-> db
|
||||
(update-in [:unviewed-messages chat-id :messages-ids] conj message-id)
|
||||
(update-in [:unviewed-messages chat-id :count] inc)))
|
||||
(update-in db [:chats chat-id :unviewed-messages] (fnil conj #{}) message-id))
|
||||
|
||||
(defn remove-unviewed-messages [db chat-id]
|
||||
(update db :unviewed-messages dissoc chat-id))
|
||||
(defn remove-unviewed-message [db chat-id message-id]
|
||||
(update-in db [:chats chat-id :unviewed-messages] disj message-id))
|
||||
|
|
|
@ -1,198 +1,110 @@
|
|||
(ns status-im.chat.screen
|
||||
(:require-macros [status-im.utils.views :refer [defview]])
|
||||
(:require [re-frame.core :refer [subscribe dispatch]]
|
||||
[status-im.ui.components.react :refer [view
|
||||
animated-view
|
||||
text
|
||||
modal
|
||||
touchable-highlight
|
||||
list-view
|
||||
list-item]]
|
||||
[status-im.ui.components.icons.vector-icons :as vi]
|
||||
[status-im.ui.components.status-bar :refer [status-bar]]
|
||||
[status-im.ui.components.chat-icon.screen :refer [chat-icon-view-action
|
||||
chat-icon-view-menu-item]]
|
||||
[status-im.chat.styles.screen :as st]
|
||||
[status-im.utils.listview :refer [to-datasource-inverted]]
|
||||
[status-im.utils.utils :refer [truncate-str]]
|
||||
(:require-macros [status-im.utils.views :refer [defview letsubs]])
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.components.icons.vector-icons :as vector-icons]
|
||||
[status-im.ui.components.status-bar :as status-bar]
|
||||
[status-im.ui.components.chat-icon.screen :as chat-icon-screen]
|
||||
[status-im.chat.styles.screen :as style]
|
||||
[status-im.utils.listview :as listview]
|
||||
[status-im.utils.datetime :as time]
|
||||
[status-im.utils.platform :as platform :refer [platform-specific]]
|
||||
[status-im.ui.components.invertible-scroll-view :refer [invertible-scroll-view]]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.ui.components.invertible-scroll-view :as scroll-view]
|
||||
[status-im.ui.components.toolbar.view :as toolbar]
|
||||
[status-im.chat.views.toolbar-content :refer [toolbar-content-view]]
|
||||
[status-im.chat.views.message.message :refer [chat-message]]
|
||||
[status-im.chat.views.message.datemark :refer [chat-datemark]]
|
||||
[status-im.chat.views.toolbar-content :as toolbar-content]
|
||||
[status-im.chat.views.message.message :as message]
|
||||
[status-im.chat.views.message.datemark :as message-datemark]
|
||||
[status-im.chat.views.input.input :as input]
|
||||
[status-im.chat.views.actions :refer [actions-view]]
|
||||
[status-im.chat.views.bottom-info :refer [bottom-info-view]]
|
||||
[status-im.chat.constants :as chat-const]
|
||||
[status-im.i18n :refer [label label-pluralize]]
|
||||
[status-im.chat.views.actions :as actions]
|
||||
[status-im.chat.views.bottom-info :as bottom-info]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.ui.components.animation :as anim]
|
||||
[status-im.ui.components.sync-state.offline :refer [offline-view]]
|
||||
[status-im.constants :refer [content-type-status]]
|
||||
[taoensso.timbre :as log]
|
||||
[clojure.string :as str]))
|
||||
|
||||
(defn contacts-by-identity [contacts]
|
||||
(->> contacts
|
||||
(map (fn [{:keys [identity] :as contact}]
|
||||
[identity contact]))
|
||||
(into {})))
|
||||
|
||||
(defn add-message-color [{:keys [from] :as message} contact-by-identity]
|
||||
(if (= "system" from)
|
||||
(assoc message :text-color :#4A5258
|
||||
:background-color :#D3EEEF)
|
||||
(let [{:keys [text-color background-color]} (get contact-by-identity from)]
|
||||
(assoc message :text-color text-color
|
||||
:background-color background-color))))
|
||||
[status-im.ui.components.sync-state.offline :as offline]
|
||||
[clojure.string :as string]))
|
||||
|
||||
(defview chat-icon []
|
||||
[chat-id [:chat :chat-id]
|
||||
group-chat [:chat :group-chat]
|
||||
name [:chat :name]
|
||||
color [:chat :color]]
|
||||
;; TODO stub data ('online' property)
|
||||
[chat-icon-view-action chat-id group-chat name color true])
|
||||
(letsubs [{:keys [chat-id group-chat name color]} [:get-current-chat]]
|
||||
[chat-icon-screen/chat-icon-view-action chat-id group-chat name color true]))
|
||||
|
||||
(defn typing [member]
|
||||
[view st/typing-view
|
||||
[view st/typing-background
|
||||
[text {:style st/typing-text
|
||||
:font :default}
|
||||
(str member " " (label :t/is-typing))]]])
|
||||
(defn- toolbar-action [show-actions?]
|
||||
[react/touchable-highlight
|
||||
{:on-press #(re-frame/dispatch [:set-chat-ui-props {:show-actions? (not show-actions?)}])
|
||||
:accessibility-label :chat-menu}
|
||||
[react/view style/action
|
||||
(if show-actions?
|
||||
[vector-icons/icon :icons/dropdown-up]
|
||||
[chat-icon])]])
|
||||
|
||||
(defn typing-all []
|
||||
[view st/typing-all
|
||||
;; TODO stub data
|
||||
(for [member ["Geoff" "Justas"]]
|
||||
^{:key member} [typing member])])
|
||||
(defview add-contact-bar []
|
||||
(letsubs [chat-id [:get-current-chat-id]
|
||||
pending-contact? [:current-contact :pending?]]
|
||||
(when pending-contact?
|
||||
[react/touchable-highlight
|
||||
{:on-press #(re-frame/dispatch [:add-pending-contact chat-id])}
|
||||
[react/view style/add-contact
|
||||
[react/text {:style style/add-contact-text}
|
||||
(i18n/label :t/add-to-contacts)]]])))
|
||||
|
||||
(defview chat-toolbar []
|
||||
(letsubs [show-actions? [:get-current-chat-ui-prop :show-actions?]
|
||||
accounts [:get-accounts]
|
||||
creating? [:get :accounts/creating-account?]]
|
||||
[react/view
|
||||
[status-bar/status-bar]
|
||||
[toolbar/toolbar {:show-sync-bar? true}
|
||||
(when-not (or show-actions? creating?)
|
||||
(if (empty? accounts)
|
||||
[toolbar/nav-clear-text (i18n/label :t/recover)
|
||||
#(re-frame/dispatch [:navigate-to-modal :recover-modal])]
|
||||
toolbar/default-nav-back))
|
||||
[toolbar-content/toolbar-content-view]
|
||||
[toolbar-action show-actions?]]
|
||||
[add-contact-bar]]))
|
||||
|
||||
(defmulti message-row (fn [{{:keys [type]} :row}] type))
|
||||
|
||||
(defmethod message-row :datemark
|
||||
[{{:keys [value]} :row}]
|
||||
(list-item [chat-datemark value]))
|
||||
(react/list-item [message-datemark/chat-datemark value]))
|
||||
|
||||
(defmethod message-row :default
|
||||
[{:keys [contact-by-identity group-chat messages-count row index last-outgoing?]}]
|
||||
(let [message (-> row
|
||||
(add-message-color contact-by-identity)
|
||||
(assoc :group-chat group-chat)
|
||||
(assoc :messages-count messages-count)
|
||||
(assoc :index index)
|
||||
(assoc :last-message (= (js/parseInt index) (dec messages-count)))
|
||||
(assoc :last-outgoing? last-outgoing?))]
|
||||
(list-item [chat-message message])))
|
||||
|
||||
(defn toolbar-action []
|
||||
(let [show-actions (subscribe [:get-current-chat-ui-prop :show-actions?])]
|
||||
(fn []
|
||||
(let [show-actions @show-actions]
|
||||
[touchable-highlight
|
||||
{:on-press #(dispatch [:set-chat-ui-props {:show-actions? (not show-actions)}])
|
||||
:accessibility-label :chat-menu}
|
||||
[view st/action
|
||||
(if show-actions
|
||||
[vi/icon :icons/dropdown-up]
|
||||
[chat-icon])]]))))
|
||||
|
||||
(defview add-contact-bar []
|
||||
[chat-id [:get :current-chat-id]
|
||||
pending-contact? [:current-contact :pending?]]
|
||||
(when pending-contact?
|
||||
[touchable-highlight
|
||||
{:on-press #(dispatch [:add-pending-contact chat-id])}
|
||||
[view st/add-contact
|
||||
[text {:style st/add-contact-text}
|
||||
(label :t/add-to-contacts)]]]))
|
||||
|
||||
(defview chat-toolbar []
|
||||
[show-actions? [:get-current-chat-ui-prop :show-actions?]
|
||||
accounts [:get-accounts]
|
||||
creating? [:get :accounts/creating-account?]]
|
||||
[view
|
||||
[status-bar]
|
||||
[toolbar/toolbar {:show-sync-bar? true}
|
||||
(when-not (or show-actions? creating?)
|
||||
(if (empty? accounts)
|
||||
[toolbar/nav-clear-text (label :t/recover) #(dispatch [:navigate-to-modal :recover-modal])]
|
||||
toolbar/default-nav-back))
|
||||
[toolbar-content-view]
|
||||
[toolbar-action]]
|
||||
[add-contact-bar]])
|
||||
|
||||
(defn get-intro-status-message [all-messages]
|
||||
(let [{:keys [timestamp content-type]} (last all-messages)]
|
||||
(when (not= content-type content-type-status)
|
||||
{:message-id chat-const/intro-status-message-id
|
||||
:content-type content-type-status
|
||||
:timestamp (or timestamp (time/now-ms))})))
|
||||
|
||||
(defn messages-with-timemarks [all-messages extras]
|
||||
(let [status-message (get-intro-status-message all-messages)
|
||||
all-messages (if status-message
|
||||
(concat all-messages [status-message])
|
||||
all-messages)
|
||||
messages (->> all-messages
|
||||
(map #(merge % (get extras (:message-id %))))
|
||||
(remove #(false? (:show? %)))
|
||||
(sort-by :clock-value >)
|
||||
(map #(assoc % :datemark (time/day-relative (:timestamp %))))
|
||||
(group-by :datemark)
|
||||
(vals)
|
||||
(sort-by (comp :clock-value first) >)
|
||||
(map (fn [v] [v {:type :datemark :value (:datemark (first v))}]))
|
||||
(flatten))
|
||||
remove-last? (some (fn [{:keys [content-type]}]
|
||||
(= content-type content-type-status))
|
||||
messages)]
|
||||
(if remove-last?
|
||||
(drop-last messages)
|
||||
messages)))
|
||||
[{:keys [group-chat current-public-key row]}]
|
||||
(react/list-item [message/chat-message (assoc row
|
||||
:group-chat group-chat
|
||||
:current-public-key current-public-key)]))
|
||||
|
||||
(defview messages-view [group-chat]
|
||||
[messages [:chat :messages]
|
||||
contacts [:chat :contacts]
|
||||
message-extras [:get :message-extras]
|
||||
loaded? [:all-messages-loaded?]
|
||||
current-chat-id [:get-current-chat-id]
|
||||
last-outgoing-message [:get-chat-last-outgoing-message @current-chat-id]]
|
||||
(let [contacts' (contacts-by-identity contacts)
|
||||
messages (messages-with-timemarks messages message-extras)]
|
||||
[list-view {:renderRow (fn [row _ index]
|
||||
(message-row {:contact-by-identity contacts'
|
||||
:group-chat group-chat
|
||||
:messages-count (count messages)
|
||||
:row row
|
||||
:index index
|
||||
:last-outgoing? (= (:message-id last-outgoing-message) (:message-id row))}))
|
||||
:renderScrollComponent #(invertible-scroll-view (js->clj %))
|
||||
:onEndReached (when-not loaded? #(dispatch [:load-more-messages]))
|
||||
:enableEmptySections true
|
||||
:keyboardShouldPersistTaps (if platform/android? :always :handled)
|
||||
:dataSource (to-datasource-inverted messages)}]))
|
||||
(letsubs [messages [:get-current-chat-messages]
|
||||
current-public-key [:get-current-public-key]]
|
||||
[react/list-view {:renderRow (fn [row _ index]
|
||||
(message-row {:group-chat group-chat
|
||||
:current-public-key current-public-key
|
||||
:row row}))
|
||||
:renderScrollComponent #(scroll-view/invertible-scroll-view (js->clj %))
|
||||
:onEndReached #(re-frame/dispatch [:load-more-messages])
|
||||
:enableEmptySections true
|
||||
:keyboardShouldPersistTaps (if platform/android? :always :handled)
|
||||
:dataSource (listview/to-datasource-inverted messages)}]))
|
||||
|
||||
(defview chat []
|
||||
[group-chat [:chat :group-chat]
|
||||
show-actions? [:get-current-chat-ui-prop :show-actions?]
|
||||
show-bottom-info? [:get-current-chat-ui-prop :show-bottom-info?]
|
||||
show-emoji? [:get-current-chat-ui-prop :show-emoji?]
|
||||
layout-height [:get :layout-height]
|
||||
input-text [:chat :input-text]]
|
||||
{:component-did-mount #(dispatch [:check-and-open-dapp!])
|
||||
:component-will-unmount #(dispatch [:set-chat-ui-props {:show-emoji? false}])}
|
||||
[view {:style st/chat-view
|
||||
:on-layout (fn [event]
|
||||
(let [height (.. event -nativeEvent -layout -height)]
|
||||
(when (not= height layout-height)
|
||||
(dispatch [:set-layout-height height]))))}
|
||||
[chat-toolbar]
|
||||
[messages-view group-chat]
|
||||
[input/container {:text-empty? (str/blank? input-text)}]
|
||||
(when show-actions?
|
||||
[actions-view])
|
||||
(when show-bottom-info?
|
||||
[bottom-info-view])
|
||||
[offline-view {:top (get-in platform-specific
|
||||
[:component-styles :status-bar :default :height])}]])
|
||||
(letsubs [{:keys [group-chat input-text]} [:get-current-chat]
|
||||
show-actions? [:get-current-chat-ui-prop :show-actions?]
|
||||
show-bottom-info? [:get-current-chat-ui-prop :show-bottom-info?]
|
||||
show-emoji? [:get-current-chat-ui-prop :show-emoji?]
|
||||
layout-height [:get :layout-height]]
|
||||
{:component-did-mount #(re-frame/dispatch [:check-and-open-dapp!])
|
||||
:component-will-unmount #(re-frame/dispatch [:set-chat-ui-props {:show-emoji? false}])}
|
||||
[react/view {:style style/chat-view
|
||||
:on-layout (fn [event]
|
||||
(let [height (.. event -nativeEvent -layout -height)]
|
||||
(when (not= height layout-height)
|
||||
(re-frame/dispatch [:set-layout-height height]))))}
|
||||
[chat-toolbar]
|
||||
[messages-view group-chat]
|
||||
[input/container {:text-empty? (string/blank? input-text)}]
|
||||
(when show-actions?
|
||||
[actions/actions-view])
|
||||
(when show-bottom-info?
|
||||
[bottom-info/bottom-info-view])
|
||||
[offline/offline-view {:top (get-in platform/platform-specific
|
||||
[:component-styles :status-bar :default :height])}]]))
|
||||
|
|
|
@ -120,27 +120,16 @@
|
|||
:from const/console-chat-id
|
||||
:to "me"}]])
|
||||
|
||||
(def intro-status
|
||||
{:message-id chat-const/intro-status-message-id
|
||||
:content (label :t/intro-status)
|
||||
:from const/console-chat-id
|
||||
:chat-id const/console-chat-id
|
||||
:content-type const/content-type-status
|
||||
:outgoing false
|
||||
:to "me"})
|
||||
|
||||
(def intro-events
|
||||
[[:received-message intro-status]
|
||||
[:received-message-when-commands-loaded
|
||||
const/console-chat-id
|
||||
{:chat-id const/console-chat-id
|
||||
:message-id chat-const/intro-message1-id
|
||||
:content {:command "password"
|
||||
:content (label :t/intro-message1)}
|
||||
:content-type const/content-type-command-request
|
||||
:outgoing false
|
||||
:from const/console-chat-id
|
||||
:to "me"}]])
|
||||
(def intro-event
|
||||
[:received-message-when-commands-loaded
|
||||
{:chat-id const/console-chat-id
|
||||
:message-id chat-const/intro-message1-id
|
||||
:content {:command "password"
|
||||
:content (label :t/intro-message1)}
|
||||
:content-type const/content-type-command-request
|
||||
:outgoing false
|
||||
:from const/console-chat-id
|
||||
:to "me"}])
|
||||
|
||||
(def console-chat
|
||||
{:chat-id const/console-chat-id
|
||||
|
@ -161,4 +150,5 @@
|
|||
:photo-path const/console-chat-id
|
||||
:dapp? true
|
||||
:unremovable? true
|
||||
:bot-url "local://console-bot"})
|
||||
:bot-url "local://console-bot"
|
||||
:status (label :t/intro-status)})
|
||||
|
|
|
@ -1,27 +1,22 @@
|
|||
(ns status-im.chat.specs
|
||||
(:require [cljs.spec.alpha :as s]))
|
||||
|
||||
(s/def :chat/chats (s/nilable map?)) ;; {id (string) chat (map)} active chats on chat's tab
|
||||
(s/def :chat/current-chat-id (s/nilable string?)) ;;current or last opened chat-id
|
||||
(s/def :chat/chat-id (s/nilable string?)) ;;what is the difference ? ^
|
||||
(s/def :chat/new-chat-name (s/nilable string?)) ;;we have name in the new-chat why do we need this field
|
||||
(s/def :chat/chat-animations (s/nilable map?)) ;;{id (string) props (map)}
|
||||
(s/def :chat/chat-ui-props (s/nilable map?)) ;;{id (string) props (map)}
|
||||
(s/def :chat/chats (s/nilable map?)) ; {id (string) chat (map)} active chats on chat's tab
|
||||
(s/def :chat/current-chat-id (s/nilable string?)) ; current or last opened chat-id
|
||||
(s/def :chat/chat-id (s/nilable string?)) ; what is the difference ? ^
|
||||
(s/def :chat/new-chat-name (s/nilable string?)) ; we have name in the new-chat why do we need this field
|
||||
(s/def :chat/chat-animations (s/nilable map?)) ; {id (string) props (map)}
|
||||
(s/def :chat/chat-ui-props (s/nilable map?)) ; {id (string) props (map)}
|
||||
(s/def :chat/chat-list-ui-props (s/nilable map?))
|
||||
(s/def :chat/layout-height (s/nilable number?)) ;;height of chat's view layout
|
||||
(s/def :chat/layout-height (s/nilable number?)) ; height of chat's view layout
|
||||
(s/def :chat/expandable-view-height-to-value (s/nilable number?))
|
||||
(s/def :chat/loading-allowed (s/nilable boolean?)) ;;allow to load more messages
|
||||
(s/def :chat/message-data (s/nilable map?))
|
||||
(s/def :chat/message-id->transaction-id (s/nilable map?))
|
||||
(s/def :chat/message-status (s/nilable map?))
|
||||
(s/def :chat/unviewed-messages (s/nilable map?))
|
||||
(s/def :chat/message-status (s/nilable map?)) ; TODO janherich: remove later
|
||||
(s/def :chat/selected-participants (s/nilable set?))
|
||||
(s/def :chat/chat-loaded-callbacks (s/nilable map?))
|
||||
(s/def :chat/command-hash-valid? (s/nilable boolean?))
|
||||
(s/def :chat/public-group-topic (s/nilable string?))
|
||||
(s/def :chat/confirmation-code-sms-listener (s/nilable any?)) ; .addListener result object
|
||||
(s/def :chat/messages (s/nilable seq?))
|
||||
(s/def :chat/confirmation-code-sms-listener (s/nilable any?)) ; .addListener result object
|
||||
(s/def :chat/messages (s/nilable map?)) ; messages indexed by message-id
|
||||
(s/def :chat/loaded-chats (s/nilable seq?))
|
||||
(s/def :chat/raw-unviewed-messages (s/nilable vector?))
|
||||
(s/def :chat/bot-db (s/nilable map?))
|
||||
(s/def :chat/geolocation (s/nilable map?))
|
||||
|
|
|
@ -1,46 +1,37 @@
|
|||
(ns status-im.chat.styles.message.message
|
||||
(:require-macros [status-im.utils.styles :refer [defstyle defnstyle]])
|
||||
(:require [status-im.ui.components.styles :refer [color-white
|
||||
color-black
|
||||
color-blue
|
||||
color-light-blue
|
||||
selected-message-color
|
||||
text1-color
|
||||
text2-color
|
||||
color-gray
|
||||
color-gray4]]
|
||||
[status-im.constants :refer [text-content-type
|
||||
content-type-command]]))
|
||||
(:require [status-im.ui.components.styles :as styles]
|
||||
[status-im.constants :as constants]))
|
||||
|
||||
(defstyle style-message-text
|
||||
{:fontSize 15
|
||||
:color text1-color
|
||||
:color styles/text1-color
|
||||
:android {:line-height 22}
|
||||
:ios {:line-height 23}})
|
||||
|
||||
(def style-sub-text
|
||||
{:top -2
|
||||
:fontSize 12
|
||||
:color text2-color
|
||||
:color styles/text2-color
|
||||
:lineHeight 14
|
||||
:height 16})
|
||||
|
||||
(defn message-padding-top
|
||||
[{:keys [first-in-date? same-author same-direction]}]
|
||||
[{:keys [first-in-date? same-author? same-direction?]}]
|
||||
(cond
|
||||
first-in-date? 20
|
||||
same-author 8
|
||||
same-direction 16
|
||||
:else 24))
|
||||
first-in-date? 20
|
||||
same-author? 8
|
||||
same-direction? 16
|
||||
:else 24))
|
||||
|
||||
(defn last-message-padding
|
||||
[{:keys [last-message typing]}]
|
||||
(when (and last-message (not typing))
|
||||
[{:keys [last? typing]}]
|
||||
(when (and last? (not typing))
|
||||
{:paddingBottom 16}))
|
||||
|
||||
(def message-datemark
|
||||
{:margin-top 10
|
||||
:height 34})
|
||||
{:margin-top 10
|
||||
:height 34})
|
||||
|
||||
(def message-empty-spacing
|
||||
{:height 16})
|
||||
|
@ -65,7 +56,7 @@
|
|||
{:marginTop 18
|
||||
:marginLeft 40
|
||||
:fontSize 12
|
||||
:color text2-color})
|
||||
:color styles/text2-color})
|
||||
|
||||
(def group-message-wrapper
|
||||
{:flexDirection :column})
|
||||
|
@ -94,7 +85,7 @@
|
|||
:opacity 0.5})
|
||||
|
||||
(defstyle delivery-text
|
||||
{:color color-gray4
|
||||
{:color styles/color-gray4
|
||||
:marginLeft 5
|
||||
:android {:font-size 13}
|
||||
:ios {:font-size 14}})
|
||||
|
@ -107,15 +98,15 @@
|
|||
(defnstyle message-view
|
||||
[{:keys [content-type outgoing group-chat selected]}]
|
||||
(merge {:padding 12
|
||||
:backgroundColor color-white
|
||||
:backgroundColor styles/color-white
|
||||
:android {:border-radius 4}
|
||||
:ios {:border-radius 8}}
|
||||
(when (= content-type content-type-command)
|
||||
(when (= content-type constants/content-type-command)
|
||||
{:paddingTop 10
|
||||
:paddingBottom 14})))
|
||||
|
||||
(defstyle author
|
||||
{:color color-gray4
|
||||
{:color styles/color-gray4
|
||||
:margin-bottom 5
|
||||
:android {:font-size 13}
|
||||
:ios {:font-size 14}})
|
||||
|
@ -127,7 +118,7 @@
|
|||
{:borderRadius 14
|
||||
:padding-vertical 10
|
||||
:paddingRight 28
|
||||
:backgroundColor color-white})
|
||||
:backgroundColor styles/color-white})
|
||||
|
||||
(def command-request-from-text
|
||||
(merge style-sub-text {:marginBottom 2}))
|
||||
|
@ -245,14 +236,14 @@
|
|||
(def status-from
|
||||
{:marginTop 20
|
||||
:fontSize 18
|
||||
:color text1-color})
|
||||
:color styles/text1-color})
|
||||
|
||||
(def status-text
|
||||
{:marginTop 10
|
||||
:fontSize 14
|
||||
:lineHeight 20
|
||||
:textAlign :center
|
||||
:color text2-color})
|
||||
:color styles/text2-color})
|
||||
|
||||
(defn message-animated-container [height]
|
||||
{:height height})
|
||||
|
@ -262,6 +253,6 @@
|
|||
:width window-width})
|
||||
|
||||
(defn new-message-container [margin on-top?]
|
||||
{:background-color color-white
|
||||
{:background-color styles/color-white
|
||||
:margin-bottom margin
|
||||
:elevation (if on-top? 6 5)})
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
(ns status-im.chat.subs
|
||||
(:require [re-frame.core :refer [reg-sub dispatch subscribe path]]
|
||||
[status-im.data-store.chats :as chats]
|
||||
[status-im.chat.constants :as const]
|
||||
(:require [re-frame.core :refer [reg-sub subscribe]]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.chat.models.input :as input-model]
|
||||
[status-im.chat.models.commands :as commands-model]
|
||||
[status-im.chat.utils :as chat-utils]
|
||||
[status-im.chat.views.input.utils :as input-utils]
|
||||
[status-im.constants :refer [response-suggesstion-resize-duration
|
||||
content-type-status
|
||||
console-chat-id]]
|
||||
[status-im.commands.utils :as commands-utils]
|
||||
[status-im.utils.platform :refer [platform-specific ios?]]
|
||||
[taoensso.timbre :as log]
|
||||
[clojure.string :as str]))
|
||||
[status-im.utils.datetime :as time]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.i18n :as i18n]
|
||||
[clojure.string :as string]))
|
||||
|
||||
(reg-sub :chats :chats)
|
||||
|
||||
|
@ -49,14 +46,20 @@
|
|||
:chat-input-margin
|
||||
:<- [:get :keyboard-height]
|
||||
(fn [kb-height]
|
||||
(if ios? kb-height 0)))
|
||||
(if platform/ios? kb-height 0)))
|
||||
|
||||
(reg-sub
|
||||
:get-chat
|
||||
:<- [:chats]
|
||||
(fn [chats [_ chat-id]]
|
||||
(get chats chat-id)))
|
||||
|
||||
(reg-sub
|
||||
:get-current-chat
|
||||
:<- [:chats]
|
||||
:<- [:get-current-chat-id]
|
||||
(fn [[chats id]]
|
||||
(get chats id)))
|
||||
(fn [_]
|
||||
(let [current-chat-id (subscribe [:get-current-chat-id])]
|
||||
(subscribe [:get-chat @current-chat-id])))
|
||||
identity)
|
||||
|
||||
(reg-sub
|
||||
:chat
|
||||
|
@ -65,6 +68,78 @@
|
|||
(fn [[chats id] [_ k chat-id]]
|
||||
(get-in chats [(or chat-id id) k])))
|
||||
|
||||
(defn message-datemark-groups
|
||||
"Transforms map of messages into sequence of `[datemark messages]` tuples, where
|
||||
messages with particular datemark are sorted according to their `:clock-value` and
|
||||
tuples themeselves are sorted according to the highest `:clock-value` in the messages."
|
||||
[id->messages]
|
||||
(let [datemark->messages (transduce (comp (map second)
|
||||
(filter :show?)
|
||||
(map (fn [{:keys [timestamp] :as msg}]
|
||||
(assoc msg :datemark (time/day-relative timestamp)))))
|
||||
(completing (fn [acc {:keys [datemark] :as msg}]
|
||||
(update acc datemark conj msg)))
|
||||
{}
|
||||
id->messages)]
|
||||
(->> datemark->messages
|
||||
(map (fn [[datemark messages]]
|
||||
[datemark (sort-by :clock-value > messages)]))
|
||||
(sort-by (comp :clock-value first second) >))))
|
||||
|
||||
(reg-sub
|
||||
:get-chat-message-datemark-groups
|
||||
(fn [[_ chat-id]]
|
||||
(subscribe [:get-chat chat-id]))
|
||||
(fn [{:keys [messages]}]
|
||||
(message-datemark-groups messages)))
|
||||
|
||||
(defn messages-stream
|
||||
"Transforms message-datemark-groups into flat sequence of messages interspersed with
|
||||
datemark messages.
|
||||
Additionaly enhances the messages in message sequence with derived stream context information,
|
||||
like `:same-author?`, `:same-direction?`, `:last?` and `:last-outgoing?` flags + contact info/status
|
||||
message for the last dategroup."
|
||||
[[[last-datemark last-messages] :as message-datemark-groups]]
|
||||
(if (seq message-datemark-groups)
|
||||
(let [messages-seq (mapcat second message-datemark-groups)
|
||||
{last-message-id :message-id} (first messages-seq)
|
||||
{last-outgoing-message-id :message-id} (->> messages-seq
|
||||
(filter :outgoing)
|
||||
first)]
|
||||
;; TODO janherich: why the heck do we display contact user info/status in chat as a message in stream ?
|
||||
;; This makes no sense, user wants to have this information always available, not as something which
|
||||
;; scrolls with message stream
|
||||
(->> (conj (rest message-datemark-groups)
|
||||
[last-datemark (conj (into [] last-messages) {:content-type constants/content-type-status})])
|
||||
(mapcat (fn [[datemark messages]]
|
||||
(let [prepared-messages (into []
|
||||
(map (fn [{:keys [message-id] :as message} previous-message]
|
||||
(assoc message
|
||||
:same-author? (= (:from message)
|
||||
(:from previous-message))
|
||||
:same-direction? (= (:outgoing message)
|
||||
(:outgoing previous-message))
|
||||
:last? (= message-id
|
||||
last-message-id)
|
||||
:last-outgoing? (= message-id
|
||||
last-outgoing-message-id)))
|
||||
messages
|
||||
(concat (rest messages) '(nil))))]
|
||||
(conj prepared-messages {:type :datemark
|
||||
:value datemark}))))))
|
||||
;; when no messages are in chat, we need to at least fake-out today datemark + status messages
|
||||
(list {:content-type constants/content-type-status}
|
||||
{:type :datemark
|
||||
:value (i18n/label :t/datetime-today)})))
|
||||
|
||||
(reg-sub
|
||||
:get-current-chat-messages
|
||||
(fn [_]
|
||||
(let [current-chat-id (subscribe [:get-current-chat-id])]
|
||||
(subscribe [:get-chat-message-datemark-groups @current-chat-id])))
|
||||
(fn [message-datemark-groups]
|
||||
(messages-stream message-datemark-groups)))
|
||||
|
||||
(reg-sub
|
||||
:get-commands-for-chat
|
||||
:<- [:get-commands-responses-by-access-scope]
|
||||
|
@ -80,27 +155,26 @@
|
|||
:<- [:get-current-account]
|
||||
:<- [:get-current-chat]
|
||||
:<- [:get-contacts]
|
||||
:<- [:chat :requests]
|
||||
(fn [[commands-responses account chat contacts requests]]
|
||||
(fn [[commands-responses account {:keys [requests] :as chat} contacts]]
|
||||
(commands-model/requested-responses commands-responses account chat contacts (vals requests))))
|
||||
|
||||
(def ^:private map->sorted-seq (comp (partial map second) (partial sort-by first)))
|
||||
|
||||
(defn- available-commands-responses [[commands-responses input-text]]
|
||||
(defn- available-commands-responses [[commands-responses {:keys [input-text]}]]
|
||||
(->> commands-responses
|
||||
map->sorted-seq
|
||||
(filter #(str/includes? (chat-utils/command-name %) (or input-text "")))))
|
||||
(filter #(string/includes? (chat-utils/command-name %) (or input-text "")))))
|
||||
|
||||
(reg-sub
|
||||
:get-available-commands
|
||||
:<- [:get-commands-for-chat]
|
||||
:<- [:chat :input-text]
|
||||
:<- [:get-current-chat]
|
||||
available-commands-responses)
|
||||
|
||||
(reg-sub
|
||||
:get-available-responses
|
||||
:<- [:get-responses-for-chat]
|
||||
:<- [:chat :input-text]
|
||||
:<- [:get-current-chat]
|
||||
available-commands-responses)
|
||||
|
||||
(reg-sub
|
||||
|
@ -121,10 +195,9 @@
|
|||
(reg-sub
|
||||
:current-chat-argument-position
|
||||
:<- [:selected-chat-command]
|
||||
:<- [:chat :input-text]
|
||||
:<- [:chat :seq-arguments]
|
||||
:<- [:get-current-chat]
|
||||
:<- [:get-current-chat-ui-prop :selection]
|
||||
(fn [[command input-text seq-arguments selection]]
|
||||
(fn [[command {:keys [input-text seq-arguments]} selection]]
|
||||
(input-model/current-chat-argument-position command input-text selection seq-arguments)))
|
||||
|
||||
(reg-sub
|
||||
|
@ -150,9 +223,9 @@
|
|||
:show-parameter-box?
|
||||
:<- [:chat-parameter-box]
|
||||
:<- [:show-suggestions?]
|
||||
:<- [:chat :input-text]
|
||||
:<- [:get-current-chat]
|
||||
:<- [:validation-messages]
|
||||
(fn [[chat-parameter-box show-suggestions? input-text validation-messages]]
|
||||
(fn [[chat-parameter-box show-suggestions? {:keys [input-text]} validation-messages]]
|
||||
(and (get chat-parameter-box :markup)
|
||||
(not validation-messages)
|
||||
(not show-suggestions?))))
|
||||
|
@ -165,24 +238,26 @@
|
|||
(reg-sub
|
||||
:show-suggestions?
|
||||
:<- [:get-current-chat-ui-prop :show-suggestions?]
|
||||
:<- [:chat :input-text]
|
||||
:<- [:get-current-chat]
|
||||
:<- [:selected-chat-command]
|
||||
:<- [:get-available-commands-responses]
|
||||
(fn [[show-suggestions? input-text selected-command commands-responses]]
|
||||
(and (or show-suggestions? (input-model/starts-as-command? (str/trim (or input-text ""))))
|
||||
(fn [[show-suggestions? {:keys [input-text]} selected-command commands-responses]]
|
||||
(and (or show-suggestions? (input-model/starts-as-command? (string/trim (or input-text ""))))
|
||||
(not (:command selected-command))
|
||||
(seq commands-responses))))
|
||||
|
||||
(reg-sub
|
||||
:is-request-answered?
|
||||
:<- [:chat :requests]
|
||||
(fn [requests [_ message-id]]
|
||||
:<- [:get-current-chat]
|
||||
(fn [{:keys [requests]} [_ message-id]]
|
||||
(not= "open" (get-in requests [message-id :status]))))
|
||||
|
||||
(reg-sub
|
||||
:unviewed-messages-count
|
||||
(fn [db [_ chat-id]]
|
||||
(get-in db [:unviewed-messages chat-id :count])))
|
||||
(fn [[_ chat-id]]
|
||||
(subscribe [:get-chat chat-id]))
|
||||
(fn [{:keys [unviewed-messages]}]
|
||||
(count unviewed-messages)))
|
||||
|
||||
(reg-sub
|
||||
:web-view-extra-js
|
||||
|
@ -190,42 +265,17 @@
|
|||
(fn [current-chat]
|
||||
(:web-view-extra-js current-chat)))
|
||||
|
||||
(reg-sub
|
||||
:all-messages-loaded?
|
||||
:<- [:get-current-chat]
|
||||
(fn [current-chat]
|
||||
(:all-loaded? current-chat)))
|
||||
|
||||
(reg-sub
|
||||
:photo-path
|
||||
:<- [:get-contacts]
|
||||
(fn [contacts [_ id]]
|
||||
(:photo-path (contacts id))))
|
||||
|
||||
;; TODO janherich: this is just bad and horribly ineffecient (always sorting to get last msg +
|
||||
;; stale `:last-message` in app-db) refactor messages data-model to properly index them ASAP
|
||||
(reg-sub
|
||||
:get-last-message
|
||||
:<- [:chats]
|
||||
(fn [chats [_ chat-id]]
|
||||
(let [{:keys [last-message messages]} (get chats chat-id)]
|
||||
(->> (conj messages last-message)
|
||||
(sort-by :clock-value >)
|
||||
(filter :show?)
|
||||
first))))
|
||||
|
||||
(reg-sub
|
||||
:get-message-short-preview-markup
|
||||
(fn [db [_ message-id]]
|
||||
(get-in db [:message-data :short-preview message-id :markup])))
|
||||
|
||||
(reg-sub
|
||||
:get-last-message-short-preview
|
||||
(fn [db [_ chat-id]]
|
||||
(let [last-message (subscribe [:get-last-message chat-id])
|
||||
preview (subscribe [:get-message-short-preview-markup (:message-id @last-message)])]
|
||||
(when-let [markup @preview]
|
||||
(commands-utils/generate-hiccup markup)))))
|
||||
(fn [[_ chat-id]]
|
||||
(subscribe [:get-chat-message-datemark-groups chat-id]))
|
||||
(comp first second first))
|
||||
|
||||
(reg-sub
|
||||
:get-default-container-area-height
|
||||
|
@ -250,25 +300,3 @@
|
|||
(fn [db [_ key type]]
|
||||
(let [chat-id (subscribe [:get-current-chat-id])]
|
||||
(get-in db [:chat-animations @chat-id key type]))))
|
||||
|
||||
(reg-sub
|
||||
:get-chat-last-outgoing-message
|
||||
:<- [:chats]
|
||||
(fn [chats [_ chat-id]]
|
||||
(->> (:messages (get chats chat-id))
|
||||
(filter :outgoing)
|
||||
(sort-by :clock-value >)
|
||||
first)))
|
||||
|
||||
(reg-sub
|
||||
:get-message-preview-markup
|
||||
(fn [db [_ message-id]]
|
||||
(get-in db [:message-data :preview message-id :markup])))
|
||||
|
||||
(reg-sub
|
||||
:get-message-preview
|
||||
(fn [[_ message-id]]
|
||||
[(subscribe [:get-message-preview-markup message-id])])
|
||||
(fn [[markup]]
|
||||
(when markup
|
||||
(commands-utils/generate-hiccup markup))))
|
||||
|
|
|
@ -1,47 +1,13 @@
|
|||
(ns status-im.chat.utils
|
||||
(:require [clojure.string :as str]
|
||||
[status-im.constants :as consts]
|
||||
[status-im.chat.constants :as chat-const]))
|
||||
|
||||
(defn console? [s]
|
||||
(= consts/console-chat-id s))
|
||||
|
||||
(def not-console?
|
||||
(complement console?))
|
||||
|
||||
(defn safe-trim [s]
|
||||
(when (string? s)
|
||||
(str/trim s)))
|
||||
(:require [status-im.chat.constants :as chat.constants]))
|
||||
|
||||
(defn add-message-to-db
|
||||
([db add-to-chat-id chat-id message] (add-message-to-db db add-to-chat-id chat-id message true))
|
||||
([db add-to-chat-id chat-id message new?]
|
||||
(let [messages [:chats add-to-chat-id :messages]]
|
||||
(update-in db messages conj (assoc message :chat-id chat-id
|
||||
:new? (if (nil? new?)
|
||||
true
|
||||
new?))))))
|
||||
([db add-to-chat-id chat-id {:keys [message-id] :as message} new?]
|
||||
(let [prepared-message (assoc message
|
||||
:chat-id chat-id
|
||||
:new? (if (nil? new?) true new?))]
|
||||
(update-in db [:chats add-to-chat-id :messages] assoc message-id prepared-message))))
|
||||
|
||||
(defn- check-message [previous-message {:keys [from outgoing] :as message}]
|
||||
(merge message
|
||||
{:same-author (if previous-message
|
||||
(= (:from previous-message) from)
|
||||
true)
|
||||
:same-direction (if previous-message
|
||||
(= (:outgoing previous-message) outgoing)
|
||||
true)}))
|
||||
|
||||
(defn check-author-direction
|
||||
([previous-message message]
|
||||
(check-message previous-message message))
|
||||
([db chat-id message]
|
||||
(let [previous-message (first (get-in db [:chats chat-id :messages]))]
|
||||
(check-message previous-message message))))
|
||||
|
||||
(defn command-name [{:keys [bot name scope]}]
|
||||
(cond
|
||||
(:global? scope)
|
||||
(str chat-const/bot-char name)
|
||||
|
||||
:default
|
||||
(str chat-const/command-char name)))
|
||||
(defn command-name [{:keys [name]}]
|
||||
(str chat.constants/command-char name))
|
||||
|
|
|
@ -121,7 +121,7 @@
|
|||
(let [input (str/trim (or @input-text ""))
|
||||
real-args (remove str/blank? (:args command))]
|
||||
(when-let [placeholder (cond
|
||||
(#{const/command-char const/bot-char} input)
|
||||
(= const/command-char input)
|
||||
(i18n/label :t/type-a-command)
|
||||
|
||||
(and command (empty? real-args))
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
(ns status-im.chat.views.message.datemark
|
||||
(:require [re-frame.core :refer [subscribe dispatch]]
|
||||
[status-im.ui.components.react :refer [view
|
||||
text]]
|
||||
(:require [status-im.ui.components.react :as react]
|
||||
[clojure.string :as str]
|
||||
[status-im.i18n :refer [label]]
|
||||
[status-im.chat.styles.message.datemark :as st]))
|
||||
|
||||
(defn chat-datemark [value]
|
||||
[view st/datemark-wrapper
|
||||
[view st/datemark
|
||||
[text {:style st/datemark-text}
|
||||
(str/capitalize (or value (label :t/datetime-today)))]]])
|
||||
[react/view st/datemark-wrapper
|
||||
[react/view st/datemark
|
||||
[react/text {:style st/datemark-text}
|
||||
(str/capitalize value)]]])
|
||||
|
|
|
@ -1,154 +1,97 @@
|
|||
(ns status-im.chat.views.message.message
|
||||
(:require-macros [status-im.utils.views :refer [defview letsubs]])
|
||||
(:require [re-frame.core :refer [subscribe dispatch]]
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[clojure.walk :as walk]
|
||||
[reagent.core :as r]
|
||||
[status-im.i18n :refer [message-status-label]]
|
||||
[status-im.ui.components.react :refer [view
|
||||
text
|
||||
image
|
||||
icon
|
||||
animated-view
|
||||
touchable-without-feedback
|
||||
touchable-highlight
|
||||
autolink
|
||||
get-dimensions
|
||||
dismiss-keyboard!]]
|
||||
[status-im.ui.components.animation :as anim]
|
||||
[status-im.ui.components.list-selection :refer [share share-or-open-map]]
|
||||
[status-im.chat.constants :as chat-consts]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.components.animation :as animation]
|
||||
[status-im.ui.components.list-selection :as list-selection]
|
||||
[status-im.chat.models.commands :as commands]
|
||||
[status-im.chat.styles.message.message :as st]
|
||||
[status-im.chat.styles.message.command-pill :as pill-st]
|
||||
[status-im.chat.views.message.request-message :refer [message-content-command-request]]
|
||||
[status-im.chat.views.message.datemark :refer [chat-datemark]]
|
||||
[status-im.react-native.resources :as res]
|
||||
[status-im.constants :refer [console-chat-id
|
||||
text-content-type
|
||||
content-type-log-message
|
||||
content-type-status
|
||||
content-type-command
|
||||
content-type-command-request] :as c]
|
||||
[status-im.ui.components.chat-icon.screen :refer [chat-icon-message-status]]
|
||||
[status-im.utils.identicon :refer [identicon]]
|
||||
[status-im.utils.gfycat.core :refer [generate-gfy]]
|
||||
[status-im.commands.utils :as commands.utils]
|
||||
[status-im.chat.utils :as chat.utils]
|
||||
[status-im.chat.styles.message.message :as style]
|
||||
[status-im.chat.styles.message.command-pill :as pill-style]
|
||||
[status-im.chat.views.message.request-message :as request-message]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.ui.components.chat-icon.screen :as chat-icon.screen]
|
||||
[status-im.utils.identicon :as identicon]
|
||||
[status-im.utils.gfycat.core :as gfycat]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.i18n :refer [label
|
||||
get-contact-translated]]
|
||||
[status-im.chat.utils :as cu]
|
||||
[clojure.string :as str]
|
||||
[status-im.chat.events.console :as console]
|
||||
[taoensso.timbre :as log]))
|
||||
[status-im.i18n :as i18n]
|
||||
[clojure.string :as string]
|
||||
[status-im.chat.events.console :as console]))
|
||||
|
||||
(def window-width (:width (get-dimensions "window")))
|
||||
(def window-width (:width (react/get-dimensions "window")))
|
||||
|
||||
(defview message-author-name [{:keys [outgoing from] :as message}]
|
||||
[current-account [:get-current-account]
|
||||
incoming-name [:contact-name-by-identity from]]
|
||||
(if-let [name (if outgoing
|
||||
(:name current-account)
|
||||
(or incoming-name "Unknown contact"))]
|
||||
[text {:style st/author} name]))
|
||||
(letsubs [current-account [:get-current-account]
|
||||
incoming-name [:contact-name-by-identity from]]
|
||||
(when-let [name (if outgoing
|
||||
(:name current-account)
|
||||
(or incoming-name "Unknown contact"))]
|
||||
[react/text {:style style/author} name])))
|
||||
|
||||
(defview message-content-status
|
||||
[{:keys [messages-count content datemark]}]
|
||||
(letsubs [chat-id [:chat :chat-id]
|
||||
group-chat [:chat :group-id]
|
||||
name [:chat :name]
|
||||
color [:chat :color]
|
||||
public-key [:chat :public-key]
|
||||
members [:current-chat-contacts]]
|
||||
(let [{:keys [status]} (if group-chat
|
||||
{:photo-path nil
|
||||
:status nil
|
||||
:last-online 0}
|
||||
(defview message-content-status []
|
||||
(letsubs [{:keys [chat-id group-id name color public-key]} [:get-current-chat]
|
||||
members [:current-chat-contacts]]
|
||||
(let [{:keys [status]} (if group-id
|
||||
{:status nil}
|
||||
(first members))]
|
||||
[view st/status-container
|
||||
[chat-icon-message-status chat-id group-chat name color false]
|
||||
[text {:style st/status-from
|
||||
:font :default
|
||||
:number-of-lines 1}
|
||||
(if (str/blank? name)
|
||||
(generate-gfy public-key)
|
||||
(or (get-contact-translated chat-id :name name)
|
||||
(label :t/chat-name)))]
|
||||
(when (or status content)
|
||||
[text {:style st/status-text
|
||||
:font :default}
|
||||
(or status content)])
|
||||
(if (> messages-count 1)
|
||||
[view st/message-datemark
|
||||
[chat-datemark datemark]]
|
||||
[view st/message-empty-spacing])])))
|
||||
[react/view style/status-container
|
||||
[chat-icon.screen/chat-icon-message-status chat-id group-id name color false]
|
||||
[react/text {:style style/status-from
|
||||
:font :default
|
||||
:number-of-lines 1}
|
||||
(if (string/blank? name)
|
||||
(gfycat/generate-gfy public-key)
|
||||
(or (i18n/get-contact-translated chat-id :name name)
|
||||
(i18n/label :t/chat-name)))]
|
||||
(when status
|
||||
[react/text {:style style/status-text
|
||||
:font :default}
|
||||
status])])))
|
||||
|
||||
(defn message-content-audio [_]
|
||||
[view st/audio-container
|
||||
[view st/play-view
|
||||
[image {;:source res/play
|
||||
:style st/play-image}]]
|
||||
[view st/track-container
|
||||
[view st/track]
|
||||
[view st/track-mark]
|
||||
[text {:style st/track-duration-text
|
||||
:font :default}
|
||||
[react/view style/audio-container
|
||||
[react/view style/play-view
|
||||
[react/image {:style style/play-image}]]
|
||||
[react/view style/track-container
|
||||
[react/view style/track]
|
||||
[react/view style/track-mark]
|
||||
[react/text {:style style/track-duration-text
|
||||
:font :default}
|
||||
"03:39"]]])
|
||||
|
||||
(defn wallet-command-preview
|
||||
[{{:keys [name]} :contact-chat
|
||||
:keys [contact-address params outgoing? current-chat-id]}]
|
||||
(let [{:keys [recipient amount]} (walk/keywordize-keys params)]
|
||||
[text {:style st/command-text
|
||||
:font :default}
|
||||
(label :t/chat-send-eth {:amount amount})]))
|
||||
|
||||
(defn wallet-command? [content-type]
|
||||
(#{c/content-type-wallet-command c/content-type-wallet-request} content-type))
|
||||
|
||||
(defn command-preview
|
||||
[{:keys [params preview content-type] :as message}]
|
||||
(cond
|
||||
(wallet-command? content-type)
|
||||
(wallet-command-preview message)
|
||||
|
||||
preview preview
|
||||
|
||||
:else
|
||||
[text {:style st/command-text
|
||||
:font :default}
|
||||
(if (= 1 (count params))
|
||||
(first (vals params))
|
||||
(str params))]))
|
||||
|
||||
(defview message-content-command
|
||||
[{:keys [message-id content content-type chat-id to from outgoing] :as message}]
|
||||
(letsubs [command [:get-command (:content-command-ref content)]
|
||||
current-chat-id [:get-current-chat-id]
|
||||
contact-chat [:get-in [:chats (if outgoing to from)]]
|
||||
preview [:get-message-preview message-id]]
|
||||
(let [{:keys [name type]
|
||||
icon-path :icon} command]
|
||||
[view st/content-command-view
|
||||
(when (:color command)
|
||||
[view st/command-container
|
||||
[view (pill-st/pill command)
|
||||
[text {:style pill-st/pill-text
|
||||
:font :default}
|
||||
(str chat-consts/command-char name)]]])
|
||||
[{:keys [content params] :as message}]
|
||||
(letsubs [command [:get-command (:content-command-ref content)]]
|
||||
{:component-will-mount #(when-not (:preview content)
|
||||
(re-frame/dispatch [:request-command-message-data
|
||||
message {:data-type :preview
|
||||
:cache-data? true}]))}
|
||||
(let [preview (:preview content)
|
||||
{:keys [type color] icon-path :icon} command]
|
||||
[react/view style/content-command-view
|
||||
(when color
|
||||
[react/view style/command-container
|
||||
[react/view (pill-style/pill command)
|
||||
[react/text {:style pill-style/pill-text
|
||||
:font :default}
|
||||
(chat.utils/command-name command)]]])
|
||||
(when icon-path
|
||||
[view st/command-image-view
|
||||
[icon icon-path st/command-image]])
|
||||
[command-preview {:command (:name command)
|
||||
:content-type content-type
|
||||
:params (:params content)
|
||||
:outgoing? outgoing
|
||||
:preview preview
|
||||
:contact-chat contact-chat
|
||||
:contact-address (if outgoing to from)
|
||||
:current-chat-id current-chat-id}]])))
|
||||
[react/view style/command-image-view
|
||||
[react/icon icon-path style/command-image]])
|
||||
(if (:markup preview)
|
||||
;; Markup was defined for command in jail, generate hiccup and render it
|
||||
(commands.utils/generate-hiccup (:markup preview))
|
||||
;; Display preview if it's defined (as a string), in worst case, render params
|
||||
[react/text {:style style/command-text
|
||||
:font :default}
|
||||
(or preview (str params))])])))
|
||||
|
||||
(defn message-view
|
||||
[{:keys [same-author index group-chat] :as message} content]
|
||||
[view (st/message-view message)
|
||||
[{:keys [group-chat] :as message} content]
|
||||
[react/view (style/message-view message)
|
||||
(when group-chat [message-author-name message])
|
||||
content])
|
||||
|
||||
|
@ -156,33 +99,31 @@
|
|||
{"\\*[^*]+\\*" {:font-weight :bold}
|
||||
"~[^~]+~" {:font-style :italic}})
|
||||
|
||||
(def regx (re-pattern (str/join "|" (map first replacements))))
|
||||
(def regx (re-pattern (string/join "|" (map first replacements))))
|
||||
|
||||
(defn get-style [string]
|
||||
(->> replacements
|
||||
(into [] (comp
|
||||
(map first)
|
||||
(map #(vector % (re-pattern %)))
|
||||
(drop-while (fn [[_ regx]] (not (re-matches regx string))))
|
||||
(take 1)))
|
||||
(into [] (comp (map first)
|
||||
(map #(vector % (re-pattern %)))
|
||||
(drop-while (fn [[_ regx]] (not (re-matches regx string))))
|
||||
(take 1)))
|
||||
ffirst
|
||||
replacements))
|
||||
|
||||
;; todo rewrite this, naive implementation
|
||||
(defn- parse-text [string]
|
||||
(if (string? string)
|
||||
(let [general-text (str/split string regx)
|
||||
(let [general-text (string/split string regx)
|
||||
general-text' (if (zero? (count general-text))
|
||||
[nil]
|
||||
general-text)
|
||||
styled-text (vec (map-indexed
|
||||
(fn [idx string]
|
||||
(let [style (get-style string)]
|
||||
[text
|
||||
{:key (str idx "_" string)
|
||||
:style style}
|
||||
(subs string 1 (dec (count string)))]))
|
||||
(re-seq regx string)))
|
||||
styled-text (vec (map-indexed (fn [idx string]
|
||||
(let [style (get-style string)]
|
||||
[react/text
|
||||
{:key (str idx "_" string)
|
||||
:style style}
|
||||
(subs string 1 (dec (count string)))]))
|
||||
(re-seq regx string)))
|
||||
styled-text' (if (> (count general-text)
|
||||
(count styled-text))
|
||||
(conj styled-text nil)
|
||||
|
@ -197,41 +138,31 @@
|
|||
simple-text? (and (= (count parsed-text) 2)
|
||||
(nil? (second parsed-text)))]
|
||||
(if simple-text?
|
||||
[autolink {:style (st/text-message message)
|
||||
:text (apply str parsed-text)
|
||||
:onPress #(dispatch [:browse-link-from-message %])}]
|
||||
[text {:style (st/text-message message)} parsed-text]))])
|
||||
[react/autolink {:style (style/text-message message)
|
||||
:text (apply str parsed-text)
|
||||
:onPress #(re-frame/dispatch [:browse-link-from-message %])}]
|
||||
[react/text {:style (style/text-message message)} parsed-text]))])
|
||||
|
||||
(defmulti message-content (fn [_ message _] (message :content-type)))
|
||||
|
||||
(defmethod message-content content-type-command-request
|
||||
(defmethod message-content constants/content-type-command-request
|
||||
[wrapper message]
|
||||
[wrapper message
|
||||
[message-view message [message-content-command-request message]]])
|
||||
[message-view message [request-message/message-content-command-request message]]])
|
||||
|
||||
(defmethod message-content c/content-type-wallet-request
|
||||
[wrapper message]
|
||||
[wrapper message
|
||||
[message-view message [message-content-command-request message]]])
|
||||
|
||||
(defmethod message-content text-content-type
|
||||
(defmethod message-content constants/text-content-type
|
||||
[wrapper message]
|
||||
[wrapper message [text-message message]])
|
||||
|
||||
(defmethod message-content content-type-log-message
|
||||
(defmethod message-content constants/content-type-log-message
|
||||
[wrapper message]
|
||||
[wrapper message [text-message message]])
|
||||
|
||||
(defmethod message-content content-type-status
|
||||
(defmethod message-content constants/content-type-status
|
||||
[_ message]
|
||||
[message-content-status message])
|
||||
[message-content-status])
|
||||
|
||||
(defmethod message-content content-type-command
|
||||
[wrapper message]
|
||||
[wrapper message
|
||||
[message-view message [message-content-command message]]])
|
||||
|
||||
(defmethod message-content c/content-type-wallet-command
|
||||
(defmethod message-content constants/content-type-command
|
||||
[wrapper message]
|
||||
[wrapper message
|
||||
[message-view message [message-content-command message]]])
|
||||
|
@ -244,162 +175,150 @@
|
|||
:content-type content-type}]]])
|
||||
|
||||
(defview group-message-delivery-status [{:keys [message-id group-id message-status user-statuses] :as msg}]
|
||||
[app-db-message-user-statuses [:get-in [:message-data :user-statuses message-id]]
|
||||
app-db-message-status-value [:get-in [:message-data :statuses message-id :status]]
|
||||
chat [:get-current-chat]
|
||||
contacts [:get-contacts]]
|
||||
(let [status (or message-status app-db-message-status-value :sending)
|
||||
user-statuses (merge user-statuses app-db-message-user-statuses)
|
||||
participants (:contacts chat)
|
||||
seen-by-everyone? (and (= (count user-statuses) (count participants))
|
||||
(every? (fn [[_ {:keys [status]}]]
|
||||
(= (keyword status) :seen)) user-statuses))]
|
||||
(if (or (zero? (count user-statuses))
|
||||
seen-by-everyone?)
|
||||
[view st/delivery-view
|
||||
[text {:style st/delivery-text
|
||||
:font :default}
|
||||
(message-status-label
|
||||
(if seen-by-everyone?
|
||||
:seen-by-everyone
|
||||
status))]]
|
||||
[touchable-highlight
|
||||
{:on-press (fn []
|
||||
(dispatch [:show-message-details {:message-status status
|
||||
:user-statuses user-statuses
|
||||
:participants participants}]))}
|
||||
[view st/delivery-view
|
||||
(for [[_ {:keys [whisper-identity]}] (take 3 user-statuses)]
|
||||
^{:key whisper-identity}
|
||||
[image {:source {:uri (or (get-in contacts [whisper-identity :photo-path])
|
||||
(identicon whisper-identity))}
|
||||
:style {:width 16
|
||||
:height 16
|
||||
:borderRadius 8}}])
|
||||
(if (> (count user-statuses) 3)
|
||||
[text {:style st/delivery-text
|
||||
:font :default}
|
||||
(str "+ " (- (count user-statuses) 3))])]])))
|
||||
(letsubs [chat [:get-current-chat]
|
||||
contacts [:get-contacts]]
|
||||
(let [status (or message-status :sending)
|
||||
participants (:contacts chat)
|
||||
seen-by-everyone? (and (= (count user-statuses) (count participants))
|
||||
(every? (fn [[_ {:keys [status]}]]
|
||||
(= (keyword status) :seen)) user-statuses))]
|
||||
(if (or (zero? (count user-statuses))
|
||||
seen-by-everyone?)
|
||||
[react/view style/delivery-view
|
||||
[react/text {:style style/delivery-text
|
||||
:font :default}
|
||||
(i18n/message-status-label
|
||||
(if seen-by-everyone?
|
||||
:seen-by-everyone
|
||||
status))]]
|
||||
[react/touchable-highlight
|
||||
{:on-press (fn []
|
||||
(re-frame/dispatch [:show-message-details {:message-status status
|
||||
:user-statuses user-statuses
|
||||
:participants participants}]))}
|
||||
[react/view style/delivery-view
|
||||
(for [[_ {:keys [whisper-identity]}] (take 3 user-statuses)]
|
||||
^{:key whisper-identity}
|
||||
[react/image {:source {:uri (or (get-in contacts [whisper-identity :photo-path])
|
||||
(identicon/identicon whisper-identity))}
|
||||
:style {:width 16
|
||||
:height 16
|
||||
:borderRadius 8}}])
|
||||
(if (> (count user-statuses) 3)
|
||||
[react/text {:style style/delivery-text
|
||||
:font :default}
|
||||
(str "+ " (- (count user-statuses) 3))])]]))))
|
||||
|
||||
(defview message-delivery-status
|
||||
(defn message-delivery-status
|
||||
[{:keys [message-id chat-id message-status user-statuses content]}]
|
||||
[app-db-message-status-value [:get-in [:message-data :statuses message-id :status]]]
|
||||
(let [delivery-status (get-in user-statuses [chat-id :status])
|
||||
status (cond (and (not (console/commands-with-delivery-status (:command content)))
|
||||
(cu/console? chat-id))
|
||||
(= constants/console-chat-id chat-id))
|
||||
:seen
|
||||
|
||||
:else
|
||||
(or delivery-status message-status app-db-message-status-value :sending))]
|
||||
[view st/delivery-view
|
||||
[text {:style st/delivery-text
|
||||
:font :default}
|
||||
(message-status-label status)]]))
|
||||
(or delivery-status message-status :sending))]
|
||||
[react/view style/delivery-view
|
||||
[react/text {:style style/delivery-text
|
||||
:font :default}
|
||||
(i18n/message-status-label status)]]))
|
||||
|
||||
(defview member-photo [from]
|
||||
[photo-path [:photo-path from]]
|
||||
[view
|
||||
[image {:source {:uri (if (str/blank? photo-path)
|
||||
(identicon from)
|
||||
photo-path)}
|
||||
:style st/photo}]])
|
||||
(letsubs [photo-path [:photo-path from]]
|
||||
[react/view
|
||||
[react/image {:source {:uri (if (string/blank? photo-path)
|
||||
(identicon/identicon from)
|
||||
photo-path)}
|
||||
:style style/photo}]]))
|
||||
|
||||
(defview my-photo [from]
|
||||
[account [:get-current-account]]
|
||||
(let [{:keys [photo-path]} account]
|
||||
[view
|
||||
[image {:source {:uri (if (str/blank? photo-path)
|
||||
(identicon from)
|
||||
photo-path)}
|
||||
:style st/photo}]]))
|
||||
(letsubs [account [:get-current-account]]
|
||||
(let [{:keys [photo-path]} account]
|
||||
[react/view
|
||||
[react/image {:source {:uri (if (string/blank? photo-path)
|
||||
(identicon/identicon from)
|
||||
photo-path)}
|
||||
:style style/photo}]])))
|
||||
|
||||
(defn message-body
|
||||
[{:keys [last-outgoing? message-type same-author from index outgoing] :as message} content]
|
||||
(let [delivery-status :seen-by-everyone]
|
||||
[view st/group-message-wrapper
|
||||
[view (st/message-body message)
|
||||
[view st/message-author
|
||||
(when (or (= index 1) (not same-author))
|
||||
(if outgoing
|
||||
[my-photo from]
|
||||
[member-photo from]))]
|
||||
[view (st/group-message-view message)
|
||||
content
|
||||
(when last-outgoing?
|
||||
(if (= (keyword message-type) :group-user-message)
|
||||
[group-message-delivery-status message]
|
||||
[message-delivery-status message]))]]]))
|
||||
[{:keys [last-outgoing? message-type same-author? from outgoing] :as message} content]
|
||||
[react/view style/group-message-wrapper
|
||||
[react/view (style/message-body message)
|
||||
[react/view style/message-author
|
||||
(when-not same-author?
|
||||
(if outgoing
|
||||
[my-photo from]
|
||||
[member-photo from]))]
|
||||
[react/view (style/group-message-view message)
|
||||
content
|
||||
(when last-outgoing?
|
||||
(if (= (keyword message-type) :group-user-message)
|
||||
[group-message-delivery-status message]
|
||||
[message-delivery-status message]))]]])
|
||||
|
||||
(defn message-container-animation-logic [{:keys [to-value val callback]}]
|
||||
(fn [_]
|
||||
(let [to-value @to-value]
|
||||
(when (pos? to-value)
|
||||
(anim/start
|
||||
(anim/timing val {:toValue to-value
|
||||
:duration 250})
|
||||
(animation/start
|
||||
(animation/timing val {:toValue to-value
|
||||
:duration 250})
|
||||
(fn [arg]
|
||||
(when (.-finished arg)
|
||||
(callback))))))))
|
||||
|
||||
(defn message-container [message & children]
|
||||
(if (:new? message)
|
||||
(let [layout-height (r/atom 0)
|
||||
anim-value (anim/create-value 1)
|
||||
anim-callback #(dispatch [:set-message-shown message])
|
||||
(let [layout-height (reagent/atom 0)
|
||||
anim-value (animation/create-value 1)
|
||||
anim-callback #(re-frame/dispatch [:set-message-shown message])
|
||||
context {:to-value layout-height
|
||||
:val anim-value
|
||||
:callback anim-callback}
|
||||
on-update (message-container-animation-logic context)]
|
||||
(r/create-class
|
||||
(reagent/create-class
|
||||
{:component-did-update
|
||||
on-update
|
||||
:display-name "message-container"
|
||||
:reagent-render
|
||||
(fn [_ & children]
|
||||
@layout-height
|
||||
[animated-view {:style (st/message-animated-container anim-value)}
|
||||
(into [view {:style (st/message-container window-width)
|
||||
:onLayout (fn [event]
|
||||
(let [height (.. event -nativeEvent -layout -height)]
|
||||
(reset! layout-height height)))}]
|
||||
[react/animated-view {:style (style/message-animated-container anim-value)}
|
||||
(into [react/view {:style (style/message-container window-width)
|
||||
:onLayout (fn [event]
|
||||
(let [height (.. event -nativeEvent -layout -height)]
|
||||
(reset! layout-height height)))}]
|
||||
children)])}))
|
||||
(into [view] children)))
|
||||
(into [react/view] children)))
|
||||
|
||||
(defn chat-message [{:keys [outgoing message-id chat-id user-statuses from] :as message}]
|
||||
(let [my-identity (subscribe [:get :current-public-key])
|
||||
status (subscribe [:get-in [:message-data :user-statuses message-id my-identity]])
|
||||
preview (subscribe [:get-message-preview message-id])]
|
||||
(r/create-class
|
||||
{:display-name "chat-message"
|
||||
:component-will-mount
|
||||
(fn []
|
||||
(let [{:keys [bot command] :as content} (get-in message [:content])
|
||||
message' (assoc message :jail-id bot)]
|
||||
(when (and command (not @preview))
|
||||
(dispatch [:request-command-preview message']))))
|
||||
|
||||
:component-did-mount
|
||||
(fn []
|
||||
(when (and (not outgoing)
|
||||
(not= :seen (keyword @status))
|
||||
(not= :seen (keyword (get-in user-statuses [@my-identity :status]))))
|
||||
(dispatch [:send-seen! {:chat-id chat-id
|
||||
:from from
|
||||
:message-id message-id}])))
|
||||
:reagent-render
|
||||
(fn [{:keys [outgoing group-chat content-type content] :as message}]
|
||||
[message-container message
|
||||
[touchable-highlight {:on-press #(when platform/ios?
|
||||
(dispatch [:set-chat-ui-props
|
||||
{:show-emoji? false}])
|
||||
(dismiss-keyboard!))
|
||||
:on-long-press #(cond (= content-type text-content-type)
|
||||
(share content (label :t/message))
|
||||
(and (= content-type content-type-command) (= "location" (:content-command content)))
|
||||
(let [address (get-in content [:params :address])
|
||||
[location lat long] (str/split address #"&")]
|
||||
(share-or-open-map location lat long)))}
|
||||
[view
|
||||
(let [incoming-group (and group-chat (not outgoing))]
|
||||
[message-content message-body (merge message
|
||||
{:incoming-group incoming-group})])]]])})))
|
||||
(defn chat-message [{:keys [outgoing message-id chat-id message-status user-statuses
|
||||
from current-public-key] :as message}]
|
||||
(reagent/create-class
|
||||
{:display-name "chat-message"
|
||||
:component-did-mount
|
||||
#(when (and message-id
|
||||
chat-id
|
||||
(not outgoing)
|
||||
(not= :seen message-status)
|
||||
(not= :seen (keyword (get-in user-statuses [current-public-key :status]))))
|
||||
(re-frame/dispatch [:send-seen! {:chat-id chat-id
|
||||
:from from
|
||||
:message-id message-id}]))
|
||||
:reagent-render
|
||||
(fn [{:keys [outgoing group-chat content-type content] :as message}]
|
||||
[message-container message
|
||||
[react/touchable-highlight {:on-press #(when platform/ios?
|
||||
(re-frame/dispatch [:set-chat-ui-props
|
||||
{:show-emoji? false}])
|
||||
(react/dismiss-keyboard!))
|
||||
:on-long-press #(cond (= content-type constants/text-content-type)
|
||||
(list-selection/share content (i18n/label :t/message))
|
||||
(and (= content-type constants/content-type-command)
|
||||
(= "location" (:content-command content)))
|
||||
(let [address (get-in content [:params :address])
|
||||
[location lat long] (string/split address #"&")]
|
||||
(list-selection/share-or-open-map location lat long)))}
|
||||
[react/view
|
||||
(let [incoming-group (and group-chat (not outgoing))]
|
||||
[message-content message-body (merge message
|
||||
{:incoming-group incoming-group})])]]])}))
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
touchable-highlight]]
|
||||
[status-im.chat.styles.message.message :as st]
|
||||
[status-im.chat.models.commands :as commands]
|
||||
[status-im.commands.utils :as commands-utils]
|
||||
[status-im.ui.components.animation :as anim]
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
|
@ -72,12 +73,15 @@
|
|||
[icon command-icon st/command-request-image])]]))})))
|
||||
|
||||
(defview message-content-command-request
|
||||
[{:keys [message-id chat-id content from incoming-group] :as message}]
|
||||
[{:keys [message-id content] :as message}]
|
||||
(letsubs [command [:get-command (:content-command-ref content)]
|
||||
answered? [:is-request-answered? message-id]
|
||||
status-initialized? [:get :status-module-initialized?]
|
||||
markup [:get-message-preview message-id]]
|
||||
(let [{:keys [prefill prefill-bot-db prefillBotDb params]
|
||||
status-initialized? [:get :status-module-initialized?]]
|
||||
{:component-will-mount #(when-not (:preview content)
|
||||
(dispatch [:request-command-message-data
|
||||
message {:data-type :preview
|
||||
:cache-data? true}]))}
|
||||
(let [{:keys [prefill prefill-bot-db prefillBotDb params preview]
|
||||
text-content :text} content
|
||||
command (if (and params command)
|
||||
(merge command {:prefill prefill
|
||||
|
@ -91,12 +95,11 @@
|
|||
[touchable-highlight
|
||||
{:on-press on-press-handler}
|
||||
[view st/command-request-message-view
|
||||
(if (and markup
|
||||
(not (string? markup)))
|
||||
[view markup]
|
||||
(if (:markup preview)
|
||||
[view (commands-utils/generate-hiccup (:markup preview))]
|
||||
[text {:style st/style-message-text
|
||||
:font :default}
|
||||
(or text-content markup (:content content))])]]
|
||||
(or preview text-content (:content content))])]]
|
||||
(when (:request-text command)
|
||||
[view st/command-request-text-view
|
||||
[text {:style st/style-sub-text
|
||||
|
|
|
@ -14,8 +14,6 @@
|
|||
(def content-type-log-message "log-message")
|
||||
(def content-type-command "command")
|
||||
(def content-type-command-request "command-request")
|
||||
(def content-type-wallet-command "wallet-command")
|
||||
(def content-type-wallet-request "wallet-request")
|
||||
(def content-type-status "status")
|
||||
|
||||
(def min-password-length 6)
|
||||
|
|
|
@ -20,9 +20,7 @@
|
|||
|
||||
(defn save
|
||||
[{:keys [last-message-id chat-id] :as chat}]
|
||||
;; TODO(janherich): remove `:last-message-id`, seems like it's not used anywhere anymore
|
||||
(let [chat (assoc chat :last-message-id (or last-message-id ""))]
|
||||
(data-store/save chat (data-store/exists? chat-id))))
|
||||
(data-store/save chat (data-store/exists? chat-id)))
|
||||
|
||||
(defn delete
|
||||
[chat-id]
|
||||
|
|
|
@ -9,15 +9,12 @@
|
|||
(defn- command-type?
|
||||
[type]
|
||||
(contains?
|
||||
#{constants/content-type-command constants/content-type-command-request
|
||||
constants/content-type-wallet-request constants/content-type-wallet-command}
|
||||
#{constants/content-type-command constants/content-type-command-request}
|
||||
type))
|
||||
|
||||
(def default-values
|
||||
{:outgoing false
|
||||
:to nil
|
||||
:same-author false
|
||||
:same-direction false
|
||||
:preview nil})
|
||||
|
||||
(defn exists? [message-id]
|
||||
|
@ -32,10 +29,6 @@
|
|||
(when (command-type? content-type)
|
||||
(reader/read-string content))))
|
||||
|
||||
(defn get-count-by-chat-id
|
||||
[chat-id]
|
||||
(data-store/get-count-by-chat-id chat-id))
|
||||
|
||||
(defn get-by-chat-id
|
||||
([chat-id]
|
||||
(get-by-chat-id chat-id 0))
|
||||
|
@ -53,13 +46,6 @@
|
|||
(filter #(= (:content-type %) constants/content-type-log-message))
|
||||
(map #(select-keys % [:content :timestamp]))))
|
||||
|
||||
(defn get-last-message
|
||||
[chat-id]
|
||||
(if-let [{:keys [content-type] :as message} (data-store/get-last-message chat-id)]
|
||||
(if (command-type? content-type)
|
||||
(update message :content reader/read-string)
|
||||
message)))
|
||||
|
||||
(defn get-last-outgoing
|
||||
[chat-id number-of-messages]
|
||||
(data-store/get-by-fields {:chat-id chat-id
|
||||
|
@ -77,25 +63,19 @@
|
|||
[]
|
||||
(data-store/get-unviewed))
|
||||
|
||||
(defn get-previews
|
||||
[]
|
||||
(->> (data-store/get-all-as-list)
|
||||
(filter :preview)
|
||||
(reduce (fn [acc {:keys [message-id preview]}]
|
||||
(assoc acc message-id (reader/read-string preview)))
|
||||
{})))
|
||||
|
||||
(defn- prepare-content [content]
|
||||
(pr-str
|
||||
(update content :params dissoc :password :password-confirmation)))
|
||||
(if (string? content)
|
||||
content
|
||||
(pr-str
|
||||
;; TODO janherich: this is ugly and not systematic, define something like `:not-persisent`
|
||||
;; option for command params instead
|
||||
(update content :params dissoc :password :password-confirmation))))
|
||||
|
||||
(defn save
|
||||
;; todo remove chat-id parameter
|
||||
[chat-id {:keys [message-id content] :as message}]
|
||||
(when-not (data-store/exists? message-id)
|
||||
(let [content' (if (string? content)
|
||||
content
|
||||
(prepare-content content))
|
||||
(let [content' (prepare-content content)
|
||||
message' (merge default-values
|
||||
message
|
||||
{:chat-id chat-id
|
||||
|
@ -106,7 +86,9 @@
|
|||
(defn update-message
|
||||
[{:keys [message-id] :as message}]
|
||||
(when (data-store/exists? message-id)
|
||||
(let [message (utils/update-if-present message :user-statuses vals)]
|
||||
(let [message (-> message
|
||||
(utils/update-if-present :user-statuses vals)
|
||||
(utils/update-if-present :content prepare-content))]
|
||||
(data-store/save message))))
|
||||
|
||||
(defn delete-by-chat-id [chat-id]
|
||||
|
|
|
@ -17,9 +17,6 @@
|
|||
(realm/fix-map message :user-statuses :whisper-identity)))
|
||||
|
||||
(defn get-by-chat-id
|
||||
"arity-1 returns realm object for queries"
|
||||
([chat-id]
|
||||
(realm/get-by-field @realm/account-realm :message :chat-id chat-id))
|
||||
([chat-id number-of-messages]
|
||||
(get-by-chat-id chat-id 0 number-of-messages))
|
||||
([chat-id from number-of-messages]
|
||||
|
@ -29,10 +26,6 @@
|
|||
realm/js-object->clj)]
|
||||
(mapv #(realm/fix-map % :user-statuses :whisper-identity) messages))))
|
||||
|
||||
(defn get-count-by-chat-id
|
||||
[chat-id]
|
||||
(realm/get-count (get-by-chat-id chat-id)))
|
||||
|
||||
(defn get-by-fields
|
||||
[fields from number-of-messages]
|
||||
(-> (realm/get-by-fields @realm/account-realm :message :and fields)
|
||||
|
@ -64,5 +57,6 @@
|
|||
|
||||
(defn delete-by-chat-id
|
||||
[chat-id]
|
||||
(realm/delete @realm/account-realm
|
||||
(get-by-chat-id chat-id)))
|
||||
(let [current-realm @realm/account-realm]
|
||||
(realm/delete current-realm
|
||||
(realm/get-by-field current-realm :message :chat-id chat-id))))
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
(ns status-im.data-store.realm.schemas.account.v19.chat
|
||||
(:require [status-im.ui.components.styles :refer [default-chat-color]]))
|
||||
|
||||
(def schema {:name :chat
|
||||
:primaryKey :chat-id
|
||||
:properties {:chat-id :string
|
||||
:name :string
|
||||
:color {:type :string
|
||||
:default default-chat-color}
|
||||
:group-chat {:type :bool
|
||||
:indexed true}
|
||||
:group-admin {:type :string
|
||||
:optional true}
|
||||
:is-active :bool
|
||||
:timestamp :int
|
||||
:contacts {:type :list
|
||||
:objectType :chat-contact}
|
||||
:unremovable? {:type :bool
|
||||
:default false}
|
||||
:removed-at {:type :int
|
||||
:optional true}
|
||||
:removed-from-at {:type :int
|
||||
:optional true}
|
||||
:added-to-at {:type :int
|
||||
:optional true}
|
||||
:updated-at {:type :int
|
||||
:optional true}
|
||||
:message-overhead {:type :int
|
||||
:default 0}
|
||||
:public-key {:type :string
|
||||
:optional true}
|
||||
:private-key {:type :string
|
||||
:optional true}
|
||||
:contact-info {:type :string
|
||||
:optional true}
|
||||
:debug? {:type :bool
|
||||
:default false}
|
||||
:public? {:type :bool
|
||||
:default false}}})
|
|
@ -1,9 +1,9 @@
|
|||
(ns status-im.data-store.realm.schemas.account.v19.core
|
||||
(:require [status-im.data-store.realm.schemas.account.v11.chat :as chat]
|
||||
(:require [status-im.data-store.realm.schemas.account.v19.chat :as chat]
|
||||
[status-im.data-store.realm.schemas.account.v1.chat-contact :as chat-contact]
|
||||
[status-im.data-store.realm.schemas.account.v19.contact :as contact]
|
||||
[status-im.data-store.realm.schemas.account.v1.discover :as discover]
|
||||
[status-im.data-store.realm.schemas.account.v10.message :as message]
|
||||
[status-im.data-store.realm.schemas.account.v19.message :as message]
|
||||
[status-im.data-store.realm.schemas.account.v12.pending-message :as pending-message]
|
||||
[status-im.data-store.realm.schemas.account.v1.processed-message :as processed-message]
|
||||
[status-im.data-store.realm.schemas.account.v19.request :as request]
|
||||
|
@ -29,12 +29,19 @@
|
|||
group-contact/schema
|
||||
local-storage/schema])
|
||||
|
||||
(defn remove-console-intro-message! [new-realm]
|
||||
(when-let [console-intro-message (some-> new-realm
|
||||
(.objects "message")
|
||||
(.filtered (str "message-id = \"intro-status\""))
|
||||
(aget 0))]
|
||||
(log/debug "v19 Removing console intro message " (pr-str console-intro-message))))
|
||||
|
||||
(defn remove-contact! [new-realm whisper-identity]
|
||||
(when-let [contact (some-> new-realm
|
||||
(.objects "contact")
|
||||
(.filtered (str "whisper-identity = \"" whisper-identity "\""))
|
||||
(aget 0))]
|
||||
(log/debug "v19 Removing contact" (pr-str contact))
|
||||
(log/debug "v19 Removing contact " (pr-str contact))
|
||||
(.delete new-realm contact)))
|
||||
|
||||
(def owner-command->new-props
|
||||
|
@ -75,9 +82,13 @@
|
|||
|
||||
(def transactor-requests->new-props
|
||||
{;; former transactor-personal request
|
||||
["send" 1] {:content-command-ref ["transactor" :response 83 "send"]}
|
||||
["send" 1] {:content-command-ref ["transactor" :response 83 "send"]
|
||||
:content-command-scope-bitmask 83
|
||||
:bot "transactor"}
|
||||
;; former transactor-group request
|
||||
["send" 2] {:content-command-ref ["transactor" :response 85 "send"]}})
|
||||
["send" 2] {:content-command-ref ["transactor" :response 85 "send"]
|
||||
:content-command-scope-bitmask 85
|
||||
:bot "transactor"}})
|
||||
|
||||
(defn update-commands [selector mapping new-realm content-type]
|
||||
(some-> new-realm
|
||||
|
@ -94,6 +105,7 @@
|
|||
(log/debug "migrating v19 account database: " old-realm new-realm)
|
||||
(remove-contact! new-realm "transactor-personal")
|
||||
(remove-contact! new-realm "transactor-group")
|
||||
(remove-console-intro-message! new-realm)
|
||||
(update-commands (juxt :bot :command) owner-command->new-props new-realm "command")
|
||||
(update-commands (juxt :command) console-requests->new-props new-realm "command-request")
|
||||
(update-commands (juxt :command (comp count :prefill)) transactor-requests->new-props new-realm "command-request"))
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
(ns status-im.data-store.realm.schemas.account.v19.message)
|
||||
|
||||
(def schema {:name :message
|
||||
:primaryKey :message-id
|
||||
:properties {:message-id :string
|
||||
:from :string
|
||||
:to {:type :string
|
||||
:optional true}
|
||||
:group-id {:type :string
|
||||
:optional true}
|
||||
:content :string ; TODO make it ArrayBuffer
|
||||
:content-type :string
|
||||
:username {:type :string
|
||||
:optional true}
|
||||
:timestamp :int
|
||||
:chat-id {:type :string
|
||||
:indexed true}
|
||||
:outgoing :bool
|
||||
:retry-count {:type :int
|
||||
:default 0}
|
||||
:message-type {:type :string
|
||||
:optional true}
|
||||
:message-status {:type :string
|
||||
:optional true}
|
||||
:user-statuses {:type :list
|
||||
:objectType "user-status"}
|
||||
:clock-value {:type :int
|
||||
:default 0}
|
||||
:show? {:type :bool
|
||||
:default true}}})
|
|
@ -33,7 +33,7 @@
|
|||
(cond
|
||||
(= path [:responses "password" :preview])
|
||||
(callback {:result {:context {},
|
||||
:messages [],
|
||||
:messages {},
|
||||
:returned {:markup ["text"
|
||||
{:style
|
||||
{:color "black",
|
||||
|
|
|
@ -309,6 +309,11 @@
|
|||
|
||||
;;; MESSAGES
|
||||
|
||||
(defn- transform-protocol-message [{:keys [from to payload]}]
|
||||
(merge payload {:from from
|
||||
:to to
|
||||
:chat-id from}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:incoming-message
|
||||
(fn [_ [_ type {:keys [payload ttl id] :as message}]]
|
||||
|
@ -320,31 +325,31 @@
|
|||
:type type
|
||||
:ttl (+ (datetime/now-ms) ttl-s)}
|
||||
route-event (case type
|
||||
:message [:received-protocol-message! message]
|
||||
:group-message [:received-protocol-message! message]
|
||||
:public-group-message [:received-protocol-message! message]
|
||||
:ack (if (#{:message :group-message} (:type payload))
|
||||
[:update-message-status message :delivered]
|
||||
[:pending-message-remove message])
|
||||
:seen [:update-message-status message :seen]
|
||||
:group-invitation [:group-chat-invite-received message]
|
||||
:update-group [:update-group-message message]
|
||||
:add-group-identity [:participant-invited-to-group message]
|
||||
:remove-group-identity [:participant-removed-from-group message]
|
||||
:leave-group [:participant-left-group message]
|
||||
:contact-request [:contact-request-received message]
|
||||
:discover [:status-received message]
|
||||
:discoveries-request [:discoveries-request-received message]
|
||||
:discoveries-response [:discoveries-response-received message]
|
||||
:profile [:contact-update-received message]
|
||||
:update-keys [:update-keys-received message]
|
||||
:online [:contact-online-received message]
|
||||
:pending [:pending-message-upsert message]
|
||||
:sent (let [{:keys [to id group-id]} message
|
||||
message' {:from to
|
||||
:payload {:message-id id
|
||||
:group-id group-id}}]
|
||||
[:update-message-status message' :sent])
|
||||
(:message
|
||||
:group-message
|
||||
:public-group-message) [:received-message (transform-protocol-message message)]
|
||||
:ack (if (#{:message :group-message} (:type payload))
|
||||
[:update-message-status message :delivered]
|
||||
[:pending-message-remove message])
|
||||
:seen [:update-message-status message :seen]
|
||||
:group-invitation [:group-chat-invite-received message]
|
||||
:update-group [:update-group-message message]
|
||||
:add-group-identity [:participant-invited-to-group message]
|
||||
:remove-group-identity [:participant-removed-from-group message]
|
||||
:leave-group [:participant-left-group message]
|
||||
:contact-request [:contact-request-received message]
|
||||
:discover [:status-received message]
|
||||
:discoveries-request [:discoveries-request-received message]
|
||||
:discoveries-response [:discoveries-response-received message]
|
||||
:profile [:contact-update-received message]
|
||||
:update-keys [:update-keys-received message]
|
||||
:online [:contact-online-received message]
|
||||
:pending [:pending-message-upsert message]
|
||||
:sent (let [{:keys [to id group-id]} message
|
||||
message' {:from to
|
||||
:payload {:message-id id
|
||||
:group-id group-id}}]
|
||||
[:update-message-status message' :sent])
|
||||
nil)]
|
||||
(when (nil? route-event) (debug "Unknown message type" type))
|
||||
(cache/add! processed-message)
|
||||
|
@ -353,15 +358,20 @@
|
|||
(when route-event {:dispatch route-event})))))))
|
||||
|
||||
(defn update-message-status [db {:keys [message-id ack-of-message group-id from status]}]
|
||||
(let [message-id' (or ack-of-message message-id)
|
||||
group? (boolean group-id)
|
||||
status-path (if (and group? (not= status :sent))
|
||||
[:message-data :user-statuses message-id' from]
|
||||
[:message-data :statuses message-id'])
|
||||
{current-status :status} (get-in db status-path)]
|
||||
(if-not (= :seen current-status)
|
||||
(assoc-in db status-path {:whisper-identity from
|
||||
:status status})
|
||||
(let [message-id' (or ack-of-message message-id)
|
||||
update-group-status? (and group-id (not= status :sent))
|
||||
message-path [:chats (or group-id from) :messages message-id']
|
||||
current-status (if update-group-status?
|
||||
(get-in db (into message-path [:user-statuses from :status]))
|
||||
(get-in db (into message-path [:message-status])))]
|
||||
;; for some strange reason, we sometimes receive status update for message we don't have,
|
||||
;; that's why the first condition in if
|
||||
(if (and (get-in db message-path)
|
||||
(not= :seen current-status))
|
||||
(if update-group-status?
|
||||
(assoc-in db (into message-path [:user-statuses from]) {:whisper-identity from
|
||||
:status status})
|
||||
(assoc-in db (into message-path [:message-status]) status))
|
||||
db)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
[view pending-inner-circle]]]))
|
||||
|
||||
(defview chat-icon-view [chat-id group-chat name online styles & [hide-dapp?]]
|
||||
[photo-path [:chat-photo chat-id]
|
||||
[photo-path [:get-chat-photo chat-id]
|
||||
dapp? [:get-in [:contacts/contacts chat-id :dapp?]]]
|
||||
[view (:container styles)
|
||||
(if-not (s/blank? photo-path)
|
||||
|
|
|
@ -28,13 +28,16 @@
|
|||
(reg-fx
|
||||
::change-account
|
||||
(fn [[address new-account?]]
|
||||
(js/setTimeout
|
||||
(fn []
|
||||
(data-store/change-account address new-account?
|
||||
#(dispatch [:change-account-handler % address new-account?])))
|
||||
;; if we don't add delay when running app without status-go
|
||||
;; "null is not an object (evaluating 'realm.schema')" error appears
|
||||
(if config/stub-status-go? 300 0))))
|
||||
;; if we don't add delay when running app without status-go
|
||||
;; "null is not an object (evaluating 'realm.schema')" error appears
|
||||
(if config/stub-status-go?
|
||||
(js/setTimeout
|
||||
(fn []
|
||||
(data-store/change-account address new-account?
|
||||
#(dispatch [:change-account-handler % address new-account?])))
|
||||
300)
|
||||
(data-store/change-account address new-account?
|
||||
#(dispatch [:change-account-handler % address new-account?])))))
|
||||
|
||||
;;;; Handlers
|
||||
|
||||
|
|
|
@ -1,62 +1,57 @@
|
|||
(ns status-im.ui.screens.chats-list.views.inner-item
|
||||
(:require-macros [status-im.utils.views :refer [defview letsubs]])
|
||||
(:require [re-frame.core :refer [subscribe dispatch]]
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[reagent.core :as reagent]
|
||||
[clojure.string :as str]
|
||||
[status-im.ui.components.react :refer [view image text]]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.components.icons.vector-icons :as vi]
|
||||
[status-im.ui.components.chat-icon.screen :refer [chat-icon-view-chat-list]]
|
||||
[status-im.ui.components.context-menu :refer [context-menu]]
|
||||
[status-im.ui.components.chat-icon.screen :as chat-icon-screen]
|
||||
[status-im.ui.components.context-menu :as context-menu]
|
||||
[status-im.ui.screens.chats-list.styles :as st]
|
||||
[status-im.utils.utils :refer [truncate-str]]
|
||||
[status-im.i18n :refer [get-contact-translated label label-pluralize]]
|
||||
[status-im.utils.utils :as utils]
|
||||
[status-im.commands.utils :as commands-utils]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.utils.datetime :as time]
|
||||
[status-im.utils.gfycat.core :refer [generate-gfy]]
|
||||
[status-im.constants :refer [console-chat-id
|
||||
content-type-command
|
||||
content-type-wallet-command
|
||||
content-type-command-request]]
|
||||
[taoensso.timbre :as log]
|
||||
[reagent.core :as r]))
|
||||
[status-im.utils.gfycat.core :as gfycat]
|
||||
[status-im.constants :as const]
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
(defn message-content-text [chat-id]
|
||||
(let [message (subscribe [:get-last-message chat-id])
|
||||
preview (subscribe [:get-last-message-short-preview chat-id])]
|
||||
(r/create-class
|
||||
{:display-name "message-content-text"
|
||||
:component-will-mount
|
||||
(fn []
|
||||
(when (and (get-in @message [:content :command])
|
||||
(not @preview))
|
||||
(dispatch [:request-command-message-data @message :short-preview])))
|
||||
(defn message-content-text [{:keys [content] :as message}]
|
||||
(reagent/create-class
|
||||
{:display-name "message-content-text"
|
||||
:component-will-mount
|
||||
#(when (and (or (:command content)
|
||||
(:content-command content))
|
||||
(not (:short-preview content)))
|
||||
(re-frame/dispatch [:request-command-message-data message
|
||||
{:data-type :short-preview
|
||||
:cache-data? true}]))
|
||||
:reagent-render
|
||||
(fn [{:keys [content] :as message}]
|
||||
[react/view st/last-message-container
|
||||
(cond
|
||||
|
||||
:reagent-render
|
||||
(fn [_]
|
||||
[view]
|
||||
(let [{:keys [content] :as message} @message
|
||||
preview @preview]
|
||||
[view st/last-message-container
|
||||
(cond
|
||||
(not message)
|
||||
[react/text {:style st/last-message-text}
|
||||
(i18n/label :t/no-messages)]
|
||||
|
||||
(not message)
|
||||
[text {:style st/last-message-text}
|
||||
(label :t/no-messages)]
|
||||
(str/blank? content)
|
||||
[react/text {:style st/last-message-text}
|
||||
""]
|
||||
|
||||
(str/blank? content)
|
||||
[text {:style st/last-message-text}
|
||||
""]
|
||||
(:content content)
|
||||
[react/text {:style st/last-message-text
|
||||
:number-of-lines 1}
|
||||
(:content content)]
|
||||
|
||||
(:content content)
|
||||
[text {:style st/last-message-text
|
||||
:number-of-lines 1}
|
||||
(:content content)]
|
||||
(and (:command content)
|
||||
(-> content :short-preview :markup))
|
||||
(commands-utils/generate-hiccup (-> content :short-preview :markup))
|
||||
|
||||
(:command content)
|
||||
preview
|
||||
|
||||
:else
|
||||
[text {:style st/last-message-text
|
||||
:number-of-lines 1}
|
||||
content])]))})))
|
||||
:else
|
||||
[react/text {:style st/last-message-text
|
||||
:number-of-lines 1}
|
||||
content])])}))
|
||||
|
||||
(defview message-status [{:keys [chat-id contacts]}
|
||||
{:keys [message-id message-status user-statuses message-type outgoing] :as msg}]
|
||||
|
@ -70,29 +65,29 @@
|
|||
(and (= (count user-statuses) (count contacts))
|
||||
(every? (fn [[_ {:keys [status]}]]
|
||||
(= (keyword status) :seen)) user-statuses)))
|
||||
(= chat-id console-chat-id)))
|
||||
[image {:source {:uri :icon_ok_small}
|
||||
:style st/status-image}]))))
|
||||
(= chat-id const/console-chat-id)))
|
||||
[react/image {:source {:uri :icon_ok_small}
|
||||
:style st/status-image}]))))
|
||||
|
||||
(defn message-timestamp [{:keys [timestamp]}]
|
||||
(when timestamp
|
||||
[text {:style st/datetime-text}
|
||||
[react/text {:style st/datetime-text}
|
||||
(time/to-short-str timestamp)]))
|
||||
|
||||
(defview unviewed-indicator [chat-id]
|
||||
(letsubs [unviewed-messages [:unviewed-messages-count chat-id]]
|
||||
(when (pos? unviewed-messages)
|
||||
[view st/new-messages-container
|
||||
[text {:style st/new-messages-text
|
||||
:font :medium}
|
||||
unviewed-messages]])))
|
||||
(letsubs [unviewed-messages-count [:unviewed-messages-count chat-id]]
|
||||
(when (pos? unviewed-messages-count)
|
||||
[react/view st/new-messages-container
|
||||
[react/text {:style st/new-messages-text
|
||||
:font :medium}
|
||||
unviewed-messages-count]])))
|
||||
|
||||
(defn options-btn [chat-id]
|
||||
(let [options [{:value #(dispatch [:remove-chat chat-id])
|
||||
:text (label :t/delete-chat)
|
||||
(let [options [{:value #(re-frame/dispatch [:remove-chat chat-id])
|
||||
:text (i18n/label :t/delete-chat)
|
||||
:destructive? true}]]
|
||||
[view st/opts-btn-container
|
||||
[context-menu
|
||||
[react/view st/opts-btn-container
|
||||
[context-menu/context-menu
|
||||
[vi/icon :icons/options]
|
||||
options
|
||||
nil
|
||||
|
@ -102,41 +97,41 @@
|
|||
(let [private-group? (and group-chat? (not public?))
|
||||
public-group? (and group-chat? public?)
|
||||
chat-name (if (str/blank? name)
|
||||
(generate-gfy public-key)
|
||||
(truncate-str name 30))]
|
||||
[view st/name-view
|
||||
(gfycat/generate-gfy public-key)
|
||||
(utils/truncate-str name 30))]
|
||||
[react/view st/name-view
|
||||
(when public-group?
|
||||
[view st/public-group-icon-container
|
||||
[react/view st/public-group-icon-container
|
||||
[vi/icon :icons/public-chat {:style st/public-group-icon}]])
|
||||
(when private-group?
|
||||
[view st/private-group-icon-container
|
||||
[vi/icon :icons/group-chat {:style st/private-group-icon}]])
|
||||
[view {:flex-shrink 1}
|
||||
[text {:style st/name-text
|
||||
:number-of-lines 1}
|
||||
[react/view st/private-group-icon-container
|
||||
[vi/icon :icons/group-chat {:style st/private-group-icon}]])
|
||||
[react/view {:flex-shrink 1}
|
||||
[react/text {:style st/name-text
|
||||
:number-of-lines 1}
|
||||
(if public-group?
|
||||
(str "#" chat-name)
|
||||
chat-name)]]]))
|
||||
|
||||
(defn chat-list-item-inner-view [{:keys [chat-id name color online
|
||||
group-chat contacts public?
|
||||
public-key unremovable?] :as chat}
|
||||
edit?]
|
||||
(let [last-message (subscribe [:get-last-message chat-id])
|
||||
name (or (get-contact-translated chat-id :name name)
|
||||
(generate-gfy public-key))]
|
||||
[view st/chat-container
|
||||
[view st/chat-icon-container
|
||||
[chat-icon-view-chat-list chat-id group-chat name color online]]
|
||||
[view st/chat-info-container
|
||||
[view st/item-upper-container
|
||||
[chat-list-item-name name group-chat public? public-key]
|
||||
(when (and (not edit?) @last-message)
|
||||
[view st/message-status-container
|
||||
[message-status chat @last-message]
|
||||
[message-timestamp @last-message]])]
|
||||
[view st/item-lower-container
|
||||
[message-content-text chat-id]
|
||||
(when-not edit? [unviewed-indicator chat-id])]]
|
||||
[view st/chat-options-container
|
||||
(when (and edit? (not unremovable?)) [options-btn chat-id])]]))
|
||||
(defview chat-list-item-inner-view [{:keys [chat-id name color online
|
||||
group-chat contacts public?
|
||||
public-key unremovable?] :as chat}
|
||||
edit?]
|
||||
(letsubs [last-message [:get-last-message chat-id]]
|
||||
(let [name (or (i18n/get-contact-translated chat-id :name name)
|
||||
(gfycat/generate-gfy public-key))]
|
||||
[react/view st/chat-container
|
||||
[react/view st/chat-icon-container
|
||||
[chat-icon-screen/chat-icon-view-chat-list chat-id group-chat name color online]]
|
||||
[react/view st/chat-info-container
|
||||
[react/view st/item-upper-container
|
||||
[chat-list-item-name name group-chat public? public-key]
|
||||
(when (and (not edit?) last-message)
|
||||
[react/view st/message-status-container
|
||||
[message-status chat last-message]
|
||||
[message-timestamp last-message]])]
|
||||
[react/view st/item-lower-container
|
||||
[message-content-text last-message]
|
||||
(when-not edit? [unviewed-indicator chat-id])]]
|
||||
[react/view st/chat-options-container
|
||||
(when (and edit? (not unremovable?)) [options-btn chat-id])]])))
|
||||
|
|
|
@ -153,11 +153,6 @@
|
|||
(fn [contacts [_ identity]]
|
||||
(:name (contacts identity))))
|
||||
|
||||
(reg-sub :chat-by-id
|
||||
:<- [:chats]
|
||||
(fn [chats [_ chat-id]]
|
||||
(get chats chat-id)))
|
||||
|
||||
(defn chat-contacts [[chat contacts] [_ fn]]
|
||||
(when chat
|
||||
(let [current-participants (->> chat
|
||||
|
@ -184,15 +179,13 @@
|
|||
|
||||
(reg-sub :contacts-by-chat
|
||||
(fn [[_ fn chat-id] _]
|
||||
[(subscribe [:chat-by-id chat-id])
|
||||
[(subscribe [:get-chat chat-id])
|
||||
(subscribe [:get-contacts])])
|
||||
chat-contacts)
|
||||
|
||||
(reg-sub :chat-photo
|
||||
(reg-sub :get-chat-photo
|
||||
(fn [[_ chat-id] _]
|
||||
[(if chat-id
|
||||
(subscribe [:chat-by-id chat-id])
|
||||
(subscribe [:get-current-chat]))
|
||||
[(subscribe [:get-chat chat-id])
|
||||
(subscribe [:contacts-by-chat filter chat-id])])
|
||||
(fn [[chat contacts] [_ chat-id]]
|
||||
(when (and chat (not (:group-chat chat)))
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
:group/selected-contacts #{}
|
||||
:chats {}
|
||||
:current-chat-id constants/console-chat-id
|
||||
:loading-allowed true
|
||||
:selected-participants #{}
|
||||
:discoveries {}
|
||||
:discover-search-tags '()
|
||||
|
@ -153,11 +152,8 @@
|
|||
:chat/chat-list-ui-props
|
||||
:chat/layout-height
|
||||
:chat/expandable-view-height-to-value
|
||||
:chat/loading-allowed
|
||||
:chat/message-data
|
||||
:chat/message-id->transaction-id
|
||||
:chat/message-status
|
||||
:chat/unviewed-messages
|
||||
:chat/selected-participants
|
||||
:chat/chat-loaded-callbacks
|
||||
:chat/command-hash-valid?
|
||||
|
@ -165,7 +161,6 @@
|
|||
:chat/confirmation-code-sms-listener
|
||||
:chat/messages
|
||||
:chat/loaded-chats
|
||||
:chat/raw-unviewed-messages
|
||||
:chat/bot-db
|
||||
:chat/geolocation
|
||||
:commands/access-scope->commands-responses
|
||||
|
|
|
@ -171,7 +171,5 @@
|
|||
(register-handler-fx
|
||||
:clear-history
|
||||
(fn [{{:keys [current-chat-id] :as db} :db} _]
|
||||
{:db (-> db
|
||||
(assoc-in [:chats current-chat-id :messages] '())
|
||||
(assoc-in [:chats current-chat-id :last-message] nil))
|
||||
{:db (assoc-in db [:chats current-chat-id :messages] {})
|
||||
::chat-events/delete-messages current-chat-id}))
|
||||
|
|
|
@ -13,8 +13,7 @@
|
|||
(every? false?
|
||||
[(string/blank? username)
|
||||
(homoglyph/matches username constants/console-chat-id)
|
||||
(string/includes? username chat.constants/command-char)
|
||||
(string/includes? username chat.constants/bot-char)])))
|
||||
(string/includes? username chat.constants/command-char)])))
|
||||
|
||||
(defn correct-email? [email]
|
||||
(let [pattern #"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"]
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
|
||||
(handlers/register-handler-fx
|
||||
:profile/send-transaction
|
||||
[re-frame/trim-v (re-frame/inject-cofx :get-stored-messages)]
|
||||
[re-frame/trim-v]
|
||||
(fn [{{:contacts/keys [contacts] :as db} :db :as cofx} [chat-id]]
|
||||
(let [send-command (get-in contacts chat-const/send-command-ref)]
|
||||
(-> (chat-events/navigate-to-chat cofx chat-id)
|
||||
|
|
|
@ -30,8 +30,8 @@
|
|||
(is (= (:current-chat-id db)
|
||||
const/console-chat-id))
|
||||
(is (= dispatch-n
|
||||
(concat [[:add-contacts [sign-up/console-contact]]]
|
||||
sign-up/intro-events)))))
|
||||
[[:add-contacts [sign-up/console-contact]]
|
||||
sign-up/intro-event]))))
|
||||
|
||||
(testing "initialising console with existing account and console chat not initialisated"
|
||||
(let [fresh-db {:chats {}
|
||||
|
|
|
@ -46,10 +46,10 @@
|
|||
(is (= "word1 \uD83D\uDC4D word2" (input/text->emoji "word1 :+1: word2"))))
|
||||
|
||||
(deftest starts-as-command?
|
||||
(is (false? (input/starts-as-command? nil)))
|
||||
(is (false? (input/text-ends-with-space? "")))
|
||||
(is (false? (input/text-ends-with-space? "word1 word2 word3")))
|
||||
(is (true? (input/text-ends-with-space? "word1 word2 "))))
|
||||
(is (not (input/starts-as-command? nil)))
|
||||
(is (not (input/text-ends-with-space? "")))
|
||||
(is (not (input/text-ends-with-space? "word1 word2 word3")))
|
||||
(is (input/text-ends-with-space? "word1 word2 ")))
|
||||
|
||||
(deftest split-command-args
|
||||
(is (nil? (input/split-command-args nil)))
|
||||
|
|
Loading…
Reference in New Issue