feat(wallet): implement search in saved addresses list (#20443)

This commit adds a feature to search through the saved addresses list by name, address, ENS and chain names.

Signed-off-by: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com>
This commit is contained in:
Mohamed Javid 2024-06-13 19:28:41 +05:30 committed by GitHub
parent 60b3d01ceb
commit aff4eb89cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 140 additions and 72 deletions

View File

@ -4,19 +4,19 @@
[quo.foundations.colors :as colors]))
(defn placeholder-color
[state blur? override-theme]
[state blur? theme]
(cond
(and blur? (= state :active))
(colors/theme-colors colors/neutral-80-opa-20 colors/white-opa-20 override-theme)
(colors/theme-colors colors/neutral-80-opa-20 colors/white-opa-20 theme)
blur? ; state is default
(colors/theme-colors colors/neutral-80-opa-40 colors/white-opa-30 override-theme)
(colors/theme-colors colors/neutral-80-opa-40 colors/white-opa-30 theme)
(= state :active)
(colors/theme-colors colors/neutral-30 colors/neutral-60 override-theme)
(colors/theme-colors colors/neutral-30 colors/neutral-60 theme)
:else ; Not blur and state is default
(colors/theme-colors colors/neutral-40 colors/neutral-50 override-theme)))
(colors/theme-colors colors/neutral-40 colors/neutral-50 theme)))
(def clear-icon-container
{:justify-content :center
@ -26,16 +26,14 @@
:width 20})
(defn clear-icon
[blur? override-theme]
[blur? theme]
(if blur?
(colors/theme-colors colors/neutral-80-opa-30 colors/white-opa-10 override-theme)
(colors/theme-colors colors/neutral-40 colors/neutral-60 override-theme)))
(colors/theme-colors colors/neutral-80-opa-30 colors/white-opa-10 theme)
(colors/theme-colors colors/neutral-40 colors/neutral-60 theme)))
(defn cursor
[customization-color override-theme]
(colors/theme-colors (colors/custom-color customization-color 50)
(colors/custom-color customization-color 60)
override-theme))
[customization-color theme]
(colors/resolve-color customization-color theme))
(def tag-separator {:width 8})
@ -61,10 +59,10 @@
:justify-content :flex-start})
(defn input-text
[disabled?]
[disabled? theme]
(assoc (text/text-style {:size :paragraph-1
:weight :regular}
nil)
theme)
:flex 1
:padding-vertical 5
:min-width 120

View File

@ -4,6 +4,7 @@
[quo.components.icon :as icon]
[quo.components.inputs.search-input.style :as style]
[quo.foundations.colors :as colors]
[quo.theme]
[react-native.core :as rn]))
(def ^:private tag-separator [rn/view {:style style/tag-separator}])
@ -15,12 +16,12 @@
tags-coll))
(defn- clear-button
[{:keys [on-press blur? override-theme]}]
[{:keys [on-press blur? theme]}]
[rn/touchable-opacity
{:style style/clear-icon-container
:on-press on-press}
[icon/icon :i/clear
{:color (style/clear-icon blur? override-theme)
{:color (style/clear-icon blur? theme)
:size 20}]])
(defn- handle-backspace
@ -31,7 +32,7 @@
(def ^:private props-to-remove
[:cursor-color :placeholder-text-color :editable :on-change-text :on-focus
:on-blur :on-clear :value :tags :disabled? :blur? :customization-color :override-theme])
:on-blur :on-clear :value :tags :disabled? :blur? :customization-color])
(defn- add-children
[text-input children]
@ -40,12 +41,13 @@
text-input))
(defn search-input
[{:keys [value tags disabled? blur? on-change-text customization-color
on-clear on-focus on-blur override-theme container-style]
[{:keys [value tags disabled? blur? on-change-text customization-color show-clear-button?
on-clear on-focus on-blur container-style]
:or {customization-color :blue}
:as props}
& children]
(let [[state set-state] (rn/use-state :default)
(let [theme (quo.theme/use-theme)
[state set-state] (rn/use-state :default)
on-focus (rn/use-callback
(fn []
(set-state :active)
@ -72,20 +74,20 @@
[inner-tags tags])
(add-children
[rn/text-input
(cond-> {:style (style/input-text disabled?)
:cursor-color (style/cursor customization-color override-theme)
:placeholder-text-color (style/placeholder-color state blur? override-theme)
(cond-> {:style (style/input-text disabled? theme)
:cursor-color (style/cursor customization-color theme)
:placeholder-text-color (style/placeholder-color state blur? theme)
:editable (not disabled?)
:on-key-press on-key-press
:keyboard-appearance (colors/theme-colors :light :dark override-theme)
:keyboard-appearance (colors/theme-colors :light :dark theme)
:on-change-text on-change-text
:on-focus on-focus
:on-blur on-blur}
use-value? (assoc :value value)
(seq clean-props) (merge clean-props))]
(when-not use-value? children))]
(when (or (seq value) (seq children))
(when (or (seq value) (seq children) show-clear-button?)
[clear-button
{:on-press on-clear
:blur? blur?
:override-theme override-theme}])]))
{:on-press on-clear
:blur? blur?
:theme theme}])]))

View File

@ -70,7 +70,8 @@
(colors/theme-colors colors/neutral-10 colors/neutral-80 theme))}))
(def search-input-container
{:margin-top 8
{:flex 0
:margin-top 8
:margin-bottom 12
:margin-horizontal 20})

View File

@ -1,16 +1,5 @@
(ns status-im.contexts.settings.wallet.saved-addresses.style)
(def title-container
{:flex 0
:padding-horizontal 20
:margin-top 12
:margin-bottom 16})
(defn page-wrapper
[inset-top]
{:padding-top inset-top
:flex 1})
(def empty-container-style
{:justify-content :center
:flex 1})

View File

@ -1,17 +1,20 @@
(ns status-im.contexts.settings.wallet.saved-addresses.view
(:require
[clojure.string :as string]
[oops.core :as oops]
[quo.core :as quo]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[react-native.platform :as platform]
[status-im.common.resources :as resources]
[status-im.contexts.settings.wallet.saved-addresses.sheets.address-options.view :as address-options]
[status-im.contexts.settings.wallet.saved-addresses.style :as style]
[utils.debounce :as debounce]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- empty-state
(defn- empty-list
[]
(let [theme (quo.theme/use-theme)]
[quo/empty-state
@ -20,6 +23,15 @@
:image (resources/get-themed-image :sweating-man theme)
:container-style style/empty-container-style}]))
(defn- empty-result
[]
(let [theme (quo.theme/use-theme)]
[quo/empty-state
{:title (i18n/label :t/nothing-found)
:description (i18n/label :t/try-to-search-something-else)
:image (resources/get-themed-image :no-contacts theme)
:container-style style/empty-container-style}]))
(defn- saved-address
[{:keys [name address chain-short-names customization-color ens? ens network-preferences-names]}]
(let [full-address (str chain-short-names address)
@ -50,15 +62,36 @@
:on-press on-press-saved-address}]))
(defn- header
[{:keys [title]}]
[{:keys [title index]}]
[quo/divider-label
{:tight? true
:blur? true}
{:tight? true
:blur? true
:container-style (when (pos? index) {:margin-top 8})}
title])
(defn- footer
[]
[rn/view {:height 8}])
(defn- filtered-list
[{:keys [search-text]}]
[rn/flat-list
{:key-fn :address
:data (rf/sub [:wallet/filtered-saved-addresses search-text])
:render-fn saved-address
:shows-vertical-scroll-indicator false
:keyboard-should-persist-taps :always
:content-container-style {:flex-grow 1}
:empty-component [empty-result]}])
(defn- unfiltered-list
[{:keys [grouped-saved-addresses]}]
[rn/section-list
{:key-fn :title
:shows-vertical-scroll-indicator false
:sticky-section-headers-enabled false
:keyboard-should-persist-taps :always
:render-section-header-fn header
:sections grouped-saved-addresses
:render-fn saved-address
:content-container-style {:flex-grow 1}
:empty-component [empty-list]}])
(defn- navigate-back
[]
@ -70,33 +103,56 @@
(defn view
[]
(let [inset-top (safe-area/get-top)
customization-color (rf/sub [:profile/customization-color])
saved-addresses (rf/sub [:wallet/grouped-saved-addresses])]
(let [alert-banners-top-margin (rf/sub [:alert-banners/top-margin])
customization-color (rf/sub [:profile/customization-color])
has-saved-addresses? (rf/sub [:wallet/has-saved-addresses?])
grouped-saved-addresses (rf/sub [:wallet/grouped-saved-addresses])
input-ref (rn/use-ref-atom nil)
[search-text set-search-text] (rn/use-state "")
set-input-ref (rn/use-callback #(reset! input-ref %))
on-clear-input (rn/use-callback
(fn []
(some-> @input-ref
(oops/ocall "clear"))
(set-search-text "")))
on-change-text (rn/use-callback
(debounce/debounce
#(set-search-text %)
500))
search-active? (not (string/blank? search-text))
page-top-props (rn/use-memo
#(cond-> {:title (i18n/label :t/saved-addresses)
:accessibility-label :saved-addresses-header
:title-right :action
:title-right-props {:icon :i/add
:customization-color
customization-color
:on-press add-address-to-save}
:blur? true}
has-saved-addresses?
(assoc
:input :search
:input-props {:placeholder (i18n/label
:t/name-ens-or-address)
:ref set-input-ref
:on-change-text on-change-text
:show-clear-button? search-active?
:on-clear on-clear-input
:customization-color customization-color}))
[has-saved-addresses? customization-color search-text])]
[quo/overlay
{:type :shell
:container-style (style/page-wrapper inset-top)}
{:type :shell
:top-inset? true}
[quo/page-nav
{:key :header
:background :blur
:icon-name :i/arrow-left
:on-press navigate-back}]
[quo/standard-title
{:title (i18n/label :t/saved-addresses)
:accessibility-label :saved-addresses-header
:right :action
:on-press add-address-to-save
:customization-color customization-color
:icon :i/add
:container-style style/title-container}]
[rn/section-list
{:key-fn :title
:shows-vertical-scroll-indicator false
:sticky-section-headers-enabled false
:render-section-header-fn header
:render-section-footer-fn footer
:sections saved-addresses
:render-fn saved-address
:separator [rn/view {:style {:height 4}}]
:content-container-style {:flex-grow 1}
:empty-component [empty-state]}]]))
[quo/page-top page-top-props]
[rn/keyboard-avoiding-view
{:style {:flex 1}
:keyboard-vertical-offset (if platform/ios? alert-banners-top-margin 0)}
(if search-active?
[filtered-list {:search-text search-text}]
[unfiltered-list {:grouped-saved-addresses grouped-saved-addresses}])]]))

View File

@ -26,6 +26,12 @@
(fn [saved-addresses [_ address]]
(contains? saved-addresses address)))
(rf/reg-sub
:wallet/has-saved-addresses?
:<- [:wallet/saved-addresses-by-network-mode]
(fn [saved-addresses]
(-> saved-addresses vals boolean)))
(rf/reg-sub
:wallet/grouped-saved-addresses
:<- [:wallet/saved-addresses-by-network-mode]
@ -49,3 +55,19 @@
:<- [:wallet/saved-addresses-by-network-mode]
(fn [saved-addresses [_ address]]
(get saved-addresses address)))
(rf/reg-sub
:wallet/filtered-saved-addresses
:<- [:wallet/saved-addresses-by-network-mode]
(fn [saved-addresses [_ query]]
(->> saved-addresses
vals
(sort-by :name)
(filter
(fn [{:keys [name address ens chain-short-names]}]
(let [lowercase-query (string/lower-case query)]
(or
(string/includes? (string/lower-case name) lowercase-query)
(string/includes? address lowercase-query)
(string/includes? ens lowercase-query)
(string/includes? chain-short-names lowercase-query))))))))