Add chat actions in home screen

This commit adds the following chat actions on the home screen:

- Mute chat
- Delete chat
- View profile (one to ones only)
- Clear history

It adds also integration tests for muting and deleting a chat.

To accommodate multiple dividers in the bottom sheet, the interface has
been changed to accept a sequence of sequences, instead of a map.
This commit is contained in:
Andrea Maria Piana 2022-10-26 17:08:40 +01:00
parent b492ed2969
commit b774ecbcb4
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
7 changed files with 279 additions and 79 deletions

View File

@ -5,64 +5,70 @@
[quo2.components.icon :as icon] [quo2.components.icon :as icon]
[quo2.foundations.colors :as colors])) [quo2.foundations.colors :as colors]))
(defn- get-icon-color [section] (defn- get-icon-color [danger?]
(if (= section :bottom) (if danger?
(colors/theme-colors colors/danger-50 colors/danger-60) (colors/theme-colors colors/danger-50 colors/danger-60)
(colors/theme-colors colors/neutral-50 colors/neutral-40))) (colors/theme-colors colors/neutral-50 colors/neutral-40)))
(defn action [section] (defn action [{:keys [icon
(fn [{:keys [icon label sub-label right-icon on-press]}] label
[rn/touchable-opacity {:on-press on-press} sub-label
[react/view {:style right-icon
{:flex 1 danger?
:height (if sub-label 56 47) on-press]}]
:margin-horizontal 20 [rn/touchable-opacity {:on-press on-press}
:flex-direction :row}} [react/view {:style
[react/view {:style {:height (if sub-label 56 47)
{:height 20 :margin-horizontal 20
:margin-top :auto :flex-direction :row}}
:margin-bottom :auto [react/view {:style
:margin-right 12 {:height 20
:width 20}} :margin-top :auto
[icon/icon icon :margin-bottom :auto
{:color (get-icon-color section) :margin-right 12
:size 20}]] :width 20}}
[react/view [icon/icon icon
{:style {:color (get-icon-color danger?)
{:flex 1 :size 20}]]
:justify-content :center}} [react/view
{:style
{:flex 1
:justify-content :center}}
[text/text
{:size :paragraph-1
:weight :medium
:style {:color
(when danger?
(colors/theme-colors colors/danger-50 colors/danger-60))}}
label]
(when sub-label
[text/text [text/text
{:size :paragraph-1 {:size :paragraph-2
:weight :medium :style {:color
:style (colors/theme-colors colors/neutral-50 colors/neutral-40)}}
{:color (when (= section :bottom) sub-label])]
(colors/theme-colors colors/danger-50 colors/danger-60))}} (when right-icon
label] [react/view {:style
(when sub-label [text/text {:height 20
{:size :paragraph-2 :margin-top :auto
:style :margin-bottom :auto
{:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}} :width 20}}
sub-label])] [icon/icon right-icon
(when right-icon {:color (get-icon-color danger?)
[react/view {:style :size 20}]])]])
{:height 20
:margin-top :auto
:margin-bottom :auto
:width 20}}
[icon/icon right-icon
{:color (get-icon-color section)
:size 20}]])]]))
(defn action-drawer [{:keys [actions actions-with-consequence]}] (defn divider []
[rn/view {:style {:border-top-width 1
:border-top-color (colors/theme-colors colors/neutral-10 colors/neutral-80)
:margin-top 8
:margin-bottom 7
:align-items :center
:flex-direction :row}}])
(defn action-drawer [sections]
[:<> {:style [:<> {:style
{:flex 1}} {:flex 1}}
(map (action :top) actions) (interpose
(when actions-with-consequence [divider]
[:<> (for [actions sections]
[rn/view {:style {:border-top-width 1 (map action actions)))])
:border-top-color (colors/theme-colors colors/neutral-10 colors/neutral-80)
:margin-top 8
:margin-bottom 7
:align-items :center
:flex-direction :row}}]
(map (action :bottom) actions-with-consequence)])])

View File

@ -14,24 +14,27 @@
:key :show-red-options? :key :show-red-options?
:type :boolean}]) :type :boolean}])
(def options-with-consequences [{:icon :main-icons2/share-context (def options-with-consequences [{:icon :main-icons2/delete
:label "Clear history"}]) :danger? true
:label "Clear history"}])
(defn render-action-sheet [state] (defn render-action-sheet [state]
[quo2/action-drawer {:actions-with-consequence (when (:show-red-options? @state) options-with-consequences) [quo2/action-drawer (cond-> [[{:icon :main-icons2/friend
:actions [{:icon :main-icons2/share-context :label "View channel members and details"}
:label "View channel members and details"} {:icon :main-icons2/communities
{:icon :main-icons2/communities :label "Mark as read"}
:label "Mark as read"} {:icon :main-icons2/muted
{:icon :main-icons2/muted :label (if (:muted? @state) "Unmute channel" "Mute channel")
:label (if (:muted? @state) "Unmute channel" "Mute channel") :right-icon :main-icons2/chevron-right
:right-icon :main-icons2/chevron-right :sub-label (when (:muted? @state) "Muted for 15 min")}
:sub-label (when (:muted? @state) "Muted for 15 min")} {:icon :main-icons2/scan
{:icon :main-icons2/scan :right-icon :main-icons2/chevron-right
:right-icon :main-icons2/chevron-right :label "Fetch messages"}
:label "Fetch messages"} {:icon :main-icons2/add-user
{:icon :main-icons2/add-user :label "Share link to the channel"}]]
:label "Share link to the channel"}]}])
(:show-red-options? @state)
(conj options-with-consequences))])
(defn cool-preview [] (defn cool-preview []
(let [state (reagent/atom {:muted? true (let [state (reagent/atom {:muted? true

View File

@ -176,6 +176,11 @@
:on-error #(log/error "failed to clear history " chat-id %)}]} :on-error #(log/error "failed to clear history " chat-id %)}]}
(clear-history chat-id remove-chat?))) (clear-history chat-id remove-chat?)))
(fx/defn chat-deactivated
{:events [::chat-deactivated]}
[_ chat-id]
(log/debug "chat deactivated" chat-id))
(fx/defn deactivate-chat (fx/defn deactivate-chat
"Deactivate chat in db, no side effects" "Deactivate chat in db, no side effects"
[{:keys [db now] :as cofx} chat-id] [{:keys [db now] :as cofx} chat-id]
@ -185,10 +190,10 @@
(assoc-in db [:chats chat-id :active] false) (assoc-in db [:chats chat-id :active] false)
(update db :chats dissoc chat-id)) (update db :chats dissoc chat-id))
(update :chats-home-list disj chat-id) (update :chats-home-list disj chat-id)
(assoc-in [:current-chat-id] nil)) (assoc :current-chat-id nil))
::json-rpc/call [{:method "wakuext_deactivateChat" ::json-rpc/call [{:method "wakuext_deactivateChat"
:params [{:id chat-id}] :params [{:id chat-id}]
:on-success #(log/debug "chat deactivated" chat-id) :on-success #(re-frame/dispatch [::chat-deactivated chat-id])
:on-error #(log/error "failed to create public chat" chat-id %)}]} :on-error #(log/error "failed to create public chat" chat-id %)}]}
(clear-history chat-id true))) (clear-history chat-id true)))
@ -227,9 +232,7 @@
{:clear-message-notifications {:clear-message-notifications
[[chat-id] (get-in db [:multiaccount :remote-push-notifications-enabled?])]} [[chat-id] (get-in db [:multiaccount :remote-push-notifications-enabled?])]}
(deactivate-chat chat-id) (deactivate-chat chat-id)
(offload-messages chat-id) (offload-messages chat-id)))
(when (not (= (:view-id db) :home))
(navigation/pop-to-root-tab :chat-stack))))
(fx/defn show-more-chats (fx/defn show-more-chats
{:events [:chat.ui/show-more-chats]} {:events [:chat.ui/show-more-chats]}
@ -385,6 +388,11 @@
(log/error "mute chat failed" chat-id error) (log/error "mute chat failed" chat-id error)
{:db (assoc-in db [:chats chat-id :muted] (not muted?))}) {:db (assoc-in db [:chats chat-id :muted] (not muted?))})
(fx/defn mute-chat-toggled-successfully
{:events [::mute-chat-toggled-successfully]}
[_ chat-id]
(log/debug "muted chat successfully" chat-id))
(fx/defn mute-chat (fx/defn mute-chat
{:events [::mute-chat-toggled]} {:events [::mute-chat-toggled]}
[{:keys [db] :as cofx} chat-id muted?] [{:keys [db] :as cofx} chat-id muted?]
@ -393,7 +401,7 @@
::json-rpc/call [{:method method ::json-rpc/call [{:method method
:params [chat-id] :params [chat-id]
:on-error #(re-frame/dispatch [::mute-chat-failed chat-id muted? %]) :on-error #(re-frame/dispatch [::mute-chat-failed chat-id muted? %])
:on-success #(log/debug "muted chat successfully")}]})) :on-success #(re-frame/dispatch [::mute-chat-toggled-successfully chat-id])}]}))
(fx/defn show-profile (fx/defn show-profile
{:events [:chat.ui/show-profile]} {:events [:chat.ui/show-profile]}

View File

@ -4,6 +4,7 @@
[clojure.string :as string] [clojure.string :as string]
[re-frame.core :as rf] [re-frame.core :as rf]
status-im.events status-im.events
[status-im.chat.models :as chat.models]
[status-im.utils.security :as security] [status-im.utils.security :as security]
[status-im.multiaccounts.logout.core :as logout] [status-im.multiaccounts.logout.core :as logout]
[status-im.transport.core :as transport] [status-im.transport.core :as transport]
@ -231,5 +232,65 @@
(logout!) (rf-test/wait-for [::logout/logout-method] ; we need to logout to make sure the node is not in an inconsistent state between tests (logout!) (rf-test/wait-for [::logout/logout-method] ; we need to logout to make sure the node is not in an inconsistent state between tests
(assert-logout)))))))) (assert-logout))))))))
(deftest delete-chat-test
(log/info "========= delete-chat-test ==================")
(rf-test/run-test-async
(initialize-app!)
(rf-test/wait-for
[:status-im.init.core/initialize-view]
(generate-and-derive-addresses!)
(rf-test/wait-for
[:multiaccount-generate-and-derive-addresses-success] ; wait for the keys
(assert-multiaccount-loaded)
(create-multiaccount!)
(rf-test/wait-for
[::transport/messenger-started]
(assert-messenger-started)
(rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat
(rf-test/wait-for
[:status-im.chat.models/one-to-one-chat-created]
(rf/dispatch-sync [:chat.ui/navigate-to-chat chat-id])
(is (= chat-id @(rf/subscribe [:chats/current-chat-id])))
(is @(rf/subscribe [:chats/chat chat-id]))
(rf/dispatch-sync [:chat.ui/remove-chat-pressed chat-id])
(rf/dispatch-sync [:chat.ui/remove-chat chat-id])
(rf-test/wait-for
[::chat.models/chat-deactivated]
(is (not @(rf/subscribe [:chats/chat chat-id])))
(logout!) (rf-test/wait-for [::logout/logout-method] ; we need to logout to make sure the node is not in an inconsistent state between tests
(assert-logout)))))))))
(deftest mute-chat-test
(log/info "========= mute-chat-test ==================")
(rf-test/run-test-async
(initialize-app!)
(rf-test/wait-for
[:status-im.init.core/initialize-view]
(generate-and-derive-addresses!)
(rf-test/wait-for
[:multiaccount-generate-and-derive-addresses-success] ; wait for the keys
(assert-multiaccount-loaded)
(create-multiaccount!)
(rf-test/wait-for
[::transport/messenger-started]
(assert-messenger-started)
(rf/dispatch-sync [:chat.ui/start-chat chat-id]) ;; start a new chat
(rf-test/wait-for
[:status-im.chat.models/one-to-one-chat-created]
(rf/dispatch-sync [:chat.ui/navigate-to-chat chat-id])
(is (= chat-id @(rf/subscribe [:chats/current-chat-id])))
(is @(rf/subscribe [:chats/chat chat-id]))
(rf/dispatch-sync [::chat.models/mute-chat-toggled chat-id true])
(rf-test/wait-for
[::chat.models/mute-chat-toggled-successfully]
(is @(rf/subscribe [:chats/muted chat-id]))
(rf/dispatch-sync [::chat.models/mute-chat-toggled chat-id false])
(rf-test/wait-for
[::chat.models/mute-chat-toggled-successfully]
(is (not @(rf/subscribe [:chats/muted chat-id])))
(logout!) (rf-test/wait-for [::logout/logout-method] ; we need to logout to make sure the node is not in an inconsistent state between tests
(assert-logout))))))))))
(comment (comment
(run-tests)) (run-tests))

View File

@ -0,0 +1,117 @@
(ns status-im.ui2.screens.chat.actions
(:require
[status-im.chat.models :as chat.models]
[status-im.chat.models.pin-message :as models.pin-message]
[status-im.i18n.i18n :as i18n]
[status-im.constants :as constants]
[status-im.utils.handlers :refer [<sub >evt]]
[quo2.components.drawers.action-drawers :as drawer]))
(defn- entry [icon label on-press danger?]
{:pre [(keyword? icon)
(string? label)
(fn? on-press)
(boolean? danger?)]}
{:icon icon
:label label
:on-press on-press
:danger? danger?})
(defn hide-sheet-and-dispatch [event]
(>evt [:bottom-sheet/hide])
(>evt event))
(defn show-profile-action [chat-id]
(hide-sheet-and-dispatch [:chat.ui/show-profile chat-id])
(>evt [::models.pin-message/load-pin-messages chat-id]))
(defn mark-all-read-action [chat-id]
(hide-sheet-and-dispatch [:chat/mark-all-as-read chat-id]))
(defn edit-nickname-action [chat-id]
(hide-sheet-and-dispatch [:chat.ui/edit-nickname chat-id]))
(defn mute-chat-action [chat-id]
(hide-sheet-and-dispatch [::chat.models/mute-chat-toggled chat-id true]))
(defn unmute-chat-action [chat-id]
(hide-sheet-and-dispatch [::chat.models/mute-chat-toggled chat-id false]))
(defn clear-history-action [chat-id]
(hide-sheet-and-dispatch [:chat.ui/clear-history-pressed chat-id]))
(defn delete-chat-action [chat-id]
(hide-sheet-and-dispatch [:chat.ui/remove-chat-pressed chat-id]))
(defn mute-chat-entry [muted? chat-id]
(entry :main-icons2/muted
(i18n/label
(if muted?
:unmute-chat
:mute-chat))
(if muted?
#(unmute-chat-action chat-id)
#(mute-chat-action chat-id))
false))
(defn mark-as-read-entry [chat-id]
(entry :main-icons2/check
(i18n/label :mark-as-read)
#(mark-all-read-action chat-id)
false))
(defn clear-history-entry [chat-id]
(entry :main-icons2/delete
(i18n/label :clear-history)
#(clear-history-action chat-id)
true))
(defn delete-chat-entry [chat-id]
(entry :main-icons2/delete
(i18n/label :delete-chat)
#(delete-chat-action chat-id)
true))
(defn view-profile-entry [chat-id]
(entry :main-icons2/friend
(i18n/label :view-profile)
#(show-profile-action chat-id)
false))
(defn edit-nickname-entry [chat-id]
(entry :main-icons2/edit
(i18n/label :edit-nickname)
#(edit-nickname-action chat-id)
false))
(defn destructive-actions [chat-id]
[(clear-history-entry chat-id)
(delete-chat-entry chat-id)])
(defn notification-actions [muted? chat-id]
[(mute-chat-entry muted? chat-id)
(mark-as-read-entry chat-id)])
(defn one-to-one-actions [muted? chat-id]
[drawer/action-drawer [[(view-profile-entry chat-id)
(edit-nickname-entry chat-id)]
(notification-actions muted? chat-id)
(destructive-actions chat-id)]])
(defn public-chat-actions [muted? chat-id]
[drawer/action-drawer [(notification-actions muted? chat-id)
(destructive-actions chat-id)]])
(defn private-group-chat-actions [muted? chat-id]
[drawer/action-drawer [(notification-actions muted? chat-id)
(destructive-actions chat-id)]])
(defn actions [chat-type chat-id]
(let [muted? (<sub [:chats/muted chat-id])]
(case chat-type
constants/one-to-one-chat-type
[one-to-one-actions muted? chat-id]
constants/public-chat-type
[public-chat-actions muted? chat-id]
constants/private-group-chat-type
[private-group-chat-actions muted? chat-id])))

View File

@ -8,6 +8,7 @@
[status-im.ui.components.list.views :as list] [status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.screens.home.styles :as styles] [status-im.ui.screens.home.styles :as styles]
[status-im.ui2.screens.chat.actions :as actions]
[status-im.ui.screens.home.views.inner-item :refer [home-list-item]] [status-im.ui.screens.home.views.inner-item :refer [home-list-item]]
[quo.design-system.colors :as quo.colors] [quo.design-system.colors :as quo.colors]
[quo.core :as quo] [quo.core :as quo]
@ -19,7 +20,6 @@
[status-im.utils.utils :as utils] [status-im.utils.utils :as utils]
[status-im.ui.components.topbar :as topbar] [status-im.ui.components.topbar :as topbar]
[status-im.ui.components.plus-button :as components.plus-button] [status-im.ui.components.plus-button :as components.plus-button]
[status-im.ui.screens.chat.sheets :as sheets]
[status-im.ui.components.tabbar.core :as tabbar] [status-im.ui.components.tabbar.core :as tabbar]
[status-im.ui.components.invite.views :as invite] [status-im.ui.components.invite.views :as invite]
[status-im.utils.handlers :refer [<sub >evt]] [status-im.utils.handlers :refer [<sub >evt]]
@ -117,7 +117,7 @@
(re-frame/dispatch [:set :public-group-topic nil]) (re-frame/dispatch [:set :public-group-topic nil])
(re-frame/dispatch [:search/home-filter-changed nil]))}])]))) (re-frame/dispatch [:search/home-filter-changed nil]))}])])))
(defn render-fn [{:keys [chat-id] :as home-item}] (defn render-fn [{:keys [chat-id chat-type] :as home-item}]
[home-list-item [home-list-item
home-item home-item
{:on-press (fn [] {:on-press (fn []
@ -129,7 +129,9 @@
(re-frame/dispatch [:accept-all-activity-center-notifications-from-chat chat-id])) (re-frame/dispatch [:accept-all-activity-center-notifications-from-chat chat-id]))
:on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet :on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (fn [] {:content (fn []
[sheets/actions home-item])}])}]) [actions/actions
chat-type
chat-id])}])}])
(defn- render-contact [{:keys [public-key] :as row}] (defn- render-contact [{:keys [public-key] :as row}]
(let [[first-name second-name] (multiaccounts/contact-two-names row true) (let [[first-name second-name] (multiaccounts/contact-two-names row true)

View File

@ -1480,6 +1480,7 @@
"page-camera-request-blocked": "camera requests blocked. To enable camera requests go to Settings", "page-camera-request-blocked": "camera requests blocked. To enable camera requests go to Settings",
"nickname": "Nickname", "nickname": "Nickname",
"add-nickname": "Add a nickname (optional)", "add-nickname": "Add a nickname (optional)",
"edit-nickname": "Edit nickname",
"nickname-description": "Nicknames help you identify others in Status.\nOnly you can see the nicknames youve added", "nickname-description": "Nicknames help you identify others in Status.\nOnly you can see the nicknames youve added",
"accept": "Accept", "accept": "Accept",
"group-invite": "Group invite", "group-invite": "Group invite",
@ -1510,6 +1511,8 @@
"name-optional": "Name (optional)", "name-optional": "Name (optional)",
"mute": "Mute", "mute": "Mute",
"unmute": "Unmute", "unmute": "Unmute",
"mute-chat": "Mute chat",
"unmute-chat": "Unmute chat",
"mute-community": "Mute community", "mute-community": "Mute community",
"unmute-community": "Unmute community", "unmute-community": "Unmute community",
"scan-tokens": "Scan tokens", "scan-tokens": "Scan tokens",