diff --git a/src/status_im/contact/core.cljs b/src/status_im/contact/core.cljs index 916cf80b29..427dddf72c 100644 --- a/src/status_im/contact/core.cljs +++ b/src/status_im/contact/core.cljs @@ -12,7 +12,8 @@ [status-im.navigation :as navigation] [status-im.utils.fx :as fx] [status-im.waku.core :as waku] - [taoensso.timbre :as log])) + [taoensso.timbre :as log] + [clojure.string :as string])) (fx/defn load-contacts {:events [::contacts-loaded]} @@ -59,7 +60,9 @@ [{:keys [db]} contacts] {:db (update db :contacts/contacts #(reduce (fn [acc {:keys [public-key] :as contact}] - (update acc public-key merge contact)) + (-> acc + (update public-key merge contact) + (assoc-in [public-key :nickname] (:nickname contact)))) % contacts))}) @@ -81,12 +84,15 @@ (fx/defn add-contact "Add a contact and set pending to false" - [{:keys [db] :as cofx} public-key] + [{:keys [db] :as cofx} public-key nickname] (when (not= (get-in db [:multiaccount :public-key]) public-key) - (let [contact (-> (get-in db [:contacts/contacts public-key] - (build-contact cofx public-key)) - (update :system-tags - (fnil #(conj % :contact/added) #{})))] + (let [contact (cond-> (get-in db [:contacts/contacts public-key] + (build-contact cofx public-key)) + nickname + (assoc :nickname nickname) + :else + (update :system-tags + (fnil #(conj % :contact/added) #{})))] (fx/merge cofx {:db (dissoc db :contacts/new-identity)} (upsert-contact contact) @@ -179,3 +185,13 @@ :ens-verified true})} (upsert-contact {:public-key public-key}))) + +(fx/defn update-nickname + {:events [:contacts/update-nickname]} + [{:keys [db] :as cofx} public-key nickname] + (fx/merge cofx + {:db (if (string/blank? nickname) + (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))) \ No newline at end of file diff --git a/src/status_im/contact/db.cljs b/src/status_im/contact/db.cljs index c50ad01b21..b799aca954 100644 --- a/src/status_im/contact/db.cljs +++ b/src/status_im/contact/db.cljs @@ -3,7 +3,8 @@ [clojure.string :as clojure.string] [status-im.ethereum.core :as ethereum] [status-im.utils.gfycat.core :as gfycat] - [status-im.utils.identicon :as identicon])) + [status-im.utils.identicon :as identicon] + [status-im.multiaccounts.core :as multiaccounts])) (defn public-key->new-contact [public-key] (let [alias (gfycat/generate-gfy public-key)] @@ -135,7 +136,8 @@ (assoc :pending? (pending? contact) :blocked? (blocked? contact) :active? (active? contact) - :added? (contains? system-tags :contact/added)))) + :added? (contains? system-tags :contact/added)) + (multiaccounts/contact-with-names))) (defn enrich-contacts [contacts] diff --git a/src/status_im/contact/db_test.cljs b/src/status_im/contact/db_test.cljs index f38ec70dca..298b12c940 100644 --- a/src/status_im/contact/db_test.cljs +++ b/src/status_im/contact/db_test.cljs @@ -32,18 +32,18 @@ admins contacts current-multiaccount) - [{:name "generated" - :identicon "generated" - :alias "generated" - :admin? true - :public-key "0x04fcf40c526b09ff9fb22f4a5dbd08490ef9b64af700870f8a0ba2133f4251d5607ed83cd9047b8c2796576bc83fa0de23a13a4dced07654b8ff137fe744047917" - :system-tags #{}} - {:alias "User A" - :photo-path "photo2" + [{:name "generated" + :identicon "generated" + :alias "generated" + :admin? true + :public-key "0x04fcf40c526b09ff9fb22f4a5dbd08490ef9b64af700870f8a0ba2133f4251d5607ed83cd9047b8c2796576bc83fa0de23a13a4dced07654b8ff137fe744047917" + :system-tags #{}} + {:alias "User A" + :photo-path "photo2" :public-key "0x048a2f8b80c60f89a91b4c1316e56f75b087f446e7b8701ceca06a40142d8efe1f5aa36bd0fee9e248060a8d5207b43ae98bef4617c18c71e66f920f324869c09f"} - {:last-updated 0 - :name "User B" - :photo-path "photo1" - :last-online 0 - :public-key "0x04985040682b77a32bb4bb58268a0719bd24ca4d07c255153fe1eb2ccd5883669627bd1a092d7cc76e8e4b9104327667b19dcda3ac469f572efabe588c38c1985f" - :system-tags #{}}])))))) + {:last-updated 0 + :name "User B" + :photo-path "photo1" + :last-online 0 + :public-key "0x04985040682b77a32bb4bb58268a0719bd24ca4d07c255153fe1eb2ccd5883669627bd1a092d7cc76e8e4b9104327667b19dcda3ac469f572efabe588c38c1985f" + :system-tags #{}}])))))) diff --git a/src/status_im/data_store/contacts.cljs b/src/status_im/data_store/contacts.cljs index ca994cf1b0..d3df16c72e 100644 --- a/src/status_im/data_store/contacts.cljs +++ b/src/status_im/data_store/contacts.cljs @@ -27,7 +27,8 @@ :ensVerificationRetries :ens-verification-retries :lastENSClockValue :last-ens-clock-value :systemTags :system-tags - :lastUpdated :last-updated}))) + :lastUpdated :last-updated + :localNickname :nickname}))) (defn ->rpc [contact] (-> contact @@ -41,7 +42,8 @@ :photo-path :photoPath :tribute-to-talk :tributeToTalk :system-tags :systemTags - :last-updated :lastUpdated}))) + :last-updated :lastUpdated + :nickname :localNickname}))) (fx/defn fetch-contacts-rpc [cofx on-success] diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index 5d99fce3a7..fe8fdb8d65 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -816,7 +816,7 @@ :contact.ui/add-to-contact-pressed [(re-frame/inject-cofx :random-id-generator)] (fn [cofx [_ public-key]] - (contact/add-contact cofx public-key))) + (contact/add-contact cofx public-key nil))) (handlers/register-handler-fx :contact.ui/block-contact-confirmed @@ -842,11 +842,11 @@ (handlers/register-handler-fx :contact.ui/contact-code-submitted [(re-frame/inject-cofx :random-id-generator)] - (fn [{{:contacts/keys [new-identity]} :db :as cofx} [_ new-contact?]] + (fn [{{:contacts/keys [new-identity]} :db :as cofx} [_ new-contact? nickname]] (let [{:keys [public-key ens-name]} new-identity] (fx/merge cofx #(if new-contact? - (contact/add-contact % public-key) + (contact/add-contact % public-key nickname) (chat/start-chat % public-key)) #(when new-contact? (navigation/navigate-back %)) diff --git a/src/status_im/multiaccounts/core.cljs b/src/status_im/multiaccounts/core.cljs index e31151df07..a3b8a017be 100644 --- a/src/status_im/multiaccounts/core.cljs +++ b/src/status_im/multiaccounts/core.cljs @@ -8,7 +8,40 @@ [status-im.utils.gfycat.core :as gfycat] [status-im.utils.identicon :as identicon] [status-im.utils.theme :as utils.theme] - [status-im.theme.core :as theme])) + [status-im.theme.core :as theme] + [status-im.utils.utils :as utils] + [clojure.string :as string])) + +(defn contact-names + "Returns map of all existing names for contact" + [{:keys [name preferred-name alias public-key ens-verified nickname]}] + (let [ens-name (or preferred-name + name)] + (cond-> {:nickname nickname + :three-words-name (or alias (gfycat/generate-gfy public-key))} + ;; Preferred name is our own otherwise we make sure it's verified + (or preferred-name (and ens-verified name)) + (assoc :ens-name (str "@" (or (stateofus/username ens-name) ens-name)))))) + +(defn contact-two-names + "Returns vector of two names in next order nickname, ens name, three word name, public key" + [{:keys [names public-key] :as contact} public-key?] + (let [{:keys [nickname ens-name three-words-name]} (or names (contact-names contact)) + short-public-key (when public-key? (utils/get-shortened-address public-key))] + (cond (not (string/blank? nickname)) + [nickname (or ens-name three-words-name short-public-key)] + (not (string/blank? ens-name)) + [ens-name (or three-words-name short-public-key)] + (not (string/blank? three-words-name)) + [three-words-name short-public-key] + :else + (when public-key? + [short-public-key short-public-key])))) + +(defn contact-with-names + "Returns contact with :names map " + [contact] + (assoc contact :names (contact-names contact))) (defn displayed-name "Use preferred name, name or alias in that order" diff --git a/src/status_im/reloader.cljs b/src/status_im/reloader.cljs index e219b90d3c..e02db66579 100644 --- a/src/status_im/reloader.cljs +++ b/src/status_im/reloader.cljs @@ -2,7 +2,8 @@ (:require [reagent.core :as reagent] [status-im.ui.components.react :as react] [status-im.react-native.resources :as resources] - [status-im.ui.components.colors :as colors])) + [status-im.ui.components.colors :as colors] + [re-frame.core :as re-frame])) (def cnt (reagent/atom 0)) (defonce cnt-prev (reagent/atom 0)) @@ -14,6 +15,7 @@ (defn reload [] (reset! warning? false) (reset! label "reloading UI") + (re-frame/clear-subscription-cache!) (swap! cnt inc)) (defn build-competed [] diff --git a/src/status_im/subs.cljs b/src/status_im/subs.cljs index e3fbbaa4e9..67c48bb5d5 100644 --- a/src/status_im/subs.cljs +++ b/src/status_im/subs.cljs @@ -1543,16 +1543,17 @@ :<- [:contacts/contacts] :<- [:contacts/current-contact-identity] (fn [[contacts identity]] - (or (contacts identity) + (or (get contacts identity) (-> identity contact.db/public-key->new-contact contact.db/enrich-contact)))) (re-frame/reg-sub :contacts/contact-by-identity - :<- [::contacts] + :<- [:contacts/contacts] (fn [contacts [_ identity]] - (get contacts identity))) + (or (get contacts identity) + (multiaccounts/contact-with-names {:public-key identity})))) (re-frame/reg-sub :contacts/contact-added? @@ -1562,27 +1563,23 @@ (contact.db/added? contact))) (re-frame/reg-sub - :contacts/raw-contact-name-by-identity + :contacts/contact-two-names-by-identity (fn [[_ identity] _] - [(re-frame/subscribe [:contacts/contact-by-identity identity])]) - (fn [[db-contact] _] - (if (and (:ens-verified db-contact) (seq (:name db-contact))) - (str "@" (:name db-contact)) - (:alias db-contact)))) + [(re-frame/subscribe [:contacts/contact-by-identity identity]) + (re-frame/subscribe [:multiaccount])]) + (fn [[contact current-multiaccount] [_ identity]] + (let [me? (= (:public-key current-multiaccount) identity)] + (if me? + [(or (:preferred-name current-multiaccount) + (gfycat/generate-gfy identity))] + (multiaccounts/contact-two-names contact false))))) (re-frame/reg-sub :contacts/contact-name-by-identity (fn [[_ identity] _] - [(re-frame/subscribe [:contacts/raw-contact-name-by-identity identity]) - (re-frame/subscribe [:multiaccount])]) - (fn [[contact-name current-multiaccount] [_ identity]] - (let [me? (= (:public-key current-multiaccount) identity)] - (if me? - (or (:preferred-name current-multiaccount) - (gfycat/generate-gfy identity)) - (or (stateofus/username contact-name) - contact-name - (gfycat/generate-gfy identity)))))) + [(re-frame/subscribe [:contacts/contact-two-names-by-identity identity])]) + (fn [[names] _] + (first names))) (re-frame/reg-sub :messages/quote-info @@ -1612,8 +1609,7 @@ :<- [::query-current-chat-contacts remove] (fn [contacts] (->> contacts - (filter contact.db/added?) - (sort-by (comp clojure.string/lower-case multiaccounts/displayed-name))))) + (filter contact.db/added?)))) (re-frame/reg-sub :contacts/current-chat-contacts @@ -1764,16 +1760,19 @@ (string/lower-case (or alias (get-in contacts [chat-id :alias]) (gfycat/generate-gfy chat-id))) - "")] - + "") + nickname (get-in contacts [chat-id :nickname])] (or (string/includes? (string/lower-case (str name)) search-filter) (string/includes? (string/lower-case alias) search-filter) + (when nickname + (string/includes? (string/lower-case nickname) search-filter)) (and (get-in contacts [chat-id :ens-verified]) (string/includes? (string/lower-case (str (get-in contacts [chat-id :name]))) search-filter))))) + (re-frame/reg-sub :search/filtered-chats :<- [:chats/active-chats] diff --git a/src/status_im/ui/components/contact/contact.cljs b/src/status_im/ui/components/contact/contact.cljs deleted file mode 100644 index dcb33a755a..0000000000 --- a/src/status_im/ui/components/contact/contact.cljs +++ /dev/null @@ -1,8 +0,0 @@ -(ns status-im.ui.components.contact.contact - (:require [status-im.ethereum.stateofus :as stateofus] - [status-im.utils.gfycat.core :as gfycat])) - -(defn format-name [{:keys [ens-verified name public-key]}] - (if ens-verified - (str "@" (or (stateofus/username name) name)) - (gfycat/generate-gfy public-key))) diff --git a/src/status_im/ui/screens/add_new/new_chat/events.cljs b/src/status_im/ui/screens/add_new/new_chat/events.cljs index 5c71722b58..bdef4374c6 100644 --- a/src/status_im/ui/screens/add_new/new_chat/events.cljs +++ b/src/status_im/ui/screens/add_new/new_chat/events.cljs @@ -93,7 +93,7 @@ (if-not validation-result (if new-contact? (fx/merge cofx - (contact/add-contact chat-key) + (contact/add-contact chat-key nil) (navigation/navigate-to-cofx :contacts-list {})) (chat/start-chat cofx chat-key)) {:utils/show-popup {:title (i18n/label :t/unable-to-read-this-code) diff --git a/src/status_im/ui/screens/add_new/new_chat/views.cljs b/src/status_im/ui/screens/add_new/new_chat/views.cljs index 4939a61e4f..f0521b798e 100644 --- a/src/status_im/ui/screens/add_new/new_chat/views.cljs +++ b/src/status_im/ui/screens/add_new/new_chat/views.cljs @@ -13,17 +13,20 @@ [status-im.ui.components.topbar :as topbar] [status-im.ui.screens.add-new.new-chat.styles :as styles] [status-im.utils.debounce :as debounce] - [status-im.utils.utils :as utils]) + [status-im.utils.utils :as utils] + [reagent.core :as reagent]) (:require-macros [status-im.utils.views :as views])) (defn- render-row [row _ _] - [quo/list-item - {:title (multiaccounts/displayed-name row) - :icon [chat-icon/contact-icon-contacts-tab - (multiaccounts/displayed-photo row)] - :chevron true - :on-press #(re-frame/dispatch [:chat.ui/start-chat - (:public-key row)])}]) + (let [[first-name second-name] (multiaccounts/contact-two-names row false)] + [quo/list-item + {:title first-name + :subtitle second-name + :icon [chat-icon/contact-icon-contacts-tab + (multiaccounts/displayed-photo row)] + :chevron true + :on-press #(re-frame/dispatch [:chat.ui/start-chat + (:public-key row)])}])) (defn- icon-wrapper [color icon] [react/view @@ -36,7 +39,7 @@ icon]) (defn- input-icon - [state new-contact?] + [state new-contact? entered-nickname] (let [icon (if new-contact? :main-icons/add :main-icons/arrow-right)] (case state :searching @@ -45,7 +48,7 @@ :valid [react/touchable-highlight - {:on-press #(debounce/dispatch-and-chill [:contact.ui/contact-code-submitted new-contact?] 3000)} + {:on-press #(debounce/dispatch-and-chill [:contact.ui/contact-code-submitted new-contact? entered-nickname] 3000)} [icon-wrapper colors/blue [vector-icons/icon icon {:color colors/white-persist}]]] @@ -83,7 +86,7 @@ (debounce/debounce-and-dispatch [:new-chat/set-new-identity %] 600)) :on-submit-editing #(when (= state :valid) - (debounce/dispatch-and-chill [:contact.ui/contact-code-submitted false] 3000)) + (debounce/dispatch-and-chill [:contact.ui/contact-code-submitted false nil] 3000)) :placeholder (i18n/label :t/enter-contact-code) :show-cancel false :accessibility-label :enter-contact-code-input @@ -91,7 +94,7 @@ :return-key-type :go}]] [react/view {:justify-content :center :align-items :center} - [input-icon state false]]] + [input-icon state false nil]]] [react/view {:min-height 30 :justify-content :flex-end} [quo/text {:style styles/message :size :small @@ -118,8 +121,20 @@ :enableEmptySections true :keyboardShouldPersistTaps :always}]])) +(defn- nickname-input [entered-nickname] + [quo/text-input + {:on-change-text #(reset! entered-nickname %) + :auto-capitalize :none + :max-length 32 + :auto-focus false + :accessibility-label :nickname-input + :placeholder (i18n/label :t/add-nickname) + :return-key-type :done + :auto-correct false}]) + (views/defview new-contact [] - (views/letsubs [{:keys [state ens-name public-key error]} [:contacts/new-identity]] + (views/letsubs [{:keys [state ens-name public-key error]} [:contacts/new-identity] + entered-nickname (reagent/atom "")] [react/view {:style {:flex 1}} [topbar/topbar {:title (i18n/label :t/new-contact) @@ -142,7 +157,7 @@ (debounce/debounce-and-dispatch [:new-chat/set-new-identity %] 600)) :on-submit-editing #(when (= state :valid) - (debounce/dispatch-and-chill [:contact.ui/contact-code-submitted true] 3000)) + (debounce/dispatch-and-chill [:contact.ui/contact-code-submitted true @entered-nickname] 3000)) :placeholder (i18n/label :t/enter-contact-code) :show-cancel false :accessibility-label :enter-contact-code-input @@ -150,12 +165,23 @@ :return-key-type :go}]] [react/view {:justify-content :center :align-items :center} - [input-icon state true]]] - [react/view {:min-height 30 :justify-content :flex-end} - [react/text {:style styles/message} + [input-icon state true @entered-nickname]]] + [react/view {:min-height 30 :justify-content :flex-end :margin-bottom 16} + [quo/text {:style styles/message + :size :small + :align :center + :color :secondary} (cond (= state :error) (get-validation-label error) (= state :valid) (str (when ens-name (str ens-name " • ")) (utils/get-shortened-address public-key)) - :else "")]]])) + :else "")]] + [react/text {:style {:margin-horizontal 16 :color colors/gray}} + (i18n/label :t/nickname-description)] + [react/view {:padding 16} + + [nickname-input entered-nickname] + [react/text {:style {:align-self :flex-end :margin-top 16 + :color colors/gray}} + (str (count @entered-nickname) " / 32")]]])) \ No newline at end of file diff --git a/src/status_im/ui/screens/chat/message/message.cljs b/src/status_im/ui/screens/chat/message/message.cljs index 07da72d43f..247385fc89 100644 --- a/src/status_im/ui/screens/chat/message/message.cljs +++ b/src/status_im/ui/screens/chat/message/message.cljs @@ -175,13 +175,13 @@ (= outgoing-status :not-sent)) [message-not-sent-text chat-id message-id])) -(defview message-author-name [from alias modal] - (letsubs [contact-name [:contacts/raw-contact-name-by-identity from]] - (chat.utils/format-author (or contact-name alias) modal))) +(defview message-author-name [from modal] + (letsubs [contact-with-names [:contacts/contact-by-identity from]] + (chat.utils/format-author contact-with-names modal))) (defn message-content-wrapper "Author, userpic and delivery wrapper" - [{:keys [alias first-in-group? display-photo? identicon display-username? + [{:keys [first-in-group? display-photo? identicon display-username? from outgoing] :as message} content {:keys [modal close-modal]}] [react/view {:style (style/message-wrapper message) @@ -200,7 +200,7 @@ [react/touchable-opacity {:style style/message-author-touchable :on-press #(do (when modal (close-modal)) (re-frame/dispatch [:chat.ui/show-profile from]))} - [message-author-name from alias modal]]) + [message-author-name from modal]]) ;;MESSAGE CONTENT [react/view content]]] diff --git a/src/status_im/ui/screens/chat/toolbar_content.cljs b/src/status_im/ui/screens/chat/toolbar_content.cljs index b2e4ba015d..8a63bbc031 100644 --- a/src/status_im/ui/screens/chat/toolbar_content.cljs +++ b/src/status_im/ui/screens/chat/toolbar_content.cljs @@ -17,7 +17,11 @@ (i18n/label-pluralize cnt :t/members-active))))]]) (defn one-to-one-name [from] - @(re-frame.core/subscribe [:contacts/contact-name-by-identity from])) + (let [[first-name _] @(re-frame.core/subscribe [:contacts/contact-two-names-by-identity from])] + [react/text {:style st/chat-name-text + :number-of-lines 1 + :accessibility-label :chat-name-text} + first-name])) (defn contact-indicator [contact-id] (let [added? @(re-frame/subscribe [:contacts/contact-added? contact-id])] @@ -39,12 +43,12 @@ [react/view {:margin-right 10} [chat-icon.screen/chat-icon-view-toolbar chat-id group-chat chat-name color]] [react/view {:style st/chat-name-view} - [react/text {:style st/chat-name-text - :number-of-lines 1 - :accessibility-label :chat-name-text} - (if group-chat - chat-name - [one-to-one-name chat-id])] + (if group-chat + [react/text {:style st/chat-name-text + :number-of-lines 1 + :accessibility-label :chat-name-text} + chat-name] + [one-to-one-name chat-id]) (when-not group-chat [contact-indicator chat-id]) (when group-chat diff --git a/src/status_im/ui/screens/chat/utils.cljs b/src/status_im/ui/screens/chat/utils.cljs index 3ccccac8c0..ef636c9ef3 100644 --- a/src/status_im/ui/screens/chat/utils.cljs +++ b/src/status_im/ui/screens/chat/utils.cljs @@ -2,26 +2,31 @@ (:require [status-im.ethereum.stateofus :as stateofus] [status-im.i18n :as i18n] [status-im.ui.components.react :as react] - [status-im.ui.components.colors :as colors])) + [status-im.ui.components.colors :as colors] + [status-im.multiaccounts.core :as multiaccounts])) (def ^:private reply-symbol "↪ ") (defn format-author - ([contact-name] (format-author contact-name false)) - ([contact-name modal] - (if (= (aget contact-name 0) "@") - (let [trimmed-name (subs contact-name 0 81)] - [react/text {:number-of-lines 2 - :style {:color (if modal colors/white-persist colors/blue) - :font-size 13 - :line-height 18 - :font-weight "500"}} - (or (stateofus/username trimmed-name) trimmed-name)]) - [react/text {:style {:color (if modal colors/white-persist colors/gray) - :font-size 12 - :line-height 18 - :font-weight "400"}} - contact-name]))) + ([contact] (format-author contact false)) + ([{:keys [names] :as contact} modal] + (let [{:keys [nickname ens-name]} names + [first-name second-name] (multiaccounts/contact-two-names contact false)] + (if (or nickname ens-name) + [react/nested-text {:number-of-lines 2 + :style {:color (if modal colors/white-persist colors/blue) + :font-size 13 + :line-height 18 + :font-weight "500"}} + (subs first-name 0 81) + (when nickname + [{:style {:color colors/gray :font-weight "400"}} + (str " " (subs second-name 0 81))])] + [react/text {:style {:color (if modal colors/white-persist colors/gray) + :font-size 12 + :line-height 18 + :font-weight "400"}} + first-name])))) (defn format-reply-author [from username current-public-key style] (let [contact-name (str reply-symbol username)] diff --git a/src/status_im/ui/screens/chat/views.cljs b/src/status_im/ui/screens/chat/views.cljs index eadac6f24f..7f27bf1099 100644 --- a/src/status_im/ui/screens/chat/views.cljs +++ b/src/status_im/ui/screens/chat/views.cljs @@ -73,7 +73,8 @@ :default-chat-icon-text style/intro-header-icon-text :size 120}]] ;; Chat title section - [react/text {:style (style/intro-header-chat-name)} (if group-chat chat-name contact-name)] + [react/text {:style (style/intro-header-chat-name)} + (if group-chat chat-name contact-name)] ;; Description section (if group-chat [chat.group/group-chat-description-container {:chat-id chat-id @@ -89,9 +90,9 @@ contact-name)])]) (defn chat-intro-one-to-one [{:keys [chat-id] :as opts}] - (let [contact-name @(re-frame/subscribe - [:contacts/contact-name-by-identity chat-id])] - (chat-intro (assoc opts :contact-name contact-name)))) + (let [contact-names @(re-frame/subscribe + [:contacts/contact-two-names-by-identity chat-id])] + (chat-intro (assoc opts :contact-name (first contact-names))))) (defn chat-intro-header-container [{:keys [group-chat diff --git a/src/status_im/ui/screens/contacts_list/views.cljs b/src/status_im/ui/screens/contacts_list/views.cljs index 4fe668eb82..8a76d22c57 100644 --- a/src/status_im/ui/screens/contacts_list/views.cljs +++ b/src/status_im/ui/screens/contacts_list/views.cljs @@ -12,12 +12,14 @@ (:require-macros [status-im.utils.views :refer [defview letsubs]])) (defn contacts-list-item [{:keys [public-key] :as contact}] - [quo/list-item - {:title (multiaccounts/displayed-name contact) - :icon [chat-icon.screen/contact-icon-contacts-tab - (multiaccounts/displayed-photo contact)] - :chevron true - :on-press #(re-frame/dispatch [:chat.ui/show-profile public-key])}]) + (let [[first-name second-name] (multiaccounts/contact-two-names contact true)] + [quo/list-item + {:title first-name + :subtitle second-name + :icon [chat-icon.screen/contact-icon-contacts-tab + (multiaccounts/displayed-photo contact)] + :chevron true + :on-press #(re-frame/dispatch [:chat.ui/show-profile public-key])}])) (defn add-new-contact [] [quo/list-item diff --git a/src/status_im/ui/screens/ens/views.cljs b/src/status_im/ui/screens/ens/views.cljs index 1ba557d5f3..7f7be3d3a4 100644 --- a/src/status_im/ui/screens/ens/views.cljs +++ b/src/status_im/ui/screens/ens/views.cljs @@ -628,7 +628,10 @@ (views/defview my-name [] (views/letsubs [contact-name [:multiaccount/preferred-name]] (when-not (string/blank? contact-name) - (chat.utils/format-author (str "@" contact-name))))) + (chat.utils/format-author {:names {:ens-name + (str "@" + (or (stateofus/username contact-name) + contact-name))}})))) (views/defview registered [names {:keys [preferred-name] :as account} _ registrations] [react/view {:style {:flex 1}} diff --git a/src/status_im/ui/screens/group/views.cljs b/src/status_im/ui/screens/group/views.cljs index c864f00d0f..b72caade92 100644 --- a/src/status_im/ui/screens/group/views.cljs +++ b/src/status_im/ui/screens/group/views.cljs @@ -6,7 +6,6 @@ [status-im.constants :as constants] [status-im.i18n :as i18n] [status-im.ui.components.chat-icon.screen :as chat-icon] - [status-im.ui.components.contact.contact :as contact] [status-im.multiaccounts.core :as multiaccounts] [status-im.ui.components.keyboard-avoid-presentation :as @@ -23,10 +22,12 @@ (:require-macros [status-im.utils.views :as views])) (defn- render-contact [row] - [quo/list-item - {:title (contact/format-name row) - :icon [chat-icon/contact-icon-contacts-tab - (multiaccounts/displayed-photo row)]}]) + (let [[first-name second-name] (multiaccounts/contact-two-names row false)] + [quo/list-item + {:title first-name + :subtitle second-name + :icon [chat-icon/contact-icon-contacts-tab + (multiaccounts/displayed-photo row)]}])) (defn- on-toggle [allow-new-users? checked? public-key] (cond @@ -52,9 +53,11 @@ (defn- toggle-item [] (fn [allow-new-users? subs-name {:keys [public-key] :as contact} on-toggle] - (let [contact-selected? @(re-frame/subscribe [subs-name public-key])] + (let [contact-selected? @(re-frame/subscribe [subs-name public-key]) + [first-name second-name] (multiaccounts/contact-two-names contact true)] [quo/list-item - {:title (contact/format-name contact) + {:title first-name + :subtitle second-name :icon [chat-icon/contact-icon-contacts-tab (multiaccounts/displayed-photo contact)] :on-press #(on-toggle allow-new-users? contact-selected? public-key) @@ -83,10 +86,12 @@ (defn filter-contacts [filter-text contacts] (let [lower-filter-text (string/lower-case (str filter-text)) - filter-fn (fn [{:keys [name alias]}] + filter-fn (fn [{:keys [name alias nickname]}] (or (string/includes? (string/lower-case (str name)) lower-filter-text) - (string/includes? (string/lower-case (str alias)) lower-filter-text)))] + (string/includes? (string/lower-case (str alias)) lower-filter-text) + (when nickname + (string/includes? (string/lower-case (str nickname)) lower-filter-text))))] (if filter-text (filter filter-fn contacts) contacts))) diff --git a/src/status_im/ui/screens/home/views/inner_item.cljs b/src/status_im/ui/screens/home/views/inner_item.cljs index 9226981645..b867c27416 100644 --- a/src/status_im/ui/screens/home/views/inner_item.cljs +++ b/src/status_im/ui/screens/home/views/inner_item.cljs @@ -163,7 +163,7 @@ ;; This looks a bit odd, but I would like only to subscribe ;; if it's a one-to-one. If wrapped in a component styling ;; won't be applied correctly. - @(re-frame/subscribe [:contacts/contact-name-by-identity chat-id]))]] + (first @(re-frame/subscribe [:contacts/contact-two-names-by-identity chat-id])))]] [message-timestamp (if (pos? (:whisper-timestamp last-message)) (:whisper-timestamp last-message) timestamp)]] diff --git a/src/status_im/ui/screens/profile/contact/views.cljs b/src/status_im/ui/screens/profile/contact/views.cljs index ee26bdcb9d..c84cbaf835 100644 --- a/src/status_im/ui/screens/profile/contact/views.cljs +++ b/src/status_im/ui/screens/profile/contact/views.cljs @@ -9,8 +9,13 @@ [status-im.ui.components.react :as react] [status-im.ui.screens.profile.components.sheets :as sheets] [status-im.ui.screens.profile.contact.styles :as styles] - [status-im.utils.gfycat.core :as gfy] - [status-im.utils.utils :as utils]) + [status-im.utils.utils :as utils] + [status-im.ui.components.topbar :as topbar] + [status-im.ui.components.colors :as colors] + [status-im.ui.components.toolbar :as toolbar] + [status-im.ui.components.keyboard-avoid-presentation :as kb-presentation] + [reagent.core :as reagent] + [clojure.string :as string]) (:require-macros [status-im.utils.views :as views])) (defn actions @@ -47,26 +52,47 @@ ; :content-height 150} ; contact]) +(defn render-detail [{:keys [public-key names name] :as detail}] + [quo/list-item + {:title (:three-words-name names) + :subtitle [quo/text {:monospace true + :color :secondary} + (utils/get-shortened-address public-key)] + :icon [chat-icon/contact-icon-contacts-tab + (multiaccounts/displayed-photo detail)] + :accessibility-label :profile-public-key + :on-press #(re-frame/dispatch [:show-popover (merge {:view :share-chat-key + :address public-key} + (when (and (:ens-name names) name) + {:ens-name name}))]) + :accessory [icons/icon :main-icons/share styles/contact-profile-detail-share-icon]}]) -(defn profile-details [{:keys [alias public-key ens-name] :as contact}] +(defn profile-details [contact] (when contact [react/view [quo/list-header [quo/text {:accessibility-label :profile-details :color :inherit} (i18n/label :t/profile-details)]] - [quo/list-item - {:title (or alias ens-name) - :subtitle [quo/text {:monospace true - :color :secondary} - (utils/get-shortened-address public-key)] - :icon [chat-icon/contact-icon-contacts-tab - (multiaccounts/displayed-photo contact)] - :accessibility-label :profile-public-key - :on-press #(re-frame/dispatch [:show-popover {:view :share-chat-key - :address public-key - :ens-name ens-name}]) - :accessory [icons/icon :main-icons/share styles/contact-profile-detail-share-icon]}]])) + [render-detail contact]])) + +(defn render-chat-settings [{:keys [names]}] + [quo/list-item + {:title (i18n/label :t/nickname) + :size :small + :accessibility-label :profile-nickname-item + :accessory :text + :accessory-text (or (:nickname names) (i18n/label :t/none)) + :on-press #(re-frame/dispatch [:navigate-to :nickname]) + :chevron true}]) + +(defn chat-settings [contact] + [react/view + [quo/list-header + [quo/text {:accessibility-label :chat-settings + :color :inherit} + (i18n/label :t/chat-settings)]] + [render-chat-settings contact]]) ;; TODO: List item (defn block-contact-action [{:keys [blocked? public-key]}] @@ -84,10 +110,56 @@ (i18n/label :t/unblock-contact) (i18n/label :t/block-contact))]]) +(defn save-nickname [public-key nickname] + (re-frame/dispatch [:contacts/update-nickname public-key nickname])) + +(defn valid-nickname? [nickname] + (not (string/blank? nickname))) + +(defn- nickname-input [nickname entered-nickname public-key] + [quo/text-input + {:on-change-text #(reset! entered-nickname %) + :on-submit-editing #(when (valid-nickname? @entered-nickname) + (save-nickname public-key @entered-nickname)) + :auto-capitalize :none + :auto-focus false + :max-length 32 + :accessibility-label :nickname-input + :default-value nickname + :placeholder (i18n/label :t/nickname) + :return-key-type :done + :auto-correct false}]) + +(defn nickname-view [public-key {:keys [nickname ens-name three-words-name]}] + (let [entered-nickname (reagent/atom nickname)] + (fn [] + [kb-presentation/keyboard-avoiding-view {:style {:flex 1}} + [topbar/topbar {:title (i18n/label :t/nickname) + :subtitle (or ens-name three-words-name) + :modal? true}] + [react/view {:flex 1 :padding 16} + [react/text {:style {:color colors/gray :margin-bottom 16}} + (i18n/label :t/nickname-description)] + [nickname-input nickname entered-nickname public-key] + [react/text {:style {:align-self :flex-end :margin-top 16 + :color colors/gray}} + (str (count @entered-nickname) " / 32")]] + [toolbar/toolbar {:show-border? true + :center + [quo/button + {:type :secondary + :on-press #(save-nickname public-key @entered-nickname)} + (i18n/label :t/done)]}]]))) + +(views/defview nickname [] + (views/letsubs [{:keys [public-key names]} [:contacts/current-contact]] + [nickname-view public-key names])) + (views/defview profile [] - (views/letsubs [{:keys [ens-verified name public-key] + (views/letsubs [{:keys [public-key name ens-verified] :as contact} [:contacts/current-contact]] - (let [on-share #(re-frame/dispatch [:show-popover (merge + (let [[first-name second-name] (multiaccounts/contact-two-names contact true) + on-share #(re-frame/dispatch [:show-popover (merge {:view :share-chat-key :address public-key} (when (and ens-verified name) @@ -104,13 +176,11 @@ :accessibility-label :back-button :on-press #(re-frame/dispatch [:navigate-back])}] :extended-header (profile-header/extended-header - {:on-press on-share - :title (multiaccounts/displayed-name contact) - :photo (multiaccounts/displayed-photo contact) + {:on-press on-share + :title first-name + :photo (multiaccounts/displayed-photo contact) :monospace (not ens-verified) - :subtitle (if (and ens-verified public-key) - (gfy/generate-gfy public-key) - public-key)})} + :subtitle second-name})} [react/view {:padding-top 12} (for [{:keys [label subtext accessibility-label icon action disabled?]} (actions contact)] @@ -124,7 +194,6 @@ :disabled disabled? :on-press action}]))] [react/view styles/contact-profile-details-container - [profile-details (cond-> contact - (and ens-verified name) - (assoc :ens-name name))]] + [profile-details contact] + [chat-settings contact]] [block-contact-action contact]]])))) diff --git a/src/status_im/ui/screens/profile/group_chat/views.cljs b/src/status_im/ui/screens/profile/group_chat/views.cljs index 5f035458e7..8999757124 100644 --- a/src/status_im/ui/screens/profile/group_chat/views.cljs +++ b/src/status_im/ui/screens/profile/group_chat/views.cljs @@ -5,7 +5,6 @@ [status-im.i18n :as i18n] [status-im.multiaccounts.core :as multiaccounts] [status-im.ui.components.chat-icon.screen :as chat-icon] - [status-im.ui.components.contact.contact :as contact] [status-im.ui.components.list.views :as list] [status-im.ui.components.profile-header.view :as profile-header] [status-im.ui.components.react :as react] @@ -16,56 +15,59 @@ (:require-macros [status-im.utils.views :refer [defview letsubs]])) (defn member-sheet [chat-id member us-admin?] - [react/view - [quo/list-item - {:theme :accent - :icon [chat-icon/contact-icon-contacts-tab - (multiaccounts/displayed-photo member)] - :title (contact/format-name member) - :subtitle (i18n/label :t/view-profile) - :accessibility-label :view-chat-details-button - :chevron true - :on-press #(chat.sheets/hide-sheet-and-dispatch - [:chat.ui/show-profile - (:public-key member)])}] - (when (and us-admin? - (not (:admin? member))) + (let [[first-name _] (multiaccounts/contact-two-names member false)] + [react/view [quo/list-item {:theme :accent - :title (i18n/label :t/make-admin) - :accessibility-label :make-admin - :icon :main-icons/make-admin - :on-press #(chat.sheets/hide-sheet-and-dispatch [:group-chats.ui/make-admin-pressed chat-id (:public-key member)])}]) - (when-not (:admin? member) - [quo/list-item - {:theme :accent - :title (i18n/label :t/remove-from-chat) - :accessibility-label :remove-from-chat - :icon :main-icons/remove-contact - :on-press #(chat.sheets/hide-sheet-and-dispatch [:group-chats.ui/remove-member-pressed chat-id (:public-key member)])}])]) + :icon [chat-icon/contact-icon-contacts-tab + (multiaccounts/displayed-photo member)] + :title first-name + :subtitle (i18n/label :t/view-profile) + :accessibility-label :view-chat-details-button + :chevron true + :on-press #(chat.sheets/hide-sheet-and-dispatch + [:chat.ui/show-profile + (:public-key member)])}] + (when (and us-admin? + (not (:admin? member))) + [quo/list-item + {:theme :accent + :title (i18n/label :t/make-admin) + :accessibility-label :make-admin + :icon :main-icons/make-admin + :on-press #(chat.sheets/hide-sheet-and-dispatch [:group-chats.ui/make-admin-pressed chat-id (:public-key member)])}]) + (when-not (:admin? member) + [quo/list-item + {:theme :accent + :title (i18n/label :t/remove-from-chat) + :accessibility-label :remove-from-chat + :icon :main-icons/remove-contact + :on-press #(chat.sheets/hide-sheet-and-dispatch [:group-chats.ui/remove-member-pressed chat-id (:public-key member)])}])])) (defn render-member [chat-id {:keys [public-key] :as member} admin? current-user-identity] - [quo/list-item - (merge - {:title (contact/format-name member) - :accessibility-label :member-item - :icon [chat-icon/contact-icon-contacts-tab - (multiaccounts/displayed-photo member)] - :on-press (when (not= public-key current-user-identity) - #(re-frame/dispatch [:chat.ui/show-profile public-key]))} - (when (:admin? member) - {:accessory :text - :accessory-text (i18n/label :t/group-chat-admin)}) - (when (and admin? - (not (:admin? member)) - (not= public-key current-user-identity)) - {:accessory [quo/button {:on-press #(re-frame/dispatch [:bottom-sheet/show-sheet - {:content (fn [] - [member-sheet chat-id member admin?])}]) - :type :icon - :theme :icon - :accessibility-label :menu-option} - :main-icons/more]}))]) + (let [[first-name second-name] (multiaccounts/contact-two-names member false)] + [quo/list-item + (merge + {:title first-name + :subtitle second-name + :accessibility-label :member-item + :icon [chat-icon/contact-icon-contacts-tab + (multiaccounts/displayed-photo member)] + :on-press (when (not= public-key current-user-identity) + #(re-frame/dispatch [:chat.ui/show-profile public-key]))} + (when (:admin? member) + {:accessory :text + :accessory-text (i18n/label :t/group-chat-admin)}) + (when (and admin? + (not (:admin? member)) + (not= public-key current-user-identity)) + {:accessory [quo/button {:on-press #(re-frame/dispatch [:bottom-sheet/show-sheet + {:content (fn [] + [member-sheet chat-id member admin?])}]) + :type :icon + :theme :icon + :accessibility-label :menu-option} + :main-icons/more]}))])) (defview chat-group-members-view [chat-id admin? current-user-identity] (letsubs [members [:contacts/current-chat-contacts]] diff --git a/src/status_im/ui/screens/routing/main.cljs b/src/status_im/ui/screens/routing/main.cljs index 09c68720d4..341021b190 100644 --- a/src/status_im/ui/screens/routing/main.cljs +++ b/src/status_im/ui/screens/routing/main.cljs @@ -23,7 +23,8 @@ [quo.previews.main :as quo.preview] [status-im.utils.config :as config] [status-im.ui.screens.chat.image.preview.views :as image-preview] - [status-im.ui.screens.notifications-settings.views :as notifications-settings])) + [status-im.ui.screens.notifications-settings.views :as notifications-settings] + [status-im.ui.screens.profile.contact.views :as contact])) (defonce main-stack (navigation/create-stack)) (defonce bottom-tabs (navigation/create-bottom-tabs)) @@ -82,6 +83,10 @@ :transition :presentation-ios :insets {:bottom true} :component new-public-chat/new-public-chat} + {:name :nickname + :transition :presentation-ios + :insets {:bottom true} + :component contact/nickname} {:name :edit-group-chat-name :transition :presentation-ios :insets {:bottom true} diff --git a/translations/en.json b/translations/en.json index e002211660..d2a6d7af97 100644 --- a/translations/en.json +++ b/translations/en.json @@ -770,7 +770,6 @@ "new-contract": "New Contract", "new-group": "New group", "new-group-chat": "New group chat", - "add-members": "Add members", "new-network": "New network", "new-pin-description": "Enter new 6-digit passcode", "new-public-group-chat": "Join public chat", @@ -1228,5 +1227,8 @@ "audio": "Audio", "update-to-see-image": "Update to latest version to see a nice image here!", "update-to-listen-audio": "Update to latest version to listen to an audio message here!", - "update-to-see-sticker": "Update to latest version to see a nice sticker here!" + "update-to-see-sticker": "Update to latest version to see a nice sticker here!", + "nickname": "Nickname", + "add-nickname": "Add a nickname (optional)", + "nickname-description": "Nicknames help you identify others in Status.\nOnly you can see the nicknames you’ve added" }