Chat replies + refactoring

This commit is contained in:
janherich 2018-09-24 18:27:04 +02:00
parent 965381f376
commit 44fbe62773
No known key found for this signature in database
GPG Key ID: C23B473AFBE94D13
120 changed files with 914 additions and 883 deletions

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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: " %)))))

View File

@ -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)
#())))

View File

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

View File

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

View File

@ -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: " %)))))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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]

View File

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

View File

@ -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]]

View File

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

View File

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

View File

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

View File

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

View File

@ -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 []

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 []

View File

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

View File

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

View File

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

View File

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

View File

@ -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]]

View File

@ -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]]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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",

View File

@ -23,7 +23,6 @@
"address-explication": "Можа быць, тут павінен быць нейкі тэкст, які тлумачыць адрас і тое, дзе яго шукаць",
"remove": "Выдаліць",
"add-members": "Дадаць членаў",
"sharing-cancel": "Адмена",
"popular-tags": "Папулярныя тэгі",
"phone-number": "Нумар тэлефона",
"removed-from-chat": "выдаліў(ла) вас з групавога чата",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -43,7 +43,6 @@
"transaction-moved-text": "Η συναλλαγή θα παραμείνει στη λίστα των 'Μη επικυρωμένων' για τα επόμενα 5 λεπτά",
"add-members": "Προσθήκη μελών",
"sign-later-title": "Επικυρώστε τη συναλλαγή αργότερα;",
"sharing-cancel": "Ακύρωση",
"yes": "Ναι",
"dapps": "ÐApps",
"popular-tags": "Δημοφιλή #hashtags",

View File

@ -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",

View File

@ -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",

View File

@ -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...",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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": "מספר טלפון",

View File

@ -25,7 +25,6 @@
"address-explication": "संभवतः यहाँ कुछ टेक्स्ट विवरण होना चाहिए कि पता क्या है और इसे कहाँ खोजा जाए",
"remove": "निकालें",
"add-members": "सदस्य जोड़ें",
"sharing-cancel": "रद्द करें",
"popular-tags": "लोकप्रिय टैग",
"twelve-words-in-correct-order": "सही क्रम में 12 शब्द",
"phone-number": "फ़ोन नंबर",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -42,7 +42,6 @@
"transaction-moved-text": "取引は'未署名'リストに5分間を残ります",
"add-members": "メンバーを追加",
"sign-later-title": "後で取引に署名する?",
"sharing-cancel": "キャンセル",
"yes": "はい",
"dapps": "ÐApps",
"popular-tags": "人気のタグ",

View File

@ -521,7 +521,6 @@
"shake-your-phone": "버그나 건의사항이 있나요? 폰을 ~흔들어~ 보세요!",
"share": "공유",
"share-contact-code": "연락처 코드 공유하기",
"sharing-cancel": "취소",
"sharing-copied-to-clipboard": "클립보드에 복사 됨",
"sharing-copy-to-clipboard": "클립보드로 복사",
"sharing-share": "공유...",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -27,7 +27,6 @@
"address-explication": "सायद यहां ठेगाना बारे बयान गर्ने अनि त्यो कहाँ खोज्ने भन्ने कुराको कुनै पाठ हुनुपर्छ ।",
"remove": "हटाउनुहोस्",
"add-members": "सदस्यहरु जोड्नुहोस्",
"sharing-cancel": "रद्द गर्नुहोस्",
"popular-tags": "लोकप्रिय ट्यागहरु",
"twelve-words-in-correct-order": "१२ सब्दहरु सही क्रममा",
"phone-number": "फोन नम्बर",

View File

@ -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",

View File

@ -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...",

View File

@ -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",

View File

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