fix improper use of current-chat-id and rename subs (#18389)

This commit is contained in:
Parvesh Monu 2024-01-11 17:20:37 +05:30 committed by GitHub
parent 09408e7811
commit 623d4a1476
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 128 additions and 150 deletions

View File

@ -51,9 +51,9 @@
(rf/defn remove-members (rf/defn remove-members
{:events [:group-chats.ui/remove-members-pressed]} {:events [:group-chats.ui/remove-members-pressed]}
[{{:keys [current-chat-id] :group-chat/keys [deselected-members]} :db :as cofx}] [{{:group-chat/keys [deselected-members]} :db :as cofx} chat-id]
{:json-rpc/call [{:method "wakuext_removeMembersFromGroupChat" {:json-rpc/call [{:method "wakuext_removeMembersFromGroupChat"
:params [nil current-chat-id deselected-members] :params [nil chat-id deselected-members]
:js-response true :js-response true
:on-success #(re-frame/dispatch [:chat-updated % true]) :on-success #(re-frame/dispatch [:chat-updated % true])
:on-error #()}]}) :on-error #()}]})
@ -97,19 +97,19 @@
(rf/defn add-members (rf/defn add-members
"Add members to a group chat" "Add members to a group chat"
{:events [:group-chats.ui/add-members-pressed]} {:events [:group-chats.ui/add-members-pressed]}
[{{:keys [current-chat-id] :group-chat/keys [selected-participants]} :db :as cofx}] [{{:group-chat/keys [selected-participants]} :db :as cofx} chat-id]
{:json-rpc/call [{:method "wakuext_addMembersToGroupChat" {:json-rpc/call [{:method "wakuext_addMembersToGroupChat"
:params [nil current-chat-id selected-participants] :params [nil chat-id selected-participants]
:js-response true :js-response true
:on-success #(re-frame/dispatch [:chat-updated % true])}]}) :on-success #(re-frame/dispatch [:chat-updated % true])}]})
(rf/defn add-members-from-invitation (rf/defn add-members-from-invitation
"Add members to a group chat" "Add members to a group chat"
{:events [:group-chats.ui/add-members-from-invitation]} {:events [:group-chats.ui/add-members-from-invitation]}
[{{:keys [current-chat-id] :as db} :db :as cofx} id participant] [{:keys [db]} id participant chat-id]
{:db (assoc-in db [:group-chat/invitations id :state] constants/invitation-state-approved) {:db (assoc-in db [:group-chat/invitations id :state] constants/invitation-state-approved)
:json-rpc/call [{:method "wakuext_addMembersToGroupChat" :json-rpc/call [{:method "wakuext_addMembersToGroupChat"
:params [nil current-chat-id [participant]] :params [nil chat-id [participant]]
:js-response true :js-response true
:on-success #(re-frame/dispatch [:chat-updated %])}]}) :on-success #(re-frame/dispatch [:chat-updated %])}]})
@ -150,23 +150,23 @@
(rf/defn membership-retry (rf/defn membership-retry
{:events [:group-chats.ui/membership-retry]} {:events [:group-chats.ui/membership-retry]}
[{{:keys [current-chat-id] :as db} :db}] [{:keys [db]} chat-id]
{:db (assoc-in db [:chat/memberships current-chat-id :retry?] true)}) {:db (assoc-in db [:chat/memberships chat-id :retry?] true)})
(rf/defn membership-message (rf/defn membership-message
{:events [:group-chats.ui/update-membership-message]} {:events [:group-chats.ui/update-membership-message]}
[{{:keys [current-chat-id] :as db} :db} message] [{:keys [db]} message chat-id]
{:db (assoc-in db [:chat/memberships current-chat-id :message] message)}) {:db (assoc-in db [:chat/memberships chat-id :message] message)})
(rf/defn send-group-chat-membership-request (rf/defn send-group-chat-membership-request
"Send group chat membership request" "Send group chat membership request"
{:events [:send-group-chat-membership-request]} {:events [:send-group-chat-membership-request]}
[{{:keys [current-chat-id chats] :as db} :db :as cofx}] [{{:keys [chats] :as db} :db :as cofx} chat-id]
(let [{:keys [invitation-admin]} (get chats current-chat-id) (let [{:keys [invitation-admin]} (get chats chat-id)
message (get-in db [:chat/memberships current-chat-id :message])] message (get-in db [:chat/memberships chat-id :message])]
{:db (assoc-in db [:chat/memberships current-chat-id] nil) {:db (assoc-in db [:chat/memberships chat-id] nil)
:json-rpc/call [{:method "wakuext_sendGroupChatInvitationRequest" :json-rpc/call [{:method "wakuext_sendGroupChatInvitationRequest"
:params [nil current-chat-id invitation-admin message] :params [nil chat-id invitation-admin message]
:js-response true :js-response true
:on-success #(re-frame/dispatch [:sanitize-messages-and-process-response %])}]})) :on-success #(re-frame/dispatch [:sanitize-messages-and-process-response %])}]}))
@ -242,15 +242,6 @@
[{db :db}] [{db :db}]
{:db (assoc db :group-chat/deselected-members #{})}) {:db (assoc db :group-chat/deselected-members #{})})
(rf/defn show-group-chat-profile
{:events [:show-group-chat-profile]}
[{:keys [db] :as cofx} chat-id]
(rf/merge cofx
{:db (-> db
(assoc :new-chat-name (get-in db [:chats chat-id :name]))
(assoc :current-chat-id chat-id))}
(navigation/navigate-to :group-chat-profile nil)))
(rf/defn ui-leave-chat-pressed (rf/defn ui-leave-chat-pressed
{:events [:group-chats.ui/leave-chat-pressed]} {:events [:group-chats.ui/leave-chat-pressed]}
[{:keys [db]} chat-id] [{:keys [db]} chat-id]

View File

@ -12,7 +12,7 @@
(def message-max-length 100) (def message-max-length 100)
(defn request-membership (defn request-membership
[{:keys [state introduction-message] :as invitation}] [{:keys [state introduction-message] :as invitation} chat-id]
(let [{:keys [message retry?]} @(re-frame/subscribe [:chats/current-chat-membership]) (let [{:keys [message retry?]} @(re-frame/subscribe [:chats/current-chat-membership])
message-length (count message)] message-length (count message)]
[react/view {:margin-horizontal 16 :margin-top 10} [react/view {:margin-horizontal 16 :margin-top 10}
@ -47,7 +47,7 @@
[react/text (i18n/label :t/introduce-yourself)] [react/text (i18n/label :t/introduce-yourself)]
[quo/text-input [quo/text-input
{:placeholder (i18n/label :t/message) {:placeholder (i18n/label :t/message)
:on-change-text #(re-frame/dispatch [:group-chats.ui/update-membership-message %]) :on-change-text #(re-frame/dispatch [:group-chats.ui/update-membership-message % chat-id])
:max-length (if platform/android? :max-length (if platform/android?
message-max-length message-max-length
(when (>= message-length message-max-length) (when (>= message-length message-max-length)
@ -63,4 +63,4 @@
[chat-id invitation-admin] [chat-id invitation-admin]
(letsubs [invitations [:group-chat/invitations-by-chat-id chat-id]] (letsubs [invitations [:group-chat/invitations-by-chat-id chat-id]]
(when invitation-admin (when invitation-admin
[request-membership (first invitations)]))) [request-membership (first invitations) chat-id])))

View File

@ -349,7 +349,7 @@
[chat-id] [chat-id]
(entry {:icon :i/members (entry {:icon :i/members
:label (i18n/label :t/group-details) :label (i18n/label :t/group-details)
:on-press #(hide-sheet-and-dispatch [:show-group-chat-profile chat-id]) :on-press #(hide-sheet-and-dispatch [:navigate-to :group-chat-profile chat-id])
:danger? false :danger? false
:accessibility-label :group-details :accessibility-label :group-details
:sub-label nil :sub-label nil

View File

@ -20,31 +20,20 @@
:icon-only? true :icon-only? true
:container-style {:margin-left 20} :container-style {:margin-left 20}
:accessibility-label :back-button :accessibility-label :back-button
:on-press (fn [] :on-press #(rf/dispatch [:navigate-back])}
(rf/dispatch [:navigate-back])
(rf/dispatch [:chat/close]))}
:i/arrow-left]) :i/arrow-left])
(defn options-button (defn options-button
[] [group]
(let [group (rf/sub [:chats/current-chat])] [quo/button
[quo/button {:type :grey
{:type :grey :size 32
:size 32 :icon-only? true
:icon-only? true :container-style {:margin-right 20}
:container-style {:margin-right 20} :accessibility-label :options-button
:accessibility-label :options-button :on-press #(rf/dispatch [:show-bottom-sheet
:on-press #(rf/dispatch [:show-bottom-sheet {:content (fn [] [actions/group-details-actions group])}])}
{:content (fn [] [actions/group-details-actions group])}])} :i/options])
:i/options]))
(defn top-buttons
[]
[rn/view
{:style {:flex-direction :row
:padding-horizontal 20
:justify-content :space-between}}
[back-button] [options-button]])
(defn count-container (defn count-container
[amount accessibility-label] [amount accessibility-label]
@ -100,7 +89,8 @@
[] []
(let [selected-participants (rf/sub [:group-chat/selected-participants]) (let [selected-participants (rf/sub [:group-chat/selected-participants])
deselected-members (rf/sub [:group-chat/deselected-members]) deselected-members (rf/sub [:group-chat/deselected-members])
{:keys [admins] :as group} (rf/sub [:chats/current-chat]) chat-id (rf/sub [:get-screen-params :group-chat-profile])
{:keys [admins] :as group} (rf/sub [:chats/chat-by-id chat-id])
admin? (get admins (rf/sub [:multiaccount/public-key]))] admin? (get admins (rf/sub [:multiaccount/public-key]))]
[rn/view {:flex 1 :margin-top 20} [rn/view {:flex 1 :margin-top 20}
[rn/touchable-opacity [rn/touchable-opacity
@ -130,9 +120,9 @@
(rf/dispatch [:navigate-back]) (rf/dispatch [:navigate-back])
(js/setTimeout (fn [] (js/setTimeout (fn []
(rf/dispatch (rf/dispatch
[:group-chats.ui/remove-members-pressed])) [:group-chats.ui/remove-members-pressed chat-id]))
500) 500)
(rf/dispatch [:group-chats.ui/add-members-pressed])) (rf/dispatch [:group-chats.ui/add-members-pressed chat-id]))
:disabled? (and (zero? (count selected-participants)) :disabled? (and (zero? (count selected-participants))
(zero? (count deselected-members)))} (zero? (count deselected-members)))}
(i18n/label :t/save)]]])) (i18n/label :t/save)]]]))
@ -151,84 +141,79 @@
:on-press show-profile-actions}}) :on-press show-profile-actions}})
item])) item]))
(defn f-group-details
[]
(fn []
(rn/use-effect (fn []
#(rf/dispatch [:chat/close])))
(let [{:keys [admins chat-id chat-name color public?
muted contacts]} (rf/sub [:chats/current-chat])
members (rf/sub [:contacts/group-members-sections])
pinned-messages (rf/sub [:chats/pinned chat-id])
current-pk (rf/sub [:multiaccount/public-key])
admin? (get admins current-pk)]
[rn/view
{:style {:flex 1
:background-color (colors/theme-colors colors/white colors/neutral-95)}}
[quo/header
{:left-component [back-button]
:right-component [options-button]
:background (colors/theme-colors colors/white colors/neutral-95)}]
[rn/view
{:style {:flex-direction :row
:margin-top 24
:padding-horizontal 20}}
[quo/group-avatar
{:customization-color color
:size :size-32}]
[quo/text
{:weight :semi-bold
:size :heading-1
:style {:margin-horizontal 8}} chat-name]
[rn/view {:style {:margin-top 8}}
[quo/icon (if public? :i/world :i/privacy)
{:size 20 :color (colors/theme-colors colors/neutral-50 colors/neutral-40)}]]]
[rn/view {:style (style/actions-view)}
[rn/touchable-opacity
{:style (style/action-container color)
:accessibility-label :pinned-messages
:on-press (fn []
(rf/dispatch [:dismiss-keyboard])
(rf/dispatch [:pin-message/show-pins-bottom-sheet chat-id]))}
[rn/view
{:style {:flex-direction :row
:justify-content :space-between}}
[quo/icon :i/pin {:size 20 :color (colors/theme-colors colors/neutral-100 colors/white)}]
[count-container (count pinned-messages) :pinned-count]]
[quo/text {:style {:margin-top 16} :size :paragraph-1 :weight :medium}
(i18n/label :t/pinned-messages)]]
[rn/touchable-opacity
{:style (style/action-container color)
:accessibility-label :toggle-mute
:on-press #(rf/dispatch [:chat.ui/mute chat-id (not muted)
(when-not muted constants/mute-till-unmuted)])}
[quo/icon (if muted :i/muted :i/activity-center)
{:size 20 :color (colors/theme-colors colors/neutral-100 colors/white)}]
[quo/text {:style {:margin-top 16} :size :paragraph-1 :weight :medium}
(i18n/label (if muted :unmute-group :mute-group))]]
[rn/touchable-opacity
{:style (style/action-container color)
:accessibility-label :manage-members
:on-press (fn []
(rf/dispatch [:group/clear-added-participants])
(rf/dispatch [:group/clear-removed-members])
(rf/dispatch [:open-modal :group-add-manage-members]))}
[rn/view
{:style {:flex-direction :row
:justify-content :space-between}}
[quo/icon :i/add-user {:size 20 :color (colors/theme-colors colors/neutral-100 colors/white)}]
[count-container (count contacts) :members-count]]
[quo/text {:style {:margin-top 16} :size :paragraph-1 :weight :medium}
(i18n/label (if admin? :t/manage-members :t/add-members))]]]
[rn/section-list
{:key-fn :title
:sticky-section-headers-enabled false
:sections members
:render-section-header-fn contacts-section-header
:render-data {:chat-id chat-id
:admin? admin?}
:render-fn contact-item-render}]])))
(defn group-details (defn group-details
[] []
[:f> f-group-details]) (let [chat-id (rf/sub [:get-screen-params :group-chat-profile])
{:keys [admins chat-id chat-name color public?
muted contacts]
:as group} (rf/sub [:chats/chat-by-id chat-id])
members (rf/sub [:contacts/group-members-sections chat-id])
pinned-messages (rf/sub [:chats/pinned chat-id])
current-pk (rf/sub [:multiaccount/public-key])
admin? (get admins current-pk)]
[rn/view
{:style {:flex 1
:background-color (colors/theme-colors colors/white colors/neutral-95)}}
[quo/header
{:left-component [back-button]
:right-component [options-button group]
:background (colors/theme-colors colors/white colors/neutral-95)}]
[rn/view
{:style {:flex-direction :row
:margin-top 24
:padding-horizontal 20}}
[quo/group-avatar
{:customization-color color
:size :size-32}]
[quo/text
{:weight :semi-bold
:size :heading-1
:style {:margin-horizontal 8}} chat-name]
[rn/view {:style {:margin-top 8}}
[quo/icon (if public? :i/world :i/privacy)
{:size 20 :color (colors/theme-colors colors/neutral-50 colors/neutral-40)}]]]
[rn/view {:style (style/actions-view)}
[rn/touchable-opacity
{:style (style/action-container color)
:accessibility-label :pinned-messages
:on-press (fn []
(rf/dispatch [:dismiss-keyboard])
(rf/dispatch [:pin-message/show-pins-bottom-sheet chat-id]))}
[rn/view
{:style {:flex-direction :row
:justify-content :space-between}}
[quo/icon :i/pin {:size 20 :color (colors/theme-colors colors/neutral-100 colors/white)}]
[count-container (count pinned-messages) :pinned-count]]
[quo/text {:style {:margin-top 16} :size :paragraph-1 :weight :medium}
(i18n/label :t/pinned-messages)]]
[rn/touchable-opacity
{:style (style/action-container color)
:accessibility-label :toggle-mute
:on-press #(rf/dispatch [:chat.ui/mute chat-id (not muted)
(when-not muted constants/mute-till-unmuted)])}
[quo/icon (if muted :i/muted :i/activity-center)
{:size 20 :color (colors/theme-colors colors/neutral-100 colors/white)}]
[quo/text {:style {:margin-top 16} :size :paragraph-1 :weight :medium}
(i18n/label (if muted :unmute-group :mute-group))]]
[rn/touchable-opacity
{:style (style/action-container color)
:accessibility-label :manage-members
:on-press (fn []
(rf/dispatch [:group/clear-added-participants])
(rf/dispatch [:group/clear-removed-members])
(rf/dispatch [:open-modal :group-add-manage-members chat-id]))}
[rn/view
{:style {:flex-direction :row
:justify-content :space-between}}
[quo/icon :i/add-user {:size 20 :color (colors/theme-colors colors/neutral-100 colors/white)}]
[count-container (count contacts) :members-count]]
[quo/text {:style {:margin-top 16} :size :paragraph-1 :weight :medium}
(i18n/label (if admin? :t/manage-members :t/add-members))]]]
[rn/section-list
{:key-fn :title
:sticky-section-headers-enabled false
:sections members
:render-section-header-fn contacts-section-header
:render-data {:chat-id chat-id
:admin? admin?}
:render-fn contact-item-render}]]))

View File

@ -48,7 +48,7 @@
(defn chats (defn chats
[{:keys [theme selected-tab set-scroll-ref scroll-shared-value]}] [{:keys [theme selected-tab set-scroll-ref scroll-shared-value]}]
(let [unfiltered-items (rf/sub [:chats-stack-items]) (let [unfiltered-items (rf/sub [:chats/chats-stack-items])
items (filter-and-sort-items-by-tab selected-tab unfiltered-items)] items (filter-and-sort-items-by-tab selected-tab unfiltered-items)]
(if (empty? items) (if (empty? items)
[common.empty-state/view [common.empty-state/view

View File

@ -46,7 +46,7 @@
[chat-id] [chat-id]
(let [pinned (rf/sub [:chats/pinned-sorted-list chat-id]) (let [pinned (rf/sub [:chats/pinned-sorted-list chat-id])
render-data (rf/sub [:chats/current-chat-message-list-view-context :in-pinned-view]) render-data (rf/sub [:chats/current-chat-message-list-view-context :in-pinned-view])
current-chat (rf/sub [:chat-by-id chat-id]) current-chat (rf/sub [:chats/chat-by-id chat-id])
{:keys [community-id]} current-chat {:keys [community-id]} current-chat
community (rf/sub [:communities/community community-id]) community (rf/sub [:communities/community community-id])
community-images (rf/sub [:community/images community-id])] community-images (rf/sub [:community/images community-id])]

View File

@ -137,7 +137,7 @@
[chat-id cover-bg-color] [chat-id cover-bg-color]
(let [latest-pin-text (rf/sub [:chats/last-pinned-message-text chat-id]) (let [latest-pin-text (rf/sub [:chats/last-pinned-message-text chat-id])
pins-count (rf/sub [:chats/pin-messages-count chat-id]) pins-count (rf/sub [:chats/pin-messages-count chat-id])
{:keys [muted muted-till chat-type]} (rf/sub [:chat-by-id chat-id]) {:keys [muted muted-till chat-type]} (rf/sub [:chats/chat-by-id chat-id])
community-channel? (= constants/community-chat-type chat-type) community-channel? (= constants/community-chat-type chat-type)
muted? (and muted (some? muted-till)) muted? (and muted (some? muted-till))
mute-chat-label (if community-channel? :t/mute-channel :t/mute-chat) mute-chat-label (if community-channel? :t/mute-channel :t/mute-chat)

View File

@ -109,7 +109,7 @@
(defn actions (defn actions
[{:keys [locked? chat-id]} inside-chat?] [{:keys [locked? chat-id]} inside-chat?]
(let [{:keys [muted muted-till chat-type]} (rf/sub [:chat-by-id chat-id])] (let [{:keys [muted muted-till chat-type]} (rf/sub [:chats/chat-by-id chat-id])]
(cond (cond
locked? locked?
[quo/action-drawer [quo/action-drawer

View File

@ -12,7 +12,7 @@
(def memo-chats-stack-items (atom nil)) (def memo-chats-stack-items (atom nil))
(re-frame/reg-sub (re-frame/reg-sub
:chats-stack-items :chats/chats-stack-items
:<- [:chats/home-list-chats] :<- [:chats/home-list-chats]
:<- [:view-id] :<- [:view-id]
:<- [:home-items-show-number] :<- [:home-items-show-number]
@ -102,7 +102,7 @@
active-chats))) active-chats)))
(re-frame/reg-sub (re-frame/reg-sub
:chat-by-id :chats/chat-by-id
:<- [:chats/chats] :<- [:chats/chats]
(fn [chats [_ chat-id]] (fn [chats [_ chat-id]]
(get chats chat-id))) (get chats chat-id)))
@ -110,35 +110,35 @@
(re-frame/reg-sub (re-frame/reg-sub
:chats/synced-from :chats/synced-from
(fn [[_ chat-id] _] (fn [[_ chat-id] _]
(re-frame/subscribe [:chat-by-id chat-id])) (re-frame/subscribe [:chats/chat-by-id chat-id]))
(fn [{:keys [synced-from]}] (fn [{:keys [synced-from]}]
synced-from)) synced-from))
(re-frame/reg-sub (re-frame/reg-sub
:chats/muted :chats/muted
(fn [[_ chat-id] _] (fn [[_ chat-id] _]
(re-frame/subscribe [:chat-by-id chat-id])) (re-frame/subscribe [:chats/chat-by-id chat-id]))
(fn [{:keys [muted]}] (fn [{:keys [muted]}]
muted)) muted))
(re-frame/reg-sub (re-frame/reg-sub
:chats/chat-type :chats/chat-type
(fn [[_ chat-id] _] (fn [[_ chat-id] _]
(re-frame/subscribe [:chat-by-id chat-id])) (re-frame/subscribe [:chats/chat-by-id chat-id]))
(fn [{:keys [chat-type]}] (fn [{:keys [chat-type]}]
chat-type)) chat-type))
(re-frame/reg-sub (re-frame/reg-sub
:chats/joined :chats/joined
(fn [[_ chat-id] _] (fn [[_ chat-id] _]
(re-frame/subscribe [:chat-by-id chat-id])) (re-frame/subscribe [:chats/chat-by-id chat-id]))
(fn [{:keys [joined]}] (fn [{:keys [joined]}]
joined)) joined))
(re-frame/reg-sub (re-frame/reg-sub
:chats/synced-to-and-from :chats/synced-to-and-from
(fn [[_ chat-id] _] (fn [[_ chat-id] _]
(re-frame/subscribe [:chat-by-id chat-id])) (re-frame/subscribe [:chats/chat-by-id chat-id]))
(fn [chat] (fn [chat]
(select-keys chat [:synced-to :synced-from]))) (select-keys chat [:synced-to :synced-from])))
@ -395,7 +395,7 @@
(re-frame/reg-sub (re-frame/reg-sub
:group-chat/inviter-info :group-chat/inviter-info
(fn [[_ chat-id] _] (fn [[_ chat-id] _]
[(re-frame/subscribe [:chat-by-id chat-id]) [(re-frame/subscribe [:chats/chat-by-id chat-id])
(re-frame/subscribe [:multiaccount/public-key])]) (re-frame/subscribe [:multiaccount/public-key])])
(fn [[chat my-public-key]] (fn [[chat my-public-key]]
{:member? (group-chats.db/member? my-public-key chat) {:member? (group-chats.db/member? my-public-key chat)

View File

@ -270,11 +270,12 @@
(re-frame/reg-sub (re-frame/reg-sub
:contacts/contacts-by-chat :contacts/contacts-by-chat
(fn [[_ _ chat-id] _] (fn [[_ chat-id]]
[(re-frame/subscribe [:chats/chat chat-id]) [(re-frame/subscribe [:chats/chat chat-id])
(re-frame/subscribe [:contacts/contacts])]) (re-frame/subscribe [:contacts/contacts])
(fn [[chat all-contacts] [_ query-fn]] (re-frame/subscribe [:profile/profile])])
(contact.db/query-chat-contacts chat all-contacts query-fn))) (fn [[{:keys [contacts admins]} all-contacts current-multiaccount]]
(contact.db/get-all-contacts-in-group-chat contacts admins all-contacts current-multiaccount)))
(re-frame/reg-sub (re-frame/reg-sub
:contacts/contact-by-address :contacts/contact-by-address
@ -316,8 +317,9 @@
(re-frame/reg-sub (re-frame/reg-sub
:contacts/group-members-sections :contacts/group-members-sections
:<- [:contacts/current-chat-contacts] (fn [[_ chat-id]]
(fn [members] [(re-frame/subscribe [:contacts/contacts-by-chat chat-id])])
(fn [[members]]
(let [admins (filter :admin? members) (let [admins (filter :admin? members)
online (filter #(and (not (:admin? %)) (:online? %)) members) online (filter #(and (not (:admin? %)) (:online? %)) members)
offline (filter #(and (not (:admin? %)) (not (:online? %))) members)] offline (filter #(and (not (:admin? %)) (not (:online? %))) members)]