diff --git a/src/quo/components/list/item.cljs b/src/quo/components/list/item.cljs index 6f97715a66..9a164394f1 100644 --- a/src/quo/components/list/item.cljs +++ b/src/quo/components/list/item.cljs @@ -83,7 +83,7 @@ [icons/icon icon {:color icon-color}]])]))) (defn title-column - [{:keys [title text-color subtitle subtitle-max-lines + [{:keys [title text-color subtitle subtitle-max-lines subtitle-secondary title-accessibility-label size text-size title-text-weight right-side-present?]}] [rn/view {:style (merge (:tiny spacing/padding-horizontal) @@ -105,14 +105,37 @@ :size text-size} title] title) - (if (string? subtitle) - [text/text {:weight :regular - :color :secondary - :ellipsize-mode :tail - :number-of-lines subtitle-max-lines - :size text-size} - subtitle] - subtitle)] + (if (string? subtitle-secondary) + [rn/view {:flex-direction :row} + [text/text {:style {:max-width "56.5%"} + :weight :regular + :color :secondary + :ellipsize-mode :tail + :number-of-lines subtitle-max-lines + :size text-size} + subtitle] + [text/text {:style {:width "7%" :text-align :center} + :weight :regular + :color :secondary + :ellipsize-mode :middle + :number-of-lines subtitle-max-lines + :size text-size} + "•"] + [text/text {:style {:max-width "36.5%"} + :weight :regular + :color :secondary + :ellipsize-mode :middle + :number-of-lines subtitle-max-lines + :size text-size} + subtitle-secondary]] + (if (string? subtitle) + [text/text {:weight :regular + :color :secondary + :ellipsize-mode :tail + :number-of-lines subtitle-max-lines + :size text-size} + subtitle] + subtitle))] title (if (string? title) @@ -170,7 +193,7 @@ (defn list-item [{:keys [theme accessory disabled subtitle-max-lines icon icon-container-style left-side-alignment - title subtitle active on-press on-long-press chevron size text-size + title subtitle subtitle-secondary active on-press on-long-press chevron size text-size accessory-text accessibility-label title-accessibility-label haptic-feedback haptic-type error animated animated-accessory? title-text-weight] :or {subtitle-max-lines 1 @@ -223,6 +246,7 @@ :text-size text-size :subtitle subtitle :subtitle-max-lines subtitle-max-lines + :subtitle-secondary subtitle-secondary :right-side-present? (or accessory chevron)}] [right-side {:chevron chevron :active active 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 2317bb5500..cf2c3c6bc4 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,22 @@ [status-im.ui.components.topbar :as topbar] [status-im.utils.debounce :as debounce] [status-im.utils.utils :as utils] - [reagent.core :as reagent]) + [reagent.core :as reagent] + [quo.react-native :as rn] + [clojure.string :as string] + [status-im.ui.components.invite.views :as invite] + [status-im.ethereum.ens :as ens] + [quo.platform :as platform] + [status-im.transport.filters.core :as filters] + [status-im.utils.identicon :as identicon]) (:require-macros [status-im.utils.views :as views])) (defn- render-row [row] - (let [[first-name second-name] (multiaccounts/contact-two-names row false)] + (let [first-name (first (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)])}])) @@ -57,13 +62,33 @@ (defn get-validation-label [value] (case value :invalid - (i18n/label :t/user-not-found) + (i18n/label :t/profile-not-found) :yourself (i18n/label :t/can-not-add-yourself))) +(defn search-contacts [filter-text {:keys [name alias nickname]}] + (or + (string/includes? (string/lower-case (str name)) filter-text) + (string/includes? (string/lower-case (str alias)) filter-text) + (when nickname + (string/includes? (string/lower-case (str nickname)) filter-text)))) + +(defn filter-contacts [filter-text contacts] + (let [lower-filter-text (string/lower-case filter-text)] + (if filter-text + (filter (partial search-contacts lower-filter-text) contacts) + contacts))) + +(defn is-valid-username? [username] + (let [is-chat-key? (and (filters/is-public-key? username) + (= (count username) 132)) + is-ens? (ens/valid-eth-name-prefix? username)] + (or is-chat-key? is-ens?))) + (views/defview new-chat [] (views/letsubs [contacts [:contacts/active] - {:keys [state ens-name public-key error]} [:contacts/new-identity]] + {:keys [state ens-name public-key error]} [:contacts/new-identity] + search-value (reagent/atom "")] [react/view {:style {:flex 1}} [topbar/topbar {:title (i18n/label :t/new-chat) @@ -76,11 +101,11 @@ :handler :contact/qr-code-scanned}])}]}] [react/view {:flex-direction :row :padding 16} - [react/view {:flex 1 - :padding-right 16} + [react/view {:flex 1} [quo/text-input {:on-change-text #(do + (reset! search-value %) (re-frame/dispatch [:set-in [:contacts/new-identity :state] :searching]) (debounce/debounce-and-dispatch [:new-chat/set-new-identity %] 600)) :on-submit-editing @@ -90,35 +115,59 @@ :show-cancel false :accessibility-label :enter-contact-code-input :auto-capitalize :none - :return-key-type :go}]] - [react/view {:justify-content :center - :align-items :center} - [input-icon state false nil]]] - [react/view {:min-height 30 :justify-content :flex-end} - [quo/text {:style {:margin-horizontal 16} - :size :small - :align :center - :color :secondary} - (cond (= state :error) - (get-validation-label error) - - (= state :valid) - (str (if ens-name - ens-name - (gfycat/generate-gfy public-key)) - " • ") - - :else "") - (when (= state :valid) - [quo/text {:monospace true - :size :inherit - :color :inherit} - (utils/get-shortened-address public-key)])]] - [list/flat-list {:data contacts - :key-fn :address - :render-fn render-row - :enableEmptySections true - :keyboardShouldPersistTaps :always}]])) + :return-key-type :go + :monospace true}]]] + [react/view (if (and + (= (count contacts) 0) + (= @search-value "")) + {:flex 1} + {:justify-content :flex-end}) + (if (and + (= (count contacts) 0) + (= @search-value "")) + [react/view {:flex 1 + :align-items :center + :padding-horizontal 58 + :padding-top 160} + [quo/text {:size :base + :align :center + :color :secondary} + (i18n/label :t/you-dont-have-contacts-invite-friends)] + [invite/button]] + [list/flat-list {:data (filter-contacts @search-value contacts) + :key-fn :address + :render-fn render-row + :enableEmptySections true + :keyboardShouldPersistTaps :always}])] + (when-not (= @search-value "") + [react/view + [quo/text {:style {:margin-horizontal 16 + :margin-vertical 14} + :size :base + :align :left + :color :secondary} + (i18n/label :t/non-contacts)] + (when (and (= state :searching) + (is-valid-username? @search-value)) + [rn/activity-indicator {:color colors/gray + :size (if platform/android? :large :small)}]) + (if (= state :valid) + [quo/list-item + (merge + {:title (or ens-name (gfycat/generate-gfy public-key)) + :subtitle (if ens-name (gfycat/generate-gfy public-key) (utils/get-shortened-address public-key)) + :icon [chat-icon/contact-icon-contacts-tab + (identicon/identicon public-key)] + :on-press #(re-frame/dispatch [:chat.ui/start-chat public-key])} + (when ens-name {:subtitle-secondary public-key}))] + [quo/text {:style {:margin-horizontal 16} + :size :base + :align :center + :color :secondary} + (if (is-valid-username? @search-value) + (when (= state :error) + (get-validation-label error)) + (i18n/label :t/invalid-username-or-key))])])])) (defn- nickname-input [entered-nickname] [quo/text-input diff --git a/test/appium/tests/atomic/chats/test_chats_management.py b/test/appium/tests/atomic/chats/test_chats_management.py index b5cc9d7a4a..5a3cf2aa76 100644 --- a/test/appium/tests/atomic/chats/test_chats_management.py +++ b/test/appium/tests/atomic/chats/test_chats_management.py @@ -95,13 +95,13 @@ class TestChatManagement(SingleDeviceTestCase): chat.public_key_edit_box.clear() chat.public_key_edit_box.set_value(invalid_chat_key) chat.confirm() - if not home.element_by_translation_id("user-not-found").is_element_displayed(): + if not home.element_by_translation_id("profile-not-found").is_element_displayed(): self.errors.append('Error is not shown for invalid public key') home.just_fyi("Check that valid ENS is resolved") chat.public_key_edit_box.clear() chat.public_key_edit_box.set_value(ens_user_ropsten['ens']) - resolved_ens = chat.get_resolved_chat_key('%s.stateofus.eth' % ens_user_ropsten['ens'], ens_user_ropsten['public_key']) + resolved_ens = '%s.stateofus.eth' % ens_user_ropsten['ens'] if not chat.element_by_text(resolved_ens).is_element_displayed(10): self.errors.append('ENS name is not resolved after pasting chat key') home.back_button.click() @@ -116,8 +116,7 @@ class TestChatManagement(SingleDeviceTestCase): chat.public_key_edit_box.paste_text_from_clipboard() if chat.public_key_edit_box.text != public_key: self.errors.append('Public key is not pasted from clipboard') - expected_resolved_name = chat.get_resolved_chat_key(basic_user['username'], public_key) - if not chat.element_by_text(expected_resolved_name).is_element_displayed(): + if not chat.element_by_text(basic_user['username']).is_element_displayed(): self.errors.append('3 random-name is not resolved after pasting chat key') chat.public_key_edit_box.click() chat.confirm_until_presence_of_element(chat.chat_message_input) @@ -585,10 +584,9 @@ class TestChatManagementMultipleDevice(MultipleDeviceTestCase): 'Check that user is added to contacts below "Start new chat" and you redirected to 1-1 on tap') home_1.plus_button.click() home_1.start_new_chat_button.click() - for name in (nickname, username_2): - if not home_1.element_by_text(name).is_element_displayed(): - home_1.driver.fail('List of contacts below "Start new chat" does not contain added user') - home_1.element_by_text(username_2).click() + if not home_1.element_by_text(nickname).is_element_displayed(): + home_1.driver.fail('List of contacts below "Start new chat" does not contain added user') + home_1.element_by_text(nickname).click() if not chat_1.chat_message_input.is_element_displayed(): home_1.driver.fail('No redirect to 1-1 chat if tap on Contact below "Start new chat"') for element in (chat_1.chat_message_input, chat_1.element_by_text(nickname)): @@ -599,7 +597,7 @@ class TestChatManagementMultipleDevice(MultipleDeviceTestCase): device_1.just_fyi('Remove user from contacts') chat_1.profile_button.click() - userprofile = profile_1.open_contact_from_profile(username_2) + userprofile = profile_1.open_contact_from_profile(nickname) userprofile.remove_from_contacts.click() if userprofile.remove_from_contacts.is_element_displayed(): self.errors.append("'Remove from contacts' is not changed to 'Add to contacts'") @@ -608,7 +606,7 @@ class TestChatManagementMultipleDevice(MultipleDeviceTestCase): device_1.just_fyi('Check that user is removed from contact list in profile') userprofile.back_button.click() - if profile_1.element_by_text(username_2).is_element_displayed(): + if profile_1.element_by_text(nickname).is_element_displayed(): self.errors.append('List of contacts in profile contains removed user') profile_1.home_button.click(desired_view='chat') if not chat_1.add_to_contacts.is_element_displayed(): diff --git a/translations/en.json b/translations/en.json index f3c071f5ca..ce11217069 100644 --- a/translations/en.json +++ b/translations/en.json @@ -503,7 +503,7 @@ "enter-a-private-key": "Enter a private key", "enter-a-seed-phrase": "Enter a seed phrase", "enter-address": "Enter address", - "enter-contact-code": "Enter ENS username or chat key", + "enter-contact-code": "ENS (vitalik94) or chat key (0x04…)", "enter-pair-code": "Enter your pairing code", "pair-code-placeholder": "Pair code...", "enter-pair-code-description": "Pairing code was displayed to you during the Keycard setup", @@ -640,6 +640,7 @@ "invalid-number": "Invalid number", "invalid-pairing-password": "Invalid pairing password", "invalid-range": "Invalid format, must be between {{min}} and {{max}}", + "invalid-username-or-key": "Invalid username or chat key", "join-me": "Hey join me on Status: {{url}}", "join-a-community": "or join a community", "http-gateway-error": "Oops, request failed!", @@ -1304,6 +1305,7 @@ "you-are-all-set-description": "If you lose your phone, you can now access your funds and chat key using your seed phrase", "you-can-change-account": "You can change the account name and color to what you wish", "you-dont-have-stickers": "You don’t have any stickers yet", + "you-dont-have-contacts-invite-friends": "You don’t have any contacts yet.\nInvite your friends to start chatting.", "your-contact-code": "Granting access authorizes this DApp to retrieve your chat key", "your-data-belongs-to-you": "If you lose your seed phrase you lose your data and funds", "your-data-belongs-to-you-description": "If you lose access, for example by losing your phone, you can only access your keys with your seed phrase. No one, but you has your seed phrase. Write it down. Keep it safe", @@ -1338,7 +1340,7 @@ "add-seed-account": "Add account with a seed phrase", "account-exists-title": "Account already exists", "add-private-key-account": "Add account from private key", - "user-not-found": "User not found", + "profile-not-found": "Profile not found", "waku-bloom-filter-mode": "Waku bloom filter mode", "appearance": "Appearance", "preference": "Preference", @@ -1496,5 +1498,6 @@ "rpc-usage-reset": "Reset", "rpc-usage-filter": "Filter methods", "rpc-usage-copy": "Copy", - "community-message-preview": "Invitation to join {{community-name}}" + "community-message-preview": "Invitation to join {{community-name}}", + "non-contacts": "Non contacts" }