Mention activity center & empty state
Signed-off-by: Brian Sztamfater <brian@status.im>
This commit is contained in:
parent
ec54de8f63
commit
af9461da74
Binary file not shown.
After Width: | Height: | Size: 338 B |
Binary file not shown.
After Width: | Height: | Size: 467 B |
Binary file not shown.
After Width: | Height: | Size: 729 B |
Binary file not shown.
After Width: | Height: | Size: 996 B |
|
@ -151,3 +151,7 @@
|
|||
(def ^:const privacy-policy-link "https://status.im/privacy-policy/")
|
||||
(def ^:const terms-of-service-link "https://status.im/terms-of-use")
|
||||
(def ^:const docs-link "https://status.im/docs/")
|
||||
|
||||
(def ^:const activity-center-notification-type-one-to-one-chat 1)
|
||||
(def ^:const activity-center-notification-type-private-group-chat 2)
|
||||
(def ^:const activity-center-notification-type-mention 3)
|
||||
|
|
|
@ -6,12 +6,20 @@
|
|||
|
||||
(defn rpc->type [{:keys [type name] :as chat}]
|
||||
(cond
|
||||
(= 2 type) (assoc chat
|
||||
(= constants/activity-center-notification-type-mention type)
|
||||
(assoc chat
|
||||
:chat-type constants/private-group-chat-type
|
||||
:chat-name name)
|
||||
|
||||
(= constants/activity-center-notification-type-private-group-chat type)
|
||||
(assoc chat
|
||||
:chat-type constants/private-group-chat-type
|
||||
:chat-name name
|
||||
:public? false
|
||||
:group-chat true)
|
||||
(= 1 type) (assoc chat
|
||||
|
||||
(= constants/activity-center-notification-type-one-to-one-chat type)
|
||||
(assoc chat
|
||||
:chat-type constants/one-to-one-chat-type
|
||||
:chat-name name
|
||||
:public? false
|
||||
|
@ -24,4 +32,5 @@
|
|||
:chatId :chat-id})
|
||||
(assoc :color (rand-nth colors/chat-colors))
|
||||
(update :last-message #(when % (messages/<-rpc %)))
|
||||
(update :message #(when % (messages/<-rpc %)))
|
||||
(dissoc :chatId)))
|
||||
|
|
|
@ -6,12 +6,15 @@
|
|||
[status-im.data-store.activities :as data-store.activities]))
|
||||
|
||||
(fx/defn handle-activities [{:keys [db]} activities]
|
||||
(if (= (:view-id db) :notifications-center)
|
||||
{:db (-> db
|
||||
(update-in [:activity.center/notifications :notifications] #(concat activities %)))
|
||||
:dispatch [:mark-all-activity-center-notifications-as-read]}
|
||||
{:db (-> db
|
||||
(update :activity.center/notifications-count + (count activities)))}))
|
||||
(update-in [:activity.center/notifications :notifications] #(concat activities %))
|
||||
(update :activity.center/notifications-count + (count activities)))
|
||||
:dispatch (cond
|
||||
(= (:view-id db) :notifications-center)
|
||||
[:mark-all-activity-center-notifications-as-read]
|
||||
|
||||
(= (:view-id db) :chat)
|
||||
[:accept-all-activity-center-notifications-from-chat (:current-chat-id db)])})
|
||||
|
||||
(fx/defn get-activity-center-notifications-count
|
||||
{:events [:get-activity-center-notifications-count]}
|
||||
|
@ -47,6 +50,22 @@
|
|||
:on-success #(re-frame/dispatch [:sanitize-messages-and-process-response %])
|
||||
:on-error #()}]})
|
||||
|
||||
(fx/defn accept-all-activity-center-notifications-from-chat
|
||||
{:events [:accept-all-activity-center-notifications-from-chat]}
|
||||
[{:keys [db]} chat-id]
|
||||
(let [notifications (get-in db [:activity.center/notifications :notifications])
|
||||
notifications-from-chat (filter #(= chat-id (:chat-id %)) notifications)
|
||||
ids (map :id notifications-from-chat)]
|
||||
{:db (-> db
|
||||
(update-in [:activity.center/notifications :notifications]
|
||||
(fn [items] (remove #(get ids (:id %)) items)))
|
||||
(update :activity.center/notifications-count - (count ids)))
|
||||
::json-rpc/call [{:method (json-rpc/call-ext-method "acceptActivityCenterNotifications")
|
||||
:params [ids]
|
||||
:js-response true
|
||||
:on-success #(re-frame/dispatch [:sanitize-messages-and-process-response %])
|
||||
:on-error #()}]}))
|
||||
|
||||
(fx/defn accept-activity-center-notification-and-open-chat
|
||||
{:events [:accept-activity-center-notification-and-open-chat]}
|
||||
[{:keys [db]} id]
|
||||
|
@ -134,7 +153,3 @@
|
|||
concat
|
||||
(map data-store.activities/<-rpc notifications)))})
|
||||
|
||||
(fx/defn close-center
|
||||
{:events [:close-notifications-center]}
|
||||
[cofx]
|
||||
(clean-notifications cofx))
|
||||
|
|
|
@ -1681,6 +1681,26 @@
|
|||
(filter (partial filter-recipient-favs
|
||||
(string/lower-case search-filter))
|
||||
favs)))))
|
||||
|
||||
;;ACTIVITY CENTER NOTIFICATIONS ========================================================================================
|
||||
|
||||
(defn- group-notifications-by-date
|
||||
[notifications]
|
||||
(->> notifications
|
||||
(group-by #(datetime/timestamp->date-key (:timestamp %)))
|
||||
(sort-by key >)
|
||||
(map (fn [[date-key notifications]]
|
||||
(let [first-notification (first notifications)]
|
||||
{:title (clojure.string/capitalize (datetime/day-relative (:timestamp first-notification)))
|
||||
:key date-key
|
||||
:data (sort-by :timestamp > notifications)})))))
|
||||
|
||||
(re-frame/reg-sub
|
||||
:activity.center/notifications-grouped-by-date
|
||||
:<- [:activity.center/notifications]
|
||||
(fn [{:keys [notifications]}]
|
||||
(group-notifications-by-date (map #(assoc % :timestamp (or (:timestamp %) (:timestamp (or (:message %) (:last-message %))))) notifications))))
|
||||
|
||||
;;WALLET TRANSACTIONS ==================================================================================================
|
||||
|
||||
(re-frame/reg-sub
|
||||
|
|
|
@ -131,7 +131,8 @@
|
|||
(re-frame/dispatch [:pop-to-root-tab :chat-stack])
|
||||
(re-frame/dispatch [:dismiss-keyboard])
|
||||
(re-frame/dispatch [:chat.ui/navigate-to-chat chat-id])
|
||||
(re-frame/dispatch [:search/home-filter-changed nil]))
|
||||
(re-frame/dispatch [:search/home-filter-changed nil])
|
||||
(re-frame/dispatch [:accept-all-activity-center-notifications-from-chat chat-id]))
|
||||
:on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet
|
||||
{:content (fn []
|
||||
[sheets/actions home-item])}])}])
|
||||
|
|
|
@ -126,7 +126,8 @@
|
|||
{:on-press (fn []
|
||||
(re-frame/dispatch [:dismiss-keyboard])
|
||||
(re-frame/dispatch [:chat.ui/navigate-to-chat chat-id])
|
||||
(re-frame/dispatch [:search/home-filter-changed nil]))
|
||||
(re-frame/dispatch [:search/home-filter-changed nil])
|
||||
(re-frame/dispatch [:accept-all-activity-center-notifications-from-chat chat-id]))
|
||||
:on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet
|
||||
{:content (fn []
|
||||
[sheets/actions home-item])}])}]
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
(ns status-im.ui.screens.notifications-center.styles
|
||||
(:require [status-im.ui.components.colors :as colors]
|
||||
[quo.design-system.colors :as quo-colors]))
|
||||
|
||||
(def notification-message-text
|
||||
{:flex 1
|
||||
:align-self :stretch
|
||||
:line-height 22
|
||||
:font-size 15
|
||||
:color (:text-01 @quo-colors/theme)})
|
||||
|
||||
(def mention-text
|
||||
{:color colors/blue})
|
||||
|
||||
(def datetime-text
|
||||
{:color colors/text-gray
|
||||
:font-size 10
|
||||
:text-align :right
|
||||
:letter-spacing 0.4
|
||||
:align-items :center
|
||||
:line-height 12
|
||||
:position :absolute
|
||||
:top 17
|
||||
:right 16})
|
||||
|
||||
(def group-info-container
|
||||
{:height 22
|
||||
:align-self :baseline
|
||||
:align-items :center
|
||||
:justify-content :center
|
||||
:border-radius 11
|
||||
:border-color colors/gray-transparent-40
|
||||
:border-width 1
|
||||
:margin-top 6
|
||||
:margin-bottom 10
|
||||
:padding-left 7
|
||||
:padding-right 5
|
||||
:flex-direction :row})
|
||||
|
||||
(defn notification-container [read]
|
||||
{:min-height 64
|
||||
:background-color (when-not read colors/blue-light)})
|
||||
|
||||
(def notification-content-container
|
||||
{:flex 1})
|
||||
|
||||
(def photo-container
|
||||
{:position :absolute
|
||||
:top 12
|
||||
:left 16})
|
||||
|
||||
(def title-text
|
||||
{:margin-left 72
|
||||
:margin-top 12
|
||||
:margin-right 50})
|
||||
|
||||
(def notification-message-container
|
||||
{:margin-left 72
|
||||
:margin-right 16})
|
||||
|
||||
(def group-icon
|
||||
{:margin-right 4})
|
||||
|
||||
(def community-info-container
|
||||
{:flex-direction :row
|
||||
:align-items :center})
|
|
@ -3,39 +3,39 @@
|
|||
[status-im.ui.components.topbar :as topbar]
|
||||
[status-im.i18n.i18n :as i18n]
|
||||
[status-im.ui.components.list.views :as list]
|
||||
[status-im.ui.screens.home.views.inner-item :as inner-item]
|
||||
[re-frame.core :as re-frame]
|
||||
[quo.core :as quo]
|
||||
[status-im.ui.components.colors :as colors]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.ui.components.toolbar :as toolbar]
|
||||
[clojure.string :as string]))
|
||||
[clojure.string :as string]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.ui.screens.notifications-center.views.notification :as notification]))
|
||||
|
||||
(def selecting (reagent/atom nil))
|
||||
(def select-all (reagent/atom nil))
|
||||
(def selected-items (reagent/atom #{}))
|
||||
|
||||
(defn render-fn [{:keys [id] :as home-item}]
|
||||
(defn render-fn [{:keys [id type] :as home-item}]
|
||||
(when id
|
||||
(let [selected (get @selected-items id)
|
||||
on-change (fn []
|
||||
(swap! selected-items #(if selected (disj % id) (conj % id))))]
|
||||
(when-not (= type constants/activity-center-notification-type-mention) (swap! selected-items #(if selected (disj % id) (conj % id)))))]
|
||||
[react/view {:flex-direction :row :flex 1 :align-items :center}
|
||||
(when @selecting
|
||||
(when (and @selecting (not (= type constants/activity-center-notification-type-mention)))
|
||||
[react/view {:padding-left 16}
|
||||
[quo/checkbox {:value (or @select-all selected)
|
||||
:disabled @select-all
|
||||
:on-change on-change}]])
|
||||
[react/view {:flex 1}
|
||||
[inner-item/home-list-item
|
||||
[notification/activity-text-item
|
||||
home-item
|
||||
{:on-press (fn []
|
||||
(if @selecting
|
||||
(on-change)
|
||||
(re-frame/dispatch [:accept-activity-center-notification-and-open-chat id])))
|
||||
:on-long-press #(do (reset! selecting true)
|
||||
(swap! selected-items conj id))}]]])))
|
||||
|
||||
(when-not (= type constants/activity-center-notification-type-mention) (swap! selected-items conj id)))}]]])))
|
||||
(defn filter-item []
|
||||
[react/view {:padding-vertical 8 :border-bottom-width 1 :border-bottom-color colors/gray-lighter}
|
||||
[react/view {:align-items :center :justify-content :space-between :padding-horizontal 16 :flex-direction :row}
|
||||
|
@ -79,21 +79,33 @@
|
|||
{:display-name "activity-center"
|
||||
:component-did-mount #(re-frame/dispatch [:get-activity-center-notifications])
|
||||
:reagent-render (fn []
|
||||
(let [{:keys [notifications]} @(re-frame/subscribe [:activity.center/notifications])]
|
||||
(let [notifications @(re-frame/subscribe [:activity.center/notifications-grouped-by-date])]
|
||||
[react/keyboard-avoiding-view {:style {:flex 1}
|
||||
:ignore-offset true}
|
||||
[topbar/topbar {:navigation {:on-press #(do
|
||||
(reset-state)
|
||||
(re-frame/dispatch [:close-notifications-center])
|
||||
(re-frame/dispatch [:navigate-back]))}
|
||||
:title (i18n/label :t/activity)}]
|
||||
(if (= (count notifications) 0)
|
||||
[react/view {:style {:flex 1
|
||||
:justify-content :center
|
||||
:align-items :center}}
|
||||
[quo/text {:color :secondary
|
||||
:size :large
|
||||
:align :center}
|
||||
(i18n/label :t/empty-activity-center)]]
|
||||
[:<>
|
||||
[filter-item]
|
||||
[list/flat-list
|
||||
{:key-fn #(or (:chat-id %) (:id %))
|
||||
[list/section-list
|
||||
{:key-fn #(str (:timestamp %) (or (:chat-id %) (:id %)))
|
||||
:on-end-reached #(re-frame/dispatch [:load-more-activity-center-notifications])
|
||||
:keyboard-should-persist-taps :always
|
||||
:data notifications
|
||||
:render-fn render-fn}]
|
||||
:sections notifications
|
||||
:render-fn render-fn
|
||||
:stickySectionHeadersEnabled false
|
||||
:render-section-header-fn
|
||||
(fn [{:keys [title]}]
|
||||
[quo/list-header title])}]
|
||||
(when (or @select-all (> (count @selected-items) 0))
|
||||
[toolbar/toolbar
|
||||
{:show-border? true
|
||||
|
@ -105,4 +117,4 @@
|
|||
:right [quo/button {:type :secondary
|
||||
:accessibility-label :accept-and-add-activity-center
|
||||
:on-press #(toolbar-action true)}
|
||||
(i18n/label :t/accept-and-add)]}])]))}))
|
||||
(i18n/label :t/accept-and-add)]}])])]))}))
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
(ns status-im.ui.screens.notifications-center.views.notification
|
||||
(:require [status-im.ui.components.react :as react]
|
||||
[re-frame.core :as re-frame]
|
||||
[quo.core :as quo]
|
||||
[clojure.string :as string]
|
||||
[status-im.i18n.i18n :as i18n]
|
||||
[status-im.ui.screens.notifications-center.styles :as styles]
|
||||
[status-im.utils.handlers :refer [<sub]]
|
||||
[status-im.ui.screens.chat.photos :as photos]
|
||||
[status-im.multiaccounts.core :as multiaccounts]
|
||||
[status-im.ui.components.icons.icons :as icons]
|
||||
[status-im.utils.contenthash :as contenthash]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.ui.components.colors :as colors]
|
||||
[status-im.ui.screens.home.views.inner-item :as home-item]))
|
||||
|
||||
(defn mention-element [from]
|
||||
(str "@" @(re-frame/subscribe [:contacts/contact-name-by-identity from])))
|
||||
|
||||
(def max-notification-length 160)
|
||||
(def max-notification-lines 2)
|
||||
|
||||
(defn add-parsed-to-message [acc {:keys [type destination literal children]}]
|
||||
(let [result (case type
|
||||
"paragraph"
|
||||
(reduce
|
||||
(fn [{:keys [_ length] :as acc-paragraph} parsed-child]
|
||||
(if (>= length max-notification-length)
|
||||
(reduced acc-paragraph)
|
||||
(add-parsed-to-message acc-paragraph parsed-child)))
|
||||
{:components [quo/text]
|
||||
:length 0}
|
||||
children)
|
||||
|
||||
"mention"
|
||||
{:components [quo/text {:style styles/mention-text} [mention-element literal]]
|
||||
:length 4} ;; we can't predict name length so take the smallest possible
|
||||
|
||||
"status-tag"
|
||||
(home-item/truncate-literal (str "#" literal))
|
||||
|
||||
"link"
|
||||
(home-item/truncate-literal destination)
|
||||
|
||||
(home-item/truncate-literal literal))]
|
||||
{:components (conj (:components acc) (:components result))
|
||||
:length (+ (:length acc) (:length result))}))
|
||||
|
||||
(defn message-wrapper
|
||||
([] (message-wrapper 1))
|
||||
([number-of-lines]
|
||||
[react/text-class {:style styles/notification-message-text
|
||||
:number-of-lines number-of-lines
|
||||
:ellipsize-mode :tail
|
||||
:accessibility-label :chat-message-text}]))
|
||||
|
||||
(defn render-notification-message
|
||||
"Render the preview of a notification message to a maximum of max-length characters"
|
||||
([parsed-text] (render-notification-message parsed-text max-notification-length 1))
|
||||
([parsed-text max-length number-of-lines]
|
||||
(let [result
|
||||
(reduce
|
||||
(fn [{:keys [_ length] :as acc-text} new-text-chunk]
|
||||
(if (>= length max-length)
|
||||
(reduced acc-text)
|
||||
(add-parsed-to-message acc-text new-text-chunk)))
|
||||
{:components (message-wrapper number-of-lines)
|
||||
:length 0}
|
||||
parsed-text)]
|
||||
(:components result))))
|
||||
|
||||
(defn message-content-text [{:keys [content content-type community-id]}]
|
||||
[react/view
|
||||
(cond
|
||||
|
||||
(not (and content content-type))
|
||||
[react/text {:style (merge
|
||||
styles/notification-message-text
|
||||
{:color colors/gray})
|
||||
:accessibility-label :no-messages-text}
|
||||
(i18n/label :t/no-messages)]
|
||||
|
||||
(= constants/content-type-sticker content-type)
|
||||
[react/image {:style {:margin 1 :width 20 :height 20}
|
||||
;;TODO (perf) move to event
|
||||
:source {:uri (contenthash/url (-> content :sticker :hash))}}]
|
||||
|
||||
(= constants/content-type-image content-type)
|
||||
[react/text {:style styles/notification-message-text
|
||||
:accessibility-label :no-messages-text}
|
||||
(i18n/label :t/image)]
|
||||
|
||||
(= constants/content-type-audio content-type)
|
||||
[react/text {:style styles/notification-message-text
|
||||
:accessibility-label :no-messages-text}
|
||||
(i18n/label :t/audio)]
|
||||
|
||||
(= constants/content-type-community content-type)
|
||||
(let [{:keys [name]}
|
||||
@(re-frame/subscribe [:communities/community community-id])]
|
||||
[react/text {:style styles/notification-message-text
|
||||
:accessibility-label :no-messages-text}
|
||||
(i18n/label :t/community-message-preview {:community-name name})])
|
||||
|
||||
(string/blank? (:text content))
|
||||
[react/text {:style styles/notification-message-text}
|
||||
""]
|
||||
|
||||
(:text content)
|
||||
(render-notification-message (:parsed-text content) max-notification-length max-notification-lines))])
|
||||
|
||||
(defn activity-text-item [home-item opts]
|
||||
(let [{:keys [chat-id chat-name message last-message muted read group-chat timestamp type]} home-item
|
||||
message (or message last-message)
|
||||
{:keys [community-id]} (<sub [:chat-by-id chat-id])
|
||||
{:keys [name]} @(re-frame/subscribe [:communities/community community-id])
|
||||
contact (when message @(re-frame/subscribe [:contacts/contact-by-identity (message :from)]))
|
||||
sender (when message (first @(re-frame/subscribe [:contacts/contact-two-names-by-identity (message :from)])))]
|
||||
[react/touchable-opacity (merge {:style (styles/notification-container read)} opts)
|
||||
[react/view {:style styles/notification-content-container}
|
||||
[react/view {:style styles/photo-container}
|
||||
[photos/photo
|
||||
(multiaccounts/displayed-photo contact)
|
||||
{:size 40
|
||||
:accessibility-label :current-account-photo}]]
|
||||
[quo/text {:weight :medium
|
||||
:color (when muted :secondary)
|
||||
:accessibility-label :chat-name-or-sender-text
|
||||
:ellipsize-mode :tail
|
||||
:number-of-lines 1
|
||||
:style styles/title-text}
|
||||
(if (= type constants/activity-center-notification-type-mention)
|
||||
sender
|
||||
[home-item/chat-item-title chat-id muted group-chat chat-name])]
|
||||
[react/text {:style styles/datetime-text
|
||||
:number-of-lines 1
|
||||
:accessibility-label :notification-time-text}
|
||||
;;TODO (perf) move to event
|
||||
(home-item/memo-timestamp timestamp)]
|
||||
[react/view {:style styles/notification-message-container}
|
||||
[message-content-text (select-keys message [:content :content-type :community-id])]
|
||||
(when (= type constants/activity-center-notification-type-mention)
|
||||
[react/view {:style styles/group-info-container
|
||||
:accessibility-label :chat-name-container}
|
||||
[icons/icon
|
||||
(if community-id :main-icons/tiny-community :main-icons/tiny-group)
|
||||
{:color colors/gray
|
||||
:width 16
|
||||
:height 16
|
||||
:container-style styles/group-icon}]
|
||||
(when community-id
|
||||
[react/view {:style styles/community-info-container}
|
||||
[quo/text {:color :secondary
|
||||
:weight :medium
|
||||
:size :small}
|
||||
name]
|
||||
[icons/icon
|
||||
:main-icons/chevron-down
|
||||
{:color colors/gray
|
||||
:width 16
|
||||
:height 22}]])
|
||||
[quo/text {:color :secondary
|
||||
:weight :medium
|
||||
:size :small}
|
||||
(str (when community-id "#") chat-name)]])]]]))
|
|
@ -1594,5 +1594,6 @@
|
|||
"wc-dispute": "Dispute resolution provisions",
|
||||
"status-is-open-source": "Status is open-source",
|
||||
"build-yourself": "To use the app without these Terms of Service, you can build your own version",
|
||||
"accept-and-continue": "Accept and continue"
|
||||
"accept-and-continue": "Accept and continue",
|
||||
"empty-activity-center": "Your chat notifications\nwill appear here"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue