mirror of
https://github.com/status-im/status-react.git
synced 2025-01-11 11:34:45 +00:00
Mentions suggestions
This commit is contained in:
parent
0675d0d8d7
commit
16fecc2ac6
@ -64,10 +64,11 @@
|
||||
:justify-content :space-between})}]
|
||||
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
|
||||
(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
|
||||
(vector? icon)
|
||||
icon
|
||||
@ -83,7 +84,7 @@
|
||||
|
||||
(defn title-column
|
||||
[{: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)
|
||||
{:justify-content :center
|
||||
:flex 1})}
|
||||
@ -93,28 +94,31 @@
|
||||
[:<>
|
||||
;; FIXME(Ferossgp): ReactNative 63 will support view inside text on andrid, remove thess if when migrating
|
||||
(if (string? title)
|
||||
[text/text {:weight :medium
|
||||
[text/text {:weight (or title-text-weight :medium)
|
||||
:style {:color text-color}
|
||||
:accessibility-label title-accessibility-label
|
||||
:ellipsize-mode :tail
|
||||
:number-of-lines 1}
|
||||
:number-of-lines 1
|
||||
:size text-size}
|
||||
title]
|
||||
title)
|
||||
(if (string? subtitle)
|
||||
[text/text {:weight :regular
|
||||
:color :secondary
|
||||
:ellipsize-mode :tail
|
||||
:number-of-lines subtitle-max-lines}
|
||||
:number-of-lines subtitle-max-lines
|
||||
:size text-size}
|
||||
subtitle]
|
||||
subtitle)]
|
||||
|
||||
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}
|
||||
:title-accessibility-label title-accessibility-label
|
||||
:ellipsize-mode :tail
|
||||
:size (size->single-title-size size)}
|
||||
:size (or text-size (size->single-title-size size))}
|
||||
title]
|
||||
title))])
|
||||
|
||||
@ -147,10 +151,10 @@
|
||||
:color (:icon-02 @colors/theme)}]])]))
|
||||
|
||||
(defn list-item
|
||||
[{:keys [theme accessory disabled subtitle-max-lines icon title
|
||||
subtitle active on-press on-long-press chevron size
|
||||
[{:keys [theme accessory disabled subtitle-max-lines icon icon-container-style
|
||||
title subtitle active on-press on-long-press chevron size text-size
|
||||
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
|
||||
theme :main
|
||||
haptic-feedback true
|
||||
@ -191,8 +195,11 @@
|
||||
:icon-bg-color icon-bg-color
|
||||
:title-accessibility-label title-accessibility-label
|
||||
:icon icon
|
||||
:icon-container-style icon-container-style
|
||||
:title title
|
||||
:title-text-weight title-text-weight
|
||||
:size size
|
||||
:text-size text-size
|
||||
:subtitle subtitle
|
||||
:subtitle-max-lines subtitle-max-lines}]
|
||||
[right-side {:chevron chevron
|
||||
|
@ -29,6 +29,18 @@
|
||||
[{{:keys [current-chat-id] :as db} :db} 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]
|
||||
{:dispatch-later [{:dispatch [:chat/disable-cooldown]
|
||||
:ms (chat.constants/cooldown-periods-ms
|
||||
@ -135,4 +147,6 @@
|
||||
input-text-with-mentions (mentions/check-mentions cofx input-text)]
|
||||
(fx/merge cofx
|
||||
(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))))
|
||||
|
@ -81,35 +81,46 @@
|
||||
(and (get-in db [:pagination-info current-chat-id :messages-initialized?])
|
||||
(not= session-id
|
||||
(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] #{})
|
||||
users (get-in db [:chats current-chat-id :users] {})
|
||||
;; We remove those messages that are already loaded, as we might get some duplicates
|
||||
{:keys [all-messages
|
||||
new-messages
|
||||
last-clock-value
|
||||
unviewed-message-ids]} (reduce (fn [{:keys [last-clock-value all-messages] :as acc}
|
||||
{:keys [clock-value seen message-id] :as message}]
|
||||
(cond-> acc
|
||||
(or (nil? last-clock-value)
|
||||
(> last-clock-value clock-value))
|
||||
(assoc :last-clock-value clock-value)
|
||||
unviewed-message-ids
|
||||
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
|
||||
(and alias (not= alias ""))
|
||||
(update :users assoc alias {:alias alias
|
||||
:name (or name alias)
|
||||
:identicon identicon
|
||||
:public-key from})
|
||||
(or (nil? last-clock-value)
|
||||
(> last-clock-value clock-value))
|
||||
(assoc :last-clock-value clock-value)
|
||||
|
||||
(not seen)
|
||||
(update :unviewed-message-ids conj message-id)
|
||||
(not seen)
|
||||
(update :unviewed-message-ids conj message-id)
|
||||
|
||||
(nil? (get all-messages message-id))
|
||||
(update :new-messages conj message)
|
||||
(nil? (get all-messages message-id))
|
||||
(update :new-messages conj message)
|
||||
|
||||
:always
|
||||
(update :all-messages assoc message-id message)))
|
||||
{:all-messages already-loaded-messages
|
||||
:unviewed-message-ids loaded-unviewed-messages-ids
|
||||
:new-messages []}
|
||||
messages)]
|
||||
:always
|
||||
(update :all-messages assoc message-id message)))
|
||||
{:all-messages already-loaded-messages
|
||||
:unviewed-message-ids loaded-unviewed-messages-ids
|
||||
:users users
|
||||
:new-messages []}
|
||||
messages)]
|
||||
(fx/merge cofx
|
||||
{:db (-> db
|
||||
(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 :users] users)
|
||||
(assoc-in [:pagination-info current-chat-id :loading-messages?] false)
|
||||
(assoc-in [:messages current-chat-id] all-messages)
|
||||
(update-in [:message-lists current-chat-id] message-list/add-many new-messages)
|
||||
|
@ -1,11 +1,16 @@
|
||||
(ns status-im.chat.models.mentions
|
||||
(: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
|
||||
[{{:keys [current-chat-id messages]
|
||||
[{{:keys [current-chat-id]
|
||||
: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])
|
||||
chat-specific-suggestions
|
||||
(cond
|
||||
@ -28,17 +33,7 @@
|
||||
{}
|
||||
group-contacts))
|
||||
|
||||
(and group-chat public?)
|
||||
(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 {})]
|
||||
:else users)]
|
||||
(reduce
|
||||
(fn [acc [key {:keys [alias name identicon]}]]
|
||||
(let [name (string/replace name ".stateofus.eth" "")]
|
||||
@ -49,7 +44,9 @@
|
||||
chat-specific-suggestions
|
||||
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]
|
||||
(let [lcase-name (string/lower-case name)
|
||||
@ -57,47 +54,64 @@
|
||||
regex (re-pattern
|
||||
(string/join
|
||||
"|"
|
||||
[(str "^" lcase-name "\\s")
|
||||
[(str "^" lcase-name ending-chars)
|
||||
(str "^" lcase-name "$")
|
||||
(str "^" lcase-alias "\\s")
|
||||
(str "^" lcase-alias ending-chars)
|
||||
(str "^" lcase-alias "$")]))
|
||||
lcase-text (string/lower-case 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
|
||||
([text users mention-key-idx]
|
||||
(match-mention text users mention-key-idx (inc mention-key-idx) []))
|
||||
([text users mention-key-idx next-word-idx words]
|
||||
(let [trimmed-text (subs text next-word-idx)]
|
||||
(when-let [word (string/trim (re-find word-regex trimmed-text))]
|
||||
(let [words (conj words word)
|
||||
searched-text (string/lower-case (string/join " " words))
|
||||
suggestions (filter
|
||||
(fn [[_ {:keys [alias name]}]]
|
||||
(let [names (set [alias name])]
|
||||
(some
|
||||
(fn [username]
|
||||
(string/starts-with?
|
||||
(string/lower-case username)
|
||||
searched-text))
|
||||
names)))
|
||||
users)
|
||||
suggestions-cnt (count suggestions)]
|
||||
(cond (zero? suggestions-cnt)
|
||||
nil
|
||||
(when-let [word (re-find word-regex (subs text next-word-idx))]
|
||||
(let [new-words (conj words word)
|
||||
searched-text (let [text (-> new-words
|
||||
string/join
|
||||
string/lower-case
|
||||
string/trim)
|
||||
last-char (dec (count text))]
|
||||
(if (re-matches ending-chars-regex (str (nth text last-char nil)))
|
||||
(subs text 0 last-char)
|
||||
text))
|
||||
suggestions (get-suggestions users searched-text)
|
||||
suggestions-cnt (count suggestions)]
|
||||
(cond (zero? suggestions-cnt)
|
||||
nil
|
||||
|
||||
(and (= 1 suggestions-cnt)
|
||||
(mentioned? (second (first suggestions))
|
||||
(subs text (inc mention-key-idx))))
|
||||
(second (first suggestions))
|
||||
(and (= 1 suggestions-cnt)
|
||||
(mentioned? (second (first suggestions))
|
||||
(subs text (inc mention-key-idx))))
|
||||
(second (first suggestions))
|
||||
|
||||
(> suggestions-cnt 1)
|
||||
(let [word-len (count word)
|
||||
text-len (count text)
|
||||
next-word-start (+ next-word-idx (inc word-len))]
|
||||
(when (> text-len next-word-start)
|
||||
(match-mention text users mention-key-idx
|
||||
next-word-start words)))))))))
|
||||
(> suggestions-cnt 1)
|
||||
(let [word-len (count word)
|
||||
text-len (count text)
|
||||
next-word-start (+ next-word-idx word-len)]
|
||||
(when (> text-len next-word-start)
|
||||
(match-mention text users mention-key-idx
|
||||
next-word-start new-words))))))))
|
||||
|
||||
(defn replace-mentions
|
||||
([text users-fn]
|
||||
@ -105,21 +119,130 @@
|
||||
([text users-fn idx]
|
||||
(if (string/blank? 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
|
||||
text
|
||||
(let [users (users-fn)
|
||||
{:keys [public-key alias]}
|
||||
(match-mention text users mention-key-idx)]
|
||||
(if-not alias
|
||||
(recur text (fn [] users) (inc mention-key-idx))
|
||||
(let [new-text (string/join
|
||||
[(subs text 0 (inc mention-key-idx))
|
||||
public-key
|
||||
(subs text (+ (inc mention-key-idx)
|
||||
(count alias)))])
|
||||
mention-end (+ (inc mention-key-idx) (count public-key))]
|
||||
(recur new-text (fn [] users) mention-end)))))))))
|
||||
(let [users (users-fn)]
|
||||
(if-not (seq users)
|
||||
text
|
||||
(let [{:keys [public-key match]}
|
||||
(match-mention text users mention-key-idx)]
|
||||
(if-not match
|
||||
(recur text (fn [] users) (inc mention-key-idx))
|
||||
(let [new-text (string/join
|
||||
[(subs text 0 (inc mention-key-idx))
|
||||
public-key
|
||||
(subs text (+ (inc mention-key-idx)
|
||||
(count match)))])
|
||||
mention-end (+ (inc mention-key-idx) (count public-key))]
|
||||
(recur new-text (fn [] users) mention-end)))))))))))
|
||||
|
||||
(defn check-mentions [cofx text]
|
||||
(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"
|
||||
:alias "User Number Three"
|
||||
: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"
|
||||
(let [text "foo bar @buzz kek @foo"
|
||||
result (mentions/replace-mentions text users)]
|
||||
@ -27,6 +47,11 @@
|
||||
result (mentions/replace-mentions text users)]
|
||||
(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"
|
||||
(let [text "@User Number Onefoo"
|
||||
result (mentions/replace-mentions text users)]
|
||||
@ -57,6 +82,11 @@
|
||||
result (mentions/replace-mentions text users)]
|
||||
(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"
|
||||
(let [text "foo@User Number One bar @User Number two baz"
|
||||
result (mentions/replace-mentions text users)]
|
||||
|
@ -69,6 +69,16 @@
|
||||
(update-in [:chats chat-id :loaded-unviewed-messages-ids]
|
||||
(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
|
||||
[{:keys [db] :as cofx}
|
||||
{:keys [chat-id clock-value] :as message}]
|
||||
@ -76,21 +86,24 @@
|
||||
cursor-clock-value (get-in db [:chats current-chat-id :cursor-clock-value])
|
||||
current-chat? (= chat-id loaded-chat-id)]
|
||||
(when current-chat?
|
||||
;; If we don't have any hidden message or the hidden message is before
|
||||
;; this one, we add the message to the UI
|
||||
(if (or (not @view.state/first-not-visible-item)
|
||||
(<= (:clock-value @view.state/first-not-visible-item)
|
||||
clock-value))
|
||||
(add-message cofx {:message message
|
||||
:seen-by-user? (and current-chat?
|
||||
(= view-id :chat))})
|
||||
;; Not in the current view, set all-loaded to false
|
||||
;; and offload to db and update cursor if necessary
|
||||
{:db (cond-> (assoc-in db [:chats chat-id :all-loaded?] false)
|
||||
(>= clock-value cursor-clock-value)
|
||||
(update-in [:chats chat-id] assoc
|
||||
:cursor (chat-loading/clock-value->cursor clock-value)
|
||||
:cursor-clock-value clock-value))}))))
|
||||
(fx/merge
|
||||
cofx
|
||||
;; If we don't have any hidden message or the hidden message is before
|
||||
;; this one, we add the message to the UI
|
||||
(if (or (not @view.state/first-not-visible-item)
|
||||
(<= (:clock-value @view.state/first-not-visible-item)
|
||||
clock-value))
|
||||
(add-message {:message message
|
||||
:seen-by-user? (and current-chat?
|
||||
(= view-id :chat))})
|
||||
;; Not in the current view, set all-loaded to false
|
||||
;; and offload to db and update cursor if necessary
|
||||
{:db (cond-> (assoc-in db [:chats chat-id :all-loaded?] false)
|
||||
(>= clock-value cursor-clock-value)
|
||||
(update-in [:chats chat-id] assoc
|
||||
:cursor (chat-loading/clock-value->cursor clock-value)
|
||||
:cursor-clock-value clock-value))})
|
||||
(add-sender-to-chat-users message)))))
|
||||
|
||||
(defn- message-loaded?
|
||||
[{:keys [db]} {:keys [chat-id message-id]}]
|
||||
|
@ -21,8 +21,12 @@
|
||||
:all-loaded? true
|
||||
:chats {chat-id {:cursor cursor
|
||||
:cursor-clock-value cursor-clock-value}}}}
|
||||
message {:chat-id chat-id
|
||||
:clock-value clock-value}]
|
||||
message {:chat-id chat-id
|
||||
:clock-value clock-value
|
||||
:alias "alias"
|
||||
:name "name"
|
||||
:identicon "identicon"
|
||||
:from "from"}]
|
||||
(testing "not current-chat"
|
||||
(is (nil? (message/add-received-message
|
||||
(update cofx :db dissoc :loaded-chat-id)
|
||||
@ -32,18 +36,44 @@
|
||||
;; <- top of the chat
|
||||
(testing "there's no hidden item"
|
||||
(with-redefs [view.state/first-not-visible-item (atom nil)]
|
||||
(is (= :added (message/add-received-message
|
||||
cofx
|
||||
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
|
||||
message)))))
|
||||
;; <- cursor
|
||||
;; <- first-hidden-item
|
||||
;; <- message
|
||||
;; <- top of the chat
|
||||
(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)})]
|
||||
(is (= :added (message/add-received-message
|
||||
cofx
|
||||
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
|
||||
message)))))
|
||||
;; <- cursor
|
||||
;; <- message
|
||||
;; <- first-hidden-item
|
||||
|
@ -116,6 +116,7 @@
|
||||
[{:keys [db] :as cofx} public-key]
|
||||
(when (not= (get-in db [:multiaccount :public-key]) public-key)
|
||||
(let [contact (build-contact cofx public-key)]
|
||||
(log/info "create contact" contact)
|
||||
(fx/merge cofx
|
||||
{:db (dissoc db :contacts/new-identity)}
|
||||
(upsert-contact contact)))))
|
||||
@ -176,15 +177,13 @@
|
||||
(fx/defn name-verified
|
||||
{:events [:contacts/ens-name-verified]}
|
||||
[{:keys [db now] :as cofx} public-key ens-name]
|
||||
(fx/merge cofx
|
||||
{:db (update-in db [:contacts/contacts public-key]
|
||||
merge
|
||||
{:name ens-name
|
||||
:last-ens-clock-value now
|
||||
:ens-verified-at now
|
||||
:ens-verified true})}
|
||||
|
||||
(upsert-contact {:public-key public-key})))
|
||||
(let [contact (-> (get-in db [:contacts/contacts public-key]
|
||||
(build-contact cofx public-key))
|
||||
(assoc :name ens-name
|
||||
:last-ens-clock-value now
|
||||
:ens-verified-at now
|
||||
:ens-verified true))]
|
||||
(upsert-contact cofx contact)))
|
||||
|
||||
(fx/defn update-nickname
|
||||
{:events [:contacts/update-nickname]}
|
||||
@ -194,4 +193,4 @@
|
||||
(update-in db [:contacts/contacts public-key] dissoc :nickname)
|
||||
(assoc-in db [:contacts/contacts public-key :nickname] nickname))}
|
||||
(upsert-contact {:public-key public-key})
|
||||
(navigation/navigate-back)))
|
||||
(navigation/navigate-back)))
|
||||
|
@ -500,6 +500,11 @@
|
||||
(fn [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
|
||||
:chat.ui/set-chat-input-text
|
||||
(fn [cofx [_ text]]
|
||||
|
@ -118,6 +118,8 @@
|
||||
(reg-root-key-sub :chat/memberships :chat/memberships)
|
||||
(reg-root-key-sub :camera-roll-photos :camera-roll-photos)
|
||||
(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
|
||||
(reg-root-key-sub :browsers :browser/browsers)
|
||||
@ -854,6 +856,43 @@
|
||||
(wallet.db/get-confirmations current-block)
|
||||
(>= 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 ============================================================================================================
|
||||
|
||||
(re-frame/reg-sub
|
||||
|
@ -13,7 +13,12 @@
|
||||
[status-im.utils.config :as config]
|
||||
[re-frame.core :as re-frame]
|
||||
[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
|
||||
:images :main-icons/photo})
|
||||
@ -73,34 +78,127 @@
|
||||
:accessibility-label :send-message-button
|
||||
:color (styles/send-icon-color)}]]])
|
||||
|
||||
(defn text-input [{:keys [cooldown-enabled? text-value on-text-change set-active-panel text-input-ref]}]
|
||||
[rn/view {:style (styles/text-input-wrapper)}
|
||||
[rn/text-input {:style (styles/text-input)
|
||||
:ref text-input-ref
|
||||
:maxFontSizeMultiplier 1
|
||||
:accessibility-label :chat-message-input
|
||||
:text-align-vertical :center
|
||||
:multiline true
|
||||
:default-value text-value
|
||||
:editable (not cooldown-enabled?)
|
||||
:blur-on-submit false
|
||||
:auto-focus false
|
||||
:on-focus #(set-active-panel nil)
|
||||
:on-change #(on-text-change (.-text ^js (.-nativeEvent ^js %)))
|
||||
:max-length chat.constants/max-text-size
|
||||
:placeholder-text-color (:text-02 @colors/theme)
|
||||
:placeholder (if cooldown-enabled?
|
||||
(i18n/label :cooldown/text-input-disabled)
|
||||
(i18n/label :t/type-a-message))
|
||||
:underlineColorAndroid :transparent
|
||||
:auto-capitalize :sentences}]])
|
||||
(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/text-input
|
||||
{:style (styles/text-input)
|
||||
:ref text-input-ref
|
||||
:maxFontSizeMultiplier 1
|
||||
:accessibility-label :chat-message-input
|
||||
:text-align-vertical :center
|
||||
:multiline true
|
||||
:default-value text-value
|
||||
:editable (not cooldown-enabled?)
|
||||
:blur-on-submit false
|
||||
:auto-focus false
|
||||
:on-focus #(set-active-panel nil)
|
||||
:max-length chat.constants/max-text-size
|
||||
:placeholder-text-color (:text-02 @colors/theme)
|
||||
:placeholder (if cooldown-enabled?
|
||||
(i18n/label :cooldown/text-input-disabled)
|
||||
(i18n/label :t/type-a-message))
|
||||
:underlineColorAndroid :transparent
|
||||
: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
|
||||
[{:keys [set-active-panel active-panel on-send-press reply
|
||||
show-send show-image show-stickers show-extensions
|
||||
sending-image input-focus show-audio]
|
||||
: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)
|
||||
(not show-image)))}
|
||||
(when show-extensions
|
||||
@ -113,40 +211,48 @@
|
||||
:accessibility-label :show-photo-icon
|
||||
:active active-panel
|
||||
:set-active set-active-panel}])]
|
||||
[animated/view {:style (styles/input-container)}
|
||||
(when reply
|
||||
[reply/reply-message reply])
|
||||
(when sending-image
|
||||
[reply/send-image sending-image])
|
||||
[rn/view {:style (styles/input-row)}
|
||||
[text-input props]
|
||||
[rn/view {:style (styles/in-input-buttons)}
|
||||
(when show-send
|
||||
[send-button {:on-send-press on-send-press}])
|
||||
(when show-stickers
|
||||
[touchable-stickers-icon {:panel :stickers
|
||||
:accessibility-label :show-stickers-icon
|
||||
:active active-panel
|
||||
:input-focus input-focus
|
||||
:set-active set-active-panel}])
|
||||
(when show-audio
|
||||
[touchable-audio-icon {:panel :audio
|
||||
:accessibility-label :show-audio-message-icon
|
||||
:active active-panel
|
||||
:input-focus input-focus
|
||||
:set-active set-active-panel}])]]]])
|
||||
[:<>
|
||||
;; 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
|
||||
[reply/reply-message reply])
|
||||
(when sending-image
|
||||
[reply/send-image sending-image])
|
||||
[rn/view {:style (styles/input-row)}
|
||||
[text-input props]
|
||||
[rn/view {:style (styles/in-input-buttons)}
|
||||
(when show-send
|
||||
[send-button {:on-send-press on-send-press}])
|
||||
(when show-stickers
|
||||
[touchable-stickers-icon {:panel :stickers
|
||||
:accessibility-label :show-stickers-icon
|
||||
:active active-panel
|
||||
:input-focus input-focus
|
||||
:set-active set-active-panel}])
|
||||
(when show-audio
|
||||
[touchable-audio-icon {:panel :audio
|
||||
:accessibility-label :show-audio-message-icon
|
||||
:active active-panel
|
||||
:input-focus input-focus
|
||||
:set-active set-active-panel}])]]]]])
|
||||
|
||||
(defn chat-toolbar []
|
||||
(let [previous-layout (atom nil)
|
||||
had-reply (atom nil)]
|
||||
(fn [{:keys [active-panel set-active-panel text-input-ref]}]
|
||||
(let [previous-layout (atom nil)
|
||||
had-reply (atom nil)]
|
||||
(fn [{:keys [active-panel set-active-panel text-input-ref on-text-change]}]
|
||||
(let [disconnected? @(re-frame/subscribe [:disconnected?])
|
||||
{:keys [processing]} @(re-frame/subscribe [:multiaccounts/login])
|
||||
mainnet? @(re-frame/subscribe [:mainnet?])
|
||||
input-text @(re-frame/subscribe [:chats/current-chat-input-text])
|
||||
cooldown-enabled? @(re-frame/subscribe [:chats/cooldown-enabled?])
|
||||
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])
|
||||
sending-image @(re-frame/subscribe [:chats/sending-image])
|
||||
input-focus (fn []
|
||||
@ -181,19 +287,20 @@
|
||||
(when (seq @previous-layout)
|
||||
(rn/configure-next
|
||||
(:ease-opacity-200 rn/custom-animations))))
|
||||
[chat-input {:set-active-panel set-active-panel
|
||||
:active-panel active-panel
|
||||
:text-input-ref text-input-ref
|
||||
:input-focus input-focus
|
||||
:reply reply
|
||||
:on-send-press #(do (re-frame/dispatch [:chat.ui/send-current-message])
|
||||
(clear-input))
|
||||
:text-value input-text
|
||||
:on-text-change #(re-frame/dispatch [:chat.ui/set-chat-input-text %])
|
||||
:cooldown-enabled? cooldown-enabled?
|
||||
:show-send show-send
|
||||
:show-stickers show-stickers
|
||||
:show-image show-image
|
||||
:show-audio show-audio
|
||||
:sending-image sending-image
|
||||
:show-extensions show-extensions}]))))
|
||||
[chat-input {:set-active-panel set-active-panel
|
||||
:active-panel active-panel
|
||||
:text-input-ref text-input-ref
|
||||
:input-focus input-focus
|
||||
:reply reply
|
||||
:on-send-press #(do (re-frame/dispatch [:chat.ui/send-current-message])
|
||||
(clear-input))
|
||||
:text-value input-text
|
||||
:on-text-change on-text-change
|
||||
:cooldown-enabled? cooldown-enabled?
|
||||
:show-send show-send
|
||||
:show-stickers show-stickers
|
||||
:show-image show-image
|
||||
:show-audio show-audio
|
||||
:sending-image sending-image
|
||||
:show-extensions show-extensions
|
||||
:chat-id chat-id}]))))
|
||||
|
@ -109,3 +109,13 @@
|
||||
|
||||
(defn send-icon-color []
|
||||
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)
|
||||
(when first-in-group?
|
||||
[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]])])
|
||||
[react/view {:style (style/message-author-wrapper outgoing display-photo?)}
|
||||
(when display-username?
|
||||
[react/touchable-opacity {:style style/message-author-touchable
|
||||
: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 CONTENT
|
||||
[react/view
|
||||
@ -310,7 +310,7 @@
|
||||
(on-long-press
|
||||
(when-not outgoing
|
||||
[{: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)}])))})
|
||||
[react/image {:style {:margin 10 :width 140 :height 140}
|
||||
;;TODO (perf) move to event
|
||||
|
@ -9,13 +9,15 @@
|
||||
{:position :relative
|
||||
:border-radius (radius size)})
|
||||
|
||||
(defn photo-border [size]
|
||||
{:position :absolute
|
||||
:width size
|
||||
:height size
|
||||
:border-color colors/black-transparent
|
||||
:border-width 1
|
||||
:border-radius (radius size)})
|
||||
(defn photo-border
|
||||
([size] (photo-border size :absolute))
|
||||
([size position]
|
||||
{:position position
|
||||
:width size
|
||||
:height size
|
||||
:border-color colors/black-transparent
|
||||
:border-width 1
|
||||
:border-radius (radius size)}))
|
||||
|
||||
(defn photo [size]
|
||||
{:border-radius (radius size)
|
||||
|
@ -276,7 +276,8 @@
|
||||
(reset! active-panel panel)
|
||||
(reagent/flush)
|
||||
(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 []
|
||||
(let [{:keys [chat-id show-input? group-chat admins invitation-admin] :as current-chat}
|
||||
@(re-frame/subscribe [:chats/current-chat])]
|
||||
@ -295,13 +296,21 @@
|
||||
[accessory/view {:y position-y
|
||||
:on-update-inset on-update}
|
||||
[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?
|
||||
[accessory/view {:y position-y
|
||||
:pan-state pan-state
|
||||
:has-panel (boolean @active-panel)
|
||||
:on-close #(set-active-panel nil)
|
||||
:on-update-inset on-update}
|
||||
[components/chat-toolbar {:active-panel @active-panel
|
||||
:set-active-panel set-active-panel
|
||||
:text-input-ref text-input-ref}]
|
||||
[components/chat-toolbar
|
||||
{:active-panel @active-panel
|
||||
:set-active-panel set-active-panel
|
||||
:text-input-ref text-input-ref
|
||||
:on-text-change on-text-change}]
|
||||
[bottom-sheet @active-panel]])]))))
|
||||
|
@ -13,12 +13,10 @@
|
||||
[status-im.ui.components.icons.vector-icons :as icons]
|
||||
[status-im.utils.contenthash :as contenthash]
|
||||
[status-im.utils.core :as utils]
|
||||
[status-im.utils.datetime :as time])
|
||||
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
|
||||
[status-im.utils.datetime :as time]))
|
||||
|
||||
(defview mention-element [from]
|
||||
(letsubs [contact-name [:contacts/contact-name-by-identity from]]
|
||||
contact-name))
|
||||
(defn mention-element [from]
|
||||
@(re-frame/subscribe [:contacts/contact-name-by-identity from]))
|
||||
|
||||
;; if truncated subheader text is too short we won't get ellipsize at the end of text
|
||||
(def max-subheader-length 100)
|
||||
|
Loading…
x
Reference in New Issue
Block a user