Add request/approve communites

Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
This commit is contained in:
Gheorghe Pinzaru 2020-12-21 10:24:58 +03:00 committed by Andrea Maria Piana
parent c2f513f67e
commit c7009ff6f7
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
53 changed files with 1648 additions and 655 deletions

View File

@ -1,5 +1,6 @@
{:lint-as {status-im.utils.views/defview clojure.core/defn
status-im.utils.views/letsubs clojure.core/let
reagent.core/with-let clojkure.core/let
status-im.utils.fx/defn clj-kondo.lint-as/def-catch-all
quo.react/with-deps-check clojure.core/fn
quo.previews.preview/list-comp clojure.core/for

View File

@ -27,5 +27,5 @@ MAX_IMAGES_BATCH=5
APN_TOPIC=im.status.ethereum.pr
VERIFY_TRANSACTION_CHAIN_ID=3
COMMUNITIES_ENABLED=1
COMMUNITIES_MANAGEMENT_ENABLED=0
DATABASE_MANAGEMENT_ENABLED=1
COMMUNITIES_MANAGEMENT_ENABLED=1

View File

@ -27,5 +27,5 @@ BLANK_PREVIEW=0
MAX_IMAGES_BATCH=5
GOOGLE_FREE=0
COMMUNITIES_ENABLED=1
COMMUNITIES_MANAGEMENT_ENABLED=0
DATABASE_MANAGEMENT_ENABLED=1
COMMUNITIES_MANAGEMENT_ENABLED=1

View File

@ -22,3 +22,4 @@ MAX_IMAGES_BATCH=5
BLANK_PREVIEW=0
COMMUNITIES_ENABLED=1
DATABASE_MANAGEMENT_ENABLED=1
COMMUNITIES_MANAGEMENT_ENABLED=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 731 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 747 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 887 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 950 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 881 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,11 +1,14 @@
(ns quo.components.list.footer
(:require [quo.react-native :as rn]
[quo.design-system.spacing :as spacing]
[quo.components.text :as text]))
[quo.components.text :as text]
[reagent.core :as reagent]))
(defn footer [& children]
(defn footer []
(let [this (reagent/current-component)
{:keys [color]
:or {color :secondary}} (reagent/props this)]
[rn/view {:style (merge (:base spacing/padding-horizontal)
(:small spacing/padding-vertical))}
(into [text/text {:color :secondary}]
children)])
(into [text/text {:color color}]
(reagent/children this))]))

View File

@ -1,11 +1,15 @@
(ns quo.components.list.header
(:require [quo.react-native :as rn]
(:require [reagent.core :as reagent]
[quo.react-native :as rn]
[quo.design-system.spacing :as spacing]
[quo.components.text :as text]))
(defn header [& children]
(defn header []
(let [this (reagent/current-component)
{:keys [color]
:or {color :secondary}} (reagent/props this)]
[rn/view {:style (merge (:base spacing/padding-horizontal)
(:x-tiny spacing/padding-vertical))}
(into [text/text {:color :secondary
(into [text/text {:color color
:style {:margin-top 10}}]
children)])
(reagent/children this))]))

View File

@ -0,0 +1,16 @@
(ns quo.components.list.index
(:require [quo.react-native :as rn]
[quo.components.text :as text]
[quo.design-system.colors :as colors]))
(defn index [{:keys [title]}]
[rn/view {:style {:padding-right 16}}
[rn/view {:style {:border-top-width 1
:border-bottom-width 1
:border-right-width 1
:border-color (colors/get-color :border-01)
:padding-vertical 3
:padding-horizontal 16
:border-top-right-radius 16
:border-bottom-right-radius 16}}
[text/text title]]])

View File

@ -9,6 +9,7 @@
[quo.components.list.header :as list-header]
[quo.components.list.footer :as list-footer]
[quo.components.list.item :as list-item]
[quo.components.list.index :as list-index]
[quo.components.controls.view :as controls]
[quo.components.bottom-sheet.view :as bottom-sheet]
[quo.components.separator :as separator]
@ -23,6 +24,7 @@
(def list-header list-header/header)
(def list-footer list-footer/footer)
(def list-item list-item/list-item)
(def list-index list-index/index)
(def bottom-sheet bottom-sheet/bottom-sheet)
(def switch controls/switch)
(def radio controls/radio)

View File

@ -15,6 +15,8 @@
(def image (reagent/adapt-react-class (.-Image rn)))
(def text (reagent/adapt-react-class (.-Text ^js rn)))
(defn resolve-asset-source [uri] (js->clj (.resolveAssetSource ^js (.-Image ^js rn) uri) :keywordize-keys true))
(def scroll-view (reagent/adapt-react-class (.-ScrollView ^js rn)))
(def modal (reagent/adapt-react-class (.-Modal ^js rn)))
(def refresh-control (reagent/adapt-react-class (.-RefreshControl ^js rn)))
@ -90,9 +92,9 @@
;; Flat-list
(def ^:private rn-flat-list (reagent/adapt-react-class (.-FlatList ^js rn)))
(defn- wrap-render-fn [f]
(defn- wrap-render-fn [f render-data]
(fn [data]
(reagent/as-element [f (.-item ^js data) (.-index ^js data) (.-separators ^js data)])))
(reagent/as-element [f (.-item ^js data) (.-index ^js data) (.-separators ^js data) render-data])))
(defn- wrap-key-fn [f]
(fn [data index]
@ -100,10 +102,10 @@
(f data index)))
(defn base-list-props
[{:keys [key-fn render-fn empty-component header footer separator data] :as props}]
[{:keys [key-fn render-fn empty-component header footer separator data render-data] :as props}]
(merge {:data (to-array data)}
(when key-fn {:keyExtractor (wrap-key-fn key-fn)})
(when render-fn {:renderItem (wrap-render-fn render-fn)})
(when render-fn {:renderItem (wrap-render-fn render-fn render-data)})
(when separator {:ItemSeparatorComponent (fn [] (reagent/as-element separator))})
(when empty-component {:ListEmptyComponent (fn [] (reagent/as-element empty-component))})
(when header {:ListHeaderComponent (reagent/as-element header)})

View File

@ -211,10 +211,11 @@
;;;; Send message
(fx/defn update-db-message-status
[{:keys [db] :as cofx} chat-id message-id status]
(when (get-in db [:messages chat-id message-id])
(fx/merge cofx
{:db (assoc-in db
[:messages chat-id message-id :outgoing-status]
status)}))
status)})))
(fx/defn update-message-status
[{:keys [db] :as cofx} chat-id message-id status]

View File

@ -2,6 +2,8 @@
(:require
[re-frame.core :as re-frame]
[clojure.walk :as walk]
[clojure.string :as string]
[clojure.set :as clojure.set]
[taoensso.timbre :as log]
[status-im.utils.fx :as fx]
[status-im.constants :as constants]
@ -9,35 +11,50 @@
[status-im.transport.filters.core :as models.filters]
[status-im.bottom-sheet.core :as bottom-sheet]
[status-im.data-store.chats :as data-store.chats]
[status-im.ethereum.json-rpc :as json-rpc]))
[status-im.ethereum.json-rpc :as json-rpc]
[status-im.ui.components.colors :as colors]
[status-im.navigation :as navigation]))
(def crop-size 1000)
(def featured
[{:name "Status"
:id constants/status-community-id}])
(def access-no-membership 1)
(def access-invitation-only 2)
(def access-on-request 3)
(defn <-request-to-join-community-rpc [r]
(clojure.set/rename-keys r {:communityId :community-id
:publicKey :public-key
:chatId :chat-id}))
(defn <-requests-to-join-community-rpc [requests]
(reduce (fn [acc r]
(assoc acc (:id r) (<-request-to-join-community-rpc r)))
{}
requests))
(defn <-chats-rpc [chats]
(reduce-kv (fn [acc k v]
(assoc acc
(name k)
(-> v
(update :members walk/stringify-keys)
(assoc :identity {:display-name (get-in v [:identity :display_name])
:description (get-in v [:identity :description])}
:id (name k)))))
(assoc :can-post? (:canPost v))
(dissoc :canPost)
(update :members walk/stringify-keys))))
{}
chats))
(defn <-rpc [{:keys [description] :as c}]
(let [identity (:identity description)]
(defn <-rpc [c]
(-> c
(update-in [:description :members] walk/stringify-keys)
(assoc-in [:description :identity] {:display-name (:display_name identity)
:description (:description identity)})
(update-in [:description :chats] <-chats-rpc))))
(clojure.set/rename-keys {:canRequestAccess :can-request-access?
:canManageUsers :can-manage-users?
:canJoin :can-join?
:requestedToJoinAt :requested-to-join-at
:isMember :is-member?})
(update :members walk/stringify-keys)
(update :chats <-chats-rpc)))
(defn fetch-community-id-input [{:keys [db]}]
(:communities/community-id-input db))
(fx/defn handle-chats [cofx chats]
(models.chat/ensure-chats cofx chats))
@ -48,6 +65,10 @@
(fx/defn handle-removed-filters [cofx filters]
(models.filters/handle-filters-removed cofx (map models.filters/responses->filters filters)))
(fx/defn handle-request-to-join [{:keys [db]} r]
(let [{:keys [id community-id] :as request} (<-request-to-join-community-rpc r)]
{:db (assoc-in db [:communities/requests-to-join community-id id] request)}))
(fx/defn handle-removed-chats [{:keys [db]} chat-ids]
{:db (reduce (fn [db chat-id]
(update db :chats dissoc chat-id))
@ -83,24 +104,27 @@
(handle-response cofx response))
(fx/defn joined
{:events [::joined]}
{:events [::joined ::requested-to-join]}
[cofx response]
(handle-response cofx response))
(fx/defn export
[cofx community-id on-success]
{:events [::export-pressed]}
[cofx community-id]
{::json-rpc/call [{:method "wakuext_exportCommunity"
:params [community-id]
:on-success on-success
:on-success #(re-frame/dispatch [:show-popover {:view :export-community
:community-key %}])
:on-error #(do
(log/error "failed to export community" community-id %)
(re-frame/dispatch [::failed-to-export %]))}]})
(fx/defn import-community
{:events [::import]}
[cofx community-key on-success]
[cofx community-key]
{::json-rpc/call [{:method "wakuext_importCommunity"
:params [community-key]
:on-success on-success
:on-success #(re-frame/dispatch [::community-imported %])
:on-error #(do
(log/error "failed to import community" %)
(re-frame/dispatch [::failed-to-import %]))}]})
@ -115,6 +139,16 @@
(log/error "failed to join community" community-id %)
(re-frame/dispatch [::failed-to-join %]))}]})
(fx/defn request-to-join
{:events [::request-to-join]}
[cofx community-id]
{::json-rpc/call [{:method "wakuext_requestToJoinCommunity"
:params [{:communityId community-id}]
:on-success #(re-frame/dispatch [::requested-to-join %])
:on-error #(do
(log/error "failed to request to join community" community-id %)
(re-frame/dispatch [::failed-to-request-to-join %]))}]})
(fx/defn leave
{:events [::leave]}
[cofx community-id]
@ -145,175 +179,256 @@
#(re-frame/dispatch [:transport/message-sent % 1])
:on-failure #(log/error "failed to send a message" %)}]})
(fx/defn invite-user [cofx
community-id
user-pk
on-success-event
on-failure-event]
(fx/merge cofx
{::json-rpc/call [{:method "wakuext_inviteUserToCommunity"
:params [community-id
user-pk]
:on-success #(re-frame/dispatch [on-success-event %])
(fx/defn invite-users
{:events [::invite-people-confirmation-pressed]}
[cofx user-pk contacts]
(let [community-id (fetch-community-id-input cofx)
pks (if (seq user-pk)
(conj contacts user-pk)
contacts)]
(when (seq pks)
{::json-rpc/call [{:method "wakuext_inviteUsersToCommunity"
:params [{:communityId community-id
:users pks}]
:on-success #(re-frame/dispatch [::people-invited %])
:on-error #(do
(log/error "failed to invite-user community" %)
(re-frame/dispatch [on-failure-event %]))}]}
(models.chat/upsert-chat {:chat-id user-pk
:active (get-in cofx [:db :chats user-pk :active])}
#(re-frame/dispatch [::chat-created community-id user-pk]))))
(re-frame/dispatch [::failed-to-invite-people %]))}]})))
(fx/defn share-community
{:events [::share-community-confirmation-pressed]}
[cofx user-pk contacts]
(let [community-id (fetch-community-id-input cofx)
pks (if (seq user-pk)
(conj contacts user-pk)
contacts)]
(when (seq pks)
{::json-rpc/call [{:method "wakuext_shareCommunity"
:params [{:communityId community-id
:users pks}]
:on-success #(re-frame/dispatch [::people-invited %])
:on-error #(do
(log/error "failed to invite-user community" %)
(re-frame/dispatch [::failed-to-share-community %]))}]})))
(fx/defn create [{:keys [db]}
community-name
community-description
community-membership
on-success-event
on-failure-event]
(let [membership (js/parseInt community-membership)
(fx/defn create
{:events [::create-confirmation-pressed]}
[{:keys [db]}]
(let [{:keys [name description membership image]} (get db :communities/create)
my-public-key (get-in db [:multiaccount :public-key])]
;; If access is ENS only, we set the access to require approval and set the rule
;; of ens only
(let [params (cond-> {:name name
:description description
:membership (or membership constants/community-no-membership-access)
:color (rand-nth colors/chat-colors)
:image (string/replace-first (str image) #"file://" "")
:imageAx 0
:imageAy 0
:imageBx crop-size
:imageBy crop-size}
(= membership constants/community-rule-ens-only)
(assoc :membership constants/community-on-request-access
:ens-only true))]
{::json-rpc/call [{:method "wakuext_createCommunity"
:params [{:identity {:display_name community-name
:description community-description}
:members {my-public-key {}}
:permissions {:access membership}}]
:on-success #(re-frame/dispatch [on-success-event %])
:params [params]
:on-success #(re-frame/dispatch [::community-created %])
:on-error #(do
(log/error "failed to create community" %)
(re-frame/dispatch [on-failure-event %]))}]}))
(re-frame/dispatch [::failed-to-create-community %]))}]})))
(defn create-channel [community-id
community-channel-name
community-channel-description
on-success-event
on-failure-event]
(fx/defn edit
{:events [::edit-confirmation-pressed]}
[{:keys [db]}]
(let [{:keys [name description membership]} (get db :communities/create)
my-public-key (get-in db [:multiaccount :public-key])]
(log/error "Edit community is not yet implemented")
;; {::json-rpc/call [{:method "wakuext_editCommunity"
;; :params [{:identity {:display_name name
;; :description description}
;; :permissions {:access membership}}]
;; :on-success #(re-frame/dispatch [::community-edited %])
;; :on-error #(do
;; (log/error "failed to create community" %)
;; (re-frame/dispatch [::failed-to-edit-community %]))}]}
))
(fx/defn create-channel
{:events [::create-channel-confirmation-pressed]}
[cofx community-channel-name community-channel-description]
(let [community-id (fetch-community-id-input cofx)]
{::json-rpc/call [{:method "wakuext_createCommunityChat"
:params [community-id
{:identity {:display_name community-channel-name
:color (rand-nth colors/chat-colors)
:description community-channel-description}
:permissions {:access access-no-membership}}]
:on-success #(re-frame/dispatch [on-success-event %])
:permissions {:access constants/community-channel-access-no-membership}}]
:on-success #(re-frame/dispatch [::community-channel-created %])
:on-error #(do
(log/error "failed to create community channel" %)
(re-frame/dispatch [on-failure-event %]))}]})
(def no-membership-access 1)
(def invitation-only-access 2)
(def on-request-access 3)
(re-frame/dispatch [::failed-to-create-community-channel %]))}]}))
(defn require-membership? [permissions]
(not= no-membership-access (:access permissions)))
(not= constants/community-no-membership-access (:access permissions)))
(def community-id-length 68)
;; TODO: test this
(defn can-post? [{:keys [admin] :as community} pk local-chat-id]
(let [chat-id (subs local-chat-id community-id-length)
can-access-community? (or (get-in community [:description :members pk])
(not (require-membership? (get-in community [:description :permissions]))))]
(or admin
(get-in community [:description :chats chat-id :members pk])
(and can-access-community?
(not (require-membership? (get-in community [:description :chats chat-id :permissions])))))))
(defn can-post? [community _ local-chat-id]
(let [chat-id (subs local-chat-id community-id-length)]
(get-in community [:chats chat-id :can-post?])))
(fx/defn reset-community-id-input [{:keys [db]} id]
{:db (assoc db :communities/community-id-input id)})
(defn fetch-community-id-input [{:keys [db]}]
(:communities/community-id-input db))
(fx/defn import-pressed
{:events [::import-pressed]}
[cofx]
(bottom-sheet/show-bottom-sheet cofx {:view :import-community}))
(fx/defn create-pressed
{:events [::create-pressed]}
[cofx]
(bottom-sheet/show-bottom-sheet cofx {:view :create-community}))
(fx/defn invite-people-pressed
{:events [::invite-people-pressed]}
[cofx id]
(fx/merge cofx
(reset-community-id-input id)
(bottom-sheet/show-bottom-sheet {:view :invite-people-community})))
(bottom-sheet/hide-bottom-sheet)
(navigation/navigate-to :invite-people-community {:invite? true})))
(fx/defn share-community-pressed
{:events [::share-community-pressed]}
[cofx id]
(fx/merge cofx
(reset-community-id-input id)
(bottom-sheet/hide-bottom-sheet)
(navigation/navigate-to :invite-people-community {})))
(fx/defn create-channel-pressed
{:events [::create-channel-pressed]}
[cofx id]
(fx/merge cofx
(reset-community-id-input id)
(bottom-sheet/show-bottom-sheet {:view :create-community-channel})))
(navigation/navigate-to :create-community-channel nil)))
(fx/defn community-created
{:events [::community-created]}
[cofx response]
(fx/merge cofx
(bottom-sheet/hide-bottom-sheet)
(navigation/navigate-back)
(handle-response response)))
(fx/defn community-edited
{:events [::community-edited]}
[cofx response]
(fx/merge cofx
(navigation/navigate-back)
(handle-response response)))
(fx/defn open-create-community
{:events [::open-create-community]}
[{:keys [db] :as cofx}]
(fx/merge cofx
{:db (assoc db :communities/create {})}
(navigation/navigate-to :community-create nil)))
(fx/defn open-edit-community
{:events [::open-edit-community]}
[{:keys [db] :as cofx} id]
(let [{:keys [identity permissions]} (get-in db [:communities id :description])
{:keys [display-name description image]} identity
{:keys [access]} permissions]
(fx/merge cofx
{:db (assoc db :communities/create {:name display-name
:description description
:image image
:membership access})}
(navigation/navigate-to :communities {:screen :community-edit}))))
(fx/defn community-imported
{:events [::community-imported]}
[cofx response]
(fx/merge cofx
(bottom-sheet/hide-bottom-sheet)
(navigation/navigate-back)
(handle-response response)))
(fx/defn people-invited
{:events [::people-invited]}
[cofx response]
(fx/merge cofx
(bottom-sheet/hide-bottom-sheet)
(navigation/navigate-back)
(handle-response response)))
(fx/defn community-channel-created
{:events [::community-channel-created]}
[cofx response]
(fx/merge cofx
(navigation/navigate-back)
(handle-response response)))
(fx/defn create-field
{:events [::create-field]}
[{:keys [db]} field value]
{:db (assoc-in db [:communities/create field] value)})
(fx/defn member-ban
{:events [::member-ban]}
[cofx community-id public-key]
(log/error "Community member ban is not yet implemented"))
(fx/defn member-kicked
{:events [::member-kicked]}
[cofx response]
(fx/merge cofx
(bottom-sheet/hide-bottom-sheet)
(handle-response response)))
(fx/defn handle-export-pressed
{:events [::export-pressed]}
(fx/defn member-kick
{:events [::member-kick]}
[cofx community-id public-key]
{::json-rpc/call [{:method "wakuext_removeUserFromCommunity"
:params [community-id public-key]
:on-success #(re-frame/dispatch [::member-kicked %])
:on-error #(log/error "failed to remove user from community" community-id public-key %)}]})
(fx/defn delete-community
{:events [::delete-community]}
[cofx community-id]
(export cofx community-id
#(re-frame/dispatch [:show-popover {:view :export-community
:community-key %}])))
(log/error "Community delete is not yet implemented"))
(fx/defn import-confirmation-pressed
{:events [::import-confirmation-pressed]}
[cofx community-key]
(import-community
cofx
community-key
#(re-frame/dispatch [::community-imported %])))
(fx/defn requests-to-join-fetched
{:events [::requests-to-join-fetched]}
[{:keys [db]} community-id requests]
{:db (assoc-in db [:communities/requests-to-join community-id] (<-requests-to-join-community-rpc requests))})
(fx/defn create-confirmation-pressed
{:events [::create-confirmation-pressed]}
[cofx community-name community-description membership]
(create
cofx
community-name
community-description
membership
::community-created
::failed-to-create-community))
(fx/defn fetch-requests-to-join
{:events [::fetch-requests-to-join]}
[cofx community-id]
{::json-rpc/call [{:method "wakuext_pendingRequestsToJoinForCommunity"
:params [community-id]
:on-success #(re-frame/dispatch [::requests-to-join-fetched community-id %])
:on-error #(log/error "failed to fetch requests-to-join" community-id %)}]})
(fx/defn create-channel-confirmation-pressed
{:events [::create-channel-confirmation-pressed]}
[cofx community-channel-name community-channel-description]
(create-channel
(fetch-community-id-input cofx)
community-channel-name
community-channel-description
::community-channel-created
::failed-to-create-community-channel))
(defn fetch-requests-to-join! [community-id]
(re-frame/dispatch [::fetch-requests-to-join community-id]))
(fx/defn invite-people-confirmation-pressed
{:events [::invite-people-confirmation-pressed]}
[cofx user-pk]
(invite-user
cofx
(fetch-community-id-input cofx)
user-pk
::people-invited
::failed-to-invite-people))
(fx/defn request-to-join-accepted
{:events [::request-to-join-accepted]}
[{:keys [db] :as cofx} community-id request-id response]
(fx/merge cofx
{:db (update-in db [:communities/requests-to-join community-id] dissoc request-id)}
(handle-response response)))
(fx/defn request-to-join-declined
{:events [::request-to-join-declined]}
[{:keys [db] :as cofx} community-id request-id]
{:db (update-in db [:communities/requests-to-join community-id] dissoc request-id)})
(fx/defn accept-request-to-join-pressed
{:events [:communities.ui/accept-request-to-join-pressed]}
[cofx community-id request-id]
{::json-rpc/call [{:method "wakuext_acceptRequestToJoinCommunity"
:params [{:id request-id}]
:on-success #(re-frame/dispatch [::request-to-join-accepted community-id request-id %])
:on-error #(log/error "failed to accept requests-to-join" community-id request-id %)}]})
(fx/defn decline-request-to-join-pressed
{:events [:communities.ui/decline-request-to-join-pressed]}
[cofx community-id request-id]
{::json-rpc/call [{:method "wakuext_declineRequestToJoinCommunity"
:params [{:id request-id}]
:on-success #(re-frame/dispatch [::request-to-join-declined community-id request-id %])
:on-error #(log/error "failed to decline requests-to-join" community-id request-id)}]})

View File

@ -27,6 +27,8 @@
(def ^:const timeline-chat-type 5)
(def ^:const community-chat-type 6)
(def request-to-join-pending-state 1)
(def reactions {emoji-reaction-love (:love resources/reactions)
emoji-reaction-thumbs-up (:thumbs-up resources/reactions)
emoji-reaction-thumbs-down (:thumbs-down resources/reactions)
@ -79,6 +81,17 @@
(def ^:const status-create-address "status_createaddress")
(def ^:const community-no-membership-access 1)
(def ^:const community-invitation-only-access 2)
(def ^:const community-on-request-access 3)
;; Community rules for joining
(def ^:const community-rule-ens-only "ens-only")
(def ^:const community-channel-access-no-membership 1)
(def ^:const community-channel-access-invitation-only 2)
(def ^:const community-channel-access-on-request 3)
; BIP44 Wallet Root Key, the extended key from which any wallet can be derived
(def ^:const path-wallet-root "m/44'/60'/0'/0")
; EIP1581 Root Key, the extended key from which any whisper key/encryption key can be derived

View File

@ -123,13 +123,17 @@
(fx/defn name-verified
{:events [:contacts/ens-name-verified]}
[{:keys [db now] :as cofx} public-key ens-name]
(let [contact (-> (get-in db [:contacts/contacts public-key]
(let [contact (-> (or (get-in db [:contacts/contacts public-key])
(build-contact cofx public-key))
(assoc :name ens-name
:last-ens-clock-value now
:ens-verified-at now
:ens-verified true))]
(upsert-contact cofx contact)))
(fx/merge cofx
{:db (-> db
(update-in [:contacts/contacts public-key] merge contact))
::json-rpc/call [{:method "wakuext_ensVerified"
:params [public-key ens-name]
:on-success #(log/debug "ens name verified successuful")}]}
(transport.filters/load-contact contact))))
(fx/defn update-nickname
{:events [:contacts/update-nickname]}

View File

@ -117,12 +117,19 @@
"multiaccounts_deleteIdentityImage" {}
"wakuext_createCommunity" {}
"wakuext_createCommunityChat" {}
"wakuext_inviteUserToCommunity" {}
"wakuext_inviteUsersToCommunity" {}
"wakuext_shareCommunity" {}
"wakuext_removeUserFromCommunity" {}
"wakuext_requestToJoinCommunity" {}
"wakuext_acceptRequestToJoinCommunity" {}
"wakuext_declineRequestToJoinCommunity" {}
"wakuext_pendingRequestsToJoinForCommunity" {}
"wakuext_joinCommunity" {}
"wakuext_leaveCommunity" {}
"wakuext_communities" {}
"wakuext_importCommunity" {}
"wakuext_exportCommunity" {}
"wakuext_ensVerified" {}
"status_chats" {}
"localnotifications_switchWalletNotifications" {}
"localnotifications_notificationPreferences" {}

View File

@ -213,6 +213,11 @@
(reg-root-key-sub :buy-crypto/on-ramps :buy-crypto/on-ramps)
;; communities
(reg-root-key-sub :communities/create :communities/create)
(reg-root-key-sub :communities/requests-to-join :communities/requests-to-join)
(reg-root-key-sub :communities/community-id-input :communities/community-id-input)
(re-frame/reg-sub
:communities
(fn [db]
@ -225,6 +230,17 @@
:else
[])))
(re-frame/reg-sub
:communities/section-list
:<- [:communities]
(fn [communities]
(->> (vals communities)
(group-by (comp (fnil string/upper-case "") first :name))
(sort-by (fn [[title]] title))
(map (fn [[title data]]
{:title title
:data data})))))
(re-frame/reg-sub
:communities/community
:<- [:communities]
@ -232,16 +248,25 @@
(get communities id)))
(re-frame/reg-sub
:communities/status-community
:communities/communities
:<- [:search/home-filter]
:<- [:communities]
(fn [[search-filter communities]]
(let [status-community (get communities constants/status-community-id)]
(when (and (:joined status-community)
(or (string/blank? search-filter)
(string/includes? (string/lower-case
(get-in status-community [:description :identity :display-name])) search-filter)))
status-community))))
(filterv
(fn [{:keys [name joined id]}]
(and joined
(or config/communities-management-enabled?
(= id constants/status-community-id))
(or (empty? search-filter)
(string/includes? (string/lower-case (str name)) search-filter))))
(vals communities))))
(re-frame/reg-sub
:communities/edited-community
:<- [:communities]
:<- [:communities/community-id-input]
(fn [[communities community-id]]
(get communities community-id)))
(re-frame/reg-sub
:communities/current-community
@ -260,6 +285,16 @@
0
chats)))
(re-frame/reg-sub
:communities/requests-to-join-for-community
:<- [:communities/requests-to-join]
(fn [requests [_ community-id]]
(->>
(get requests community-id {})
vals
(filter (fn [{:keys [state]}]
(= state constants/request-to-join-pending-state))))))
;;GENERAL ==============================================================================================================
@ -1198,9 +1233,21 @@
:home-items
:<- [:search/home-filter]
:<- [:search/filtered-chats]
(fn [[search-filter filtered-chats]]
:<- [:communities/communities]
(fn [[search-filter filtered-chats communities]]
(let [communities-count (count communities)
chats-count (count filtered-chats)
;; If we have both communities & chats we want to display
;; a separator between them
communities-with-separator (if (and (pos? communities-count)
(pos? chats-count))
(update communities
(dec communities-count)
assoc :last? true)
communities)]
{:search-filter search-filter
:chats filtered-chats}))
:items (concat communities-with-separator filtered-chats)})))
;;PAIRING ==============================================================================================================

View File

@ -28,6 +28,9 @@
(fx/defn handle-community [cofx community]
(models.communities/handle-community cofx community))
(fx/defn handle-request-to-join-community [cofx request]
(models.communities/handle-request-to-join cofx request))
(fx/defn handle-reactions [cofx reactions]
(models.reactions/receive-signal cofx reactions))
@ -44,6 +47,7 @@
{:events [::process]}
[cofx ^js response-js]
(let [^js communities (.-communities response-js)
^js requests-to-join-community (.-requestsToJoinCommunity response-js)
^js chats (.-chats response-js)
^js contacts (.-contacts response-js)
^js installations (.-installations response-js)
@ -72,6 +76,11 @@
(fx/merge cofx
{:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]}
(handle-community (types/js->clj community))))
(seq requests-to-join-community)
(let [request (.pop requests-to-join-community)]
(fx/merge cofx
{:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]}
(handle-request-to-join-community (types/js->clj request))))
(seq chats)
(let [chats-clj (types/js->clj chats)]
(js-delete response-js "chats")

View File

@ -0,0 +1,10 @@
(ns status-im.ui.components.unviewed-indicator
(:require [status-im.ui.components.badge :as badge]
[status-im.ui.components.react :as react]))
(defn unviewed-indicator [c]
(when (pos? c)
[react/view {:padding-left 16
:justify-content :flex-end
:align-items :flex-end}
[badge/message-counter c]]))

View File

@ -4,7 +4,6 @@
[status-im.ui.screens.home.sheet.views :as home.sheet]
[status-im.ui.screens.keycard.views :as keycard]
[status-im.ui.screens.about-app.views :as about-app]
[status-im.ui.screens.communities.views :as communities]
[status-im.ui.screens.multiaccounts.recover.views :as recover.views]
[quo.core :as quo]))
@ -33,18 +32,6 @@
(= view :learn-more)
(merge about-app/learn-more)
(= view :create-community)
(merge communities/create-sheet)
(= view :import-community)
(merge communities/import-sheet)
(= view :create-community-channel)
(merge communities/create-channel-sheet)
(= view :invite-people-community)
(merge communities/invite-people-sheet)
(= view :recover-sheet)
(merge recover.views/bottom-sheet))]
[quo/bottom-sheet opts

View File

@ -2,7 +2,6 @@
(:require [re-frame.core :as re-frame]
[status-im.constants :as constants]
[status-im.i18n.i18n :as i18n]
[status-im.communities.core :as communities]
[status-im.utils.config :as config]
[status-im.react-native.resources :as resources]
[status-im.ui.components.colors :as colors]
@ -216,7 +215,7 @@
(chat.utils/format-author contact-with-names opts)))
(defview community-content [{:keys [community-id] :as message}]
(letsubs [{:keys [joined verified] :as community} [:communities/community community-id]]
(letsubs [{:keys [name description verified] :as community} [:communities/community community-id]]
(when (and
config/communities-enabled?
community)
@ -253,25 +252,24 @@
:style {:width 40
:height 40}}]
(let [display-name (get-in community [:description :identity :display-name])]
[chat-icon/chat-icon-view-chat-list
display-name
name
true
display-name
colors/default-community-color]))]
name
colors/default-community-color])]
[react/view {:padding-right 14}
[react/text {:style {:font-weight "700"
:font-size 17}}
(get-in community [:description :identity :display-name])]
[react/text (get-in community [:description :identity :description])]]]
name]
[react/text description]]]
[react/view {:border-width 1
:padding-vertical 8
:border-bottom-left-radius 10
:border-bottom-right-radius 10
:border-color colors/gray-lighter}
[react/touchable-highlight {:on-press #(re-frame/dispatch [(if joined ::communities/leave ::communities/join) (:id community)])}
[react/touchable-highlight {:on-press #(re-frame/dispatch [:navigate-to :community {:community-id (:id community)}])}
[react/text {:style {:text-align :center
:color colors/blue}} (if joined (i18n/label :t/leave) (i18n/label :t/join))]]]])))
:color colors/blue}} (i18n/label :t/view)]]]])))
(defn message-content-wrapper
"Author, userpic and delivery wrapper"

View File

@ -0,0 +1,245 @@
(ns status-im.ui.screens.communities.community
(:require [status-im.ui.components.topbar :as topbar]
[quo.react-native :as rn]
[status-im.ui.components.toolbar :as toolbar]
[quo.core :as quo]
[status-im.constants :as constants]
[status-im.utils.handlers :refer [>evt <sub]]
[status-im.i18n.i18n :as i18n]
[status-im.utils.datetime :as datetime]
[status-im.utils.config :as config]
[status-im.communities.core :as communities]
[status-im.ui.components.list.views :as list]
[status-im.ui.screens.home.views.inner-item :as inner-item]
[status-im.ui.screens.chat.photos :as photos]
[status-im.react-native.resources :as resources]
[status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.icons.icons :as icons]
[status-im.utils.core :as utils]))
(def request-cooldown-ms (* 60 1000))
(defn can-request-access-again? [requested-at]
(> (datetime/timestamp) (+ (* requested-at 1000) request-cooldown-ms)))
(defn toolbar-content [id display-name color images show-members-count? members]
(let [thumbnail-image (get-in images [:thumbnail :uri])]
[rn/view {:style {:flex 1
:align-items :center
:flex-direction :row}}
[rn/view {:padding-right 10}
(cond
(= id constants/status-community-id)
[rn/image {:source (resources/get-image :status-logo)
:style {:width 40
:height 40}}]
(seq thumbnail-image)
[photos/photo thumbnail-image {:size 40}]
:else
[chat-icon.screen/chat-icon-view-toolbar
id
true
display-name
(or color (rand-nth colors/chat-colors))])]
[rn/view {:style {:flex 1 :justify-content :center}}
[quo/text {:number-of-lines 1
:accessibility-label :community-name-text}
display-name]
(when show-members-count?
[quo/text {:number-of-lines 1
:size :small
:color :secondary}
(i18n/label-pluralize members :t/community-members {:count members})])]]))
(defn hide-sheet-and-dispatch [event]
(>evt [:bottom-sheet/hide])
(>evt event))
(defn community-actions [{:keys [id
permissions
can-manage-users? name images color]}]
(let [can-invite? (and can-manage-users? (not= (:access permissions) constants/community-no-membership-access))
can-share? (not= (:access permissions) constants/community-invitation-only-access)
thumbnail-image (get-in images [:thumbnail :uri])]
[:<>
[quo/list-item
{:title name
:on-press #(hide-sheet-and-dispatch [:navigate-to :community-management {:community-id id}])
:chevron true
:icon (cond
(= id constants/status-community-id)
[rn/image {:source (resources/get-image :status-logo)
:style {:width 40
:height 40}}]
(seq thumbnail-image)
[photos/photo thumbnail-image {:size 40}]
:else
[chat-icon.screen/chat-icon-view-chat-sheet
name
true
name
(or color (rand-nth colors/chat-colors))])}]
(when (and config/communities-management-enabled? can-manage-users?)
[:<>
[quo/list-item
{:theme :accent
:title (i18n/label :t/export-key)
:accessibility-label :community-export-key
:icon :main-icons/objects
:on-press #(hide-sheet-and-dispatch [::communities/export-pressed id])}]
[quo/list-item
{:theme :accent
:title (i18n/label :t/create-channel)
:accessibility-label :community-create-channel
:icon :main-icons/channel
:on-press #(hide-sheet-and-dispatch [::communities/create-channel-pressed id])}]])
(when can-invite?
[quo/list-item
{:theme :accent
:title (i18n/label :t/invite-people)
:icon :main-icons/share
:accessibility-label :community-invite-people
:on-press #(>evt [::communities/invite-people-pressed id])}])
(when can-share?
[quo/list-item
{:theme :accent
:title (i18n/label :t/share)
:icon :main-icons/share
:accessibility-label :community-share
:on-press #(>evt [::communities/share-community-pressed id])}])
[quo/list-item
{:theme :accent
:title (i18n/label :t/leave-community)
:accessibility-label :leave
:icon :main-icons/arrow-left
:on-press #(do
(>evt [:bottom-sheet/hide])
(>evt [:navigate-to :home])
(>evt [::communities/leave id]))}]]))
(defn welcome-blank-page []
[rn/view {:style {:padding 16 :flex 1 :flex-direction :row :align-items :center :justify-content :center}}
[quo/text {:align :center
:color :secondary}
(i18n/label :t/welcome-community-blank-message)]])
(defn community-chat-item [home-item]
[inner-item/home-list-item home-item])
(defn community-chat-list [chats]
(if (empty? chats)
[welcome-blank-page]
[list/flat-list
{:key-fn :chat-id
:content-container-style {:padding-vertical 8}
:keyboard-should-persist-taps :always
:data chats
:render-fn community-chat-item
:footer [rn/view {:height 68}]}]))
(defn community-channel-list [id]
(let [chats (<sub [:chats/by-community-id id])
chats (cond->> chats
(= id constants/status-community-id)
(map #(assoc % :color colors/blue)))]
[community-chat-list chats]))
(defn channel-preview-item [{:keys [id color name]}]
(let [color (or color (rand-nth colors/chat-colors))]
[quo/list-item
{:icon [chat-icon.screen/chat-icon-view-chat-list
id true name color false false]
:title [rn/view {:flex-direction :row
:flex 1
:padding-right 16
:align-items :center}
[icons/icon :main-icons/tiny-group
{:color colors/black
:width 15
:height 15
:container-style {:width 15
:height 15
:margin-right 2}}]
[quo/text {:weight :medium
:accessibility-label :chat-name-text
:ellipsize-mode :tail
:number-of-lines 1}
(utils/truncate-str name 30)]]
:title-accessibility-label :chat-name-text}]))
(defn community-channel-preview-list [_ chats-without-id]
(let [chats (reduce-kv
(fn [acc k v]
(conj acc (assoc v :id (name k))))
[]
chats-without-id)]
[list/flat-list
{:key-fn :id
:content-container-style {:padding-vertical 8}
:keyboard-should-persist-taps :always
:data chats
:render-fn channel-preview-item}]))
(defn community [route]
(let [{:keys [community-id]} (get-in route [:route :params])
{:keys [id
chats
name
images
members
permissions
color
joined
can-request-access?
can-join?
requested-to-join-at
admin]
:as community} (<sub [:communities/community community-id])]
[rn/view {:style {:flex 1}}
[topbar/topbar
{:content [toolbar-content id
name
color
images
(not= (:access permissions) constants/community-no-membership-access)
(count members)]
:right-accessories (when (or admin joined)
[{:icon :main-icons/more
:accessibility-label :community-menu-button
:on-press
#(>evt [:bottom-sheet/show-sheet
{:content (fn []
[community-actions community])
:height 256}])}])}]
(if joined
[community-channel-list id]
[community-channel-preview-list id chats])
(when-not joined
(cond
can-join?
[toolbar/toolbar
{:show-border? true
:center [quo/button {:on-press #(>evt [::communities/join id])
:type :secondary}
(i18n/label :t/join)]}]
can-request-access?
(if (and (pos? requested-to-join-at)
(not (can-request-access-again? requested-to-join-at)))
[toolbar/toolbar
{:show-border? true
:left [quo/text {:color :secondary} (i18n/label :t/membership-request-pending)]}]
[toolbar/toolbar
{:show-border? true
:center [quo/button {:on-press #(>evt [::communities/request-to-join id])
:type :secondary}
(i18n/label :t/request-access)]}])
:else
[toolbar/toolbar
{:show-border? true
:center [quo/button {:on-press #(>evt [::communities/join id])
:type :secondary}
(i18n/label :t/follow)]}]))]))

View File

@ -0,0 +1,170 @@
(ns status-im.ui.screens.communities.create
(:require [quo.react-native :as rn]
[status-im.i18n.i18n :as i18n]
[quo.core :as quo]
[clojure.string :as str]
[status-im.utils.handlers :refer [>evt <sub]]
[status-im.communities.core :as communities]
[status-im.ui.components.toolbar :as toolbar]
[status-im.ui.components.topbar :as topbar]
[status-im.utils.image :as utils.image]
[quo.design-system.colors :as colors]
[status-im.ui.components.react :as react]
[status-im.ui.screens.communities.membership :as memberships]
[status-im.ui.components.icons.icons :as icons]))
(def max-name-length 30)
(def max-description-length 140)
(defn valid? [community-name community-description]
(and (not (str/blank? community-name))
(not (str/blank? community-description))
(<= (count community-name) max-name-length)
(<= (count community-description) max-description-length)))
(def crop-size 1000)
(def crop-opts {:cropping true
:cropperCircleOverlay true
:width crop-size
:height crop-size})
(defn pick-pic []
(react/show-image-picker
#(>evt [::communities/create-field :image (.-path ^js %)])
crop-opts))
(defn take-pic []
(react/show-image-picker-camera
#(>evt [::communities/create-field :image (.-path ^js %)])
crop-opts))
(defn bottom-sheet [has-picture]
(fn []
[:<>
[quo/list-item {:accessibility-label :take-photo
:theme :accent
:icon :main-icons/camera
:title (i18n/label :t/community-image-take)
:on-press #(do
(>evt [:bottom-sheet/hide])
(take-pic))}]
[quo/list-item {:accessibility-label :pick-photo
:icon :main-icons/gallery
:theme :accent
:title (i18n/label :t/community-image-pick)
:on-press #(do
(>evt [:bottom-sheet/hide])
(pick-pic))}]
(when has-picture
[quo/list-item {:accessibility-label :remove-photo
:icon :main-icons/delete
:theme :accent
:title (i18n/label :t/community-image-remove)
:on-press #(do
(>evt [:bottom-sheet/hide]))}])]))
(defn photo-picker []
(let [{:keys [image]} (<sub [:communities/create])]
[rn/view {:style {:padding-top 16
:align-items :center}}
[rn/touchable-opacity {:on-press #(>evt [:bottom-sheet/show-sheet
{:content (bottom-sheet (boolean image))}])}
[rn/view {:style {:width 128
:height 128}}
[rn/view {:style {:flex 1
:border-radius 64
:background-color (colors/get-color :ui-01)
:justify-content :center
:align-items :center}}
(if image
[rn/image {:source (utils.image/source image)
:style {:width 128
:height 128
:border-radius 64}
:resize-mode :cover
:accessibility-label :community-image}]
[:<>
[icons/icon :main-icons/photo {:color (colors/get-color :icon-02)}]
[quo/text {:color :secondary}
(i18n/label :t/community-thumbnail-upload)]])]
[rn/view {:style {:position :absolute
:top 0
:right 7}}
[rn/view {:style {:width 40
:height 40
:background-color (colors/get-color :interactive-01)
:border-radius 20
:align-items :center
:justify-content :center
:shadow-offset {:width 0 :height 1}
:shadow-radius 6
:shadow-opacity 1
:shadow-color (colors/get-color :shadow-01)
:elevation 2}}
[icons/icon :main-icons/add {:color colors/white}]]]]]]))
(defn countable-label [{:keys [label value max-length]}]
[rn/view {:style {:padding-bottom 10
:justify-content :space-between
:align-items :flex-end
:flex-direction :row
:flex-wrap :nowrap}}
[quo/text label]
[quo/text {:size :small
:color (if (> (count value) max-length)
:negative
:secondary)}
(str (count value) "/" max-length)]])
(defn form []
(let [{:keys [name description membership]} (<sub [:communities/create])]
[rn/scroll-view {:style {:flex 1}
:content-container-style {:padding-vertical 16}}
[rn/view {:style {:padding-bottom 16
:padding-top 10
:padding-horizontal 16}}
[countable-label {:label (i18n/label :t/name-your-community)
:value name
:max-length max-name-length}]
[quo/text-input
{:placeholder (i18n/label :t/name-your-community-placeholder)
:default-value name
:on-change-text #(>evt [::communities/create-field :name %])
:auto-focus true}]]
[rn/view {:style {:padding-bottom 16
:padding-top 10
:padding-horizontal 16}}
[countable-label {:label (i18n/label :t/give-a-short-description-community)
:value description
:max-length max-description-length}]
[quo/text-input
{:placeholder (i18n/label :t/give-a-short-description-community)
:multiline true
:default-value description
:on-change-text #(>evt [::communities/create-field :description %])}]]
[quo/list-header {:color :main}
(i18n/label :t/community-thumbnail-image)]
[photo-picker]
[:<>
[quo/separator {:style {:margin-vertical 10}}]
[quo/list-item {:title (i18n/label :t/membership-button)
:accessory-text (i18n/label (get-in memberships/options [membership :title] :t/membership-none))
:accessory :text
:on-press #(>evt [:navigate-to :community-membership])
:chevron true
:size :small}]
[quo/list-footer
(i18n/label (get-in memberships/options [membership :description] :t/membership-none-placeholder))]]]))
(defn view []
(let [{:keys [name description]} (<sub [:communities/create])]
[rn/view {:style {:flex 1}}
[topbar/topbar {:title (i18n/label :t/new-community-title)}]
[form]
[toolbar/toolbar
{:show-border? true
:center
[quo/button {:disabled (not (valid? name description))
:type :secondary
:on-press #(>evt [::communities/create-confirmation-pressed])}
(i18n/label :t/create)]}]]))

View File

@ -0,0 +1,36 @@
(ns status-im.ui.screens.communities.create-channel
(:require [clojure.string :as str]
[reagent.core :as reagent]
[quo.react-native :as rn]
[quo.core :as quo]
[status-im.i18n.i18n :as i18n]
[status-im.ui.components.toolbar :as toolbar]
[status-im.utils.handlers :refer [>evt]]
[status-im.communities.core :as communities]
[status-im.ui.components.topbar :as topbar]))
(defn valid? [community-name]
(not (str/blank? community-name)))
(defn create-channel []
(let [channel-name (reagent/atom "")]
(fn []
[:<>
[topbar/topbar {:title (i18n/label :t/create-channel-title)}]
[rn/scroll-view {:style {:flex 1}
:content-container-style {:padding-vertical 16}}
[rn/view {:style {:padding-bottom 16
:padding-top 10
:padding-horizontal 16}}
[quo/text-input
{:label (i18n/label :t/name-your-channel)
:placeholder (i18n/label :t/name-your-channel-placeholder)
:on-change-text #(reset! channel-name %)
:auto-focus true}]]]
[toolbar/toolbar
{:show-border? true
:center
[quo/button {:disabled (not (valid? @channel-name))
:type :secondary
:on-press #(>evt [::communities/create-channel-confirmation-pressed @channel-name])}
(i18n/label :t/create)]}]])))

View File

@ -0,0 +1,21 @@
(ns status-im.ui.screens.communities.edit
(:require [status-im.ui.components.topbar :as topbar]
[quo.core :as quo]
[status-im.i18n.i18n :as i18n]
[status-im.ui.screens.communities.create :as community.create]
[status-im.utils.handlers :refer [>evt <sub]]
[status-im.communities.core :as communities]
[status-im.ui.components.toolbar :as toolbar]))
(defn edit []
(let [{:keys [name description]} (<sub [:communities/create])]
[:<>
[topbar/topbar {:title (i18n/label :t/community-edit-title)}]
[community.create/form]
[toolbar/toolbar
{:show-border? true
:center
[quo/button {:disabled (not (community.create/valid? name description))
:type :secondary
:on-press #(>evt [::communities/edit-confirmation-pressed])}
(i18n/label :t/save)]}]]))

View File

@ -0,0 +1,32 @@
(ns status-im.ui.screens.communities.import
(:require [quo.react-native :as rn]
[reagent.core :as reagent]
[quo.core :as quo]
[status-im.i18n.i18n :as i18n]
[status-im.utils.handlers :refer [>evt]]
[status-im.communities.core :as communities]
[status-im.ui.components.topbar :as topbar]
[status-im.ui.components.toolbar :as toolbar]))
(defn view []
(let [community-key (reagent/atom "")]
(fn []
[rn/view {:style {:flex 1}}
[topbar/topbar {:title (i18n/label :t/import-community-title)}]
[rn/scroll-view {:style {:flex 1}
:content-container-style {:padding 16}}
[rn/view {:style {:padding-bottom 16
:padding-top 10}}
[quo/text-input
{:label (i18n/label :t/community-key)
:placeholder (i18n/label :t/community-key-placeholder)
:on-change-text #(reset! community-key %)
:default-value @community-key
:auto-focus true}]]]
[toolbar/toolbar
{:show-border? true
:center [quo/button {:disabled (= @community-key "")
:type :secondary
:on-press #(>evt [::communities/import @community-key])}
(i18n/label :t/import)]}]])))

View File

@ -0,0 +1,79 @@
(ns status-im.ui.screens.communities.invite
(:require [reagent.core :as reagent]
[quo.react-native :as rn]
[quo.core :as quo]
[status-im.i18n.i18n :as i18n]
[status-im.constants :as constants]
[status-im.ui.components.toolbar :as toolbar]
[status-im.utils.handlers :refer [>evt <sub]]
[status-im.communities.core :as communities]
[status-im.ui.components.topbar :as topbar]
[status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[status-im.multiaccounts.core :as multiaccounts]
[clojure.string :as str]))
(defn header [user-pk]
[:<>
[rn/view {:style {:padding-horizontal 16
:padding-vertical 8}}
[quo/text-input
{:label (i18n/label :t/enter-user-pk)
:placeholder (i18n/label :t/enter-user-pk)
:on-change-text #(reset! user-pk %)
:default-value @user-pk
:auto-focus true}]]
[quo/separator {:style {:margin-vertical 8}}]
[quo/list-header (i18n/label :t/contacts)]])
(defn contacts-list-item [{:keys [public-key active] :as contact} _ _ {:keys [selected]}]
(let [[first-name second-name] (multiaccounts/contact-two-names contact true)]
[quo/list-item
{:title first-name
:subtitle second-name
:icon [chat-icon.screen/contact-icon-contacts-tab
(multiaccounts/displayed-photo contact)]
:accessory :checkbox
:active active
:on-press (fn []
(if active
(swap! selected disj public-key)
(swap! selected conj public-key)))}]))
(defn invite []
(let [user-pk (reagent/atom "")
contacts-selected (reagent/atom #{})]
(fn [route]
(let [contacts-data (<sub [:contacts/active])
invite? (get-in route [:route :params :invite?])
{:keys [permissions
can-manage-users?]} (<sub [:communities/edited-community])
selected @contacts-selected
contacts (map (fn [{:keys [public-key] :as contact}]
(assoc contact :active (contains? selected public-key)))
contacts-data)
;; no-membership communities can only be shared
can-invite? (and can-manage-users?
invite?
(not= (:access permissions) constants/community-no-membership-access))]
[:<>
[topbar/topbar {:title (i18n/label (if can-invite?
:t/community-invite-title
:t/community-share-title))}]
[rn/flat-list {:style {:flex 1}
:content-container-style {:padding-vertical 16}
:header [header user-pk]
:render-data {:selected contacts-selected}
:render-fn contacts-list-item
:key-fn (fn [{:keys [active public-key]}]
(str public-key active))
:data contacts}]
[toolbar/toolbar
{:show-border? true
:center
[quo/button {:disabled (and (str/blank? @user-pk)
(zero? (count selected)))
:type :secondary
:on-press #(>evt [(if can-invite?
::communities/invite-people-confirmation-pressed
::communities/share-community-confirmation-pressed) @user-pk selected])}
(i18n/label (if can-invite? :t/invite :t/share))]}]]))))

View File

@ -0,0 +1,113 @@
(ns status-im.ui.screens.communities.members
(:require [quo.react-native :as rn]
[quo.core :as quo]
[reagent.core :as reagent]
[status-im.constants :as constants]
[status-im.ui.components.react :as react]
[status-im.utils.handlers :refer [<sub >evt]]
[status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.ui.components.unviewed-indicator :as unviewed-indicator]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.components.topbar :as topbar]
[status-im.i18n.i18n :as i18n]
[status-im.communities.core :as communities]))
(defn hide-sheet-and-dispatch [event]
(>evt [:bottom-sheet/hide])
(>evt event))
(defn member-sheet [{:keys [public-key] :as member} community-id can-kick-users?]
[:<>
[quo/list-item
{:theme :accent
:icon [chat-icon/contact-icon-contacts-tab
(multiaccounts/displayed-photo member)]
:title (multiaccounts/displayed-name member)
:subtitle (i18n/label :t/view-profile)
:accessibility-label :view-chat-details-button
:chevron true
:on-press #(hide-sheet-and-dispatch [:chat.ui/show-profile public-key])}]
(when can-kick-users?
[:<>
[quo/separator {:style {:margin-vertical 8}}]
[quo/list-item {:theme :negative
:icon :main-icons/arrow-left
:title (i18n/label :t/member-kick)
:on-press #(>evt [::communities/member-kick community-id public-key])}]
; ban not implemented
#_[quo/list-item {:theme :negative
:icon :main-icons/cancel
:title (i18n/label :t/member-ban)
:on-press #(>evt [::communities/member-ban community-id public-key])}]])])
(defn render-member [public-key _ _ {:keys [community-id
my-public-key
can-kick-users?]}]
(let [{:keys [nickname] :as member} (or (<sub [:contacts/contact-by-identity public-key])
{:public-key public-key})]
[quo/list-item
{:title (if (seq nickname)
nickname
(multiaccounts/displayed-name member))
:accessibility-label :member-item
:icon [chat-icon/contact-icon-contacts-tab
(multiaccounts/displayed-photo member)]
:accessory (when (not= public-key my-public-key)
[quo/button {:on-press #(>evt [:bottom-sheet/show-sheet
{:content (fn [] [member-sheet member community-id can-kick-users?])}])
:type :icon
:theme :icon
:accessibility-label :menu-option}
:main-icons/more])}]))
(defn header [community-id]
[:<>
[quo/list-item {:icon :main-icons/share
:title (i18n/label :t/invite-people)
:accessibility-label :community-invite-people
:theme :accent
:on-press #(>evt [::communities/invite-people-pressed community-id])}]
[quo/separator {:style {:margin-vertical 8}}]])
(defn requests-to-join [community-id]
(let [requests (<sub [:communities/requests-to-join-for-community community-id])
requests-count (count requests)]
[:<>
[quo/list-item {:chevron true
:accessory
[react/view {:flex-direction :row}
(when (pos? requests-count)
[unviewed-indicator/unviewed-indicator requests-count])]
:on-press #(>evt [:navigate-to :community-requests-to-join {:community-id community-id}])
:title (i18n/label :t/membership-requests)}]
[quo/separator {:style {:margin-vertical 8}}]]))
(defn members [route]
(let [{:keys [community-id]} (get-in route [:route :params])
my-public-key (<sub [:multiaccount/public-key])
{:keys [members
permissions
can-manage-users?]} (<sub [:communities/community community-id])]
[:<>
[topbar/topbar {:title (i18n/label :t/community-members-title)
:subtitle (str (count members))}]
[header community-id]
(when (and can-manage-users? (= constants/community-on-request-access (:access permissions)))
[requests-to-join community-id])
[rn/flat-list {:data (keys members)
:render-data {:community-id community-id
:my-public-key my-public-key
:can-kick-users? (and can-manage-users?
(not= (:access permissions)
constants/community-no-membership-access))
:can-manage-users? can-manage-users?}
:key-fn identity
:render-fn render-member}]]))
(defn members-container [route]
(reagent/create-class
{:display-name "community-members-view"
:component-did-mount (fn []
(communities/fetch-requests-to-join! (get-in route [:route :params :community-id])))
:reagent-render members}))

View File

@ -0,0 +1,50 @@
(ns status-im.ui.screens.communities.membership
(:require [quo.react-native :as rn]
[status-im.ui.components.topbar :as topbar]
[status-im.ui.components.toolbar :as toolbar]
[quo.core :as quo]
[status-im.i18n.i18n :as i18n]
[status-im.utils.handlers :refer [>evt <sub]]
[status-im.communities.core :as communities]
[status-im.constants :as constants]))
(def options {constants/community-on-request-access
{:title :t/membership-approval
:description :t/membership-approval-description}
constants/community-invitation-only-access
{:title :t/membership-invite
:description :t/membership-invite-description}
; disabled for now
; constants/community-rule-ens-only
; {:title :t/membership-ens
; :description :t/membership-ens-description}
constants/community-no-membership-access
{:title :t/membership-free
:description :t/membership-free-description}})
(defn option [{:keys [title description]} {:keys [selected on-select]}]
[:<>
[quo/list-item {:title (i18n/label title)
:size :small
:accessory :radio
:active selected
:on-press on-select}]
[quo/list-footer
(i18n/label description)]
[quo/separator {:style {:margin-vertical 8}}]])
(defn membership []
(let [{:keys [membership]} (<sub [:communities/create])]
[:<>
[topbar/topbar {:title (i18n/label :t/membership-title)}]
[rn/scroll-view {}
(doall
(for [[id o] options]
^{:key (str "option-" id)}
[option o {:selected (= id membership)
:on-select #(>evt [::communities/create-field :membership id])}]))]
[toolbar/toolbar
{:show-border? true
:center [quo/button {:type :secondary
:on-press #(>evt [:navigate-back])}
(i18n/label :t/done)]}]]))

View File

@ -0,0 +1,96 @@
(ns status-im.ui.screens.communities.profile
(:require [quo.core :as quo]
[status-im.utils.handlers :refer [>evt <sub]]
[status-im.ui.components.profile-header.view :as profile-header]
[status-im.i18n.i18n :as i18n]
[reagent.core :as reagent]
[status-im.communities.core :as communities]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.react :as react]
[status-im.constants :as constants]
[status-im.react-native.resources :as resources]
[status-im.ui.components.unviewed-indicator :as unviewed-indicator]
[quo.react-native :as rn]))
(defn management [route]
(let [{:keys [community-id]} (get-in route [:route :params])
requests-to-join (<sub [:communities/requests-to-join-for-community community-id])
community (<sub [:communities/community community-id])
{:keys [color
members
permissions
description
name admin]} community
roles false
notifications false
show-members-count? (not= (:access permissions) constants/community-no-membership-access)
members-count (count members)]
[:<>
[quo/animated-header {:left-accessories [{:icon :main-icons/arrow-left
:accessibility-label :back-button
:on-press #(>evt [:navigate-back])}]
:right-accessories [{:icon :main-icons/share
:accessibility-label :invite-button
:on-press #(>evt [::communities/share-community-pressed community-id])}]
:extended-header (profile-header/extended-header
{:title name
:color (or color (rand-nth colors/chat-colors))
:photo (if (= community-id constants/status-community-id)
(:uri
(rn/resolve-asset-source
(resources/get-image :status-logo)))
(get-in community [:images :large :uri]))
:subtitle (when show-members-count? (i18n/label-pluralize members-count :t/community-members {:count members-count}))})
:use-insets true}
[:<>
[quo/list-footer {:color :main}
(get-in description [:identity :description])]
[quo/separator {:style {:margin-vertical 8}}]
(when show-members-count?
[quo/list-item {:chevron true
:accessory
[react/view {:flex-direction :row}
(when (pos? members-count)
[quo/text {:color :secondary} (str members-count)])
[unviewed-indicator/unviewed-indicator (count requests-to-join)]]
:on-press #(>evt [:navigate-to :community-members {:community-id community-id}])
:title (i18n/label :t/members-label)
:icon :main-icons/group-chat}])
(when (and admin roles)
[quo/list-item {:chevron true
:title (i18n/label :t/commonuity-role)
:icon :main-icons/objects}])
(when notifications
[quo/list-item {:chevron true
:title (i18n/label :t/chat-notification-preferences)
:icon :main-icons/notification}])
(when (or show-members-count? notifications (and admin roles))
[quo/separator {:style {:margin-vertical 8}}])
;; Disable as not implemented yet
(when false
[quo/list-item {:theme :accent
:icon :main-icons/edit
:title (i18n/label :t/edit-community)
:on-press #(>evt [::communities/open-edit-community community-id])}])
[quo/list-item {:theme :accent
:icon :main-icons/arrow-left
:title (i18n/label :t/leave-community)
:on-press #(>evt [::communities/leave community-id])}]
;; Disable as not implemented yet
(when false
[quo/list-item {:theme :negative
:icon :main-icons/delete
:title (i18n/label :t/delete)
:on-press #(>evt [::communities/delete-community community-id])}])]]]))
(defn management-container [route]
(reagent/create-class
{:display-name "community-profile-view"
:component-did-mount (fn []
(communities/fetch-requests-to-join! (get-in route [:route :params :community-id])))
:reagent-render management}))

View File

@ -0,0 +1,64 @@
(ns status-im.ui.screens.communities.requests-to-join
(:require [quo.react-native :as rn]
[quo.core :as quo]
[reagent.core :as reagent]
[re-frame.core :as re-frame]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.react :as react]
[status-im.utils.handlers :refer [<sub >evt]]
[status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.topbar :as topbar]
[status-im.i18n.i18n :as i18n]
[status-im.communities.core :as communities]))
(defn hide-sheet-and-dispatch [event]
(>evt [:bottom-sheet/hide])
(>evt event))
(defn request-actions [community-id request-id]
[react/view {:flex-direction :row}
[react/touchable-highlight {:on-press #(re-frame/dispatch [:communities.ui/accept-request-to-join-pressed community-id request-id])}
[icons/icon :main-icons/checkmark-circle {:width 35
:height 35
:color colors/green}]]
[react/touchable-highlight {:on-press #(re-frame/dispatch [:communities.ui/decline-request-to-join-pressed community-id request-id])}
[icons/icon :main-icons/cancel {:width 35
:height 35
:container-style {:padding-left 10}
:color colors/red}]]])
(defn render-request [{:keys [id public-key]} _ _ {:keys [community-id
can-manage-users?]}]
(let [member (or (<sub [:contacts/contact-by-identity public-key])
{:public-key public-key})]
[quo/list-item
{:title (multiaccounts/displayed-name member)
:accessibility-label :member-item
:accessory (when can-manage-users?
[request-actions community-id id])
:icon [chat-icon/contact-icon-contacts-tab
(multiaccounts/displayed-photo member)]}]))
(defn requests-to-join [route]
(fn []
(let [{:keys [community-id]} (get-in route [:route :params])
requests (<sub [:communities/requests-to-join-for-community community-id])
{:keys [can-manage-users?]} (<sub [:communities/community community-id])]
[:<>
[topbar/topbar {:title (i18n/label :t/community-requests-to-join-title)
:subtitle (str (count requests))}]
[rn/flat-list {:data requests
:render-data {:community-id community-id
:can-manage-users? can-manage-users?}
:key-fn :id
:render-fn render-request}]])))
(defn requests-to-join-container [route]
(reagent/create-class
{:display-name "community-requests-to-join-view"
:component-did-mount (fn []
(communities/fetch-requests-to-join! (get-in route [:route :params :community-id])))
:reagent-render requests-to-join}))

View File

@ -1,304 +1,55 @@
(ns status-im.ui.screens.communities.views (:require-macros [status-im.utils.views :as views])
(ns status-im.ui.screens.communities.views
(:require
[reagent.core :as reagent]
[re-frame.core :as re-frame]
[quo.core :as quo]
[status-im.i18n.i18n :as i18n]
[status-im.utils.core :as utils]
[status-im.utils.config :as config]
[status-im.constants :as constants]
[status-im.communities.core :as communities]
[status-im.ui.screens.home.views.inner-item :as inner-item]
[status-im.ui.screens.home.styles :as home.styles]
[status-im.utils.handlers :refer [>evt <sub]]
[status-im.ui.screens.chat.photos :as photos]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.copyable-text :as copyable-text]
[status-im.react-native.resources :as resources]
[status-im.ui.components.topbar :as topbar]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[status-im.ui.components.toolbar :as toolbar]
[status-im.ui.components.react :as react]))
(defn hide-sheet-and-dispatch [event]
(re-frame/dispatch [:bottom-sheet/hide])
(re-frame/dispatch event))
(>evt [:bottom-sheet/hide])
(>evt event))
(defn community-list-item [{:keys [id description]}]
(let [identity (:identity description)]
[quo/list-item
{:icon (if (= id constants/status-community-id)
[react/image {:source (resources/get-image :status-logo)
:style {:width 40
:height 40}}]
[chat-icon.screen/chat-icon-view-chat-list
id
true
(:display-name identity)
;; TODO: should be derived by id
(or (:color identity)
(rand-nth colors/chat-colors))
false
false])
:title [react/view {:flex-direction :row
:flex 1}
[react/view {:flex-direction :row
:flex 1
:padding-right 16
:align-items :center}
[quo/text {:weight :medium
:accessibility-label :community-name-text
:ellipsize-mode :tail
:number-of-lines 1}
(utils/truncate-str (:display-name identity) 30)]]]
:title-accessibility-label :community-name-text
:subtitle [react/view {:flex-direction :row}
[react/view {:flex 1}
[quo/text
(utils/truncate-str (:description identity) 30)]]]
:on-press #(do
(re-frame/dispatch [:dismiss-keyboard])
(re-frame/dispatch [:navigate-to :community id]))}]))
(defn communities-actions []
[react/view
[quo/list-item
{:theme :accent
:title (i18n/label :t/import-community)
:accessibility-label :community-import-community
:icon :main-icons/check
:on-press #(hide-sheet-and-dispatch [::communities/import-pressed])}]
[quo/list-item
{:theme :accent
:title (i18n/label :t/create-community)
:accessibility-label :community-create-community
:icon :main-icons/check
:on-press #(hide-sheet-and-dispatch [::communities/create-pressed])}]])
(views/defview communities []
(views/letsubs [communities [:communities]]
[react/view {:flex 1}
[topbar/topbar (cond-> {:title (i18n/label :t/communities)}
config/communities-management-enabled?
(assoc :right-accessories [{:icon :main-icons/more
:accessibility-label :chat-menu-button
:on-press
#(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (fn []
[communities-actions])
:height 256}])}]))]
[react/scroll-view {:style {:flex 1}
:content-container-style {:padding-vertical 8}}
[list/flat-list
{:key-fn :id
:keyboard-should-persist-taps :always
:data (vals communities)
:render-fn (fn [community] [community-list-item community])}]]
(when config/communities-management-enabled?
[toolbar/toolbar
{:show-border? true
:center [quo/button {:on-press #(re-frame/dispatch [::communities/create-pressed])}
(i18n/label :t/create)]}])]))
(defn valid? [community-name community-description]
(and (not= "" community-name)
(not= "" community-description)))
(defn import-community []
(let [community-key (reagent/atom "")]
(fn []
[react/view {:style {:padding-left 16
:padding-right 8}}
[react/view {:style {:padding-horizontal 20}}
[quo/text-input
{:label (i18n/label :t/community-key)
:placeholder (i18n/label :t/community-key-placeholder)
:on-change-text #(reset! community-key %)
:auto-focus true}]]
[react/view {:style {:padding-top 20
:padding-horizontal 20}}
[quo/button {:disabled (= @community-key "")
:on-press #(re-frame/dispatch [::communities/import-confirmation-pressed @community-key])}
(i18n/label :t/import)]]])))
(defn create []
(let [community-name (reagent/atom "")
membership (reagent/atom 1)
community-description (reagent/atom "")]
(fn []
[react/view {:style {:padding-left 16
:padding-right 8}}
[react/view {:style {:padding-horizontal 20}}
[quo/text-input
{:label (i18n/label :t/name-your-community)
:placeholder (i18n/label :t/name-your-community-placeholder)
:on-change-text #(reset! community-name %)
:auto-focus true}]]
[react/view {:style {:padding-horizontal 20}}
[quo/text-input
{:label (i18n/label :t/give-a-short-description-community)
:placeholder (i18n/label :t/give-a-short-description-community)
:multiline true
:number-of-lines 4
:on-change-text #(reset! community-description %)}]]
[react/view {:style {:padding-horizontal 20}}
[quo/text-input
{:label (i18n/label :t/membership-type)
:placeholder (i18n/label :t/membership-type-placeholder)
:on-change-text #(reset! membership %)}]]
[react/view {:style {:padding-top 20
:padding-horizontal 20}}
[quo/button {:disabled (not (valid? @community-name @community-description))
:on-press #(re-frame/dispatch [::communities/create-confirmation-pressed @community-name @community-description @membership])}
(i18n/label :t/create)]]])))
(def create-sheet
{:content create})
(def import-sheet
{:content import-community})
(defn create-channel []
(let [channel-name (reagent/atom "")
channel-description (reagent/atom "")]
(fn []
[react/view {:style {:padding-left 16
:padding-right 8}}
[react/view {:style {:padding-horizontal 20}}
[quo/text-input
{:label (i18n/label :t/name-your-channel)
:placeholder (i18n/label :t/name-your-channel-placeholder)
:on-change-text #(reset! channel-name %)
:auto-focus true}]]
[react/view {:style {:padding-horizontal 20}}
[quo/text-input
{:label (i18n/label :t/give-a-short-description-channel)
:placeholder (i18n/label :t/give-a-short-description-channel)
:multiline true
:number-of-lines 4
:on-change-text #(reset! channel-description %)}]]
(when config/communities-management-enabled?
[react/view {:style {:padding-top 20
:padding-horizontal 20}}
[quo/button {:disabled (not (valid? @channel-name @channel-description))
:on-press #(re-frame/dispatch [::communities/create-channel-confirmation-pressed @channel-name @channel-description])}
(i18n/label :t/create)]])])))
(def create-channel-sheet
{:content create-channel})
(defn invite-people []
(let [user-pk (reagent/atom "")]
(fn []
[react/view {:style {:padding-left 16
:padding-right 8}}
[react/view {:style {:padding-horizontal 20}}
[quo/text-input
{:label (i18n/label :t/enter-user-pk)
:placeholder (i18n/label :t/enter-user-pk)
:on-change-text #(reset! user-pk %)
:auto-focus true}]]
[react/view {:style {:padding-top 20
:padding-horizontal 20}}
[quo/button {:disabled (= "" user-pk)
:on-press #(re-frame/dispatch [::communities/invite-people-confirmation-pressed @user-pk])}
(i18n/label :t/invite)]]])))
(def invite-people-sheet
{:content invite-people})
(defn community-actions [id admin]
[react/view
(when (and config/communities-management-enabled? admin)
[quo/list-item
{:theme :accent
:title (i18n/label :t/export-key)
:accessibility-label :community-export-key
:icon :main-icons/check
:on-press #(hide-sheet-and-dispatch [::communities/export-pressed id])}])
(when (and config/communities-management-enabled? admin)
[quo/list-item
{:theme :accent
:title (i18n/label :t/create-channel)
:accessibility-label :community-create-channel
:icon :main-icons/check
:on-press #(hide-sheet-and-dispatch [::communities/create-channel-pressed id])}])
(when (and config/communities-management-enabled? admin)
[quo/list-item
{:theme :accent
:title (i18n/label :t/invite-people)
:accessibility-label :community-invite-people
:icon :main-icons/close
:on-press #(re-frame/dispatch [::communities/invite-people-pressed id])}])
[quo/list-item
{:theme :accent
:title (i18n/label :t/leave)
:accessibility-label :leave
:icon :main-icons/close
:on-press #(do
(re-frame/dispatch [:navigate-to :home])
(re-frame/dispatch [:bottom-sheet/hide])
(re-frame/dispatch [::communities/leave id]))}]])
(defn toolbar-content [id display-name color]
[react/view {:style {:flex 1
:align-items :center
:flex-direction :row}}
[react/view {:margin-right 10}
(if (= id constants/status-community-id)
[react/image {:source (resources/get-image :status-logo)
:style {:width 40
:height 40}}]
[chat-icon.screen/chat-icon-view-toolbar
id
true
display-name
(or color
(rand-nth colors/chat-colors))])]
[react/view {:style {:flex 1 :justify-content :center}}
[react/text {:style {:typography :main-medium
:font-size 15
:line-height 22}
:number-of-lines 1
:accessibility-label :community-name-text}
display-name]]])
(defn topbar [id display-name color admin joined]
[topbar/topbar
{:content [toolbar-content id display-name color]
:navigation {:on-press #(re-frame/dispatch [:navigate-back])}
:right-accessories (when (or admin joined)
[{:icon :main-icons/more
:accessibility-label :community-menu-button
:on-press
#(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (fn []
[community-actions id admin])
:height 256}])}])}])
(defn welcome-blank-page []
[react/view {:style {:flex 1 :flex-direction :row :align-items :center :justify-content :center}}
[react/i18n-text {:style home.styles/welcome-blank-text :key :welcome-blank-message}]])
(views/defview community-unviewed-count [id]
(views/letsubs [unviewed-count [:communities/unviewed-count id]]
(when-not (zero? unviewed-count)
(defn community-unviewed-count [id]
(when-not (zero? (<sub [:communities/unviewed-count id]))
[react/view {:style {:background-color colors/blue
:border-radius 6
:margin-right 5
:margin-top 2
:width 12
:height 12}
:accessibility-label :unviewed-messages-public}])))
:accessibility-label :unviewed-messages-public}]))
(defn status-community [{:keys [id description]}]
[quo/list-item
{:icon [react/image {:source (resources/get-image :status-logo)
(defn community-icon [{:keys [id name images color]}]
(let [color (or color (rand-nth colors/chat-colors))
thumbnail-image (get-in images [:thumbnail :uri])]
(cond
(= id constants/status-community-id)
[react/image {:source (resources/get-image :status-logo)
:style {:width 40
:height 40}}]
(seq thumbnail-image)
[photos/photo thumbnail-image {:size 40}]
:else
[chat-icon.screen/chat-icon-view-chat-list
id true name color false false])))
(defn community-home-list-item [{:keys [id name last?] :as community}]
[react/view
[quo/list-item
{:icon [community-icon community]
:title [react/view {:flex-direction :row
:flex 1}
[react/view {:flex-direction :row
@ -310,114 +61,129 @@
:font-size 17
:ellipsize-mode :tail
:number-of-lines 1}
(get-in description [:identity :display-name])]]
name]]
[react/view {:flex-direction :row
:flex 1
:justify-content :flex-end
:align-items :center}
[community-unviewed-count id]]]
:title-accessibility-label :chat-name-text
:on-press #(do
(re-frame/dispatch [:dismiss-keyboard])
(re-frame/dispatch [:navigate-to :community id]))
(>evt [:dismiss-keyboard])
(>evt [:navigate-to :community {:community-id id}]))
;; TODO: actions
:on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet
nil])}])
;; :on-long-press #(>evt [:bottom-sheet/show-sheet
;; nil])
}]
(when last?
[quo/separator])])
(defn channel-preview-item [{:keys [id identity]}]
(defn community-list-item [{:keys [id permissions members name description] :as community}]
(let [members-count (count members)
show-members-count? (not= (:access permissions) constants/community-no-membership-access)]
[quo/list-item
{:icon [chat-icon.screen/chat-icon-view-chat-list
id true (:display-name identity) colors/blue false false]
{:icon [community-icon community]
:title [react/view {:flex-direction :row
:flex 1}
[react/view {:flex-direction :row
:flex 1
:padding-right 16
:align-items :center}
[icons/icon :main-icons/tiny-group
{:color colors/black
:width 15
:height 15
:container-style {:width 15
:height 15
:margin-right 2}}]
[quo/text {:weight :medium
:accessibility-label :chat-name-text
:accessibility-label :community-name-text
:ellipsize-mode :tail
:number-of-lines 1}
(utils/truncate-str (:display-name identity) 30)]]]
:title-accessibility-label :chat-name-text
:subtitle [react/view {:flex-direction :row}
[react/text-class {:style home.styles/last-message-text
:number-of-lines 1
:ellipsize-mode :tail
:accessibility-label :chat-message-text} (:description identity)]]}])
(utils/truncate-str name 30)]]
:title-accessibility-label :community-name-text
:subtitle [react/view
[quo/text {:number-of-lines 1}
description]
[quo/text {:number-of-lines 1
:color :secondary}
(if show-members-count?
(i18n/label-pluralize members-count :t/community-members {:count members-count})
(i18n/label :t/open-membership))]]
:on-press #(do
(>evt [:dismiss-keyboard])
(>evt [:navigate-to :community {:community-id id}]))}]))
(defn community-channel-preview-list [_ description]
(let [chats (reduce-kv
(fn [acc k v]
(conj acc (assoc v :id (name k))))
[]
(get-in description [:chats]))]
(defn communities-actions []
[:<>
[quo/list-item
{:theme :accent
:title (i18n/label :t/import-community)
:accessibility-label :community-import-community
:icon :main-icons/objects
:on-press #(hide-sheet-and-dispatch [:navigate-to :community-import])}]
[quo/list-item
{:theme :accent
:title (i18n/label :t/create-community)
:accessibility-label :community-create-community
:icon :main-icons/add
:on-press #(hide-sheet-and-dispatch [::communities/open-create-community])}]])
(defn communities-home-list [communities]
[list/flat-list
{:key-fn :id
:keyboard-should-persist-taps :always
:data chats
:render-fn channel-preview-item}]))
:data communities
:render-fn community-home-list-item}])
(defn community-chat-list [chats]
(if (empty? chats)
[welcome-blank-page]
[list/flat-list
{:key-fn :chat-id
(defn communities-list [communities]
[list/section-list
{:content-container-style {:padding-vertical 8}
:key-fn :id
:keyboard-should-persist-taps :always
:data chats
:render-fn (fn [home-item] [inner-item/home-list-item (assoc home-item :color colors/blue)])
:footer [react/view {:height 68}]}]))
:sticky-section-headers-enabled false
:sections communities
:render-section-header-fn quo/list-index
:render-fn community-list-item}])
(views/defview community-channel-list [id]
(views/letsubs [chats [:chats/by-community-id id]]
[community-chat-list chats]))
(defn communities []
(let [communities (<sub [:communities/section-list])]
[react/view {:flex 1}
[topbar/topbar (cond-> {:title (i18n/label :t/communities)
:modal? true}
config/communities-management-enabled?
(assoc :right-accessories [{:icon :main-icons/more
:accessibility-label :chat-menu-button
:on-press
#(>evt [:bottom-sheet/show-sheet
{:content (fn []
[communities-actions])
:height 256}])}]))]
[communities-list communities]
(when config/communities-management-enabled?
[toolbar/toolbar
{:show-border? true
:center [quo/button {:on-press #(>evt [::communities/open-create-community])
:type :secondary}
(i18n/label :t/create)]}])]))
(views/defview community [route]
(views/letsubs [{:keys [id description joined admin]} [:communities/community (get-in route [:route :params])]]
[react/view {:style {:flex 1}}
[topbar
id
(get-in description [:identity :display-name])
(get-in description [:identity :color])
admin
joined]
(if joined
[community-channel-list id]
[community-channel-preview-list id description])
(when-not joined
[react/view {:style {:padding-top 20
:margin-bottom 10
:padding-horizontal 20}}
[quo/button {:on-press #(re-frame/dispatch [::communities/join id])}
(i18n/label :t/join)]])]))
(views/defview export-community []
(views/letsubs [{:keys [community-key]} [:popover/popover]]
[react/view {}
[react/view {:style {:padding-top 16 :padding-horizontal 16}}
(defn export-community []
(let [{:keys [community-key]} (<sub [:popover/popover])]
[react/view {:style {:padding-top 16
:padding-horizontal 16}}
[quo/text {:size :x-large
:align :center}
(i18n/label :t/community-private-key)]
[copyable-text/copyable-text-view
{:label :t/community-key
:container-style {:margin-top 12 :margin-bottom 4}
{:container-style {:padding-vertical 12}
:copied-text community-key}
[quo/text {:number-of-lines 1
:ellipsize-mode :middle
:accessibility-label :chat-key
:monospace true}
community-key]]]]))
community-key]]]))
(defn render-featured-community [{:keys [name id]}]
^{:key id}
[react/touchable-highlight {:on-press #(re-frame/dispatch [:navigate-to :community id])
[react/touchable-highlight {:on-press #(>evt [:navigate-to :community {:community-id id}])
:accessibility-label :chat-item}
[react/view {:padding-right 8 :padding-vertical 8}
[react/view {:border-color colors/gray-lighter :border-radius 36 :border-width 1 :padding-horizontal 8 :padding-vertical 5}
[react/text {:style {:color colors/blue :typography :main-medium}} name]]]])
[react/view {:padding-right 8
:padding-vertical 8}
[react/view {:border-color colors/gray-lighter
:border-radius 36
:border-width 1
:padding-horizontal 8
:padding-vertical 5}
[quo/text {:color :link} name]]]])

View File

@ -53,7 +53,7 @@
:title (i18n/label :t/communities-alpha)
:accessibility-label :communities-button
:icon :main-icons/communities
:on-press #(hide-sheet-and-dispatch [:navigate-to :communities])}])
:on-press #(hide-sheet-and-dispatch [:navigate-to :communities {:screen :communities}])}])
[invite/list-item
{:accessibility-label :chats-menu-invite-friends-button}]])

View File

@ -145,15 +145,17 @@
(re-frame/dispatch [:set :public-group-topic nil])
(re-frame/dispatch [:search/home-filter-changed nil]))}])])))
(defn render-fn [home-item]
[inner-item/home-list-item home-item])
(defn render-fn [{:keys [chat-id] :as home-item}]
;; We use `chat-id` to distinguish communities from chats
(if chat-id
[inner-item/home-list-item home-item]
[communities.views/community-home-list-item home-item]))
(defn communities-and-chats [chats status-community loading? search-filter hide-home-tooltip?]
(defn communities-and-chats [items loading? search-filter hide-home-tooltip?]
(if loading?
[react/view {:flex 1 :align-items :center :justify-content :center}
[react/activity-indicator {:animating true}]]
(if (and (empty? chats)
(not status-community)
(if (and (empty? items)
(empty? search-filter)
hide-home-tooltip?
(not @search-active?))
@ -161,33 +163,26 @@
[list/flat-list
{:key-fn :chat-id
:keyboard-should-persist-taps :always
:data chats
:data items
:render-fn render-fn
:header [:<>
(when (or (seq chats) @search-active? (seq search-filter))
[search-input-wrapper search-filter chats])
(when (or (seq items) @search-active? (seq search-filter))
[search-input-wrapper search-filter items])
[referral-item/list-item]
(when (and (empty? chats)
(not status-community)
(when
(and (empty? items)
(or @search-active? (seq search-filter)))
[start-suggestion search-filter])
(when status-community
;; We only support one community now, Status
[communities.views/status-community status-community])
(when (and status-community
(seq chats))
[quo/separator])]
[start-suggestion search-filter])]
:footer (if (and (not hide-home-tooltip?) (not @search-active?))
[home-tooltip-view]
[react/view {:height 68}])}])))
(views/defview chats-list []
(views/letsubs [status-community [:communities/status-community]
loading? [:chats/loading?]
{:keys [chats search-filter]} [:home-items]
(views/letsubs [loading? [:chats/loading?]
{:keys [items
search-filter]} [:home-items]
{:keys [hide-home-tooltip?]} [:multiaccount]]
[react/scroll-view
[communities-and-chats chats status-community loading? search-filter hide-home-tooltip?]]))
[communities-and-chats items loading? search-filter hide-home-tooltip?]))
(views/defview plus-button []
(views/letsubs [logging-in? [:multiaccounts/login]]

View File

@ -5,12 +5,24 @@
[status-im.ui.screens.group.views :as group]
[status-im.ui.screens.referrals.public-chat :as referrals.public-chat]
[status-im.ui.screens.communities.views :as communities]
[status-im.ui.screens.communities.community :as community]
[status-im.ui.screens.communities.create :as communities.create]
[status-im.ui.screens.communities.import :as communities.import]
[status-im.ui.screens.communities.profile :as community.profile]
[status-im.ui.screens.communities.edit :as community.edit]
[status-im.ui.screens.communities.create-channel :as create-channel]
[status-im.ui.screens.communities.membership :as membership]
[status-im.ui.screens.communities.members :as members]
[status-im.ui.screens.communities.requests-to-join :as requests-to-join]
[status-im.ui.screens.communities.invite :as invite]
[status-im.ui.screens.profile.group-chat.views :as profile.group-chat]
[status-im.ui.components.tabbar.styles :as tabbar.styles]
[status-im.ui.screens.stickers.views :as stickers]))
[status-im.ui.screens.stickers.views :as stickers]
[status-im.utils.config :as config]))
(defonce stack (navigation/create-stack))
(defonce group-stack (navigation/create-stack))
(defonce communities-stack (navigation/create-stack))
(defn chat-stack []
[stack {:initial-route-name :home
@ -20,14 +32,6 @@
:component home/home}
{:name :referral-enclav
:component referrals.public-chat/view}
{:name :communities
:transition :presentation-ios
:insets {:bottom true}
:component communities/communities}
{:name :community
:transition :presentation-ios
:insets {:bottom true}
:component communities/community}
{:name :chat
:component chat/chat}
{:name :group-chat-profile
@ -38,7 +42,50 @@
{:name :stickers
:component stickers/packs}
{:name :stickers-pack
:component stickers/pack}]])
:component stickers/pack}
;; Community
{:name :community
:component community/community}
{:name :community-management
:insets {:top false}
:component community.profile/management-container}
{:name :community-members
:component members/members-container}
{:name :community-requests-to-join
:component requests-to-join/requests-to-join-container}
{:name :create-community-channel
:component create-channel/create-channel}
{:name :invite-people-community
:component invite/invite}]])
(defn communities []
[communities-stack {:header-mode :none}
(concat
[{:name :communities
:insets {:bottom true
:top false}
:component communities/communities}
{:name :community-import
:insets {:bottom true
:top false}
:component communities.import/view}
{:name :invite-people-community
:insets {:bottom true
:top false}
:component invite/invite}]
(when config/communities-management-enabled?
[{:name :community-edit
:insets {:bottom true
:top false}
:component community.edit/edit}
{:name :community-create
:insets {:bottom true
:top false}
:component communities.create/view}
{:name :community-membership
:insets {:bottom true
:top false}
:component membership/membership}]))])
(defn new-group-chat []
[group-stack {:header-mode :none

View File

@ -100,6 +100,9 @@
{:name :create-group-chat
:transition :presentation-ios
:component chat-stack/new-group-chat}
{:name :communities
:transition :presentation-ios
:component chat-stack/communities}
{:name :referral-invite
:transition :presentation-ios
:insets {:bottom true}

View File

@ -52,3 +52,7 @@
name
[debug-handlers-names (re-frame/inject-cofx :now) interceptors]
handler)))
(def <sub (comp deref re-frame/subscribe))
(def >evt re-frame/dispatch)

View File

@ -2,7 +2,7 @@
"_comment": "DO NOT EDIT THIS FILE BY HAND. USE 'scripts/update-status-go.sh <tag>' instead",
"owner": "status-im",
"repo": "status-go",
"version": "v0.71.7",
"commit-sha1": "99a304686faadd465a90704b1e228f62ef873df2",
"src-sha256": "1wgwsh140bbha049adrr4jlaxzwmwd7n6a89r2wjpvdydfcpxpiv"
"version": "v0.72.0",
"commit-sha1": "f115b8d289684ac51dec30e5d27f57722c0a6724",
"src-sha256": "0h94ki44by233abwg055nhm4wg7i6f8f8jmndm9813a965x51rgp"
}

View File

@ -146,17 +146,64 @@
"close-app-title": "Warning!",
"command-button-send": "Send",
"communities": "Communities",
"community-members": {
"one": "{{count}} member",
"other": "{{count}} members"
},
"members-label": "Members",
"open-membership": "Open membership",
"member-kick": "Kick member",
"membership-requests": "Membership requests",
"community-members-title": "Members",
"community-requests-to-join-title": "Membership requests",
"name-your-channel": "Name your channel",
"name-your-channel-placeholder": "Channel name",
"give-a-short-description": "Give a short description",
"communities-alpha": "Communities (alpha)",
"communities-verified": "✓ Verified Status Community",
"request-access": "Request access",
"membership-request-pending": "Membership request pending",
"create-community": "Create a community",
"edit-community": "Edit community",
"community-edit-title": "Edit community",
"community-invite-title": "Invite",
"community-share-title": "Share",
"invite": "Invite",
"create-channel": "Create a channel",
"import-community": "Import a community",
"import-community-title": "Import a community",
"name-your-community": "Name your community",
"name-your-community-placeholder": "A catchy name",
"give-a-short-description-community": "Give it a short description",
"new-community-title": "New community",
"membership-title": "Membership requirement",
"create-channel-title": "New channel",
"community-thumbnail-image": "Thumbnail image",
"community-thumbnail-upload": "Upload",
"community-image-take": "Take a photo",
"community-image-pick": "Pick an image",
"community-image-delete": "",
"community-color": "Community colour",
"community-color-placeholder": "Pick a colour",
"membership-button": "Membership requirement",
"membership-none": "None",
"membership-none-placeholder": "You can require new members to meet certain criteria before they can join. This can be changed at any time",
"membership-approval": "Require approval",
"membership-approval-description": "Your community is free to join, but new members are required to be approved by the community creator first",
"membership-invite": "Require invite from another member",
"membership-invite-description": "Your community can only be joined by an invitation from existing community members",
"membership-ens": "Require ENS username",
"membership-ens-description": "Your community requires an ENS username to be able to join",
"membership-free": "No requirement",
"membership-free-description": "Your community is free for anyone to join",
"community-roles": "Roles",
"community-key": "Community private key",
"community-key-placeholder": "Type your community private key",
"leave-community": "Leave community",
"enter-user-pk": "Enter user public key",
"import": "Import",
"complete-hardwallet-setup": "This card is now linked. You need it to sign transactions and unlock your keys",
"chat-notification-preferences": "Notification settings",
"completed": "Completed",
"confirm": "Confirm",
"confirmation-request": "Confirmation request",
@ -479,7 +526,8 @@
"ethereum-node-started-incorrectly-title": "Ethereum node started incorrectly",
"etherscan-lookup": "Look up on Etherscan",
"export-account": "Export account",
"export-key": "Export key",
"export-key": "Export private key",
"community-private-key": "Community private key",
"failed": "Failed",
"faq": "Frequently asked questions",
"fetch-messages": "↓ Fetch messages",
@ -490,6 +538,7 @@
"fleet": "Fleet",
"fleet-settings": "Fleet settings",
"follow-your-interests": "Jump into a public chat and meet new people",
"follow": "Follow",
"free": "↓ Free",
"from": "From",
"gas-limit": "Gas limit",
@ -1177,6 +1226,7 @@
"validation-amount-invalid-number": "Amount is not a valid number",
"validation-amount-is-too-precise": "Amount is too precise. Max number of decimals is {{decimals}}.",
"version": "App version",
"view": "View",
"view-cryptokitties": "View in CryptoKitties",
"view-cryptostrikers": "View in CryptoStrikers",
"view-etheremon": "View in Etheremon",
@ -1215,6 +1265,8 @@
"welcome-to-status": "Welcome to Status!",
"welcome-to-status-description": "Set up your crypto wallet, invite friends to chat and browse decentralized apps",
"welcome-blank-message": "Your chats will appear here. To start new chats press the ⊕ button",
"welcome-community-blank-message": "Your chats will appear here. To start new chats click on the 3 dots above and select \"Create a channel\"",
"welcome-blank-community-message": "Your communities will appear here.",
"seed-phrase-placeholder": "Seed phrase...",
"word-count": "Word count",
"word-n": "Word #{{number}}",