Mention activity center & empty state

Signed-off-by: Brian Sztamfater <brian@status.im>
This commit is contained in:
Brian Sztamfater 2021-05-27 16:12:43 -03:00
parent ec54de8f63
commit af9461da74
No known key found for this signature in database
GPG Key ID: 59EB921E0706B48F
14 changed files with 346 additions and 52 deletions

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

View File

@ -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)

View File

@ -6,16 +6,24 @@
(defn rpc->type [{:keys [type name] :as chat}]
(cond
(= 2 type) (assoc chat
:chat-type constants/private-group-chat-type
:chat-name name
:public? false
:group-chat true)
(= 1 type) (assoc chat
:chat-type constants/one-to-one-chat-type
:chat-name name
:public? false
:group-chat false)))
(= 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)
(= 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
:group-chat false)))
(defn <-rpc [item]
(-> item
@ -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)))

View File

@ -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)))}))
{:db (-> db
(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))

View File

@ -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

View File

@ -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])}])}])

View File

@ -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])}])}]

View File

@ -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})

View File

@ -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,30 +79,42 @@
{: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)}]
[filter-item]
[list/flat-list
{:key-fn #(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}]
(when (or @select-all (> (count @selected-items) 0))
[toolbar/toolbar
{:show-border? true
:left [quo/button {:type :secondary
:theme :negative
:accessibility-label :reject-and-delete-activity-center
:on-press #(toolbar-action false)}
(i18n/label :t/reject-and-delete)]
:right [quo/button {:type :secondary
:accessibility-label :accept-and-add-activity-center
:on-press #(toolbar-action true)}
(i18n/label :t/accept-and-add)]}])]))}))
(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/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
: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
:left [quo/button {:type :secondary
:theme :negative
:accessibility-label :reject-and-delete-activity-center
:on-press #(toolbar-action false)}
(i18n/label :t/reject-and-delete)]
:right [quo/button {:type :secondary
:accessibility-label :accept-and-add-activity-center
:on-press #(toolbar-action true)}
(i18n/label :t/accept-and-add)]}])])]))}))

View File

@ -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)]])]]]))

View File

@ -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"
}