parent
390ac858dc
commit
73983b2abd
Binary file not shown.
After Width: | Height: | Size: 454 B |
Binary file not shown.
After Width: | Height: | Size: 673 B |
Binary file not shown.
After Width: | Height: | Size: 249 B |
Binary file not shown.
After Width: | Height: | Size: 282 B |
|
@ -248,6 +248,8 @@
|
||||||
|
|
||||||
(def react-native-gradien #js {:default #js {}})
|
(def react-native-gradien #js {:default #js {}})
|
||||||
|
|
||||||
|
(def masked-view #js {:default #js {}})
|
||||||
|
|
||||||
(def react-native-permissions #js {:default #js {}})
|
(def react-native-permissions #js {:default #js {}})
|
||||||
|
|
||||||
(def push-notification-ios #js {:default #js {:abandonPermissions identity}})
|
(def push-notification-ios #js {:default #js {:abandonPermissions identity}})
|
||||||
|
@ -308,6 +310,7 @@
|
||||||
"react-native-device-info" react-native-device-info
|
"react-native-device-info" react-native-device-info
|
||||||
"react-native-push-notification" react-native-push-notification
|
"react-native-push-notification" react-native-push-notification
|
||||||
"react-native-linear-gradient" react-native-gradien
|
"react-native-linear-gradient" react-native-gradien
|
||||||
|
"@react-native-community/masked-view" masked-view
|
||||||
"react-native-blob-util" react-native-blob-util
|
"react-native-blob-util" react-native-blob-util
|
||||||
"react-native-navigation" react-native-navigation
|
"react-native-navigation" react-native-navigation
|
||||||
"@react-native-community/push-notification-ios" push-notification-ios
|
"@react-native-community/push-notification-ios" push-notification-ios
|
||||||
|
|
|
@ -1,21 +1,139 @@
|
||||||
(ns quo2.components.tabs.tabs
|
(ns quo2.components.tabs.tabs
|
||||||
(:require [reagent.core :as reagent]
|
(:require [oops.core :refer [oget]]
|
||||||
[quo.react-native :as rn]
|
[quo.react-native :as rn]
|
||||||
[quo2.components.tabs.tab :as tab]))
|
[quo2.components.tabs.tab :as tab]
|
||||||
|
[reagent.core :as reagent]
|
||||||
|
[status-im.ui.components.react :as react]
|
||||||
|
[status-im.utils.number :as number-utils]))
|
||||||
|
|
||||||
|
(def default-tab-size 32)
|
||||||
|
|
||||||
(defn tabs [{:keys [default-active on-change]}]
|
(defn tabs [{:keys [default-active on-change]}]
|
||||||
(let [active-tab-id (reagent/atom default-active)]
|
(let [active-tab-id (reagent/atom default-active)]
|
||||||
(fn [{:keys [data size] :or {size 32}}]
|
(fn [{:keys [data size] :or {size default-tab-size}}]
|
||||||
(let [active-id @active-tab-id]
|
(let [active-id @active-tab-id]
|
||||||
[rn/view {:flex-direction :row}
|
[rn/view {:flex-direction :row}
|
||||||
(for [{:keys [label id]} data]
|
(for [{:keys [label id]} data]
|
||||||
^{:key id}
|
^{:key id}
|
||||||
[rn/view {:margin-right (if (= size 32) 12 8)}
|
[rn/view {:style {:margin-right (if (= size default-tab-size) 12 8)}}
|
||||||
[tab/tab
|
[tab/tab
|
||||||
{:id id
|
{:id id
|
||||||
:size size
|
:size size
|
||||||
:active (= id active-id)
|
:active (= id active-id)
|
||||||
:on-press #(do (reset! active-tab-id %)
|
:on-press (fn [^js press-event id]
|
||||||
(when on-change
|
(reset! active-tab-id id)
|
||||||
(on-change %)))}
|
(when on-change
|
||||||
|
(on-change press-event id)))}
|
||||||
label]])]))))
|
label]])]))))
|
||||||
|
|
||||||
|
(defn- calculate-fade-end-percentage
|
||||||
|
[{:keys [offset-x content-width layout-width max-fade-percentage]}]
|
||||||
|
(let [fade-percentage (max max-fade-percentage
|
||||||
|
(/ (+ layout-width offset-x)
|
||||||
|
content-width))]
|
||||||
|
;; Truncate to avoid unnecessary rendering.
|
||||||
|
(if (> 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]])})])))))
|
||||||
|
|
|
@ -20,13 +20,19 @@
|
||||||
(fn [db]
|
(fn [db]
|
||||||
(get-in db [:activity-center :current-status-filter])))
|
(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
|
(re-frame/reg-sub
|
||||||
:activity-center/notifications-per-read-status
|
:activity-center/notifications-per-read-status
|
||||||
:<- [:activity-center/notifications-read]
|
:<- [:activity-center/notifications-read]
|
||||||
:<- [:activity-center/notifications-unread]
|
:<- [:activity-center/notifications-unread]
|
||||||
:<- [:activity-center/current-status-filter]
|
:<- [:activity-center/status-filter-unread-enabled?]
|
||||||
(fn [[notifications-read notifications-unread status-filter]]
|
(fn [[notifications-read notifications-unread unread-filter-enabled?]]
|
||||||
(if (= status-filter :unread)
|
(if unread-filter-enabled?
|
||||||
notifications-unread
|
notifications-unread
|
||||||
notifications-read)))
|
notifications-read)))
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
:refer (SafeAreaProvider SafeAreaInsetsContext)]
|
:refer (SafeAreaProvider SafeAreaInsetsContext)]
|
||||||
["@react-native-community/clipboard" :default Clipboard]
|
["@react-native-community/clipboard" :default Clipboard]
|
||||||
["react-native-linear-gradient" :default LinearGradient]
|
["react-native-linear-gradient" :default LinearGradient]
|
||||||
|
["@react-native-community/masked-view" :default MaskedView]
|
||||||
["react-native-navigation" :refer (Navigation)]
|
["react-native-navigation" :refer (Navigation)]
|
||||||
["react-native-fast-image" :as FastImage]
|
["react-native-fast-image" :as FastImage]
|
||||||
["@react-native-community/blur" :as blur])
|
["@react-native-community/blur" :as blur])
|
||||||
|
@ -40,6 +41,8 @@
|
||||||
|
|
||||||
(def linear-gradient (reagent/adapt-react-class LinearGradient))
|
(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)))
|
(def blur-view (reagent/adapt-react-class (.-BlurView blur)))
|
||||||
|
|
||||||
(defn valid-source? [source]
|
(defn valid-source? [source]
|
||||||
|
|
|
@ -2,17 +2,21 @@
|
||||||
(:require [quo.components.animated.pressable :as animation]
|
(:require [quo.components.animated.pressable :as animation]
|
||||||
[quo.react-native :as rn]
|
[quo.react-native :as rn]
|
||||||
[quo2.components.buttons.button :as button]
|
[quo2.components.buttons.button :as button]
|
||||||
|
[quo2.components.markdown.text :as text]
|
||||||
[quo2.components.notifications.activity-logs :as activity-logs]
|
[quo2.components.notifications.activity-logs :as activity-logs]
|
||||||
|
[quo2.components.tabs.tabs :as tabs]
|
||||||
[quo2.components.tags.context-tags :as context-tags]
|
[quo2.components.tags.context-tags :as context-tags]
|
||||||
[quo2.foundations.colors :as colors]
|
[quo2.foundations.colors :as colors]
|
||||||
[reagent.core :as reagent]
|
[reagent.core :as reagent]
|
||||||
[status-im.constants :as constants]
|
[status-im.constants :as constants]
|
||||||
[status-im.i18n.i18n :as i18n]
|
[status-im.i18n.i18n :as i18n]
|
||||||
[status-im.multiaccounts.core :as multiaccounts]
|
[status-im.multiaccounts.core :as multiaccounts]
|
||||||
[status-im.ui.components.topbar :as topbar]
|
|
||||||
[status-im.utils.datetime :as datetime]
|
[status-im.utils.datetime :as datetime]
|
||||||
[status-im.utils.handlers :refer [<sub >evt]]))
|
[status-im.utils.handlers :refer [<sub >evt]]))
|
||||||
|
|
||||||
|
(defonce selected-activity-type
|
||||||
|
(reagent/atom :activity-type/all))
|
||||||
|
|
||||||
(defn activity-title
|
(defn activity-title
|
||||||
[{:keys [type]}]
|
[{:keys [type]}]
|
||||||
(case type
|
(case type
|
||||||
|
@ -107,24 +111,64 @@
|
||||||
(defn notifications-list
|
(defn notifications-list
|
||||||
[]
|
[]
|
||||||
(let [notifications (<sub [:activity-center/notifications-per-read-status])]
|
(let [notifications (<sub [:activity-center/notifications-per-read-status])]
|
||||||
[rn/flat-list {:style {:padding-horizontal 8}
|
[rn/flat-list {:data notifications
|
||||||
:data notifications
|
|
||||||
:key-fn :id
|
:key-fn :id
|
||||||
:on-end-reached #(>evt [:activity-center.notifications/fetch-next-page])
|
:on-end-reached #(>evt [:activity-center.notifications/fetch-next-page])
|
||||||
:render-fn render-notification}]))
|
:render-fn render-notification}]))
|
||||||
|
|
||||||
|
(defn filter-selector-read []
|
||||||
|
(let [unread-filter-enabled? (<sub [:activity-center/status-filter-unread-enabled?])]
|
||||||
|
;; TODO: Replace the button by a Filter Selector component once available for use.
|
||||||
|
[button/button {:icon true
|
||||||
|
:type (if unread-filter-enabled? :primary :outline)
|
||||||
|
:size 32
|
||||||
|
:on-press #(if 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 []
|
(defn activity-center []
|
||||||
(reagent/create-class
|
(reagent/create-class
|
||||||
{:component-did-mount #(>evt [:activity-center.notifications/fetch-first-page {:status-filter :unread}])
|
{:component-did-mount #(>evt [:activity-center.notifications/fetch-first-page {:status-filter :unread}])
|
||||||
:reagent-render
|
:reagent-render
|
||||||
(fn []
|
(fn []
|
||||||
[:<>
|
(let [screen-padding 20]
|
||||||
[topbar/topbar {:navigation {:on-press #(>evt [:navigate-back])}
|
[:<>
|
||||||
:title (i18n/label :t/activity)}]
|
[button/button {:icon true
|
||||||
;; TODO(ilmotta): Temporary solution to switch between read/unread
|
:type :grey
|
||||||
;; notifications while the Design team works on the mockups.
|
:size 32
|
||||||
[button/button {:on-press #(>evt [:activity-center.notifications/fetch-first-page {:status-filter :unread}])}
|
:style {:margin-vertical 12
|
||||||
"Unread"]
|
:margin-left screen-padding}
|
||||||
[button/button {:on-press #(>evt [:activity-center.notifications/fetch-first-page {:status-filter :read}])}
|
:on-press #(>evt [:navigate-back])}
|
||||||
"Read"]
|
:close]
|
||||||
[notifications-list]])}))
|
[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]]]))}))
|
||||||
|
|
|
@ -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)))
|
|
@ -1762,6 +1762,7 @@
|
||||||
"new-ui": "New UI",
|
"new-ui": "New UI",
|
||||||
"send-contact-request-message": "To start a chat you need to become contacts",
|
"send-contact-request-message": "To start a chat you need to become contacts",
|
||||||
"contact-request": "Contact request",
|
"contact-request": "Contact request",
|
||||||
|
"contact-requests": "Contact Requests",
|
||||||
"say-hi": "Say hi",
|
"say-hi": "Say hi",
|
||||||
"opened" : "Opened",
|
"opened" : "Opened",
|
||||||
"accepted": "Accepted",
|
"accepted": "Accepted",
|
||||||
|
@ -1798,5 +1799,10 @@
|
||||||
"edit-message": "Edit message",
|
"edit-message": "Edit message",
|
||||||
"save-image-library": "Save image to library",
|
"save-image-library": "Save image to library",
|
||||||
"share-image": "Share image",
|
"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"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue