[14556, 14259] Allow users to mute community channels and to mute chats for specific duration (#15128)

This commit is contained in:
Ibrahem Khalil 2023-06-22 08:25:17 +03:00 committed by GitHub
parent db44ee67e6
commit 7aa40b8adf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 498 additions and 142 deletions

View File

@ -38,13 +38,13 @@
uses `ring-background` to display the ring behind the initials when given. Otherwise, uses `ring-background` to display the ring behind the initials when given. Otherwise,
shows the `profile-picture` which already comes with the ring drawn." shows the `profile-picture` which already comes with the ring drawn."
[{:keys [full-name status-indicator? online? size profile-picture ring-background [{:keys [full-name status-indicator? online? size profile-picture ring-background
customization-color static?] customization-color static? muted?]
:or {status-indicator? true :or {status-indicator? true
online? true online? true
size :big size :big
customization-color :turquoise}}] customization-color :turquoise}}]
(let [full-name (or full-name "empty name") (let [full-name (or full-name "empty name")
draw-ring? (and ring-background (valid-ring-sizes size)) draw-ring? (when-not muted? (and ring-background (valid-ring-sizes size)))
outer-styles (style/outer size) outer-styles (style/outer size)
;; Once image is loaded, fast image rerenders view with the help of reagent atom, ;; Once image is loaded, fast image rerenders view with the help of reagent atom,
;; But dynamic updates don't work when user-avatar is used inside hole-view ;; But dynamic updates don't work when user-avatar is used inside hole-view

View File

@ -35,7 +35,8 @@
on-press on-press
add-divider? add-divider?
override-theme override-theme
accessibility-label] accessibility-label
icon-color]
:as action-props}] :as action-props}]
(when action-props (when action-props
[:<> {:key label} [:<> {:key label}
@ -53,7 +54,7 @@
:accessible true :accessible true
:style style/left-icon} :style style/left-icon}
[icon/icon icon [icon/icon icon
{:color (get-icon-color danger? override-theme) {:color (or icon-color (get-icon-color danger? override-theme))
:size 20}]] :size 20}]]
[rn/view [rn/view
{:style style/text-container} {:style style/text-container}

View File

@ -57,7 +57,7 @@
muted?) muted?)
[quo2.icons/icon :i/muted [quo2.icons/icon :i/muted
{:size 20 {:size 20
:no-color true}]) :color colors/neutral-40}])
(when (and (not locked?) (when (and (not locked?)
(not muted?) (not muted?)
(pos? (int mentions-count))) (pos? (int mentions-count)))

View File

@ -65,7 +65,8 @@
:lastMessage :last-message :lastMessage :last-message
:lastClockValue :last-clock-value :lastClockValue :last-clock-value
:invitationAdmin :invitation-admin :invitationAdmin :invitation-admin
:profile :profile-public-key}) :profile :profile-public-key
:muteTill :muted-till})
rpc->type rpc->type
unmarshal-members unmarshal-members
(update :last-message #(when % (messages/<-rpc %))) (update :last-message #(when % (messages/<-rpc %)))
@ -81,7 +82,7 @@
:alias (.-alias chat) :alias (.-alias chat)
:muted (.-muted chat) :muted (.-muted chat)
:joined (.-joined chat) :joined (.-joined chat)
:muted-till (.-mutetill chat) :muted-till (.-muteTill chat)
:chat-id (.-id chat) :chat-id (.-id chat)
:community-id (.-communityId chat) :community-id (.-communityId chat)
:synced-from (.-syncedFrom chat) :synced-from (.-syncedFrom chat)

View File

@ -7,7 +7,9 @@
[status-im2.constants :as constants] [status-im2.constants :as constants]
[status-im2.contexts.contacts.drawers.nickname-drawer.view :as nickname-drawer] [status-im2.contexts.contacts.drawers.nickname-drawer.view :as nickname-drawer]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]
[status-im2.common.mute-chat-drawer.view :as mute-chat-drawer]
[utils.datetime :as datetime]))
(defn- entry (defn- entry
[{:keys [icon label on-press danger? sub-label chevron? add-divider? accessibility-label]}] [{:keys [icon label on-press danger? sub-label chevron? add-divider? accessibility-label]}]
@ -51,8 +53,11 @@
:accessibility-label :edit-nickname}])}])) :accessibility-label :edit-nickname}])}]))
(defn mute-chat-action (defn mute-chat-action
[chat-id] [chat-id chat-type]
(hide-sheet-and-dispatch [:chat.ui/mute chat-id true constants/mute-till-unmuted])) (hide-sheet-and-dispatch [:show-bottom-sheet
{:content (fn []
[mute-chat-drawer/mute-chat-drawer chat-id
:mute-chat-for-duration chat-type])}]))
(defn unmute-chat-action (defn unmute-chat-action
[chat-id] [chat-id]
@ -118,19 +123,22 @@
public-key])}])}])) public-key])}])}]))
(defn mute-chat-entry (defn mute-chat-entry
[chat-id] [chat-id chat-type muted-till]
(let [muted? (rf/sub [:chats/muted chat-id])] (let [muted? (rf/sub [:chats/muted chat-id])]
(entry {:icon (if muted? :i/muted :i/activity-center) (entry {:icon (if muted? :i/muted :i/activity-center)
:label (i18n/label :label (i18n/label
(if muted? (if muted?
:unmute-chat :unmute-chat
:mute-chat)) :mute-chat))
:sub-label (when (and muted? (some? muted-till))
(str (i18n/label :t/muted-until)
" "
(datetime/format-mute-till muted-till)))
:on-press (if muted? :on-press (if muted?
#(unmute-chat-action chat-id) #(unmute-chat-action chat-id)
#(mute-chat-action chat-id)) #(mute-chat-action chat-id chat-type))
:danger? false :danger? false
:accessibility-label :mute-chat :accessibility-label :mute-chat
:sub-label nil
:chevron? true}))) :chevron? true})))
(defn mark-as-read-entry (defn mark-as-read-entry
@ -237,7 +245,7 @@
:label (i18n/label :t/notifications) :label (i18n/label :t/notifications)
:on-press #(js/alert "TODO: to be implemented, requires design input") :on-press #(js/alert "TODO: to be implemented, requires design input")
:danger? false :danger? false
:sub-label "All messages" ; TODO: placeholder :sub-label (i18n/label :t/all-messages)
:accessibility-label :manage-notifications :accessibility-label :manage-notifications
:chevron? true :chevron? true
:add-divider? add-divider?})) :add-divider? add-divider?}))
@ -296,7 +304,7 @@
:sub-label nil :sub-label nil
:chevron? false})) :chevron? false}))
;; TODO(OmarBasem): to be implemented. ;; TODO(OmarBasem): to be implemented.chat/check-channel-muted?
(defn share-group-entry (defn share-group-entry
[] []
(entry {:icon :i/share (entry {:icon :i/share
@ -404,9 +412,9 @@
(delete-chat-entry item inside-chat?))]) (delete-chat-entry item inside-chat?))])
(defn notification-actions (defn notification-actions
[{:keys [chat-id group-chat public?]} inside-chat? needs-divider?] [{:keys [chat-id group-chat public? chat-type muted-till]} inside-chat? needs-divider?]
[(mark-as-read-entry chat-id needs-divider?) [(mark-as-read-entry chat-id needs-divider?)
(mute-chat-entry chat-id) (mute-chat-entry chat-id chat-type muted-till)
(notifications-entry false) (notifications-entry false)
(when inside-chat? (when inside-chat?
(fetch-messages-entry)) (fetch-messages-entry))

View File

@ -4,20 +4,25 @@
[react-native.core :as rn] [react-native.core :as rn]
[status-im2.constants :as constants] [status-im2.constants :as constants]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[status-im2.common.mute-chat-drawer.style :as style])) [status-im2.common.mute-chat-drawer.style :as style]
[utils.chats :as chat-utils]))
(defn hide-sheet-and-dispatch (defn hide-sheet-and-dispatch
[event] [event]
(rf/dispatch [:bottom-sheet/hide]) (rf/dispatch [:hide-bottom-sheet])
(rf/dispatch event)) (rf/dispatch event))
(defn mute-chat-drawer (defn mute-chat-drawer
[chat-id accessibility-label] [chat-id accessibility-label chat-type]
[rn/view {:accessibility-label accessibility-label} [rn/view {:accessibility-label accessibility-label}
[quo/text [quo/text
{:weight :medium {:weight :medium
:size :paragraph-2 :size :paragraph-2
:style (style/header-text)} (i18n/label :t/mute-channel)] :style (style/header-text)}
(i18n/label
(if (chat-utils/not-community-chat? chat-type)
:t/mute-chat-capitialized
:t/mute-channel))]
[quo/menu-item [quo/menu-item
{:type :transparent {:type :transparent
:title (i18n/label :t/mute-for-15-mins) :title (i18n/label :t/mute-for-15-mins)

View File

@ -334,3 +334,29 @@
(def ^:const auth-method-none "none") (def ^:const auth-method-none "none")
(def ^:const image-description-in-lightbox? false) (def ^:const image-description-in-lightbox? false)
(def ^:const int->weekday
"Maps the corresponding string representation of a weekday
By it's numeric index as in cljs-time"
{1 "mon"
2 "tue"
3 "wed"
4 "thu"
5 "fri"
6 "sat"
7 "sun"})
(def ^:const months
"Maps the corresponding string representation of a weekday
By it's numeric index as in cljs-time"
{1 "jan"
2 "feb"
3 "mar"
4 "apr"
5 "may"
6 "jun"
7 "jul"
8 "aug"
9 "sep"
10 "oct"
11 "nov"
12 "dec"})

View File

@ -16,7 +16,10 @@
[status-im.multiaccounts.model :as multiaccounts.model] [status-im.multiaccounts.model :as multiaccounts.model]
[status-im.utils.clocks :as utils.clocks] [status-im.utils.clocks :as utils.clocks]
[status-im.utils.types :as types] [status-im.utils.types :as types]
[reagent.core :as reagent])) [reagent.core :as reagent]
[quo2.foundations.colors :as colors]
[utils.datetime :as datetime]
[utils.chats :as chat-utils]))
(defn- get-chat (defn- get-chat
[cofx chat-id] [cofx chat-id]
@ -253,8 +256,6 @@
(update-in [:chats chat-id :unviewed-mentions-count] (update-in [:chats chat-id :unviewed-mentions-count]
#(max (- % countWithMentions) 0)))}) #(max (- % countWithMentions) 0)))})
;;;; UI
(rf/defn start-chat (rf/defn start-chat
"Start a chat, making sure it exists" "Start a chat, making sure it exists"
{:events [:chat.ui/start-chat]} {:events [:chat.ui/start-chat]}
@ -297,20 +298,64 @@
(rf/defn mute-chat-toggled-successfully (rf/defn mute-chat-toggled-successfully
{:events [:chat/mute-successfully]} {:events [:chat/mute-successfully]}
[{:keys [db]} chat-id muted-till] [{:keys [db]} chat-id muted-till mute-type muted? chat-type]
(log/debug "muted chat successfully" chat-id " for" muted-till) (log/debug "muted chat successfully" chat-id " for" muted-till)
{:db (assoc-in db [:chats chat-id :muted-till] muted-till)}) (let [time-string (fn [duration-kw unmute-time]
(i18n/label duration-kw {:duration unmute-time}))
not-community-chat? (chat-utils/not-community-chat? chat-type)
mute-duration-text
(fn [unmute-time]
(if unmute-time
(str
(condp = mute-type
constants/mute-for-15-mins-type (time-string
(if (chat-utils/not-community-chat? chat-type)
:t/chat-muted-for-15-minutes
:t/channel-muted-for-15-minutes)
unmute-time)
constants/mute-for-1-hour-type (time-string
(if (chat-utils/not-community-chat? chat-type)
:t/chat-muted-for-1-hour
:t/channel-muted-for-1-hour)
unmute-time)
constants/mute-for-8-hours-type (time-string
(if (chat-utils/not-community-chat? chat-type)
:t/chat-muted-for-8-hours
:t/channel-muted-for-8-hours)
unmute-time)
constants/mute-for-1-week (time-string
(if (chat-utils/not-community-chat? chat-type)
:t/chat-muted-for-1-week
:t/channel-muted-for-1-week)
unmute-time)
constants/mute-till-unmuted (time-string
(if (chat-utils/not-community-chat? chat-type)
:t/chat-muted-till-unmuted
:t/channel-muted-till-unmuted)
unmute-time)))
(i18n/label (if (chat-utils/not-community-chat? chat-type)
:t/chat-unmuted-successfully
:t/channel-unmuted-successfully))))]
{:db (assoc-in db [:chats chat-id :muted-till] muted-till)
:dispatch [:toasts/upsert
{:icon :correct
:icon-color (colors/theme-colors colors/success-60
colors/success-50)
:text (mute-duration-text (when (some? muted-till)
(str (datetime/format-mute-till muted-till))))}]}))
(rf/defn mute-chat (rf/defn mute-chat
{:events [:chat.ui/mute]} {:events [:chat.ui/mute]}
[{:keys [db]} chat-id muted? mute-type] [{:keys [db]} chat-id muted? mute-type]
(let [method (if muted? "wakuext_muteChatV2" "wakuext_unmuteChat") (let [method (if muted? "wakuext_muteChatV2" "wakuext_unmuteChat")
params (if muted? [{:chatId chat-id :mutedType mute-type}] [chat-id])] params (if muted? [{:chatId chat-id :mutedType mute-type}] [chat-id])
chat-type (get-in db [:chats chat-id :chat-type])]
{:db (assoc-in db [:chats chat-id :muted] muted?) {:db (assoc-in db [:chats chat-id :muted] muted?)
:json-rpc/call [{:method method :json-rpc/call [{:method method
:params params :params params
:on-error #(rf/dispatch [:chat/mute-failed chat-id muted? %]) :on-error #(rf/dispatch [:chat/mute-failed chat-id muted? %])
:on-success #(rf/dispatch [:chat/mute-successfully chat-id %])}]})) :on-success #(rf/dispatch [:chat/mute-successfully chat-id % mute-type
(not muted?) chat-type])}]}))
(rf/defn show-clear-history-confirmation (rf/defn show-clear-history-confirmation
{:events [:chat.ui/show-clear-history-confirmation]} {:events [:chat.ui/show-clear-history-confirmation]}

View File

@ -21,8 +21,15 @@
:top 16 :top 16
:background-color (colors/theme-colors colors/neutral-40 colors/neutral-60)}) :background-color (colors/theme-colors colors/neutral-40 colors/neutral-60)})
(def muted-icon
{:position :absolute
:right 19
:top 16})
(defn timestamp (defn timestamp
[] [muted?]
{:color (colors/theme-colors colors/neutral-50 colors/neutral-40) {:color (or (when muted?
colors/neutral-50)
(colors/theme-colors colors/neutral-50 colors/neutral-40))
:margin-top 3 :margin-top 3
:margin-left 8}) :margin-left 8})

View File

@ -8,7 +8,8 @@
[utils.re-frame :as rf] [utils.re-frame :as rf]
[status-im2.constants :as constants] [status-im2.constants :as constants]
[clojure.string :as string] [clojure.string :as string]
[utils.i18n :as i18n])) [utils.i18n :as i18n]
[quo2.components.icon :as quo2.icons]))
(def max-subheader-length 50) (def max-subheader-length 50)
@ -177,25 +178,21 @@
:color (colors/theme-colors colors/primary-50 colors/primary-60)}]]))) :color (colors/theme-colors colors/primary-50 colors/primary-60)}]])))
(defn name-view (defn name-view
[display-name contact timestamp] [display-name contact timestamp muted?]
[rn/view [rn/view {:style {:flex-direction :row}}
{:style {:flex 1
:flex-direction :row}}
[quo/text [quo/text
{:weight :semi-bold {:weight :semi-bold
:accessibility-label :chat-name-text :accessibility-label :chat-name-text
:style {:flex-shrink 1} :style {:color (when muted? colors/neutral-50)}}
:number-of-lines 1
:ellipsize-mode :tail}
display-name] display-name]
[verified-or-contact-icon contact] [verified-or-contact-icon contact]
[quo/text [quo/text
{:size :label {:size :label
:style (style/timestamp)} :style (style/timestamp muted?)}
(datetime/to-short-str timestamp)]]) (datetime/to-short-str timestamp)]])
(defn avatar-view (defn avatar-view
[{:keys [contact chat-id full-name color]}] [{:keys [contact chat-id full-name color muted?]}]
(if contact ; `contact` is passed when it's not a group chat (if contact ; `contact` is passed when it's not a group chat
(let [online? (rf/sub [:visibility-status-updates/online? chat-id]) (let [online? (rf/sub [:visibility-status-updates/online? chat-id])
photo-path (rf/sub [:chats/photo-path chat-id]) photo-path (rf/sub [:chats/photo-path chat-id])
@ -204,7 +201,8 @@
{:full-name full-name {:full-name full-name
:size :small :size :small
:online? online? :online? online?
image-key photo-path}]) image-key photo-path
:muted? muted?}])
[quo/group-avatar [quo/group-avatar
{:color color {:color color
:size :medium}])) :size :medium}]))
@ -228,15 +226,18 @@
{:contact contact {:contact contact
:chat-id chat-id :chat-id chat-id
:full-name display-name :full-name display-name
:color color}] :color color
[rn/view :muted? muted}]
{:style {:flex 1 [rn/view {:style {:margin-left 8}}
:margin-left 8 [name-view display-name contact timestamp muted]
:margin-right (if show-unread-badge? 36 0)}} [last-message-preview group-chat last-message muted]]
[name-view display-name contact timestamp] (if-not muted
[last-message-preview group-chat last-message]]
(when show-unread-badge? (when show-unread-badge?
[quo/info-count [quo/info-count
{:style {:top 16 {:style {:top 16
:right 16}} :right 16}}
unviewed-messages-count])])) unviewed-messages-count])
[quo2.icons/icon :i/muted
{:size 20
:color colors/neutral-40
:container-style style/muted-icon}])]))

View File

@ -1,7 +1,26 @@
(ns status-im2.contexts.communities.actions.chat.view (ns status-im2.contexts.communities.actions.chat.view
(:require [quo2.core :as quo] (:require [quo2.core :as quo]
[status-im2.common.not-implemented :as not-implemented] [status-im2.common.not-implemented :as not-implemented]
[utils.i18n :as i18n])) [utils.datetime :as datetime]
[utils.i18n :as i18n]
[status-im2.common.mute-chat-drawer.view :as mute-chat-drawer]
[utils.re-frame :as rf]))
(defn hide-sheet-and-dispatch
[event]
(rf/dispatch [:hide-bottom-sheet])
(rf/dispatch event))
(defn- mute-channel-action
[chat-id chat-type]
(hide-sheet-and-dispatch [:show-bottom-sheet
{:content (fn []
[mute-chat-drawer/mute-chat-drawer chat-id
:mute-chat-for-duration chat-type])}]))
(defn- unmute-channel-action
[chat-id]
(hide-sheet-and-dispatch [:chat.ui/mute chat-id false 0]))
(defn- action-view-members-and-details (defn- action-view-members-and-details
[] []
@ -26,12 +45,19 @@
:label (i18n/label :t/mark-as-read)}) :label (i18n/label :t/mark-as-read)})
(defn- action-toggle-muted (defn- action-toggle-muted
[] [id muted? muted-till chat-type]
(let [muted (and muted? (some? muted-till))]
{:icon :i/muted {:icon :i/muted
:right-icon :i/chevron-right :right-icon :i/chevron-right
:accessibility-label :chat-toggle-muted :accessibility-label :chat-toggle-muted
:on-press not-implemented/alert :sub-label (when muted
:label (i18n/label :t/mute-channel)}) (str (i18n/label :t/muted-until) " " (datetime/format-mute-till muted-till)))
:on-press (if muted?
#(unmute-channel-action id)
#(mute-channel-action id chat-type))
:label (i18n/label (if muted
:t/unmute-channel
:t/mute-channel))}))
(defn- action-notification-settings (defn- action-notification-settings
[] []
@ -80,7 +106,8 @@
:label (i18n/label :t/share-channel)}) :label (i18n/label :t/share-channel)})
(defn actions (defn actions
[{:keys [locked?]} inside-chat?] [{:keys [locked? id community-id]} inside-chat?]
(let [{:keys [muted muted-till chat-type]} (rf/sub [:chat-by-id (str community-id id)])]
(cond (cond
locked? locked?
[quo/action-drawer [quo/action-drawer
@ -93,7 +120,7 @@
[quo/action-drawer [quo/action-drawer
[[(action-view-members-and-details) [[(action-view-members-and-details)
(action-mark-as-read) (action-mark-as-read)
(action-toggle-muted) (action-toggle-muted (str community-id id) muted muted-till chat-type)
(action-notification-settings) (action-notification-settings)
(action-pinned-messages) (action-pinned-messages)
(action-invite-people) (action-invite-people)
@ -105,11 +132,11 @@
[[(action-view-members-and-details) [[(action-view-members-and-details)
(action-token-requirements) (action-token-requirements)
(action-mark-as-read) (action-mark-as-read)
(action-toggle-muted) (action-toggle-muted (str community-id id) muted muted-till chat-type)
(action-notification-settings) (action-notification-settings)
(action-fetch-messages) (action-fetch-messages)
(action-invite-people) (action-invite-people)
(action-qr-code) (action-qr-code)
(action-share)]]] (action-share)]]]
:else nil)) :else nil)))

View File

@ -15,7 +15,8 @@
[status-im2.contexts.communities.overview.style :as style] [status-im2.contexts.communities.overview.style :as style]
[status-im2.contexts.communities.overview.utils :as utils] [status-im2.contexts.communities.overview.utils :as utils]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.re-frame :as rf])) [utils.re-frame :as rf]
[status-im2.contexts.communities.actions.chat.view :as chat-actions]))
(defn preview-user-list (defn preview-user-list
[user-list] [user-list]
@ -77,13 +78,20 @@
{:style {:margin-left 8 {:style {:margin-left 8
:margin-top 10 :margin-top 10
:margin-bottom 8}} :margin-bottom 8}}
(for [chat chats (for [{:keys [muted? id] :as chat} chats
:let [chat (assoc chat :chat-type constants/community-chat-type)]] :let [chat (assoc chat
:chat-type
constants/community-chat-type)
channel-muted? (or muted?
(rf/sub [:chat/check-channel-muted?
community-id id]))]]
[rn/view [rn/view
{:key (:id chat) {:key (:id chat)
:style {:margin-top 4}} :style {:margin-top 4}}
[quo/channel-list-item [quo/channel-list-item
(assoc chat (assoc chat
:muted?
channel-muted?
:on-long-press :on-long-press
(fn [] (fn []
(rf/dispatch [:show-bottom-sheet (rf/dispatch [:show-bottom-sheet
@ -206,22 +214,30 @@
(i18n/label :t/joined) (i18n/label :t/joined)
(i18n/label :t/pending))}]])) (i18n/label :t/pending))}]]))
(defn add-on-press-handler (defn add-handlers
[community-id {:keys [id locked?] :or {locked? false} :as chat}] [community-id
{:keys [id locked?]
:or {locked? false}
:as chat}]
(merge (merge
chat chat
(when (and (not locked?) id) (when (and (not locked?) id)
{:on-press (fn [] {:on-press (fn []
(rf/dispatch [:dismiss-keyboard]) (rf/dispatch [:dismiss-keyboard])
(rf/dispatch [:chat/navigate-to-chat (str community-id id)]))}))) (rf/dispatch [:chat/navigate-to-chat (str community-id id)]))
:on-long-press #(rf/dispatch
[:show-bottom-sheet
{:content (fn []
[chat-actions/actions community-id id])}])
:community-id community-id})))
(defn add-on-press-handler-to-chats (defn add-handlers-to-chats
[community-id chats] [community-id chats]
(mapv (partial add-on-press-handler community-id) chats)) (mapv (partial add-handlers community-id) chats))
(defn add-on-press-handler-to-categorized-chats (defn add-handlers-to-categorized-chats
[community-id categorized-chats] [community-id categorized-chats]
(let [add-on-press (partial add-on-press-handler-to-chats community-id)] (let [add-on-press (partial add-handlers-to-chats community-id)]
(map (fn [[category v]] (map (fn [[category v]]
[category (update v :chats add-on-press)]) [category (update v :chats add-on-press)])
categorized-chats))) categorized-chats)))
@ -268,7 +284,7 @@
{:on-category-layout on-category-layout {:on-category-layout on-category-layout
:community-id id :community-id id
:on-first-channel-height-changed on-first-channel-height-changed} :on-first-channel-height-changed on-first-channel-height-changed}
(add-on-press-handler-to-categorized-chats id chats-by-category)]])) (add-handlers-to-categorized-chats id chats-by-category)]]))
(defn sticky-category-header (defn sticky-category-header
[_] [_]

View File

@ -419,3 +419,10 @@
:<- [:chats/link-previews-unfurled] :<- [:chats/link-previews-unfurled]
(fn [previews] (fn [previews]
(boolean (seq previews)))) (boolean (seq previews))))
(re-frame/reg-sub
:chat/check-channel-muted?
(fn [[_ community-id channel-id]]
[(re-frame/subscribe [:chats/chat (str community-id channel-id)])])
(fn [[chat]]
(:muted? chat)))

View File

@ -165,9 +165,13 @@
(defn calculate-unviewed-counts (defn calculate-unviewed-counts
[chats] [chats]
(reduce (fn [acc {:keys [unviewed-mentions-count unviewed-messages-count]}] (reduce (fn [acc {:keys [unviewed-mentions-count unviewed-messages-count muted]}]
{:unviewed-messages-count (+ (:unviewed-messages-count acc) (or unviewed-messages-count 0)) {:unviewed-messages-count (if-not muted
:unviewed-mentions-count (+ (:unviewed-mentions-count acc) (or unviewed-mentions-count 0))}) (+ (:unviewed-messages-count acc) (or unviewed-messages-count 0))
0)
:unviewed-mentions-count (if-not muted
(+ (:unviewed-mentions-count acc) (or unviewed-mentions-count 0))
0)})
{:unviewed-messages-count 0 {:unviewed-messages-count 0
:unviewed-mentions-count 0} :unviewed-mentions-count 0}
chats)) chats))
@ -215,7 +219,8 @@
[_ {:keys [name categoryID position id emoji can-post?]}]] [_ {:keys [name categoryID position id emoji can-post?]}]]
(let [category-id (if (seq categoryID) categoryID constants/empty-category-id) (let [category-id (if (seq categoryID) categoryID constants/empty-category-id)
{:keys [unviewed-messages-count {:keys [unviewed-messages-count
unviewed-mentions-count]} (get full-chats-data unviewed-mentions-count
muted]} (get full-chats-data
(str community-id id)) (str community-id id))
acc-with-category (if (get acc category-id) acc-with-category (if (get acc category-id)
acc acc
@ -229,6 +234,7 @@
:chats []))) :chats [])))
chat {:name name chat {:name name
:emoji emoji :emoji emoji
:muted? muted
:unread-messages? (pos? unviewed-messages-count) :unread-messages? (pos? unviewed-messages-count)
:position position :position position
:mentions-count (or unviewed-mentions-count 0) :mentions-count (or unviewed-mentions-count 0)

View File

@ -84,9 +84,27 @@
(swap! rf-db/app-db assoc (swap! rf-db/app-db assoc
:communities :communities
{"0x1" {:id "0x1" {"0x1" {:id "0x1"
:chats {"0x1" {:id "0x1" :position 1 :name "chat1" :categoryID "1" :can-post? true} :chats {"0x1"
"0x2" {:id "0x2" :position 2 :name "chat2" :categoryID "1" :can-post? false} {:id "0x1"
"0x3" {:id "0x3" :position 3 :name "chat3" :categoryID "2" :can-post? true}} :position 1
:name "chat1"
:muted? nil
:categoryID "1"
:can-post? true}
"0x2"
{:id "0x2"
:position 2
:name "chat2"
:muted? nil
:categoryID "1"
:can-post? false}
"0x3"
{:id "0x3"
:position 3
:name "chat3"
:muted? nil
:categoryID "2"
:can-post? true}}
:categories {"1" {:id "1" :categories {"1" {:id "1"
:position 2 :position 2
:name "category1"} :name "category1"}
@ -103,6 +121,7 @@
:chats [{:name "chat3" :chats [{:name "chat3"
:position 3 :position 3
:emoji nil :emoji nil
:muted? nil
:locked? false :locked? false
:id "0x3" :id "0x3"
:unread-messages? false :unread-messages? false
@ -115,6 +134,7 @@
:chats [{:name "chat1" :chats [{:name "chat1"
:emoji nil :emoji nil
:position 1 :position 1
:muted? nil
:locked? false :locked? false
:id "0x1" :id "0x1"
:unread-messages? false :unread-messages? false
@ -122,6 +142,7 @@
{:name "chat2" {:name "chat2"
:emoji nil :emoji nil
:position 2 :position 2
:muted? nil
:locked? true :locked? true
:id "0x2" :id "0x2"
:unread-messages? false :unread-messages? false
@ -131,9 +152,21 @@
(swap! rf-db/app-db assoc (swap! rf-db/app-db assoc
:communities :communities
{"0x1" {:id "0x1" {"0x1" {:id "0x1"
:chats {"0x1" {:id "0x1" :position 1 :name "chat1" :categoryID "1" :can-post? true} :chats {"0x1"
"0x2" {:id "0x2" :position 2 :name "chat2" :categoryID "1" :can-post? false} {:id "0x1"
"0x3" {:id "0x3" :position 3 :name "chat3" :can-post? true}} :position 1
:name "chat1"
:categoryID "1"
:can-post? true
:muted? nil}
"0x2"
{:id "0x2"
:position 2
:name "chat2"
:categoryID "1"
:can-post? false
:muted? nil}
"0x3" {:id "0x3" :position 3 :name "chat3" :can-post? true :muted? nil}}
:categories {"1" {:id "1" :categories {"1" {:id "1"
:position 1 :position 1
:name "category1"} :name "category1"}
@ -150,6 +183,7 @@
:emoji nil :emoji nil
:position 3 :position 3
:locked? false :locked? false
:muted? nil
:id "0x3" :id "0x3"
:unread-messages? false :unread-messages? false
:mentions-count 0}]}] :mentions-count 0}]}]
@ -162,6 +196,7 @@
:emoji nil :emoji nil
:position 1 :position 1
:locked? false :locked? false
:muted? nil
:id "0x1" :id "0x1"
:unread-messages? false :unread-messages? false
:mentions-count 0} :mentions-count 0}
@ -170,6 +205,7 @@
:position 2 :position 2
:locked? true :locked? true
:id "0x2" :id "0x2"
:muted? nil
:unread-messages? false :unread-messages? false
:mentions-count 0}]}]] :mentions-count 0}]}]]
(rf/sub [sub-name "0x1"])))) (rf/sub [sub-name "0x1"]))))
@ -194,12 +230,14 @@
:position 1 :position 1
:locked? false :locked? false
:id "0x1" :id "0x1"
:muted? nil
:unread-messages? true :unread-messages? true
:mentions-count 2} :mentions-count 2}
{:name "chat2" {:name "chat2"
:emoji nil :emoji nil
:position 2 :position 2
:locked? true :locked? true
:muted? nil
:id "0x2" :id "0x2"
:unread-messages? false :unread-messages? false
:mentions-count 0}]}]] :mentions-count 0}]}]]

10
src/utils/chats.cljs Normal file
View File

@ -0,0 +1,10 @@
(ns ^{:doc "Utils needed for chat related operations"}
utils.chats
(:require [status-im2.constants :as constants]))
(defn not-community-chat?
[chat-type]
(contains? #{constants/public-chat-type
constants/private-group-chat-type
constants/one-to-one-chat-type}
chat-type))

View File

@ -5,7 +5,8 @@
[cljs-time.format :as t.format] [cljs-time.format :as t.format]
[clojure.string :as string] [clojure.string :as string]
[utils.i18n :as i18n] [utils.i18n :as i18n]
[utils.i18n-goog :as i18n-goog])) [utils.i18n-goog :as i18n-goog]
[status-im2.constants :as constants]))
(defn now [] (t/now)) (defn now [] (t/now))
@ -102,6 +103,13 @@
(= (t/month now) (t/month datetime)) (= (t/month now) (t/month datetime))
(= (t/day now) (t/day datetime))))) (= (t/day now) (t/day datetime)))))
(defn tomorrow?
[datetime]
(= (-> (t/now)
(t/plus (t/days 1))
t/day)
(t/day datetime)))
(defn within-last-n-days? (defn within-last-n-days?
"Returns true if `datetime` is within last `n` days (inclusive on both ends)." "Returns true if `datetime` is within last `n` days (inclusive on both ends)."
[datetime n] [datetime n]
@ -259,3 +267,39 @@
[ms] [ms]
(let [sec (quot ms 1000)] (let [sec (quot ms 1000)]
(gstring/format "%02d:%02d" (quot sec 60) (mod sec 60)))) (gstring/format "%02d:%02d" (quot sec 60) (mod sec 60))))
(def ^:const go-default-time
"Zero value for golang's time var"
"0001-01-01T00:00:00Z")
(defn- add-leading-zero
[input-string]
(if (> 10 input-string)
(str "0" input-string)
input-string))
(defn format-mute-till
[muted-till-string]
(let [parsed-time (t.format/parse (t.format/formatters :date-time-no-ms) muted-till-string)
hours-and-minutes (str (add-leading-zero (t/hour (t/plus parsed-time time-zone-offset)))
":"
(add-leading-zero (t/minute parsed-time)))
when-to-unmute (cond (= go-default-time
muted-till-string) (i18n/label :t/until-you-turn-it-back-on)
(today? parsed-time) (str hours-and-minutes " today")
(tomorrow? parsed-time) (str hours-and-minutes " tomorrow")
:else (str hours-and-minutes
" "
(i18n/label
(keyword "t"
(get constants/int->weekday
(t/day-of-week
parsed-time))))
" "
(t/day parsed-time)
" "
(i18n/label
(keyword "t"
(get constants/months
(t/month parsed-time))))))]
when-to-unmute))

View File

@ -1,9 +1,12 @@
(ns utils.datetime-test (ns utils.datetime-test
(:require [cljs-time.coerce :as time-coerce] (:require [cljs-time.coerce :as time-coerce]
[cljs-time.core :as t] [cljs-time.core :as t]
[cljs.test :refer-macros [deftest testing is]] [cljs-time.format :as t.format]
[cljs.test :refer-macros [deftest testing is are]]
[clojure.string :as string]
[utils.datetime :as datetime]
[utils.i18n-goog :as i18n-goog] [utils.i18n-goog :as i18n-goog]
[utils.datetime :as datetime])) [status-im2.constants :as constants]))
(defn match (defn match
[name symbols] [name symbols]
@ -189,3 +192,75 @@
"it" "it"
#'utils.datetime/medium-date-time-format)] #'utils.datetime/medium-date-time-format)]
(is (= (datetime/day-relative epoch) "01 gen 1970, 12:00:00 AM"))))) (is (= (datetime/day-relative epoch) "01 gen 1970, 12:00:00 AM")))))
(deftest format-mute-till-test
(let [remove-msecs #(string/replace % #"\.\w*Z" "Z")
time-str-to-obj #(t.format/parse (remove-msecs (time-coerce/to-string %)))
curr-time (t/now)
custom-HH-MM-formatter (t.format/formatter "HH:mm")
custom-DD-formatter (t.format/formatter "DD")
get-hh-mm #(t.format/unparse custom-HH-MM-formatter %)
get-day #(t.format/unparse custom-DD-formatter %)
get-week-day #(->> %
t/day-of-week
(get constants/int->weekday))
mock-today (t.format/unparse (t.format/formatters :date-time-no-ms) curr-time)
in-n-days #(-> (time-str-to-obj mock-today)
(t/plus (t/days %)))
in-n-minutes #(-> (time-str-to-obj mock-today)
(t/plus (t/minutes %)))
in-n-hours #(-> (time-str-to-obj mock-today)
(t/plus (t/hours %)))
mock-tomorrow (in-n-days 1)
mock-in-two-days (in-n-days 2)
mock-in-three-days (in-n-days 3)
mock-in-four-days (in-n-days 4)
mock-in-five-days (in-n-days 5)
mock-in-six-days (in-n-days 6)
mock-in-15-minutes (in-n-minutes 15)
mock-in-1-hour (in-n-hours 1)
mock-in-8-hour (in-n-hours 8)
get-month-day-int #(int (get-day %))
today? (fn [mocked curr-time]
(=
(t.format/unparse (t.format/formatter "MM:DD") mocked)
(t.format/unparse (t.format/formatter "MM:DD") curr-time)))
tomorrow? (fn [mocked curr-time]
(some #(= %
(-
(int (get-month-day-int mocked))
(int (get-month-day-int curr-time))))
[1 30 29 27]))
form-full-date #(str (get-hh-mm %)
" " (string/capitalize (get-week-day %))
" " (get-month-day-int %)
" " (string/capitalize (get constants/months (t/month %))))
today-date #(str (get-hh-mm %) " today")
tomorrow-date #(str (get-hh-mm %) " tomorrow")
write-date #(cond (today? % curr-time) (today-date %)
(tomorrow? % curr-time) (tomorrow-date %)
:else (form-full-date %))
will-unmute-in-1-hour (remove-msecs (time-coerce/to-string mock-in-1-hour))
will-unmute-in-8-hours (remove-msecs (time-coerce/to-string mock-in-8-hour))
will-unmute-in-15-mins (remove-msecs (time-coerce/to-string mock-in-15-minutes))
will-unmute-in-two-days (remove-msecs (time-coerce/to-string mock-in-two-days))
will-unmute-tomorrow (remove-msecs (time-coerce/to-string mock-tomorrow))
will-unmute-in-three-days (remove-msecs (time-coerce/to-string mock-in-three-days))
will-unmute-in-four-days (remove-msecs (time-coerce/to-string mock-in-four-days))
will-unmute-in-five-days (remove-msecs (time-coerce/to-string mock-in-five-days))
will-unmute-in-six-days (remove-msecs (time-coerce/to-string mock-in-six-days))]
(testing "Mute for minutes and hours"
(are [arg expected] (= (datetime/format-mute-till arg) expected)
will-unmute-in-15-mins (write-date mock-in-15-minutes)
will-unmute-in-1-hour (write-date mock-in-1-hour)
will-unmute-in-8-hours (write-date mock-in-8-hour)))
(testing "Weekdays"
(are [arg expected] (= (datetime/format-mute-till arg) expected)
will-unmute-tomorrow (write-date mock-tomorrow)
will-unmute-in-two-days (write-date mock-in-two-days)
will-unmute-in-three-days (write-date mock-in-three-days)
will-unmute-in-four-days (write-date mock-in-four-days)
will-unmute-in-five-days (write-date mock-in-five-days)
will-unmute-in-six-days (write-date mock-in-six-days)))
(testing "Until the user turns it back on"
(is (= "you turn it back on" (datetime/format-mute-till datetime/go-default-time))))))

View File

@ -3,7 +3,7 @@
"_comment": "Instead use: scripts/update-status-go.sh <rev>", "_comment": "Instead use: scripts/update-status-go.sh <rev>",
"owner": "status-im", "owner": "status-im",
"repo": "status-go", "repo": "status-go",
"version": "v0.159.4", "version": "v0.159.5",
"commit-sha1": "7da1ed38d4da3c6f9f5bd6fb32657b4183e8fa74", "commit-sha1": "d3e650d5e5d2fecb06a925eb7ccc3ba400d9633c",
"src-sha256": "10j8p8ng0i6jkv8bplfwwq847yfvyvnz1r2xf5csiy0b86gdwn8n" "src-sha256": "1g353jh27glhm106q65q6m8lamvg42yqsig59qzq38zamm957qaq"
} }

View File

@ -1650,6 +1650,7 @@
"mute": "Mute", "mute": "Mute",
"unmute": "Unmute", "unmute": "Unmute",
"mute-chat": "Mute chat", "mute-chat": "Mute chat",
"mute-chat-capitialized": "Mute Chat",
"unmute-chat": "Unmute chat", "unmute-chat": "Unmute chat",
"mute-community": "Mute community", "mute-community": "Mute community",
"unmute-community": "Unmute community", "unmute-community": "Unmute community",
@ -2200,5 +2201,43 @@
"token-gated-communities": "Token gated communities", "token-gated-communities": "Token gated communities",
"read-more": "Read more", "read-more": "Read more",
"token-gated-communities-info": "Here will be something relevant about this topic. This will help the user get more context and therefore have a better understanding of it.", "token-gated-communities-info": "Here will be something relevant about this topic. This will help the user get more context and therefore have a better understanding of it.",
"dont-yell-at-me": "Dont yell at me" "dont-yell-at-me": "Dont yell at me",
"view-channel-members-and-details": "View channel members and details",
"unmute-channel": "Unmute channel",
"share-channel": "Share channel",
"all-messages": "All messages",
"muted-until": "Muted until",
"until-you-turn-it-back-on": "you turn it back on",
"mon": "Mon",
"tue": "Tue",
"wed": "Wed",
"thu": "Thu",
"fri": "Fri",
"sat": "Sat",
"sun": "Sun",
"jan": "Jan",
"feb": "Feb",
"mar": "Mar",
"apr": "Apr",
"may": "May",
"jun": "Jun",
"jul": "Jul",
"aug": "Aug",
"sep": "Sep",
"oct": "Oct",
"nov": "Nov",
"dec": "Dec",
"channel-muted-for-15-minutes": "Channel muted for 15 minutes\n (until {{duration}}) ",
"channel-muted-for-1-hour": "Channel muted for 1 hour\n (until {{duration}}) ",
"channel-muted-for-8-hours": "Channel muted for 8 hours\n (until {{duration}}) ",
"channel-muted-for-1-week": "Channel muted for 1 week \n (until {{duration}}) ",
"channel-muted-till-unmuted": "Channel muted till unmuted\n (until {{duration}}) ",
"chat-muted-for-15-minutes": "Chat muted for 15 minutes\n (until {{duration}}) ",
"chat-muted-for-1-hour": "Chat muted for 1 hour\n (until {{duration}}) ",
"chat-muted-for-8-hours": "Chat muted for 8 hours\n (until {{duration}}) ",
"chat-muted-for-1-week": "Chat muted for 1 week\n (until {{duration}}) ",
"chat-muted-till-unmuted": "Chat muted till unmuted\n (until {{duration}}) ",
"until": "until",
"chat-unmuted-successfully": "Chat unmuted successfully! ",
"channel-unmuted-successfully": "Channel unmuted successfully! "
} }