new searchbar design
Signed-off-by: Volodymyr Kozieiev <vkjr.sp@gmail.com>
This commit is contained in:
parent
fb9ea6529f
commit
f3df8b2ec8
|
@ -441,6 +441,7 @@ var TopLevel = {
|
|||
"scrollTo" : function () {},
|
||||
"scrollToEnd" : function () {},
|
||||
"scrollToIndex" : function () {},
|
||||
"scrollToLocation" : function () {},
|
||||
"section" : function () {},
|
||||
"selection" : function () {},
|
||||
"sendDirectMessage" : function () {},
|
||||
|
|
|
@ -1784,15 +1784,21 @@
|
|||
(let [{:keys [name random-name tags]} (val chat)]
|
||||
(into [name random-name] tags)))
|
||||
|
||||
(defn sort-by-timestamp
|
||||
[coll]
|
||||
(when (not-empty coll)
|
||||
(sort-by #(-> % second :timestamp) >
|
||||
(into {} coll))))
|
||||
|
||||
(defn apply-filter
|
||||
"extract-attributes-fn is a function that take an element from the collection
|
||||
and returns a vector of attributes which are strings
|
||||
apply-filter returns the elements for which at least one attribute includes
|
||||
the search-filter
|
||||
apply-filter returns nil if the search-filter is empty or if there is no element
|
||||
that match the filter"
|
||||
apply-filter returns nil if there is no element that match the filter
|
||||
apply-filter returns full collection if the search-filter is empty"
|
||||
[search-filter coll extract-attributes-fn]
|
||||
(when (not-empty search-filter)
|
||||
(if (not-empty search-filter)
|
||||
(let [search-filter (string/lower-case search-filter)
|
||||
results (filter (fn [element]
|
||||
(some (fn [s]
|
||||
|
@ -1801,9 +1807,8 @@
|
|||
search-filter)))
|
||||
(extract-attributes-fn element)))
|
||||
coll)]
|
||||
(when (not-empty results)
|
||||
(sort-by #(-> % second :timestamp) >
|
||||
(into {} results))))))
|
||||
(sort-by-timestamp results))
|
||||
(sort-by-timestamp coll)))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:search/filtered-chats
|
||||
|
|
|
@ -194,8 +194,10 @@
|
|||
(when render-fn {:renderItem (wrap-render-fn render-fn)})
|
||||
(when separator {:ItemSeparatorComponent (fn [] (reagent/as-element separator))})
|
||||
(when empty-component {:ListEmptyComponent (fn [] (reagent/as-element empty-component))})
|
||||
(when header {:ListHeaderComponent (fn [] (reagent/as-element header))})
|
||||
(when footer {:ListFooterComponent (fn [] (reagent/as-element footer))}))))
|
||||
;; header and footer not wrapped in anonymous function to prevent re-creation on every re-render
|
||||
;; More details can be found here - https://github.com/facebook/react-native/issues/13602#issuecomment-300608431
|
||||
(when header {:ListHeaderComponent (reagent/as-element header)})
|
||||
(when footer {:ListFooterComponent (reagent/as-element footer)}))))
|
||||
|
||||
;; Workaround an issue in reagent that does not consider JS array as JS value
|
||||
;; This forces clj <-> js serialization and breaks clj semantic
|
||||
|
|
|
@ -1,140 +0,0 @@
|
|||
(ns status-im.ui.screens.home.filter.views
|
||||
(:require [status-im.ui.components.list.views :as list]
|
||||
[status-im.ui.screens.home.styles :as styles]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.ui.components.colors :as colors]
|
||||
[status-im.ui.screens.home.views.inner-item :as inner-item]
|
||||
[status-im.utils.utils :as utils]
|
||||
[status-im.ui.components.animation :as animation]
|
||||
[reagent.core :as reagent]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.ui.components.icons.vector-icons :as icons]))
|
||||
|
||||
(def animation-duration 150)
|
||||
|
||||
(defn search-input [_ {:keys [on-cancel on-focus on-change]}]
|
||||
(let [input-is-focused? (reagent/atom false)
|
||||
input-ref (reagent/atom nil)]
|
||||
(fn [search-filter]
|
||||
(let [show-cancel? (or @input-is-focused?
|
||||
search-filter)]
|
||||
[react/view {:style styles/search-container}
|
||||
[react/view {:style styles/search-input-container}
|
||||
[icons/icon :main-icons/search {:color colors/gray
|
||||
:container-style {:margin-left 6
|
||||
:margin-right 2}}]
|
||||
[react/text-input {:placeholder (i18n/label :t/search)
|
||||
:blur-on-submit true
|
||||
:multiline false
|
||||
:ref #(reset! input-ref %)
|
||||
:style styles/search-input
|
||||
:default-value search-filter
|
||||
:on-focus #(do
|
||||
(when on-focus
|
||||
(on-focus search-filter))
|
||||
(reset! input-is-focused? true))
|
||||
:on-change (fn [e]
|
||||
(let [native-event (.-nativeEvent e)
|
||||
text (.-text native-event)]
|
||||
(when on-change
|
||||
(on-change text))))}]]
|
||||
(when show-cancel?
|
||||
[react/touchable-highlight
|
||||
{:on-press #(do
|
||||
(when on-cancel
|
||||
(on-cancel))
|
||||
(.blur @input-ref)
|
||||
(reset! input-is-focused? false))
|
||||
:style {:margin-left 16}}
|
||||
[react/text {:style {:color colors/blue}}
|
||||
(i18n/label :t/cancel)]])]))))
|
||||
|
||||
(defonce search-input-state
|
||||
(reagent/atom {:show? false
|
||||
:height (animation/create-value
|
||||
(- styles/search-input-height))
|
||||
:to-hide? false}))
|
||||
|
||||
(defn show-search!
|
||||
[]
|
||||
(when-not (:to-hide? @search-input-state)
|
||||
(swap! search-input-state assoc :show? true)
|
||||
(animation/start
|
||||
(animation/timing (:height @search-input-state)
|
||||
{:toValue 0
|
||||
:duration animation-duration
|
||||
:easing (.out (animation/easing)
|
||||
(.-quad (animation/easing)))
|
||||
:useNativeDriver true})
|
||||
#(swap! search-input-state assoc :to-hide? true))))
|
||||
|
||||
(defn update-search-state!
|
||||
[]
|
||||
(let [visible? (:to-hide? @search-input-state)]
|
||||
(swap! search-input-state assoc :show? visible?)
|
||||
(animation/set-value (:height @search-input-state)
|
||||
(if visible? 0 (- styles/search-input-height)))))
|
||||
|
||||
(defn hide-search!
|
||||
[]
|
||||
(utils/set-timeout
|
||||
#(swap! search-input-state assoc :show? false)
|
||||
350)
|
||||
(animation/start
|
||||
(animation/timing (:height @search-input-state)
|
||||
{:toValue (- styles/search-input-height)
|
||||
:duration animation-duration
|
||||
:easing (.in (animation/easing)
|
||||
(.-quad (animation/easing)))
|
||||
:useNativeDriver true})
|
||||
#(swap! search-input-state assoc :to-hide? false)))
|
||||
|
||||
(defn search-input-wrapper
|
||||
[search-filter]
|
||||
(reagent/create-class
|
||||
{:component-will-unmount
|
||||
#(update-search-state!)
|
||||
:component-did-mount
|
||||
#(update-search-state!)
|
||||
:reagent-render
|
||||
(fn [search-filter]
|
||||
[search-input search-filter
|
||||
{:on-cancel #(do
|
||||
(re-frame/dispatch [:search/filter-changed nil])
|
||||
(hide-search!))
|
||||
:on-focus (fn [search-filter]
|
||||
(when-not search-filter
|
||||
(re-frame/dispatch [:search/filter-changed ""])))
|
||||
:on-change (fn [text]
|
||||
(re-frame/dispatch [:search/filter-changed text]))}])}))
|
||||
|
||||
(defn home-filtered-items-list
|
||||
[chats]
|
||||
[list/section-list
|
||||
{:style {:margin-bottom -35}
|
||||
:sections [{:title :t/chats
|
||||
:data chats}]
|
||||
:key-fn first
|
||||
;; true by default on iOS
|
||||
:stickySectionHeadersEnabled false
|
||||
:render-section-header-fn
|
||||
(fn [{:keys [title data]}]
|
||||
[react/view {:style {:height 40}}
|
||||
[react/text {:style styles/filter-section-title}
|
||||
(i18n/label title)]])
|
||||
:render-section-footer-fn
|
||||
(fn [{:keys [title data]}]
|
||||
(when (empty? data)
|
||||
[list/big-list-item
|
||||
{:text (i18n/label :t/no-result)
|
||||
:text-color colors/gray
|
||||
:hide-chevron? true
|
||||
:action-fn #()
|
||||
:icon (case title
|
||||
"messages" :main-icons/one-on-one-chat
|
||||
"browser" :main-icons/browser
|
||||
"chats" :main-icons/message)
|
||||
:icon-color colors/gray}]))
|
||||
:render-fn (fn [home-item]
|
||||
[inner-item/home-list-item home-item])}])
|
|
@ -1,6 +1,7 @@
|
|||
(ns status-im.ui.screens.home.styles
|
||||
(:require [status-im.ui.components.colors :as colors]
|
||||
[status-im.utils.styles :as styles]))
|
||||
[status-im.utils.styles :as styles]
|
||||
[status-im.utils.platform :as platform]))
|
||||
|
||||
(defn toolbar []
|
||||
{:background-color colors/white})
|
||||
|
@ -24,12 +25,17 @@
|
|||
(def search-input-height 56)
|
||||
|
||||
(def search-container
|
||||
(merge
|
||||
{:height search-input-height
|
||||
:flex-direction :row
|
||||
:padding-horizontal 16
|
||||
:background-color colors/white
|
||||
:align-items :center
|
||||
:justify-content :center})
|
||||
:justify-content :center}
|
||||
(when platform/ios?
|
||||
{:position :absolute
|
||||
:top (- search-input-height)
|
||||
:width "100%"})))
|
||||
|
||||
(def search-input-container
|
||||
{:background-color colors/gray-lighter
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.components.toolbar.view :as toolbar]
|
||||
[status-im.ui.screens.home.styles :as styles]
|
||||
[status-im.ui.screens.home.filter.views :as filter.views]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.ui.components.tabbar.styles :as tabs.styles]
|
||||
[status-im.ui.screens.home.views.inner-item :as inner-item]
|
||||
|
@ -23,6 +22,8 @@
|
|||
[status-im.ui.components.button :as button])
|
||||
(:require-macros [status-im.utils.views :as views]))
|
||||
|
||||
(defonce search-active? (reagent/atom false))
|
||||
|
||||
(defn welcome-video-wrapper []
|
||||
[react/view {:style {:align-items :center
|
||||
:justify-content :center
|
||||
|
@ -85,45 +86,101 @@
|
|||
[react/view {:style {:flex 1 :flex-direction :row :align-items :center :justify-content :center}}
|
||||
[react/i18n-text {:style styles/welcome-blank-text :key :welcome-blank-message}]])
|
||||
|
||||
(defn home-items-view [_ _ _ _ search-input-state]
|
||||
(let [analyze-pos? (reagent/atom true)
|
||||
start-pos (reagent/atom 0)]
|
||||
(fn [search-filter chats all-home-items hide-home-tooltip?]
|
||||
(if search-filter
|
||||
[filter.views/home-filtered-items-list chats]
|
||||
[react/animated-view
|
||||
{:style {:flex 1
|
||||
:background-color :white
|
||||
:margin-bottom (- styles/search-input-height)
|
||||
:transform [{:translateY (:height @search-input-state)}]}}
|
||||
(if (or (seq all-home-items) (not hide-home-tooltip?))
|
||||
[list/flat-list (merge {:data all-home-items
|
||||
:key-fn first
|
||||
:header [react/view {:height 4 :flex 1}]
|
||||
:footer [react/view
|
||||
(when-not hide-home-tooltip?
|
||||
(defn chat-list-footer [hide-home-tooltip?]
|
||||
(let [show-tooltip? (and (not hide-home-tooltip?) (not @search-active?))]
|
||||
[react/view
|
||||
(when show-tooltip?
|
||||
[home-tooltip-view])
|
||||
[react/view {:height 68 :flex 1}]]
|
||||
:on-scroll-begin-drag
|
||||
(fn [e]
|
||||
(reset! analyze-pos? true)
|
||||
(reset! start-pos (.-y (.-contentOffset (.-nativeEvent e)))))
|
||||
[react/view {:height 68 :flex 1}]]))
|
||||
|
||||
(views/defview search-input [{:keys [on-cancel on-focus on-change]}]
|
||||
(views/letsubs
|
||||
[{:keys [search-filter]} [:home-items]
|
||||
input-ref (reagent/atom nil)]
|
||||
[react/view {:style styles/search-container}
|
||||
[react/view {:style styles/search-input-container}
|
||||
[icons/icon :main-icons/search {:color colors/gray
|
||||
:container-style {:margin-left 6
|
||||
:margin-right 2}}]
|
||||
[react/text-input {:placeholder (i18n/label :t/search)
|
||||
:blur-on-submit true
|
||||
:multiline false
|
||||
:ref #(reset! input-ref %)
|
||||
:style styles/search-input
|
||||
:default-value search-filter
|
||||
:on-focus #(do
|
||||
(when on-focus
|
||||
(on-focus search-filter))
|
||||
(reset! search-active? true))
|
||||
:on-change (fn [e]
|
||||
(let [native-event (.-nativeEvent e)
|
||||
text (.-text native-event)]
|
||||
(when on-change
|
||||
(on-change text))))}]]
|
||||
(when @search-active?
|
||||
[react/touchable-highlight
|
||||
{:on-press (fn []
|
||||
(.clear @input-ref)
|
||||
(.blur @input-ref)
|
||||
(when on-cancel
|
||||
(on-cancel))
|
||||
(reset! search-active? false))
|
||||
:style {:margin-left 16}}
|
||||
[react/text {:style {:color colors/blue}}
|
||||
(i18n/label :t/cancel)]])]))
|
||||
|
||||
(defn search-input-wrapper []
|
||||
[search-input
|
||||
{:on-cancel #(re-frame/dispatch [:search/filter-changed nil])
|
||||
:on-focus (fn [search-filter]
|
||||
(when-not search-filter
|
||||
(re-frame/dispatch [:search/filter-changed ""])))
|
||||
:on-change (fn [text]
|
||||
(re-frame/dispatch [:search/filter-changed text]))}])
|
||||
|
||||
(defn section-footer [{:keys [title data]}]
|
||||
(when (and @search-active? (empty? data))
|
||||
[list/big-list-item
|
||||
{:text (i18n/label :t/no-result)
|
||||
:text-color colors/gray
|
||||
:hide-chevron? true
|
||||
:action-fn #()
|
||||
:icon (case title
|
||||
"messages" :main-icons/one-on-one-chat
|
||||
"browser" :main-icons/browser
|
||||
"chats" :main-icons/message)
|
||||
:icon-color colors/gray}]))
|
||||
|
||||
(views/defview home-filtered-items-list []
|
||||
(views/letsubs
|
||||
[{:keys [chats all-home-items]} [:home-items]
|
||||
{:keys [hide-home-tooltip?]} [:multiaccount]]
|
||||
(let [list-ref (reagent/atom nil)]
|
||||
[list/section-list
|
||||
(merge
|
||||
{:sections [{:title :t/chats
|
||||
:data (if @search-active? chats all-home-items)}]
|
||||
:key-fn first
|
||||
;; true by default on iOS
|
||||
:stickySectionHeadersEnabled false
|
||||
:keyboard-should-persist-taps :always
|
||||
:ref #(reset! list-ref %)
|
||||
:footer [chat-list-footer hide-home-tooltip?]
|
||||
:contentInset {:top styles/search-input-height}
|
||||
:render-section-header-fn (fn [data] [react/view])
|
||||
:render-section-footer-fn section-footer
|
||||
:render-fn (fn [home-item]
|
||||
[inner-item/home-list-item home-item])
|
||||
:header (when (or @search-active? (not-empty all-home-items))
|
||||
[search-input-wrapper])
|
||||
:on-scroll-end-drag
|
||||
(fn [e]
|
||||
(reset! analyze-pos? false))
|
||||
:on-scroll
|
||||
(fn [e]
|
||||
(let [y-pos (.-y (.-contentOffset (.-nativeEvent e)))
|
||||
scroling-down? (> y-pos @start-pos)
|
||||
scrolling-up-from-top? (< y-pos -20)]
|
||||
(if (and @analyze-pos? scrolling-up-from-top?)
|
||||
(filter.views/show-search!))
|
||||
(if (and @analyze-pos? scroling-down?)
|
||||
(filter.views/hide-search!))))
|
||||
:render-fn
|
||||
(fn [home-item _]
|
||||
[inner-item/home-list-item home-item])})]
|
||||
[welcome-blank-page])]))))
|
||||
(let [y (-> e .-nativeEvent .-contentOffset .-y)
|
||||
hide-searchbar? (cond
|
||||
platform/ios? (and (neg? y) (> y (- (/ styles/search-input-height 2))))
|
||||
platform/android? (and (< y styles/search-input-height) (> y (/ styles/search-input-height 2))))]
|
||||
(if hide-searchbar?
|
||||
(.scrollToLocation @list-ref #js {:sectionIndex 0 :itemIndex 0}))))})])))
|
||||
|
||||
(views/defview home-action-button [home-width]
|
||||
(views/letsubs [logging-in? [:multiaccounts/login]]
|
||||
|
@ -139,7 +196,7 @@
|
|||
(views/defview home [loading?]
|
||||
(views/letsubs
|
||||
[anim-translate-y (animation/create-value connectivity/neg-connectivity-bar-height)
|
||||
{:keys [search-filter chats all-home-items]} [:home-items]
|
||||
{:keys [all-home-items]} [:home-items]
|
||||
{:keys [hide-home-tooltip?]} [:multiaccount]
|
||||
window-width [:dimensions/window-width]
|
||||
two-pane-ui-enabled? [:two-pane-ui-enabled?]]
|
||||
|
@ -172,13 +229,9 @@
|
|||
[react/activity-indicator {:flex 1
|
||||
:animating true}]
|
||||
[react/view {:flex 1}
|
||||
[filter.views/search-input-wrapper search-filter]
|
||||
[home-items-view
|
||||
search-filter
|
||||
chats
|
||||
all-home-items
|
||||
hide-home-tooltip?
|
||||
filter.views/search-input-state]])]
|
||||
(if (and (empty? all-home-items) hide-home-tooltip? (not @search-active?))
|
||||
[welcome-blank-page]
|
||||
[home-filtered-items-list])])]
|
||||
[home-action-button home-width]]])))
|
||||
|
||||
(views/defview home-wrapper []
|
||||
|
|
|
@ -83,6 +83,7 @@
|
|||
tribute-label))
|
||||
:subtitle-row-accessory [unviewed-indicator chat-id]
|
||||
:on-press #(do
|
||||
(re-frame/dispatch [:dismiss-keyboard])
|
||||
(re-frame/dispatch [:chat.ui/navigate-to-chat chat-id])
|
||||
(re-frame/dispatch [:chat.ui/mark-messages-seen :chat]))
|
||||
:on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet chat-actions {:chat-id chat-id}])}]))
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
:random-name "random-name4"
|
||||
:tags #{"tag4"}}}]
|
||||
(testing "no search filter"
|
||||
(is (= 0
|
||||
(is (= (count chats)
|
||||
(count (search.subs/apply-filter ""
|
||||
chats
|
||||
search.subs/extract-chat-attributes)))))
|
||||
|
|
Loading…
Reference in New Issue