Communities

Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
This commit is contained in:
Andrea Maria Piana 2020-10-20 16:28:52 +02:00
parent 35c2c226df
commit 2ca39daa59
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
42 changed files with 1243 additions and 315 deletions

2
.env
View File

@ -26,3 +26,5 @@ ENABLE_REFERRAL_INVITE=1
ENABLE_QUO_PREVIEW=1 ENABLE_QUO_PREVIEW=1
MAX_IMAGES_BATCH=5 MAX_IMAGES_BATCH=5
APN_TOPIC=im.status.ethereum.pr APN_TOPIC=im.status.ethereum.pr
COMMUNITIES_ENABLED=1
COMMUNITIES_MANAGEMENT_ENABLED=1

View File

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

View File

@ -26,3 +26,5 @@ APN_TOPIC=im.status.ethereum.pr
BLANK_PREVIEW=0 BLANK_PREVIEW=0
MAX_IMAGES_BATCH=5 MAX_IMAGES_BATCH=5
GOOGLE_FREE=0 GOOGLE_FREE=0
COMMUNITIES_ENABLED=1
COMMUNITIES_MANAGEMENT_ENABLED=0

View File

@ -20,3 +20,4 @@ ENABLE_ROOT_ALERT=1
ENABLE_REFERRAL_INVITE=0 ENABLE_REFERRAL_INVITE=0
MAX_IMAGES_BATCH=5 MAX_IMAGES_BATCH=5
BLANK_PREVIEW=0 BLANK_PREVIEW=0
COMMUNITIES_ENABLED=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 841 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -10,6 +10,7 @@
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.mailserver.core :as mailserver] [status-im.mailserver.core :as mailserver]
[status-im.ui.components.colors :as colors] [status-im.ui.components.colors :as colors]
[status-im.constants :as constants]
[status-im.navigation :as navigation] [status-im.navigation :as navigation]
[status-im.utils.clocks :as utils.clocks] [status-im.utils.clocks :as utils.clocks]
[status-im.utils.fx :as fx] [status-im.utils.fx :as fx]
@ -32,6 +33,9 @@
(def one-to-one-chat? (def one-to-one-chat?
(complement multi-user-chat?)) (complement multi-user-chat?))
(defn community-chat? [{:keys [chat-type]}]
(= chat-type constants/community-chat-type))
(defn public-chat? (defn public-chat?
([chat] ([chat]
(:public? chat)) (:public? chat))
@ -94,6 +98,7 @@
{:chat-id chat-id {:chat-id chat-id
:name (or name "") :name (or name "")
:color (rand-nth colors/chat-colors) :color (rand-nth colors/chat-colors)
:chat-type constants/one-to-one-chat-type
:group-chat false :group-chat false
:is-active true :is-active true
:timestamp now :timestamp now
@ -176,6 +181,9 @@
:name topic :name topic
:chat-name (str "#" topic) :chat-name (str "#" topic)
:group-chat true :group-chat true
:chat-type (if timeline?
constants/timeline-chat-type
constants/public-chat-type)
:contacts #{} :contacts #{}
:public? true :public? true
:might-have-join-time-messages? (get-in cofx [:db :multiaccount :use-mailservers?]) :might-have-join-time-messages? (get-in cofx [:db :multiaccount :use-mailservers?])

View File

@ -1,6 +1,7 @@
(ns status-im.chat.models.mentions (ns status-im.chat.models.mentions
(:require [clojure.string :as string] (:require [clojure.string :as string]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[status-im.constants :as constants]
[status-im.utils.fx :as fx] [status-im.utils.fx :as fx]
[status-im.contact.db :as contact.db] [status-im.contact.db :as contact.db]
[status-im.utils.platform :as platform] [status-im.utils.platform :as platform]
@ -172,11 +173,11 @@
(defn get-mentionable-users (defn get-mentionable-users
[{{:keys [current-chat-id] [{{:keys [current-chat-id]
:contacts/keys [contacts] :as db} :db}] :contacts/keys [contacts] :as db} :db}]
(let [{:keys [group-chat public? users] :as chat} (let [{:keys [chat-type users] :as chat}
(get-in db [:chats current-chat-id]) (get-in db [:chats current-chat-id])
chat-specific-suggestions chat-specific-suggestions
(cond (cond
(and group-chat (not public?)) (= chat-type constants/private-group-chat-type)
(let [{:keys [public-key]} (:multiaccount db) (let [{:keys [public-key]} (:multiaccount db)
all-contacts (:contacts/contacts db) all-contacts (:contacts/contacts db)
group-contacts group-contacts

View File

@ -114,20 +114,6 @@
(get-in db [:chats chat-id])] (get-in db [:chats chat-id])]
(>= deleted-at-clock-value clock-value))) (>= deleted-at-clock-value clock-value)))
(defn extract-chat-id
"Validate and return a valid chat-id"
[cofx {:keys [chat-id from message-type]}]
(cond
(and (= constants/message-type-private-group message-type)
(and (get-in cofx [:db :chats chat-id :contacts from])
(get-in cofx [:db :chats chat-id :members-joined (multiaccounts.model/current-public-key cofx)]))) chat-id
(and (= constants/message-type-public-group message-type)
(get-in cofx [:db :chats chat-id :public?])) chat-id
(and (= constants/message-type-one-to-one message-type)
(= (multiaccounts.model/current-public-key cofx) from)) chat-id
(= constants/message-type-private-group-system-message message-type) chat-id
(= constants/message-type-one-to-one message-type) from))
(fx/defn update-unviewed-count (fx/defn update-unviewed-count
[{:keys [db] :as cofx} {:keys [chat-id from message-type message-id new?]}] [{:keys [db] :as cofx} {:keys [chat-id from message-type message-id new?]}]
(when-not (= message-type constants/message-type-private-group-system-message) (when-not (= message-type constants/message-type-private-group-system-message)
@ -159,24 +145,22 @@
(fx/defn receive-one (fx/defn receive-one
{:events [::receive-one]} {:events [::receive-one]}
[{:keys [db] :as cofx} {:keys [message-id] :as message}] [{:keys [db] :as cofx} {:keys [message-id chat-id] :as message}]
(when-let [chat-id (extract-chat-id cofx message)] (fx/merge cofx
(fx/merge cofx ;;If its a profile updates we want to add this message to the timeline as well
;;If its a profile updates we want to add this message to the timeline as well #(when (get-in cofx [:db :chats chat-id :profile-public-key])
#(when (get-in cofx [:db :chats chat-id :profile-public-key]) {:dispatch-n [[::receive-one (assoc message :chat-id chat-model/timeline-chat-id)]]})
{:dispatch-n [[::receive-one (assoc message :chat-id chat-model/timeline-chat-id)]]}) #(when-not (earlier-than-deleted-at? cofx message)
#(let [message-with-chat-id (assoc message :chat-id chat-id)] (if (message-loaded? cofx message)
(when-not (earlier-than-deleted-at? cofx message-with-chat-id) ;; If the message is already loaded, it means it's an update, that
(if (message-loaded? cofx message-with-chat-id) ;; happens when a message that was missing a reply had the reply
;; If the message is already loaded, it means it's an update, that ;; coming through, in which case we just insert the new message
;; happens when a message that was missing a reply had the reply {:db (assoc-in db [:messages chat-id message-id] message)}
;; coming through, in which case we just insert the new message (fx/merge cofx
{:db (assoc-in db [:messages chat-id message-id] message-with-chat-id)} (add-received-message message)
(fx/merge cofx (update-unviewed-count message)
(add-received-message message-with-chat-id) (chat-model/join-time-messages-checked chat-id)
(update-unviewed-count message-with-chat-id) (check-for-incoming-tx message))))))
(chat-model/join-time-messages-checked chat-id)
(check-for-incoming-tx message-with-chat-id))))))))
;;TODO currently we process every message, we need to precess them by batches ;;TODO currently we process every message, we need to precess them by batches
;;or better move processing to status-go ;;or better move processing to status-go

View File

@ -3,11 +3,8 @@
[status-im.chat.models.loading :as chat-loading] [status-im.chat.models.loading :as chat-loading]
[status-im.chat.models.message :as message] [status-im.chat.models.message :as message]
[status-im.chat.models.message-list :as models.message-list] [status-im.chat.models.message-list :as models.message-list]
[status-im.constants :as constants]
[status-im.ui.screens.chat.state :as view.state] [status-im.ui.screens.chat.state :as view.state]
[status-im.utils.datetime :as time] [status-im.utils.datetime :as time]))
[status-im.utils.gfycat.core :as gfycat]
[status-im.utils.identicon :as identicon]))
(deftest add-received-message-test (deftest add-received-message-test
(with-redefs [message/add-message (constantly :added)] (with-redefs [message/add-message (constantly :added)]
@ -154,129 +151,6 @@
:clock-value 0 :clock-value 0
:chat-id "a"})))) :chat-id "a"}))))
(deftest add-own-received-message
(let [db {:multiaccount {:public-key "me"}
:view-id :chat
:loaded-chat-id "chat-id"
:current-chat-id "chat-id"
:messages {"chat-id" {}}
:chats {"chat-id" {}}}]
(testing "a message coming from you!"
(let [actual (message/receive-one {:db db}
{:from "me"
:message-type constants/message-type-one-to-one
:timestamp 0
:whisper-timestamp 0
:message-id "id"
:chat-id "chat-id"
:outgoing true
:content "b"
:clock-value 1})
message (get-in actual [:db :messages "chat-id" "id"])]
(testing "it adds the message"
(is message))))))
(deftest receive-group-chats
(let [cofx {:db {:chats {"chat-id" {:contacts #{"present"}
:members-joined #{"a"}}}
:multiaccount {:public-key "a"}
:loaded-chat-id "chat-id"
:current-chat-id "chat-id"
:view-id :chat}}
cofx-without-member (update-in cofx [:db :chats "chat-id" :members-joined] disj "a")
valid-message {:chat-id "chat-id"
:from "present"
:message-type constants/message-type-private-group
:message-id "1"
:clock-value 1
:whisper-timestamp 0
:timestamp 0}
bad-chat-id-message {:chat-id "bad-chat-id"
:from "present"
:message-type constants/message-type-private-group
:message-id "1"
:clock-value 1
:whisper-timestamp 0
:timestamp 0}
bad-from-message {:chat-id "chat-id"
:from "not-present"
:message-type constants/message-type-private-group
:message-id "1"
:clock-value 1
:whisper-timestamp 0
:timestamp 0}]
(testing "a valid message"
(is (get-in (message/receive-one cofx valid-message) [:db :messages "chat-id" "1"])))
(testing "a message from someone not in the list of participants"
(is (not (message/receive-one cofx bad-from-message))))
(testing "a message with non existing chat-id"
(is (not (message/receive-one cofx bad-chat-id-message))))
(testing "a message from a delete chat"
(is (not (message/receive-one cofx-without-member valid-message))))))
(deftest receive-public-chats
(let [cofx {:db {:chats {"chat-id" {:public? true}}
:multiaccount {:public-key "a"}
:loaded-chat-id "chat-id"
:current-chat-id "chat-id"
:view-id :chat}}
valid-message {:chat-id "chat-id"
:from "anyone"
:message-type constants/message-type-public-group
:message-id "1"
:clock-value 1
:whisper-timestamp 0
:timestamp 0}
bad-chat-id-message {:chat-id "bad-chat-id"
:from "present"
:message-type constants/message-type-public-group
:message-id "1"
:clock-value 1
:whisper-timestamp 0
:timestamp 0}]
(testing "a valid message"
(is (get-in (message/receive-one cofx valid-message) [:db :messages "chat-id" "1"])))
(testing "a message with non existing chat-id"
(is (not (message/receive-one cofx bad-chat-id-message))))))
(deftest receive-one-to-one
(with-redefs [gfycat/generate-gfy (constantly "generated")
identicon/identicon (constantly "generated")]
(let [cofx {:db {:chats {"matching" {}}
:multiaccount {:public-key "me"}
:current-chat-id "matching"
:loaded-chat-id "matching"
:view-id :chat}}
valid-message {:chat-id "matching"
:from "matching"
:message-type constants/message-type-one-to-one
:message-id "1"
:clock-value 1
:whisper-timestamp 0
:timestamp 0}
own-message {:chat-id "matching"
:from "me"
:message-type constants/message-type-one-to-one
:message-id "1"
:clock-value 1
:whisper-timestamp 0
:timestamp 0}
bad-chat-id-message {:chat-id "bad-chat-id"
:from "not-matching"
:message-type constants/message-type-one-to-one
:message-id "1"
:clock-value 1
:whisper-timestamp 0
:timestamp 0}]
(testing "a valid message"
(is (get-in (message/receive-one cofx valid-message) [:db :messages "matching" "1"])))
(testing "our own message"
(is (get-in (message/receive-one cofx own-message) [:db :messages "matching" "1"])))
(testing "a message with non matching chat-id"
(is (not (get-in (message/receive-one cofx bad-chat-id-message) [:db :messages "not-matching" "1"])))))))
(deftest delete-message (deftest delete-message
(with-redefs [time/day-relative (constantly "day-relative") (with-redefs [time/day-relative (constantly "day-relative")
time/timestamp->time (constantly "timestamp")] time/timestamp->time (constantly "timestamp")]

View File

@ -0,0 +1,319 @@
(ns status-im.communities.core
(:require
[re-frame.core :as re-frame]
[clojure.walk :as walk]
[taoensso.timbre :as log]
[status-im.utils.fx :as fx]
[status-im.constants :as constants]
[status-im.chat.models :as models.chat]
[status-im.transport.filters.core :as models.filters]
[status-im.ui.components.bottom-sheet.core :as bottom-sheet]
[status-im.data-store.chats :as data-store.chats]
[status-im.ethereum.json-rpc :as json-rpc]))
(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 <-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)))))
{}
chats))
(defn <-rpc [{:keys [description] :as c}]
(let [identity (:identity description)]
(-> 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))))
(fx/defn handle-chats [cofx chats]
(models.chat/ensure-chats cofx chats))
(fx/defn handle-filters [cofx filters]
(models.filters/handle-filters cofx filters))
(fx/defn handle-removed-filters [cofx filters]
(models.filters/handle-filters-removed cofx (map models.filters/responses->filters filters)))
(fx/defn handle-removed-chats [{:keys [db]} chat-ids]
{:db (reduce (fn [db chat-id]
(update db :chats dissoc chat-id))
db
chat-ids)})
(fx/defn handle-community
[{:keys [db]} {:keys [id] :as community}]
{:db (assoc-in db [:communities id] (<-rpc community))})
(fx/defn handle-fetched
{:events [::fetched]}
[{:keys [db]} communities]
{:db (reduce (fn [db {:keys [id] :as community}]
(assoc-in db [:communities id] (<-rpc community)))
db
communities)})
(fx/defn handle-response [cofx response]
(fx/merge cofx
(handle-removed-chats (:removedChats response))
(handle-chats (map #(-> %
(data-store.chats/<-rpc)
(dissoc :unviewed-messages-count))
(:chats response)))
(handle-fetched (:communities response))
(handle-removed-filters (:removedFilters response))
(handle-filters (:filters response))))
(fx/defn left
{:events [::left]}
[cofx response]
(handle-response cofx response))
(fx/defn joined
{:events [::joined]}
[cofx response]
(handle-response cofx response))
(fx/defn export
[cofx community-id on-success]
{::json-rpc/call [{:method "wakuext_exportCommunity"
:params [community-id]
:on-success on-success
: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]
{::json-rpc/call [{:method "wakuext_importCommunity"
:params [community-key]
:on-success on-success
:on-error #(do
(log/error "failed to import community" %)
(re-frame/dispatch [::failed-to-import %]))}]})
(fx/defn join
{:events [::join]}
[cofx community-id]
{::json-rpc/call [{:method "wakuext_joinCommunity"
:params [community-id]
:on-success #(re-frame/dispatch [::joined %])
:on-error #(do
(log/error "failed to join community" community-id %)
(re-frame/dispatch [::failed-to-join %]))}]})
(fx/defn leave
{:events [::leave]}
[cofx community-id]
{::json-rpc/call [{:method "wakuext_leaveCommunity"
:params [community-id]
:on-success #(re-frame/dispatch [::left %])
:on-error #(do
(log/error "failed to leave community" community-id %)
(re-frame/dispatch [::failed-to-leave %]))}]})
(fx/defn fetch [_]
{::json-rpc/call [{:method "wakuext_communities"
:params []
:on-success #(re-frame/dispatch [::fetched %])
:on-error #(do
(log/error "failed to fetch communities" %)
(re-frame/dispatch [::failed-to-fetch %]))}]})
(fx/defn chat-created
{:events [::chat-created]}
[cofx community-id user-pk]
{::json-rpc/call [{:method "wakuext_sendChatMessage"
:params [{:chatId user-pk
:text "Upgrade here to see an invitation to community"
:communityId community-id
:contentType constants/content-type-community}]
:on-success
#(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 %])
: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]))))
(fx/defn create [{:keys [db]}
community-name
community-description
community-membership
on-success-event
on-failure-event]
(let [membership (js/parseInt community-membership)
my-public-key (get-in db [:multiaccount :public-key])]
{::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 %])
:on-error #(do
(log/error "failed to create community" %)
(re-frame/dispatch [on-failure-event %]))}]}))
(defn create-channel [community-id
community-channel-name
community-channel-description
on-success-event
on-failure-event]
{::json-rpc/call [{:method "wakuext_createCommunityChat"
:params [community-id
{:identity {:display_name community-channel-name
:description community-channel-description}
:permissions {:access access-no-membership}}]
:on-success #(re-frame/dispatch [on-success-event %])
: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)
(defn require-membership? [permissions]
(not= 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])))))))
(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})))
(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})))
(fx/defn community-created
{:events [::community-created]}
[cofx response]
(fx/merge cofx
(bottom-sheet/hide-bottom-sheet)
(handle-response response)))
(fx/defn community-imported
{:events [::community-imported]}
[cofx response]
(fx/merge cofx
(bottom-sheet/hide-bottom-sheet)
(handle-response response)))
(fx/defn people-invited
{:events [::people-invited]}
[cofx response]
(fx/merge cofx
(bottom-sheet/hide-bottom-sheet)
(handle-response response)))
(fx/defn community-channel-created
{:events [::community-channel-created]}
[cofx response]
(fx/merge cofx
(bottom-sheet/hide-bottom-sheet)
(handle-response response)))
(fx/defn handle-export-pressed
{:events [::export-pressed]}
[cofx community-id]
(export cofx community-id
#(re-frame/dispatch [:show-popover {:view :export-community
:community-key %}])))
(fx/defn import-confirmation-pressed
{:events [::import-confirmation-pressed]}
[cofx community-key]
(import-community
cofx
community-key
#(re-frame/dispatch [::community-imported %])))
(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 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))
(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))

View File

@ -14,6 +14,7 @@
(def content-type-system-text 6) (def content-type-system-text 6)
(def content-type-image 7) (def content-type-image 7)
(def content-type-audio 8) (def content-type-audio 8)
(def content-type-community 9)
(def emoji-reaction-love 1) (def emoji-reaction-love 1)
(def emoji-reaction-thumbs-up 2) (def emoji-reaction-thumbs-up 2)
@ -22,6 +23,13 @@
(def emoji-reaction-sad 5) (def emoji-reaction-sad 5)
(def emoji-reaction-angry 6) (def emoji-reaction-angry 6)
(def one-to-one-chat-type 1)
(def public-chat-type 2)
(def private-group-chat-type 3)
(def profile-chat-type 4)
(def timeline-chat-type 5)
(def community-chat-type 6)
(def reactions {emoji-reaction-love (:love resources/reactions) (def reactions {emoji-reaction-love (:love resources/reactions)
emoji-reaction-thumbs-up (:thumbs-up resources/reactions) emoji-reaction-thumbs-up (:thumbs-up resources/reactions)
emoji-reaction-thumbs-down (:thumbs-down resources/reactions) emoji-reaction-thumbs-down (:thumbs-down resources/reactions)
@ -199,3 +207,5 @@
(def faq "https://status.im/faq/") (def faq "https://status.im/faq/")
(def faq-keycard (str faq "#keycard")) (def faq-keycard (str faq "#keycard"))
(def keycard-integration-link "https://status.im/keycard-integration") (def keycard-integration-link "https://status.im/keycard-integration")
(def status-community-id "0x039b2da47552aa117a96ea8f1d4d108ba66637c7517a3c94a57b99dbb8a002eda2")

View File

@ -2,41 +2,41 @@
(:require [clojure.set :as clojure.set] (:require [clojure.set :as clojure.set]
[status-im.data-store.messages :as messages] [status-im.data-store.messages :as messages]
[status-im.ethereum.json-rpc :as json-rpc] [status-im.ethereum.json-rpc :as json-rpc]
[status-im.constants :as constants]
[status-im.utils.fx :as fx] [status-im.utils.fx :as fx]
[taoensso.timbre :as log])) [taoensso.timbre :as log]))
(def one-to-one-chat-type 1) (defn type->rpc [{:keys [chat-type public? group-chat profile-public-key timeline?] :as chat}]
(def public-chat-type 2) (if chat-type
(def private-group-chat-type 3) (assoc chat :chatType chat-type)
(def profile-chat-type 4) (assoc chat :chatType (cond
(def timeline-chat-type 5) profile-public-key constants/profile-chat-type
timeline? constants/timeline-chat-type
(defn type->rpc [{:keys [public? group-chat profile-public-key timeline?] :as chat}] public? constants/public-chat-type
(assoc chat :chatType (cond group-chat constants/private-group-chat-type
profile-public-key profile-chat-type :else constants/one-to-one-chat-type))))
timeline? timeline-chat-type
public? public-chat-type
group-chat private-group-chat-type
:else one-to-one-chat-type)))
(defn rpc->type [{:keys [chatType name] :as chat}] (defn rpc->type [{:keys [chatType name] :as chat}]
(cond (cond
(or (= public-chat-type chatType) (or (= constants/public-chat-type chatType)
(= profile-chat-type chatType) (= constants/profile-chat-type chatType)
(= timeline-chat-type chatType)) (assoc chat (= constants/timeline-chat-type chatType)) (assoc chat
:chat-name (str "#" name) :chat-name (str "#" name)
:public? true :public? true
:group-chat true :group-chat true
:timeline? (= timeline-chat-type chatType)) :timeline? (= constants/timeline-chat-type chatType))
(= private-group-chat-type chatType) (assoc chat (= constants/community-chat-type chatType) (assoc chat
:chat-name name :chat-name name
:public? false :group-chat true)
:group-chat true) (= constants/private-group-chat-type chatType) (assoc chat
:chat-name name
:public? false
:group-chat true)
:else (assoc chat :public? false :group-chat false))) :else (assoc chat :public? false :group-chat false)))
(defn- marshal-members [{:keys [admins contacts members-joined chatType] :as chat}] (defn- marshal-members [{:keys [admins contacts members-joined chat-type] :as chat}]
(cond-> chat (cond-> chat
(= chatType private-group-chat-type) (= chat-type constants/private-group-chat-type)
(assoc :members (map #(hash-map :id % (assoc :members (map #(hash-map :id %
:admin (boolean (admins %)) :admin (boolean (admins %))
:joined (boolean (members-joined %))) contacts)) :joined (boolean (members-joined %))) contacts))
@ -45,23 +45,23 @@
(defn- unmarshal-members [{:keys [members chatType] :as chat}] (defn- unmarshal-members [{:keys [members chatType] :as chat}]
(cond (cond
(= public-chat-type chatType) (assoc chat (= constants/public-chat-type chatType) (assoc chat
:contacts #{} :contacts #{}
:admins #{} :admins #{}
:members-joined #{}) :members-joined #{})
(= private-group-chat-type chatType) (merge chat (= constants/private-group-chat-type chatType) (merge chat
(reduce (fn [acc member] (reduce (fn [acc member]
(cond-> acc (cond-> acc
(:admin member) (:admin member)
(update :admins conj (:id member)) (update :admins conj (:id member))
(:joined member) (:joined member)
(update :members-joined conj (:id member)) (update :members-joined conj (:id member))
:always :always
(update :contacts conj (:id member)))) (update :contacts conj (:id member))))
{:admins #{} {:admins #{}
:members-joined #{} :members-joined #{}
:contacts #{}} :contacts #{}}
members)) members))
:else :else
(assoc chat (assoc chat
:contacts #{(:id chat)} :contacts #{(:id chat)}
@ -70,20 +70,21 @@
(defn- ->rpc [chat] (defn- ->rpc [chat]
(-> chat (-> chat
type->rpc
marshal-members marshal-members
(update :last-message messages/->rpc) (update :last-message messages/->rpc)
type->rpc
(clojure.set/rename-keys {:chat-id :id (clojure.set/rename-keys {:chat-id :id
:membership-update-events :membershipUpdateEvents :membership-update-events :membershipUpdateEvents
:unviewed-messages-count :unviewedMessagesCount :unviewed-messages-count :unviewedMessagesCount
:last-message :lastMessage :last-message :lastMessage
:community-id :communityId
:deleted-at-clock-value :deletedAtClockValue :deleted-at-clock-value :deletedAtClockValue
:is-active :active :is-active :active
:last-clock-value :lastClockValue :last-clock-value :lastClockValue
:profile-public-key :profile}) :profile-public-key :profile})
(dissoc :public? :group-chat :messages (dissoc :public? :group-chat :messages
:might-have-join-time-messages? :might-have-join-time-messages?
:loaded-unviewed-messages-ids :loaded-unviewed-messages-ids :chat-type
:contacts :admins :members-joined))) :contacts :admins :members-joined)))
(defn <-rpc [chat] (defn <-rpc [chat]
@ -91,8 +92,10 @@
rpc->type rpc->type
unmarshal-members unmarshal-members
(clojure.set/rename-keys {:id :chat-id (clojure.set/rename-keys {:id :chat-id
:communityId :community-id
:membershipUpdateEvents :membership-update-events :membershipUpdateEvents :membership-update-events
:deletedAtClockValue :deleted-at-clock-value :deletedAtClockValue :deleted-at-clock-value
:chatType :chat-type
:unviewedMessagesCount :unviewed-messages-count :unviewedMessagesCount :unviewed-messages-count
:lastMessage :last-message :lastMessage :last-message
:active :is-active :active :is-active
@ -100,7 +103,7 @@
:invitationAdmin :invitation-admin :invitationAdmin :invitation-admin
:profile :profile-public-key}) :profile :profile-public-key})
(update :last-message #(when % (messages/<-rpc %))) (update :last-message #(when % (messages/<-rpc %)))
(dissoc :chatType :members))) (dissoc :members)))
(fx/defn save-chat [cofx {:keys [chat-id] :as chat} on-success] (fx/defn save-chat [cofx {:keys [chat-id] :as chat} on-success]
{::json-rpc/call [{:method (json-rpc/call-ext-method "saveChat") {::json-rpc/call [{:method (json-rpc/call-ext-method "saveChat")

View File

@ -8,6 +8,7 @@
:color "color" :color "color"
:contacts #{"a" "b" "c" "d"} :contacts #{"a" "b" "c" "d"}
:last-clock-value 10 :last-clock-value 10
:chat-type 3
:admins #{"a" "b"} :admins #{"a" "b"}
:members-joined #{"a" "c"} :members-joined #{"a" "c"}
:name "name" :name "name"
@ -70,6 +71,7 @@
:color "color" :color "color"
:chat-name "name" :chat-name "name"
:contacts #{"a" "b" "c" "d"} :contacts #{"a" "b" "c" "d"}
:chat-type 3
:last-clock-value 10 :last-clock-value 10
:last-message nil :last-message nil
:admins #{"a" "b"} :admins #{"a" "b"}

View File

@ -11,6 +11,7 @@
:sticker (:sticker content)) :sticker (:sticker content))
:always :always
(clojure.set/rename-keys {:chat-id :chatId (clojure.set/rename-keys {:chat-id :chatId
:community-id :communityId
:clock-value :clock}))) :clock-value :clock})))
(defn <-rpc [message] (defn <-rpc [message]
@ -20,6 +21,7 @@
:commandParameters :command-parameters :commandParameters :command-parameters
:messageType :message-type :messageType :message-type
:localChatId :chat-id :localChatId :chat-id
:communityId :community-id
:contentType :content-type :contentType :content-type
:clock :clock-value :clock :clock-value
:quotedMessage :quoted-message :quotedMessage :quoted-message
@ -27,7 +29,7 @@
:audioDurationMs :audio-duration-ms :audioDurationMs :audio-duration-ms
:new :new?}) :new :new?})
(update :quoted-message clojure.set/rename-keys {:parsedText :parsed-text}) (update :quoted-message clojure.set/rename-keys {:parsedText :parsed-text :communityId :community-id})
(update :outgoing-status keyword) (update :outgoing-status keyword)
(update :command-parameters clojure.set/rename-keys {:transactionHash :transaction-hash (update :command-parameters clojure.set/rename-keys {:transactionHash :transaction-hash
:commandState :command-state}) :commandState :command-state})

View File

@ -115,6 +115,14 @@
"multiaccounts_getIdentityImage" {} "multiaccounts_getIdentityImage" {}
"multiaccounts_storeIdentityImage" {} "multiaccounts_storeIdentityImage" {}
"multiaccounts_deleteIdentityImage" {} "multiaccounts_deleteIdentityImage" {}
"wakuext_createCommunity" {}
"wakuext_createCommunityChat" {}
"wakuext_inviteUserToCommunity" {}
"wakuext_joinCommunity" {}
"wakuext_leaveCommunity" {}
"wakuext_communities" {}
"wakuext_importCommunity" {}
"wakuext_exportCommunity" {}
"status_chats" {} "status_chats" {}
"localnotifications_switchWalletNotifications" {} "localnotifications_switchWalletNotifications" {}
"localnotifications_notificationPreferences" {} "localnotifications_notificationPreferences" {}

View File

@ -5,7 +5,7 @@
(defn button [label accessibility-label handler] (defn button [label accessibility-label handler]
[react/view [react/view
{:style {:width 50 {:style {:width 50
:height 40 :height 30
:justify-content :center :justify-content :center
:align-items :center}} :align-items :center}}
[react/text [react/text
@ -16,7 +16,7 @@
(defn test-menu [] (defn test-menu []
[react/view [react/view
{:style {:position :absolute {:style {:position :absolute
:top 100 :top 70
:right 0 :right 0
:width 50 :width 50
:justify-content :center :justify-content :center

View File

@ -234,12 +234,12 @@
(fx/defn connect-to-mailserver (fx/defn connect-to-mailserver
"Add mailserver as a peer using `::add-peer` cofx and generate sym-key when "Add mailserver as a peer using `::add-peer` cofx and generate sym-key when
it doesn't exists it doesn't exists
Peer summary will change and we will receive a signal from status go when Peer summary will change and we will receive a signal from status go when
this is successful this is successful
A connection-check is made after `connection timeout` is reached and A connection-check is made after `connection timeout` is reached and
mailserver-state is changed to error if it is not connected by then mailserver-state is changed to error if it is not connected by then
No attempt is made if mailserver usage is disabled" No attempt is made if mailserver usage is disabled"
{:events [:mailserver.ui/reconnect-mailserver-pressed]} {:events [:mailserver.ui/reconnect-mailserver-pressed]}
[{:keys [db] :as cofx}] [{:keys [db] :as cofx}]
(let [{:keys [address]} (fetch-current db) (let [{:keys [address]} (fetch-current db)

View File

@ -96,7 +96,7 @@
(fx/defn update-many [cofx mailserver-topics] (fx/defn update-many [cofx mailserver-topics]
(apply fx/merge cofx (map (partial update-topic true) mailserver-topics))) (apply fx/merge cofx (map (partial update-topic true) mailserver-topics)))
(fx/defn delete [{:keys [db] :as cofx} {:keys [chat-id filter-id]}] (fx/defn delete [{:keys [db] :as cofx} {:keys [filter-id]}]
(when-let [matching-topics (filter (fn [{:keys [filter-ids] :as topic}] (when-let [matching-topics (filter (fn [{:keys [filter-ids] :as topic}]
(if (not filter-ids) (if (not filter-ids)
(do (log/warn "topic not initialized, removing" topic) (do (log/warn "topic not initialized, removing" topic)

View File

@ -14,6 +14,7 @@
[status-im.native-module.core :as status] [status-im.native-module.core :as status]
[status-im.notifications.core :as notifications] [status-im.notifications.core :as notifications]
[status-im.popover.core :as popover] [status-im.popover.core :as popover]
[status-im.communities.core :as communities]
[status-im.protocol.core :as protocol] [status-im.protocol.core :as protocol]
[status-im.stickers.core :as stickers] [status-im.stickers.core :as stickers]
[status-im.ui.screens.mobile-network-settings.events :as mobile-network] [status-im.ui.screens.mobile-network-settings.events :as mobile-network]
@ -221,6 +222,7 @@
(protocol/initialize-protocol {:default-mailserver true}) (protocol/initialize-protocol {:default-mailserver true})
(check-network-version network-id) (check-network-version network-id)
(chat.loading/initialize-chats) (chat.loading/initialize-chats)
(communities/fetch)
(contact/initialize-contacts) (contact/initialize-contacts)
(stickers/init-stickers-packs) (stickers/init-stickers-packs)
(mobile-network/on-network-status-change) (mobile-network/on-network-status-change)
@ -293,6 +295,8 @@
:mailserver-ranges {} :mailserver-ranges {}
:mailserver-topics {} :mailserver-topics {}
:default-mailserver true}) :default-mailserver true})
(communities/fetch)
(multiaccounts/switch-preview-privacy-mode-flag) (multiaccounts/switch-preview-privacy-mode-flag)
(link-preview/request-link-preview-whitelist) (link-preview/request-link-preview-whitelist)
(logging/set-log-level (:log-level multiaccount))))) (logging/set-log-level (:log-level multiaccount)))))

View File

@ -14,6 +14,7 @@
[status-im.ethereum.transactions.core :as transactions] [status-im.ethereum.transactions.core :as transactions]
[status-im.fleet.core :as fleet] [status-im.fleet.core :as fleet]
[status-im.group-chats.db :as group-chats.db] [status-im.group-chats.db :as group-chats.db]
[status-im.communities.core :as communities]
[status-im.group-chats.core :as group-chat] [status-im.group-chats.core :as group-chat]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.multiaccounts.core :as multiaccounts] [status-im.multiaccounts.core :as multiaccounts]
@ -217,8 +218,58 @@
(reg-root-key-sub :push-notifications/preferences :push-notifications/preferences) (reg-root-key-sub :push-notifications/preferences :push-notifications/preferences)
(reg-root-key-sub :acquisition :acquisition) (reg-root-key-sub :acquisition :acquisition)
;; communities
(re-frame/reg-sub
:communities
(fn [db]
(cond
config/communities-management-enabled?
(:communities db)
config/communities-enabled?
;; If no management enabled, only return status-community
(select-keys (:communities db) [constants/status-community-id])
:else
[])))
(re-frame/reg-sub
:communities/community
:<- [:communities]
(fn [communities [_ id]]
(get communities id)))
(re-frame/reg-sub
:communities/status-community
:<- [: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))))
(re-frame/reg-sub
:communities/current-community
:<- [:communities]
:<- [:chats/current-raw-chat]
(fn [[communities {:keys [community-id]}]]
(get communities community-id)))
(re-frame/reg-sub
:communities/unviewed-count
(fn [[_ community-id]]
[(re-frame/subscribe [:chats/by-community-id community-id])])
(fn [[chats]]
(reduce (fn [acc {:keys [unviewed-messages-count]}]
(+ acc (or unviewed-messages-count 0)))
0
chats)))
;;GENERAL ============================================================================================================== ;;GENERAL ==============================================================================================================
(re-frame/reg-sub (re-frame/reg-sub
:multiaccount/logged-in? :multiaccount/logged-in?
(fn [db] (fn [db]
@ -616,6 +667,16 @@
(fn [chats [_ chat-id]] (fn [chats [_ chat-id]]
(get chats chat-id))) (get chats chat-id)))
(re-frame/reg-sub
:chats/by-community-id
:<- [:chats/active-chats]
(fn [chats [_ community-id]]
(->> chats
(keep (fn [[_ chat]]
(when (= (:community-id chat) community-id)
chat)))
(sort-by :timestamp >))))
(re-frame/reg-sub (re-frame/reg-sub
:chats/current-chat-ui-props :chats/current-chat-ui-props
:<- [::chat-ui-props] :<- [::chat-ui-props]
@ -678,8 +739,11 @@
:<- [:chats/current-raw-chat] :<- [:chats/current-raw-chat]
:<- [:multiaccount/public-key] :<- [:multiaccount/public-key]
:<- [:inactive-chat-id] :<- [:inactive-chat-id]
:<- [:communities/current-community]
(fn [[{:keys [group-chat] :as current-chat} (fn [[{:keys [group-chat] :as current-chat}
my-public-key inactive-chat-id]] my-public-key
inactive-chat-id
community]]
(when (and current-chat (= (:chat-id current-chat) inactive-chat-id)) (when (and current-chat (= (:chat-id current-chat) inactive-chat-id))
(cond-> current-chat (cond-> current-chat
(chat.models/public-chat? current-chat) (chat.models/public-chat? current-chat)
@ -690,6 +754,10 @@
(assoc :show-input? true (assoc :show-input? true
:joined? true) :joined? true)
(and (chat.models/community-chat? current-chat)
(communities/can-post? community my-public-key (:chat-id current-chat)))
(assoc :show-input? true)
(not group-chat) (not group-chat)
(assoc :show-input? true))))) (assoc :show-input? true)))))
@ -698,7 +766,14 @@
:<- [:chats/current-raw-chat] :<- [:chats/current-raw-chat]
(fn [current-chat] (fn [current-chat]
(select-keys current-chat (select-keys current-chat
[:public? :group-chat :chat-id :chat-name :color :invitation-admin]))) [:community-id
:public?
:group-chat
:chat-type
:chat-id
:chat-name
:color
:invitation-admin])))
(re-frame/reg-sub (re-frame/reg-sub
:current-chat/one-to-one-chat? :current-chat/one-to-one-chat?
@ -2011,7 +2086,10 @@
(vals chats)) (vals chats))
(vals chats))] (vals chats))]
(sort-by :timestamp > filtered-chats)))) (sort-by :timestamp > (filter (fn [{:keys [community-id]}]
;; Ignore communities
(not community-id))
filtered-chats)))))
(defn extract-currency-attributes [currency] (defn extract-currency-attributes [currency]
(let [{:keys [code display-name]} (val currency)] (let [{:keys [code display-name]} (val currency)]

View File

@ -2,6 +2,7 @@
"This namespace is used to handle filters loading and unloading from statusgo" "This namespace is used to handle filters loading and unloading from statusgo"
(:require [clojure.string :as string] (:require [clojure.string :as string]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[status-im.constants :as constants]
[status-im.contact.db :as contact.db] [status-im.contact.db :as contact.db]
[status-im.ethereum.json-rpc :as json-rpc] [status-im.ethereum.json-rpc :as json-rpc]
[status-im.mailserver.core :as mailserver] [status-im.mailserver.core :as mailserver]
@ -65,10 +66,8 @@
(fx/defn upsert-group-chat-topics (fx/defn upsert-group-chat-topics
"Update topics for each member of the group chat" "Update topics for each member of the group chat"
[{:keys [db] :as cofx}] [{:keys [db] :as cofx}]
(let [group-chats (filter (fn [{:keys [group-chat (let [group-chats (filter (fn [{:keys [chat-type]}]
public?]}] (= chat-type constants/private-group-chat-type))
(and group-chat
(not public?)))
(vals (:chats db)))] (vals (:chats db)))]
(apply fx/merge (apply fx/merge
cofx cofx
@ -195,12 +194,12 @@
;; shh filters ;; shh filters
(defn- responses->filters [{:keys [negotiated (defn responses->filters [{:keys [negotiated
discovery discovery
filterId filterId
chatId chatId
topic topic
identity]}] identity]}]
{:chat-id (if (not= identity "") (str "0x" identity) chatId) {:chat-id (if (not= identity "") (str "0x" identity) chatId)
:id chatId :id chatId
:filter-id filterId :filter-id filterId
@ -232,6 +231,9 @@
(mailserver/process-next-messages-request)) (mailserver/process-next-messages-request))
(set-filters-initialized))) (set-filters-initialized)))
(fx/defn handle-filters [cofx filters]
(handle-filters-added cofx (map responses->filters filters)))
(fx/defn handle-filters-removed (fx/defn handle-filters-removed
"Called when we remove a filter from status-go, it will update the mailserver "Called when we remove a filter from status-go, it will update the mailserver
topics" topics"

View File

@ -4,7 +4,9 @@
[status-im.chat.models :as models.chat] [status-im.chat.models :as models.chat]
[status-im.chat.models.reactions :as models.reactions] [status-im.chat.models.reactions :as models.reactions]
[status-im.contact.core :as models.contact] [status-im.contact.core :as models.contact]
[status-im.communities.core :as models.communities]
[status-im.pairing.core :as models.pairing] [status-im.pairing.core :as models.pairing]
[status-im.transport.filters.core :as models.filters]
[status-im.data-store.messages :as data-store.messages] [status-im.data-store.messages :as data-store.messages]
[status-im.data-store.reactions :as data-store.reactions] [status-im.data-store.reactions :as data-store.reactions]
[status-im.data-store.contacts :as data-store.contacts] [status-im.data-store.contacts :as data-store.contacts]
@ -23,20 +25,32 @@
(fx/defn handle-message [cofx message] (fx/defn handle-message [cofx message]
(models.message/receive-one cofx message)) (models.message/receive-one cofx message))
(fx/defn handle-community [cofx community]
(models.communities/handle-community cofx community))
(fx/defn handle-reactions [cofx reactions] (fx/defn handle-reactions [cofx reactions]
(models.reactions/receive-signal cofx reactions)) (models.reactions/receive-signal cofx reactions))
(fx/defn handle-invitations [cofx invitations] (fx/defn handle-invitations [cofx invitations]
(models.group/handle-invitations cofx invitations)) (models.group/handle-invitations cofx invitations))
(fx/defn handle-filters [cofx filters]
(models.filters/handle-filters cofx filters))
(fx/defn handle-filters-removed [cofx filters]
(models.filters/handle-filters-removed cofx filters))
(fx/defn process-response (fx/defn process-response
{:events [::process]} {:events [::process]}
[cofx ^js response-js] [cofx ^js response-js]
(let [^js chats (.-chats response-js) (let [^js communities (.-communities response-js)
^js chats (.-chats response-js)
^js contacts (.-contacts response-js) ^js contacts (.-contacts response-js)
^js installations (.-installations response-js) ^js installations (.-installations response-js)
^js messages (.-messages response-js) ^js messages (.-messages response-js)
^js emoji-reactions (.-emojiReactions response-js) ^js emoji-reactions (.-emojiReactions response-js)
^js filters (.-filters response-js)
^js removed-filters (.-removedFilters response-js)
^js invitations (.-invitations response-js)] ^js invitations (.-invitations response-js)]
(cond (cond
(seq installations) (seq installations)
@ -53,6 +67,11 @@
{:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]} {:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]}
(handle-contacts (map data-store.contacts/<-rpc contacts-clj)))) (handle-contacts (map data-store.contacts/<-rpc contacts-clj))))
(seq communities)
(let [community (.pop communities)]
(fx/merge cofx
{:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]}
(handle-community (types/js->clj community))))
(seq chats) (seq chats)
(let [chats-clj (types/js->clj chats)] (let [chats-clj (types/js->clj chats)]
(js-delete response-js "chats") (js-delete response-js "chats")
@ -81,7 +100,20 @@
(js-delete response-js "invitations") (js-delete response-js "invitations")
(fx/merge cofx (fx/merge cofx
{:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]} {:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]}
(handle-invitations (map data-store.invitations/<-rpc invitations))))))) (handle-invitations (map data-store.invitations/<-rpc invitations))))
(seq filters)
(let [filters (types/js->clj filters)]
(js-delete response-js "filters")
(fx/merge cofx
{:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]}
(handle-filters filters)))
(seq removed-filters)
(let [removed-filters (types/js->clj removed-filters)]
(js-delete response-js "removedFilters")
(fx/merge cofx
{:utils/dispatch-later [{:ms 20 :dispatch [::process response-js]}]}
(handle-filters-removed filters))))))
(fx/defn remove-hash (fx/defn remove-hash
[{:keys [db] :as cofx} envelope-hash] [{:keys [db] :as cofx} envelope-hash]

View File

@ -9,6 +9,7 @@
text text
response-to response-to
ens-name ens-name
community-id
image-path image-path
audio-path audio-path
audio-duration-ms audio-duration-ms
@ -21,6 +22,7 @@
:imagePath image-path :imagePath image-path
:audioPath audio-path :audioPath audio-path
:audioDurationMs audio-duration-ms :audioDurationMs audio-duration-ms
:communityId community-id
:sticker sticker :sticker sticker
:contentType content-type}) :contentType content-type})

View File

@ -24,13 +24,6 @@
(second name) (second name)
(first name)))]])) (first name)))]]))
(defn dapp-badge [{:keys [online-view-wrapper online-view online-dot-left online-dot-right]}]
[react/view online-view-wrapper
[react/view online-view
[react/view
[react/view online-dot-left]
[react/view online-dot-right]]]])
(defn chat-icon-view (defn chat-icon-view
[chat-id group-chat name styles] [chat-id group-chat name styles]
[react/view (:container styles) [react/view (:container styles)
@ -43,10 +36,6 @@
[chat-id group-chat name color] [chat-id group-chat name color]
[chat-icon-view chat-id group-chat name [chat-icon-view chat-id group-chat name
{:container styles/container-chat-toolbar {:container styles/container-chat-toolbar
:online-view-wrapper styles/online-view-wrapper
:online-view styles/online-view
:online-dot-left styles/online-dot-left
:online-dot-right styles/online-dot-right
:size 36 :size 36
:chat-icon styles/chat-icon-chat-toolbar :chat-icon styles/chat-icon-chat-toolbar
:default-chat-icon (styles/default-chat-icon-chat-toolbar color) :default-chat-icon (styles/default-chat-icon-chat-toolbar color)
@ -56,10 +45,6 @@
[chat-id group-chat name color] [chat-id group-chat name color]
[chat-icon-view chat-id group-chat name [chat-icon-view chat-id group-chat name
{:container styles/container-chat-list {:container styles/container-chat-list
:online-view-wrapper styles/online-view-wrapper
:online-view styles/online-view
:online-dot-left styles/online-dot-left
:online-dot-right styles/online-dot-right
:size 40 :size 40
:chat-icon styles/chat-icon-chat-list :chat-icon styles/chat-icon-chat-list
:default-chat-icon (styles/default-chat-icon-chat-list color) :default-chat-icon (styles/default-chat-icon-chat-list color)
@ -69,10 +54,6 @@
[chat-id group-chat name color] [chat-id group-chat name color]
[chat-icon-view chat-id group-chat name [chat-icon-view chat-id group-chat name
{:container styles/container-chat-list {:container styles/container-chat-list
:online-view-wrapper styles/online-view-wrapper
:online-view styles/online-view
:online-dot-left styles/online-dot-left
:online-dot-right styles/online-dot-right
:size 40 :size 40
:chat-icon styles/chat-icon-chat-list :chat-icon styles/chat-icon-chat-list
:default-chat-icon (styles/default-chat-icon-chat-list color) :default-chat-icon (styles/default-chat-icon-chat-list color)
@ -85,13 +66,9 @@
:default-chat-icon-text (styles/default-chat-icon-text (or size 40))}]]) :default-chat-icon-text (styles/default-chat-icon-text (or size 40))}]])
(defn contact-icon-view (defn contact-icon-view
[{:keys [name dapp?] :as contact} {:keys [container] :as styles}] [contact {:keys [container] :as styles}]
[react/view container [react/view container
(if dapp? [photos/photo (multiaccounts/displayed-photo contact) styles]])
[default-chat-icon name styles]
[photos/photo (multiaccounts/displayed-photo contact) styles])
(when dapp?
[dapp-badge styles])])
(defn contact-icon-contacts-tab [photo-path] (defn contact-icon-contacts-tab [photo-path]
[react/view styles/container-chat-list [react/view styles/container-chat-list
@ -100,10 +77,6 @@
(defn dapp-icon-permission [contact size] (defn dapp-icon-permission [contact size]
[contact-icon-view contact [contact-icon-view contact
{:container {:width size :height size} {:container {:width size :height size}
:online-view-wrapper styles/online-view-wrapper
:online-view styles/online-view
:online-dot-left styles/online-dot-left
:online-dot-right styles/online-dot-right
:size size :size size
:chat-icon (styles/custom-size-icon size) :chat-icon (styles/custom-size-icon size)
:default-chat-icon (styles/default-chat-icon-profile colors/default-chat-color size) :default-chat-icon (styles/default-chat-icon-profile colors/default-chat-color size)
@ -118,9 +91,6 @@
(defn profile-icon-view [photo-path name color edit? size override-styles] (defn profile-icon-view [photo-path name color edit? size override-styles]
(let [styles (merge {:container {:width size :height size} (let [styles (merge {:container {:width size :height size}
:online-view styles/online-view-profile
:online-dot-left styles/online-dot-left-profile
:online-dot-right styles/online-dot-right-profile
:size size :size size
:chat-icon styles/chat-icon-profile :chat-icon styles/chat-icon-profile
:default-chat-icon (styles/default-chat-icon-profile color size) :default-chat-icon (styles/default-chat-icon-profile color size)

View File

@ -94,6 +94,7 @@
(def mention-outgoing "#9EE8FA") (def mention-outgoing "#9EE8FA")
(def text black) (def text black)
(def text-gray gray) (def text-gray gray)
(def default-community-color "#773377")
(def default-chat-color "#a187d5") ;; legacy (def default-chat-color "#a187d5") ;; legacy

View File

@ -2,12 +2,12 @@
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[quo.core :as quo] [quo.core :as quo]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.constants :as constants]
[status-im.utils.universal-links.utils :as links] [status-im.utils.universal-links.utils :as links]
[status-im.ui.screens.chat.styles.main :as style] [status-im.ui.screens.chat.styles.main :as style]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.ui.components.list-selection :as list-selection] [status-im.ui.components.list-selection :as list-selection]
[status-im.ui.components.colors :as colors] [status-im.ui.components.colors :as colors]
[status-im.constants :as constants]
[status-im.utils.debounce :as debounce]) [status-im.utils.debounce :as debounce])
(:require-macros [status-im.utils.views :refer [defview letsubs]])) (:require-macros [status-im.utils.views :refer [defview letsubs]]))
@ -83,18 +83,33 @@
:size :small :size :small
:color colors/gray}]]) :color colors/gray}]])
(defview no-messages-group-chat-description-container [chat-id] (defn calculate-quiet-time [highest-request-to
lowest-request-from]
(let [quiet-hours (quot (- highest-request-to lowest-request-from)
(* 60 60))]
(if (<= quiet-hours 24)
(i18n/label :t/quiet-hours
{:quiet-hours quiet-hours})
(i18n/label :t/quiet-days
{:quiet-days (quot quiet-hours 24)}))))
(defview no-messages-community-chat-description-container [chat-id]
(letsubs [{:keys [highest-request-to lowest-request-from]}
[:mailserver/ranges-by-chat-id chat-id]]
[react/text {:style (merge style/intro-header-description
{:margin-bottom 36})}
(let [quiet-time (calculate-quiet-time highest-request-to
lowest-request-from)]
(i18n/label :t/empty-chat-description-community
{:quiet-hours quiet-time}))]))
(defview no-messages-private-group-chat-description-container [chat-id]
(letsubs [{:keys [highest-request-to lowest-request-from]} (letsubs [{:keys [highest-request-to lowest-request-from]}
[:mailserver/ranges-by-chat-id chat-id]] [:mailserver/ranges-by-chat-id chat-id]]
[react/nested-text {:style (merge style/intro-header-description [react/nested-text {:style (merge style/intro-header-description
{:margin-bottom 36})} {:margin-bottom 36})}
(let [quiet-hours (quot (- highest-request-to lowest-request-from) (let [quiet-time (calculate-quiet-time highest-request-to
(* 60 60)) lowest-request-from)]
quiet-time (if (<= quiet-hours 24)
(i18n/label :t/quiet-hours
{:quiet-hours quiet-hours})
(i18n/label :t/quiet-days
{:quiet-days (quot quiet-hours 24)}))]
(i18n/label :t/empty-chat-description-public (i18n/label :t/empty-chat-description-public
{:quiet-hours quiet-time})) {:quiet-hours quiet-time}))
[{:style {:color colors/blue} [{:style {:color colors/blue}
@ -143,20 +158,23 @@
(i18n/label :t/membership-description)]) (i18n/label :t/membership-description)])
(defn group-chat-description-container (defn group-chat-description-container
[{:keys [public? [{:keys [invitation-admin
invitation-admin
chat-id chat-id
chat-name chat-name
chat-type
loading-messages? loading-messages?
no-messages?]}] no-messages?]}]
(cond loading-messages? (cond loading-messages?
group-chat-description-loading group-chat-description-loading
(and no-messages? public?) (and no-messages? (= chat-type constants/public-chat-type))
[no-messages-group-chat-description-container chat-id] [no-messages-private-group-chat-description-container chat-id]
(and no-messages? (= chat-type constants/community-chat-type))
[no-messages-community-chat-description-container chat-id]
invitation-admin invitation-admin
[group-chat-membership-description] [group-chat-membership-description]
(not public?) (= chat-type constants/private-group-chat-type)
[group-chat-inviter-description-container chat-id chat-name])) [group-chat-inviter-description-container chat-id chat-name]))

View File

@ -2,6 +2,9 @@
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.i18n :as i18n] [status-im.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] [status-im.ui.components.colors :as colors]
[status-im.ui.components.icons.vector-icons :as vector-icons] [status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
@ -10,6 +13,7 @@
[status-im.ui.screens.chat.message.command :as message.command] [status-im.ui.screens.chat.message.command :as message.command]
[status-im.ui.screens.chat.photos :as photos] [status-im.ui.screens.chat.photos :as photos]
[status-im.ui.screens.chat.sheets :as sheets] [status-im.ui.screens.chat.sheets :as sheets]
[status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.ui.screens.chat.styles.message.message :as style] [status-im.ui.screens.chat.styles.message.message :as style]
[status-im.ui.screens.chat.utils :as chat.utils] [status-im.ui.screens.chat.utils :as chat.utils]
[status-im.utils.contenthash :as contenthash] [status-im.utils.contenthash :as contenthash]
@ -209,6 +213,64 @@
(letsubs [contact-with-names [:multiaccount/contact]] (letsubs [contact-with-names [:multiaccount/contact]]
(chat.utils/format-author contact-with-names opts))) (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]]
(when (and
config/communities-enabled?
community)
[react/view {:style (assoc (style/message-wrapper message)
:margin-vertical 10
:width 271)}
(when verified
[react/view {:border-right-width 1
:border-left-width 1
:border-top-width 1
:border-left-color colors/gray-lighter
:border-right-color colors/gray-lighter
:border-top-left-radius 10
:border-top-right-radius 10
:padding-vertical 8
:padding-horizontal 15
:border-top-color colors/gray-lighter}
[react/text {:style {:font-size 13
:color colors/blue}} (i18n/label :t/communities-verified)]])
[react/view {:flex-direction :row
:padding-vertical 12
:border-top-left-radius (when-not verified 10)
:border-top-right-radius (when-not verified 10)
:border-right-width 1
:border-left-width 1
:border-top-width 1
:border-color colors/gray-lighter}
[react/view {:width 62
:padding-left 14}
(if (= community-id constants/status-community-id)
[react/image {:source (resources/get-image :status-logo)
:style {:width 40
:height 40}}]
(let [display-name (get-in community [:description :identity :display-name])]
[chat-icon/chat-icon-view-chat-list
display-name
true
display-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])]]]
[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/text {:style {:text-align :center
:color colors/blue}} (if joined (i18n/label :t/leave) (i18n/label :t/join))]]]])))
(defn message-content-wrapper (defn message-content-wrapper
"Author, userpic and delivery wrapper" "Author, userpic and delivery wrapper"
[{:keys [first-in-group? display-photo? display-username? [{:keys [first-in-group? display-photo? display-username?
@ -356,6 +418,10 @@
[collapsible-text-message message on-long-press modal] [collapsible-text-message message on-long-press modal]
reaction-picker]) reaction-picker])
(defmethod ->message constants/content-type-community
[message]
[community-content message])
(defmethod ->message constants/content-type-status (defmethod ->message constants/content-type-status
[{:keys [content content-type] :as message}] [{:keys [content content-type] :as message}]
[message-content-wrapper message [message-content-wrapper message

View File

@ -2,6 +2,7 @@
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.constants :as constants]
[status-im.ui.components.list-selection :as list-selection] [status-im.ui.components.list-selection :as list-selection]
[status-im.utils.universal-links.utils :as universal-links] [status-im.utils.universal-links.utils :as universal-links]
[status-im.ui.components.chat-icon.screen :as chat-icon] [status-im.ui.components.chat-icon.screen :as chat-icon]
@ -78,6 +79,27 @@
:icon :main-icons/delete :icon :main-icons/delete
:on-press #(re-frame/dispatch [:chat.ui/remove-chat-pressed chat-id])}]])) :on-press #(re-frame/dispatch [:chat.ui/remove-chat-pressed chat-id])}]]))
(defn community-chat-accents []
(fn [{:keys [chat-id group-chat chat-name color]}]
[react/view
[quo/list-item
{:theme :accent
:title chat-name
:icon [chat-icon/chat-icon-view-chat-sheet
chat-id group-chat chat-name color]}]
[quo/list-item
{:theme :accent
:title (i18n/label :t/mark-all-read)
:accessibility-label :mark-all-read-button
:icon :main-icons/check
:on-press #(hide-sheet-and-dispatch [:chat.ui/mark-all-read-pressed chat-id])}]
[quo/list-item
{:theme :accent
:title (i18n/label :t/clear-history)
:accessibility-label :clear-history-button
:icon :main-icons/close
:on-press #(re-frame/dispatch [:chat.ui/clear-history-pressed chat-id])}]]))
(defn group-chat-accents [] (defn group-chat-accents []
(fn [{:keys [chat-id group-chat chat-name color invitation-admin]}] (fn [{:keys [chat-id group-chat chat-name color invitation-admin]}]
(let [{:keys [joined?]} @(re-frame/subscribe [:group-chat/inviter-info chat-id])] (let [{:keys [joined?]} @(re-frame/subscribe [:group-chat/inviter-info chat-id])]
@ -117,11 +139,20 @@
:icon :main-icons/arrow-left :icon :main-icons/arrow-left
:on-press #(re-frame/dispatch [:group-chats.ui/leave-chat-pressed chat-id])}])])))) :on-press #(re-frame/dispatch [:group-chats.ui/leave-chat-pressed chat-id])}])]))))
(defn actions [{:keys [public? group-chat] (defn actions [{:keys [chat-type]
:as current-chat}] :as current-chat}]
(cond (cond
public? [public-chat-accents current-chat] (#{constants/public-chat-type
group-chat [group-chat-accents current-chat] constants/profile-chat-type
constants/timeline-chat-type} chat-type)
[public-chat-accents current-chat]
(= chat-type constants/community-chat-type)
[community-chat-accents current-chat]
(= chat-type constants/private-group-chat-type)
[group-chat-accents current-chat]
:else [one-to-one-chat-accents current-chat])) :else [one-to-one-chat-accents current-chat]))
(defn options [chat-id message-id] (defn options [chat-id message-id]

View File

@ -1,5 +1,6 @@
(ns status-im.ui.screens.chat.toolbar-content (ns status-im.ui.screens.chat.toolbar-content
(:require [status-im.i18n :as i18n] (:require [status-im.i18n :as i18n]
[status-im.constants :as constants]
[status-im.ui.components.chat-icon.screen :as chat-icon.screen] [status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.screens.chat.styles.main :as st] [status-im.ui.screens.chat.styles.main :as st]
@ -35,6 +36,7 @@
color color
chat-id chat-id
contacts contacts
chat-type
chat-name chat-name
public?]}] public?]}]
[react/view {:style st/toolbar-container} [react/view {:style st/toolbar-container}
@ -49,6 +51,6 @@
[one-to-one-name chat-id]) [one-to-one-name chat-id])
(when-not group-chat (when-not group-chat
[contact-indicator chat-id]) [contact-indicator chat-id])
(when (and group-chat (not invitation-admin)) (when (and group-chat (not invitation-admin) (not= chat-type constants/community-chat-type))
[group-last-activity {:contacts contacts [group-last-activity {:contacts contacts
:public? public?}])]]) :public? public?}])]])

View File

@ -74,10 +74,10 @@
(defn chat-intro [{:keys [chat-id (defn chat-intro [{:keys [chat-id
chat-name chat-name
chat-type
group-chat group-chat
invitation-admin invitation-admin
contact-name contact-name
public?
color color
loading-messages? loading-messages?
no-messages?]}] no-messages?]}]
@ -99,7 +99,7 @@
:invitation-admin invitation-admin :invitation-admin invitation-admin
:loading-messages? loading-messages? :loading-messages? loading-messages?
:chat-name chat-name :chat-name chat-name
:public? public? :chat-type chat-type
:no-messages? no-messages?}] :no-messages? no-messages?}]
[react/text {:style (assoc style/intro-header-description [react/text {:style (assoc style/intro-header-description
:margin-bottom 32)} :margin-bottom 32)}
@ -115,6 +115,7 @@
(defn chat-intro-header-container (defn chat-intro-header-container
[{:keys [group-chat invitation-admin [{:keys [group-chat invitation-admin
chat-type
might-have-join-time-messages? might-have-join-time-messages?
color chat-id chat-name color chat-id chat-name
public?]} public?]}
@ -128,6 +129,7 @@
{:chat-id chat-id {:chat-id chat-id
:group-chat group-chat :group-chat group-chat
:invitation-admin invitation-admin :invitation-admin invitation-admin
:chat-type chat-type
:chat-name chat-name :chat-name chat-name
:public? public? :public? public?
:color color :color color
@ -170,7 +172,7 @@
(defn messages-view (defn messages-view
[{:keys [chat bottom-space pan-responder space-keeper]}] [{:keys [chat bottom-space pan-responder space-keeper]}]
(let [{:keys [group-chat chat-id public? invitation-admin]} chat (let [{:keys [group-chat chat-id chat-type public? invitation-admin]} chat
messages @(re-frame/subscribe [:chats/current-chat-messages-stream]) messages @(re-frame/subscribe [:chats/current-chat-messages-stream])
no-messages? @(re-frame/subscribe [:chats/current-chat-no-messages?]) no-messages? @(re-frame/subscribe [:chats/current-chat-no-messages?])
@ -180,11 +182,11 @@
pan-responder pan-responder
{:key-fn #(or (:message-id %) (:value %)) {:key-fn #(or (:message-id %) (:value %))
:ref #(reset! messages-list-ref %) :ref #(reset! messages-list-ref %)
:header (when (and group-chat (not public?)) :header (when (= chat-type constants/private-group-chat-type)
[chat.group/group-chat-footer chat-id invitation-admin]) [chat.group/group-chat-footer chat-id invitation-admin])
:footer [:<> :footer [:<>
[chat-intro-header-container chat no-messages?] [chat-intro-header-container chat no-messages?]
(when (and (not group-chat) (not public?)) (when (= chat-type constants/one-to-one-chat-type)
[invite.chat/reward-messages])] [invite.chat/reward-messages])]
:data messages :data messages
:inverted true :inverted true

View File

@ -0,0 +1,423 @@
(ns status-im.ui.screens.communities.views (:require-macros [status-im.utils.views :as views])
(:require
[reagent.core :as reagent]
[re-frame.core :as re-frame]
[quo.core :as quo]
[status-im.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.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.vector-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))
(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)
[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}])))
(defn status-community [{:keys [id description]}]
[quo/list-item
{:icon [react/image {:source (resources/get-image :status-logo)
:style {:width 40
:height 40}}]
: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 :chat-name-text
:font-size 17
:ellipsize-mode :tail
:number-of-lines 1}
(get-in description [:identity :display-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]))
;; TODO: actions
:on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet
nil])}])
(defn channel-preview-item [{:keys [id identity]}]
[quo/list-item
{:icon [chat-icon.screen/chat-icon-view-chat-list
id true (:display-name identity) colors/blue false false]
: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
: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)]]}])
(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]))]
[list/flat-list
{:key-fn :id
:keyboard-should-persist-taps :always
:data chats
:render-fn channel-preview-item}]))
(defn community-chat-list [chats]
(if (empty? chats)
[welcome-blank-page]
[list/flat-list
{:key-fn :chat-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}]}]))
(views/defview community-channel-list [id]
(views/letsubs [chats [:chats/by-community-id id]]
[community-chat-list chats]))
(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}}
[copyable-text/copyable-text-view
{:label :t/community-key
:container-style {:margin-top 12 :margin-bottom 4}
:copied-text community-key}
[quo/text {:number-of-lines 1
:ellipsize-mode :middle
:accessibility-label :chat-key
:monospace true}
community-key]]]]))
(defn render-featured-community [{:keys [name id]}]
^{:key id}
[react/touchable-highlight {:on-press #(re-frame/dispatch [:navigate-to :community 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]]]])

View File

@ -47,6 +47,13 @@
:accessibility-label :join-public-chat-button :accessibility-label :join-public-chat-button
:icon :main-icons/public-chat :icon :main-icons/public-chat
:on-press #(hide-sheet-and-dispatch [:navigate-to :new-public-chat])}] :on-press #(hide-sheet-and-dispatch [:navigate-to :new-public-chat])}]
(when config/communities-enabled?
[quo/list-item
{:theme :accent
:title (i18n/label :t/communities-alpha)
:accessibility-label :communities-button
:icon :main-icons/communities
:on-press #(hide-sheet-and-dispatch [:navigate-to :communities])}])
[invite/list-item [invite/list-item
{:accessibility-label :chats-menu-invite-friends-button}]]) {:accessibility-label :chats-menu-invite-friends-button}]])

View File

@ -3,11 +3,13 @@
[reagent.core :as reagent] [reagent.core :as reagent]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.react-native.resources :as resources] [status-im.react-native.resources :as resources]
[status-im.communities.core :as communities]
[status-im.ui.components.connectivity.view :as connectivity] [status-im.ui.components.connectivity.view :as connectivity]
[status-im.ui.components.icons.vector-icons :as icons] [status-im.ui.components.icons.vector-icons :as icons]
[status-im.ui.components.list.views :as list] [status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.screens.home.styles :as styles] [status-im.ui.screens.home.styles :as styles]
[status-im.ui.screens.communities.views :as communities.views]
[status-im.ui.screens.home.views.inner-item :as inner-item] [status-im.ui.screens.home.views.inner-item :as inner-item]
[status-im.ui.screens.referrals.home-item :as referral-item] [status-im.ui.screens.referrals.home-item :as referral-item]
[status-im.ui.components.colors :as colors] [status-im.ui.components.colors :as colors]
@ -79,7 +81,14 @@
[react/view {:style styles/tags-wrapper} [react/view {:style styles/tags-wrapper}
[react/view {:flex-direction :row :flex-wrap :wrap :justify-content :center} [react/view {:flex-direction :row :flex-wrap :wrap :justify-content :center}
(for [chat (new-public-chat/featured-public-chats)] (for [chat (new-public-chat/featured-public-chats)]
(new-public-chat/render-topic chat))]]]]) (new-public-chat/render-topic chat))]]
[react/i18n-text {:style {:margin-horizontal 16
:text-align :center}
:key :join-a-community}]
[react/view {:style styles/tags-wrapper}
[react/view {:flex-direction :row :flex-wrap :wrap :justify-content :center}
(for [community communities/featured]
(communities.views/render-featured-community community))]]]])
(defn welcome-blank-page [] (defn welcome-blank-page []
[react/view {:style {:flex 1 :flex-direction :row :align-items :center :justify-content :center}} [react/view {:style {:flex 1 :flex-direction :row :align-items :center :justify-content :center}}
@ -136,31 +145,48 @@
(defn render-fn [home-item] (defn render-fn [home-item]
[inner-item/home-list-item home-item]) [inner-item/home-list-item home-item])
(defn communities-and-chats [chats status-community 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)
(empty? search-filter)
hide-home-tooltip?
(not @search-active?))
[welcome-blank-page]
[react/view
[:<>
(when (or (seq chats) @search-active? (seq search-filter))
[search-input-wrapper search-filter chats])
[referral-item/list-item]]
(when
(and (empty? chats)
(not status-community))
(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])
[list/flat-list
{:key-fn :chat-id
:keyboard-should-persist-taps :always
:data chats
:render-fn render-fn
:footer (if (and (not hide-home-tooltip?) (not @search-active?))
[home-tooltip-view]
[react/view {:height 68}])}]])))
(views/defview chats-list [] (views/defview chats-list []
(views/letsubs [loading? [:chats/loading?] (views/letsubs [status-community [:communities/status-community]
loading? [:chats/loading?]
{:keys [chats search-filter]} [:home-items] {:keys [chats search-filter]} [:home-items]
{:keys [hide-home-tooltip?]} [:multiaccount]] {:keys [hide-home-tooltip?]} [:multiaccount]]
(if loading? [react/scroll-view
[react/view {:flex 1 :align-items :center :justify-content :center} [communities-and-chats chats status-community loading? search-filter hide-home-tooltip?]]))
[react/activity-indicator {:animating true}]]
(if (and (empty? chats)
(empty? search-filter)
hide-home-tooltip?
(not @search-active?))
[welcome-blank-page]
[list/flat-list
{:key-fn :chat-id
:keyboard-should-persist-taps :always
:data chats
:render-fn render-fn
:header [:<> (when (or (seq chats) @search-active? (seq search-filter))
[search-input-wrapper search-filter chats])
[referral-item/list-item]]
:empty-component (when (or @search-active? (seq search-filter))
[start-suggestion search-filter])
:footer (if (and (not hide-home-tooltip?) (not @search-active?))
[home-tooltip-view]
[react/view {:height 68}])}]))))
(views/defview plus-button [] (views/defview plus-button []
(views/letsubs [logging-in? [:multiaccounts/login]] (views/letsubs [logging-in? [:multiaccounts/login]]

View File

@ -6,6 +6,7 @@
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[status-im.utils.platform :as platform] [status-im.utils.platform :as platform]
[status-im.ui.screens.wallet.signing-phrase.views :as signing-phrase] [status-im.ui.screens.wallet.signing-phrase.views :as signing-phrase]
[status-im.ui.screens.communities.views :as communities]
[status-im.ui.screens.wallet.request.views :as request] [status-im.ui.screens.wallet.request.views :as request]
[status-im.ui.screens.profile.user.views :as profile.user] [status-im.ui.screens.profile.user.views :as profile.user]
["react-native" :refer (BackHandler)] ["react-native" :refer (BackHandler)]
@ -147,6 +148,9 @@
(= :advertiser-invite view) (= :advertiser-invite view)
[advertiser.invite/accept-popover] [advertiser.invite/accept-popover]
(= :export-community view)
[communities/export-community]
(= :dapp-invite view) (= :dapp-invite view)
[dapp.invite/accept-popover] [dapp.invite/accept-popover]

View File

@ -4,6 +4,7 @@
[status-im.ui.screens.chat.views :as chat] [status-im.ui.screens.chat.views :as chat]
[status-im.ui.screens.group.views :as group] [status-im.ui.screens.group.views :as group]
[status-im.ui.screens.referrals.public-chat :as referrals.public-chat] [status-im.ui.screens.referrals.public-chat :as referrals.public-chat]
[status-im.ui.screens.communities.views :as communities]
[status-im.ui.screens.profile.group-chat.views :as profile.group-chat] [status-im.ui.screens.profile.group-chat.views :as profile.group-chat]
[status-im.ui.components.tabbar.styles :as tabbar.styles] [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]))
@ -19,6 +20,14 @@
:component home/home} :component home/home}
{:name :referral-enclav {:name :referral-enclav
:component referrals.public-chat/view} :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 {:name :chat
:component chat/chat} :component chat/chat}
{:name :group-chat-profile {:name :group-chat-profile

View File

@ -154,7 +154,6 @@
:transition :presentation-ios :transition :presentation-ios
:insets {:bottom true} :insets {:bottom true}
:component contact/profile}] :component contact/profile}]
(when config/quo-preview-enabled? (when config/quo-preview-enabled?
[{:name :quo-preview [{:name :quo-preview
:insets {:top false :bottom false} :insets {:top false :bottom false}

View File

@ -12,6 +12,7 @@
[status-im.ui.screens.routing.main :as routing] [status-im.ui.screens.routing.main :as routing]
[status-im.ui.screens.signing.views :as signing] [status-im.ui.screens.signing.views :as signing]
[status-im.ui.screens.popover.views :as popover] [status-im.ui.screens.popover.views :as popover]
[status-im.ui.screens.communities.views :as communities]
[status-im.ui.screens.multiaccounts.recover.views :as recover.views] [status-im.ui.screens.multiaccounts.recover.views :as recover.views]
[status-im.ui.screens.wallet.send.views :as wallet] [status-im.ui.screens.wallet.send.views :as wallet]
[status-im.ui.components.status-bar.view :as statusbar] [status-im.ui.components.status-bar.view :as statusbar]
@ -49,6 +50,18 @@
(= view :learn-more) (= view :learn-more)
(merge about-app/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) (= view :recover-sheet)
(merge recover.views/bottom-sheet))] (merge recover.views/bottom-sheet))]
[quo/bottom-sheet opts [quo/bottom-sheet opts

View File

@ -46,6 +46,10 @@
(def referrals-invite-enabled? (enabled? (get-config :ENABLE_REFERRAL_INVITE "0"))) (def referrals-invite-enabled? (enabled? (get-config :ENABLE_REFERRAL_INVITE "0")))
(def quo-preview-enabled? (enabled? (get-config :ENABLE_QUO_PREVIEW "0"))) (def quo-preview-enabled? (enabled? (get-config :ENABLE_QUO_PREVIEW "0")))
(def google-free (enabled? (get-config :GOOGLE_FREE "0"))) (def google-free (enabled? (get-config :GOOGLE_FREE "0")))
(def communities-enabled? (enabled? (get-config :COMMUNITIES_ENABLED "0")))
(def communities-management-enabled? (and (enabled? (get-config :COMMUNITIES_MANAGEMENT_ENABLED "0"))
communities-enabled?))
;; CONFIG VALUES ;; CONFIG VALUES
(def log-level (def log-level
(string/upper-case (get-config :LOG_LEVEL ""))) (string/upper-case (get-config :LOG_LEVEL "")))

View File

@ -20,7 +20,8 @@
(when-not (= json "undefined") (when-not (= json "undefined")
(try (try
(js->clj (.parse js/JSON json)) (js->clj (.parse js/JSON json))
(catch js/Error _ (when (string? json) json))))) (catch js/Error _
(when (string? json) json)))))
(def serialize clj->json) (def serialize clj->json)
(defn deserialize [o] (try (json->clj o) (defn deserialize [o] (try (json->clj o)

View File

@ -2,7 +2,7 @@
"_comment": "DO NOT EDIT THIS FILE BY HAND. USE 'scripts/update-status-go.sh <tag>' instead", "_comment": "DO NOT EDIT THIS FILE BY HAND. USE 'scripts/update-status-go.sh <tag>' instead",
"owner": "status-im", "owner": "status-im",
"repo": "status-go", "repo": "status-go",
"version": "v0.66.2", "version": "v0.67.0",
"commit-sha1": "fee08aafbe0cdad352a4bdccff2ec883a3443b9c", "commit-sha1": "f5482ec187b981dd77d184cc7003dcc0c6ae5d22",
"src-sha256": "0q5x70mdya3561dyigmkickhb8hkz92d14cc9hhwmbkpz41bcrd8" "src-sha256": "18bzsf1nskvsvkxxw9v8v4zpgxfaag7q6a07fbdmz7f0y3a3mxjz"
} }

View File

@ -145,6 +145,17 @@
"close-app-content": "The app will stop and close. When you reopen it, the selected network will be used", "close-app-content": "The app will stop and close. When you reopen it, the selected network will be used",
"close-app-title": "Warning!", "close-app-title": "Warning!",
"command-button-send": "Send", "command-button-send": "Send",
"communities": "Communities",
"name-your-channel": "Name your channel",
"give-a-short-description": "Give a short description",
"communities-alpha": "Communities (alpha)",
"communities-verified": "✓ Verified Status Community",
"create-community": "Create a community",
"create-channel": "Create a channel",
"import-community": "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",
"complete-hardwallet-setup": "This card is now linked. You need it to sign transactions and unlock your keys", "complete-hardwallet-setup": "This card is now linked. You need it to sign transactions and unlock your keys",
"completed": "Completed", "completed": "Completed",
"confirm": "Confirm", "confirm": "Confirm",
@ -355,6 +366,7 @@
"empty-chat-description": "There are no messages \nin this chat yet", "empty-chat-description": "There are no messages \nin this chat yet",
"empty-chat-description-one-to-one": "Any messages you send here are encrypted and can only be read by you and ", "empty-chat-description-one-to-one": "Any messages you send here are encrypted and can only be read by you and ",
"empty-chat-description-public": "It's been quiet here for the last {{quiet-hours}}. Start the conversation or ", "empty-chat-description-public": "It's been quiet here for the last {{quiet-hours}}. Start the conversation or ",
"empty-chat-description-community": "It's been quiet here for the last {{quiet-hours}}.",
"empty-chat-description-public-share-this": "share this chat.", "empty-chat-description-public-share-this": "share this chat.",
"enable": "Enable", "enable": "Enable",
"encrypt-with-password": "Encrypt with password", "encrypt-with-password": "Encrypt with password",
@ -466,6 +478,7 @@
"ethereum-node-started-incorrectly-title": "Ethereum node started incorrectly", "ethereum-node-started-incorrectly-title": "Ethereum node started incorrectly",
"etherscan-lookup": "Look up on Etherscan", "etherscan-lookup": "Look up on Etherscan",
"export-account": "Export account", "export-account": "Export account",
"export-key": "Export key",
"failed": "Failed", "failed": "Failed",
"faq": "Frequently asked questions", "faq": "Frequently asked questions",
"fetch-messages": "↓ Fetch messages", "fetch-messages": "↓ Fetch messages",
@ -574,9 +587,11 @@
"invalid-pairing-password": "Invalid pairing password", "invalid-pairing-password": "Invalid pairing password",
"invalid-range": "Invalid format, must be between {{min}} and {{max}}", "invalid-range": "Invalid format, must be between {{min}} and {{max}}",
"join-me": "Hey join me on Status: {{url}}", "join-me": "Hey join me on Status: {{url}}",
"join-a-community": "or join a community",
"http-gateway-error": "Oops, request failed!", "http-gateway-error": "Oops, request failed!",
"sign-request-failed": "Could not sign message", "sign-request-failed": "Could not sign message",
"invite-friends": "Invite friends", "invite-friends": "Invite friends",
"invite-people": "Invite people",
"invite-reward": "Earn crypto for every friend you invite!", "invite-reward": "Earn crypto for every friend you invite!",
"invite-select-account": "Select an account to receive your referral bonus", "invite-select-account": "Select an account to receive your referral bonus",
"invited": "invited", "invited": "invited",
@ -681,6 +696,7 @@
"learn-more": "Learn more", "learn-more": "Learn more",
"learn-more-about-keycard": "Learn more about Keycard", "learn-more-about-keycard": "Learn more about Keycard",
"leave": "Leave", "leave": "Leave",
"joined": "Joined",
"leave-group": "Leave group", "leave-group": "Leave group",
"left": "left", "left": "left",
"lets-go": "Let's go", "lets-go": "Let's go",