From 73983b2abd04de8609d516394a15d19b428af18f Mon Sep 17 00:00:00 2001 From: Icaro Motta Date: Wed, 5 Oct 2022 14:44:37 -0300 Subject: [PATCH] [#13967] Displays filtering tabs and read/unread filter (#14105) --- resources/images/icons/close20@2x.png | Bin 0 -> 454 bytes resources/images/icons/close20@3x.png | Bin 0 -> 673 bytes resources/images/icons/unread20@2x.png | Bin 0 -> 249 bytes resources/images/icons/unread20@3x.png | Bin 0 -> 282 bytes src/mocks/js_dependencies.cljs | 3 + src/quo2/components/tabs/tabs.cljs | 140 ++++++++++++++++-- src/status_im/subs/activity_center.cljs | 14 +- src/status_im/ui/components/react.cljs | 3 + .../ui/screens/activity_center/views.cljs | 70 +++++++-- src/status_im/utils/number.cljs | 15 ++ translations/en.json | 8 +- 11 files changed, 224 insertions(+), 29 deletions(-) create mode 100644 resources/images/icons/close20@2x.png create mode 100644 resources/images/icons/close20@3x.png create mode 100644 resources/images/icons/unread20@2x.png create mode 100644 resources/images/icons/unread20@3x.png create mode 100644 src/status_im/utils/number.cljs diff --git a/resources/images/icons/close20@2x.png b/resources/images/icons/close20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..139f5cfd3a72c5e6c960b2eae186404b2f2a5608 GIT binary patch literal 454 zcmV;%0XhDOP)q^j%Rq*CQi ziV{2iUp`r~V;~R+1OnSZqQ{qsPVXRu8O(xD7&z2v$x9;1+B(NhGO`&oL-Y)&jL!;~ zxJa^wwN*=HRi;OF^x^SPkKSEovN?0iU>5!Z7s=k_TqJv!TSszZayF7a@$}b%d{@`Ez?g w*uomtc4c~DPp1=?nQOxgW+4y=1OnT^H^ceCnA6bg>i_@%07*qoM6N<$f-+mX5&!@I literal 0 HcmV?d00001 diff --git a/resources/images/icons/close20@3x.png b/resources/images/icons/close20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..44e11166d8ed0fbd46825825d8f5dcabdb3f5975 GIT binary patch literal 673 zcmV;S0$%-zP)PT?)*Y&)e=t;h$$jE4leZO?|#c2?N$0 zdu?=AhqCW(&N>AY4DOo6os8Pw?&1CWDm8iv7E{WW@?T9|RTNM#7z1;&xW$Ej8r%I* zS7k|o1%nY6nhT4fz$i7E6N{q3C>5F;i;{p*ELsAKl7UeuS_+Gjg3+vKNi0eZMz2Ln zV~6L!m$5~W(e*A-Ta4~+he&Dca3*h0p{JY*JN9l03G^~r275P&gc_pz4n4W1t=wnh zWeF^bS`W`wvpNNf?BI7Yv0SAmn|?KZ2v_f}`FM6+U+fce>|kTRt-9OO|M9(9={79y zMs8Wz&)nFusm~6HG3MJ6us0hMBdf+;pztUkT*$^=<}G#p)Yb7OF~tUc4trIV2Y9w% zizR6^3-~oypENo-cowWr5}gzrg7rzElYwKfJ_&RZa0;xC8yyW!iS==!qre7|jGP-U^J>E98>%KVgxcrq|J%m3E`8#K zoFm7V#s%nwC@U;-4jNZozsHF7zG9Q7BQz>my_A~PO2N|G6qjqKbLh*2~7ZRB3a4+ literal 0 HcmV?d00001 diff --git a/resources/images/icons/unread20@3x.png b/resources/images/icons/unread20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..118d80db4c553b607d9c82d209c76dd04d32708e GIT binary patch literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^HXzKw1|+Ti+$;i8oCO|{#S9FJ79h;%I?XTvD9BhG zgB=ckb3} fade-percentage 0.99) + 0.99 + (number-utils/naive-round fade-percentage 2)))) + +(defn scrollable-tabs + "Just like the component `tabs`, displays horizontally scrollable tabs with + extra options to control if/how the end of the scroll view fades. + + Tabs are rendered using ReactNative's FlatList, which offers the convenient + `scrollToIndex` method. FlatList accepts VirtualizedList and ScrollView props, + and so does this component. + + Usage: + [tabs/scrollable-tabs + {:scroll-on-press? true + :fade-end? true + :on-change #(...) + :default-active :tab-a + :data [{:id :tab-a :label \"Tab A\"} + {:id :tab-b :label \"Tab B\"}]}]] + + Opts: + - `size` number + - `scroll-on-press?` When non-nil, clicking on a tab centers it the middle + (with animation enabled). + - `fade-end?` When non-nil, causes the end of the scrollable view to fade out. + - `fade-end-percentage` Percentage where fading starts relative to the total + layout width of the `flat-list` data." + [{:keys [default-active fade-end-percentage] + :or {fade-end-percentage 0.8}}] + (let [active-tab-id (reagent/atom default-active) + fading (reagent/atom {:fade-end-percentage fade-end-percentage}) + flat-list-ref (atom nil)] + (fn [{:keys [data + fade-end-percentage + fade-end? + on-change + on-scroll + scroll-event-throttle + scroll-on-press? + size] + :or {fade-end-percentage fade-end-percentage + fade-end? false + scroll-event-throttle 64 + scroll-on-press? false + size default-tab-size} + :as props}] + (let [maybe-mask-wrapper (if fade-end? + [react/masked-view + {:mask-element (reagent/as-element + [react/linear-gradient {:colors ["black" "transparent"] + :locations [(@fading :fade-end-percentage) 1] + :start {:x 0 :y 0} + :end {:x 1 :y 0} + :pointer-events :none + :style {:width "100%" + :height "100%"}}])}] + [:<>])] + (conj maybe-mask-wrapper + [rn/flat-list + (merge (dissoc props + :default-active + :fade-end-percentage + :fade-end? + :on-change + :scroll-on-press? + :size) + {:ref (partial reset! flat-list-ref) + :extra-data (str @active-tab-id) + :horizontal true + :scroll-event-throttle scroll-event-throttle + :shows-horizontal-scroll-indicator false + :data data + :key-fn (comp str :id) + :on-scroll (fn [^js e] + (when fade-end? + (let [offset-x (oget e "nativeEvent.contentOffset.x") + content-width (oget e "nativeEvent.contentSize.width") + layout-width (oget e "nativeEvent.layoutMeasurement.width") + new-percentage (calculate-fade-end-percentage {:offset-x offset-x + :content-width content-width + :layout-width layout-width + :max-fade-percentage fade-end-percentage})] + ;; Avoid unnecessary re-rendering. + (when (not= new-percentage (@fading :fade-end-percentage)) + (swap! fading assoc :fade-end-percentage new-percentage)))) + (when on-scroll + (on-scroll e))) + :render-fn (fn [{:keys [id label]} index] + [rn/view {:style {:margin-right (if (= size default-tab-size) 12 8) + :padding-right (when (= index (dec (count data))) + (get-in props [:style :padding-left]))}} + [tab/tab {:id id + :size size + :active (= id @active-tab-id) + :on-press (fn [id] + (reset! active-tab-id id) + (when scroll-on-press? + (.scrollToIndex @flat-list-ref + #js {:animated true + :index index + :viewPosition 0.5})) + (when on-change + (on-change id)))} + label]])})]))))) diff --git a/src/status_im/subs/activity_center.cljs b/src/status_im/subs/activity_center.cljs index 53dbc1a77b..5b7e3a5559 100644 --- a/src/status_im/subs/activity_center.cljs +++ b/src/status_im/subs/activity_center.cljs @@ -20,13 +20,19 @@ (fn [db] (get-in db [:activity-center :current-status-filter]))) +(re-frame/reg-sub + :activity-center/status-filter-unread-enabled? + :<- [:activity-center/current-status-filter] + (fn [current-status-filter] + (= :unread current-status-filter))) + (re-frame/reg-sub :activity-center/notifications-per-read-status :<- [:activity-center/notifications-read] :<- [:activity-center/notifications-unread] - :<- [:activity-center/current-status-filter] - (fn [[notifications-read notifications-unread status-filter]] - (if (= status-filter :unread) + :<- [:activity-center/status-filter-unread-enabled?] + (fn [[notifications-read notifications-unread unread-filter-enabled?]] + (if unread-filter-enabled? notifications-unread notifications-read))) @@ -64,4 +70,4 @@ (map #(assoc % :timestamp (or (:timestamp %) (:timestamp (or (:message %) (:last-message %)))) :contact (multiaccounts/contact-by-identity contacts (get-in % [:message :from]))) - supported-notifications))))) \ No newline at end of file + supported-notifications))))) diff --git a/src/status_im/ui/components/react.cljs b/src/status_im/ui/components/react.cljs index 75fcbf580b..2414c65934 100644 --- a/src/status_im/ui/components/react.cljs +++ b/src/status_im/ui/components/react.cljs @@ -12,6 +12,7 @@ :refer (SafeAreaProvider SafeAreaInsetsContext)] ["@react-native-community/clipboard" :default Clipboard] ["react-native-linear-gradient" :default LinearGradient] + ["@react-native-community/masked-view" :default MaskedView] ["react-native-navigation" :refer (Navigation)] ["react-native-fast-image" :as FastImage] ["@react-native-community/blur" :as blur]) @@ -40,6 +41,8 @@ (def linear-gradient (reagent/adapt-react-class LinearGradient)) +(def masked-view (reagent/adapt-react-class MaskedView)) + (def blur-view (reagent/adapt-react-class (.-BlurView blur))) (defn valid-source? [source] diff --git a/src/status_im/ui/screens/activity_center/views.cljs b/src/status_im/ui/screens/activity_center/views.cljs index c286f62919..cf9d9657ca 100644 --- a/src/status_im/ui/screens/activity_center/views.cljs +++ b/src/status_im/ui/screens/activity_center/views.cljs @@ -2,17 +2,21 @@ (:require [quo.components.animated.pressable :as animation] [quo.react-native :as rn] [quo2.components.buttons.button :as button] + [quo2.components.markdown.text :as text] [quo2.components.notifications.activity-logs :as activity-logs] + [quo2.components.tabs.tabs :as tabs] [quo2.components.tags.context-tags :as context-tags] [quo2.foundations.colors :as colors] [reagent.core :as reagent] [status-im.constants :as constants] [status-im.i18n.i18n :as i18n] [status-im.multiaccounts.core :as multiaccounts] - [status-im.ui.components.topbar :as topbar] [status-im.utils.datetime :as datetime] [status-im.utils.handlers :refer [evt]])) +(defonce selected-activity-type + (reagent/atom :activity-type/all)) + (defn activity-title [{:keys [type]}] (case type @@ -107,24 +111,64 @@ (defn notifications-list [] (let [notifications (evt [:activity-center.notifications/fetch-next-page]) :render-fn render-notification}])) +(defn filter-selector-read [] + (let [unread-filter-enabled? (evt [:activity-center.notifications/fetch-first-page {:status-filter :read}]) + (>evt [:activity-center.notifications/fetch-first-page {:status-filter :unread}]))} + :unread])) + (defn activity-center [] (reagent/create-class {:component-did-mount #(>evt [:activity-center.notifications/fetch-first-page {:status-filter :unread}]) :reagent-render (fn [] - [:<> - [topbar/topbar {:navigation {:on-press #(>evt [:navigate-back])} - :title (i18n/label :t/activity)}] - ;; TODO(ilmotta): Temporary solution to switch between read/unread - ;; notifications while the Design team works on the mockups. - [button/button {:on-press #(>evt [:activity-center.notifications/fetch-first-page {:status-filter :unread}])} - "Unread"] - [button/button {:on-press #(>evt [:activity-center.notifications/fetch-first-page {:status-filter :read}])} - "Read"] - [notifications-list]])})) + (let [screen-padding 20] + [:<> + [button/button {:icon true + :type :grey + :size 32 + :style {:margin-vertical 12 + :margin-left screen-padding} + :on-press #(>evt [:navigate-back])} + :close] + [text/text {:size :heading-1 + :weight :semi-bold + :style {:padding-horizontal screen-padding + :padding-vertical 12}} + (i18n/label :t/notifications)] + [rn/view {:flex-direction :row + :padding-vertical 12} + [rn/view {:flex 1 + :align-self :stretch} + [tabs/scrollable-tabs {:size 32 + :style {:padding-left screen-padding} + :fade-end-percentage 0.79 + :scroll-on-press? true + :fade-end? true + :on-change (partial reset! selected-activity-type) + :default-active :activity-type/all + :data [{:id :activity-type/all :label (i18n/label :t/all)} + {:id :activity-type/admin :label (i18n/label :t/admin)} + {:id :activity-type/mention :label (i18n/label :t/mentions)} + {:id :activity-type/reply :label (i18n/label :t/replies)} + {:id :activity-type/contact-request :label (i18n/label :t/contact-requests)} + {:id :activity-type/identity-verification :label (i18n/label :t/identity-verification)} + {:id :activity-type/transaction :label (i18n/label :t/transactions)} + {:id :activity-type/membership :label (i18n/label :t/membership)} + {:id :activity-type/system :label (i18n/label :t/system)}]}]] + [rn/view {:flex-grow 0 + :margin-left 16 + :padding-right screen-padding} + [filter-selector-read]]] + [rn/view {:padding-horizontal screen-padding} + [notifications-list]]]))})) diff --git a/src/status_im/utils/number.cljs b/src/status_im/utils/number.cljs new file mode 100644 index 0000000000..4b268fca7e --- /dev/null +++ b/src/status_im/utils/number.cljs @@ -0,0 +1,15 @@ +(ns status-im.utils.number) + +(defn naive-round + "Quickly and naively round number `n` up to `decimal-places`. + + Example usage: use it to avoid re-renders caused by floating-point number + changes in Reagent atoms. Such numbers can be rounded up to a certain number + of `decimal-places` in order to avoid re-rendering due to tiny fractional + changes. + + Don't use this function for arbitrary-precision arithmetic." + [n decimal-places] + (let [scale (Math/pow 10 decimal-places)] + (/ (Math/round (* n scale)) + scale))) diff --git a/translations/en.json b/translations/en.json index fc8082c6ce..5fd23aea3e 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1762,6 +1762,7 @@ "new-ui": "New UI", "send-contact-request-message": "To start a chat you need to become contacts", "contact-request": "Contact request", + "contact-requests": "Contact Requests", "say-hi": "Say hi", "opened" : "Opened", "accepted": "Accepted", @@ -1798,5 +1799,10 @@ "edit-message": "Edit message", "save-image-library": "Save image to library", "share-image": "Share image", - "see-sticker-set": "See the full sticker set" + "see-sticker-set": "See the full sticker set", + "mentions": "Mentions", + "admin": "Admin", + "replies": "Replies", + "identity-verification": "Identity verification", + "membership": "Membership" }