Refactored message data-model and view

This commit is contained in:
janherich 2017-12-05 14:03:25 +01:00
parent df21ec8c3a
commit eb8d0a8a79
No known key found for this signature in database
GPG Key ID: C23B473AFBE94D13
38 changed files with 916 additions and 1110 deletions

View File

@ -3,7 +3,6 @@
(def command-char "/") (def command-char "/")
(def spacing-char " ") (def spacing-char " ")
(def arg-wrapping-char "\"") (def arg-wrapping-char "\"")
(def bot-char "@")
(def input-height 56) (def input-height 56)
(def max-input-height 66) (def max-input-height 66)

View File

@ -40,16 +40,6 @@
(fn [cofx _] (fn [cofx _]
(assoc cofx :get-stored-messages msg-store/get-by-chat-id))) (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 (re-frame/reg-cofx
:all-stored-chats :all-stored-chats
(fn [cofx _] (fn [cofx _]
@ -120,38 +110,25 @@
:show-emoji? false :show-emoji? false
:bottom-info details}))) :bottom-info details})))
(def index-messages (partial into {} (map (juxt :message-id identity))))
(handlers/register-handler-fx (handlers/register-handler-fx
:load-more-messages :load-more-messages
[(re-frame/inject-cofx :get-stored-messages)] [(re-frame/inject-cofx :get-stored-messages)]
(fn [{{:keys [current-chat-id loading-allowed] :as db} :db (fn [{{:keys [current-chat-id] :as db} :db get-stored-messages :get-stored-messages} _]
get-stored-messages :get-stored-messages} _] (when-not (get-in db [:chats current-chat-id :all-loaded?])
(let [all-loaded? (get-in db [:chats current-chat-id :all-loaded?])] (let [loaded-count (count (get-in db [:chats current-chat-id :messages]))
(if (and loading-allowed (not all-loaded?)) new-messages (get-stored-messages current-chat-id loaded-count)]
(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 {:db (-> db
(assoc :loading-allowed false) (update-in [:chats current-chat-id :messages] merge (index-messages new-messages))
(update-in messages-path concat new-messages) (assoc-in [:chats current-chat-id :all-loaded?]
(assoc-in [:chats current-chat-id :all-loaded?] all-loaded?)) (> const/default-number-of-messages (count new-messages))))}))))
;; we permit loading more messages again after 400ms
:dispatch-later [{:ms 400 :dispatch [:set :loading-allowed true]}]})
{:db db}))))
(handlers/register-handler-db (handlers/register-handler-db
:set-message-shown :set-message-shown
[re-frame/trim-v] [re-frame/trim-v]
(fn [db [{:keys [chat-id message-id]}]] (fn [db [{:keys [chat-id message-id]}]]
(update-in db (update-in db [:chats chat-id :messages message-id] assoc :new? false)))
[:chats chat-id :messages]
(fn [messages]
(map (fn [message]
(if (= message-id (:message-id message))
(assoc message :new? false)
message))
messages)))))
(defn init-console-chat (defn init-console-chat
[{:keys [chats] :accounts/keys [current-account-id] :as db}] [{:keys [chats] :accounts/keys [current-account-id] :as db}]
@ -165,7 +142,7 @@
:save-all-contacts [sign-up/console-contact]} :save-all-contacts [sign-up/console-contact]}
(not current-account-id) (not current-account-id)
(update :dispatch-n concat sign-up/intro-events)))) (update :dispatch-n conj sign-up/intro-event))))
(handlers/register-handler-fx (handlers/register-handler-fx
:init-console-chat :init-console-chat
@ -175,37 +152,36 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:initialize-chats :initialize-chats
[(re-frame/inject-cofx :all-stored-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 :stored-unviewed-messages)
(re-frame/inject-cofx :get-stored-unanswered-requests) (re-frame/inject-cofx :get-stored-unanswered-requests)]
(re-frame/inject-cofx :get-last-stored-message)
(re-frame/inject-cofx :get-message-previews)]
(fn [{:keys [db (fn [{:keys [db
all-stored-chats all-stored-chats
stored-unanswered-requests stored-unanswered-requests
stored-unviewed-messages get-stored-messages
get-last-stored-message stored-unviewed-messages]} _]
message-previews]} _] (let [{:accounts/keys [account-creation?]} db
(let [{:accounts/keys [account-creation?] :contacts/keys [contacts]} db load-default-contacts-event [:load-default-contacts!]]
new-db (unviewed-messages-model/load-unviewed-messages db stored-unviewed-messages)
event [:load-default-contacts!]]
(if account-creation? (if account-creation?
{:db new-db {:db db
:dispatch event} :dispatch load-default-contacts-event}
(let [chat->message-id->request (reduce (fn [acc {:keys [chat-id message-id] :as request}] (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)) (assoc-in acc [chat-id message-id] request))
{} {}
stored-unanswered-requests) stored-unanswered-requests)
chats (->> all-stored-chats chats (reduce (fn [acc {:keys [chat-id] :as chat}]
(map (fn [{:keys [chat-id] :as chat}] (assoc acc chat-id
[chat-id (assoc chat (assoc chat
:last-message (get-last-stored-message chat-id) :unviewed-messages (get chat->unviewed-messages chat-id)
:requests (get chat->message-id->request chat-id))])) :requests (get chat->message-id->request chat-id)
(into {}))] :messages (index-messages (get-stored-messages chat-id)))))
(-> new-db {}
(assoc-in [:message-data :preview] message-previews) all-stored-chats)]
(-> db
(assoc :chats chats) (assoc :chats chats)
init-console-chat init-console-chat
(update :dispatch-n conj event))))))) (update :dispatch-n conj load-default-contacts-event)))))))
(handlers/register-handler-fx (handlers/register-handler-fx
:send-seen! :send-seen!
@ -214,7 +190,9 @@
(let [{:keys [web3 current-public-key chats] (let [{:keys [web3 current-public-key chats]
:contacts/keys [contacts]} db :contacts/keys [contacts]} db
{:keys [group-chat public?]} (get chats chat-id)] {: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 :update-message {:message-id message-id
:message-status :seen}} :message-status :seen}}
(and (not (get-in contacts [chat-id] :dapp?)) (and (not (get-in contacts [chat-id] :dapp?))
@ -256,7 +234,7 @@
(defn preload-chat-data (defn preload-chat-data
"Takes coeffects map and chat-id, returns effects necessary when navigating to chat" "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]) (let [messages (get-in db [:chats chat-id :messages])
chat-loaded-event (get-in db [:chats chat-id :chat-loaded-event]) chat-loaded-event (get-in db [:chats chat-id :chat-loaded-event])
jail-loaded? (get-in db [:contacts/contacts chat-id :jail-loaded?])] jail-loaded? (get-in db [:contacts/contacts chat-id :jail-loaded?])]
@ -266,9 +244,6 @@
(model/set-chat-ui-props {:validation-messages nil}) (model/set-chat-ui-props {:validation-messages nil})
(update-in [:chats chat-id] dissoc :chat-loaded-event))} (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 chat-loaded-event
(assoc :dispatch chat-loaded-event)))) (assoc :dispatch chat-loaded-event))))
@ -301,14 +276,13 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:navigate-to-chat :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?]}]] (fn [cofx [chat-id {:keys [navigation-replace?]}]]
(navigate-to-chat cofx chat-id navigation-replace?))) (navigate-to-chat cofx chat-id navigation-replace?)))
(handlers/register-handler-fx (handlers/register-handler-fx
:start-chat :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?]}]] (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 (when (not= (:current-public-key db) contact-id) ; don't allow to open chat with yourself
(if (get (:chats db) contact-id) (if (get (:chats db) contact-id)

View File

@ -11,13 +11,12 @@
(defn- generate-context (defn- generate-context
"Generates context for jail call" "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 (merge {:platform platform/platform
:from current-account-id :from current-account-id
:to to :to to
:chat {:chat-id chat-id :chat {:chat-id chat-id
:group-chat (or (get-in chats [chat-id :group-chat]) :group-chat (not (nil? group-id))}}
(not (nil? group-id)))}}
i18n/delimeters)) i18n/delimeters))
(defn request-command-message-data (defn request-command-message-data
@ -25,52 +24,56 @@
[db [db
{{command-name :command {{command-name :command
content-command-name :content-command content-command-name :content-command
:keys [content-command-scope-bitmask scope-bitmask params type bot]} :content :keys [content-command-scope-bitmask bot scope-bitmask params type]} :content
:keys [chat-id jail-id group-id] :as message} :keys [chat-id group-id jail-id] :as message}
data-type] {:keys [data-type proceed-event-creator cache-data?] :as opts}]
(let [{:keys [chats] (let [{:accounts/keys [current-account-id]
:accounts/keys [current-account-id]
:contacts/keys [contacts]} db :contacts/keys [contacts]} db
jail-id (or bot jail-id chat-id) jail-id (or bot jail-id chat-id)
jail-command-name (or content-command-name command-name)] jail-command-name (or content-command-name command-name)]
(if (get-in contacts [jail-id :jail-loaded?]) (if (get-in contacts [jail-id :jail-loaded?])
(let [path [(if (= :response (keyword type)) :responses :commands) (let [path [(if (= :response (keyword type)) :responses :commands)
[jail-command-name [jail-command-name
(or scope-bitmask content-command-scope-bitmask)] (or content-command-scope-bitmask scope-bitmask)]
data-type] data-type]
to (get-in contacts [chat-id :address]) to (get-in contacts [chat-id :address])
jail-params {:parameters params 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 {:call-jail {:jail-id jail-id
:path path :path path
:params jail-params :params jail-params
:callback-events-creator (fn [jail-response] :callback-events-creator (fn [jail-response]
[[::jail-command-data-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] {: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
(handlers/register-handler-fx (handlers/register-handler-fx
::jail-command-data-response ::jail-command-data-response
[re-frame/trim-v] [re-frame/trim-v]
(fn [{:keys [db]} [{{:keys [returned]} :result} {:keys [message-id on-requested]} data-type]] (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-> {} (cond-> {}
returned
(assoc :db (assoc-in db [:message-data data-type message-id] returned)) (and cache-data? existing-message returned)
(and returned (as-> fx
(= :preview data-type)) (let [updated-message (assoc-in existing-message [:content data-type] returned)]
(assoc :update-message {:message-id message-id (assoc fx
:preview (prn-str returned)}) :db (assoc-in db [:chats chat-id :messages message-id] updated-message)
on-requested :update-message (select-keys updated-message [:message-id :content]))))
(assoc :dispatch (on-requested returned)))))
proceed-event-creator
(assoc :dispatch (proceed-event-creator returned))))))
(handlers/register-handler-fx (handlers/register-handler-fx
:request-command-message-data :request-command-message-data
[re-frame/trim-v (re-frame/inject-cofx :get-local-storage-data)] [re-frame/trim-v (re-frame/inject-cofx :get-local-storage-data)]
(fn [{:keys [db]} [message data-type]] (fn [{:keys [db]} [message opts]]
(request-command-message-data db message data-type))) (request-command-message-data db message opts)))
(handlers/register-handler-fx (handlers/register-handler-fx
:execute-command-immediately :execute-command-immediately
@ -82,18 +85,3 @@
[:read-external-storage] [:read-external-storage]
#(re-frame/dispatch [:initialize-geth])]} #(re-frame/dispatch [:initialize-geth])]}
(log/debug "ignoring command: " command-name)))) (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)))))))

View File

@ -270,10 +270,11 @@
:content {:command (:name command) :content {:command (:name command)
:scope-bitmask (:scope-bitmask command) :scope-bitmask (:scope-bitmask command)
:params params :params params
:type (:type command)} :type (:type command)}}]
:on-requested (fn [jail-response] (commands-events/request-command-message-data db request-data
(event-after-creator command-message jail-response))}] {:data-type data-type
(commands-events/request-command-message-data db request-data data-type))) :proceed-event-creator (partial event-after-creator
command-message)})))
(defn proceed-command (defn proceed-command
"Proceed with command processing by setting up execution chain of events: "Proceed with command processing by setting up execution chain of events:
@ -429,10 +430,13 @@
(animation-events/choose-predefined-expandable-height :result-box :max)) (animation-events/choose-predefined-expandable-height :result-box :max))
::dismiss-keyboard nil} ::dismiss-keyboard nil}
;; regular command message, we need to fetch preview before sending the command message ;; regular command message, we need to fetch preview before sending the command message
(request-command-data db (merge params-template (request-command-data
db
(merge params-template
{:data-type :preview {:data-type :preview
:event-after-creator (fn [command-message _] :event-after-creator (fn [command-message returned]
[::send-command command-message])}))))) [::send-command (assoc-in command-message
[:command :preview] returned)])})))))
(handlers/register-handler-fx (handlers/register-handler-fx
:send-current-message :send-current-message

View File

@ -9,6 +9,7 @@
[status-im.chat.models :as model] [status-im.chat.models :as model]
[status-im.chat.models.commands :as commands-model] [status-im.chat.models.commands :as commands-model]
[status-im.chat.models.unviewed-messages :as unviewed-messages-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.chat.events.requests :as requests-events]
[status-im.data-store.chats :as chat-store] [status-im.data-store.chats :as chat-store]
[status-im.data-store.messages :as msg-store])) [status-im.data-store.messages :as msg-store]))
@ -43,35 +44,36 @@
contacts)] contacts)]
(:ref (get available-commands-responses response-name)))) (: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 (defn add-message
[{:keys [db message-exists? get-last-stored-message pop-up-chat? [{:keys [db message-exists? pop-up-chat? get-last-clock-value now] :as cofx}
get-last-clock-value now random-id] :as cofx} {:keys [from group-id chat-id content-type content message-id timestamp clock-value]
{:keys [from group-id chat-id content-type content
message-id timestamp clock-value]
:as message :as message
:or {clock-value 0}}] :or {clock-value 0}}]
(let [{:keys [access-scope->commands-responses] :contacts/keys [contacts]} db (let [{:keys [access-scope->commands-responses] :contacts/keys [contacts]} db
chat-identifier (or group-id chat-id from) {:keys [public-key] :as current-account} (get-current-account db)
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, ;; 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 ;; it's not from current user (outgoing message) and it's for relevant chat
;; (either current active chat or new chat not existing yet) ;; (either current active chat or new chat not existing yet)
(if (and (not (message-exists? message-id)) (when (and (not (message-exists? message-id))
(not= from (:public-key current-account)) (not= from public-key)
(pop-up-chat? chat-identifier)) (pop-up-chat? chat-identifier))
(let [group-chat? (not (nil? group-id)) (let [fx (if (get-in db [:chats chat-identifier])
chat-exists? (get-in db [:chats chat-identifier])
fx (if chat-exists?
(model/upsert-chat cofx {:chat-id 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)) (model/add-chat cofx chat-identifier))
command-request? (= content-type const/content-type-command-request) command-request? (= content-type const/content-type-command-request)
command (:command content) command (:command content)
enriched-message (cond-> (assoc (chat-utils/check-author-direction enriched-message (cond-> (assoc message
(get-last-stored-message chat-identifier)
message)
:chat-id chat-identifier :chat-id chat-identifier
:timestamp (or timestamp now) :timestamp (or timestamp now)
:show? true
:clock-value (clocks/receive :clock-value (clocks/receive
clock-value clock-value
(get-last-clock-value chat-identifier))) (get-last-clock-value chat-identifier)))
@ -81,49 +83,50 @@
current-account current-account
(get-in fx [:db :chats chat-identifier]) (get-in fx [:db :chats chat-identifier])
contacts contacts
command))) 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))]
(cond-> (-> fx (cond-> (-> fx
(update :db update-db-fx) (update :db add-message-to-db enriched-message chat-identifier)
(assoc :save-message (dissoc enriched-message :new?))) (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? command-request?
(requests-events/add-request chat-identifier enriched-message))) (requests-events/add-request chat-identifier enriched-message))))))
{:db db})))
(def ^:private receive-interceptors (def ^:private receive-interceptors
[(re-frame/inject-cofx :message-exists?) (re-frame/inject-cofx :get-last-stored-message) [(re-frame/inject-cofx :message-exists?) (re-frame/inject-cofx :pop-up-chat?)
(re-frame/inject-cofx :pop-up-chat?) (re-frame/inject-cofx :get-last-clock-value) (re-frame/inject-cofx :get-last-clock-value) (re-frame/inject-cofx :get-stored-chat)
(re-frame/inject-cofx :random-id) (re-frame/inject-cofx :get-stored-chat) re-frame/trim-v]) 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 (handlers/register-handler-fx
:received-protocol-message! ::received-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
receive-interceptors receive-interceptors
(fn [cofx [message]] (fn [cofx [message]]
(add-message 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 (handlers/register-handler-fx
:received-message-when-commands-loaded :received-message-when-commands-loaded
receive-interceptors 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) (if (and (:status-node-started? db)
(get-in db [:contacts/contacts chat-id :jail-loaded?])) (get-in db [:contacts/contacts chat-id :jail-loaded?]))
(add-message cofx message) (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]}]})))

View File

@ -45,7 +45,7 @@
(defn- message-seen [{:keys [db] :as fx} message-id] (defn- message-seen [{:keys [db] :as fx} message-id]
(-> fx (-> 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 (assoc :update-message {:message-id message-id
:message-status :seen}))) :message-status :seen})))
@ -90,7 +90,8 @@
(message-seen message-id)))) (message-seen message-id))))
(defn- extract-last-phone-number [chats] (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}] (some (fn [{:keys [type content] :as message}]
(when (and (= type :response) (when (and (= type :response)
(= (:command content) "phone")) (= (:command content) "phone"))

View File

@ -40,6 +40,8 @@
:content-command (:name command) :content-command (:name command)
:content-command-scope-bitmask (:scope-bitmask command) :content-command-scope-bitmask (:scope-bitmask command)
:content-command-ref (:ref command) :content-command-ref (:ref command)
:preview (:preview command)
:short-preview (:short-preview command)
:bot (or (:bot command) :bot (or (:bot command)
(:owner-id command)))] (:owner-id command)))]
{:message-id id {:message-id id
@ -90,12 +92,11 @@
hidden-params (->> (:params command) hidden-params (->> (:params command)
(filter :hidden) (filter :hidden)
(map :name)) (map :name))
command' (->> (prepare-command current-public-key chat-id clock-value request content) command' (prepare-command current-public-key chat-id clock-value request content)]
(cu/check-author-direction db chat-id))]
(dispatch [:update-message-overhead! chat-id network-status]) (dispatch [:update-message-overhead! chat-id network-status])
(dispatch [:set-chat-ui-props {:sending-in-progress? false}]) (dispatch [:set-chat-ui-props {:sending-in-progress? false}])
(dispatch [::send-command! add-to-chat-id (assoc params :command command') hidden-params]) (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])))))) (dispatch [:console-respond-command params]))))))
(register-handler ::send-command! (register-handler ::send-command!
@ -104,7 +105,8 @@
(dispatch [::add-command add-to-chat-id params]) (dispatch [::add-command add-to-chat-id params])
(dispatch [::save-command! add-to-chat-id params hidden-params]) (dispatch [::save-command! add-to-chat-id params hidden-params])
(dispatch [::dispatch-responded-requests! 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 (register-handler ::add-command
(after (fn [_ [_ _ {:keys [handler]}]] (after (fn [_ [_ _ {:keys [handler]}]]
@ -115,11 +117,9 @@
(register-handler ::save-command! (register-handler ::save-command!
(u/side-effect! (u/side-effect!
(fn [db [_ chat-id {:keys [command]} hidden-params]] (fn [db [_ chat-id {:keys [command]} hidden-params]]
(let [preview (get-in db [:message-data :preview (:message-id command)]) (let [command (cond-> (-> command
command (cond-> (-> command
(update-in [:content :params] #(apply dissoc % hidden-params)) (update-in [:content :params] #(apply dissoc % hidden-params))
(dissoc :to-message :has-handler :raw-input)) (dissoc :to-message :has-handler :raw-input)))]
preview (assoc :preview (pr-str preview)))]
(dispatch [:upsert-chat! {:chat-id chat-id}]) (dispatch [:upsert-chat! {:chat-id chat-id}])
(messages/save chat-id command))))) (messages/save chat-id command)))))
@ -168,9 +168,7 @@
(fn [{:keys [network-status] :as db} [_ {:keys [chat-id identity message] :as params}]] (fn [{:keys [network-status] :as db} [_ {:keys [chat-id identity message] :as params}]]
(let [{:keys [group-chat public?]} (get-in db [:chats chat-id]) (let [{:keys [group-chat public?]} (get-in db [:chats chat-id])
clock-value (messages/get-last-clock-value chat-id) clock-value (messages/get-last-clock-value chat-id)
message' (cu/check-author-direction message' {:message-id (random/id)
db chat-id
{:message-id (random/id)
:chat-id chat-id :chat-id chat-id
:content message :content message
:from identity :from identity
@ -178,7 +176,7 @@
:outgoing true :outgoing true
:timestamp (datetime/now-ms) :timestamp (datetime/now-ms)
:clock-value (clocks/send clock-value) :clock-value (clocks/send clock-value)
:show? true}) :show? true}
message'' (cond-> message' message'' (cond-> message'
(and group-chat public?) (and group-chat public?)
(assoc :group-id chat-id :message-type :public-group-user-message) (assoc :group-id chat-id :message-type :public-group-user-message)

View File

@ -24,16 +24,13 @@
original))))) original)))))
(defn text-ends-with-space? [text] (defn text-ends-with-space? [text]
(and (not (nil? text)) (and text (str/ends-with? text const/spacing-char)))
(str/ends-with? text const/spacing-char)))
(defn starts-as-command? (defn starts-as-command?
"Returns true if `text` may be treated as a 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." To make sure that text is command we need to use `possible-chat-actions` function."
[text] [text]
(and (not (nil? text)) (and text (str/starts-with? text const/command-char)))
(or (str/starts-with? text const/bot-char)
(str/starts-with? text const/command-char))))
(defn split-command-args (defn split-command-args
"Returns a list of command's arguments including the command's name. "Returns a list of command's arguments including the command's name.

View File

@ -1,18 +1,13 @@
(ns status-im.chat.models.unviewed-messages) (ns status-im.chat.models.unviewed-messages)
(defn load-unviewed-messages [db raw-unviewed-messages] (defn index-unviewed-messages [unviewed-messages]
(assoc db :unviewed-messages (into {}
(->> raw-unviewed-messages (map (fn [[chat-id messages]]
(group-by :chat-id) [chat-id (into #{} (map :message-id) messages)]))
(map (fn [[id messages]] (group-by :chat-id unviewed-messages)))
[id {:messages-ids (map :message-id messages)
:count (count messages)}]))
(into {}))))
(defn add-unviewed-message [db chat-id message-id] (defn add-unviewed-message [db chat-id message-id]
(-> db (update-in db [:chats chat-id :unviewed-messages] (fnil conj #{}) message-id))
(update-in [:unviewed-messages chat-id :messages-ids] conj message-id)
(update-in [:unviewed-messages chat-id :count] inc)))
(defn remove-unviewed-messages [db chat-id] (defn remove-unviewed-message [db chat-id message-id]
(update db :unviewed-messages dissoc chat-id)) (update-in db [:chats chat-id :unviewed-messages] disj message-id))

View File

@ -1,198 +1,110 @@
(ns status-im.chat.screen (ns status-im.chat.screen
(:require-macros [status-im.utils.views :refer [defview]]) (:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :refer [subscribe dispatch]] (:require [re-frame.core :as re-frame]
[status-im.ui.components.react :refer [view [status-im.ui.components.react :as react]
animated-view [status-im.ui.components.icons.vector-icons :as vector-icons]
text [status-im.ui.components.status-bar :as status-bar]
modal [status-im.ui.components.chat-icon.screen :as chat-icon-screen]
touchable-highlight [status-im.chat.styles.screen :as style]
list-view [status-im.utils.listview :as listview]
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]]
[status-im.utils.datetime :as time] [status-im.utils.datetime :as time]
[status-im.utils.platform :as platform :refer [platform-specific]] [status-im.utils.platform :as platform]
[status-im.ui.components.invertible-scroll-view :refer [invertible-scroll-view]] [status-im.ui.components.invertible-scroll-view :as scroll-view]
[status-im.ui.components.toolbar.view :as toolbar] [status-im.ui.components.toolbar.view :as toolbar]
[status-im.chat.views.toolbar-content :refer [toolbar-content-view]] [status-im.chat.views.toolbar-content :as toolbar-content]
[status-im.chat.views.message.message :refer [chat-message]] [status-im.chat.views.message.message :as message]
[status-im.chat.views.message.datemark :refer [chat-datemark]] [status-im.chat.views.message.datemark :as message-datemark]
[status-im.chat.views.input.input :as input] [status-im.chat.views.input.input :as input]
[status-im.chat.views.actions :refer [actions-view]] [status-im.chat.views.actions :as actions]
[status-im.chat.views.bottom-info :refer [bottom-info-view]] [status-im.chat.views.bottom-info :as bottom-info]
[status-im.chat.constants :as chat-const] [status-im.i18n :as i18n]
[status-im.i18n :refer [label label-pluralize]]
[status-im.ui.components.animation :as anim] [status-im.ui.components.animation :as anim]
[status-im.ui.components.sync-state.offline :refer [offline-view]] [status-im.ui.components.sync-state.offline :as offline]
[status-im.constants :refer [content-type-status]] [clojure.string :as string]))
[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))))
(defview chat-icon [] (defview chat-icon []
[chat-id [:chat :chat-id] (letsubs [{:keys [chat-id group-chat name color]} [:get-current-chat]]
group-chat [:chat :group-chat] [chat-icon-screen/chat-icon-view-action chat-id group-chat name color true]))
name [:chat :name]
color [:chat :color]]
;; TODO stub data ('online' property)
[chat-icon-view-action chat-id group-chat name color true])
(defn typing [member] (defn- toolbar-action [show-actions?]
[view st/typing-view [react/touchable-highlight
[view st/typing-background {:on-press #(re-frame/dispatch [:set-chat-ui-props {:show-actions? (not show-actions?)}])
[text {:style st/typing-text :accessibility-label :chat-menu}
:font :default} [react/view style/action
(str member " " (label :t/is-typing))]]]) (if show-actions?
[vector-icons/icon :icons/dropdown-up]
[chat-icon])]])
(defn typing-all [] (defview add-contact-bar []
[view st/typing-all (letsubs [chat-id [:get-current-chat-id]
;; TODO stub data pending-contact? [:current-contact :pending?]]
(for [member ["Geoff" "Justas"]] (when pending-contact?
^{:key member} [typing member])]) [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)) (defmulti message-row (fn [{{:keys [type]} :row}] type))
(defmethod message-row :datemark (defmethod message-row :datemark
[{{:keys [value]} :row}] [{{:keys [value]} :row}]
(list-item [chat-datemark value])) (react/list-item [message-datemark/chat-datemark value]))
(defmethod message-row :default (defmethod message-row :default
[{:keys [contact-by-identity group-chat messages-count row index last-outgoing?]}] [{:keys [group-chat current-public-key row]}]
(let [message (-> row (react/list-item [message/chat-message (assoc row
(add-message-color contact-by-identity) :group-chat group-chat
(assoc :group-chat group-chat) :current-public-key current-public-key)]))
(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)))
(defview messages-view [group-chat] (defview messages-view [group-chat]
[messages [:chat :messages] (letsubs [messages [:get-current-chat-messages]
contacts [:chat :contacts] current-public-key [:get-current-public-key]]
message-extras [:get :message-extras] [react/list-view {:renderRow (fn [row _ index]
loaded? [:all-messages-loaded?] (message-row {:group-chat group-chat
current-chat-id [:get-current-chat-id] :current-public-key current-public-key
last-outgoing-message [:get-chat-last-outgoing-message @current-chat-id]] :row row}))
(let [contacts' (contacts-by-identity contacts) :renderScrollComponent #(scroll-view/invertible-scroll-view (js->clj %))
messages (messages-with-timemarks messages message-extras)] :onEndReached #(re-frame/dispatch [:load-more-messages])
[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 :enableEmptySections true
:keyboardShouldPersistTaps (if platform/android? :always :handled) :keyboardShouldPersistTaps (if platform/android? :always :handled)
:dataSource (to-datasource-inverted messages)}])) :dataSource (listview/to-datasource-inverted messages)}]))
(defview chat [] (defview chat []
[group-chat [:chat :group-chat] (letsubs [{:keys [group-chat input-text]} [:get-current-chat]
show-actions? [:get-current-chat-ui-prop :show-actions?] show-actions? [:get-current-chat-ui-prop :show-actions?]
show-bottom-info? [:get-current-chat-ui-prop :show-bottom-info?] show-bottom-info? [:get-current-chat-ui-prop :show-bottom-info?]
show-emoji? [:get-current-chat-ui-prop :show-emoji?] show-emoji? [:get-current-chat-ui-prop :show-emoji?]
layout-height [:get :layout-height] layout-height [:get :layout-height]]
input-text [:chat :input-text]] {:component-did-mount #(re-frame/dispatch [:check-and-open-dapp!])
{:component-did-mount #(dispatch [:check-and-open-dapp!]) :component-will-unmount #(re-frame/dispatch [:set-chat-ui-props {:show-emoji? false}])}
:component-will-unmount #(dispatch [:set-chat-ui-props {:show-emoji? false}])} [react/view {:style style/chat-view
[view {:style st/chat-view
:on-layout (fn [event] :on-layout (fn [event]
(let [height (.. event -nativeEvent -layout -height)] (let [height (.. event -nativeEvent -layout -height)]
(when (not= height layout-height) (when (not= height layout-height)
(dispatch [:set-layout-height height]))))} (re-frame/dispatch [:set-layout-height height]))))}
[chat-toolbar] [chat-toolbar]
[messages-view group-chat] [messages-view group-chat]
[input/container {:text-empty? (str/blank? input-text)}] [input/container {:text-empty? (string/blank? input-text)}]
(when show-actions? (when show-actions?
[actions-view]) [actions/actions-view])
(when show-bottom-info? (when show-bottom-info?
[bottom-info-view]) [bottom-info/bottom-info-view])
[offline-view {:top (get-in platform-specific [offline/offline-view {:top (get-in platform/platform-specific
[:component-styles :status-bar :default :height])}]]) [:component-styles :status-bar :default :height])}]]))

View File

@ -120,19 +120,8 @@
:from const/console-chat-id :from const/console-chat-id
:to "me"}]]) :to "me"}]])
(def intro-status (def intro-event
{: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 [:received-message-when-commands-loaded
const/console-chat-id
{:chat-id const/console-chat-id {:chat-id const/console-chat-id
:message-id chat-const/intro-message1-id :message-id chat-const/intro-message1-id
:content {:command "password" :content {:command "password"
@ -140,7 +129,7 @@
:content-type const/content-type-command-request :content-type const/content-type-command-request
:outgoing false :outgoing false
:from const/console-chat-id :from const/console-chat-id
:to "me"}]]) :to "me"}])
(def console-chat (def console-chat
{:chat-id const/console-chat-id {:chat-id const/console-chat-id
@ -161,4 +150,5 @@
:photo-path const/console-chat-id :photo-path const/console-chat-id
:dapp? true :dapp? true
:unremovable? true :unremovable? true
:bot-url "local://console-bot"}) :bot-url "local://console-bot"
:status (label :t/intro-status)})

View File

@ -1,27 +1,22 @@
(ns status-im.chat.specs (ns status-im.chat.specs
(:require [cljs.spec.alpha :as s])) (: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/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/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/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/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-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-ui-props (s/nilable map?)) ; {id (string) props (map)}
(s/def :chat/chat-list-ui-props (s/nilable 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/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-status (s/nilable map?)) ; TODO janherich: remove later
(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/selected-participants (s/nilable set?)) (s/def :chat/selected-participants (s/nilable set?))
(s/def :chat/chat-loaded-callbacks (s/nilable map?)) (s/def :chat/chat-loaded-callbacks (s/nilable map?))
(s/def :chat/command-hash-valid? (s/nilable boolean?)) (s/def :chat/command-hash-valid? (s/nilable boolean?))
(s/def :chat/public-group-topic (s/nilable string?)) (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/confirmation-code-sms-listener (s/nilable any?)) ; .addListener result object
(s/def :chat/messages (s/nilable seq?)) (s/def :chat/messages (s/nilable map?)) ; messages indexed by message-id
(s/def :chat/loaded-chats (s/nilable seq?)) (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/bot-db (s/nilable map?))
(s/def :chat/geolocation (s/nilable map?)) (s/def :chat/geolocation (s/nilable map?))

View File

@ -1,41 +1,32 @@
(ns status-im.chat.styles.message.message (ns status-im.chat.styles.message.message
(:require-macros [status-im.utils.styles :refer [defstyle defnstyle]]) (:require-macros [status-im.utils.styles :refer [defstyle defnstyle]])
(:require [status-im.ui.components.styles :refer [color-white (:require [status-im.ui.components.styles :as styles]
color-black [status-im.constants :as constants]))
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]]))
(defstyle style-message-text (defstyle style-message-text
{:fontSize 15 {:fontSize 15
:color text1-color :color styles/text1-color
:android {:line-height 22} :android {:line-height 22}
:ios {:line-height 23}}) :ios {:line-height 23}})
(def style-sub-text (def style-sub-text
{:top -2 {:top -2
:fontSize 12 :fontSize 12
:color text2-color :color styles/text2-color
:lineHeight 14 :lineHeight 14
:height 16}) :height 16})
(defn message-padding-top (defn message-padding-top
[{:keys [first-in-date? same-author same-direction]}] [{:keys [first-in-date? same-author? same-direction?]}]
(cond (cond
first-in-date? 20 first-in-date? 20
same-author 8 same-author? 8
same-direction 16 same-direction? 16
:else 24)) :else 24))
(defn last-message-padding (defn last-message-padding
[{:keys [last-message typing]}] [{:keys [last? typing]}]
(when (and last-message (not typing)) (when (and last? (not typing))
{:paddingBottom 16})) {:paddingBottom 16}))
(def message-datemark (def message-datemark
@ -65,7 +56,7 @@
{:marginTop 18 {:marginTop 18
:marginLeft 40 :marginLeft 40
:fontSize 12 :fontSize 12
:color text2-color}) :color styles/text2-color})
(def group-message-wrapper (def group-message-wrapper
{:flexDirection :column}) {:flexDirection :column})
@ -94,7 +85,7 @@
:opacity 0.5}) :opacity 0.5})
(defstyle delivery-text (defstyle delivery-text
{:color color-gray4 {:color styles/color-gray4
:marginLeft 5 :marginLeft 5
:android {:font-size 13} :android {:font-size 13}
:ios {:font-size 14}}) :ios {:font-size 14}})
@ -107,15 +98,15 @@
(defnstyle message-view (defnstyle message-view
[{:keys [content-type outgoing group-chat selected]}] [{:keys [content-type outgoing group-chat selected]}]
(merge {:padding 12 (merge {:padding 12
:backgroundColor color-white :backgroundColor styles/color-white
:android {:border-radius 4} :android {:border-radius 4}
:ios {:border-radius 8}} :ios {:border-radius 8}}
(when (= content-type content-type-command) (when (= content-type constants/content-type-command)
{:paddingTop 10 {:paddingTop 10
:paddingBottom 14}))) :paddingBottom 14})))
(defstyle author (defstyle author
{:color color-gray4 {:color styles/color-gray4
:margin-bottom 5 :margin-bottom 5
:android {:font-size 13} :android {:font-size 13}
:ios {:font-size 14}}) :ios {:font-size 14}})
@ -127,7 +118,7 @@
{:borderRadius 14 {:borderRadius 14
:padding-vertical 10 :padding-vertical 10
:paddingRight 28 :paddingRight 28
:backgroundColor color-white}) :backgroundColor styles/color-white})
(def command-request-from-text (def command-request-from-text
(merge style-sub-text {:marginBottom 2})) (merge style-sub-text {:marginBottom 2}))
@ -245,14 +236,14 @@
(def status-from (def status-from
{:marginTop 20 {:marginTop 20
:fontSize 18 :fontSize 18
:color text1-color}) :color styles/text1-color})
(def status-text (def status-text
{:marginTop 10 {:marginTop 10
:fontSize 14 :fontSize 14
:lineHeight 20 :lineHeight 20
:textAlign :center :textAlign :center
:color text2-color}) :color styles/text2-color})
(defn message-animated-container [height] (defn message-animated-container [height]
{:height height}) {:height height})
@ -262,6 +253,6 @@
:width window-width}) :width window-width})
(defn new-message-container [margin on-top?] (defn new-message-container [margin on-top?]
{:background-color color-white {:background-color styles/color-white
:margin-bottom margin :margin-bottom margin
:elevation (if on-top? 6 5)}) :elevation (if on-top? 6 5)})

View File

@ -1,18 +1,15 @@
(ns status-im.chat.subs (ns status-im.chat.subs
(:require [re-frame.core :refer [reg-sub dispatch subscribe path]] (:require [re-frame.core :refer [reg-sub subscribe]]
[status-im.data-store.chats :as chats] [status-im.constants :as constants]
[status-im.chat.constants :as const]
[status-im.chat.models.input :as input-model] [status-im.chat.models.input :as input-model]
[status-im.chat.models.commands :as commands-model] [status-im.chat.models.commands :as commands-model]
[status-im.chat.utils :as chat-utils] [status-im.chat.utils :as chat-utils]
[status-im.chat.views.input.utils :as input-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.commands.utils :as commands-utils]
[status-im.utils.platform :refer [platform-specific ios?]] [status-im.utils.datetime :as time]
[taoensso.timbre :as log] [status-im.utils.platform :as platform]
[clojure.string :as str])) [status-im.i18n :as i18n]
[clojure.string :as string]))
(reg-sub :chats :chats) (reg-sub :chats :chats)
@ -49,14 +46,20 @@
:chat-input-margin :chat-input-margin
:<- [:get :keyboard-height] :<- [:get :keyboard-height]
(fn [kb-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 (reg-sub
:get-current-chat :get-current-chat
:<- [:chats] (fn [_]
:<- [:get-current-chat-id] (let [current-chat-id (subscribe [:get-current-chat-id])]
(fn [[chats id]] (subscribe [:get-chat @current-chat-id])))
(get chats id))) identity)
(reg-sub (reg-sub
:chat :chat
@ -65,6 +68,78 @@
(fn [[chats id] [_ k chat-id]] (fn [[chats id] [_ k chat-id]]
(get-in chats [(or chat-id id) k]))) (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 (reg-sub
:get-commands-for-chat :get-commands-for-chat
:<- [:get-commands-responses-by-access-scope] :<- [:get-commands-responses-by-access-scope]
@ -80,27 +155,26 @@
:<- [:get-current-account] :<- [:get-current-account]
:<- [:get-current-chat] :<- [:get-current-chat]
:<- [:get-contacts] :<- [:get-contacts]
:<- [:chat :requests] (fn [[commands-responses account {:keys [requests] :as chat} contacts]]
(fn [[commands-responses account chat contacts requests]]
(commands-model/requested-responses commands-responses account chat contacts (vals requests)))) (commands-model/requested-responses commands-responses account chat contacts (vals requests))))
(def ^:private map->sorted-seq (comp (partial map second) (partial sort-by first))) (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 (->> commands-responses
map->sorted-seq 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 (reg-sub
:get-available-commands :get-available-commands
:<- [:get-commands-for-chat] :<- [:get-commands-for-chat]
:<- [:chat :input-text] :<- [:get-current-chat]
available-commands-responses) available-commands-responses)
(reg-sub (reg-sub
:get-available-responses :get-available-responses
:<- [:get-responses-for-chat] :<- [:get-responses-for-chat]
:<- [:chat :input-text] :<- [:get-current-chat]
available-commands-responses) available-commands-responses)
(reg-sub (reg-sub
@ -121,10 +195,9 @@
(reg-sub (reg-sub
:current-chat-argument-position :current-chat-argument-position
:<- [:selected-chat-command] :<- [:selected-chat-command]
:<- [:chat :input-text] :<- [:get-current-chat]
:<- [:chat :seq-arguments]
:<- [:get-current-chat-ui-prop :selection] :<- [: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))) (input-model/current-chat-argument-position command input-text selection seq-arguments)))
(reg-sub (reg-sub
@ -150,9 +223,9 @@
:show-parameter-box? :show-parameter-box?
:<- [:chat-parameter-box] :<- [:chat-parameter-box]
:<- [:show-suggestions?] :<- [:show-suggestions?]
:<- [:chat :input-text] :<- [:get-current-chat]
:<- [:validation-messages] :<- [: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) (and (get chat-parameter-box :markup)
(not validation-messages) (not validation-messages)
(not show-suggestions?)))) (not show-suggestions?))))
@ -165,24 +238,26 @@
(reg-sub (reg-sub
:show-suggestions? :show-suggestions?
:<- [:get-current-chat-ui-prop :show-suggestions?] :<- [:get-current-chat-ui-prop :show-suggestions?]
:<- [:chat :input-text] :<- [:get-current-chat]
:<- [:selected-chat-command] :<- [:selected-chat-command]
:<- [:get-available-commands-responses] :<- [:get-available-commands-responses]
(fn [[show-suggestions? input-text selected-command commands-responses]] (fn [[show-suggestions? {:keys [input-text]} selected-command commands-responses]]
(and (or show-suggestions? (input-model/starts-as-command? (str/trim (or input-text "")))) (and (or show-suggestions? (input-model/starts-as-command? (string/trim (or input-text ""))))
(not (:command selected-command)) (not (:command selected-command))
(seq commands-responses)))) (seq commands-responses))))
(reg-sub (reg-sub
:is-request-answered? :is-request-answered?
:<- [:chat :requests] :<- [:get-current-chat]
(fn [requests [_ message-id]] (fn [{:keys [requests]} [_ message-id]]
(not= "open" (get-in requests [message-id :status])))) (not= "open" (get-in requests [message-id :status]))))
(reg-sub (reg-sub
:unviewed-messages-count :unviewed-messages-count
(fn [db [_ chat-id]] (fn [[_ chat-id]]
(get-in db [:unviewed-messages chat-id :count]))) (subscribe [:get-chat chat-id]))
(fn [{:keys [unviewed-messages]}]
(count unviewed-messages)))
(reg-sub (reg-sub
:web-view-extra-js :web-view-extra-js
@ -190,42 +265,17 @@
(fn [current-chat] (fn [current-chat]
(:web-view-extra-js 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 (reg-sub
:photo-path :photo-path
:<- [:get-contacts] :<- [:get-contacts]
(fn [contacts [_ id]] (fn [contacts [_ id]]
(:photo-path (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 (reg-sub
:get-last-message :get-last-message
:<- [:chats] (fn [[_ chat-id]]
(fn [chats [_ chat-id]] (subscribe [:get-chat-message-datemark-groups chat-id]))
(let [{:keys [last-message messages]} (get chats chat-id)] (comp first second first))
(->> (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)))))
(reg-sub (reg-sub
:get-default-container-area-height :get-default-container-area-height
@ -250,25 +300,3 @@
(fn [db [_ key type]] (fn [db [_ key type]]
(let [chat-id (subscribe [:get-current-chat-id])] (let [chat-id (subscribe [:get-current-chat-id])]
(get-in db [:chat-animations @chat-id key type])))) (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))))

View File

@ -1,47 +1,13 @@
(ns status-im.chat.utils (ns status-im.chat.utils
(:require [clojure.string :as str] (:require [status-im.chat.constants :as chat.constants]))
[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)))
(defn add-message-to-db (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] (add-message-to-db db add-to-chat-id chat-id message true))
([db add-to-chat-id chat-id message new?] ([db add-to-chat-id chat-id {:keys [message-id] :as message} new?]
(let [messages [:chats add-to-chat-id :messages]] (let [prepared-message (assoc message
(update-in db messages conj (assoc message :chat-id chat-id :chat-id chat-id
:new? (if (nil? new?) :new? (if (nil? new?) true new?))]
true (update-in db [:chats add-to-chat-id :messages] assoc message-id prepared-message))))
new?))))))
(defn- check-message [previous-message {:keys [from outgoing] :as message}] (defn command-name [{:keys [name]}]
(merge message (str chat.constants/command-char name))
{: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)))

View File

@ -121,7 +121,7 @@
(let [input (str/trim (or @input-text "")) (let [input (str/trim (or @input-text ""))
real-args (remove str/blank? (:args command))] real-args (remove str/blank? (:args command))]
(when-let [placeholder (cond (when-let [placeholder (cond
(#{const/command-char const/bot-char} input) (= const/command-char input)
(i18n/label :t/type-a-command) (i18n/label :t/type-a-command)
(and command (empty? real-args)) (and command (empty? real-args))

View File

@ -1,13 +1,10 @@
(ns status-im.chat.views.message.datemark (ns status-im.chat.views.message.datemark
(:require [re-frame.core :refer [subscribe dispatch]] (:require [status-im.ui.components.react :as react]
[status-im.ui.components.react :refer [view
text]]
[clojure.string :as str] [clojure.string :as str]
[status-im.i18n :refer [label]]
[status-im.chat.styles.message.datemark :as st])) [status-im.chat.styles.message.datemark :as st]))
(defn chat-datemark [value] (defn chat-datemark [value]
[view st/datemark-wrapper [react/view st/datemark-wrapper
[view st/datemark [react/view st/datemark
[text {:style st/datemark-text} [react/text {:style st/datemark-text}
(str/capitalize (or value (label :t/datetime-today)))]]]) (str/capitalize value)]]])

View File

@ -1,154 +1,97 @@
(ns status-im.chat.views.message.message (ns status-im.chat.views.message.message
(:require-macros [status-im.utils.views :refer [defview letsubs]]) (: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] [clojure.walk :as walk]
[reagent.core :as r] [reagent.core :as reagent]
[status-im.i18n :refer [message-status-label]] [status-im.ui.components.react :as react]
[status-im.ui.components.react :refer [view [status-im.ui.components.animation :as animation]
text [status-im.ui.components.list-selection :as list-selection]
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]
[status-im.chat.models.commands :as commands] [status-im.chat.models.commands :as commands]
[status-im.chat.styles.message.message :as st] [status-im.commands.utils :as commands.utils]
[status-im.chat.styles.message.command-pill :as pill-st] [status-im.chat.utils :as chat.utils]
[status-im.chat.views.message.request-message :refer [message-content-command-request]] [status-im.chat.styles.message.message :as style]
[status-im.chat.views.message.datemark :refer [chat-datemark]] [status-im.chat.styles.message.command-pill :as pill-style]
[status-im.react-native.resources :as res] [status-im.chat.views.message.request-message :as request-message]
[status-im.constants :refer [console-chat-id [status-im.constants :as constants]
text-content-type [status-im.ui.components.chat-icon.screen :as chat-icon.screen]
content-type-log-message [status-im.utils.identicon :as identicon]
content-type-status [status-im.utils.gfycat.core :as gfycat]
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.utils.platform :as platform] [status-im.utils.platform :as platform]
[status-im.i18n :refer [label [status-im.i18n :as i18n]
get-contact-translated]] [clojure.string :as string]
[status-im.chat.utils :as cu] [status-im.chat.events.console :as console]))
[clojure.string :as str]
[status-im.chat.events.console :as console]
[taoensso.timbre :as log]))
(def window-width (:width (get-dimensions "window"))) (def window-width (:width (react/get-dimensions "window")))
(defview message-author-name [{:keys [outgoing from] :as message}] (defview message-author-name [{:keys [outgoing from] :as message}]
[current-account [:get-current-account] (letsubs [current-account [:get-current-account]
incoming-name [:contact-name-by-identity from]] incoming-name [:contact-name-by-identity from]]
(if-let [name (if outgoing (when-let [name (if outgoing
(:name current-account) (:name current-account)
(or incoming-name "Unknown contact"))] (or incoming-name "Unknown contact"))]
[text {:style st/author} name])) [react/text {:style style/author} name])))
(defview message-content-status (defview message-content-status []
[{:keys [messages-count content datemark]}] (letsubs [{:keys [chat-id group-id name color public-key]} [:get-current-chat]
(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]] members [:current-chat-contacts]]
(let [{:keys [status]} (if group-chat (let [{:keys [status]} (if group-id
{:photo-path nil {:status nil}
:status nil
:last-online 0}
(first members))] (first members))]
[view st/status-container [react/view style/status-container
[chat-icon-message-status chat-id group-chat name color false] [chat-icon.screen/chat-icon-message-status chat-id group-id name color false]
[text {:style st/status-from [react/text {:style style/status-from
:font :default :font :default
:number-of-lines 1} :number-of-lines 1}
(if (str/blank? name) (if (string/blank? name)
(generate-gfy public-key) (gfycat/generate-gfy public-key)
(or (get-contact-translated chat-id :name name) (or (i18n/get-contact-translated chat-id :name name)
(label :t/chat-name)))] (i18n/label :t/chat-name)))]
(when (or status content) (when status
[text {:style st/status-text [react/text {:style style/status-text
:font :default} :font :default}
(or status content)]) status])])))
(if (> messages-count 1)
[view st/message-datemark
[chat-datemark datemark]]
[view st/message-empty-spacing])])))
(defn message-content-audio [_] (defn message-content-audio [_]
[view st/audio-container [react/view style/audio-container
[view st/play-view [react/view style/play-view
[image {;:source res/play [react/image {:style style/play-image}]]
:style st/play-image}]] [react/view style/track-container
[view st/track-container [react/view style/track]
[view st/track] [react/view style/track-mark]
[view st/track-mark] [react/text {:style style/track-duration-text
[text {:style st/track-duration-text
:font :default} :font :default}
"03:39"]]]) "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 (defview message-content-command
[{:keys [message-id content content-type chat-id to from outgoing] :as message}] [{:keys [content params] :as message}]
(letsubs [command [:get-command (:content-command-ref content)] (letsubs [command [:get-command (:content-command-ref content)]]
current-chat-id [:get-current-chat-id] {:component-will-mount #(when-not (:preview content)
contact-chat [:get-in [:chats (if outgoing to from)]] (re-frame/dispatch [:request-command-message-data
preview [:get-message-preview message-id]] message {:data-type :preview
(let [{:keys [name type] :cache-data? true}]))}
icon-path :icon} command] (let [preview (:preview content)
[view st/content-command-view {:keys [type color] icon-path :icon} command]
(when (:color command) [react/view style/content-command-view
[view st/command-container (when color
[view (pill-st/pill command) [react/view style/command-container
[text {:style pill-st/pill-text [react/view (pill-style/pill command)
[react/text {:style pill-style/pill-text
:font :default} :font :default}
(str chat-consts/command-char name)]]]) (chat.utils/command-name command)]]])
(when icon-path (when icon-path
[view st/command-image-view [react/view style/command-image-view
[icon icon-path st/command-image]]) [react/icon icon-path style/command-image]])
[command-preview {:command (:name command) (if (:markup preview)
:content-type content-type ;; Markup was defined for command in jail, generate hiccup and render it
:params (:params content) (commands.utils/generate-hiccup (:markup preview))
:outgoing? outgoing ;; Display preview if it's defined (as a string), in worst case, render params
:preview preview [react/text {:style style/command-text
:contact-chat contact-chat :font :default}
:contact-address (if outgoing to from) (or preview (str params))])])))
:current-chat-id current-chat-id}]])))
(defn message-view (defn message-view
[{:keys [same-author index group-chat] :as message} content] [{:keys [group-chat] :as message} content]
[view (st/message-view message) [react/view (style/message-view message)
(when group-chat [message-author-name message]) (when group-chat [message-author-name message])
content]) content])
@ -156,12 +99,11 @@
{"\\*[^*]+\\*" {:font-weight :bold} {"\\*[^*]+\\*" {:font-weight :bold}
"~[^~]+~" {:font-style :italic}}) "~[^~]+~" {: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] (defn get-style [string]
(->> replacements (->> replacements
(into [] (comp (into [] (comp (map first)
(map first)
(map #(vector % (re-pattern %))) (map #(vector % (re-pattern %)))
(drop-while (fn [[_ regx]] (not (re-matches regx string)))) (drop-while (fn [[_ regx]] (not (re-matches regx string))))
(take 1))) (take 1)))
@ -171,14 +113,13 @@
;; todo rewrite this, naive implementation ;; todo rewrite this, naive implementation
(defn- parse-text [string] (defn- parse-text [string]
(if (string? 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)) general-text' (if (zero? (count general-text))
[nil] [nil]
general-text) general-text)
styled-text (vec (map-indexed styled-text (vec (map-indexed (fn [idx string]
(fn [idx string]
(let [style (get-style string)] (let [style (get-style string)]
[text [react/text
{:key (str idx "_" string) {:key (str idx "_" string)
:style style} :style style}
(subs string 1 (dec (count string)))])) (subs string 1 (dec (count string)))]))
@ -197,41 +138,31 @@
simple-text? (and (= (count parsed-text) 2) simple-text? (and (= (count parsed-text) 2)
(nil? (second parsed-text)))] (nil? (second parsed-text)))]
(if simple-text? (if simple-text?
[autolink {:style (st/text-message message) [react/autolink {:style (style/text-message message)
:text (apply str parsed-text) :text (apply str parsed-text)
:onPress #(dispatch [:browse-link-from-message %])}] :onPress #(re-frame/dispatch [:browse-link-from-message %])}]
[text {:style (st/text-message message)} parsed-text]))]) [react/text {:style (style/text-message message)} parsed-text]))])
(defmulti message-content (fn [_ message _] (message :content-type))) (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]
[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 (defmethod message-content constants/text-content-type
[wrapper message]
[wrapper message
[message-view message [message-content-command-request message]]])
(defmethod message-content text-content-type
[wrapper message] [wrapper message]
[wrapper message [text-message 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]
[wrapper message [text-message message]]) [wrapper message [text-message message]])
(defmethod message-content content-type-status (defmethod message-content constants/content-type-status
[_ message] [_ message]
[message-content-status message]) [message-content-status])
(defmethod message-content content-type-command (defmethod message-content constants/content-type-command
[wrapper message]
[wrapper message
[message-view message [message-content-command message]]])
(defmethod message-content c/content-type-wallet-command
[wrapper message] [wrapper message]
[wrapper message [wrapper message
[message-view message [message-content-command message]]]) [message-view message [message-content-command message]]])
@ -244,98 +175,93 @@
:content-type content-type}]]]) :content-type content-type}]]])
(defview group-message-delivery-status [{:keys [message-id group-id message-status user-statuses] :as msg}] (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]] (letsubs [chat [:get-current-chat]
app-db-message-status-value [:get-in [:message-data :statuses message-id :status]]
chat [:get-current-chat]
contacts [:get-contacts]] contacts [:get-contacts]]
(let [status (or message-status app-db-message-status-value :sending) (let [status (or message-status :sending)
user-statuses (merge user-statuses app-db-message-user-statuses)
participants (:contacts chat) participants (:contacts chat)
seen-by-everyone? (and (= (count user-statuses) (count participants)) seen-by-everyone? (and (= (count user-statuses) (count participants))
(every? (fn [[_ {:keys [status]}]] (every? (fn [[_ {:keys [status]}]]
(= (keyword status) :seen)) user-statuses))] (= (keyword status) :seen)) user-statuses))]
(if (or (zero? (count user-statuses)) (if (or (zero? (count user-statuses))
seen-by-everyone?) seen-by-everyone?)
[view st/delivery-view [react/view style/delivery-view
[text {:style st/delivery-text [react/text {:style style/delivery-text
:font :default} :font :default}
(message-status-label (i18n/message-status-label
(if seen-by-everyone? (if seen-by-everyone?
:seen-by-everyone :seen-by-everyone
status))]] status))]]
[touchable-highlight [react/touchable-highlight
{:on-press (fn [] {:on-press (fn []
(dispatch [:show-message-details {:message-status status (re-frame/dispatch [:show-message-details {:message-status status
:user-statuses user-statuses :user-statuses user-statuses
:participants participants}]))} :participants participants}]))}
[view st/delivery-view [react/view style/delivery-view
(for [[_ {:keys [whisper-identity]}] (take 3 user-statuses)] (for [[_ {:keys [whisper-identity]}] (take 3 user-statuses)]
^{:key whisper-identity} ^{:key whisper-identity}
[image {:source {:uri (or (get-in contacts [whisper-identity :photo-path]) [react/image {:source {:uri (or (get-in contacts [whisper-identity :photo-path])
(identicon whisper-identity))} (identicon/identicon whisper-identity))}
:style {:width 16 :style {:width 16
:height 16 :height 16
:borderRadius 8}}]) :borderRadius 8}}])
(if (> (count user-statuses) 3) (if (> (count user-statuses) 3)
[text {:style st/delivery-text [react/text {:style style/delivery-text
:font :default} :font :default}
(str "+ " (- (count user-statuses) 3))])]]))) (str "+ " (- (count user-statuses) 3))])]]))))
(defview message-delivery-status (defn message-delivery-status
[{:keys [message-id chat-id message-status user-statuses content]}] [{: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]) (let [delivery-status (get-in user-statuses [chat-id :status])
status (cond (and (not (console/commands-with-delivery-status (:command content))) status (cond (and (not (console/commands-with-delivery-status (:command content)))
(cu/console? chat-id)) (= constants/console-chat-id chat-id))
:seen :seen
:else :else
(or delivery-status message-status app-db-message-status-value :sending))] (or delivery-status message-status :sending))]
[view st/delivery-view [react/view style/delivery-view
[text {:style st/delivery-text [react/text {:style style/delivery-text
:font :default} :font :default}
(message-status-label status)]])) (i18n/message-status-label status)]]))
(defview member-photo [from] (defview member-photo [from]
[photo-path [:photo-path from]] (letsubs [photo-path [:photo-path from]]
[view [react/view
[image {:source {:uri (if (str/blank? photo-path) [react/image {:source {:uri (if (string/blank? photo-path)
(identicon from) (identicon/identicon from)
photo-path)} photo-path)}
:style st/photo}]]) :style style/photo}]]))
(defview my-photo [from] (defview my-photo [from]
[account [:get-current-account]] (letsubs [account [:get-current-account]]
(let [{:keys [photo-path]} account] (let [{:keys [photo-path]} account]
[view [react/view
[image {:source {:uri (if (str/blank? photo-path) [react/image {:source {:uri (if (string/blank? photo-path)
(identicon from) (identicon/identicon from)
photo-path)} photo-path)}
:style st/photo}]])) :style style/photo}]])))
(defn message-body (defn message-body
[{:keys [last-outgoing? message-type same-author from index outgoing] :as message} content] [{:keys [last-outgoing? message-type same-author? from outgoing] :as message} content]
(let [delivery-status :seen-by-everyone] [react/view style/group-message-wrapper
[view st/group-message-wrapper [react/view (style/message-body message)
[view (st/message-body message) [react/view style/message-author
[view st/message-author (when-not same-author?
(when (or (= index 1) (not same-author))
(if outgoing (if outgoing
[my-photo from] [my-photo from]
[member-photo from]))] [member-photo from]))]
[view (st/group-message-view message) [react/view (style/group-message-view message)
content content
(when last-outgoing? (when last-outgoing?
(if (= (keyword message-type) :group-user-message) (if (= (keyword message-type) :group-user-message)
[group-message-delivery-status message] [group-message-delivery-status message]
[message-delivery-status message]))]]])) [message-delivery-status message]))]]])
(defn message-container-animation-logic [{:keys [to-value val callback]}] (defn message-container-animation-logic [{:keys [to-value val callback]}]
(fn [_] (fn [_]
(let [to-value @to-value] (let [to-value @to-value]
(when (pos? to-value) (when (pos? to-value)
(anim/start (animation/start
(anim/timing val {:toValue to-value (animation/timing val {:toValue to-value
:duration 250}) :duration 250})
(fn [arg] (fn [arg]
(when (.-finished arg) (when (.-finished arg)
@ -343,63 +269,56 @@
(defn message-container [message & children] (defn message-container [message & children]
(if (:new? message) (if (:new? message)
(let [layout-height (r/atom 0) (let [layout-height (reagent/atom 0)
anim-value (anim/create-value 1) anim-value (animation/create-value 1)
anim-callback #(dispatch [:set-message-shown message]) anim-callback #(re-frame/dispatch [:set-message-shown message])
context {:to-value layout-height context {:to-value layout-height
:val anim-value :val anim-value
:callback anim-callback} :callback anim-callback}
on-update (message-container-animation-logic context)] on-update (message-container-animation-logic context)]
(r/create-class (reagent/create-class
{:component-did-update {:component-did-update
on-update on-update
:display-name "message-container" :display-name "message-container"
:reagent-render :reagent-render
(fn [_ & children] (fn [_ & children]
@layout-height @layout-height
[animated-view {:style (st/message-animated-container anim-value)} [react/animated-view {:style (style/message-animated-container anim-value)}
(into [view {:style (st/message-container window-width) (into [react/view {:style (style/message-container window-width)
:onLayout (fn [event] :onLayout (fn [event]
(let [height (.. event -nativeEvent -layout -height)] (let [height (.. event -nativeEvent -layout -height)]
(reset! layout-height height)))}] (reset! layout-height height)))}]
children)])})) children)])}))
(into [view] children))) (into [react/view] children)))
(defn chat-message [{:keys [outgoing message-id chat-id user-statuses from] :as message}] (defn chat-message [{:keys [outgoing message-id chat-id message-status user-statuses
(let [my-identity (subscribe [:get :current-public-key]) from current-public-key] :as message}]
status (subscribe [:get-in [:message-data :user-statuses message-id my-identity]]) (reagent/create-class
preview (subscribe [:get-message-preview message-id])]
(r/create-class
{:display-name "chat-message" {: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 :component-did-mount
(fn [] #(when (and message-id
(when (and (not outgoing) chat-id
(not= :seen (keyword @status)) (not outgoing)
(not= :seen (keyword (get-in user-statuses [@my-identity :status])))) (not= :seen message-status)
(dispatch [:send-seen! {:chat-id chat-id (not= :seen (keyword (get-in user-statuses [current-public-key :status]))))
(re-frame/dispatch [:send-seen! {:chat-id chat-id
:from from :from from
:message-id message-id}]))) :message-id message-id}]))
:reagent-render :reagent-render
(fn [{:keys [outgoing group-chat content-type content] :as message}] (fn [{:keys [outgoing group-chat content-type content] :as message}]
[message-container message [message-container message
[touchable-highlight {:on-press #(when platform/ios? [react/touchable-highlight {:on-press #(when platform/ios?
(dispatch [:set-chat-ui-props (re-frame/dispatch [:set-chat-ui-props
{:show-emoji? false}]) {:show-emoji? false}])
(dismiss-keyboard!)) (react/dismiss-keyboard!))
:on-long-press #(cond (= content-type text-content-type) :on-long-press #(cond (= content-type constants/text-content-type)
(share content (label :t/message)) (list-selection/share content (i18n/label :t/message))
(and (= content-type content-type-command) (= "location" (:content-command content))) (and (= content-type constants/content-type-command)
(= "location" (:content-command content)))
(let [address (get-in content [:params :address]) (let [address (get-in content [:params :address])
[location lat long] (str/split address #"&amp;")] [location lat long] (string/split address #"&amp;")]
(share-or-open-map location lat long)))} (list-selection/share-or-open-map location lat long)))}
[view [react/view
(let [incoming-group (and group-chat (not outgoing))] (let [incoming-group (and group-chat (not outgoing))]
[message-content message-body (merge message [message-content message-body (merge message
{:incoming-group incoming-group})])]]])}))) {:incoming-group incoming-group})])]]])}))

View File

@ -10,6 +10,7 @@
touchable-highlight]] touchable-highlight]]
[status-im.chat.styles.message.message :as st] [status-im.chat.styles.message.message :as st]
[status-im.chat.models.commands :as commands] [status-im.chat.models.commands :as commands]
[status-im.commands.utils :as commands-utils]
[status-im.ui.components.animation :as anim] [status-im.ui.components.animation :as anim]
[taoensso.timbre :as log])) [taoensso.timbre :as log]))
@ -72,12 +73,15 @@
[icon command-icon st/command-request-image])]]))}))) [icon command-icon st/command-request-image])]]))})))
(defview message-content-command-request (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)] (letsubs [command [:get-command (:content-command-ref content)]
answered? [:is-request-answered? message-id] answered? [:is-request-answered? message-id]
status-initialized? [:get :status-module-initialized?] status-initialized? [:get :status-module-initialized?]]
markup [:get-message-preview message-id]] {:component-will-mount #(when-not (:preview content)
(let [{:keys [prefill prefill-bot-db prefillBotDb params] (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 text-content :text} content
command (if (and params command) command (if (and params command)
(merge command {:prefill prefill (merge command {:prefill prefill
@ -91,12 +95,11 @@
[touchable-highlight [touchable-highlight
{:on-press on-press-handler} {:on-press on-press-handler}
[view st/command-request-message-view [view st/command-request-message-view
(if (and markup (if (:markup preview)
(not (string? markup))) [view (commands-utils/generate-hiccup (:markup preview))]
[view markup]
[text {:style st/style-message-text [text {:style st/style-message-text
:font :default} :font :default}
(or text-content markup (:content content))])]] (or preview text-content (:content content))])]]
(when (:request-text command) (when (:request-text command)
[view st/command-request-text-view [view st/command-request-text-view
[text {:style st/style-sub-text [text {:style st/style-sub-text

View File

@ -14,8 +14,6 @@
(def content-type-log-message "log-message") (def content-type-log-message "log-message")
(def content-type-command "command") (def content-type-command "command")
(def content-type-command-request "command-request") (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 content-type-status "status")
(def min-password-length 6) (def min-password-length 6)

View File

@ -20,9 +20,7 @@
(defn save (defn save
[{:keys [last-message-id chat-id] :as chat}] [{:keys [last-message-id chat-id] :as chat}]
;; TODO(janherich): remove `:last-message-id`, seems like it's not used anywhere anymore (data-store/save chat (data-store/exists? chat-id)))
(let [chat (assoc chat :last-message-id (or last-message-id ""))]
(data-store/save chat (data-store/exists? chat-id))))
(defn delete (defn delete
[chat-id] [chat-id]

View File

@ -9,15 +9,12 @@
(defn- command-type? (defn- command-type?
[type] [type]
(contains? (contains?
#{constants/content-type-command constants/content-type-command-request #{constants/content-type-command constants/content-type-command-request}
constants/content-type-wallet-request constants/content-type-wallet-command}
type)) type))
(def default-values (def default-values
{:outgoing false {:outgoing false
:to nil :to nil
:same-author false
:same-direction false
:preview nil}) :preview nil})
(defn exists? [message-id] (defn exists? [message-id]
@ -32,10 +29,6 @@
(when (command-type? content-type) (when (command-type? content-type)
(reader/read-string content)))) (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 (defn get-by-chat-id
([chat-id] ([chat-id]
(get-by-chat-id chat-id 0)) (get-by-chat-id chat-id 0))
@ -53,13 +46,6 @@
(filter #(= (:content-type %) constants/content-type-log-message)) (filter #(= (:content-type %) constants/content-type-log-message))
(map #(select-keys % [:content :timestamp])))) (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 (defn get-last-outgoing
[chat-id number-of-messages] [chat-id number-of-messages]
(data-store/get-by-fields {:chat-id chat-id (data-store/get-by-fields {:chat-id chat-id
@ -77,25 +63,19 @@
[] []
(data-store/get-unviewed)) (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] (defn- prepare-content [content]
(if (string? content)
content
(pr-str (pr-str
(update content :params dissoc :password :password-confirmation))) ;; 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 (defn save
;; todo remove chat-id parameter ;; todo remove chat-id parameter
[chat-id {:keys [message-id content] :as message}] [chat-id {:keys [message-id content] :as message}]
(when-not (data-store/exists? message-id) (when-not (data-store/exists? message-id)
(let [content' (if (string? content) (let [content' (prepare-content content)
content
(prepare-content content))
message' (merge default-values message' (merge default-values
message message
{:chat-id chat-id {:chat-id chat-id
@ -106,7 +86,9 @@
(defn update-message (defn update-message
[{:keys [message-id] :as message}] [{:keys [message-id] :as message}]
(when (data-store/exists? message-id) (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)))) (data-store/save message))))
(defn delete-by-chat-id [chat-id] (defn delete-by-chat-id [chat-id]

View File

@ -17,9 +17,6 @@
(realm/fix-map message :user-statuses :whisper-identity))) (realm/fix-map message :user-statuses :whisper-identity)))
(defn get-by-chat-id (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] ([chat-id number-of-messages]
(get-by-chat-id chat-id 0 number-of-messages)) (get-by-chat-id chat-id 0 number-of-messages))
([chat-id from number-of-messages] ([chat-id from number-of-messages]
@ -29,10 +26,6 @@
realm/js-object->clj)] realm/js-object->clj)]
(mapv #(realm/fix-map % :user-statuses :whisper-identity) messages)))) (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 (defn get-by-fields
[fields from number-of-messages] [fields from number-of-messages]
(-> (realm/get-by-fields @realm/account-realm :message :and fields) (-> (realm/get-by-fields @realm/account-realm :message :and fields)
@ -64,5 +57,6 @@
(defn delete-by-chat-id (defn delete-by-chat-id
[chat-id] [chat-id]
(realm/delete @realm/account-realm (let [current-realm @realm/account-realm]
(get-by-chat-id chat-id))) (realm/delete current-realm
(realm/get-by-field current-realm :message :chat-id chat-id))))

View File

@ -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}}})

View File

@ -1,9 +1,9 @@
(ns status-im.data-store.realm.schemas.account.v19.core (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.v1.chat-contact :as chat-contact]
[status-im.data-store.realm.schemas.account.v19.contact :as 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.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.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.v1.processed-message :as processed-message]
[status-im.data-store.realm.schemas.account.v19.request :as request] [status-im.data-store.realm.schemas.account.v19.request :as request]
@ -29,6 +29,13 @@
group-contact/schema group-contact/schema
local-storage/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] (defn remove-contact! [new-realm whisper-identity]
(when-let [contact (some-> new-realm (when-let [contact (some-> new-realm
(.objects "contact") (.objects "contact")
@ -75,9 +82,13 @@
(def transactor-requests->new-props (def transactor-requests->new-props
{;; former transactor-personal request {;; 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 ;; 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] (defn update-commands [selector mapping new-realm content-type]
(some-> new-realm (some-> new-realm
@ -94,6 +105,7 @@
(log/debug "migrating v19 account database: " old-realm new-realm) (log/debug "migrating v19 account database: " old-realm new-realm)
(remove-contact! new-realm "transactor-personal") (remove-contact! new-realm "transactor-personal")
(remove-contact! new-realm "transactor-group") (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 :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) console-requests->new-props new-realm "command-request")
(update-commands (juxt :command (comp count :prefill)) transactor-requests->new-props new-realm "command-request")) (update-commands (juxt :command (comp count :prefill)) transactor-requests->new-props new-realm "command-request"))

View File

@ -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}}})

View File

@ -33,7 +33,7 @@
(cond (cond
(= path [:responses "password" :preview]) (= path [:responses "password" :preview])
(callback {:result {:context {}, (callback {:result {:context {},
:messages [], :messages {},
:returned {:markup ["text" :returned {:markup ["text"
{:style {:style
{:color "black", {:color "black",

View File

@ -309,6 +309,11 @@
;;; MESSAGES ;;; MESSAGES
(defn- transform-protocol-message [{:keys [from to payload]}]
(merge payload {:from from
:to to
:chat-id from}))
(handlers/register-handler-fx (handlers/register-handler-fx
:incoming-message :incoming-message
(fn [_ [_ type {:keys [payload ttl id] :as message}]] (fn [_ [_ type {:keys [payload ttl id] :as message}]]
@ -320,9 +325,9 @@
:type type :type type
:ttl (+ (datetime/now-ms) ttl-s)} :ttl (+ (datetime/now-ms) ttl-s)}
route-event (case type route-event (case type
:message [:received-protocol-message! message] (:message
:group-message [:received-protocol-message! message] :group-message
:public-group-message [:received-protocol-message! message] :public-group-message) [:received-message (transform-protocol-message message)]
:ack (if (#{:message :group-message} (:type payload)) :ack (if (#{:message :group-message} (:type payload))
[:update-message-status message :delivered] [:update-message-status message :delivered]
[:pending-message-remove message]) [:pending-message-remove message])
@ -354,14 +359,19 @@
(defn update-message-status [db {:keys [message-id ack-of-message group-id from status]}] (defn update-message-status [db {:keys [message-id ack-of-message group-id from status]}]
(let [message-id' (or ack-of-message message-id) (let [message-id' (or ack-of-message message-id)
group? (boolean group-id) update-group-status? (and group-id (not= status :sent))
status-path (if (and group? (not= status :sent)) message-path [:chats (or group-id from) :messages message-id']
[:message-data :user-statuses message-id' from] current-status (if update-group-status?
[:message-data :statuses message-id']) (get-in db (into message-path [:user-statuses from :status]))
{current-status :status} (get-in db status-path)] (get-in db (into message-path [:message-status])))]
(if-not (= :seen current-status) ;; for some strange reason, we sometimes receive status update for message we don't have,
(assoc-in db status-path {:whisper-identity from ;; 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}) :status status})
(assoc-in db (into message-path [:message-status]) status))
db))) db)))
(handlers/register-handler-fx (handlers/register-handler-fx

View File

@ -41,7 +41,7 @@
[view pending-inner-circle]]])) [view pending-inner-circle]]]))
(defview chat-icon-view [chat-id group-chat name online styles & [hide-dapp?]] (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?]]] dapp? [:get-in [:contacts/contacts chat-id :dapp?]]]
[view (:container styles) [view (:container styles)
(if-not (s/blank? photo-path) (if-not (s/blank? photo-path)

View File

@ -28,13 +28,16 @@
(reg-fx (reg-fx
::change-account ::change-account
(fn [[address new-account?]] (fn [[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?
(js/setTimeout (js/setTimeout
(fn [] (fn []
(data-store/change-account address new-account? (data-store/change-account address new-account?
#(dispatch [:change-account-handler % address new-account?]))) #(dispatch [:change-account-handler % address new-account?])))
;; if we don't add delay when running app without status-go 300)
;; "null is not an object (evaluating 'realm.schema')" error appears (data-store/change-account address new-account?
(if config/stub-status-go? 300 0)))) #(dispatch [:change-account-handler % address new-account?])))))
;;;; Handlers ;;;; Handlers

View File

@ -1,62 +1,57 @@
(ns status-im.ui.screens.chats-list.views.inner-item (ns status-im.ui.screens.chats-list.views.inner-item
(:require-macros [status-im.utils.views :refer [defview letsubs]]) (: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] [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.icons.vector-icons :as vi]
[status-im.ui.components.chat-icon.screen :refer [chat-icon-view-chat-list]] [status-im.ui.components.chat-icon.screen :as chat-icon-screen]
[status-im.ui.components.context-menu :refer [context-menu]] [status-im.ui.components.context-menu :as context-menu]
[status-im.ui.screens.chats-list.styles :as st] [status-im.ui.screens.chats-list.styles :as st]
[status-im.utils.utils :refer [truncate-str]] [status-im.utils.utils :as utils]
[status-im.i18n :refer [get-contact-translated label label-pluralize]] [status-im.commands.utils :as commands-utils]
[status-im.i18n :as i18n]
[status-im.utils.datetime :as time] [status-im.utils.datetime :as time]
[status-im.utils.gfycat.core :refer [generate-gfy]] [status-im.utils.gfycat.core :as gfycat]
[status-im.constants :refer [console-chat-id [status-im.constants :as const]
content-type-command [taoensso.timbre :as log]))
content-type-wallet-command
content-type-command-request]]
[taoensso.timbre :as log]
[reagent.core :as r]))
(defn message-content-text [chat-id] (defn message-content-text [{:keys [content] :as message}]
(let [message (subscribe [:get-last-message chat-id]) (reagent/create-class
preview (subscribe [:get-last-message-short-preview chat-id])]
(r/create-class
{:display-name "message-content-text" {:display-name "message-content-text"
:component-will-mount :component-will-mount
(fn [] #(when (and (or (:command content)
(when (and (get-in @message [:content :command]) (:content-command content))
(not @preview)) (not (:short-preview content)))
(dispatch [:request-command-message-data @message :short-preview]))) (re-frame/dispatch [:request-command-message-data message
{:data-type :short-preview
:cache-data? true}]))
:reagent-render :reagent-render
(fn [_] (fn [{:keys [content] :as message}]
[view] [react/view st/last-message-container
(let [{:keys [content] :as message} @message
preview @preview]
[view st/last-message-container
(cond (cond
(not message) (not message)
[text {:style st/last-message-text} [react/text {:style st/last-message-text}
(label :t/no-messages)] (i18n/label :t/no-messages)]
(str/blank? content) (str/blank? content)
[text {:style st/last-message-text} [react/text {:style st/last-message-text}
""] ""]
(:content content) (:content content)
[text {:style st/last-message-text [react/text {:style st/last-message-text
:number-of-lines 1} :number-of-lines 1}
(:content content)] (:content content)]
(:command content) (and (:command content)
preview (-> content :short-preview :markup))
(commands-utils/generate-hiccup (-> content :short-preview :markup))
:else :else
[text {:style st/last-message-text [react/text {:style st/last-message-text
:number-of-lines 1} :number-of-lines 1}
content])]))}))) content])])}))
(defview message-status [{:keys [chat-id contacts]} (defview message-status [{:keys [chat-id contacts]}
{:keys [message-id message-status user-statuses message-type outgoing] :as msg}] {:keys [message-id message-status user-statuses message-type outgoing] :as msg}]
@ -70,29 +65,29 @@
(and (= (count user-statuses) (count contacts)) (and (= (count user-statuses) (count contacts))
(every? (fn [[_ {:keys [status]}]] (every? (fn [[_ {:keys [status]}]]
(= (keyword status) :seen)) user-statuses))) (= (keyword status) :seen)) user-statuses)))
(= chat-id console-chat-id))) (= chat-id const/console-chat-id)))
[image {:source {:uri :icon_ok_small} [react/image {:source {:uri :icon_ok_small}
:style st/status-image}])))) :style st/status-image}]))))
(defn message-timestamp [{:keys [timestamp]}] (defn message-timestamp [{:keys [timestamp]}]
(when timestamp (when timestamp
[text {:style st/datetime-text} [react/text {:style st/datetime-text}
(time/to-short-str timestamp)])) (time/to-short-str timestamp)]))
(defview unviewed-indicator [chat-id] (defview unviewed-indicator [chat-id]
(letsubs [unviewed-messages [:unviewed-messages-count chat-id]] (letsubs [unviewed-messages-count [:unviewed-messages-count chat-id]]
(when (pos? unviewed-messages) (when (pos? unviewed-messages-count)
[view st/new-messages-container [react/view st/new-messages-container
[text {:style st/new-messages-text [react/text {:style st/new-messages-text
:font :medium} :font :medium}
unviewed-messages]]))) unviewed-messages-count]])))
(defn options-btn [chat-id] (defn options-btn [chat-id]
(let [options [{:value #(dispatch [:remove-chat chat-id]) (let [options [{:value #(re-frame/dispatch [:remove-chat chat-id])
:text (label :t/delete-chat) :text (i18n/label :t/delete-chat)
:destructive? true}]] :destructive? true}]]
[view st/opts-btn-container [react/view st/opts-btn-container
[context-menu [context-menu/context-menu
[vi/icon :icons/options] [vi/icon :icons/options]
options options
nil nil
@ -102,41 +97,41 @@
(let [private-group? (and group-chat? (not public?)) (let [private-group? (and group-chat? (not public?))
public-group? (and group-chat? public?) public-group? (and group-chat? public?)
chat-name (if (str/blank? name) chat-name (if (str/blank? name)
(generate-gfy public-key) (gfycat/generate-gfy public-key)
(truncate-str name 30))] (utils/truncate-str name 30))]
[view st/name-view [react/view st/name-view
(when public-group? (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}]]) [vi/icon :icons/public-chat {:style st/public-group-icon}]])
(when private-group? (when private-group?
[view st/private-group-icon-container [react/view st/private-group-icon-container
[vi/icon :icons/group-chat {:style st/private-group-icon}]]) [vi/icon :icons/group-chat {:style st/private-group-icon}]])
[view {:flex-shrink 1} [react/view {:flex-shrink 1}
[text {:style st/name-text [react/text {:style st/name-text
:number-of-lines 1} :number-of-lines 1}
(if public-group? (if public-group?
(str "#" chat-name) (str "#" chat-name)
chat-name)]]])) chat-name)]]]))
(defn chat-list-item-inner-view [{:keys [chat-id name color online (defview chat-list-item-inner-view [{:keys [chat-id name color online
group-chat contacts public? group-chat contacts public?
public-key unremovable?] :as chat} public-key unremovable?] :as chat}
edit?] edit?]
(let [last-message (subscribe [:get-last-message chat-id]) (letsubs [last-message [:get-last-message chat-id]]
name (or (get-contact-translated chat-id :name name) (let [name (or (i18n/get-contact-translated chat-id :name name)
(generate-gfy public-key))] (gfycat/generate-gfy public-key))]
[view st/chat-container [react/view st/chat-container
[view st/chat-icon-container [react/view st/chat-icon-container
[chat-icon-view-chat-list chat-id group-chat name color online]] [chat-icon-screen/chat-icon-view-chat-list chat-id group-chat name color online]]
[view st/chat-info-container [react/view st/chat-info-container
[view st/item-upper-container [react/view st/item-upper-container
[chat-list-item-name name group-chat public? public-key] [chat-list-item-name name group-chat public? public-key]
(when (and (not edit?) @last-message) (when (and (not edit?) last-message)
[view st/message-status-container [react/view st/message-status-container
[message-status chat @last-message] [message-status chat last-message]
[message-timestamp @last-message]])] [message-timestamp last-message]])]
[view st/item-lower-container [react/view st/item-lower-container
[message-content-text chat-id] [message-content-text last-message]
(when-not edit? [unviewed-indicator chat-id])]] (when-not edit? [unviewed-indicator chat-id])]]
[view st/chat-options-container [react/view st/chat-options-container
(when (and edit? (not unremovable?)) [options-btn chat-id])]])) (when (and edit? (not unremovable?)) [options-btn chat-id])]])))

View File

@ -153,11 +153,6 @@
(fn [contacts [_ identity]] (fn [contacts [_ identity]]
(:name (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]] (defn chat-contacts [[chat contacts] [_ fn]]
(when chat (when chat
(let [current-participants (->> chat (let [current-participants (->> chat
@ -184,15 +179,13 @@
(reg-sub :contacts-by-chat (reg-sub :contacts-by-chat
(fn [[_ fn chat-id] _] (fn [[_ fn chat-id] _]
[(subscribe [:chat-by-id chat-id]) [(subscribe [:get-chat chat-id])
(subscribe [:get-contacts])]) (subscribe [:get-contacts])])
chat-contacts) chat-contacts)
(reg-sub :chat-photo (reg-sub :get-chat-photo
(fn [[_ chat-id] _] (fn [[_ chat-id] _]
[(if chat-id [(subscribe [:get-chat chat-id])
(subscribe [:chat-by-id chat-id])
(subscribe [:get-current-chat]))
(subscribe [:contacts-by-chat filter chat-id])]) (subscribe [:contacts-by-chat filter chat-id])])
(fn [[chat contacts] [_ chat-id]] (fn [[chat contacts] [_ chat-id]]
(when (and chat (not (:group-chat chat))) (when (and chat (not (:group-chat chat)))

View File

@ -26,7 +26,6 @@
:group/selected-contacts #{} :group/selected-contacts #{}
:chats {} :chats {}
:current-chat-id constants/console-chat-id :current-chat-id constants/console-chat-id
:loading-allowed true
:selected-participants #{} :selected-participants #{}
:discoveries {} :discoveries {}
:discover-search-tags '() :discover-search-tags '()
@ -153,11 +152,8 @@
:chat/chat-list-ui-props :chat/chat-list-ui-props
:chat/layout-height :chat/layout-height
:chat/expandable-view-height-to-value :chat/expandable-view-height-to-value
:chat/loading-allowed
:chat/message-data :chat/message-data
:chat/message-id->transaction-id
:chat/message-status :chat/message-status
:chat/unviewed-messages
:chat/selected-participants :chat/selected-participants
:chat/chat-loaded-callbacks :chat/chat-loaded-callbacks
:chat/command-hash-valid? :chat/command-hash-valid?
@ -165,7 +161,6 @@
:chat/confirmation-code-sms-listener :chat/confirmation-code-sms-listener
:chat/messages :chat/messages
:chat/loaded-chats :chat/loaded-chats
:chat/raw-unviewed-messages
:chat/bot-db :chat/bot-db
:chat/geolocation :chat/geolocation
:commands/access-scope->commands-responses :commands/access-scope->commands-responses

View File

@ -171,7 +171,5 @@
(register-handler-fx (register-handler-fx
:clear-history :clear-history
(fn [{{:keys [current-chat-id] :as db} :db} _] (fn [{{:keys [current-chat-id] :as db} :db} _]
{:db (-> db {:db (assoc-in db [:chats current-chat-id :messages] {})
(assoc-in [:chats current-chat-id :messages] '())
(assoc-in [:chats current-chat-id :last-message] nil))
::chat-events/delete-messages current-chat-id})) ::chat-events/delete-messages current-chat-id}))

View File

@ -13,8 +13,7 @@
(every? false? (every? false?
[(string/blank? username) [(string/blank? username)
(homoglyph/matches username constants/console-chat-id) (homoglyph/matches username constants/console-chat-id)
(string/includes? username chat.constants/command-char) (string/includes? username chat.constants/command-char)])))
(string/includes? username chat.constants/bot-char)])))
(defn correct-email? [email] (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])?"] (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])?"]

View File

@ -32,7 +32,7 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:profile/send-transaction :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]] (fn [{{:contacts/keys [contacts] :as db} :db :as cofx} [chat-id]]
(let [send-command (get-in contacts chat-const/send-command-ref)] (let [send-command (get-in contacts chat-const/send-command-ref)]
(-> (chat-events/navigate-to-chat cofx chat-id) (-> (chat-events/navigate-to-chat cofx chat-id)

View File

@ -30,8 +30,8 @@
(is (= (:current-chat-id db) (is (= (:current-chat-id db)
const/console-chat-id)) const/console-chat-id))
(is (= dispatch-n (is (= dispatch-n
(concat [[:add-contacts [sign-up/console-contact]]] [[:add-contacts [sign-up/console-contact]]
sign-up/intro-events))))) sign-up/intro-event]))))
(testing "initialising console with existing account and console chat not initialisated" (testing "initialising console with existing account and console chat not initialisated"
(let [fresh-db {:chats {} (let [fresh-db {:chats {}

View File

@ -46,10 +46,10 @@
(is (= "word1 \uD83D\uDC4D word2" (input/text->emoji "word1 :+1: word2")))) (is (= "word1 \uD83D\uDC4D word2" (input/text->emoji "word1 :+1: word2"))))
(deftest starts-as-command? (deftest starts-as-command?
(is (false? (input/starts-as-command? nil))) (is (not (input/starts-as-command? nil)))
(is (false? (input/text-ends-with-space? ""))) (is (not (input/text-ends-with-space? "")))
(is (false? (input/text-ends-with-space? "word1 word2 word3"))) (is (not (input/text-ends-with-space? "word1 word2 word3")))
(is (true? (input/text-ends-with-space? "word1 word2 ")))) (is (input/text-ends-with-space? "word1 word2 ")))
(deftest split-command-args (deftest split-command-args
(is (nil? (input/split-command-args nil))) (is (nil? (input/split-command-args nil)))