mirror of
https://github.com/status-im/status-react.git
synced 2025-01-28 11:45:45 +00:00
Chat replies + refactoring
This commit is contained in:
parent
965381f376
commit
44fbe62773
3
resources/icons/reply.svg
Normal file
3
resources/icons/reply.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 5.08946V3.62446C8 3.00346 8.373 2.81246 8.824 3.20246L12.828 6.64746C13.055 6.84246 13.054 7.15746 12.827 7.35146L8.822 10.7975C8.373 11.1855 8 10.9955 8 10.3745V8.99946C5.498 8.99946 4.183 10.8865 3.524 12.5315C3.421 12.7885 3.271 12.7885 3.2 12.5205C3.071 12.0355 3 11.5255 3 10.9995C3 8.02646 5.164 5.56546 8 5.08946Z" fill="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 452 B |
@ -59,16 +59,16 @@
|
||||
{:success-event (when (custom-bootnodes-in-use? cofx)
|
||||
[:accounts.update.callback/save-settings-success])}))))
|
||||
|
||||
(fx/defn upsert [{{:bootnodes/keys [manage] :account/keys [account] :as db} :db :as cofx}]
|
||||
(let [{:keys [name
|
||||
id
|
||||
url]} manage
|
||||
network (:network db)
|
||||
bootnode (build
|
||||
(or (:value id) (:random-id cofx))
|
||||
(:value name)
|
||||
(:value url)
|
||||
network)
|
||||
(fx/defn upsert
|
||||
[{{:bootnodes/keys [manage] :account/keys [account] :as db} :db
|
||||
random-id-generator :random-id-generator :as cofx}]
|
||||
(let [{:keys [name id url]} manage
|
||||
network (:network db)
|
||||
bootnode (build
|
||||
(or (:value id) (random-id-generator))
|
||||
(:value name)
|
||||
(:value url)
|
||||
network)
|
||||
new-bootnodes (assoc-in
|
||||
(:bootnodes account)
|
||||
[network (:id bootnode)]
|
||||
|
@ -311,7 +311,7 @@
|
||||
(fx/defn open-chat-from-browser
|
||||
[cofx host]
|
||||
(let [topic (string/lower-case (apply str (map filter-letters-numbers-and-replace-dot-on-dash host)))]
|
||||
{:dispatch [:create-new-public-chat topic true]}))
|
||||
{:dispatch [:chat.ui/start-public-chat topic true]}))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:browser/resolve-ens-multihash
|
||||
|
@ -55,7 +55,7 @@
|
||||
(if suggestions
|
||||
(update param :suggestions partial
|
||||
(fn [value]
|
||||
[:set-command-parameter
|
||||
[:chat.ui/set-command-parameter
|
||||
(= idx last-param-idx) idx value]))
|
||||
param))
|
||||
parameters))))
|
||||
|
@ -275,7 +275,7 @@
|
||||
;; by the wallet, where we yield control in the next step
|
||||
(personal-send-request-validation parameters cofx))
|
||||
(on-send [_ {:keys [chat-id] :as send-message} {:keys [db]}]
|
||||
(when-let [{:keys [responding-to]} (get-in db [:chats chat-id :input-metadata])]
|
||||
(when-let [responding-to (get-in db [:chats chat-id :metadata :responding-to-command])]
|
||||
(when-let [request-message (get-in db [:chats chat-id :messages responding-to])]
|
||||
(when (params-unchanged? send-message request-message)
|
||||
(let [updated-request-message (assoc-in request-message [:content :params :answered?] true)]
|
||||
@ -430,10 +430,9 @@
|
||||
status-initialized?
|
||||
(not outgoing)
|
||||
(not answered?))
|
||||
[react/touchable-highlight {:on-press #(re-frame/dispatch [:select-chat-input-command
|
||||
command
|
||||
[(or asset "ETH") amount]
|
||||
{:responding-to message-id}])}
|
||||
[react/touchable-highlight
|
||||
{:on-press #(re-frame/dispatch [:chat.ui/select-chat-input-command
|
||||
command [(or asset "ETH") amount] message-id])}
|
||||
markup]
|
||||
markup))))
|
||||
|
||||
|
@ -1,18 +1,87 @@
|
||||
(ns status-im.chat.commands.input
|
||||
(:require [clojure.string :as string]
|
||||
[status-im.chat.constants :as constants]
|
||||
[status-im.chat.commands.core :as commands]
|
||||
[status-im.chat.constants :as chat-constants]
|
||||
[status-im.chat.models :as chat-model]
|
||||
[status-im.chat.models.input :as input-model]
|
||||
[status-im.chat.constants :as chat.constants]
|
||||
[status-im.chat.models :as chat]
|
||||
[status-im.utils.fx :as fx]))
|
||||
|
||||
(defn command-ends-with-space? [text]
|
||||
(and text (string/ends-with? text constants/spacing-char)))
|
||||
|
||||
(defn starts-as-command?
|
||||
"Returns true if `text` may be treated as a command.
|
||||
To make sure that text is command we need to use `possible-chat-actions` function."
|
||||
[text]
|
||||
(and text (string/starts-with? text constants/command-char)))
|
||||
|
||||
(defn split-command-args
|
||||
"Returns a list of command's arguments including the command's name.
|
||||
|
||||
Examples:
|
||||
Input: '/send Jarrad 1.0'
|
||||
Output: ['/send' 'Jarrad' '1.0']
|
||||
|
||||
Input: '/send \"Complex name with space in between\" 1.0'
|
||||
Output: ['/send' 'Complex name with space in between' '1.0']
|
||||
|
||||
All the complex logic inside this function aims to support wrapped arguments."
|
||||
[command-text]
|
||||
(when command-text
|
||||
(let [space? (command-ends-with-space? command-text)
|
||||
command-text (if space?
|
||||
(str command-text ".")
|
||||
command-text)
|
||||
command-text-normalized (if command-text
|
||||
(string/replace (string/trim command-text) #" +" " ")
|
||||
command-text)
|
||||
splitted (cond-> (string/split command-text-normalized constants/spacing-char)
|
||||
space? (drop-last))]
|
||||
(->> splitted
|
||||
(reduce (fn [[list command-started?] arg]
|
||||
(let [quotes-count (count (filter #(= % constants/arg-wrapping-char) arg))
|
||||
has-quote? (and (= quotes-count 1)
|
||||
(string/index-of arg constants/arg-wrapping-char))
|
||||
arg (string/replace arg (re-pattern constants/arg-wrapping-char) "")
|
||||
new-list (if command-started?
|
||||
(let [index (dec (count list))]
|
||||
(update list index str constants/spacing-char arg))
|
||||
(conj list arg))
|
||||
command-continues? (or (and command-started? (not has-quote?))
|
||||
(and (not command-started?) has-quote?))]
|
||||
[new-list command-continues?]))
|
||||
[[] false])
|
||||
(first)))))
|
||||
|
||||
(defn join-command-args
|
||||
"Transforms a list of args to a string. The opposite of `split-command-args`.
|
||||
|
||||
Examples:
|
||||
Input: ['/send' 'Jarrad' '1.0']
|
||||
Output: '/send Jarrad 1.0'
|
||||
|
||||
Input: ['/send' '\"Jarrad\"' '1.0']
|
||||
Output: '/send Jarrad 1.0'
|
||||
|
||||
Input: ['/send' 'Complex name with space in between' '1.0']
|
||||
Output: '/send \"Complex name with space in between\" 1.0'"
|
||||
[args]
|
||||
(when args
|
||||
(->> args
|
||||
(map (fn [arg]
|
||||
(let [arg (string/replace arg (re-pattern constants/arg-wrapping-char) "")]
|
||||
(if (not (string/index-of arg constants/spacing-char))
|
||||
arg
|
||||
(str constants/arg-wrapping-char arg constants/arg-wrapping-char)))))
|
||||
(string/join constants/spacing-char))))
|
||||
|
||||
(defn- current-param-position [input-text selection]
|
||||
(when selection
|
||||
(when-let [subs-input-text (subs input-text 0 selection)]
|
||||
(let [input-params (input-model/split-command-args subs-input-text)
|
||||
(let [input-params (split-command-args subs-input-text)
|
||||
param-index (dec (count input-params))
|
||||
wrapping-count (get (frequencies subs-input-text) chat-constants/arg-wrapping-char 0)]
|
||||
(if (and (string/ends-with? subs-input-text chat-constants/spacing-char)
|
||||
wrapping-count (get (frequencies subs-input-text) chat.constants/arg-wrapping-char 0)]
|
||||
(if (and (string/ends-with? subs-input-text chat.constants/spacing-char)
|
||||
(even? wrapping-count))
|
||||
param-index
|
||||
(dec param-index))))))
|
||||
@ -36,8 +105,8 @@
|
||||
# `:command-completion` - indication of command completion, possible values are `:complete`,
|
||||
`:less-then-needed` and `more-then-needed`"
|
||||
[input-text text-selection id->command-props]
|
||||
(when (input-model/starts-as-command? input-text)
|
||||
(let [[command-name & input-params] (input-model/split-command-args input-text)]
|
||||
(when (starts-as-command? input-text)
|
||||
(let [[command-name & input-params] (split-command-args input-text)]
|
||||
(when-let [{:keys [params] :as command-props} (get id->command-props (subs command-name 1))] ;; trim leading `/` for lookup
|
||||
command-props
|
||||
(let [input-params (into {}
|
||||
@ -51,27 +120,37 @@
|
||||
:current-param-position (current-param-position input-text text-selection)
|
||||
:command-completion (command-completion input-params params)))))))
|
||||
|
||||
(defn parse-parameters
|
||||
"Parses parameters from input for defined command params,
|
||||
returns map of `param-name->param-value`"
|
||||
[params input-text]
|
||||
(let [input-params (->> (split-command-args input-text) rest (into []))]
|
||||
(into {}
|
||||
(keep-indexed (fn [idx {:keys [id]}]
|
||||
(when-let [value (get input-params idx)]
|
||||
[id value])))
|
||||
params)))
|
||||
|
||||
(fx/defn set-command-parameter
|
||||
"Set value as command parameter for the current chat"
|
||||
[{:keys [db]} last-param? param-index value]
|
||||
(let [{:keys [current-chat-id chats]} db
|
||||
[command & params] (-> (get-in chats [current-chat-id :input-text])
|
||||
input-model/split-command-args)
|
||||
split-command-args)
|
||||
param-count (count params)
|
||||
;; put the new value at the right place in parameters array
|
||||
new-params (cond-> (into [] params)
|
||||
(< param-index param-count) (assoc param-index value)
|
||||
(>= param-index param-count) (conj value))
|
||||
;; if the parameter is not the last one for the command, add space
|
||||
input-text (cond-> (str command chat-constants/spacing-char
|
||||
(input-model/join-command-args
|
||||
new-params))
|
||||
input-text (cond-> (str command chat.constants/spacing-char
|
||||
(join-command-args new-params))
|
||||
(and (not last-param?)
|
||||
(or (= 0 param-count)
|
||||
(= param-index (dec param-count))))
|
||||
(str chat-constants/spacing-char))]
|
||||
(str chat.constants/spacing-char))]
|
||||
{:db (-> db
|
||||
(chat-model/set-chat-ui-props {:validation-messages nil})
|
||||
(chat/set-chat-ui-props {:validation-messages nil})
|
||||
(assoc-in [:chats current-chat-id :input-text] input-text))}))
|
||||
|
||||
(fx/defn select-chat-input-command
|
||||
@ -79,20 +158,15 @@
|
||||
[{:keys [db]} {:keys [type params]} input-params]
|
||||
(let [{:keys [current-chat-id chat-ui-props]} db]
|
||||
{:db (-> db
|
||||
(chat-model/set-chat-ui-props {:show-suggestions? false
|
||||
:validation-messages nil})
|
||||
(chat/set-chat-ui-props {:show-suggestions? false
|
||||
:validation-messages nil})
|
||||
(assoc-in [:chats current-chat-id :input-text]
|
||||
(str (commands/command-name type)
|
||||
chat-constants/spacing-char
|
||||
(input-model/join-command-args input-params))))}))
|
||||
chat.constants/spacing-char
|
||||
(join-command-args input-params))))}))
|
||||
|
||||
(defn parse-parameters
|
||||
"Parses parameters from input for defined command params,
|
||||
returns map of `param-name->param-value`"
|
||||
[params input-text]
|
||||
(let [input-params (->> (input-model/split-command-args input-text) rest (into []))]
|
||||
(into {}
|
||||
(keep-indexed (fn [idx {:keys [id]}]
|
||||
(when-let [value (get input-params idx)]
|
||||
[id value])))
|
||||
params)))
|
||||
(fx/defn set-command-reference
|
||||
"Set reference to previous command message"
|
||||
[{:keys [db] :as cofx} command-message-id]
|
||||
(let [current-chat-id (:current-chat-id db)]
|
||||
{:db (assoc-in db [:chats current-chat-id :metadata :responding-to-command] command-message-id)}))
|
||||
|
@ -2,10 +2,9 @@
|
||||
(:require [status-im.constants :as constants]
|
||||
[status-im.chat.commands.protocol :as protocol]
|
||||
[status-im.chat.commands.core :as commands]
|
||||
[status-im.chat.commands.input :as commands-input]
|
||||
[status-im.chat.models :as chat-model]
|
||||
[status-im.chat.models.input :as input-model]
|
||||
[status-im.chat.models.message :as message-model]
|
||||
[status-im.chat.commands.input :as commands.input]
|
||||
[status-im.chat.models :as chat]
|
||||
[status-im.chat.models.message :as chat.message]
|
||||
[status-im.utils.fx :as fx]))
|
||||
|
||||
;; TODO(janherich) remove after couple of releases when danger of messages
|
||||
@ -45,30 +44,22 @@
|
||||
|
||||
(fx/defn validate-and-send
|
||||
"Validates and sends command in current chat"
|
||||
[{:keys [db now random-id] :as cofx} input-text {:keys [type params] :as command}]
|
||||
[{:keys [db now] :as cofx} input-text {:keys [type params] :as command}]
|
||||
(let [chat-id (:current-chat-id db)
|
||||
parameter-map (commands-input/parse-parameters params input-text)]
|
||||
parameter-map (commands.input/parse-parameters params input-text)]
|
||||
(if-let [validation-error (protocol/validate type parameter-map cofx)]
|
||||
;; errors during validation
|
||||
{:db (chat-model/set-chat-ui-props db {:validation-messages validation-error
|
||||
:sending-in-progress? false})}
|
||||
;; no errors, define clean-up effects which will have to be performed in all cases
|
||||
(let [cleanup-fx (fx/merge cofx
|
||||
{:db (chat-model/set-chat-ui-props
|
||||
db {:sending-in-progress? false})}
|
||||
(input-model/set-chat-input-text nil))]
|
||||
(if (satisfies? protocol/Yielding type)
|
||||
;; yield control implemented, don't send the message
|
||||
{:db (chat/set-chat-ui-props db {:validation-messages validation-error})}
|
||||
;; no errors
|
||||
(if (satisfies? protocol/Yielding type)
|
||||
;; yield control implemented, don't send the message
|
||||
(protocol/yield-control type parameter-map cofx)
|
||||
;; no yield control, proceed with sending the command message
|
||||
(let [command-message (create-command-message chat-id type parameter-map cofx)]
|
||||
(fx/merge cofx
|
||||
cleanup-fx
|
||||
#(protocol/yield-control type parameter-map %))
|
||||
;; no yield control, proceed with sending the command message
|
||||
(let [command-message (create-command-message chat-id type parameter-map cofx)]
|
||||
(fx/merge cofx
|
||||
cleanup-fx
|
||||
#(protocol/on-send type command-message %)
|
||||
(input-model/set-chat-input-metadata nil)
|
||||
(message-model/send-message command-message))))))))
|
||||
#(protocol/on-send type command-message %)
|
||||
(commands.input/set-command-reference nil)
|
||||
(chat.message/send-message command-message)))))))
|
||||
|
||||
(fx/defn send
|
||||
"Sends command with given parameters in particular chat"
|
||||
@ -76,5 +67,5 @@
|
||||
(let [command-message (create-command-message chat-id type parameter-map cofx)]
|
||||
(fx/merge cofx
|
||||
#(protocol/on-send type command-message %)
|
||||
(input-model/set-chat-input-metadata nil)
|
||||
(message-model/send-message command-message))))
|
||||
(commands.input/set-command-reference nil)
|
||||
(chat.message/send-message command-message))))
|
||||
|
@ -1,170 +0,0 @@
|
||||
(ns status-im.chat.events
|
||||
(:require status-im.chat.events.input
|
||||
status-im.chat.events.send-message
|
||||
status-im.chat.events.receive-message
|
||||
[clojure.string :as string]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.chat.models :as models]
|
||||
[status-im.chat.models.loading :as chat-loading]
|
||||
[status-im.chat.models.message :as models.message]
|
||||
[status-im.data-store.user-statuses :as user-statuses-store]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.transport.message.core :as transport.message]
|
||||
[status-im.transport.message.v1.core :as protocol]
|
||||
[status-im.transport.message.v1.public-chat :as public-chat]
|
||||
[status-im.ui.screens.navigation :as navigation]
|
||||
[status-im.utils.fx :as fx]
|
||||
[status-im.group-chats.core :as group-chats]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.utils.utils :as utils]))
|
||||
|
||||
;;;; Effects
|
||||
|
||||
|
||||
(re-frame/reg-fx
|
||||
:show-cooldown-warning
|
||||
(fn [_]
|
||||
(utils/show-popup nil
|
||||
(i18n/label :cooldown/warning-message)
|
||||
#())))
|
||||
|
||||
;;;; Handlers
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:set-chat-ui-props
|
||||
(fn [{:keys [db]} [_ kvs]]
|
||||
{:db (models/set-chat-ui-props db kvs)}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:toggle-chat-ui-props
|
||||
(fn [{:keys [db]} [_ ui-element]]
|
||||
{:db (models/toggle-chat-ui-prop db ui-element)}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:show-message-details
|
||||
(fn [{:keys [db]} [_ details]]
|
||||
{:db (models/set-chat-ui-props db {:show-bottom-info? true
|
||||
:bottom-info details})}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:show-message-options
|
||||
(fn [{:keys [db]} [_ options]]
|
||||
{:db (models/set-chat-ui-props db {:show-message-options? true
|
||||
:message-options options})}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:update-message-status
|
||||
(fn [{:keys [db]} [_ chat-id message-id user-id status]]
|
||||
(let [new-status {:chat-id chat-id
|
||||
:message-id message-id
|
||||
:whisper-identity user-id
|
||||
:status status}]
|
||||
{:db (assoc-in db
|
||||
[:chats chat-id :message-statuses message-id user-id]
|
||||
new-status)
|
||||
:data-store/tx [(user-statuses-store/save-status-tx new-status)]})))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:navigate-to-chat
|
||||
(fn [cofx [_ chat-id opts]]
|
||||
(models/navigate-to-chat cofx chat-id opts)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:load-more-messages
|
||||
[(re-frame/inject-cofx :data-store/get-messages)
|
||||
(re-frame/inject-cofx :data-store/get-user-statuses)]
|
||||
(fn [cofx _]
|
||||
(chat-loading/load-more-messages cofx)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:start-chat
|
||||
(fn [cofx [_ contact-id opts]]
|
||||
(models/start-chat cofx contact-id opts)))
|
||||
|
||||
(defn remove-chat-and-navigate-home [cofx [_ chat-id]]
|
||||
(fx/merge cofx
|
||||
(models/remove-chat chat-id)
|
||||
(navigation/navigate-to-cofx :home {})))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:remove-chat-and-navigate-home
|
||||
remove-chat-and-navigate-home)
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:remove-chat-and-navigate-home?
|
||||
(fn [_ [_ chat-id group?]]
|
||||
{:ui/show-confirmation {:title (i18n/label :t/delete-confirmation)
|
||||
:content (i18n/label :t/delete-chat-confirmation)
|
||||
:confirm-button-text (i18n/label :t/delete)
|
||||
:on-accept #(re-frame/dispatch [:remove-chat-and-navigate-home chat-id])}}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:clear-history
|
||||
(fn [{{:keys [current-chat-id]} :db :as cofx} _]
|
||||
(models/clear-history cofx current-chat-id)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:clear-history?
|
||||
(fn [_ _]
|
||||
{:ui/show-confirmation {:title (i18n/label :t/clear-history-confirmation)
|
||||
:content (i18n/label :t/clear-history-confirmation-content)
|
||||
:confirm-button-text (i18n/label :t/clear)
|
||||
:on-accept #(re-frame/dispatch [:clear-history])}}))
|
||||
|
||||
(fx/defn create-new-public-chat [cofx topic modal?]
|
||||
(fx/merge cofx
|
||||
(models/add-public-chat topic)
|
||||
(models/navigate-to-chat topic {:modal? modal?
|
||||
:navigation-reset? true})
|
||||
(public-chat/join-public-chat topic)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:create-new-public-chat
|
||||
(fn [cofx [_ topic modal?]]
|
||||
(create-new-public-chat cofx topic modal?)))
|
||||
|
||||
(defn- group-name-from-contacts [selected-contacts all-contacts username]
|
||||
(->> selected-contacts
|
||||
(map (comp :name (partial get all-contacts)))
|
||||
(cons username)
|
||||
(string/join ", ")))
|
||||
|
||||
(fx/defn send-group-update [cofx group-update chat-id]
|
||||
(transport.message/send group-update chat-id cofx))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:create-new-group-chat-and-open
|
||||
[(re-frame/inject-cofx :random-id)]
|
||||
(fn [{:keys [db random-id] :as cofx} [_ group-name]]
|
||||
(let [my-public-key (:current-public-key db)
|
||||
selected-contacts (conj (:group/selected-contacts db)
|
||||
my-public-key)
|
||||
group-update (protocol/GroupMembershipUpdate. random-id group-name my-public-key selected-contacts nil nil nil)]
|
||||
(fx/merge cofx
|
||||
{:db (assoc db :group/selected-contacts #{})}
|
||||
(models/navigate-to-chat random-id {})
|
||||
(group-chats/handle-membership-update group-update my-public-key)
|
||||
(send-group-update group-update random-id)))))
|
||||
|
||||
(fx/defn show-profile [{:keys [db]} identity]
|
||||
(navigation/navigate-to-cofx {:db (assoc db :contacts/identity identity)} :profile nil))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:show-profile
|
||||
(fn [cofx [_ identity]]
|
||||
(show-profile cofx identity)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:resend-message
|
||||
(fn [cofx [_ chat-id message-id]]
|
||||
(models.message/resend-message cofx chat-id message-id)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:delete-message
|
||||
(fn [cofx [_ chat-id message-id]]
|
||||
(models.message/delete-message cofx chat-id message-id)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:disable-cooldown
|
||||
(fn [{:keys [db]}]
|
||||
{:db (assoc db :chat/cooldown-enabled? false)}))
|
@ -1,137 +0,0 @@
|
||||
(ns status-im.chat.events.input
|
||||
(:require [clojure.string :as string]
|
||||
[re-frame.core :as re-frame]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.chat.constants :as chat-constants]
|
||||
[status-im.chat.models :as model]
|
||||
[status-im.chat.models.input :as input-model]
|
||||
[status-im.chat.models.message :as message-model]
|
||||
[status-im.chat.commands.core :as commands]
|
||||
[status-im.chat.commands.input :as commands-input]
|
||||
[status-im.chat.commands.sending :as commands-sending]
|
||||
[status-im.ui.components.react :as react-comp]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.utils.fx :as fx]))
|
||||
|
||||
;;;; Effects
|
||||
|
||||
(re-frame/reg-fx
|
||||
::focus-rn-component
|
||||
(fn [ref]
|
||||
(try
|
||||
(.focus ref)
|
||||
(catch :default e
|
||||
(log/debug "Cannot focus the reference")))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::blur-rn-component
|
||||
(fn [ref]
|
||||
(try
|
||||
(.blur ref)
|
||||
(catch :default e
|
||||
(log/debug "Cannot blur the reference")))))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::dismiss-keyboard
|
||||
(fn [_]
|
||||
(react-comp/dismiss-keyboard!)))
|
||||
|
||||
(re-frame/reg-fx
|
||||
::set-native-props
|
||||
(fn [{:keys [ref props]}]
|
||||
(.setNativeProps ref (clj->js props))))
|
||||
|
||||
;;;; Helper functions
|
||||
|
||||
(fx/defn chat-input-focus
|
||||
"Returns fx for focusing on active chat input reference"
|
||||
[{{:keys [current-chat-id chat-ui-props]} :db} ref]
|
||||
(when-let [cmp-ref (get-in chat-ui-props [current-chat-id ref])]
|
||||
{::focus-rn-component cmp-ref}))
|
||||
|
||||
;;;; Handlers
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:set-chat-input-text
|
||||
(fn [cofx [_ text]]
|
||||
(input-model/set-chat-input-text cofx text)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:select-chat-input-command
|
||||
(fn [{:keys [db] :as cofx} [_ command params metadata]]
|
||||
(fx/merge cofx
|
||||
(input-model/set-chat-input-metadata metadata)
|
||||
(commands-input/select-chat-input-command command params)
|
||||
(chat-input-focus :input-ref))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:set-command-parameter
|
||||
(fn [cofx [_ last-param? index value]]
|
||||
(commands-input/set-command-parameter cofx last-param? index value)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat-input-focus
|
||||
(fn [cofx [_ ref]]
|
||||
(chat-input-focus cofx ref)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat-input-blur
|
||||
(fn [{{:keys [current-chat-id chat-ui-props]} :db} [_ ref]]
|
||||
(when-let [cmp-ref (get-in chat-ui-props [current-chat-id ref])]
|
||||
{::blur-rn-component cmp-ref})))
|
||||
|
||||
(defn command-complete-fx
|
||||
"command is complete, set `:sending-in-progress?` flag and proceed with command processing"
|
||||
[input-text command {:keys [db now random-id] :as cofx}]
|
||||
(fx/merge cofx
|
||||
{:db (model/set-chat-ui-props db {:sending-in-progress? true})}
|
||||
(commands-sending/validate-and-send input-text command)))
|
||||
|
||||
(defn command-not-complete-fx
|
||||
"command is not complete, just add space after command if necessary"
|
||||
[input-text current-chat-id {:keys [db]}]
|
||||
{:db (cond-> db
|
||||
(not (input-model/text-ends-with-space? input-text))
|
||||
(assoc-in [:chats current-chat-id :input-text]
|
||||
(str input-text chat-constants/spacing-char)))})
|
||||
|
||||
(defn plain-text-message-fx
|
||||
"no command detected, when not empty, proceed by sending text message without command processing"
|
||||
[input-text current-chat-id {:keys [db] :as cofx}]
|
||||
(when-not (string/blank? input-text)
|
||||
(fx/merge cofx
|
||||
(input-model/set-chat-input-text nil)
|
||||
(input-model/set-chat-input-metadata nil)
|
||||
(message-model/send-message {:chat-id current-chat-id
|
||||
:content-type constants/text-content-type
|
||||
:content input-text}))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:send-current-message
|
||||
message-model/send-interceptors
|
||||
(fn [{{:keys [current-chat-id id->command access-scope->command-id] :as db} :db :as cofx} _]
|
||||
(when-not (get-in db [:chat-ui-props current-chat-id :sending-in-progress?])
|
||||
(let [input-text (get-in db [:chats current-chat-id :input-text])
|
||||
command (commands-input/selected-chat-command
|
||||
input-text nil (commands/chat-commands id->command
|
||||
access-scope->command-id
|
||||
(get-in db [:chats current-chat-id])))]
|
||||
(if command
|
||||
;; Returns true if current input contains command
|
||||
(if (= :complete (:command-completion command))
|
||||
(command-complete-fx input-text command cofx)
|
||||
(command-not-complete-fx input-text current-chat-id cofx))
|
||||
(plain-text-message-fx input-text current-chat-id cofx))))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:update-text-selection
|
||||
(fn [{:keys [db]} [_ selection]]
|
||||
{:db (model/set-chat-ui-props db {:selection selection})}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:show-suggestions
|
||||
(fn [{:keys [db]} _]
|
||||
{:db (-> db
|
||||
(model/toggle-chat-ui-prop :show-suggestions?)
|
||||
(model/set-chat-ui-props {:validation-messages nil}))}))
|
@ -1,32 +0,0 @@
|
||||
(ns status-im.chat.events.receive-message
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.chat.models.message :as message-model]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.models.transactions :as wallet.transactions]))
|
||||
|
||||
;;;; Handlers
|
||||
|
||||
(re-frame.core/reg-fx
|
||||
:chat-received-message/add-fx
|
||||
(fn [messages]
|
||||
(re-frame/dispatch [:chat-received-message/add messages])))
|
||||
|
||||
(defn- filter-messages [cofx messages]
|
||||
(:accumulated (reduce (fn [{:keys [seen-ids] :as acc}
|
||||
{:keys [message-id] :as message}]
|
||||
(if (and (message-model/add-to-chat? cofx message)
|
||||
(not (seen-ids message-id)))
|
||||
(-> acc
|
||||
(update :seen-ids conj message-id)
|
||||
(update :accumulated conj message))
|
||||
acc))
|
||||
{:seen-ids #{}
|
||||
:accumulated []}
|
||||
messages)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat-received-message/add
|
||||
message-model/receive-interceptors
|
||||
(fn [cofx [_ messages]]
|
||||
(message-model/receive-many cofx (filter-messages cofx messages))))
|
@ -1,15 +0,0 @@
|
||||
(ns status-im.chat.events.send-message
|
||||
(:require [taoensso.timbre :as log]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.chat.models.message :as message-model]
|
||||
[status-im.native-module.core :as status]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.utils.types :as types]))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:send-notification
|
||||
(fn [{:keys [message payload tokens]}]
|
||||
(let [payload-json (types/clj->json payload)
|
||||
tokens-json (types/clj->json tokens)]
|
||||
(log/debug "send-notification message: " message " payload-json: " payload-json " tokens-json: " tokens-json)
|
||||
(status/notify-users {:message message :payload payload-json :tokens tokens-json} #(log/debug "send-notification cb result: " %)))))
|
@ -1,13 +1,18 @@
|
||||
(ns status-im.chat.models
|
||||
(:require [status-im.data-store.chats :as chats-store]
|
||||
(:require [clojure.string :as string]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.data-store.chats :as chats-store]
|
||||
[status-im.data-store.messages :as messages-store]
|
||||
[status-im.data-store.user-statuses :as user-statuses-store]
|
||||
[status-im.transport.message.core :as transport.message]
|
||||
[status-im.transport.message.v1.protocol :as protocol]
|
||||
[status-im.transport.message.v1.core :as transport]
|
||||
[status-im.transport.message.v1.public-chat :as public-chat]
|
||||
[status-im.transport.utils :as transport.utils]
|
||||
[status-im.ui.components.styles :as styles]
|
||||
[status-im.ui.screens.navigation :as navigation]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.utils.utils :as utils]
|
||||
[status-im.utils.clocks :as utils.clocks]
|
||||
[status-im.utils.datetime :as time]
|
||||
[status-im.utils.gfycat.core :as gfycat]
|
||||
@ -129,7 +134,8 @@
|
||||
#(when (multi-user-chat? % chat-id)
|
||||
(remove-transport % chat-id))
|
||||
(deactivate-chat chat-id)
|
||||
(clear-history chat-id)))
|
||||
(clear-history chat-id)
|
||||
(navigation/navigate-to-cofx :home {})))
|
||||
|
||||
(fx/defn send-messages-seen
|
||||
[{:keys [db] :as cofx} chat-id message-ids]
|
||||
@ -200,3 +206,25 @@
|
||||
(upsert-chat {:chat-id chat-id
|
||||
:is-active true})
|
||||
(navigate-to-chat chat-id opts))))
|
||||
|
||||
(fx/defn start-public-chat
|
||||
"Starts a new public chat"
|
||||
[cofx topic modal?]
|
||||
(fx/merge cofx
|
||||
(add-public-chat topic)
|
||||
(navigate-to-chat topic {:modal? modal?
|
||||
:navigation-replace? true})
|
||||
(public-chat/join-public-chat topic)))
|
||||
|
||||
(fx/defn disable-chat-cooldown
|
||||
"Turns off chat cooldown (protection against message spamming)"
|
||||
[{:keys [db]}]
|
||||
{:db (assoc db :chat/cooldown-enabled? false)})
|
||||
|
||||
;; effects
|
||||
(re-frame/reg-fx
|
||||
:show-cooldown-warning
|
||||
(fn [_]
|
||||
(utils/show-popup nil
|
||||
(i18n/label :cooldown/warning-message)
|
||||
#())))
|
||||
|
@ -1,13 +1,16 @@
|
||||
(ns status-im.chat.models.group-chat
|
||||
(:require
|
||||
[clojure.set :as set]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.transport.utils :as transport.utils]
|
||||
[status-im.ui.screens.group.core :as group]
|
||||
[status-im.chat.models :as models.chat]
|
||||
[status-im.transport.message.core :as message]
|
||||
[status-im.chat.models.message :as models.message]
|
||||
[status-im.utils.fx :as fx]))
|
||||
(:require [clojure.set :as set]
|
||||
[clojure.string :as string]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.transport.utils :as transport.utils]
|
||||
[status-im.transport.message.core :as transport]
|
||||
[status-im.transport.message.v1.core :as transport.message]
|
||||
[status-im.ui.screens.group.core :as group]
|
||||
[status-im.group-chats.core :as group-chat]
|
||||
[status-im.chat.models :as models.chat]
|
||||
[status-im.transport.message.core :as message]
|
||||
[status-im.chat.models.message :as models.message]
|
||||
[status-im.utils.fx :as fx]))
|
||||
|
||||
(defn- participants-diff [existing-participants-set new-participants-set]
|
||||
{:removed (set/difference existing-participants-set new-participants-set)
|
||||
@ -29,15 +32,10 @@
|
||||
(seq removed-participants)
|
||||
(str admin-name " " (i18n/label :t/removed) " " (apply str (interpose ", " removed-participants-names))))))
|
||||
|
||||
(defn handle-group-chat-create [{:keys [chat-name participants chat-id]} signature {:keys [now db random-id] :as cofx}]
|
||||
(models.chat/add-group-chat chat-id
|
||||
chat-name
|
||||
signature
|
||||
participants
|
||||
cofx))
|
||||
|
||||
(defn handle-group-admin-update [{:keys [chat-name participants chat-id]} chat-id signature {:keys [now db random-id] :as cofx}]
|
||||
(let [me (:current-public-key db)]
|
||||
(fx/defn handle-group-admin-update [{:keys [now db random-id-generator] :as cofx}
|
||||
{:keys [chat-name participants]} chat-id signature]
|
||||
(let [me (:current-public-key db)
|
||||
system-message-id (random-id-generator)]
|
||||
;; we have to check if we already have a chat, or it's a new one
|
||||
(if-let [{:keys [group-admin contacts] :as chat} (get-in db [:chats chat-id])]
|
||||
;; update for existing group chat
|
||||
@ -49,7 +47,7 @@
|
||||
(if (removed me) ;; we were removed
|
||||
(fx/merge cofx
|
||||
(models.message/receive
|
||||
(models.message/system-message chat-id random-id now
|
||||
(models.message/system-message chat-id system-message-id now
|
||||
(str admin-name " " (i18n/label :t/removed-from-chat))))
|
||||
(models.chat/upsert-chat {:chat-id chat-id
|
||||
:removed-from-at now
|
||||
@ -57,7 +55,7 @@
|
||||
(transport.utils/unsubscribe-from-chat chat-id))
|
||||
(fx/merge cofx
|
||||
(models.message/receive
|
||||
(models.message/system-message chat-id random-id now
|
||||
(models.message/system-message chat-id system-message-id now
|
||||
(prepare-system-message admin-name
|
||||
added
|
||||
removed
|
||||
@ -69,8 +67,9 @@
|
||||
(models.chat/add-group-chat cofx chat-id chat-name signature participants)))))
|
||||
|
||||
(fx/defn handle-group-leave
|
||||
[{:keys [db random-id now] :as cofx} chat-id signature]
|
||||
[{:keys [db random-id-generator now] :as cofx} chat-id signature]
|
||||
(let [me (:current-public-key db)
|
||||
system-message-id (random-id-generator)
|
||||
participant-leaving-name (or (get-in db [:contacts/contacts signature :name])
|
||||
signature)]
|
||||
(when (and
|
||||
@ -79,6 +78,29 @@
|
||||
|
||||
(fx/merge cofx
|
||||
(models.message/receive
|
||||
(models.message/system-message chat-id random-id now
|
||||
(models.message/system-message chat-id system-message-id now
|
||||
(str participant-leaving-name " " (i18n/label :t/left))))
|
||||
(group/participants-removed chat-id #{signature})))))
|
||||
|
||||
(defn- group-name-from-contacts [selected-contacts all-contacts username]
|
||||
(->> selected-contacts
|
||||
(map (comp :name (partial get all-contacts)))
|
||||
(cons username)
|
||||
(string/join ", ")))
|
||||
|
||||
(fx/defn send-group-update [cofx group-update chat-id]
|
||||
(transport/send group-update chat-id cofx))
|
||||
|
||||
(fx/defn start-group-chat
|
||||
"Starts a new group chat"
|
||||
[{:keys [db random-id-generator] :as cofx} group-name]
|
||||
(let [my-public-key (:current-public-key db)
|
||||
chat-id (random-id-generator)
|
||||
selected-contacts (conj (:group/selected-contacts db)
|
||||
my-public-key)
|
||||
group-update (transport.message/GroupMembershipUpdate. chat-id group-name my-public-key selected-contacts nil nil nil)]
|
||||
(fx/merge cofx
|
||||
{:db (assoc db :group/selected-contacts #{})}
|
||||
(models.chat/navigate-to-chat chat-id {})
|
||||
(group-chat/handle-membership-update group-update my-public-key)
|
||||
(send-group-update group-update chat-id))))
|
||||
|
@ -1,129 +1,60 @@
|
||||
(ns status-im.chat.models.input
|
||||
(:require [clojure.string :as str]
|
||||
(:require [clojure.string :as string]
|
||||
[re-frame.core :as re-frame]
|
||||
[goog.object :as object]
|
||||
[status-im.chat.constants :as const]
|
||||
[status-im.chat.models :as chat-model]
|
||||
[status-im.utils.config :as config]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.chat.constants :as chat.constants]
|
||||
[status-im.chat.models :as chat]
|
||||
[status-im.chat.models.message :as chat.message]
|
||||
[status-im.chat.commands.core :as commands]
|
||||
[status-im.chat.commands.input :as commands.input]
|
||||
[status-im.chat.commands.sending :as commands.sending]
|
||||
[status-im.utils.datetime :as datetime]
|
||||
[status-im.js-dependencies :as dependencies]
|
||||
[status-im.utils.fx :as fx]))
|
||||
|
||||
(def space-char " ")
|
||||
[status-im.utils.fx :as fx]
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
(defn text->emoji
|
||||
"Replaces emojis in a specified `text`"
|
||||
[text]
|
||||
(when text
|
||||
(str/replace text
|
||||
#":([a-z_\-+0-9]*):"
|
||||
(fn [[original emoji-id]]
|
||||
(if-let [emoji-map (object/get (object/get dependencies/emojis "lib") emoji-id)]
|
||||
(object/get emoji-map "char")
|
||||
original)))))
|
||||
|
||||
(defn text-ends-with-space? [text]
|
||||
(and text (str/ends-with? text const/spacing-char)))
|
||||
|
||||
(defn starts-as-command?
|
||||
"Returns true if `text` may be treated as a command.
|
||||
To make sure that text is command we need to use `possible-chat-actions` function."
|
||||
[text]
|
||||
(and text (str/starts-with? text const/command-char)))
|
||||
|
||||
(defn split-command-args
|
||||
"Returns a list of command's arguments including the command's name.
|
||||
|
||||
Examples:
|
||||
Input: '/send Jarrad 1.0'
|
||||
Output: ['/send' 'Jarrad' '1.0']
|
||||
|
||||
Input: '/send \"Complex name with space in between\" 1.0'
|
||||
Output: ['/send' 'Complex name with space in between' '1.0']
|
||||
|
||||
All the complex logic inside this function aims to support wrapped arguments."
|
||||
[command-text]
|
||||
(when command-text
|
||||
(let [space? (text-ends-with-space? command-text)
|
||||
command-text (if space?
|
||||
(str command-text ".")
|
||||
command-text)
|
||||
command-text-normalized (if command-text
|
||||
(str/replace (str/trim command-text) #" +" " ")
|
||||
command-text)
|
||||
splitted (cond-> (str/split command-text-normalized const/spacing-char)
|
||||
space? (drop-last))]
|
||||
(->> splitted
|
||||
(reduce (fn [[list command-started?] arg]
|
||||
(let [quotes-count (count (filter #(= % const/arg-wrapping-char) arg))
|
||||
has-quote? (and (= quotes-count 1)
|
||||
(str/index-of arg const/arg-wrapping-char))
|
||||
arg (str/replace arg (re-pattern const/arg-wrapping-char) "")
|
||||
new-list (if command-started?
|
||||
(let [index (dec (count list))]
|
||||
(update list index str const/spacing-char arg))
|
||||
(conj list arg))
|
||||
command-continues? (or (and command-started? (not has-quote?))
|
||||
(and (not command-started?) has-quote?))]
|
||||
[new-list command-continues?]))
|
||||
[[] false])
|
||||
(first)))))
|
||||
|
||||
(defn join-command-args
|
||||
"Transforms a list of args to a string. The opposite of `split-command-args`.
|
||||
|
||||
Examples:
|
||||
Input: ['/send' 'Jarrad' '1.0']
|
||||
Output: '/send Jarrad 1.0'
|
||||
|
||||
Input: ['/send' '\"Jarrad\"' '1.0']
|
||||
Output: '/send Jarrad 1.0'
|
||||
|
||||
Input: ['/send' 'Complex name with space in between' '1.0']
|
||||
Output: '/send \"Complex name with space in between\" 1.0'"
|
||||
[args]
|
||||
(when args
|
||||
(->> args
|
||||
(map (fn [arg]
|
||||
(let [arg (str/replace arg (re-pattern const/arg-wrapping-char) "")]
|
||||
(if (not (str/index-of arg const/spacing-char))
|
||||
arg
|
||||
(str const/arg-wrapping-char arg const/arg-wrapping-char)))))
|
||||
(str/join const/spacing-char))))
|
||||
(string/replace text
|
||||
#":([a-z_\-+0-9]*):"
|
||||
(fn [[original emoji-id]]
|
||||
(if-let [emoji-map (object/get (object/get dependencies/emojis "lib") emoji-id)]
|
||||
(object/get emoji-map "char")
|
||||
original)))))
|
||||
|
||||
(fx/defn set-chat-input-text
|
||||
"Set input text for current-chat. Takes db and input text and cofx
|
||||
as arguments and returns new fx. Always clear all validation messages."
|
||||
[{{:keys [current-chat-id] :as db} :db} new-input]
|
||||
{:db (-> (chat-model/set-chat-ui-props db {:validation-messages nil})
|
||||
{:db (-> (chat/set-chat-ui-props db {:validation-messages nil})
|
||||
(assoc-in [:chats current-chat-id :input-text] (text->emoji new-input)))})
|
||||
|
||||
(fx/defn set-chat-input-metadata
|
||||
"Sets user invisible chat input metadata for current-chat"
|
||||
[{:keys [db] :as cofx} metadata]
|
||||
(let [current-chat-id (:current-chat-id db)]
|
||||
{:db (assoc-in db [:chats current-chat-id :input-metadata] metadata)}))
|
||||
|
||||
(defn- start-cooldown [{:keys [db]} cooldowns]
|
||||
{:dispatch-later [{:dispatch [:disable-cooldown]
|
||||
:ms (const/cooldown-periods-ms cooldowns
|
||||
const/default-cooldown-period-ms)}]
|
||||
{:dispatch-later [{:dispatch [:chat/disable-cooldown]
|
||||
:ms (chat.constants/cooldown-periods-ms
|
||||
cooldowns
|
||||
chat.constants/default-cooldown-period-ms)}]
|
||||
:show-cooldown-warning nil
|
||||
:db (assoc db
|
||||
:chat/cooldowns (if (= const/cooldown-reset-threshold cooldowns)
|
||||
:chat/cooldowns (if (= chat.constants/cooldown-reset-threshold cooldowns)
|
||||
0
|
||||
cooldowns)
|
||||
:chat/spam-messages-frequency 0
|
||||
:chat/cooldown-enabled? true)})
|
||||
|
||||
(fx/defn process-cooldown
|
||||
"Process cooldown to protect against message spammers"
|
||||
[{{:keys [chat/last-outgoing-message-sent-at
|
||||
chat/cooldowns
|
||||
chat/spam-messages-frequency
|
||||
current-chat-id] :as db} :db :as cofx}]
|
||||
(when (chat-model/public-chat? current-chat-id cofx)
|
||||
(when (chat/public-chat? current-chat-id cofx)
|
||||
(let [spamming-fast? (< (- (datetime/timestamp) last-outgoing-message-sent-at)
|
||||
(+ const/spam-interval-ms (* 1000 cooldowns)))
|
||||
spamming-frequently? (= const/spam-message-frequency-threshold spam-messages-frequency)]
|
||||
(+ chat.constants/spam-interval-ms (* 1000 cooldowns)))
|
||||
spamming-frequently? (= chat.constants/spam-message-frequency-threshold spam-messages-frequency)]
|
||||
(cond-> {:db (assoc db
|
||||
:chat/last-outgoing-message-sent-at (datetime/timestamp)
|
||||
:chat/spam-messages-frequency (if spamming-fast?
|
||||
@ -132,3 +63,97 @@
|
||||
|
||||
(and spamming-fast? spamming-frequently?)
|
||||
(start-cooldown (inc cooldowns))))))
|
||||
|
||||
(fx/defn chat-input-focus
|
||||
"Returns fx for focusing on active chat input reference"
|
||||
[{{:keys [current-chat-id chat-ui-props]} :db} ref]
|
||||
(when-let [cmp-ref (get-in chat-ui-props [current-chat-id ref])]
|
||||
{::focus-rn-component cmp-ref}))
|
||||
|
||||
(fx/defn select-chat-input-command
|
||||
"Sets chat command and focuses on input"
|
||||
[{:keys [db] :as cofx} command params previous-command-message]
|
||||
(fx/merge cofx
|
||||
(commands.input/set-command-reference previous-command-message)
|
||||
(commands.input/select-chat-input-command command params)
|
||||
(chat-input-focus :input-ref)))
|
||||
|
||||
(fx/defn set-command-prefix
|
||||
"Sets command prefix character and focuses on input"
|
||||
[{:keys [db] :as cofx}]
|
||||
(fx/merge cofx
|
||||
(set-chat-input-text chat.constants/command-char)
|
||||
(chat-input-focus :input-ref)))
|
||||
|
||||
(fx/defn reply-to-message
|
||||
"Sets reference to previous chat message and focuses on input"
|
||||
[{:keys [db] :as cofx} message-id]
|
||||
(let [current-chat-id (:current-chat-id db)]
|
||||
(fx/merge cofx
|
||||
{:db (assoc-in db [:chats current-chat-id :metadata :responding-to-message] message-id)}
|
||||
(chat-input-focus :input-ref))))
|
||||
|
||||
(fx/defn cancel-message-reply
|
||||
"Cancels stage message reply"
|
||||
[{:keys [db] :as cofx}]
|
||||
(let [current-chat-id (:current-chat-id db)]
|
||||
(fx/merge cofx
|
||||
{:db (assoc-in db [:chats current-chat-id :metadata :responding-to-message] nil)}
|
||||
(chat-input-focus :input-ref))))
|
||||
|
||||
(defn command-complete-fx
|
||||
"command is complete, proceed with command processing"
|
||||
[input-text command {:keys [db now] :as cofx}]
|
||||
(fx/merge cofx
|
||||
(commands.sending/validate-and-send input-text command)
|
||||
(set-chat-input-text nil)
|
||||
(process-cooldown)))
|
||||
|
||||
(defn command-not-complete-fx
|
||||
"command is not complete, just add space after command if necessary"
|
||||
[input-text current-chat-id {:keys [db]}]
|
||||
{:db (cond-> db
|
||||
(not (commands.input/command-ends-with-space? input-text))
|
||||
(assoc-in [:chats current-chat-id :input-text]
|
||||
(str input-text chat.constants/spacing-char)))})
|
||||
|
||||
(defn plain-text-message-fx
|
||||
"no command detected, when not empty, proceed by sending text message without command processing"
|
||||
[input-text current-chat-id {:keys [db] :as cofx}]
|
||||
(when-not (string/blank? input-text)
|
||||
(let [reply-to-message (get-in db [:chats current-chat-id :metadata :responding-to-message])]
|
||||
(fx/merge cofx
|
||||
{:db (assoc-in db [:chats current-chat-id :metadata :responding-to-message] nil)}
|
||||
(chat.message/send-message {:chat-id current-chat-id
|
||||
:content-type constants/text-content-type
|
||||
:content (cond-> {:text input-text}
|
||||
reply-to-message
|
||||
(assoc :response-to reply-to-message))})
|
||||
(commands.input/set-command-reference nil)
|
||||
(set-chat-input-text nil)
|
||||
(process-cooldown)))))
|
||||
|
||||
(fx/defn send-current-message
|
||||
"Sends message from current chat input"
|
||||
[{{:keys [current-chat-id id->command access-scope->command-id] :as db} :db :as cofx}]
|
||||
(let [input-text (get-in db [:chats current-chat-id :input-text])
|
||||
command (commands.input/selected-chat-command
|
||||
input-text nil (commands/chat-commands id->command
|
||||
access-scope->command-id
|
||||
(get-in db [:chats current-chat-id])))]
|
||||
(if command
|
||||
;; Returns true if current input contains command
|
||||
(if (= :complete (:command-completion command))
|
||||
(command-complete-fx input-text command cofx)
|
||||
(command-not-complete-fx input-text current-chat-id cofx))
|
||||
(plain-text-message-fx input-text current-chat-id cofx))))
|
||||
|
||||
;; effects
|
||||
|
||||
(re-frame/reg-fx
|
||||
::focus-rn-component
|
||||
(fn [ref]
|
||||
(try
|
||||
(.focus ref)
|
||||
(catch :default e
|
||||
(log/debug "Cannot focus the reference")))))
|
||||
|
@ -1,5 +1,7 @@
|
||||
(ns status-im.chat.models.message
|
||||
(:require [re-frame.core :as re-frame]
|
||||
(:require [clojure.string :as string]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.native-module.core :as status]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.utils.core :as utils]
|
||||
@ -9,21 +11,18 @@
|
||||
[status-im.group-chats.core :as group-chats]
|
||||
[status-im.chat.models :as chat-model]
|
||||
[status-im.chat.models.loading :as chat-loading]
|
||||
[status-im.chat.models.input :as input]
|
||||
[status-im.chat.commands.receiving :as commands-receiving]
|
||||
[status-im.utils.clocks :as utils.clocks]
|
||||
[status-im.utils.money :as money]
|
||||
[status-im.utils.types :as types]
|
||||
[status-im.notifications.core :as notifications]
|
||||
[status-im.transport.utils :as transport.utils]
|
||||
[status-im.transport.message.core :as transport]
|
||||
[status-im.transport.message.v1.protocol :as protocol]
|
||||
[status-im.data-store.messages :as messages-store]
|
||||
[status-im.data-store.user-statuses :as user-statuses-store]
|
||||
[clojure.string :as string]
|
||||
[status-im.utils.fx :as fx]))
|
||||
|
||||
(def receive-interceptors
|
||||
[(re-frame/inject-cofx :random-id)])
|
||||
[status-im.utils.fx :as fx]
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
(defn- emoji-only-content?
|
||||
[content]
|
||||
@ -73,7 +72,7 @@
|
||||
(assoc message :outgoing (= from (:current-public-key db))))
|
||||
|
||||
(fx/defn add-message
|
||||
[{:keys [db] :as cofx} batch? {:keys [chat-id message-id clock-value content] :as message} current-chat?]
|
||||
[{:keys [db] :as cofx} batch? {:keys [chat-id message-id clock-value] :as message} current-chat?]
|
||||
(let [prepared-message (-> message
|
||||
(prepare-message chat-id current-chat?)
|
||||
(add-outgoing-status cofx))]
|
||||
@ -102,7 +101,7 @@
|
||||
message
|
||||
(assoc message :clock-value (utils.clocks/send last-clock-value))))
|
||||
|
||||
(defn- update-legacy-type [{:keys [content-type] :as message}]
|
||||
(defn- update-legacy-data [{:keys [content-type content] :as message}]
|
||||
(cond-> message
|
||||
(= constants/content-type-command-request content-type)
|
||||
(assoc :content-type constants/content-type-command)))
|
||||
@ -133,7 +132,7 @@
|
||||
;; TODO (cammellos): Refactor so it's not computed twice
|
||||
(add-outgoing-status cofx)
|
||||
;; TODO (janherich): Remove after couple of releases
|
||||
update-legacy-type)]
|
||||
update-legacy-data)]
|
||||
(fx/merge cofx
|
||||
{:confirm-messages-processed [{:web3 web3
|
||||
:js-obj js-obj}]}
|
||||
@ -165,15 +164,37 @@
|
||||
(re-index-message-groups chat-id)
|
||||
(chat-loading/group-chat-messages chat-id (get chat->message chat-id))))
|
||||
|
||||
(defn- add-to-chat?
|
||||
[{:keys [db]} {:keys [chat-id clock-value message-id] :as message}]
|
||||
(let [{:keys [deleted-at-clock-value messages not-loaded-message-ids]}
|
||||
(get-in db [:chats chat-id])]
|
||||
(not (or (get messages message-id)
|
||||
(get not-loaded-message-ids message-id)
|
||||
(>= deleted-at-clock-value clock-value)))))
|
||||
|
||||
(defn- filter-messages [cofx messages]
|
||||
(:accumulated (reduce (fn [{:keys [seen-ids] :as acc}
|
||||
{:keys [message-id] :as message}]
|
||||
(if (and (add-to-chat? cofx message)
|
||||
(not (seen-ids message-id)))
|
||||
(-> acc
|
||||
(update :seen-ids conj message-id)
|
||||
(update :accumulated conj message))
|
||||
acc))
|
||||
{:seen-ids #{}
|
||||
:accumulated []}
|
||||
messages)))
|
||||
|
||||
(fx/defn receive-many
|
||||
[{:keys [now] :as cofx} messages]
|
||||
(let [chat->message (group-by :chat-id messages)
|
||||
chat-ids (keys chat->message)
|
||||
chats-fx-fns (map #(chat-model/upsert-chat {:chat-id %
|
||||
:is-active true
|
||||
:timestamp now})
|
||||
chat-ids)
|
||||
messages-fx-fns (map #(add-received-message true %) messages)
|
||||
(let [deduped-messages (filter-messages cofx messages)
|
||||
chat->message (group-by :chat-id deduped-messages)
|
||||
chat-ids (keys chat->message)
|
||||
chats-fx-fns (map #(chat-model/upsert-chat {:chat-id %
|
||||
:is-active true
|
||||
:timestamp now})
|
||||
chat-ids)
|
||||
messages-fx-fns (map #(add-received-message true %) deduped-messages)
|
||||
groups-fx-fns (map #(update-group-messages chat->message %) chat-ids)]
|
||||
(apply fx/merge cofx (concat chats-fx-fns messages-fx-fns groups-fx-fns))))
|
||||
|
||||
@ -190,25 +211,13 @@
|
||||
(defn group-message? [{:keys [message-type]}]
|
||||
(#{:group-user-message :public-group-user-message} message-type))
|
||||
|
||||
(defn add-to-chat?
|
||||
[{:keys [db]} {:keys [chat-id clock-value message-id] :as message}]
|
||||
(let [{:keys [deleted-at-clock-value messages not-loaded-message-ids]}
|
||||
(get-in db [:chats chat-id])]
|
||||
(not (or (get messages message-id)
|
||||
(get not-loaded-message-ids message-id)
|
||||
(>= deleted-at-clock-value clock-value)))))
|
||||
|
||||
;;;; Send message
|
||||
|
||||
(def send-interceptors
|
||||
[(re-frame/inject-cofx :random-id)
|
||||
(re-frame/inject-cofx :random-id-seq)])
|
||||
|
||||
(fx/defn send
|
||||
[{{:keys [network-status current-public-key]} :db :as cofx} chat-id message-id send-record]
|
||||
[{{:keys [network-status]} :db :as cofx} chat-id message-id send-record]
|
||||
(if (= network-status :offline)
|
||||
{:dispatch-later [{:ms 10000
|
||||
:dispatch [:update-message-status chat-id message-id current-public-key :not-sent]}]}
|
||||
:dispatch [:message/update-message-status chat-id message-id :not-sent]}]}
|
||||
(let [wrapped-record (if (= (:message-type send-record) :group-user-message)
|
||||
(group-chats/wrap-group-message cofx chat-id send-record)
|
||||
send-record)]
|
||||
@ -231,7 +240,6 @@
|
||||
message-id (transport.utils/message-id send-record)
|
||||
message-with-id (assoc message :message-id message-id)]
|
||||
(fx/merge cofx
|
||||
(input/process-cooldown)
|
||||
(chat-model/upsert-chat {:chat-id chat-id
|
||||
:timestamp now})
|
||||
(add-message false message-with-id true)
|
||||
@ -247,8 +255,9 @@
|
||||
:sound notifications/sound-name}
|
||||
:tokens [fcm-token]}}))
|
||||
|
||||
(fx/defn update-message-status [{:keys [db]} {:keys [chat-id message-id from]} status]
|
||||
(let [updated-status (-> db
|
||||
(fx/defn update-message-status [{:keys [db]} chat-id message-id status]
|
||||
(let [from (get-in db [:chats chat-id :messages message-id :from])
|
||||
updated-status (-> db
|
||||
(get-in [:chats chat-id :message-statuses message-id from])
|
||||
(assoc :status status))]
|
||||
{:db (assoc-in db
|
||||
@ -264,7 +273,7 @@
|
||||
protocol/map->Message)]
|
||||
(fx/merge cofx
|
||||
(send chat-id message-id send-record)
|
||||
(update-message-status message :sending))))
|
||||
(update-message-status chat-id message-id :sending))))
|
||||
|
||||
(fx/defn remove-message-from-group
|
||||
[{:keys [db]} chat-id {:keys [timestamp message-id]}]
|
||||
@ -300,3 +309,18 @@
|
||||
:show? true)
|
||||
(add-message-type chat))]
|
||||
(upsert-and-send cofx message-data)))
|
||||
|
||||
;; effects
|
||||
|
||||
(re-frame.core/reg-fx
|
||||
:chat-received-message/add-fx
|
||||
(fn [messages]
|
||||
(re-frame/dispatch [:message/add messages])))
|
||||
|
||||
(re-frame/reg-fx
|
||||
:send-notification
|
||||
(fn [{:keys [message payload tokens]}]
|
||||
(let [payload-json (types/clj->json payload)
|
||||
tokens-json (types/clj->json tokens)]
|
||||
(log/debug "send-notification message: " message " payload-json: " payload-json " tokens-json: " tokens-json)
|
||||
(status/notify-users {:message message :payload payload-json :tokens tokens-json} #(log/debug "send-notification cb result: " %)))))
|
||||
|
@ -1,10 +1,9 @@
|
||||
(ns status-im.chat.subs
|
||||
(:require [clojure.string :as string]
|
||||
[re-frame.core :refer [reg-sub subscribe]]
|
||||
[status-im.chat.constants :as chat-constants]
|
||||
[status-im.chat.models.input :as input-model]
|
||||
[status-im.chat.constants :as chat.constants]
|
||||
[status-im.chat.commands.core :as commands]
|
||||
[status-im.chat.commands.input :as commands-input]
|
||||
[status-im.chat.commands.input :as commands.input]
|
||||
[status-im.utils.datetime :as time]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.utils.gfycat.core :as gfycat]
|
||||
@ -122,12 +121,19 @@
|
||||
(or message-statuses {})))
|
||||
|
||||
(defn sort-message-groups
|
||||
"Sorts message groups according to timestamp of first message in group "
|
||||
"Sorts message groups according to timestamp of first message in group"
|
||||
[message-groups messages]
|
||||
(sort-by
|
||||
(comp unchecked-negate :timestamp (partial get messages) :message-id first second)
|
||||
message-groups))
|
||||
|
||||
(defn quoted-message-data
|
||||
"Selects certain data from quoted message which must be available in the view"
|
||||
[message-id messages]
|
||||
(let [{:keys [from content]} (get messages message-id)]
|
||||
{:from from
|
||||
:text (:text content)}))
|
||||
|
||||
(defn messages-with-datemarks-and-statuses
|
||||
"Converts message groups into sequence of messages interspersed with datemarks,
|
||||
with correct user statuses associated into message"
|
||||
@ -136,10 +142,13 @@
|
||||
(into (list {:value datemark
|
||||
:type :datemark})
|
||||
(map (fn [{:keys [message-id timestamp-str]}]
|
||||
(assoc (get messages message-id)
|
||||
:datemark datemark
|
||||
:timestamp-str timestamp-str
|
||||
:user-statuses (get message-statuses message-id))))
|
||||
(let [{:keys [content] :as message} (get messages message-id)]
|
||||
(cond-> (assoc message
|
||||
:datemark datemark
|
||||
:timestamp-str timestamp-str
|
||||
:user-statuses (get message-statuses message-id))
|
||||
(:response-to content) ;; quoted message reference
|
||||
(assoc-in [:content :response-to] (quoted-message-data (:response-to content) messages))))))
|
||||
message-references))
|
||||
message-groups))
|
||||
|
||||
@ -232,7 +241,7 @@
|
||||
(->> commands
|
||||
map->sorted-seq
|
||||
(filter (fn [{:keys [type]}]
|
||||
(when (input-model/starts-as-command? input-text)
|
||||
(when (commands.input/starts-as-command? input-text)
|
||||
(string/includes? (commands/command-name type) input-text))))))
|
||||
|
||||
(reg-sub
|
||||
@ -253,14 +262,14 @@
|
||||
:<- [:get-current-chat-ui-prop :selection]
|
||||
:<- [:get-commands-for-chat]
|
||||
(fn [[{:keys [input-text]} selection commands]]
|
||||
(commands-input/selected-chat-command input-text selection commands)))
|
||||
(commands.input/selected-chat-command input-text selection commands)))
|
||||
|
||||
(reg-sub
|
||||
:chat-input-placeholder
|
||||
:<- [:get-current-chat]
|
||||
:<- [:selected-chat-command]
|
||||
(fn [[{:keys [input-text]} {:keys [params current-param-position]}]]
|
||||
(when (string/ends-with? (or input-text "") chat-constants/spacing-char)
|
||||
(when (string/ends-with? (or input-text "") chat.constants/spacing-char)
|
||||
(get-in params [current-param-position :placeholder]))))
|
||||
|
||||
(reg-sub
|
||||
@ -290,7 +299,7 @@
|
||||
:<- [:get-all-available-commands]
|
||||
(fn [[show-suggestions? {:keys [input-text]} commands]]
|
||||
(and (or show-suggestions?
|
||||
(input-model/starts-as-command? (string/trim (or input-text ""))))
|
||||
(commands.input/starts-as-command? (string/trim (or input-text ""))))
|
||||
(seq commands))))
|
||||
|
||||
(reg-sub
|
||||
@ -310,8 +319,11 @@
|
||||
(reg-sub
|
||||
:get-photo-path
|
||||
:<- [:get-contacts]
|
||||
(fn [contacts [_ id]]
|
||||
(:photo-path (contacts id))))
|
||||
:<- [:get-current-account]
|
||||
(fn [[contacts account] [_ id]]
|
||||
(or (:photo-path (contacts id))
|
||||
(when (= id (:public-key account))
|
||||
(:photo-path account)))))
|
||||
|
||||
(reg-sub
|
||||
:get-last-message
|
||||
@ -361,3 +373,9 @@
|
||||
(fn [[{:keys [public?]} cooldown-enabled?]]
|
||||
(and public?
|
||||
cooldown-enabled?)))
|
||||
|
||||
(reg-sub
|
||||
:get-reply-message
|
||||
:<- [:get-current-chat]
|
||||
(fn [{:keys [metadata messages]}]
|
||||
(get messages (:responding-to-message metadata))))
|
||||
|
@ -7,20 +7,14 @@
|
||||
(def ethereum-rpc-url "http://localhost:8545")
|
||||
|
||||
(def text-content-type "text/plain")
|
||||
(def content-type-log-message "log-message")
|
||||
(def content-type-command "command")
|
||||
(def content-type-command-request "command-request")
|
||||
(def content-type-status "status")
|
||||
(def content-type-placeholder "placeholder")
|
||||
(def content-type-emoji "emoji")
|
||||
|
||||
(def desktop-content-types
|
||||
#{text-content-type content-type-emoji})
|
||||
|
||||
(def command-send "send")
|
||||
(def command-request "request")
|
||||
(def command-send-status-update-interval-ms 60000)
|
||||
|
||||
(def min-password-length 6)
|
||||
(def max-chat-name-length 20)
|
||||
(def response-suggesstion-resize-duration 100)
|
||||
|
@ -1,20 +1,14 @@
|
||||
(ns status-im.data-store.messages
|
||||
(:require [cljs.reader :as reader]
|
||||
(:require [cljs.tools.reader.edn :as edn]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.data-store.realm.core :as core]
|
||||
[status-im.utils.core :as utils]))
|
||||
|
||||
(defn- command-type?
|
||||
[type]
|
||||
(contains?
|
||||
#{constants/content-type-command constants/content-type-command-request}
|
||||
type))
|
||||
|
||||
(defn- transform-message [{:keys [content-type] :as message}]
|
||||
(cond-> (update message :message-type keyword)
|
||||
(command-type? content-type)
|
||||
(update :content reader/read-string)))
|
||||
(defn- transform-message [message]
|
||||
(-> message
|
||||
(update :message-type keyword)
|
||||
(update :content edn/read-string)))
|
||||
|
||||
(defn- get-by-chat-id
|
||||
([chat-id]
|
||||
@ -26,13 +20,6 @@
|
||||
(core/all-clj :message))]
|
||||
(map transform-message messages))))
|
||||
|
||||
;; TODO janherich: define as cofx once debug handlers are refactored
|
||||
(defn get-log-messages
|
||||
[chat-id]
|
||||
(->> (get-by-chat-id chat-id 0)
|
||||
(filter #(= (:content-type %) constants/content-type-log-message))
|
||||
(map #(select-keys % [:content :timestamp]))))
|
||||
|
||||
(def default-values
|
||||
{:to nil})
|
||||
|
||||
@ -78,10 +65,7 @@
|
||||
(defn- prepare-content [content]
|
||||
(if (string? content)
|
||||
content
|
||||
(pr-str
|
||||
;; TODO janherich: this is ugly and not systematic, define something like `:not-persisent`
|
||||
;; option for command params instead
|
||||
(update content :params dissoc :password :password-confirmation))))
|
||||
(pr-str content)))
|
||||
|
||||
(defn- prepare-message [message]
|
||||
(utils/update-if-present message :content prepare-content))
|
||||
|
@ -122,6 +122,16 @@
|
||||
browser/v8
|
||||
dapp-permissions/v9])
|
||||
|
||||
(def v12 [chat/v5
|
||||
transport/v6
|
||||
contact/v1
|
||||
message/v7
|
||||
mailserver/v11
|
||||
user-status/v1
|
||||
local-storage/v1
|
||||
browser/v8
|
||||
dapp-permissions/v9])
|
||||
|
||||
;; put schemas ordered by version
|
||||
(def schemas [{:schema v1
|
||||
:schemaVersion 1
|
||||
@ -155,4 +165,7 @@
|
||||
:migration migrations/v10}
|
||||
{:schema v11
|
||||
:schemaVersion 11
|
||||
:migration migrations/v11}])
|
||||
:migration migrations/v11}
|
||||
{:schema v12
|
||||
:schemaVersion 12
|
||||
:migration migrations/v12}])
|
||||
|
@ -66,3 +66,21 @@
|
||||
(let [mailservers (.objects new-realm "mailserver")]
|
||||
(dotimes [i (.-length mailservers)]
|
||||
(aset (aget mailservers i) "fleet" "eth.beta"))))
|
||||
|
||||
(defn v12 [old-realm new-realm]
|
||||
(log/debug "migrating v12 account database")
|
||||
(some-> new-realm
|
||||
(.objects "message")
|
||||
(.filtered (str "content-type = \"text/plain\""))
|
||||
(.map (fn [message _ _]
|
||||
(let [content (aget message "content")
|
||||
new-content {:text content}]
|
||||
(aset message "content" (pr-str new-content))))))
|
||||
(some-> new-realm
|
||||
(.objects "message")
|
||||
(.filtered (str "content-type = \"emoji\""))
|
||||
(.map (fn [message _ _]
|
||||
(let [content (aget message "content")
|
||||
new-content {:text content}]
|
||||
(aset message "content" (pr-str new-content)))))))
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
(ns status-im.data-store.user-statuses
|
||||
(:require [clojure.string :as string]
|
||||
[cljs.reader :as reader]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.data-store.realm.core :as core]))
|
||||
|
||||
|
@ -26,7 +26,7 @@
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:process-http-request
|
||||
[(re-frame/inject-cofx :random-id)]
|
||||
[(re-frame/inject-cofx :random-id-generator)]
|
||||
(fn [cofx [_ url type data]]
|
||||
(try
|
||||
(models.dev-server/process-request! {:cofx cofx
|
||||
|
@ -9,6 +9,12 @@
|
||||
[status-im.bootnodes.core :as bootnodes]
|
||||
[status-im.browser.core :as browser]
|
||||
[status-im.browser.permissions :as browser.permissions]
|
||||
[status-im.chat.models :as chat]
|
||||
[status-im.chat.models.group-chat :as chat.group]
|
||||
[status-im.chat.models.message :as chat.message]
|
||||
[status-im.chat.models.loading :as chat.loading]
|
||||
[status-im.chat.models.input :as chat.input]
|
||||
[status-im.chat.commands.input :as commands.input]
|
||||
[status-im.data-store.core :as data-store]
|
||||
[status-im.fleet.core :as fleet]
|
||||
[status-im.hardwallet.core :as hardwallet]
|
||||
@ -254,7 +260,7 @@
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:mailserver.ui/save-pressed
|
||||
[(re-frame/inject-cofx :random-id)]
|
||||
[(re-frame/inject-cofx :random-id-generator)]
|
||||
(fn [cofx _]
|
||||
(mailserver/upsert cofx)))
|
||||
|
||||
@ -292,7 +298,7 @@
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:network.ui/save-network-pressed
|
||||
[(re-frame/inject-cofx :random-id)]
|
||||
[(re-frame/inject-cofx :random-id-generator)]
|
||||
(fn [cofx]
|
||||
(network/save-network cofx)))
|
||||
|
||||
@ -377,7 +383,7 @@
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:bootnodes.ui/save-pressed
|
||||
[(re-frame/inject-cofx :random-id)]
|
||||
[(re-frame/inject-cofx :random-id-generator)]
|
||||
(fn [cofx _]
|
||||
(bootnodes/upsert cofx)))
|
||||
|
||||
@ -441,18 +447,138 @@
|
||||
(handlers/register-handler-fx
|
||||
:chat.ui/clear-history-pressed
|
||||
(fn [_ _]
|
||||
{:ui/show-confirmation {:title (i18n/label :t/clear-history-title)
|
||||
:content (i18n/label :t/clear-history-confirmation-content)
|
||||
{:ui/show-confirmation {:title (i18n/label :t/clear-history-title)
|
||||
:content (i18n/label :t/clear-history-confirmation-content)
|
||||
:confirm-button-text (i18n/label :t/clear-history-action)
|
||||
:on-accept #(re-frame/dispatch [:clear-history])}}))
|
||||
:on-accept #(re-frame/dispatch [:chat.ui/clear-history])}}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat.ui/delete-chat-pressed
|
||||
(fn [_ [_ chat-id]]
|
||||
{:ui/show-confirmation {:title (i18n/label :t/delete-chat-confirmation)
|
||||
:content ""
|
||||
:confirm-button-text (i18n/label :t/delete-chat-action)
|
||||
:on-accept #(re-frame/dispatch [:remove-chat-and-navigate-home chat-id])}}))
|
||||
:chat.ui/remove-chat-pressed
|
||||
(fn [_ [_ chat-id group?]]
|
||||
{:ui/show-confirmation {:title (i18n/label :t/delete-confirmation)
|
||||
:content (i18n/label :t/delete-chat-confirmation)
|
||||
:confirm-button-text (i18n/label :t/delete)
|
||||
:on-accept #(re-frame/dispatch [:chat.ui/remove-chat chat-id])}}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat.ui/set-chat-ui-props
|
||||
(fn [{:keys [db]} [_ kvs]]
|
||||
{:db (chat/set-chat-ui-props db kvs)}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat.ui/show-message-details
|
||||
(fn [{:keys [db]} [_ details]]
|
||||
{:db (chat/set-chat-ui-props db {:show-bottom-info? true
|
||||
:bottom-info details})}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat.ui/show-message-options
|
||||
(fn [{:keys [db]} [_ options]]
|
||||
{:db (chat/set-chat-ui-props db {:show-message-options? true
|
||||
:message-options options})}))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat.ui/navigate-to-chat
|
||||
(fn [cofx [_ chat-id opts]]
|
||||
(chat/navigate-to-chat cofx chat-id opts)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat.ui/load-more-messages
|
||||
[(re-frame/inject-cofx :data-store/get-messages)
|
||||
(re-frame/inject-cofx :data-store/get-user-statuses)]
|
||||
(fn [cofx _]
|
||||
(chat.loading/load-more-messages cofx)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat.ui/start-chat
|
||||
(fn [cofx [_ contact-id opts]]
|
||||
(chat/start-chat cofx contact-id opts)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat.ui/start-public-chat
|
||||
(fn [cofx [_ topic modal?]]
|
||||
(chat/start-public-chat cofx topic modal?)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat.ui/start-group-chat
|
||||
(fn [cofx [_ group-name]]
|
||||
(chat.group/start-group-chat cofx group-name)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat.ui/remove-chat
|
||||
(fn [cofx [_ chat-id]]
|
||||
(chat/remove-chat cofx chat-id)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat.ui/clear-history
|
||||
(fn [{{:keys [current-chat-id]} :db :as cofx} _]
|
||||
(chat/clear-history cofx current-chat-id)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat.ui/resend-message
|
||||
(fn [cofx [_ chat-id message-id]]
|
||||
(chat.message/resend-message cofx chat-id message-id)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat.ui/delete-message
|
||||
(fn [cofx [_ chat-id message-id]]
|
||||
(chat.message/delete-message cofx chat-id message-id)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat.ui/show-profile
|
||||
(fn [cofx [_ identity]]
|
||||
(navigation/navigate-to-cofx
|
||||
(assoc-in cofx [:db :contacts/identity] identity) :profile nil)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat.ui/set-chat-input-text
|
||||
(fn [cofx [_ text]]
|
||||
(chat.input/set-chat-input-text cofx text)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat.ui/select-chat-input-command
|
||||
(fn [cofx [_ command params previous-command-message]]
|
||||
(chat.input/select-chat-input-command cofx command params previous-command-message)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat.ui/set-command-prefix
|
||||
(fn [cofx _]
|
||||
(chat.input/set-command-prefix cofx)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat.ui/cancel-message-reply
|
||||
(fn [cofx _]
|
||||
(chat.input/cancel-message-reply cofx)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat.ui/reply-to-message
|
||||
(fn [cofx [_ message-id]]
|
||||
(chat.input/reply-to-message cofx message-id)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat.ui/set-command-parameter
|
||||
(fn [cofx [_ last-param? index value]]
|
||||
(commands.input/set-command-parameter cofx last-param? index value)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat.ui/send-current-message
|
||||
(fn [cofx _]
|
||||
(chat.input/send-current-message cofx)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:chat/disable-cooldown
|
||||
(fn [cofx _]
|
||||
(chat/disable-chat-cooldown cofx)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:message/add
|
||||
(fn [cofx [_ messages]]
|
||||
(chat.message/receive-many cofx messages)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:message/update-message-status
|
||||
(fn [cofx [_ chat-id message-id status]]
|
||||
(chat.message/update-message-status cofx chat-id message-id status)))
|
||||
|
||||
;; signal module
|
||||
|
||||
|
@ -1,14 +1,13 @@
|
||||
(ns status-im.group-chats.core
|
||||
(:require
|
||||
[status-im.utils.config :as config]
|
||||
[status-im.transport.utils :as transport.utils]
|
||||
[status-im.transport.db :as transport.db]
|
||||
[status-im.transport.utils :as transport.utils]
|
||||
[status-im.transport.message.core :as protocol.message]
|
||||
[status-im.transport.message.v1.core :as transport]
|
||||
[status-im.transport.message.v1.protocol :as transport.protocol]
|
||||
[status-im.utils.fx :as fx]
|
||||
[status-im.chat.models :as models.chat]))
|
||||
(:require [status-im.utils.config :as config]
|
||||
[status-im.transport.utils :as transport.utils]
|
||||
[status-im.transport.db :as transport.db]
|
||||
[status-im.transport.utils :as transport.utils]
|
||||
[status-im.transport.message.core :as protocol.message]
|
||||
[status-im.transport.message.v1.core :as transport]
|
||||
[status-im.transport.message.v1.protocol :as transport.protocol]
|
||||
[status-im.utils.fx :as fx]
|
||||
[status-im.chat.models :as models.chat]))
|
||||
|
||||
(defn wrap-group-message [cofx chat-id message]
|
||||
(when-let [chat (get-in cofx [:db :chats chat-id])]
|
||||
|
@ -65,7 +65,7 @@
|
||||
:datetime-ago-format :close-app-button :block :camera-access-error
|
||||
:wallet-invalid-address :address-explication :remove
|
||||
:transactions-delete-content :transactions-unsigned-empty
|
||||
:transaction-moved-text :add-members :sign-later-title :sharing-cancel
|
||||
:transaction-moved-text :add-members :sign-later-title
|
||||
:yes :dapps :popular-tags :network-settings :twelve-words-in-correct-order
|
||||
:transaction-moved-title :photos-access-error :hash
|
||||
:removed-from-chat :done :remove-from-contacts :delete-chat :new-group-chat
|
||||
|
@ -138,12 +138,13 @@
|
||||
(navigation/navigate-to-cofx :edit-mailserver nil))))
|
||||
|
||||
(fx/defn upsert
|
||||
[{{:mailservers/keys [manage] :account/keys [account] :as db} :db :as cofx}]
|
||||
[{{:mailservers/keys [manage] :account/keys [account] :as db} :db
|
||||
random-id-generator :random-id-generator :as cofx}]
|
||||
(let [{:keys [name url id]} manage
|
||||
current-fleet (fleet/current-fleet db)
|
||||
mailserver (build
|
||||
(or (:value id)
|
||||
(keyword (string/replace (:random-id cofx) "-" "")))
|
||||
(keyword (string/replace (random-id-generator) "-" "")))
|
||||
(:value name)
|
||||
(:value url))
|
||||
current (connected? cofx (:id mailserver))]
|
||||
|
@ -68,28 +68,26 @@
|
||||
(when handler
|
||||
(handler data cofx))))
|
||||
|
||||
(defn save
|
||||
([cofx]
|
||||
(save cofx nil))
|
||||
([{{:network/keys [manage]
|
||||
:account/keys [account] :as db} :db :as cofx}
|
||||
{:keys [data success-event on-success on-failure]}]
|
||||
(let [data (or data manage)]
|
||||
(if (valid-manage? data)
|
||||
(let [{:keys [name url chain network-id]} data
|
||||
network (new-network (:random-id cofx)
|
||||
(:value name)
|
||||
(:value url)
|
||||
(:value chain)
|
||||
(:value network-id))
|
||||
new-networks (merge {(:id network) network} (:networks account))]
|
||||
(fx/merge cofx
|
||||
{:db (dissoc db :networks/manage)}
|
||||
#(action-handler on-success (:id network) %)
|
||||
(accounts.update/account-update
|
||||
{:networks new-networks}
|
||||
{:success-event success-event})))
|
||||
(action-handler on-failure)))))
|
||||
(fx/defn save
|
||||
[{{:network/keys [manage] :account/keys [account] :as db} :db
|
||||
random-id-generator :random-id-generator :as cofx}
|
||||
{:keys [data success-event on-success on-failure]}]
|
||||
(let [data (or data manage)]
|
||||
(if (valid-manage? data)
|
||||
(let [{:keys [name url chain network-id]} data
|
||||
network (new-network (random-id-generator)
|
||||
(:value name)
|
||||
(:value url)
|
||||
(:value chain)
|
||||
(:value network-id))
|
||||
new-networks (merge {(:id network) network} (:networks account))]
|
||||
(fx/merge cofx
|
||||
{:db (dissoc db :networks/manage)}
|
||||
#(action-handler on-success (:id network) %)
|
||||
(accounts.update/account-update
|
||||
{:networks new-networks}
|
||||
{:success-event success-event})))
|
||||
(action-handler on-failure))))
|
||||
|
||||
;; No edit functionality actually implemented
|
||||
(fx/defn edit
|
||||
|
@ -38,7 +38,7 @@
|
||||
(aget array i)))
|
||||
|
||||
(fx/defn receive-whisper-messages
|
||||
[{:keys [now] :as cofx} [_ js-error js-messages chat-id]]
|
||||
[{:keys [now] :as cofx} js-error js-messages chat-id]
|
||||
(if (and (not js-error)
|
||||
js-messages)
|
||||
(let [now-in-s (quot now 1000)
|
||||
@ -50,9 +50,9 @@
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:protocol/receive-whisper-message
|
||||
[handlers/logged-in
|
||||
(re-frame/inject-cofx :random-id)]
|
||||
receive-whisper-messages)
|
||||
[handlers/logged-in (re-frame/inject-cofx :random-id-generator)]
|
||||
(fn [cofx [_ js-error js-messages chat-id]]
|
||||
(receive-whisper-messages cofx js-error js-messages chat-id)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:protocol/send-status-message-error
|
||||
@ -61,7 +61,8 @@
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:contact/send-new-sym-key
|
||||
(fn [{:keys [db random-id] :as cofx} [_ {:keys [chat-id topic message sym-key sym-key-id]}]]
|
||||
(fn [{:keys [db] :as cofx}
|
||||
[_ {:keys [chat-id topic message sym-key sym-key-id]}]]
|
||||
(let [{:keys [web3 current-public-key]} db
|
||||
chat-transport-info (-> (get-in db [:transport/chats chat-id])
|
||||
(assoc :sym-key-id sym-key-id
|
||||
@ -162,11 +163,11 @@
|
||||
(remove-hash envelope-hash)
|
||||
(update-resend-contact-message chat-id)))
|
||||
|
||||
(when-let [message (get-in db [:chats chat-id :messages message-id])]
|
||||
(when-let [{:keys [from]} (get-in db [:chats chat-id :messages message-id])]
|
||||
(let [{:keys [fcm-token]} (get-in db [:contacts/contacts chat-id])]
|
||||
(fx/merge cofx
|
||||
(remove-hash envelope-hash)
|
||||
(models.message/update-message-status message status)
|
||||
(models.message/update-message-status chat-id message-id status)
|
||||
(models.message/send-push-notification fcm-token status)))))))
|
||||
|
||||
(defn- own-info [db]
|
||||
|
@ -15,7 +15,7 @@
|
||||
(extend-type transport.protocol/GroupLeave
|
||||
message/StatusMessage
|
||||
(receive [this chat-id signature _ cofx]
|
||||
(models.group-chat/handle-group-leave chat-id signature cofx)))
|
||||
(models.group-chat/handle-group-leave cofx chat-id signature)))
|
||||
|
||||
(extend-type transport.contact/ContactRequest
|
||||
message/StatusMessage
|
||||
|
@ -3,6 +3,7 @@
|
||||
(:require [status-im.transport.message.v1.contact :as v1.contact]
|
||||
[status-im.transport.message.v1.protocol :as v1.protocol]
|
||||
[status-im.transport.message.v1.core :as v1]
|
||||
[status-im.constants :as constants]
|
||||
[cognitect.transit :as transit]))
|
||||
|
||||
;; When adding a new reccord implenting the StatusMessage protocol it is required to implement:
|
||||
@ -45,7 +46,7 @@
|
||||
|
||||
(deftype MessageHandler []
|
||||
Object
|
||||
(tag [this v] "c4")
|
||||
(tag [this v] "c7")
|
||||
(rep [this {:keys [content content-type message-type clock-value timestamp]}]
|
||||
#js [content content-type message-type clock-value timestamp]))
|
||||
|
||||
@ -82,6 +83,16 @@
|
||||
;; Reader handlers
|
||||
;;
|
||||
|
||||
(defn- safe-content-parse [content-type content]
|
||||
;; handling only the text content case
|
||||
(if (= content-type constants/text-content-type)
|
||||
(if (and (map? content) (string? (:text content)))
|
||||
;; correctly formatted map
|
||||
content
|
||||
;; create safe `{:text string-content}` value from anything else
|
||||
{:text (str content)})
|
||||
content))
|
||||
|
||||
;; Here we only need to call the record with the arguments parsed from the clojure datastructures
|
||||
(def reader (transit/reader :json
|
||||
{:handlers
|
||||
@ -92,7 +103,9 @@
|
||||
"c3" (fn [[name profile-image address fcm-token]]
|
||||
(v1.contact/ContactRequestConfirmed. name profile-image address fcm-token))
|
||||
"c4" (fn [[content content-type message-type clock-value timestamp]]
|
||||
(v1.protocol/Message. content content-type message-type clock-value timestamp))
|
||||
(v1.protocol/Message. (safe-content-parse content-type content) content-type message-type clock-value timestamp))
|
||||
"c7" (fn [[content content-type message-type clock-value timestamp]]
|
||||
(v1.protocol/Message. (safe-content-parse content-type content) content-type message-type clock-value timestamp))
|
||||
"c5" (fn [message-ids]
|
||||
(v1.protocol/MessagesSeen. message-ids))
|
||||
"c6" (fn [[name profile-image address fcm-token]]
|
||||
|
@ -9,8 +9,8 @@
|
||||
|
||||
(defrecord ContactRequest [name profile-image address fcm-token]
|
||||
message/StatusMessage
|
||||
(send [this chat-id {:keys [db random-id] :as cofx}]
|
||||
(let [topic (transport.utils/get-topic random-id)
|
||||
(send [this chat-id {:keys [db random-id-generator] :as cofx}]
|
||||
(let [topic (transport.utils/get-topic (random-id-generator))
|
||||
on-success (fn [sym-key sym-key-id]
|
||||
(re-frame/dispatch [:contact/send-new-sym-key
|
||||
{:sym-key-id sym-key-id
|
||||
|
@ -25,6 +25,7 @@
|
||||
(def blue-dark "#3147ac") ;; Used as secondary wallet color (icon background)
|
||||
(def hawkes-blue "#dce2fb") ;; Outgoing chat messages background
|
||||
(def wild-blue-yonder "#707caf") ;; Text color for outgoing messages timestamp
|
||||
(def blue-light "#cad1ed") ;; Text and bottom border color for own quoted messages
|
||||
(def red "#ff2d55") ;; Used to highlight errors or "dangerous" actions
|
||||
(def red-light "#ffe5ea") ;; error tooltip
|
||||
(def text-light-gray "#212121") ;; Used for labels (home items)
|
||||
|
@ -95,7 +95,8 @@
|
||||
:icons/info (js/require "./resources/icons/info.svg")
|
||||
:icons/hardwallet (js/require "./resources/icons/hardwallet.svg")
|
||||
:icons/password (js/require "./resources/icons/password.svg")
|
||||
:icons/nfc (js/require "./resources/icons/nfc.svg")}
|
||||
:icons/nfc (js/require "./resources/icons/nfc.svg")
|
||||
:icons/reply (js/require "./resources/icons/reply.svg")}
|
||||
{:icons/discover (components.svg/slurp-svg "./resources/icons/bottom/discover_gray.svg")
|
||||
:icons/contacts (components.svg/slurp-svg "./resources/icons/bottom/contacts_gray.svg")
|
||||
:icons/home (components.svg/slurp-svg "./resources/icons/bottom/home_gray.svg")
|
||||
@ -164,7 +165,8 @@
|
||||
:icons/info (components.svg/slurp-svg "./resources/icons/info.svg")
|
||||
:icons/hardwallet (components.svg/slurp-svg "./resources/icons/hardwallet.svg")
|
||||
:icons/password (components.svg/slurp-svg "./resources/icons/password.svg")
|
||||
:icons/nfc (components.svg/slurp-svg "./resources/icons/nfc.svg")}))
|
||||
:icons/nfc (components.svg/slurp-svg "./resources/icons/nfc.svg")
|
||||
:icons/reply (components.svg/slurp-svg "./resources/icons/reply.svg")}))
|
||||
|
||||
(defn normalize-property-name [n]
|
||||
(if (= n :icons/options)
|
||||
|
@ -12,8 +12,10 @@
|
||||
(:url content))
|
||||
(.share react/sharing (clj->js content))))
|
||||
|
||||
(defn share-options [text]
|
||||
[{:label (i18n/label :t/sharing-copy-to-clipboard)
|
||||
(defn- message-options [message-id text]
|
||||
[{:label (i18n/label :t/message-reply)
|
||||
:action #(re-frame/dispatch [:chat.ui/reply-to-message message-id])}
|
||||
{:label (i18n/label :t/sharing-copy-to-clipboard)
|
||||
:action #(react/copy-to-clipboard text)}
|
||||
{:label (i18n/label :t/sharing-share)
|
||||
:action #(open-share {:message text})}])
|
||||
@ -23,10 +25,10 @@
|
||||
(action-sheet/show options)
|
||||
(dialog/show options)))
|
||||
|
||||
(defn share [text dialog-title]
|
||||
(defn chat-message [message-id text dialog-title]
|
||||
(show {:title dialog-title
|
||||
:options (share-options text)
|
||||
:cancel-text (i18n/label :t/sharing-cancel)}))
|
||||
:options (message-options message-id text)
|
||||
:cancel-text (i18n/label :t/message-options-cancel)}))
|
||||
|
||||
(defn browse [link]
|
||||
(show {:title (i18n/label :t/browsing-title)
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
(defn- render-row [row _ _]
|
||||
[contact-view/contact-view {:contact row
|
||||
:on-press #(re-frame/dispatch [:start-chat (:whisper-identity %) {:navigation-reset? true}])
|
||||
:on-press #(re-frame/dispatch [:chat.ui/start-chat (:whisper-identity %) {:navigation-reset? true}])
|
||||
:show-forward? true}])
|
||||
|
||||
(views/defview new-chat []
|
||||
|
@ -30,7 +30,7 @@
|
||||
(i18n/label :t/topic-name-error))])
|
||||
(re-frame/dispatch [:set :public-group-topic %]))
|
||||
:on-submit-editing #(when (and topic (spec/valid? ::v/topic topic))
|
||||
(re-frame/dispatch [:create-new-public-chat topic]))
|
||||
(re-frame/dispatch [:chat.ui/start-public-chat topic]))
|
||||
:auto-capitalize :none
|
||||
:auto-focus false
|
||||
:accessibility-label :chat-name-input
|
||||
@ -47,7 +47,7 @@
|
||||
(first topic)]])
|
||||
|
||||
(defn- render-topic [topic]
|
||||
[react/touchable-highlight {:on-press #(re-frame/dispatch [:create-new-public-chat topic])
|
||||
[react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/start-public-chat topic])
|
||||
:accessibility-label :chat-item}
|
||||
[react/view
|
||||
[list/item
|
||||
|
@ -1,5 +1,5 @@
|
||||
(ns status-im.ui.screens.browser.views
|
||||
(:require [cljs.reader :as reader]
|
||||
(:require [cljs.tools.reader.edn :as edn]
|
||||
[re-frame.core :as re-frame]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.browser.core :as browser]
|
||||
@ -24,7 +24,7 @@
|
||||
[status-im.utils.views :as views]))
|
||||
|
||||
(def browser-config
|
||||
(reader/read-string (slurp "./src/status_im/utils/browser_config.edn")))
|
||||
(edn/read-string (slurp "./src/status_im/utils/browser_config.edn")))
|
||||
|
||||
(defn toolbar-content [url {:keys [secure?] :as browser} url-editing?]
|
||||
(let [url-text (atom url)]
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
(defn view-profile [chat-id]
|
||||
{:label (i18n/label :t/view-profile)
|
||||
:action #(re-frame/dispatch [:show-profile chat-id])})
|
||||
:action #(re-frame/dispatch [:chat.ui/show-profile chat-id])})
|
||||
|
||||
(defn group-info [chat-id]
|
||||
{:label (i18n/label :t/group-info)
|
||||
@ -16,11 +16,11 @@
|
||||
|
||||
(defn- clear-history []
|
||||
{:label (i18n/label :t/clear-history)
|
||||
:action #(re-frame/dispatch [:clear-history?])})
|
||||
:action #(re-frame/dispatch [:chat.ui/clear-history-pressed])})
|
||||
|
||||
(defn- delete-chat [chat-id group?]
|
||||
{:label (i18n/label :t/delete-chat)
|
||||
:action #(re-frame/dispatch [:remove-chat-and-navigate-home? chat-id group?])})
|
||||
:action #(re-frame/dispatch [:chat.ui/remove-chat-pressed chat-id group?])})
|
||||
|
||||
(defn- chat-actions [chat-id]
|
||||
[view-my-wallet
|
||||
|
@ -74,7 +74,7 @@
|
||||
:status message-status}]))
|
||||
(into {}))
|
||||
statuses (vals (merge participants user-statuses))]
|
||||
[overlay {:on-click-outside #(re-frame/dispatch [:set-chat-ui-props {:show-bottom-info? false}])}
|
||||
[overlay {:on-click-outside #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:show-bottom-info? false}])}
|
||||
[container (* styles/item-height (count statuses))
|
||||
[list/flat-list {:contentContainerStyle styles/bottom-info-list-container
|
||||
:data statuses
|
||||
|
@ -3,18 +3,20 @@
|
||||
(:require [clojure.string :as string]
|
||||
[reagent.core :as reagent]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.chat.constants :as constants]
|
||||
[status-im.ui.screens.chat.styles.input.input :as style]
|
||||
[status-im.ui.screens.chat.styles.message.message :as message-style]
|
||||
[status-im.ui.screens.chat.input.parameter-box :as parameter-box]
|
||||
[status-im.ui.screens.chat.input.send-button :as send-button]
|
||||
[status-im.ui.screens.chat.input.suggestions :as suggestions]
|
||||
[status-im.ui.screens.chat.input.validation-messages :as validation-messages]
|
||||
[status-im.ui.screens.chat.photos :as photos]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.ui.components.animation :as animation]
|
||||
[status-im.ui.components.colors :as colors]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.components.icons.vector-icons :as vi]
|
||||
[status-im.ui.components.icons.vector-icons :as vector-icons]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.utils.gfycat.core :as gfycat]
|
||||
[status-im.utils.utils :as utils]))
|
||||
|
||||
(defview basic-text-input [{:keys [set-container-width-fn height single-line-input?]}]
|
||||
@ -22,23 +24,23 @@
|
||||
cooldown-enabled? [:chat-cooldown-enabled?]]
|
||||
[react/text-input
|
||||
(merge
|
||||
{:ref #(when % (re-frame/dispatch [:set-chat-ui-props {:input-ref %}]))
|
||||
{:ref #(when % (re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-ref %}]))
|
||||
:accessibility-label :chat-message-input
|
||||
:multiline (not single-line-input?)
|
||||
:default-value (or input-text "")
|
||||
:editable (not cooldown-enabled?)
|
||||
:blur-on-submit false
|
||||
:on-focus #(re-frame/dispatch [:set-chat-ui-props {:input-focused? true
|
||||
:messages-focused? false}])
|
||||
:on-blur #(re-frame/dispatch [:set-chat-ui-props {:input-focused? false}])
|
||||
:on-focus #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-focused? true
|
||||
:messages-focused? false}])
|
||||
:on-blur #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-focused? false}])
|
||||
:on-submit-editing #(when single-line-input?
|
||||
(re-frame/dispatch [:send-current-message]))
|
||||
(re-frame/dispatch [:chat.ui/send-current-message]))
|
||||
:on-layout #(set-container-width-fn (.-width (.-layout (.-nativeEvent %))))
|
||||
:on-change #(re-frame/dispatch [:set-chat-input-text (.-text (.-nativeEvent %))])
|
||||
:on-change #(re-frame/dispatch [:chat.ui/set-chat-input-text (.-text (.-nativeEvent %))])
|
||||
:on-selection-change #(let [s (-> (.-nativeEvent %)
|
||||
(.-selection))
|
||||
end (.-end s)]
|
||||
(re-frame/dispatch [:update-text-selection end]))
|
||||
(re-frame/dispatch [:chat.ui/set-chat-ui-props {:selection end}]))
|
||||
:style (style/input-view single-line-input?)
|
||||
:placeholder-text-color colors/gray
|
||||
:auto-capitalize :sentences}
|
||||
@ -92,15 +94,40 @@
|
||||
[input-helper {:width width}]]])))
|
||||
|
||||
(defview commands-button []
|
||||
(letsubs [commands [:get-all-available-commands]]
|
||||
(when (seq commands)
|
||||
(letsubs [commands [:get-all-available-commands]
|
||||
reply-message [:get-reply-message]]
|
||||
(when (and (not reply-message) (seq commands))
|
||||
[react/touchable-highlight
|
||||
{:on-press #(do (re-frame/dispatch [:set-chat-input-text constants/command-char])
|
||||
(re-frame/dispatch [:chat-input-focus :input-ref]))
|
||||
{:on-press #(re-frame/dispatch [:chat.ui/set-command-prefix])
|
||||
:accessibility-label :chat-commands-button}
|
||||
[react/view
|
||||
[vi/icon :icons/input-commands {:container-style style/input-commands-icon
|
||||
:color :dark}]]])))
|
||||
[vector-icons/icon :icons/input-commands {:container-style style/input-commands-icon
|
||||
:color :dark}]]])))
|
||||
|
||||
(defview reply-message [from message-text]
|
||||
(letsubs [username [:get-contact-name-by-identity from]
|
||||
current-public-key [:get-current-public-key]]
|
||||
[react/view {:style style/reply-message-content}
|
||||
[react/text {:style style/reply-message-author} (or (and (= from current-public-key)
|
||||
(i18n/label :t/You))
|
||||
username
|
||||
(gfycat/generate-gfy from))]
|
||||
[react/text {:style message-style/style-message-text} message-text]]))
|
||||
|
||||
(defview reply-message-view []
|
||||
(letsubs [{:keys [content from] :as message} [:get-reply-message]]
|
||||
(when message
|
||||
[react/view {:style style/reply-message-container}
|
||||
[react/view {:style style/reply-message}
|
||||
[photos/member-photo from]
|
||||
[reply-message from (:text content)]]
|
||||
[react/touchable-highlight
|
||||
{:style style/cancel-reply-highlight
|
||||
:on-press #(re-frame/dispatch [:chat.ui/cancel-message-reply])
|
||||
:accessibility-label :cancel-message-reply}
|
||||
[react/view {:style style/cancel-reply-container}
|
||||
[vector-icons/icon :icons/close {:container-style style/cancel-reply-icon
|
||||
:color colors/white}]]]])))
|
||||
|
||||
(defview input-container []
|
||||
(letsubs [margin [:chat-input-margin]
|
||||
@ -112,7 +139,8 @@
|
||||
(.-layout)
|
||||
(.-height))]
|
||||
(when (> h 0)
|
||||
(re-frame/dispatch [:set-chat-ui-props {:input-height h}])))}
|
||||
(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-height h}])))}
|
||||
[reply-message-view]
|
||||
[react/view {:style style/input-container}
|
||||
[input-view {:single-line-input? single-line-input?}]
|
||||
(if (string/blank? input-text)
|
||||
|
@ -30,7 +30,7 @@
|
||||
(when (and (sendable? input-text network-status)
|
||||
(or (not command-completion)
|
||||
(#{:complete :less-than-needed} command-completion)))
|
||||
[react/touchable-highlight {:on-press #(re-frame/dispatch [:send-current-message])}
|
||||
[react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/send-current-message])}
|
||||
(let [spin (.interpolate spin-value (clj->js {:inputRange [0 1]
|
||||
:outputRange ["0deg" "90deg"]}))]
|
||||
[react/animated-view
|
||||
|
@ -26,7 +26,7 @@
|
||||
(map-indexed
|
||||
(fn [i {:keys [type] :as command}]
|
||||
^{:key i}
|
||||
[suggestion-item {:on-press #(re-frame/dispatch [:select-chat-input-command command nil])
|
||||
[suggestion-item {:on-press #(re-frame/dispatch [:chat.ui/select-chat-input-command command nil])
|
||||
:name (commands/command-name type)
|
||||
:description (commands/command-description type)
|
||||
:last? (= i (dec (count available-commands)))
|
||||
|
@ -57,7 +57,7 @@
|
||||
|
||||
(defview message-timestamp [t justify-timestamp? outgoing command? content]
|
||||
(when-not command?
|
||||
(let [rtl? (right-to-left-text? content)]
|
||||
(let [rtl? (right-to-left-text? (:text content))]
|
||||
[react/text {:style (style/message-timestamp-text justify-timestamp? outgoing rtl?)} t])))
|
||||
|
||||
(defn message-view
|
||||
@ -165,12 +165,25 @@
|
||||
:on-press on-press}
|
||||
(i18n/label (if @collapsed? :show-more :show-less))])
|
||||
|
||||
(defview quoted-message [{:keys [from text]} outgoing current-public-key]
|
||||
(letsubs [username [:get-contact-name-by-identity from]]
|
||||
[react/view {:style (style/quoted-message-container outgoing)}
|
||||
[react/view {:style style/quoted-message-author-container}
|
||||
[vector-icons/icon :icons/reply {:color (if outgoing colors/wild-blue-yonder colors/gray)}]
|
||||
[react/text {:style (style/quoted-message-author outgoing)} (or (and (= from current-public-key)
|
||||
(i18n/label :t/You))
|
||||
username
|
||||
(gfycat/generate-gfy from))]]
|
||||
[react/text {:style (style/quoted-message-text outgoing)
|
||||
:number-of-lines 5}
|
||||
text]]))
|
||||
|
||||
(defn text-message
|
||||
[{:keys [content timestamp-str group-chat outgoing] :as message}]
|
||||
[{:keys [content timestamp-str group-chat outgoing current-public-key] :as message}]
|
||||
[message-view message
|
||||
(let [parsed-text (cached-parse-text content :browser.ui/message-link-pressed)
|
||||
(let [parsed-text (cached-parse-text (:text content) :browser.ui/message-link-pressed)
|
||||
ref (reagent/atom nil)
|
||||
collapsible? (should-collapse? content group-chat)
|
||||
collapsible? (should-collapse? (:text content) group-chat)
|
||||
collapsed? (reagent/atom collapsible?)
|
||||
on-press (when collapsible?
|
||||
#(do
|
||||
@ -180,6 +193,8 @@
|
||||
number-of-lines)}))
|
||||
(reset! collapsed? (not @collapsed?))))]
|
||||
[react/view
|
||||
(when (:response-to content)
|
||||
[quoted-message (:response-to content) outgoing current-public-key])
|
||||
[react/text {:style (style/text-message collapsible?)
|
||||
:number-of-lines (when collapsible? number-of-lines)
|
||||
:ref (partial reset! ref)}
|
||||
@ -192,7 +207,7 @@
|
||||
(defn emoji-message
|
||||
[{:keys [content] :as message}]
|
||||
[message-view message
|
||||
[react/text {:style (style/emoji-message message)} content]])
|
||||
[react/text {:style (style/emoji-message message)} (:text content)]])
|
||||
|
||||
(defmulti message-content (fn [_ message _] (message :content-type)))
|
||||
|
||||
@ -200,10 +215,6 @@
|
||||
[wrapper message]
|
||||
[wrapper message [text-message message]])
|
||||
|
||||
(defmethod message-content constants/content-type-log-message
|
||||
[wrapper message]
|
||||
[wrapper message [text-message message]])
|
||||
|
||||
(defmethod message-content constants/content-type-status
|
||||
[_ _]
|
||||
[message-content-status])
|
||||
@ -248,9 +259,9 @@
|
||||
(if (or seen-by-everyone (zero? delivery-statuses-count))
|
||||
[text-status (or seen-by-everyone outgoing-status)]
|
||||
[react/touchable-highlight
|
||||
{:on-press #(re-frame/dispatch [:show-message-details {:message-status outgoing-status
|
||||
:user-statuses delivery-statuses
|
||||
:participants participants}])}
|
||||
{:on-press #(re-frame/dispatch [:chat.ui/show-message-details {:message-status outgoing-status
|
||||
:user-statuses delivery-statuses
|
||||
:participants participants}])}
|
||||
[react/view style/delivery-view
|
||||
(for [[whisper-identity] (take 3 delivery-statuses)]
|
||||
^{:key whisper-identity}
|
||||
@ -272,13 +283,13 @@
|
||||
[react/touchable-highlight {:on-press (fn [] (if platform/ios?
|
||||
(action-sheet/show {:title (i18n/label :message-not-sent)
|
||||
:options [{:label (i18n/label :resend-message)
|
||||
:action #(re-frame/dispatch [:resend-message chat-id message-id])}
|
||||
:action #(re-frame/dispatch [:chat.ui/resend-message chat-id message-id])}
|
||||
{:label (i18n/label :delete-message)
|
||||
:destructive? true
|
||||
:action #(re-frame/dispatch [:delete-message chat-id message-id])}]})
|
||||
:action #(re-frame/dispatch [:chat.ui/delete-message chat-id message-id])}]})
|
||||
(re-frame/dispatch
|
||||
[:show-message-options {:chat-id chat-id
|
||||
:message-id message-id}])))}
|
||||
[:chat.ui/show-message-options {:chat-id chat-id
|
||||
:message-id message-id}])))}
|
||||
[react/view style/not-sent-view
|
||||
[react/text {:style style/not-sent-text}
|
||||
(i18n/message-status-label (if platform/desktop?
|
||||
@ -333,7 +344,7 @@
|
||||
(when display-photo?
|
||||
[react/view style/message-author
|
||||
(when last-in-group?
|
||||
[react/touchable-highlight {:on-press #(when-not modal? (re-frame/dispatch [:show-profile from]))}
|
||||
[react/touchable-highlight {:on-press #(when-not modal? (re-frame/dispatch [:chat.ui/show-profile from]))}
|
||||
[react/view
|
||||
[photos/member-photo from]]])])
|
||||
[react/view (style/group-message-view outgoing)
|
||||
@ -344,13 +355,13 @@
|
||||
[react/view (style/delivery-status outgoing)
|
||||
[message-delivery-status message]]])
|
||||
|
||||
(defn chat-message [{:keys [outgoing group-chat modal? current-public-key content-type content] :as message}]
|
||||
(defn chat-message [{:keys [message-id outgoing group-chat modal? current-public-key content-type content] :as message}]
|
||||
[react/view
|
||||
[react/touchable-highlight {:on-press (fn [_]
|
||||
(re-frame/dispatch [:set-chat-ui-props {:messages-focused? true}])
|
||||
(re-frame/dispatch [:chat.ui/set-chat-ui-props {:messages-focused? true}])
|
||||
(react/dismiss-keyboard!))
|
||||
:on-long-press #(when (= content-type constants/text-content-type)
|
||||
(list-selection/share content (i18n/label :t/message)))}
|
||||
(list-selection/chat-message message-id (:text content) (i18n/label :t/message)))}
|
||||
[react/view {:accessibility-label :chat-item}
|
||||
(let [incoming-group (and group-chat (not outgoing))]
|
||||
[message-content message-body (merge message
|
||||
|
@ -19,7 +19,7 @@
|
||||
|
||||
(defn view []
|
||||
(let [{:keys [chat-id message-id]} @(re-frame/subscribe [:get-current-chat-ui-prop :message-options])
|
||||
close-message-options-fn #(re-frame/dispatch [:set-chat-ui-props {:show-message-options? false}])]
|
||||
close-message-options-fn #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:show-message-options? false}])]
|
||||
[bottom-info/overlay {:on-click-outside close-message-options-fn}
|
||||
[bottom-info/container (* styles/item-height 2)
|
||||
[react/view
|
||||
@ -29,10 +29,10 @@
|
||||
:icon :icons/refresh
|
||||
:on-press #(do
|
||||
(close-message-options-fn)
|
||||
(re-frame/dispatch [:resend-message chat-id message-id]))}]
|
||||
(re-frame/dispatch [:chat.ui/resend-message chat-id message-id]))}]
|
||||
[action-item {:label :delete-message
|
||||
:icon :icons/delete
|
||||
:style {:color colors/red}
|
||||
:on-press #(do
|
||||
(close-message-options-fn)
|
||||
(re-frame/dispatch [:delete-message chat-id message-id]))}]]]]))
|
||||
(re-frame/dispatch [:chat.ui/delete-message chat-id message-id]))}]]]]))
|
||||
|
@ -15,6 +15,51 @@
|
||||
:border-top-color colors/gray-light
|
||||
:elevation 2})
|
||||
|
||||
(def reply-message
|
||||
{:flex-direction :row
|
||||
:align-items :flex-start
|
||||
:border-width 1
|
||||
:border-radius 10
|
||||
:border-color colors/gray-light
|
||||
:padding-top 10
|
||||
:padding-bottom 10
|
||||
:padding-right 14
|
||||
:padding-left 7
|
||||
:margin-top 12
|
||||
:margin-left 12
|
||||
:margin-right 12})
|
||||
|
||||
(def reply-message-content
|
||||
{:flex-direction :column
|
||||
:padding-left 7
|
||||
:margin-right 30
|
||||
:max-height 140
|
||||
:overflow :scroll})
|
||||
|
||||
(def reply-message-author
|
||||
{:font-size 12
|
||||
:color colors/gray
|
||||
:padding-bottom 6})
|
||||
|
||||
(def reply-message-container
|
||||
{:flex-direction :column-reverse})
|
||||
|
||||
(def cancel-reply-highlight
|
||||
{:position :absolute
|
||||
:z-index 5
|
||||
:top 0
|
||||
:right 0
|
||||
:height 26})
|
||||
|
||||
(def cancel-reply-container
|
||||
{:flex-direction :row
|
||||
:justify-content :flex-end
|
||||
:margin-right 12})
|
||||
|
||||
(def cancel-reply-icon
|
||||
{:background-color colors/gray
|
||||
:border-radius 12})
|
||||
|
||||
(def input-container
|
||||
{:flex-direction :row
|
||||
:align-items :flex-end
|
||||
|
@ -200,3 +200,31 @@
|
||||
{:font-size 12
|
||||
:padding-top 6
|
||||
:color colors/gray})
|
||||
|
||||
(defn quoted-message-container [outgoing]
|
||||
{:margin-bottom 6
|
||||
:padding-bottom 6
|
||||
:border-bottom-color (if outgoing
|
||||
colors/blue-light
|
||||
colors/gray-lighter)
|
||||
:border-bottom-width 2
|
||||
:border-bottom-left-radius 2
|
||||
:border-bottom-right-radius 2})
|
||||
|
||||
(def quoted-message-author-container
|
||||
{:flex-direction :row
|
||||
:align-items :center
|
||||
:justify-content :flex-start})
|
||||
|
||||
(defn quoted-message-author [outgoing]
|
||||
{:font-size 12
|
||||
:padding-bottom 5
|
||||
:padding-top 4
|
||||
:color (if outgoing
|
||||
colors/wild-blue-yonder
|
||||
colors/gray)})
|
||||
|
||||
(defn quoted-message-text [outgoing]
|
||||
{:color (if outgoing
|
||||
colors/wild-blue-yonder
|
||||
colors/gray)})
|
||||
|
@ -95,7 +95,7 @@
|
||||
:preview [react/view style/message-view-preview]}
|
||||
[react/touchable-without-feedback
|
||||
{:on-press (fn [_]
|
||||
(re-frame/dispatch [:set-chat-ui-props {:messages-focused? true}])
|
||||
(re-frame/dispatch [:chat.ui/set-chat-ui-props {:messages-focused? true}])
|
||||
(react/dismiss-keyboard!))}
|
||||
[react/animated-view {:style (style/message-view-animated opacity)}
|
||||
message-view]]]))
|
||||
@ -118,8 +118,8 @@
|
||||
(letsubs [messages [:get-current-chat-messages-stream]
|
||||
chat [:get-current-chat]
|
||||
current-public-key [:get-current-public-key]]
|
||||
{:component-did-mount #(re-frame/dispatch [:set-chat-ui-props {:messages-focused? true
|
||||
:input-focused? false}])}
|
||||
{:component-did-mount #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:messages-focused? true
|
||||
:input-focused? false}])}
|
||||
(if (empty? messages)
|
||||
[empty-chat-container chat]
|
||||
[list/flat-list {:data messages
|
||||
@ -130,7 +130,7 @@
|
||||
:current-public-key current-public-key
|
||||
:row message}])
|
||||
:inverted true
|
||||
:onEndReached #(re-frame/dispatch [:load-more-messages])
|
||||
:onEndReached #(re-frame/dispatch [:chat.ui/load-more-messages])
|
||||
:enableEmptySections true
|
||||
:keyboardShouldPersistTaps :handled}])))
|
||||
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:add-contact
|
||||
[(re-frame/inject-cofx :random-id)]
|
||||
[(re-frame/inject-cofx :random-id-generator)]
|
||||
(fn [cofx [_ whisper-id]]
|
||||
(models.contact/add-contact cofx whisper-id)))
|
||||
|
||||
@ -41,7 +41,7 @@
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:set-contact-identity-from-qr
|
||||
[(re-frame/inject-cofx :random-id)]
|
||||
[(re-frame/inject-cofx :random-id-generator)]
|
||||
(fn [{:keys [db] :as cofx} [_ _ contact-identity]]
|
||||
(let [current-account (:account/account db)
|
||||
fx {:db (assoc db :contacts/new-identity contact-identity)}
|
||||
@ -63,13 +63,13 @@
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:open-chat-with-contact
|
||||
[(re-frame/inject-cofx :random-id)]
|
||||
[(re-frame/inject-cofx :random-id-generator)]
|
||||
(fn [cofx [_ {:keys [whisper-identity]}]]
|
||||
(add-contact-and-open-chat cofx whisper-identity)))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:add-contact-handler
|
||||
[(re-frame/inject-cofx :random-id)]
|
||||
[(re-frame/inject-cofx :random-id-generator)]
|
||||
(fn [{{:contacts/keys [new-identity]} :db :as cofx} _]
|
||||
(when (seq new-identity)
|
||||
(add-contact-and-open-chat cofx new-identity))))
|
||||
|
@ -119,7 +119,7 @@
|
||||
:on-press #(when-not topic-error
|
||||
(do
|
||||
(re-frame/dispatch [:set :public-group-topic nil])
|
||||
(re-frame/dispatch [:create-new-public-chat topic])))}
|
||||
(re-frame/dispatch [:chat.ui/start-public-chat topic])))}
|
||||
[react/view {:style (styles/add-contact-button disable?)}
|
||||
[react/text {:style (styles/add-contact-button-text disable?)}
|
||||
(i18n/label :new-public-group-chat)]]]])
|
||||
@ -131,7 +131,7 @@
|
||||
^{:key topic}
|
||||
[react/touchable-highlight {:on-press #(do
|
||||
(re-frame/dispatch [:set :public-group-topic nil])
|
||||
(re-frame/dispatch [:create-new-public-chat topic]))}
|
||||
(re-frame/dispatch [:chat.ui/start-public-chat topic]))}
|
||||
[react/view {:style styles/suggested-contact-view}
|
||||
[react/view {:style styles/suggested-topic-image}
|
||||
[react/text {:style styles/suggested-topic-text} (string/capitalize (first topic))]]
|
||||
|
@ -63,7 +63,7 @@
|
||||
:on-press #(re-frame/dispatch [:chat.ui/clear-history-pressed])}
|
||||
(i18n/label :t/clear-history)]
|
||||
[react/text {:style (styles/profile-actions-text colors/black)
|
||||
:on-press #(re-frame/dispatch [:chat.ui/delete-chat-pressed chat-id])}
|
||||
:on-press #(re-frame/dispatch [:chat.ui/remove-chat-pressed chat-id])}
|
||||
(i18n/label :t/delete-chat)]]]))
|
||||
|
||||
(views/defview message-author-name [{:keys [outgoing from] :as message}]
|
||||
@ -177,14 +177,14 @@
|
||||
y (.-y (.-contentOffset ne))]
|
||||
(when (<= y 0)
|
||||
(when @scroll-timer (js/clearTimeout @scroll-timer))
|
||||
(reset! scroll-timer (js/setTimeout #(re-frame/dispatch [:load-more-messages]) 300)))
|
||||
(reset! scroll-timer (js/setTimeout #(re-frame/dispatch [:chat.ui/load-more-messages]) 300)))
|
||||
(reset! scroll-height (+ y (.-height (.-layoutMeasurement ne))))))
|
||||
:ref #(reset! scroll-ref %)}
|
||||
[react/view
|
||||
(doall
|
||||
(for [[index {:keys [from content message-id type value] :as message-obj}] (map-indexed vector messages)]
|
||||
^{:key message-obj}
|
||||
[message content (= from current-public-key)
|
||||
[message (:text content) (= from current-public-key)
|
||||
(assoc message-obj :group-chat group-chat
|
||||
:current-public-key current-public-key)]))]]
|
||||
[connectivity/error-view]])))
|
||||
@ -216,17 +216,17 @@
|
||||
(when should-send
|
||||
(.clear @inp-ref)
|
||||
(.focus @inp-ref)
|
||||
(re-frame/dispatch [:send-current-message]))))
|
||||
(re-frame/dispatch [:chat.ui/send-current-message]))))
|
||||
:on-change (fn [e]
|
||||
(let [native-event (.-nativeEvent e)
|
||||
text (.-text native-event)]
|
||||
(reagent/set-state component {:empty? (= "" text)})
|
||||
(re-frame/dispatch [:set-chat-input-text text])))}]
|
||||
(re-frame/dispatch [:chat.ui/set-chat-input-text text])))}]
|
||||
[react/touchable-highlight {:style styles/send-button
|
||||
:on-press (fn []
|
||||
(.clear @inp-ref)
|
||||
(.focus @inp-ref)
|
||||
(re-frame/dispatch [:send-current-message]))}
|
||||
(re-frame/dispatch [:chat.ui/send-current-message]))}
|
||||
[react/view {:style (styles/send-icon empty?)}
|
||||
[icons/icon :icons/arrow-left {:style (styles/send-icon-arrow empty?)}]]]])))
|
||||
|
||||
|
@ -57,12 +57,12 @@
|
||||
:style styles/chat-last-message}
|
||||
(if (= constants/content-type-command (:content-type last-message))
|
||||
[chat-item/command-short-preview last-message]
|
||||
(or (:content last-message) (i18n/label :no-messages-yet)))]]
|
||||
(or (get-in last-message [:content :text]) (i18n/label :no-messages-yet)))]]
|
||||
[react/view {:style styles/timestamp}
|
||||
[chat-item/message-timestamp (:timestamp last-message)]]])))
|
||||
|
||||
(defn chat-list-item [[chat-id chat]]
|
||||
[react/touchable-highlight {:on-press #(re-frame/dispatch [:navigate-to-chat chat-id])}
|
||||
[react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/navigate-to-chat chat-id])}
|
||||
[chat-list-item-inner-view (assoc chat :chat-id chat-id)]])
|
||||
|
||||
(views/defview chat-list-view []
|
||||
|
@ -1,6 +1,5 @@
|
||||
(ns status-im.ui.screens.events
|
||||
(:require status-im.events
|
||||
status-im.chat.events
|
||||
status-im.dev-server.events
|
||||
status-im.ui.screens.add-new.events
|
||||
status-im.ui.screens.add-new.new-chat.events
|
||||
|
@ -20,9 +20,11 @@
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:add-new-group-chat-participants
|
||||
[(re-frame/inject-cofx :random-id)]
|
||||
(fn [{{:keys [current-chat-id selected-participants] :as db} :db now :now message-id :random-id :as cofx} _]
|
||||
(let [participants (concat (get-in db [:chats current-chat-id :contacts]) selected-participants)
|
||||
[(re-frame/inject-cofx :random-id-generator)]
|
||||
(fn [{{:keys [current-chat-id selected-participants] :as db} :db
|
||||
now :now random-id-generator :random-id-generator :as cofx} _]
|
||||
(let [message-id (random-id-generator)
|
||||
participants (concat (get-in db [:chats current-chat-id :contacts]) selected-participants)
|
||||
contacts (:contacts/contacts db)
|
||||
added-participants-names (map #(get-in contacts [% :name]) selected-participants)]
|
||||
(fx/merge cofx
|
||||
@ -37,9 +39,11 @@
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:remove-group-chat-participants
|
||||
[(re-frame/inject-cofx :random-id)]
|
||||
(fn [{{:keys [current-chat-id] :as db} :db now :now message-id :random-id :as cofx} [_ removed-participants]]
|
||||
(let [participants (remove removed-participants (get-in db [:chats current-chat-id :contacts]))
|
||||
[(re-frame/inject-cofx :random-id-generator)]
|
||||
(fn [{{:keys [current-chat-id] :as db} :db now :now random-id-generator :random-id-generator :as cofx}
|
||||
[_ removed-participants]]
|
||||
(let [message-id (random-id-generator)
|
||||
participants (remove removed-participants (get-in db [:chats current-chat-id :contacts]))
|
||||
contacts (:contacts/contacts db)
|
||||
removed-participants-names (map #(get-in contacts [% :name]) removed-participants)]
|
||||
(fx/merge cofx
|
||||
|
@ -33,7 +33,7 @@
|
||||
toolbar/default-nav-back
|
||||
[toolbar/content-title (i18n/label :t/group-chat)]
|
||||
(when save-btn-enabled?
|
||||
(let [handler #(re-frame/dispatch [:create-new-group-chat-and-open group-name])]
|
||||
(let [handler #(re-frame/dispatch [:chat.ui/start-group-chat group-name])]
|
||||
(if platform/android?
|
||||
[toolbar/actions [{:icon :icons/ok
|
||||
:icon-opts {:color :blue
|
||||
|
@ -40,7 +40,7 @@
|
||||
(views/defview home-list-item [[home-item-id home-item]]
|
||||
(views/letsubs [swiped? [:delete-swipe-position home-item-id]]
|
||||
(let [delete-action (if (:chat-id home-item)
|
||||
:remove-chat-and-navigate-home
|
||||
:chat.ui/remove-chat
|
||||
:browser.ui/remove-browser-pressed)
|
||||
inner-item-view (if (:chat-id home-item)
|
||||
inner-item/home-list-chat-item-inner-view
|
||||
|
@ -38,11 +38,11 @@
|
||||
[react/text {:style styles/last-message-text}
|
||||
""]
|
||||
|
||||
(:content content)
|
||||
(:text content)
|
||||
[react/text {:style styles/last-message-text
|
||||
:number-of-lines 1
|
||||
:accessibility-label :chat-message-text}
|
||||
(:content content)]
|
||||
(:text content)]
|
||||
|
||||
(contains? #{constants/content-type-command
|
||||
constants/content-type-command-request}
|
||||
@ -91,7 +91,7 @@
|
||||
(letsubs [last-message [:get-last-message chat-id]
|
||||
chat-name [:get-chat-name chat-id]]
|
||||
(let [truncated-chat-name (utils/truncate-str chat-name 30)]
|
||||
[react/touchable-highlight {:on-press #(re-frame/dispatch [:navigate-to-chat chat-id])}
|
||||
[react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/navigate-to-chat chat-id])}
|
||||
[react/view styles/chat-container
|
||||
[react/view styles/chat-icon-container
|
||||
[chat-icon.screen/chat-icon-view-chat-list chat-id group-chat truncated-chat-name color online false]]
|
||||
|
@ -51,7 +51,7 @@
|
||||
:accessibility-label :delete-chat-button}]))
|
||||
|
||||
(defn contact-actions [contact]
|
||||
[{:action #(re-frame/dispatch [:show-profile (:whisper-identity contact)])
|
||||
[{:action #(re-frame/dispatch [:chat.ui/show-profile (:whisper-identity contact)])
|
||||
:label (i18n/label :t/view-profile)}
|
||||
#_{:action #(re-frame/dispatch [:remove-group-chat-participants #{(:whisper-identity contact)}])
|
||||
:label (i18n/label :t/remove-from-chat)}])
|
||||
@ -66,7 +66,7 @@
|
||||
:accessibility-label :member-item
|
||||
:inner-props {:accessibility-label :member-name-text}
|
||||
:on-press (when (not= whisper-identity current-user-identity)
|
||||
#(re-frame/dispatch [:show-profile whisper-identity]))}]])
|
||||
#(re-frame/dispatch [:chat.ui/show-profile whisper-identity]))}]])
|
||||
|
||||
(defview chat-group-contacts-view [admin? group-admin-identity current-user-identity]
|
||||
(letsubs [contacts [:get-current-chat-contacts]]
|
||||
|
@ -154,7 +154,7 @@
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:send-transaction-message
|
||||
(concat models.message/send-interceptors
|
||||
(concat [(re-frame/inject-cofx :random-id-generator)]
|
||||
navigation/navigation-interceptors)
|
||||
(fn [{:keys [db] :as cofx} [_ chat-id params]]
|
||||
;;NOTE(goranjovic): we want to send the payment message only when we have a whisper id
|
||||
|
@ -28,11 +28,6 @@
|
||||
(assoc coeffects :random-guid-generator guid)))
|
||||
|
||||
(re-frame/reg-cofx
|
||||
:random-id
|
||||
:random-id-generator
|
||||
(fn [coeffects _]
|
||||
(assoc coeffects :random-id (id))))
|
||||
|
||||
(re-frame/reg-cofx
|
||||
:random-id-seq
|
||||
(fn [coeffects _]
|
||||
(assoc coeffects :random-id-seq (repeatedly id))))
|
||||
(assoc coeffects :random-id-generator id)))
|
||||
|
@ -2,7 +2,7 @@
|
||||
(:require [cljs.spec.alpha :as spec]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.accounts.db :as accounts.db]
|
||||
[status-im.chat.events :as chat.events]
|
||||
[status-im.chat.models :as chat]
|
||||
[status-im.ui.components.list-selection :as list-selection]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.screens.add-new.new-chat.db :as new-chat.db]
|
||||
@ -42,13 +42,13 @@
|
||||
|
||||
(fx/defn handle-public-chat [cofx public-chat]
|
||||
(log/info "universal-links: handling public chat " public-chat)
|
||||
(chat.events/create-new-public-chat cofx public-chat false))
|
||||
(chat/start-public-chat cofx public-chat false))
|
||||
|
||||
(fx/defn handle-view-profile [{:keys [db] :as cofx} profile-id]
|
||||
(log/info "universal-links: handling view profile" profile-id)
|
||||
(if (new-chat.db/own-whisper-identity? db profile-id)
|
||||
(navigation/navigate-to-cofx cofx :my-profile nil)
|
||||
(chat.events/show-profile cofx profile-id)))
|
||||
(navigation/navigate-to-cofx (assoc-in cofx [:db :contacts/identity] profile-id) :profile nil)))
|
||||
|
||||
(fx/defn handle-extension [cofx url]
|
||||
(log/info "universal-links: handling url profile" url)
|
||||
|
@ -71,12 +71,12 @@
|
||||
(get-in fx [:db :id->command
|
||||
(core/command-id TestCommandInstance) :type]))))
|
||||
(testing "Suggestions for parameters are injected with correct selection events"
|
||||
(is (= [:set-command-parameter false 0 "first-value"]
|
||||
(is (= [:chat.ui/set-command-parameter false 0 "first-value"]
|
||||
((get-in fx [:db :id->command
|
||||
(core/command-id TestCommandInstance) :params
|
||||
0 :suggestions])
|
||||
"first-value")))
|
||||
(is (= [:set-command-parameter true 2 "last-value"]
|
||||
(is (= [:chat.ui/set-command-parameter true 2 "last-value"]
|
||||
((get-in fx [:db :id->command
|
||||
(core/command-id TestCommandInstance) :params
|
||||
2 :suggestions])
|
||||
|
@ -4,6 +4,25 @@
|
||||
[status-im.chat.commands.core :as core]
|
||||
[status-im.chat.commands.input :as input]))
|
||||
|
||||
(deftest starts-as-command?-test
|
||||
(is (not (input/starts-as-command? nil)))
|
||||
(is (not (input/command-ends-with-space? "")))
|
||||
(is (not (input/command-ends-with-space? "word1 word2 word3")))
|
||||
(is (input/command-ends-with-space? "word1 word2 ")))
|
||||
|
||||
(deftest split-command-args-test
|
||||
(is (nil? (input/split-command-args nil)))
|
||||
(is (= [""] (input/split-command-args "")))
|
||||
(is (= ["@browse" "google.com"] (input/split-command-args "@browse google.com")))
|
||||
(is (= ["@browse" "google.com"] (input/split-command-args " @browse google.com ")))
|
||||
(is (= ["/send" "1.0" "John Doe"] (input/split-command-args "/send 1.0 \"John Doe\"")))
|
||||
(is (= ["/send" "1.0" "John Doe"] (input/split-command-args "/send 1.0 \"John Doe\" "))))
|
||||
|
||||
(deftest join-command-args-test
|
||||
(is (nil? (input/join-command-args nil)))
|
||||
(is (= "" (input/join-command-args [""])))
|
||||
(is (= "/send 1.0 \"John Doe\"" (input/join-command-args ["/send" "1.0" "John Doe"]))))
|
||||
|
||||
(deftest selected-chat-command-test
|
||||
(let [fx (core/load-commands {:db {}} #{test-core/TestCommandInstance test-core/AnotherTestCommandInstance})
|
||||
commands (core/chat-commands (get-in fx [:db :id->command])
|
||||
|
@ -1,10 +0,0 @@
|
||||
(ns status-im.test.chat.events
|
||||
(:require [cljs.test :refer [deftest is testing]]
|
||||
[status-im.chat.events :as chat-events]))
|
||||
|
||||
(deftest show-profile-test
|
||||
(testing "Dafault behaviour: navigate to profile"
|
||||
(let [{:keys [db]} (chat-events/show-profile
|
||||
{:db {:navigation-stack '(:home)}}
|
||||
"a")]
|
||||
(is (= "a" (:contacts/identity db))))))
|
@ -11,25 +11,6 @@
|
||||
(is (= "test" (input/text->emoji "test")))
|
||||
(is (= "word1 \uD83D\uDC4D word2" (input/text->emoji "word1 :+1: word2"))))
|
||||
|
||||
(deftest starts-as-command?
|
||||
(is (not (input/starts-as-command? nil)))
|
||||
(is (not (input/text-ends-with-space? "")))
|
||||
(is (not (input/text-ends-with-space? "word1 word2 word3")))
|
||||
(is (input/text-ends-with-space? "word1 word2 ")))
|
||||
|
||||
(deftest split-command-args
|
||||
(is (nil? (input/split-command-args nil)))
|
||||
(is (= [""] (input/split-command-args "")))
|
||||
(is (= ["@browse" "google.com"] (input/split-command-args "@browse google.com")))
|
||||
(is (= ["@browse" "google.com"] (input/split-command-args " @browse google.com ")))
|
||||
(is (= ["/send" "1.0" "John Doe"] (input/split-command-args "/send 1.0 \"John Doe\"")))
|
||||
(is (= ["/send" "1.0" "John Doe"] (input/split-command-args "/send 1.0 \"John Doe\" "))))
|
||||
|
||||
(deftest join-command-args
|
||||
(is (nil? (input/join-command-args nil)))
|
||||
(is (= "" (input/join-command-args [""])))
|
||||
(is (= "/send 1.0 \"John Doe\"" (input/join-command-args ["/send" "1.0" "John Doe"]))))
|
||||
|
||||
(deftest process-cooldown-fx
|
||||
(let [db {:current-chat-id "chat"
|
||||
:chats {"chat" {:public? true}}
|
||||
@ -61,7 +42,7 @@
|
||||
:chat/spam-messages-frequency 0
|
||||
:chat/cooldown-enabled? true)
|
||||
:show-cooldown-warning nil
|
||||
:dispatch-later [{:dispatch [:disable-cooldown]
|
||||
:dispatch-later [{:dispatch [:chat/disable-cooldown]
|
||||
:ms (constants/cooldown-periods-ms 1)}]}
|
||||
actual (input/process-cooldown {:db db})]
|
||||
(is (= expected actual))))
|
||||
@ -77,7 +58,7 @@
|
||||
:chat/spam-messages-frequency 0
|
||||
:chat/cooldown-enabled? true)
|
||||
:show-cooldown-warning nil
|
||||
:dispatch-later [{:dispatch [:disable-cooldown]
|
||||
:dispatch-later [{:dispatch [:chat/disable-cooldown]
|
||||
:ms (constants/cooldown-periods-ms 2)}]}
|
||||
actual (input/process-cooldown {:db db})]
|
||||
(is (= expected actual))))
|
||||
@ -93,7 +74,7 @@
|
||||
:chat/spam-messages-frequency 0
|
||||
:chat/cooldown-enabled? true)
|
||||
:show-cooldown-warning nil
|
||||
:dispatch-later [{:dispatch [:disable-cooldown]
|
||||
:dispatch-later [{:dispatch [:chat/disable-cooldown]
|
||||
:ms (constants/cooldown-periods-ms 3)}]}
|
||||
actual (input/process-cooldown {:db db})]
|
||||
(is (= expected actual)))))))
|
||||
|
@ -182,7 +182,7 @@
|
||||
|
||||
(deftest delete-mailserver
|
||||
(testing "the user is not connected to the mailserver"
|
||||
(let [cofx {:random-id "random-id"
|
||||
(let [cofx {:random-id-generator (constantly "random-id")
|
||||
:db {:inbox/wnodes {:eth.beta {"a" {:id "a"
|
||||
:name "old-name"
|
||||
:user-defined true
|
||||
@ -193,7 +193,7 @@
|
||||
(testing "it stores it in the db"
|
||||
(is (= 1 (count (:data-store/tx actual)))))))
|
||||
(testing "the mailserver is not user-defined"
|
||||
(let [cofx {:random-id "random-id"
|
||||
(let [cofx {:random-id-generator (constantly "random-id")
|
||||
:db {:inbox/wnodes {:eth.beta {"a" {:id "a"
|
||||
:name "old-name"
|
||||
:address "enode://old-id:old-password@url:port"}}}}}
|
||||
@ -201,7 +201,7 @@
|
||||
(testing "it does not delete the mailserver"
|
||||
(is (= {:dispatch [:navigate-back]} actual)))))
|
||||
(testing "the user is connected to the mailserver"
|
||||
(let [cofx {:random-id "random-id"
|
||||
(let [cofx {:random-id-generator (constantly "random-id")
|
||||
:db {:inbox/wnodes {:eth.beta {"a" {:id "a"
|
||||
:name "old-name"
|
||||
:address "enode://old-id:old-password@url:port"}}}}}
|
||||
@ -211,7 +211,7 @@
|
||||
|
||||
(deftest upsert-mailserver
|
||||
(testing "new mailserver"
|
||||
(let [cofx {:random-id "random-id"
|
||||
(let [cofx {:random-id-generator (constantly "random-id")
|
||||
:db {:mailservers/manage {:name {:value "test-name"}
|
||||
:url {:value "enode://test-id:test-password@url:port"}}
|
||||
|
||||
@ -231,7 +231,7 @@
|
||||
(testing "it stores it in the db"
|
||||
(is (= 1 (count (:data-store/tx actual)))))))
|
||||
(testing "existing mailserver"
|
||||
(let [cofx {:random-id "random-id"
|
||||
(let [cofx {:random-id-generator (constantly "random-id")
|
||||
:db {:mailservers/manage {:id {:value :a}
|
||||
:name {:value "new-name"}
|
||||
:url {:value "enode://new-id:new-password@url:port"}}
|
||||
|
@ -16,7 +16,7 @@
|
||||
:chain "mainnet_rpc"
|
||||
:id "someid"}}}
|
||||
actual (model/upsert
|
||||
{:random-id "some-id"
|
||||
{:random-id-generator (constantly "some-id")
|
||||
:db {:bootnodes/manage new-bootnode
|
||||
:network "mainnet_rpc"
|
||||
:account/account {}}})]
|
||||
@ -30,7 +30,7 @@
|
||||
:chain "mainnet_rpc"
|
||||
:id "a"}}}
|
||||
actual (model/upsert
|
||||
{:random-id "some-id"
|
||||
{:random-id-generator (constantly "some-id")
|
||||
:db {:bootnodes/manage new-bootnode
|
||||
:network "mainnet_rpc"
|
||||
:account/account {:bootnodes
|
||||
|
@ -96,8 +96,9 @@
|
||||
|
||||
(deftest save
|
||||
(testing "it does not save a network with an invalid url"
|
||||
(is (nil? (model/save {:random-id "random"
|
||||
(is (nil? (model/save {:random-id-generator (constantly "random")
|
||||
:db {:networks/manage {:url {:value "wrong"}
|
||||
:chain {:value "1"}
|
||||
:name {:value "empty"}}
|
||||
:account/account {}}})))))
|
||||
:account/account {}}}
|
||||
{})))))
|
||||
|
@ -1,6 +1,5 @@
|
||||
(ns status-im.test.runner
|
||||
(:require [doo.runner :refer-macros [doo-tests]]
|
||||
[status-im.test.chat.events]
|
||||
[status-im.test.contacts.events]
|
||||
[status-im.test.contacts.subs]
|
||||
[status-im.test.data-store.realm.core]
|
||||
@ -68,7 +67,6 @@
|
||||
|
||||
(doo-tests
|
||||
'status-im.test.utils.async
|
||||
'status-im.test.chat.events
|
||||
'status-im.test.chat.subs
|
||||
'status-im.test.chat.models
|
||||
'status-im.test.contacts.events
|
||||
|
@ -15,10 +15,10 @@
|
||||
|
||||
(deftest receive-whisper-messages-test
|
||||
(testing "an error is reported"
|
||||
(is (nil? (handlers/receive-whisper-messages {:db {}} [nil "error" #js [] nil]))))
|
||||
(is (nil? (handlers/receive-whisper-messages {:db {}} "error" #js [] nil))))
|
||||
(testing "messages is undefined"
|
||||
(is (nil? (handlers/receive-whisper-messages {:db {}} [nil nil js/undefined nil]))))
|
||||
(is (nil? (handlers/receive-whisper-messages {:db {}} nil js/undefined nil))))
|
||||
(testing "happy path"
|
||||
(let [actual (handlers/receive-whisper-messages {:db {}} [nil nil messages sig])]
|
||||
(let [actual (handlers/receive-whisper-messages {:db {}} nil messages sig)]
|
||||
(testing "it add an fx for the message"
|
||||
(is (:chat-received-message/add-fx actual))))))
|
||||
|
@ -25,7 +25,6 @@
|
||||
"address-explication": "Miskien moet hier 'n bietjie teks wees wat verduidelik wat 'n adres is en waar om daarvoor te soek.",
|
||||
"remove": "Verwyder",
|
||||
"add-members": "Voeg lede by",
|
||||
"sharing-cancel": "Kanselleer",
|
||||
"popular-tags": "Gewilde oortjies",
|
||||
"twelve-words-in-correct-order": "12 woorde in korrekte volgorde",
|
||||
"phone-number": "Telefoonnommer",
|
||||
|
@ -23,7 +23,6 @@
|
||||
"address-explication": "Можа быць, тут павінен быць нейкі тэкст, які тлумачыць адрас і тое, дзе яго шукаць",
|
||||
"remove": "Выдаліць",
|
||||
"add-members": "Дадаць членаў",
|
||||
"sharing-cancel": "Адмена",
|
||||
"popular-tags": "Папулярныя тэгі",
|
||||
"phone-number": "Нумар тэлефона",
|
||||
"removed-from-chat": "выдаліў(ла) вас з групавога чата",
|
||||
|
@ -28,7 +28,6 @@
|
||||
"remove": "Odstranit",
|
||||
"transactions-unsigned-empty": "Nemáš žádné nepodepsané transakce",
|
||||
"add-members": "Přidat členy",
|
||||
"sharing-cancel": "Storno",
|
||||
"popular-tags": "Populární tagy",
|
||||
"twelve-words-in-correct-order": "12 anglických slov ve správném pořadí",
|
||||
"phone-number": "Telefonní číslo",
|
||||
|
@ -27,7 +27,6 @@
|
||||
"address-explication": "Maybe here should be some text explaining what an address is and where to look for it",
|
||||
"remove": "Fjern",
|
||||
"add-members": "Tilføj medlemmere",
|
||||
"sharing-cancel": "Annullér",
|
||||
"popular-tags": "Populære tags",
|
||||
"twelve-words-in-correct-order": "12 ord i den korrekte rækkefølge",
|
||||
"phone-number": "Telefonnummer",
|
||||
|
@ -40,7 +40,6 @@
|
||||
"transaction-moved-text": "Die Transaktion wird für die nächsten 5 Minuten in der 'Unsigniert' Liste bleiben",
|
||||
"add-members": "Mitglieder hinzufügen",
|
||||
"sign-later-title": "Transaktion später signieren?",
|
||||
"sharing-cancel": "Abbrechen",
|
||||
"yes": "Ja",
|
||||
"dapps": "ÐApps",
|
||||
"popular-tags": "Beliebte #hashtags",
|
||||
|
@ -43,7 +43,6 @@
|
||||
"transaction-moved-text": "Η συναλλαγή θα παραμείνει στη λίστα των 'Μη επικυρωμένων' για τα επόμενα 5 λεπτά",
|
||||
"add-members": "Προσθήκη μελών",
|
||||
"sign-later-title": "Επικυρώστε τη συναλλαγή αργότερα;",
|
||||
"sharing-cancel": "Ακύρωση",
|
||||
"yes": "Ναι",
|
||||
"dapps": "ÐApps",
|
||||
"popular-tags": "Δημοφιλή #hashtags",
|
||||
|
@ -89,7 +89,8 @@
|
||||
"leave-public-chat": "Leave public chat",
|
||||
"sign-later-title": "Sign transaction later?",
|
||||
"manage-permissions": "Manage permissions",
|
||||
"sharing-cancel": "Cancel",
|
||||
"message-reply" : "Reply",
|
||||
"message-options-cancel": "Cancel",
|
||||
"currency-display-name-pln": "Poland Zloty",
|
||||
"about-app": "About",
|
||||
"yes": "Yes",
|
||||
|
@ -40,7 +40,6 @@
|
||||
"transaction-moved-text": "La transacción permanecerá en la lista 'Sin firmar' durante los siguientes 5 minutos",
|
||||
"add-members": "Añadir miembros",
|
||||
"sign-later-title": "¿Firmar transacción mas tarde?",
|
||||
"sharing-cancel": "Cancelar",
|
||||
"yes": "Sí",
|
||||
"dapps": "ÐApps",
|
||||
"popular-tags": "#hashtags populares",
|
||||
|
@ -508,7 +508,6 @@
|
||||
"shake-your-phone": "¿Encontraste un bug o tienes una sugerencia? ¡~Agíta tu teléfono~!",
|
||||
"share": "Compartir...",
|
||||
"share-contact-code": "Compartir mi código de contacto",
|
||||
"sharing-cancel": "Cancelar",
|
||||
"sharing-copied-to-clipboard": "Copiado al portapapeles",
|
||||
"sharing-copy-to-clipboard": "Copiar",
|
||||
"sharing-share": "Compartir...",
|
||||
|
@ -25,7 +25,6 @@
|
||||
"address-explication": "Tal vez, aquí debería haber alguna indicación explicando qué es una dirección y dónde buscarla",
|
||||
"remove": "Eliminar",
|
||||
"add-members": "Agregar miembros",
|
||||
"sharing-cancel": "Cancelar",
|
||||
"popular-tags": "Etiquetas populares",
|
||||
"twelve-words-in-correct-order": "12 palabras en el orden correcto",
|
||||
"phone-number": "Número telefónico",
|
||||
|
@ -27,7 +27,6 @@
|
||||
"address-explication": "Tal vez aquí debería haber algún texto explicando qué es una dirección y dónde encontrarla",
|
||||
"remove": "Remover",
|
||||
"add-members": "Agregar miembros",
|
||||
"sharing-cancel": "Cancelar",
|
||||
"popular-tags": "Etiquetas populares",
|
||||
"twelve-words-in-correct-order": "12 palabras en orden correcto",
|
||||
"phone-number": "Número de teléfono",
|
||||
|
@ -27,7 +27,6 @@
|
||||
"address-explication": "Ehkä tässä pitäisi olla jokin teksti, jossa selitetään, mikä osoite on ja mistä etsiä sitä",
|
||||
"remove": "Poista",
|
||||
"add-members": "Lisää käyttäjiä",
|
||||
"sharing-cancel": "Peruuta",
|
||||
"popular-tags": "Suositut tunnisteet",
|
||||
"twelve-words-in-correct-order": "12 sanaa oikeassa järjestyksessä",
|
||||
"phone-number": "Puhelinnumero",
|
||||
|
@ -42,7 +42,6 @@
|
||||
"transaction-moved-text": "La transaction va rester dans la liste 'Non-signées' pendant 5 mins",
|
||||
"add-members": "Ajouter des membres",
|
||||
"sign-later-title": "Voulez vous signer la transaction ultérieurement?",
|
||||
"sharing-cancel": "Annuler",
|
||||
"yes": "Oui",
|
||||
"dapps": "ÐApps",
|
||||
"popular-tags": "Mots-clés populaires",
|
||||
|
@ -25,7 +25,6 @@
|
||||
"address-explication": "On pourrait mettre ici un texte qui explique ce qu'est une adresse et comment la rechercher",
|
||||
"remove": "Supprimer",
|
||||
"add-members": "Ajouter des membres",
|
||||
"sharing-cancel": "Annuler",
|
||||
"popular-tags": "Clés populaires",
|
||||
"twelve-words-in-correct-order": "12 mots dans le bon ordre",
|
||||
"phone-number": "Numéro de téléphone",
|
||||
|
@ -27,7 +27,6 @@
|
||||
"address-explication": "Maybe here should be some text explaining what an address is and where to look for it",
|
||||
"remove": "Fuortsmite",
|
||||
"add-members": "Foegje leden ta",
|
||||
"sharing-cancel": "Ôfbrekke",
|
||||
"popular-tags": "Populêre tags",
|
||||
"twelve-words-in-correct-order": "12 wurden yn de goeie folchoarder",
|
||||
"phone-number": "Telefoannûmer",
|
||||
|
@ -27,7 +27,6 @@
|
||||
"address-explication": "Maybe here should be some text explaining what an address is and where to look for it",
|
||||
"remove": "הסר",
|
||||
"add-members": "הוסף מספרים",
|
||||
"sharing-cancel": "בטל",
|
||||
"popular-tags": "תגים פופולאריים",
|
||||
"twelve-words-in-correct-order": "12 מילים בסדר הנכון",
|
||||
"phone-number": "מספר טלפון",
|
||||
|
@ -25,7 +25,6 @@
|
||||
"address-explication": "संभवतः यहाँ कुछ टेक्स्ट विवरण होना चाहिए कि पता क्या है और इसे कहाँ खोजा जाए",
|
||||
"remove": "निकालें",
|
||||
"add-members": "सदस्य जोड़ें",
|
||||
"sharing-cancel": "रद्द करें",
|
||||
"popular-tags": "लोकप्रिय टैग",
|
||||
"twelve-words-in-correct-order": "सही क्रम में 12 शब्द",
|
||||
"phone-number": "फ़ोन नंबर",
|
||||
|
@ -25,7 +25,6 @@
|
||||
"address-explication": "Itt talán szükség lenne egy kis szövegre, ami elmagyarázná, mi is az a cím és hol lehet megtalálni",
|
||||
"remove": "Eltávolítás",
|
||||
"add-members": "Tagok hozzáadása",
|
||||
"sharing-cancel": "Mégse",
|
||||
"popular-tags": "Népszerű címkék",
|
||||
"twelve-words-in-correct-order": "12 szó helyes sorrendben",
|
||||
"phone-number": "Telefonszám",
|
||||
|
@ -40,7 +40,6 @@
|
||||
"transaction-moved-text": "La transazione rimarrà nella lista delle transazioni 'Non Firmate' per i prossimi 5 minuti",
|
||||
"add-members": "Aggiungi membri",
|
||||
"sign-later-title": "Firmare la transazione dopo?",
|
||||
"sharing-cancel": "Annulla",
|
||||
"yes": "Sì",
|
||||
"dapps": "ÐApps",
|
||||
"popular-tags": "#hashtags popolari",
|
||||
|
@ -25,7 +25,6 @@
|
||||
"address-explication": "Forse qui dovremmo spiegare cos'è un indirizzo e dove cercarlo",
|
||||
"remove": "Rimuovi",
|
||||
"add-members": "Aggiungi membri",
|
||||
"sharing-cancel": "Annulla",
|
||||
"popular-tags": "Tag popolari",
|
||||
"twelve-words-in-correct-order": "12 parole in ordine corretto",
|
||||
"phone-number": "Numero di telefono",
|
||||
|
@ -42,7 +42,6 @@
|
||||
"transaction-moved-text": "取引は'未署名'リストに5分間を残ります",
|
||||
"add-members": "メンバーを追加",
|
||||
"sign-later-title": "後で取引に署名する?",
|
||||
"sharing-cancel": "キャンセル",
|
||||
"yes": "はい",
|
||||
"dapps": "ÐApps",
|
||||
"popular-tags": "人気のタグ",
|
||||
|
@ -521,7 +521,6 @@
|
||||
"shake-your-phone": "버그나 건의사항이 있나요? 폰을 ~흔들어~ 보세요!",
|
||||
"share": "공유",
|
||||
"share-contact-code": "연락처 코드 공유하기",
|
||||
"sharing-cancel": "취소",
|
||||
"sharing-copied-to-clipboard": "클립보드에 복사 됨",
|
||||
"sharing-copy-to-clipboard": "클립보드로 복사",
|
||||
"sharing-share": "공유...",
|
||||
|
@ -40,7 +40,6 @@
|
||||
"transaction-moved-text": "Sandėris sėkmingai perkeltas į “Nepasirašytos”",
|
||||
"add-members": "Pridėti narių",
|
||||
"sign-later-title": "Pasirašyti sandėrį vėliau?",
|
||||
"sharing-cancel": "Atšaukti",
|
||||
"yes": "Taip",
|
||||
"dapps": "DApp'ai",
|
||||
"popular-tags": "Populiarūs hashtagai",
|
||||
|
@ -27,7 +27,6 @@
|
||||
"address-explication": "Maybe here should be some text explaining what an address is and where to look for it",
|
||||
"remove": "Noņemt",
|
||||
"add-members": "Pievienot biedrus",
|
||||
"sharing-cancel": "Atcelt",
|
||||
"popular-tags": "Popular tags",
|
||||
"twelve-words-in-correct-order": "12 vārdi",
|
||||
"phone-number": "Telefona numurs",
|
||||
|
@ -27,7 +27,6 @@
|
||||
"address-explication": "Mungkin disini sepatutnya terdapat sedikit teks menjelaskan apa itu address dan dimana untuk melihatnya",
|
||||
"remove": "Buang",
|
||||
"add-members": "Tambah ahli",
|
||||
"sharing-cancel": "Batal",
|
||||
"popular-tags": "Popular",
|
||||
"twelve-words-in-correct-order": "12 perkataan dalam susunan yang betul",
|
||||
"phone-number": "Nombor telefon",
|
||||
|
@ -29,7 +29,6 @@
|
||||
"remove": "Fjern",
|
||||
"transactions-unsigned-empty": "Du har ingen usignerte transaksjoner",
|
||||
"add-members": "Legg til medlemmer",
|
||||
"sharing-cancel": "Avbryt",
|
||||
"popular-tags": "Populære etiketter",
|
||||
"twelve-words-in-correct-order": "12 ord i riktig rekkefølge",
|
||||
"phone-number": "Telefonnummer",
|
||||
|
@ -27,7 +27,6 @@
|
||||
"address-explication": "सायद यहां ठेगाना बारे बयान गर्ने अनि त्यो कहाँ खोज्ने भन्ने कुराको कुनै पाठ हुनुपर्छ ।",
|
||||
"remove": "हटाउनुहोस्",
|
||||
"add-members": "सदस्यहरु जोड्नुहोस्",
|
||||
"sharing-cancel": "रद्द गर्नुहोस्",
|
||||
"popular-tags": "लोकप्रिय ट्यागहरु",
|
||||
"twelve-words-in-correct-order": "१२ सब्दहरु सही क्रममा",
|
||||
"phone-number": "फोन नम्बर",
|
||||
|
@ -26,7 +26,6 @@
|
||||
"address-explication": "Misschien zou hier wat tekst moeten staan waarin wordt uitgelegd wat een adres is en waar je deze kunt vinden",
|
||||
"remove": "Verwijderen",
|
||||
"add-members": "Voeg leden toe",
|
||||
"sharing-cancel": "Annuleren",
|
||||
"popular-tags": "Populaire tags",
|
||||
"twelve-words-in-correct-order": "12 woorden in goede volgorde",
|
||||
"phone-number": "Telefoonnummer",
|
||||
|
@ -517,7 +517,6 @@
|
||||
"shake-your-phone": "Znalazłeś błąd lub masz pytanie? Wystarczy ~potrząsnąć~ telefonem!",
|
||||
"share": "Udostępnij",
|
||||
"share-contact-code": "Udostępnij mój kod kontaktowy",
|
||||
"sharing-cancel": "Anuluj",
|
||||
"sharing-copied-to-clipboard": "Skopiowane do schowka",
|
||||
"sharing-copy-to-clipboard": "Skopiuj do schowka",
|
||||
"sharing-share": "Udostępnij...",
|
||||
|
@ -25,7 +25,6 @@
|
||||
"address-explication": "Talvez aqui deveria haver algum texto explicando o que é um endereço e onde procurá-lo",
|
||||
"remove": "Remover",
|
||||
"add-members": "Adicionar membros",
|
||||
"sharing-cancel": "Cancelar",
|
||||
"popular-tags": "Tags populares",
|
||||
"twelve-words-in-correct-order": "12 palavras na ordem correta",
|
||||
"phone-number": "Número de telefone",
|
||||
|
@ -25,7 +25,6 @@
|
||||
"address-explication": "Talvez aqui devesse aparecer algum texto a explicar o que é um endereço e onde procurá-lo",
|
||||
"remove": "Remover",
|
||||
"add-members": "Adicionar Membros",
|
||||
"sharing-cancel": "Cancelar",
|
||||
"popular-tags": "Tags populares",
|
||||
"twelve-words-in-correct-order": "12 palavras na ordem correta",
|
||||
"phone-number": "Número de telefone",
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user