Mentions suggestions
This commit is contained in:
parent
0675d0d8d7
commit
16fecc2ac6
|
@ -64,10 +64,11 @@
|
||||||
:justify-content :space-between})}]
|
:justify-content :space-between})}]
|
||||||
children))
|
children))
|
||||||
|
|
||||||
(defn- icon-column [{:keys [icon icon-bg-color icon-color size]}]
|
(defn- icon-column
|
||||||
|
[{:keys [icon icon-bg-color icon-color size icon-container-style]}]
|
||||||
(when icon
|
(when icon
|
||||||
(let [icon-size (size->icon-size size)]
|
(let [icon-size (size->icon-size size)]
|
||||||
[rn/view {:style (:tiny spacing/padding-horizontal)}
|
[rn/view {:style (or icon-container-style (:tiny spacing/padding-horizontal))}
|
||||||
(cond
|
(cond
|
||||||
(vector? icon)
|
(vector? icon)
|
||||||
icon
|
icon
|
||||||
|
@ -83,7 +84,7 @@
|
||||||
|
|
||||||
(defn title-column
|
(defn title-column
|
||||||
[{:keys [title text-color subtitle subtitle-max-lines
|
[{:keys [title text-color subtitle subtitle-max-lines
|
||||||
title-accessibility-label size]}]
|
title-accessibility-label size text-size title-text-weight]}]
|
||||||
[rn/view {:style (merge (:tiny spacing/padding-horizontal)
|
[rn/view {:style (merge (:tiny spacing/padding-horizontal)
|
||||||
{:justify-content :center
|
{:justify-content :center
|
||||||
:flex 1})}
|
:flex 1})}
|
||||||
|
@ -93,28 +94,31 @@
|
||||||
[:<>
|
[:<>
|
||||||
;; FIXME(Ferossgp): ReactNative 63 will support view inside text on andrid, remove thess if when migrating
|
;; FIXME(Ferossgp): ReactNative 63 will support view inside text on andrid, remove thess if when migrating
|
||||||
(if (string? title)
|
(if (string? title)
|
||||||
[text/text {:weight :medium
|
[text/text {:weight (or title-text-weight :medium)
|
||||||
:style {:color text-color}
|
:style {:color text-color}
|
||||||
:accessibility-label title-accessibility-label
|
:accessibility-label title-accessibility-label
|
||||||
:ellipsize-mode :tail
|
:ellipsize-mode :tail
|
||||||
:number-of-lines 1}
|
:number-of-lines 1
|
||||||
|
:size text-size}
|
||||||
title]
|
title]
|
||||||
title)
|
title)
|
||||||
(if (string? subtitle)
|
(if (string? subtitle)
|
||||||
[text/text {:weight :regular
|
[text/text {:weight :regular
|
||||||
:color :secondary
|
:color :secondary
|
||||||
:ellipsize-mode :tail
|
:ellipsize-mode :tail
|
||||||
:number-of-lines subtitle-max-lines}
|
:number-of-lines subtitle-max-lines
|
||||||
|
:size text-size}
|
||||||
subtitle]
|
subtitle]
|
||||||
subtitle)]
|
subtitle)]
|
||||||
|
|
||||||
title
|
title
|
||||||
(if (string? title)
|
(if (string? title)
|
||||||
[text/text {:number-of-lines 1
|
[text/text {:weight (or title-text-weight :regular)
|
||||||
|
:number-of-lines 1
|
||||||
:style {:color text-color}
|
:style {:color text-color}
|
||||||
:title-accessibility-label title-accessibility-label
|
:title-accessibility-label title-accessibility-label
|
||||||
:ellipsize-mode :tail
|
:ellipsize-mode :tail
|
||||||
:size (size->single-title-size size)}
|
:size (or text-size (size->single-title-size size))}
|
||||||
title]
|
title]
|
||||||
title))])
|
title))])
|
||||||
|
|
||||||
|
@ -147,10 +151,10 @@
|
||||||
:color (:icon-02 @colors/theme)}]])]))
|
:color (:icon-02 @colors/theme)}]])]))
|
||||||
|
|
||||||
(defn list-item
|
(defn list-item
|
||||||
[{:keys [theme accessory disabled subtitle-max-lines icon title
|
[{:keys [theme accessory disabled subtitle-max-lines icon icon-container-style
|
||||||
subtitle active on-press on-long-press chevron size
|
title subtitle active on-press on-long-press chevron size text-size
|
||||||
accessory-text accessibility-label title-accessibility-label
|
accessory-text accessibility-label title-accessibility-label
|
||||||
haptic-feedback haptic-type error animated]
|
haptic-feedback haptic-type error animated title-text-weight]
|
||||||
:or {subtitle-max-lines 1
|
:or {subtitle-max-lines 1
|
||||||
theme :main
|
theme :main
|
||||||
haptic-feedback true
|
haptic-feedback true
|
||||||
|
@ -191,8 +195,11 @@
|
||||||
:icon-bg-color icon-bg-color
|
:icon-bg-color icon-bg-color
|
||||||
:title-accessibility-label title-accessibility-label
|
:title-accessibility-label title-accessibility-label
|
||||||
:icon icon
|
:icon icon
|
||||||
|
:icon-container-style icon-container-style
|
||||||
:title title
|
:title title
|
||||||
|
:title-text-weight title-text-weight
|
||||||
:size size
|
:size size
|
||||||
|
:text-size text-size
|
||||||
:subtitle subtitle
|
:subtitle subtitle
|
||||||
:subtitle-max-lines subtitle-max-lines}]
|
:subtitle-max-lines subtitle-max-lines}]
|
||||||
[right-side {:chevron chevron
|
[right-side {:chevron chevron
|
||||||
|
|
|
@ -29,6 +29,18 @@
|
||||||
[{{:keys [current-chat-id] :as db} :db} new-input]
|
[{{:keys [current-chat-id] :as db} :db} new-input]
|
||||||
{:db (assoc-in db [:chat/inputs current-chat-id :input-text] (text->emoji new-input))})
|
{:db (assoc-in db [:chat/inputs current-chat-id :input-text] (text->emoji new-input))})
|
||||||
|
|
||||||
|
(fx/defn select-mention
|
||||||
|
[{:keys [db] :as cofx} {:keys [name] :as user}]
|
||||||
|
(let [chat-id (:current-chat-id db)
|
||||||
|
new-text (mentions/new-input-text-with-mention cofx user)
|
||||||
|
at-sign-idx (get-in db [:chats chat-id :mentions :at-sign-idx])]
|
||||||
|
(fx/merge
|
||||||
|
cofx
|
||||||
|
{:db (-> db
|
||||||
|
(assoc-in [:chats/cursor chat-id] (+ at-sign-idx (count name) 2))
|
||||||
|
(assoc-in [:chats/mention-suggestions chat-id] nil))}
|
||||||
|
(set-chat-input-text new-text))))
|
||||||
|
|
||||||
(defn- start-cooldown [{:keys [db]} cooldowns]
|
(defn- start-cooldown [{:keys [db]} cooldowns]
|
||||||
{:dispatch-later [{:dispatch [:chat/disable-cooldown]
|
{:dispatch-later [{:dispatch [:chat/disable-cooldown]
|
||||||
:ms (chat.constants/cooldown-periods-ms
|
:ms (chat.constants/cooldown-periods-ms
|
||||||
|
@ -135,4 +147,6 @@
|
||||||
input-text-with-mentions (mentions/check-mentions cofx input-text)]
|
input-text-with-mentions (mentions/check-mentions cofx input-text)]
|
||||||
(fx/merge cofx
|
(fx/merge cofx
|
||||||
(send-image)
|
(send-image)
|
||||||
(send-plain-text-message input-text-with-mentions current-chat-id))))
|
(send-plain-text-message input-text-with-mentions current-chat-id)
|
||||||
|
(mentions/clear-suggestions)
|
||||||
|
(mentions/clear-cursor))))
|
||||||
|
|
|
@ -83,13 +83,22 @@
|
||||||
(get-in db [:pagination-info current-chat-id :messages-initialized?]))))
|
(get-in db [:pagination-info current-chat-id :messages-initialized?]))))
|
||||||
(let [already-loaded-messages (get-in db [:messages current-chat-id])
|
(let [already-loaded-messages (get-in db [:messages current-chat-id])
|
||||||
loaded-unviewed-messages-ids (get-in db [:chats current-chat-id :loaded-unviewed-messages-ids] #{})
|
loaded-unviewed-messages-ids (get-in db [:chats current-chat-id :loaded-unviewed-messages-ids] #{})
|
||||||
|
users (get-in db [:chats current-chat-id :users] {})
|
||||||
;; We remove those messages that are already loaded, as we might get some duplicates
|
;; We remove those messages that are already loaded, as we might get some duplicates
|
||||||
{:keys [all-messages
|
{:keys [all-messages
|
||||||
new-messages
|
new-messages
|
||||||
last-clock-value
|
last-clock-value
|
||||||
unviewed-message-ids]} (reduce (fn [{:keys [last-clock-value all-messages] :as acc}
|
unviewed-message-ids
|
||||||
{:keys [clock-value seen message-id] :as message}]
|
users]}
|
||||||
|
(reduce (fn [{:keys [last-clock-value all-messages users] :as acc}
|
||||||
|
{:keys [clock-value seen message-id alias name identicon from]
|
||||||
|
:as message}]
|
||||||
(cond-> acc
|
(cond-> acc
|
||||||
|
(and alias (not= alias ""))
|
||||||
|
(update :users assoc alias {:alias alias
|
||||||
|
:name (or name alias)
|
||||||
|
:identicon identicon
|
||||||
|
:public-key from})
|
||||||
(or (nil? last-clock-value)
|
(or (nil? last-clock-value)
|
||||||
(> last-clock-value clock-value))
|
(> last-clock-value clock-value))
|
||||||
(assoc :last-clock-value clock-value)
|
(assoc :last-clock-value clock-value)
|
||||||
|
@ -104,12 +113,14 @@
|
||||||
(update :all-messages assoc message-id message)))
|
(update :all-messages assoc message-id message)))
|
||||||
{:all-messages already-loaded-messages
|
{:all-messages already-loaded-messages
|
||||||
:unviewed-message-ids loaded-unviewed-messages-ids
|
:unviewed-message-ids loaded-unviewed-messages-ids
|
||||||
|
:users users
|
||||||
:new-messages []}
|
:new-messages []}
|
||||||
messages)]
|
messages)]
|
||||||
(fx/merge cofx
|
(fx/merge cofx
|
||||||
{:db (-> db
|
{:db (-> db
|
||||||
(assoc-in [:pagination-info current-chat-id :cursor-clock-value] (when (seq cursor) (cursor->clock-value cursor)))
|
(assoc-in [:pagination-info current-chat-id :cursor-clock-value] (when (seq cursor) (cursor->clock-value cursor)))
|
||||||
(assoc-in [:chats current-chat-id :loaded-unviewed-messages-ids] unviewed-message-ids)
|
(assoc-in [:chats current-chat-id :loaded-unviewed-messages-ids] unviewed-message-ids)
|
||||||
|
(assoc-in [:chats current-chat-id :users] users)
|
||||||
(assoc-in [:pagination-info current-chat-id :loading-messages?] false)
|
(assoc-in [:pagination-info current-chat-id :loading-messages?] false)
|
||||||
(assoc-in [:messages current-chat-id] all-messages)
|
(assoc-in [:messages current-chat-id] all-messages)
|
||||||
(update-in [:message-lists current-chat-id] message-list/add-many new-messages)
|
(update-in [:message-lists current-chat-id] message-list/add-many new-messages)
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
(ns status-im.chat.models.mentions
|
(ns status-im.chat.models.mentions
|
||||||
(:require [clojure.string :as string]
|
(:require [clojure.string :as string]
|
||||||
[status-im.contact.db :as contact.db]))
|
[status-im.utils.fx :as fx]
|
||||||
|
[status-im.contact.db :as contact.db]
|
||||||
|
[status-im.utils.platform :as platform]
|
||||||
|
[taoensso.timbre :as log]))
|
||||||
|
|
||||||
|
(def at-sign "@")
|
||||||
|
|
||||||
(defn get-mentionable-users
|
(defn get-mentionable-users
|
||||||
[{{:keys [current-chat-id messages]
|
[{{:keys [current-chat-id]
|
||||||
:contacts/keys [contacts] :as db} :db}]
|
:contacts/keys [contacts] :as db} :db}]
|
||||||
(let [{:keys [group-chat public?] :as chat}
|
(let [{:keys [group-chat public? users] :as chat}
|
||||||
(get-in db [:chats current-chat-id])
|
(get-in db [:chats current-chat-id])
|
||||||
chat-specific-suggestions
|
chat-specific-suggestions
|
||||||
(cond
|
(cond
|
||||||
|
@ -28,17 +33,7 @@
|
||||||
{}
|
{}
|
||||||
group-contacts))
|
group-contacts))
|
||||||
|
|
||||||
(and group-chat public?)
|
:else users)]
|
||||||
(reduce
|
|
||||||
(fn [acc [_ {:keys [alias name identicon from]}]]
|
|
||||||
(assoc acc alias {:alias alias
|
|
||||||
:name (or name alias)
|
|
||||||
:identicon identicon
|
|
||||||
:public-key from}))
|
|
||||||
nil
|
|
||||||
(get messages current-chat-id))
|
|
||||||
|
|
||||||
:else {})]
|
|
||||||
(reduce
|
(reduce
|
||||||
(fn [acc [key {:keys [alias name identicon]}]]
|
(fn [acc [key {:keys [alias name identicon]}]]
|
||||||
(let [name (string/replace name ".stateofus.eth" "")]
|
(let [name (string/replace name ".stateofus.eth" "")]
|
||||||
|
@ -49,7 +44,9 @@
|
||||||
chat-specific-suggestions
|
chat-specific-suggestions
|
||||||
contacts)))
|
contacts)))
|
||||||
|
|
||||||
(def word-regex #"^[\S]*\s|^[\S]*$")
|
(def ending-chars "[\\s\\.,;:]")
|
||||||
|
(def ending-chars-regex (re-pattern ending-chars))
|
||||||
|
(def word-regex (re-pattern (str "^[\\w\\d]*" ending-chars "|^[\\S]*$")))
|
||||||
|
|
||||||
(defn mentioned? [{:keys [alias name]} text]
|
(defn mentioned? [{:keys [alias name]} text]
|
||||||
(let [lcase-name (string/lower-case name)
|
(let [lcase-name (string/lower-case name)
|
||||||
|
@ -57,31 +54,48 @@
|
||||||
regex (re-pattern
|
regex (re-pattern
|
||||||
(string/join
|
(string/join
|
||||||
"|"
|
"|"
|
||||||
[(str "^" lcase-name "\\s")
|
[(str "^" lcase-name ending-chars)
|
||||||
(str "^" lcase-name "$")
|
(str "^" lcase-name "$")
|
||||||
(str "^" lcase-alias "\\s")
|
(str "^" lcase-alias ending-chars)
|
||||||
(str "^" lcase-alias "$")]))
|
(str "^" lcase-alias "$")]))
|
||||||
lcase-text (string/lower-case text)]
|
lcase-text (string/lower-case text)]
|
||||||
(re-find regex lcase-text)))
|
(re-find regex lcase-text)))
|
||||||
|
|
||||||
|
(defn get-suggestions [users searched-text]
|
||||||
|
(reduce
|
||||||
|
(fn [acc [k {:keys [alias name] :as user}]]
|
||||||
|
(if-let [match
|
||||||
|
(cond
|
||||||
|
(and alias
|
||||||
|
(string/starts-with?
|
||||||
|
(string/lower-case alias)
|
||||||
|
searched-text))
|
||||||
|
alias
|
||||||
|
|
||||||
|
(string/starts-with?
|
||||||
|
(string/lower-case name)
|
||||||
|
searched-text)
|
||||||
|
name)]
|
||||||
|
(assoc acc k (assoc user :match match))
|
||||||
|
acc))
|
||||||
|
{}
|
||||||
|
users))
|
||||||
|
|
||||||
(defn match-mention
|
(defn match-mention
|
||||||
([text users mention-key-idx]
|
([text users mention-key-idx]
|
||||||
(match-mention text users mention-key-idx (inc mention-key-idx) []))
|
(match-mention text users mention-key-idx (inc mention-key-idx) []))
|
||||||
([text users mention-key-idx next-word-idx words]
|
([text users mention-key-idx next-word-idx words]
|
||||||
(let [trimmed-text (subs text next-word-idx)]
|
(when-let [word (re-find word-regex (subs text next-word-idx))]
|
||||||
(when-let [word (string/trim (re-find word-regex trimmed-text))]
|
(let [new-words (conj words word)
|
||||||
(let [words (conj words word)
|
searched-text (let [text (-> new-words
|
||||||
searched-text (string/lower-case (string/join " " words))
|
string/join
|
||||||
suggestions (filter
|
string/lower-case
|
||||||
(fn [[_ {:keys [alias name]}]]
|
string/trim)
|
||||||
(let [names (set [alias name])]
|
last-char (dec (count text))]
|
||||||
(some
|
(if (re-matches ending-chars-regex (str (nth text last-char nil)))
|
||||||
(fn [username]
|
(subs text 0 last-char)
|
||||||
(string/starts-with?
|
text))
|
||||||
(string/lower-case username)
|
suggestions (get-suggestions users searched-text)
|
||||||
searched-text))
|
|
||||||
names)))
|
|
||||||
users)
|
|
||||||
suggestions-cnt (count suggestions)]
|
suggestions-cnt (count suggestions)]
|
||||||
(cond (zero? suggestions-cnt)
|
(cond (zero? suggestions-cnt)
|
||||||
nil
|
nil
|
||||||
|
@ -94,10 +108,10 @@
|
||||||
(> suggestions-cnt 1)
|
(> suggestions-cnt 1)
|
||||||
(let [word-len (count word)
|
(let [word-len (count word)
|
||||||
text-len (count text)
|
text-len (count text)
|
||||||
next-word-start (+ next-word-idx (inc word-len))]
|
next-word-start (+ next-word-idx word-len)]
|
||||||
(when (> text-len next-word-start)
|
(when (> text-len next-word-start)
|
||||||
(match-mention text users mention-key-idx
|
(match-mention text users mention-key-idx
|
||||||
next-word-start words)))))))))
|
next-word-start new-words))))))))
|
||||||
|
|
||||||
(defn replace-mentions
|
(defn replace-mentions
|
||||||
([text users-fn]
|
([text users-fn]
|
||||||
|
@ -105,21 +119,130 @@
|
||||||
([text users-fn idx]
|
([text users-fn idx]
|
||||||
(if (string/blank? text)
|
(if (string/blank? text)
|
||||||
text
|
text
|
||||||
(let [mention-key-idx (string/index-of text "@" idx)]
|
(let [mention-key-idx (string/index-of text at-sign idx)]
|
||||||
(if-not mention-key-idx
|
(if-not mention-key-idx
|
||||||
text
|
text
|
||||||
(let [users (users-fn)
|
(let [users (users-fn)]
|
||||||
{:keys [public-key alias]}
|
(if-not (seq users)
|
||||||
|
text
|
||||||
|
(let [{:keys [public-key match]}
|
||||||
(match-mention text users mention-key-idx)]
|
(match-mention text users mention-key-idx)]
|
||||||
(if-not alias
|
(if-not match
|
||||||
(recur text (fn [] users) (inc mention-key-idx))
|
(recur text (fn [] users) (inc mention-key-idx))
|
||||||
(let [new-text (string/join
|
(let [new-text (string/join
|
||||||
[(subs text 0 (inc mention-key-idx))
|
[(subs text 0 (inc mention-key-idx))
|
||||||
public-key
|
public-key
|
||||||
(subs text (+ (inc mention-key-idx)
|
(subs text (+ (inc mention-key-idx)
|
||||||
(count alias)))])
|
(count match)))])
|
||||||
mention-end (+ (inc mention-key-idx) (count public-key))]
|
mention-end (+ (inc mention-key-idx) (count public-key))]
|
||||||
(recur new-text (fn [] users) mention-end)))))))))
|
(recur new-text (fn [] users) mention-end)))))))))))
|
||||||
|
|
||||||
(defn check-mentions [cofx text]
|
(defn check-mentions [cofx text]
|
||||||
(replace-mentions text #(get-mentionable-users cofx)))
|
(replace-mentions text #(get-mentionable-users cofx)))
|
||||||
|
|
||||||
|
(defn check-for-at-sign
|
||||||
|
([text]
|
||||||
|
(check-for-at-sign text 0 0))
|
||||||
|
([text from cnt]
|
||||||
|
(if-let [idx (string/index-of text at-sign from)]
|
||||||
|
(recur text (inc idx) (inc cnt))
|
||||||
|
cnt)))
|
||||||
|
|
||||||
|
(defn at-sign-change [previous-text new-text]
|
||||||
|
(cond
|
||||||
|
(= "" previous-text)
|
||||||
|
(check-for-at-sign new-text)
|
||||||
|
|
||||||
|
(= "" new-text)
|
||||||
|
(- (check-for-at-sign previous-text))
|
||||||
|
|
||||||
|
:else
|
||||||
|
(- (check-for-at-sign new-text)
|
||||||
|
(check-for-at-sign previous-text))))
|
||||||
|
|
||||||
|
(fx/defn on-text-input
|
||||||
|
{:events [::on-text-input]}
|
||||||
|
[{:keys [db] :as cofx} {:keys [new-text previous-text start end] :as args}]
|
||||||
|
(log/debug "[mentions] on-text-input args" args)
|
||||||
|
(let [normalized-previous-text
|
||||||
|
;; NOTE(rasom): on iOS `previous-text` contains entire input's text. To
|
||||||
|
;; get only removed part of text we have cut it.
|
||||||
|
(if platform/android?
|
||||||
|
previous-text
|
||||||
|
(subs previous-text start end))
|
||||||
|
chat-id (:current-chat-id db)
|
||||||
|
change (at-sign-change normalized-previous-text new-text)
|
||||||
|
previous-state (get-in db [:chats chat-id :mentions])
|
||||||
|
new-state (-> previous-state
|
||||||
|
(update :at-sign-counter + change)
|
||||||
|
(merge args)
|
||||||
|
(assoc :previous-text normalized-previous-text))]
|
||||||
|
(log/debug "[mentions] on-text-input state" new-state)
|
||||||
|
{:db (assoc-in db [:chats chat-id :mentions] new-state)}))
|
||||||
|
|
||||||
|
(fx/defn calculate-suggestion
|
||||||
|
{:events [::calculate-suggestions]}
|
||||||
|
[{:keys [db] :as cofx} mentionable-users]
|
||||||
|
(let [chat-id (:current-chat-id db)
|
||||||
|
text (get-in db [:chat/inputs chat-id :input-text])
|
||||||
|
{:keys [new-text at-sign-counter start end] :as state}
|
||||||
|
(get-in db [:chats chat-id :mentions])
|
||||||
|
new-text (or new-text text)]
|
||||||
|
(log/debug "[mentions] calculate suggestions"
|
||||||
|
"state" state)
|
||||||
|
(if-not (pos? at-sign-counter)
|
||||||
|
{:db (assoc-in db [:chats/mention-suggestions chat-id] nil)}
|
||||||
|
(let [addition? (<= start end)
|
||||||
|
end (if addition?
|
||||||
|
(+ start (count new-text))
|
||||||
|
start)
|
||||||
|
at-sign-idx (string/last-index-of text at-sign start)
|
||||||
|
searched-text (string/lower-case (subs text (inc at-sign-idx) end))
|
||||||
|
mentions
|
||||||
|
(when (and (not (> at-sign-idx start))
|
||||||
|
(not (> (- end at-sign-idx) 100)))
|
||||||
|
(get-suggestions mentionable-users searched-text))]
|
||||||
|
(log/debug "[mentions] mention detected"
|
||||||
|
"addition" addition?
|
||||||
|
"end" end
|
||||||
|
"searched-text" (pr-str searched-text)
|
||||||
|
"mentions" (count mentions))
|
||||||
|
{:db (-> db
|
||||||
|
(update-in [:chats chat-id :mentions]
|
||||||
|
assoc
|
||||||
|
:at-sign-idx at-sign-idx
|
||||||
|
:mention-end end)
|
||||||
|
(assoc-in [:chats/mention-suggestions chat-id] mentions))}))))
|
||||||
|
|
||||||
|
(defn new-input-text-with-mention
|
||||||
|
[{:keys [db]} {:keys [name]}]
|
||||||
|
(let [chat-id (:current-chat-id db)
|
||||||
|
text (get-in db [:chat/inputs chat-id :input-text])
|
||||||
|
{:keys [mention-end at-sign-idx] :as state}
|
||||||
|
(get-in db [:chats chat-id :mentions])]
|
||||||
|
(log/debug "[mentions] clear suggestions"
|
||||||
|
"state" state)
|
||||||
|
(string/join
|
||||||
|
[(subs text 0 (inc at-sign-idx))
|
||||||
|
name
|
||||||
|
(let [next-char (get text mention-end)]
|
||||||
|
(when (or (not next-char)
|
||||||
|
(and next-char
|
||||||
|
(not (re-matches #"\s" next-char))))
|
||||||
|
" "))
|
||||||
|
(subs text mention-end)])))
|
||||||
|
|
||||||
|
(fx/defn clear-suggestions
|
||||||
|
[{:keys [db]}]
|
||||||
|
(log/debug "[mentions] clear suggestions")
|
||||||
|
(let [chat-id (:current-chat-id db)]
|
||||||
|
{:db (-> db
|
||||||
|
(update-in [:chats chat-id] dissoc :mentions)
|
||||||
|
(update :chats/mention-suggestions dissoc chat-id))}))
|
||||||
|
|
||||||
|
(fx/defn clear-cursor
|
||||||
|
{:events [::clear-cursor]}
|
||||||
|
[{:keys [db]}]
|
||||||
|
(log/debug "[mentions] clear cursor")
|
||||||
|
{:db
|
||||||
|
(update db :chats/cursor dissoc (:current-chat-id db))})
|
||||||
|
|
|
@ -17,6 +17,26 @@
|
||||||
{:name "user3"
|
{:name "user3"
|
||||||
:alias "User Number Three"
|
:alias "User Number Three"
|
||||||
:public-key "0xpk3"}})]
|
:public-key "0xpk3"}})]
|
||||||
|
(test/testing "empty string"
|
||||||
|
(let [text ""
|
||||||
|
result (mentions/replace-mentions text users)]
|
||||||
|
(test/is (= result text) (pr-str text))))
|
||||||
|
|
||||||
|
(test/testing "no text"
|
||||||
|
(let [text nil
|
||||||
|
result (mentions/replace-mentions text users)]
|
||||||
|
(test/is (= result text) (pr-str text))))
|
||||||
|
|
||||||
|
(test/testing "incomlepte mention 1"
|
||||||
|
(let [text "@"
|
||||||
|
result (mentions/replace-mentions text users)]
|
||||||
|
(test/is (= result text) (pr-str text))))
|
||||||
|
|
||||||
|
(test/testing "incomplete mention 2"
|
||||||
|
(let [text "@r"
|
||||||
|
result (mentions/replace-mentions text users)]
|
||||||
|
(test/is (= result text) (pr-str text))))
|
||||||
|
|
||||||
(test/testing "no mentions"
|
(test/testing "no mentions"
|
||||||
(let [text "foo bar @buzz kek @foo"
|
(let [text "foo bar @buzz kek @foo"
|
||||||
result (mentions/replace-mentions text users)]
|
result (mentions/replace-mentions text users)]
|
||||||
|
@ -27,6 +47,11 @@
|
||||||
result (mentions/replace-mentions text users)]
|
result (mentions/replace-mentions text users)]
|
||||||
(test/is (= result "@0xpk1") (pr-str text))))
|
(test/is (= result "@0xpk1") (pr-str text))))
|
||||||
|
|
||||||
|
(test/testing "starts with mention, comma after mention"
|
||||||
|
(let [text "@User Number One,"
|
||||||
|
result (mentions/replace-mentions text users)]
|
||||||
|
(test/is (= result "@0xpk1,") (pr-str text))))
|
||||||
|
|
||||||
(test/testing "starts with mention but no space after"
|
(test/testing "starts with mention but no space after"
|
||||||
(let [text "@User Number Onefoo"
|
(let [text "@User Number Onefoo"
|
||||||
result (mentions/replace-mentions text users)]
|
result (mentions/replace-mentions text users)]
|
||||||
|
@ -57,6 +82,11 @@
|
||||||
result (mentions/replace-mentions text users)]
|
result (mentions/replace-mentions text users)]
|
||||||
(test/is (= result "@0xpk1 @0xpk2") (pr-str text))))
|
(test/is (= result "@0xpk1 @0xpk2") (pr-str text))))
|
||||||
|
|
||||||
|
(test/testing "two different mentions, separated with comma"
|
||||||
|
(let [text "@User Number One,@User Number two"
|
||||||
|
result (mentions/replace-mentions text users)]
|
||||||
|
(test/is (= result "@0xpk1,@0xpk2") (pr-str text))))
|
||||||
|
|
||||||
(test/testing "two different mentions inside text"
|
(test/testing "two different mentions inside text"
|
||||||
(let [text "foo@User Number One bar @User Number two baz"
|
(let [text "foo@User Number One bar @User Number two baz"
|
||||||
result (mentions/replace-mentions text users)]
|
result (mentions/replace-mentions text users)]
|
||||||
|
|
|
@ -69,6 +69,16 @@
|
||||||
(update-in [:chats chat-id :loaded-unviewed-messages-ids]
|
(update-in [:chats chat-id :loaded-unviewed-messages-ids]
|
||||||
(fnil conj #{}) message-id))}))))
|
(fnil conj #{}) message-id))}))))
|
||||||
|
|
||||||
|
(fx/defn add-sender-to-chat-users
|
||||||
|
[{:keys [db]} {:keys [chat-id alias name identicon from]}]
|
||||||
|
(when (and alias (not= alias ""))
|
||||||
|
{:db (update-in db [:chats chat-id :users] assoc
|
||||||
|
alias
|
||||||
|
{:alias alias
|
||||||
|
:name (or name alias)
|
||||||
|
:identicon identicon
|
||||||
|
:public-key from})}))
|
||||||
|
|
||||||
(fx/defn add-received-message
|
(fx/defn add-received-message
|
||||||
[{:keys [db] :as cofx}
|
[{:keys [db] :as cofx}
|
||||||
{:keys [chat-id clock-value] :as message}]
|
{:keys [chat-id clock-value] :as message}]
|
||||||
|
@ -76,12 +86,14 @@
|
||||||
cursor-clock-value (get-in db [:chats current-chat-id :cursor-clock-value])
|
cursor-clock-value (get-in db [:chats current-chat-id :cursor-clock-value])
|
||||||
current-chat? (= chat-id loaded-chat-id)]
|
current-chat? (= chat-id loaded-chat-id)]
|
||||||
(when current-chat?
|
(when current-chat?
|
||||||
|
(fx/merge
|
||||||
|
cofx
|
||||||
;; If we don't have any hidden message or the hidden message is before
|
;; If we don't have any hidden message or the hidden message is before
|
||||||
;; this one, we add the message to the UI
|
;; this one, we add the message to the UI
|
||||||
(if (or (not @view.state/first-not-visible-item)
|
(if (or (not @view.state/first-not-visible-item)
|
||||||
(<= (:clock-value @view.state/first-not-visible-item)
|
(<= (:clock-value @view.state/first-not-visible-item)
|
||||||
clock-value))
|
clock-value))
|
||||||
(add-message cofx {:message message
|
(add-message {:message message
|
||||||
:seen-by-user? (and current-chat?
|
:seen-by-user? (and current-chat?
|
||||||
(= view-id :chat))})
|
(= view-id :chat))})
|
||||||
;; Not in the current view, set all-loaded to false
|
;; Not in the current view, set all-loaded to false
|
||||||
|
@ -90,7 +102,8 @@
|
||||||
(>= clock-value cursor-clock-value)
|
(>= clock-value cursor-clock-value)
|
||||||
(update-in [:chats chat-id] assoc
|
(update-in [:chats chat-id] assoc
|
||||||
:cursor (chat-loading/clock-value->cursor clock-value)
|
:cursor (chat-loading/clock-value->cursor clock-value)
|
||||||
:cursor-clock-value clock-value))}))))
|
:cursor-clock-value clock-value))})
|
||||||
|
(add-sender-to-chat-users message)))))
|
||||||
|
|
||||||
(defn- message-loaded?
|
(defn- message-loaded?
|
||||||
[{:keys [db]} {:keys [chat-id message-id]}]
|
[{:keys [db]} {:keys [chat-id message-id]}]
|
||||||
|
|
|
@ -22,7 +22,11 @@
|
||||||
:chats {chat-id {:cursor cursor
|
:chats {chat-id {:cursor cursor
|
||||||
:cursor-clock-value cursor-clock-value}}}}
|
:cursor-clock-value cursor-clock-value}}}}
|
||||||
message {:chat-id chat-id
|
message {:chat-id chat-id
|
||||||
:clock-value clock-value}]
|
:clock-value clock-value
|
||||||
|
:alias "alias"
|
||||||
|
:name "name"
|
||||||
|
:identicon "identicon"
|
||||||
|
:from "from"}]
|
||||||
(testing "not current-chat"
|
(testing "not current-chat"
|
||||||
(is (nil? (message/add-received-message
|
(is (nil? (message/add-received-message
|
||||||
(update cofx :db dissoc :loaded-chat-id)
|
(update cofx :db dissoc :loaded-chat-id)
|
||||||
|
@ -32,7 +36,20 @@
|
||||||
;; <- top of the chat
|
;; <- top of the chat
|
||||||
(testing "there's no hidden item"
|
(testing "there's no hidden item"
|
||||||
(with-redefs [view.state/first-not-visible-item (atom nil)]
|
(with-redefs [view.state/first-not-visible-item (atom nil)]
|
||||||
(is (= :added (message/add-received-message
|
(is (= {:db {:loaded-chat-id "chat-id",
|
||||||
|
:current-chat-id "chat-id",
|
||||||
|
:all-loaded? true,
|
||||||
|
:chats
|
||||||
|
{"chat-id"
|
||||||
|
{:cursor
|
||||||
|
"00000000000000000000000000000000000000000000000000090x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
:cursor-clock-value 9,
|
||||||
|
:users
|
||||||
|
{"alias" {:alias "alias",
|
||||||
|
:name "name",
|
||||||
|
:identicon "identicon",
|
||||||
|
:public-key "from"}}}}}}
|
||||||
|
(message/add-received-message
|
||||||
cofx
|
cofx
|
||||||
message)))))
|
message)))))
|
||||||
;; <- cursor
|
;; <- cursor
|
||||||
|
@ -41,7 +58,20 @@
|
||||||
;; <- top of the chat
|
;; <- top of the chat
|
||||||
(testing "the hidden item has a clock value less than the current"
|
(testing "the hidden item has a clock value less than the current"
|
||||||
(with-redefs [view.state/first-not-visible-item (atom {:clock-value (dec clock-value)})]
|
(with-redefs [view.state/first-not-visible-item (atom {:clock-value (dec clock-value)})]
|
||||||
(is (= :added (message/add-received-message
|
(is (= {:db {:loaded-chat-id "chat-id",
|
||||||
|
:current-chat-id "chat-id",
|
||||||
|
:all-loaded? true,
|
||||||
|
:chats
|
||||||
|
{"chat-id"
|
||||||
|
{:cursor
|
||||||
|
"00000000000000000000000000000000000000000000000000090x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
:cursor-clock-value 9,
|
||||||
|
:users
|
||||||
|
{"alias" {:alias "alias",
|
||||||
|
:name "name",
|
||||||
|
:identicon "identicon",
|
||||||
|
:public-key "from"}}}}}}
|
||||||
|
(message/add-received-message
|
||||||
cofx
|
cofx
|
||||||
message)))))
|
message)))))
|
||||||
;; <- cursor
|
;; <- cursor
|
||||||
|
|
|
@ -116,6 +116,7 @@
|
||||||
[{:keys [db] :as cofx} public-key]
|
[{:keys [db] :as cofx} public-key]
|
||||||
(when (not= (get-in db [:multiaccount :public-key]) public-key)
|
(when (not= (get-in db [:multiaccount :public-key]) public-key)
|
||||||
(let [contact (build-contact cofx public-key)]
|
(let [contact (build-contact cofx public-key)]
|
||||||
|
(log/info "create contact" contact)
|
||||||
(fx/merge cofx
|
(fx/merge cofx
|
||||||
{:db (dissoc db :contacts/new-identity)}
|
{:db (dissoc db :contacts/new-identity)}
|
||||||
(upsert-contact contact)))))
|
(upsert-contact contact)))))
|
||||||
|
@ -176,15 +177,13 @@
|
||||||
(fx/defn name-verified
|
(fx/defn name-verified
|
||||||
{:events [:contacts/ens-name-verified]}
|
{:events [:contacts/ens-name-verified]}
|
||||||
[{:keys [db now] :as cofx} public-key ens-name]
|
[{:keys [db now] :as cofx} public-key ens-name]
|
||||||
(fx/merge cofx
|
(let [contact (-> (get-in db [:contacts/contacts public-key]
|
||||||
{:db (update-in db [:contacts/contacts public-key]
|
(build-contact cofx public-key))
|
||||||
merge
|
(assoc :name ens-name
|
||||||
{:name ens-name
|
|
||||||
:last-ens-clock-value now
|
:last-ens-clock-value now
|
||||||
:ens-verified-at now
|
:ens-verified-at now
|
||||||
:ens-verified true})}
|
:ens-verified true))]
|
||||||
|
(upsert-contact cofx contact)))
|
||||||
(upsert-contact {:public-key public-key})))
|
|
||||||
|
|
||||||
(fx/defn update-nickname
|
(fx/defn update-nickname
|
||||||
{:events [:contacts/update-nickname]}
|
{:events [:contacts/update-nickname]}
|
||||||
|
|
|
@ -500,6 +500,11 @@
|
||||||
(fn [cofx [_ chat-id message-id]]
|
(fn [cofx [_ chat-id message-id]]
|
||||||
(chat.message/toggle-expand-message cofx chat-id message-id)))
|
(chat.message/toggle-expand-message cofx chat-id message-id)))
|
||||||
|
|
||||||
|
(handlers/register-handler-fx
|
||||||
|
:chat.ui/select-mention
|
||||||
|
(fn [cofx [_ mention]]
|
||||||
|
(chat.input/select-mention cofx mention)))
|
||||||
|
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
:chat.ui/set-chat-input-text
|
:chat.ui/set-chat-input-text
|
||||||
(fn [cofx [_ text]]
|
(fn [cofx [_ text]]
|
||||||
|
|
|
@ -118,6 +118,8 @@
|
||||||
(reg-root-key-sub :chat/memberships :chat/memberships)
|
(reg-root-key-sub :chat/memberships :chat/memberships)
|
||||||
(reg-root-key-sub :camera-roll-photos :camera-roll-photos)
|
(reg-root-key-sub :camera-roll-photos :camera-roll-photos)
|
||||||
(reg-root-key-sub :group-chat/invitations :group-chat/invitations)
|
(reg-root-key-sub :group-chat/invitations :group-chat/invitations)
|
||||||
|
(reg-root-key-sub :chats/mention-suggestions :chats/mention-suggestions)
|
||||||
|
(reg-root-key-sub :chats/cursor :chats/cursor)
|
||||||
|
|
||||||
;;browser
|
;;browser
|
||||||
(reg-root-key-sub :browsers :browser/browsers)
|
(reg-root-key-sub :browsers :browser/browsers)
|
||||||
|
@ -854,6 +856,43 @@
|
||||||
(wallet.db/get-confirmations current-block)
|
(wallet.db/get-confirmations current-block)
|
||||||
(>= transactions/confirmations-count-threshold))})))
|
(>= transactions/confirmations-count-threshold))})))
|
||||||
|
|
||||||
|
(re-frame/reg-sub
|
||||||
|
:chats/mentionable-contacts
|
||||||
|
:<- [:contacts/contacts]
|
||||||
|
(fn [contacts]
|
||||||
|
(reduce
|
||||||
|
(fn [acc [key {:keys [alias name identicon]}]]
|
||||||
|
(if (and alias (not= alias ""))
|
||||||
|
(let [name (string/replace name ".stateofus.eth" "")]
|
||||||
|
(assoc acc alias {:alias alias
|
||||||
|
:name (or name alias)
|
||||||
|
:identicon identicon
|
||||||
|
:public-key key}))
|
||||||
|
acc))
|
||||||
|
{}
|
||||||
|
contacts)))
|
||||||
|
|
||||||
|
(re-frame/reg-sub
|
||||||
|
:chats/mentionable-users
|
||||||
|
:<- [:chats/current-chat]
|
||||||
|
:<- [:chats/mentionable-contacts]
|
||||||
|
(fn [[{:keys [users]} contacts]]
|
||||||
|
(merge users contacts)))
|
||||||
|
|
||||||
|
(re-frame/reg-sub
|
||||||
|
:chat/mention-suggestions
|
||||||
|
:<- [:chats/current-chat-id]
|
||||||
|
:<- [:chats/mention-suggestions]
|
||||||
|
(fn [[chat-id mentions]]
|
||||||
|
(take 15 (get mentions chat-id))))
|
||||||
|
|
||||||
|
(re-frame/reg-sub
|
||||||
|
:chat/cursor
|
||||||
|
:<- [:chats/current-chat-id]
|
||||||
|
:<- [:chats/cursor]
|
||||||
|
(fn [[chat-id cursor]]
|
||||||
|
(get cursor chat-id)))
|
||||||
|
|
||||||
;;BOOTNODES ============================================================================================================
|
;;BOOTNODES ============================================================================================================
|
||||||
|
|
||||||
(re-frame/reg-sub
|
(re-frame/reg-sub
|
||||||
|
|
|
@ -13,7 +13,12 @@
|
||||||
[status-im.utils.config :as config]
|
[status-im.utils.config :as config]
|
||||||
[re-frame.core :as re-frame]
|
[re-frame.core :as re-frame]
|
||||||
[status-im.i18n :as i18n]
|
[status-im.i18n :as i18n]
|
||||||
[clojure.string :as string]))
|
[clojure.string :as string]
|
||||||
|
[status-im.chat.models.mentions :as mentions]
|
||||||
|
[status-im.ui.components.list.views :as list]
|
||||||
|
[quo.components.list.item :as list-item]
|
||||||
|
[status-im.ui.screens.chat.styles.photos :as photo-style]
|
||||||
|
[reagent.core :as reagent]))
|
||||||
|
|
||||||
(def panel->icons {:extensions :main-icons/commands
|
(def panel->icons {:extensions :main-icons/commands
|
||||||
:images :main-icons/photo})
|
:images :main-icons/photo})
|
||||||
|
@ -73,9 +78,13 @@
|
||||||
:accessibility-label :send-message-button
|
:accessibility-label :send-message-button
|
||||||
:color (styles/send-icon-color)}]]])
|
:color (styles/send-icon-color)}]]])
|
||||||
|
|
||||||
(defn text-input [{:keys [cooldown-enabled? text-value on-text-change set-active-panel text-input-ref]}]
|
(defn text-input
|
||||||
|
[{:keys [cooldown-enabled? text-value on-text-change set-active-panel text-input-ref]}]
|
||||||
|
(let [cursor @(re-frame/subscribe [:chat/cursor])
|
||||||
|
mentionable-users @(re-frame/subscribe [:chats/mentionable-users])]
|
||||||
[rn/view {:style (styles/text-input-wrapper)}
|
[rn/view {:style (styles/text-input-wrapper)}
|
||||||
[rn/text-input {:style (styles/text-input)
|
[rn/text-input
|
||||||
|
{:style (styles/text-input)
|
||||||
:ref text-input-ref
|
:ref text-input-ref
|
||||||
:maxFontSizeMultiplier 1
|
:maxFontSizeMultiplier 1
|
||||||
:accessibility-label :chat-message-input
|
:accessibility-label :chat-message-input
|
||||||
|
@ -86,21 +95,110 @@
|
||||||
:blur-on-submit false
|
:blur-on-submit false
|
||||||
:auto-focus false
|
:auto-focus false
|
||||||
:on-focus #(set-active-panel nil)
|
:on-focus #(set-active-panel nil)
|
||||||
:on-change #(on-text-change (.-text ^js (.-nativeEvent ^js %)))
|
|
||||||
:max-length chat.constants/max-text-size
|
:max-length chat.constants/max-text-size
|
||||||
:placeholder-text-color (:text-02 @colors/theme)
|
:placeholder-text-color (:text-02 @colors/theme)
|
||||||
:placeholder (if cooldown-enabled?
|
:placeholder (if cooldown-enabled?
|
||||||
(i18n/label :cooldown/text-input-disabled)
|
(i18n/label :cooldown/text-input-disabled)
|
||||||
(i18n/label :t/type-a-message))
|
(i18n/label :t/type-a-message))
|
||||||
:underlineColorAndroid :transparent
|
:underlineColorAndroid :transparent
|
||||||
:auto-capitalize :sentences}]])
|
:auto-capitalize :sentences
|
||||||
|
:selection
|
||||||
|
;; NOTE(rasom): In case if mention is added on pressing suggestion and
|
||||||
|
;; it is placed inside some text we have to specify `:selection` on
|
||||||
|
;; Android to ensure that cursor is added after the mention, not after
|
||||||
|
;; the last char in input. On iOS it works that way without this code
|
||||||
|
(when (and cursor platform/android?)
|
||||||
|
(clj->js
|
||||||
|
{:start cursor
|
||||||
|
:end cursor}))
|
||||||
|
|
||||||
|
:on-selection-change
|
||||||
|
(fn [_]
|
||||||
|
;; NOTE(rasom): we have to reset `cursor` value when user starts using
|
||||||
|
;; text-input because otherwise cursor will stay in the same position
|
||||||
|
(when (and cursor platform/android?)
|
||||||
|
(re-frame/dispatch [::mentions/clear-cursor])))
|
||||||
|
|
||||||
|
:on-change
|
||||||
|
(fn [args]
|
||||||
|
(let [text (.-text ^js (.-nativeEvent ^js args))]
|
||||||
|
(on-text-change text)
|
||||||
|
;; NOTE(rasom): on iOS `on-change` is dispatched after `on-text-input`,
|
||||||
|
;; that's why mention suggestions are calculated on `on-change`
|
||||||
|
(when platform/ios?
|
||||||
|
(re-frame/dispatch [::mentions/calculate-suggestions mentionable-users]))))
|
||||||
|
|
||||||
|
:on-text-input
|
||||||
|
(fn [args]
|
||||||
|
(let [native-event (.-nativeEvent ^js args)
|
||||||
|
text (.-text ^js native-event)
|
||||||
|
previous-text (.-previousText ^js native-event)
|
||||||
|
range (.-range ^js native-event)
|
||||||
|
start (.-start ^js range)
|
||||||
|
end (.-end ^js range)]
|
||||||
|
(re-frame/dispatch
|
||||||
|
[::mentions/on-text-input
|
||||||
|
{:new-text text
|
||||||
|
:previous-text previous-text
|
||||||
|
:start start
|
||||||
|
:end end}])
|
||||||
|
;; NOTE(rasom): on Android `on-text-input` is dispatched after
|
||||||
|
;; `on-change`, that's why mention suggestions are calculated
|
||||||
|
;; on `on-change`
|
||||||
|
(when platform/android?
|
||||||
|
(re-frame/dispatch [::mentions/calculate-suggestions mentionable-users]))))}]]))
|
||||||
|
|
||||||
|
(defn mention-item
|
||||||
|
[[_ {:keys [identicon alias name] :as user}]]
|
||||||
|
(let [title name
|
||||||
|
subtitle? (not= alias name)]
|
||||||
|
[list-item/list-item
|
||||||
|
(cond-> {:icon
|
||||||
|
[rn/view {:style {}}
|
||||||
|
[rn/image
|
||||||
|
{:source {:uri identicon}
|
||||||
|
:style (photo-style/photo-border
|
||||||
|
photo-style/default-size
|
||||||
|
nil)
|
||||||
|
:resize-mode :cover}]]
|
||||||
|
:icon-container-style {}
|
||||||
|
:size :small
|
||||||
|
:text-size :small
|
||||||
|
:title title
|
||||||
|
:title-text-weight :medium
|
||||||
|
:on-press
|
||||||
|
(fn []
|
||||||
|
(re-frame/dispatch [:chat.ui/select-mention user]))}
|
||||||
|
|
||||||
|
subtitle?
|
||||||
|
(assoc :subtitle alias))]))
|
||||||
|
|
||||||
|
(def chat-input-height (reagent/atom nil))
|
||||||
|
|
||||||
|
(defn autocomplete-mentions []
|
||||||
|
(let [suggestions @(re-frame/subscribe [:chat/mention-suggestions])]
|
||||||
|
(when (and (seq suggestions) @chat-input-height)
|
||||||
|
(let [height (+ 16 (* 52 (min 4.5 (count suggestions))))]
|
||||||
|
[rn/view
|
||||||
|
{:style (styles/autocomplete-container @chat-input-height)}
|
||||||
|
[rn/view
|
||||||
|
{:style {:height height}}
|
||||||
|
[list/flat-list
|
||||||
|
{:keyboardShouldPersistTaps :always
|
||||||
|
:footer [rn/view {:style {:height 8}}]
|
||||||
|
:header [rn/view {:style {:height 8}}]
|
||||||
|
:data suggestions
|
||||||
|
:key-fn first
|
||||||
|
:render-fn #(mention-item %)}]]]))))
|
||||||
|
|
||||||
(defn chat-input
|
(defn chat-input
|
||||||
[{:keys [set-active-panel active-panel on-send-press reply
|
[{:keys [set-active-panel active-panel on-send-press reply
|
||||||
show-send show-image show-stickers show-extensions
|
show-send show-image show-stickers show-extensions
|
||||||
sending-image input-focus show-audio]
|
sending-image input-focus show-audio]
|
||||||
:as props}]
|
:as props}]
|
||||||
[rn/view {:style (styles/toolbar)}
|
[rn/view {:style (styles/toolbar)
|
||||||
|
:on-layout #(reset! chat-input-height
|
||||||
|
(-> ^js % .-nativeEvent .-layout .-height))}
|
||||||
[rn/view {:style (styles/actions-wrapper (and (not show-extensions)
|
[rn/view {:style (styles/actions-wrapper (and (not show-extensions)
|
||||||
(not show-image)))}
|
(not show-image)))}
|
||||||
(when show-extensions
|
(when show-extensions
|
||||||
|
@ -113,7 +211,14 @@
|
||||||
:accessibility-label :show-photo-icon
|
:accessibility-label :show-photo-icon
|
||||||
:active active-panel
|
:active active-panel
|
||||||
:set-active set-active-panel}])]
|
:set-active set-active-panel}])]
|
||||||
[animated/view {:style (styles/input-container)}
|
[:<>
|
||||||
|
;; NOTE(rasom): on iOS `autocomplete-mentions` should be placed inside
|
||||||
|
;; `chat-input` (otherwise suggestions will be hidden by keyboard) but
|
||||||
|
;; outside animated view below because it adds horizontal margin
|
||||||
|
(when platform/ios?
|
||||||
|
[autocomplete-mentions])
|
||||||
|
[animated/view
|
||||||
|
{:style (styles/input-container)}
|
||||||
(when reply
|
(when reply
|
||||||
[reply/reply-message reply])
|
[reply/reply-message reply])
|
||||||
(when sending-image
|
(when sending-image
|
||||||
|
@ -134,19 +239,20 @@
|
||||||
:accessibility-label :show-audio-message-icon
|
:accessibility-label :show-audio-message-icon
|
||||||
:active active-panel
|
:active active-panel
|
||||||
:input-focus input-focus
|
:input-focus input-focus
|
||||||
:set-active set-active-panel}])]]]])
|
:set-active set-active-panel}])]]]]])
|
||||||
|
|
||||||
(defn chat-toolbar []
|
(defn chat-toolbar []
|
||||||
(let [previous-layout (atom nil)
|
(let [previous-layout (atom nil)
|
||||||
had-reply (atom nil)]
|
had-reply (atom nil)]
|
||||||
(fn [{:keys [active-panel set-active-panel text-input-ref]}]
|
(fn [{:keys [active-panel set-active-panel text-input-ref on-text-change]}]
|
||||||
(let [disconnected? @(re-frame/subscribe [:disconnected?])
|
(let [disconnected? @(re-frame/subscribe [:disconnected?])
|
||||||
{:keys [processing]} @(re-frame/subscribe [:multiaccounts/login])
|
{:keys [processing]} @(re-frame/subscribe [:multiaccounts/login])
|
||||||
mainnet? @(re-frame/subscribe [:mainnet?])
|
mainnet? @(re-frame/subscribe [:mainnet?])
|
||||||
input-text @(re-frame/subscribe [:chats/current-chat-input-text])
|
input-text @(re-frame/subscribe [:chats/current-chat-input-text])
|
||||||
cooldown-enabled? @(re-frame/subscribe [:chats/cooldown-enabled?])
|
cooldown-enabled? @(re-frame/subscribe [:chats/cooldown-enabled?])
|
||||||
one-to-one-chat? @(re-frame/subscribe [:current-chat/one-to-one-chat?])
|
one-to-one-chat? @(re-frame/subscribe [:current-chat/one-to-one-chat?])
|
||||||
public? @(re-frame/subscribe [:current-chat/public?])
|
{:keys [public?
|
||||||
|
chat-id]} @(re-frame/subscribe [:current-chat/metadata])
|
||||||
reply @(re-frame/subscribe [:chats/reply-message])
|
reply @(re-frame/subscribe [:chats/reply-message])
|
||||||
sending-image @(re-frame/subscribe [:chats/sending-image])
|
sending-image @(re-frame/subscribe [:chats/sending-image])
|
||||||
input-focus (fn []
|
input-focus (fn []
|
||||||
|
@ -189,11 +295,12 @@
|
||||||
:on-send-press #(do (re-frame/dispatch [:chat.ui/send-current-message])
|
:on-send-press #(do (re-frame/dispatch [:chat.ui/send-current-message])
|
||||||
(clear-input))
|
(clear-input))
|
||||||
:text-value input-text
|
:text-value input-text
|
||||||
:on-text-change #(re-frame/dispatch [:chat.ui/set-chat-input-text %])
|
:on-text-change on-text-change
|
||||||
:cooldown-enabled? cooldown-enabled?
|
:cooldown-enabled? cooldown-enabled?
|
||||||
:show-send show-send
|
:show-send show-send
|
||||||
:show-stickers show-stickers
|
:show-stickers show-stickers
|
||||||
:show-image show-image
|
:show-image show-image
|
||||||
:show-audio show-audio
|
:show-audio show-audio
|
||||||
:sending-image sending-image
|
:sending-image sending-image
|
||||||
:show-extensions show-extensions}]))))
|
:show-extensions show-extensions
|
||||||
|
:chat-id chat-id}]))))
|
||||||
|
|
|
@ -109,3 +109,13 @@
|
||||||
|
|
||||||
(defn send-icon-color []
|
(defn send-icon-color []
|
||||||
colors/white)
|
colors/white)
|
||||||
|
|
||||||
|
(defn autocomplete-container [bottom]
|
||||||
|
{:position :absolute
|
||||||
|
:left 0
|
||||||
|
:right 0
|
||||||
|
:bottom bottom
|
||||||
|
:background-color (colors/get-color :ui-background)
|
||||||
|
:flex-direction :column
|
||||||
|
:border-top-width 1
|
||||||
|
:border-top-color (colors/get-color :ui-01)})
|
||||||
|
|
|
@ -194,13 +194,13 @@
|
||||||
[react/view (style/message-author-userpic outgoing)
|
[react/view (style/message-author-userpic outgoing)
|
||||||
(when first-in-group?
|
(when first-in-group?
|
||||||
[react/touchable-highlight {:on-press #(do (when modal (close-modal))
|
[react/touchable-highlight {:on-press #(do (when modal (close-modal))
|
||||||
(re-frame/dispatch [:chat.ui/show-profile from]))}
|
(re-frame/dispatch [:chat.ui/show-profile-without-adding-contact from]))}
|
||||||
[photos/member-identicon identicon]])])
|
[photos/member-identicon identicon]])])
|
||||||
[react/view {:style (style/message-author-wrapper outgoing display-photo?)}
|
[react/view {:style (style/message-author-wrapper outgoing display-photo?)}
|
||||||
(when display-username?
|
(when display-username?
|
||||||
[react/touchable-opacity {:style style/message-author-touchable
|
[react/touchable-opacity {:style style/message-author-touchable
|
||||||
:on-press #(do (when modal (close-modal))
|
:on-press #(do (when modal (close-modal))
|
||||||
(re-frame/dispatch [:chat.ui/show-profile from]))}
|
(re-frame/dispatch [:chat.ui/show-profile-without-adding-contact from]))}
|
||||||
[message-author-name from modal]])
|
[message-author-name from modal]])
|
||||||
;;MESSAGE CONTENT
|
;;MESSAGE CONTENT
|
||||||
[react/view
|
[react/view
|
||||||
|
@ -310,7 +310,7 @@
|
||||||
(on-long-press
|
(on-long-press
|
||||||
(when-not outgoing
|
(when-not outgoing
|
||||||
[{:on-press #(when pack
|
[{:on-press #(when pack
|
||||||
(re-frame/dispatch [:chat.ui/show-profile from]))
|
(re-frame/dispatch [:chat.ui/show-profile-without-adding-contact from]))
|
||||||
:label (i18n/label :t/view-details)}])))})
|
:label (i18n/label :t/view-details)}])))})
|
||||||
[react/image {:style {:margin 10 :width 140 :height 140}
|
[react/image {:style {:margin 10 :width 140 :height 140}
|
||||||
;;TODO (perf) move to event
|
;;TODO (perf) move to event
|
||||||
|
|
|
@ -9,13 +9,15 @@
|
||||||
{:position :relative
|
{:position :relative
|
||||||
:border-radius (radius size)})
|
:border-radius (radius size)})
|
||||||
|
|
||||||
(defn photo-border [size]
|
(defn photo-border
|
||||||
{:position :absolute
|
([size] (photo-border size :absolute))
|
||||||
|
([size position]
|
||||||
|
{:position position
|
||||||
:width size
|
:width size
|
||||||
:height size
|
:height size
|
||||||
:border-color colors/black-transparent
|
:border-color colors/black-transparent
|
||||||
:border-width 1
|
:border-width 1
|
||||||
:border-radius (radius size)})
|
:border-radius (radius size)}))
|
||||||
|
|
||||||
(defn photo [size]
|
(defn photo [size]
|
||||||
{:border-radius (radius size)
|
{:border-radius (radius size)
|
||||||
|
|
|
@ -276,7 +276,8 @@
|
||||||
(reset! active-panel panel)
|
(reset! active-panel panel)
|
||||||
(reagent/flush)
|
(reagent/flush)
|
||||||
(when panel
|
(when panel
|
||||||
(js/setTimeout #(react/dismiss-keyboard!) 100)))]
|
(js/setTimeout #(react/dismiss-keyboard!) 100)))
|
||||||
|
on-text-change #(re-frame/dispatch [:chat.ui/set-chat-input-text %])]
|
||||||
(fn []
|
(fn []
|
||||||
(let [{:keys [chat-id show-input? group-chat admins invitation-admin] :as current-chat}
|
(let [{:keys [chat-id show-input? group-chat admins invitation-admin] :as current-chat}
|
||||||
@(re-frame/subscribe [:chats/current-chat])]
|
@(re-frame/subscribe [:chats/current-chat])]
|
||||||
|
@ -295,13 +296,21 @@
|
||||||
[accessory/view {:y position-y
|
[accessory/view {:y position-y
|
||||||
:on-update-inset on-update}
|
:on-update-inset on-update}
|
||||||
[invitation-bar chat-id]])
|
[invitation-bar chat-id]])
|
||||||
|
;; NOTE(rasom): on android we have to place `autocomplete-mentions`
|
||||||
|
;; outside `accessory/view` because otherwise :keyboardShouldPersistTaps
|
||||||
|
;; :always doesn't work and keyboard is hidden on pressing suggestion.
|
||||||
|
;; Scrolling of suggestions doesn't work neither in this case.
|
||||||
|
(when platform/android?
|
||||||
|
[components/autocomplete-mentions])
|
||||||
(when show-input?
|
(when show-input?
|
||||||
[accessory/view {:y position-y
|
[accessory/view {:y position-y
|
||||||
:pan-state pan-state
|
:pan-state pan-state
|
||||||
:has-panel (boolean @active-panel)
|
:has-panel (boolean @active-panel)
|
||||||
:on-close #(set-active-panel nil)
|
:on-close #(set-active-panel nil)
|
||||||
:on-update-inset on-update}
|
:on-update-inset on-update}
|
||||||
[components/chat-toolbar {:active-panel @active-panel
|
[components/chat-toolbar
|
||||||
|
{:active-panel @active-panel
|
||||||
:set-active-panel set-active-panel
|
:set-active-panel set-active-panel
|
||||||
:text-input-ref text-input-ref}]
|
:text-input-ref text-input-ref
|
||||||
|
:on-text-change on-text-change}]
|
||||||
[bottom-sheet @active-panel]])]))))
|
[bottom-sheet @active-panel]])]))))
|
||||||
|
|
|
@ -13,12 +13,10 @@
|
||||||
[status-im.ui.components.icons.vector-icons :as icons]
|
[status-im.ui.components.icons.vector-icons :as icons]
|
||||||
[status-im.utils.contenthash :as contenthash]
|
[status-im.utils.contenthash :as contenthash]
|
||||||
[status-im.utils.core :as utils]
|
[status-im.utils.core :as utils]
|
||||||
[status-im.utils.datetime :as time])
|
[status-im.utils.datetime :as time]))
|
||||||
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
|
|
||||||
|
|
||||||
(defview mention-element [from]
|
(defn mention-element [from]
|
||||||
(letsubs [contact-name [:contacts/contact-name-by-identity from]]
|
@(re-frame/subscribe [:contacts/contact-name-by-identity from]))
|
||||||
contact-name))
|
|
||||||
|
|
||||||
;; if truncated subheader text is too short we won't get ellipsize at the end of text
|
;; if truncated subheader text is too short we won't get ellipsize at the end of text
|
||||||
(def max-subheader-length 100)
|
(def max-subheader-length 100)
|
||||||
|
|
Loading…
Reference in New Issue