Contact refactoring

This commit is contained in:
janherich 2018-04-09 09:59:45 +02:00
parent 067ea99db5
commit f406feacc7
No known key found for this signature in database
GPG Key ID: C23B473AFBE94D13
33 changed files with 667 additions and 1452 deletions

1098
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -57,7 +57,7 @@
"react-native-testfairy": "2.8.0",
"react-native-udp": "2.2.1",
"react-native-webview-bridge": "github:status-im/react-native-webview-bridge#feature/camera-permissions",
"realm": "2.0.7",
"realm": "2.3.3",
"rn-snoopy": "github:status-im/rn-snoopy",
"string_decoder": "0.10.31",
"url": "0.10.3",

View File

@ -13,7 +13,22 @@
"Dentacoin",
"Augur",
"Ethlance",
"Commiteth"
"Commiteth",
"cryptokitties",
"cryptopunks",
"name-bazaar",
"cent",
"erc-dex",
"oasis-direct",
"etherbots",
"peepeth",
"hexel",
"kyber",
"bancor",
"opensea",
"decentraland",
"cryptocribs",
"cryptofighters"
]
},
"state-of-dapps":
@ -26,9 +41,7 @@
[
"flight-delays-suck",
"FirstBlood",
"auction-house",
"gnosis",
"mkr-market"
"auction-house"
]
}
}

View File

@ -26,7 +26,7 @@
"en": "Jarrad"
},
"photo-path": "contacts://jarrad",
"add-chat?": false,
"public-key": "0x0428c9d6c1aaaa8369a7c63819684f30e34396dc0907d49afeac85a0a774ccb919b3482097d992e66bcc538e7a0c6acf874c77748f396f53c0a102e10d1a37765b",
"dapp?": false
},
@ -42,7 +42,6 @@
{
"en": "http://auctionhouse.dappbench.com"
},
"groups": ["state-of-dapps"],
"description": "Decentralized auctions for on-chain assets."
},
@ -58,7 +57,6 @@
{
"en": "https://fdd.etherisc.com"
},
"groups": ["state-of-dapps"],
"description": "Get instant payout in case your flight is late."
},
@ -74,7 +72,6 @@
{
"en": "https://app.firstblood.io"
},
"groups": ["state-of-dapps"],
"description": "ESports for Everyone! A platform that lets players challenge the field and win rewards."
},
@ -88,9 +85,8 @@
"dapp?": true,
"dapp-url":
{
"en": "https://district0x.github.io/ethlance/resources/public/"
"en": "https://district0x.github.io/ethlance/resources/public/"
},
"groups": ["dapps"],
"description": "The future of work is now. Hire people or work yourself in return for ETH."
},
@ -106,121 +102,106 @@
{
"en": "https://www.cryptokitties.co"
},
"groups": ["dapps"],
"description": "Collect and breed adorable digital cats."
},
"cryptopunks": {
"name" : {"en" : "CryptoPunks"},
"description": "10,000 unique collectible punks",
"dapp-url": {"en" :"https://www.larvalabs.com/cryptopunks"},
"groups": ["dapps"],
"dapp?": true,
"photo-path": "contacts://cryptopunks"
},
"name-bazaar": {
"name" : {"en" : "Name Bazaar"},
"description": "ENS name marketplace",
"dapp-url": {"en": "https://namebazaar.io"},
"groups": ["dapps"],
"dapp?": true,
"photo-path": "contacts://name-bazaar"
},
"cent": {
"name" : {"en" : "Cent"},
"description": "Get wisdom, get money",
"dapp-url": {"en" : "https://beta.cent.co"},
"groups": ["dapps"],
"dapp?": true,
"photo-path": "contacts://cent"
},
"erc-dex": {
"name" : {"en" : "ERC dEX"},
"description": "Trustless trading has arrived on Ethereum",
"dapp-url": {"en" : "https://app.ercdex.com/"},
"groups": ["dapps"],
"dapp?": true,
"photo-path": "contacts://erc-dex"
},
"oasis-direct": {
"name" : {"en" : "Oasis Direct"},
"description": "The first decentralized instant exchange",
"dapp-url": {"en" : "https://oasis.direct/"},
"groups": ["dapps"],
"dapp?": true,
"photo-path": "contacts://oasis-direct"
},
"etherbots": {
"name" : {"en" : "Etherbots"},
"description": "Robot wars on the Ethereum Platform",
"dapp-url": {"en" : "https://etherbots.io/"},
"groups": ["dapps"],
"dapp?": true,
"photo-path": "contacts://etherbots"
},
"peepeth": {
"name" : {"en" : "Peepeth"},
"description": "Blockchain-powered microblogging",
"dapp-url": {"en" : "http://peepeth.com/"},
"groups": ["dapps"],
"dapp?": true,
"photo-path": "contacts://peepeth"
},
"hexel": {
"name" : {"en" : "Hexel"},
"description": "Create your own cryptocurrency",
"dapp-url": {"en" : "https://www.onhexel.com/"},
"groups": ["dapps"],
"dapp?": true,
"photo-path": "contacts://hexel"
},
"kyber": {
"name" : {"en" : "Kyber"},
"description": "On-chain, instant and liquid platform for exchange and payment service",
"dapp-url": {"en" : "https://web3.kyber.network"},
"groups": ["dapps"],
"dapp?": true,
"photo-path": "contacts://kyber"
},
"bancor": {
"name" : {"en" : "Bancor"},
"description": "Bancor is a decentralized liquidity network",
"dapp-url": {"en" : "https://www.bancor.network/"},
"groups": ["dapps"],
"dapp?": true,
"photo-path": "contacts://bancor"
},
"opensea": {
"name" : {"en" : "OpenSea"},
"description": "The largest decentralized marketplace for cryptogoods",
"dapp-url": {"en" : "https://opensea.io"},
"groups": ["dapps"],
"dapp?": true,
"photo-path": "contacts://opensea"
},
"decentraland": {
"name" : {"en" : "Decentraland"},
"description": "Decentraland is a virtual reality platform powered by the Ethereum blockchain.",
"dapp-url": {"en" : "https://market.decentraland.org/"},
"groups": ["dapps"],
"dapp?": true,
"photo-path": "contacts://decentraland"
},
"cryptocribs": {
"name" : {"en" : "CryptoCribs"},
"description": "Travel the globe. Pay in crypto.",
"dapp-url": {"en" : "https://cryptocribs.com"},
"groups": ["dapps"],
"dapp?": true,
"photo-path": "contacts://cryptocribs"
},
"cryptofighters": {
"name" : {"en" : "CryptoFighters"},
"description": "Collect train and fight digital fighters.",
"dapp-url": {"en" : "https://cryptofighters.io"},
"groups": ["dapps"],
"dapp?": true,
"photo-path": "contacts://cryptofighters"
}
"cryptopunks": {
"name" : {"en" : "CryptoPunks"},
"description": "10,000 unique collectible punks",
"dapp-url": {"en" :"https://www.larvalabs.com/cryptopunks"},
"dapp?": true,
"photo-path": "contacts://cryptopunks"
},
"name-bazaar": {
"name" : {"en" : "Name Bazaar"},
"description": "ENS name marketplace",
"dapp-url": {"en": "https://namebazaar.io"},
"dapp?": true,
"photo-path": "contacts://name-bazaar"
},
"cent": {
"name" : {"en" : "Cent"},
"description": "Get wisdom, get money",
"dapp-url": {"en" : "https://beta.cent.co"},
"dapp?": true,
"photo-path": "contacts://cent"
},
"erc-dex": {
"name" : {"en" : "ERC dEX"},
"description": "Trustless trading has arrived on Ethereum",
"dapp-url": {"en" : "https://app.ercdex.com/"},
"dapp?": true,
"photo-path": "contacts://erc-dex"
},
"oasis-direct": {
"name" : {"en" : "Oasis Direct"},
"description": "The first decentralized instant exchange",
"dapp-url": {"en" : "https://oasis.direct/"},
"dapp?": true,
"photo-path": "contacts://oasis-direct"
},
"etherbots": {
"name" : {"en" : "Etherbots"},
"description": "Robot wars on the Ethereum Platform",
"dapp-url": {"en" : "https://etherbots.io/"},
"dapp?": true,
"photo-path": "contacts://etherbots"
},
"peepeth": {
"name" : {"en" : "Peepeth"},
"description": "Blockchain-powered microblogging",
"dapp-url": {"en" : "http://peepeth.com/"},
"dapp?": true,
"photo-path": "contacts://peepeth"
},
"hexel": {
"name" : {"en" : "Hexel"},
"description": "Create your own cryptocurrency",
"dapp-url": {"en" : "https://www.onhexel.com/"},
"dapp?": true,
"photo-path": "contacts://hexel"
},
"kyber": {
"name" : {"en" : "Kyber"},
"description": "On-chain, instant and liquid platform for exchange and payment service",
"dapp-url": {"en" : "https://web3.kyber.network"},
"dapp?": true,
"photo-path": "contacts://kyber"
},
"bancor": {
"name" : {"en" : "Bancor"},
"description": "Bancor is a decentralized liquidity network",
"dapp-url": {"en" : "https://www.bancor.network/"},
"dapp?": true,
"photo-path": "contacts://bancor"
},
"opensea": {
"name" : {"en" : "OpenSea"},
"description": "The largest decentralized marketplace for cryptogoods",
"dapp-url": {"en" : "https://opensea.io"},
"dapp?": true,
"photo-path": "contacts://opensea"
},
"decentraland": {
"name" : {"en" : "Decentraland"},
"description": "Decentraland is a virtual reality platform powered by the Ethereum blockchain.",
"dapp-url": {"en" : "https://market.decentraland.org/"},
"dapp?": true,
"photo-path": "contacts://decentraland"
},
"cryptocribs": {
"name" : {"en" : "CryptoCribs"},
"description": "Travel the globe. Pay in crypto.",
"dapp-url": {"en" : "https://cryptocribs.com"},
"dapp?": true,
"photo-path": "contacts://cryptocribs"
},
"cryptofighters": {
"name" : {"en" : "CryptoFighters"},
"description": "Collect train and fight digital fighters.",
"dapp-url": {"en" : "https://cryptofighters.io"},
"dapp?": true,
"photo-path": "contacts://cryptofighters"
}
}

View File

@ -18,18 +18,16 @@
:content-type content-type})
(def chat
{:chat-id constants/console-chat-id
:name (i18n/label :t/status-console)
:color default-chat-color
:group-chat false
:is-active true
:unremovable? true
:timestamp (.getTime (js/Date.))
:photo-path (str "contacts://" constants/console-chat-id)
:contacts [{:identity constants/console-chat-id
:text-color "#FFFFFF"
:background-color "#AB7967"}]
:last-clock-value 0})
{:chat-id constants/console-chat-id
:name (i18n/label :t/status-console)
:color default-chat-color
:group-chat false
:is-active true
:unremovable? true
:timestamp (.getTime (js/Date.))
:photo-path (str "contacts://" constants/console-chat-id)
:contacts [constants/console-chat-id]
:last-clock-value 0})
(def contact
{:whisper-identity constants/console-chat-id

View File

@ -7,10 +7,14 @@
[status-im.chat.models :as models]
[status-im.chat.console :as console]
[status-im.chat.constants :as chat.constants]
[status-im.commands.events.loading :as events.loading]
[status-im.ui.components.list-selection :as list-selection]
[status-im.ui.screens.navigation :as navigation]
[status-im.ui.screens.group.events :as group.events]
[status-im.utils.handlers :as handlers]
[status-im.transport.message.core :as transport]
[status-im.utils.contacts :as utils.contacts]
[status-im.transport.core :as transport]
[status-im.transport.message.core :as transport.message]
[status-im.transport.message.v1.protocol :as protocol]
[status-im.transport.message.v1.public-chat :as public-chat]
[status-im.transport.message.v1.group-chat :as group-chat]
@ -81,36 +85,57 @@
:data-store/update-message (-> (get-in new-db msg-path) (select-keys [:message-id :user-statuses]))})))
(defn init-console-chat
[{:keys [chats] :as db}]
(if (chats constants/console-chat-id)
{:db db}
{:db (-> db
(assoc :current-chat-id constants/console-chat-id)
(update :chats assoc constants/console-chat-id console/chat))
:dispatch [:add-contacts [console/contact]]
:data-store/save-chat console/chat
:data-store/save-contact console/contact}))
[{:keys [db] :as cofx}]
(when-not (get-in db [:chats constants/console-chat-id])
{:db (-> db
(assoc :current-chat-id constants/console-chat-id)
(update :chats assoc constants/console-chat-id console/chat))
:data-store/save-chat console/chat}))
(handlers/register-handler-fx
:init-console-chat
(fn [{:keys [db]} _]
(init-console-chat db)))
(defn- add-default-contacts
[{:keys [db default-contacts] :as cofx}]
(let [new-contacts (-> {}
(into (map (fn [[id props]]
(let [contact-id (name id)]
[contact-id {:whisper-identity contact-id
:address (utils.contacts/public-key->address contact-id)
:name (-> props :name :en)
:photo-path (:photo-path props)
:public-key (:public-key props)
:unremovable? (-> props :unremovable? boolean)
:hide-contact? (-> props :hide-contact? boolean)
:pending? (-> props :pending? boolean)
:dapp? (:dapp? props)
:dapp-url (-> props :dapp-url :en)
:bot-url (:bot-url props)
:description (:description props)}])))
default-contacts)
(assoc constants/console-chat-id console/contact))
existing-contacts (:contacts/contacts db)
contacts-to-add (select-keys new-contacts (set/difference (set (keys new-contacts))
(set (keys existing-contacts))))]
(handlers/merge-fx cofx
{:db (update db :contacts/contacts merge contacts-to-add)
:data-store/save-contacts (vals contacts-to-add)}
(events.loading/load-commands))))
(handlers/register-handler-fx
:initialize-chats
[(re-frame/inject-cofx :data-store/all-chats)
[(re-frame/inject-cofx :get-default-contacts-and-groups)
(re-frame/inject-cofx :data-store/all-chats)
(re-frame/inject-cofx :data-store/inactive-chat-ids)
(re-frame/inject-cofx :data-store/get-messages)
(re-frame/inject-cofx :data-store/unviewed-messages)
(re-frame/inject-cofx :data-store/message-ids)
(re-frame/inject-cofx :data-store/get-unanswered-requests)]
(re-frame/inject-cofx :data-store/get-unanswered-requests)
(re-frame/inject-cofx :data-store/get-local-storage-data)]
(fn [{:keys [db
all-stored-chats
inactive-chat-ids
stored-unanswered-requests
get-stored-messages
stored-unviewed-messages
stored-message-ids]} _]
stored-message-ids] :as cofx} _]
(let [chat->message-id->request (reduce (fn [acc {:keys [chat-id message-id] :as request}]
(assoc-in acc [chat-id message-id] request))
{}
@ -126,11 +151,13 @@
(-> chat-messages keys set))))))
{}
all-stored-chats)]
(-> db
(assoc :chats chats
:deleted-chats inactive-chat-ids)
init-console-chat
(update :dispatch-n conj [:load-default-contacts!])))))
(handlers/merge-fx cofx
{:db (assoc db
:chats chats
:deleted-chats inactive-chat-ids)}
(init-console-chat)
(group.events/add-default-groups)
(add-default-contacts)))))
(handlers/register-handler-fx
:browse-link-from-message
@ -147,7 +174,7 @@
(defn- send-messages-seen [chat-id message-ids {:keys [db] :as cofx}]
(when (and (seq message-ids)
(not (models/bot-only-chat? db chat-id)))
(transport/send (protocol/map->MessagesSeen {:message-ids message-ids}) chat-id cofx)))
(transport.message/send (protocol/map->MessagesSeen {:message-ids message-ids}) chat-id cofx)))
;;TODO (yenda) find a more elegant solution for system messages
(defn- mark-messages-seen
@ -254,11 +281,20 @@
(fn [cofx [chat]]
(models/update-chat chat cofx)))
(defn- remove-transport [chat-id {:keys [db] :as cofx}]
(let [{:keys [group-chat public?]} (get-in db [:chats chat-id])]
;; if this is private group chat, we have to broadcast leave and unsubscribe after that
(if (and group-chat (not public?))
(handlers/merge-fx cofx (transport.message/send (group-chat/GroupLeave.) chat-id))
(handlers/merge-fx cofx (transport/unsubscribe-from-chat chat-id)))))
(handlers/register-handler-fx
:remove-chat
[re-frame/trim-v]
(fn [cofx [chat-id]]
(models/remove-chat chat-id cofx)))
(handlers/merge-fx cofx
(models/remove-chat chat-id)
(remove-transport chat-id))))
(handlers/register-handler-fx
:remove-chat-and-navigate-home
@ -266,6 +302,7 @@
(fn [cofx [chat-id]]
(handlers/merge-fx cofx
(models/remove-chat chat-id)
(remove-transport chat-id)
(navigation/replace-view :home))))
(handlers/register-handler-fx
@ -312,28 +349,16 @@
(models/add-group-chat random-id chat-name (:current-public-key db) selected-contacts)
(navigation/navigate-to-clean :home)
(navigate-to-chat random-id {})
(transport/send (group-chat/GroupAdminUpdate. chat-name selected-contacts) random-id)))))
(defn- broadcast-leave [{:keys [public? chat-id]} cofx]
(when-not public?
(transport/send (group-chat/GroupLeave.) chat-id cofx)))
(handlers/register-handler-fx
:leave-group-chat
;; stop listening to group here
(fn [{{:keys [current-chat-id chats] :as db} :db :as cofx} _]
(handlers/merge-fx cofx
(models/remove-chat current-chat-id)
(navigation/replace-view :home)
(broadcast-leave (get chats current-chat-id)))))
(transport.message/send (group-chat/GroupAdminUpdate. chat-name selected-contacts) random-id)))))
(handlers/register-handler-fx
:leave-group-chat?
(fn [_ _]
[re-frame/trim-v]
(fn [_ [chat-id]]
{:show-confirmation {:title (i18n/label :t/leave-confirmation)
:content (i18n/label :t/leave-group-chat-confirmation)
:confirm-button-text (i18n/label :t/leave)
:on-accept #(re-frame/dispatch [:leave-group-chat])}}))
:on-accept #(re-frame/dispatch [:remove-chat-and-navigate-home chat-id])}}))
(handlers/register-handler-fx
:show-profile

View File

@ -1,8 +1,7 @@
(ns status-im.chat.models
(:require [status-im.ui.components.styles :as styles]
[status-im.utils.gfycat.core :as gfycat]
[status-im.utils.handlers :as handlers]
[status-im.transport.core :as transport]))
[status-im.utils.handlers :as handlers]))
(defn set-chat-ui-props
"Updates ui-props in active chat by merging provided kvs into them"
@ -17,14 +16,14 @@
(defn- create-new-chat
[chat-id {:keys [db now]}]
(let [name (get-in db [:contacts/contacts chat-id :name])]
{:chat-id chat-id
:name (or name (gfycat/generate-gfy chat-id))
:color styles/default-chat-color
:group-chat false
:is-active true
:timestamp now
:contacts [{:identity chat-id}]
:last-clock-value 0}))
{:chat-id chat-id
:name (or name (gfycat/generate-gfy chat-id))
:color styles/default-chat-color
:group-chat false
:is-active true
:timestamp now
:contacts [chat-id]
:last-clock-value 0}))
(defn add-chat
"Adds new chat to db & realm, if the chat with same id already exists, justs restores it"
@ -36,38 +35,38 @@
(assoc (get-stored-chat chat-id) :is-active true)
(create-new-chat chat-id cofx))
chat-props)]
{:db (-> db
(update :chats assoc chat-id new-chat)
(update :deleted-chats (fnil disj #{}) chat-id))
{:db (-> db
(update :chats assoc chat-id new-chat)
(update :deleted-chats (fnil disj #{}) chat-id))
:data-store/save-chat new-chat})))
(defn add-public-chat
"Adds new public group chat to db & realm"
[topic {:keys [db now] :as cofx}]
(let [chat {:chat-id topic
:name topic
:color styles/default-chat-color
:group-chat true
:public? true
:is-active true
:timestamp now
:last-clock-value 0}]
{:db (assoc-in db [:chats topic] chat)
(let [chat {:chat-id topic
:name topic
:color styles/default-chat-color
:group-chat true
:public? true
:is-active true
:timestamp now
:last-clock-value 0}]
{:db (assoc-in db [:chats topic] chat)
:data-store/save-chat chat}))
(defn add-group-chat
"Adds new private group chat to db & realm"
[chat-id chat-name admin participants {:keys [db now] :as cofx}]
(let [chat {:chat-id chat-id
:name chat-name
:color styles/default-chat-color
:group-chat true
:group-admin admin
:is-active true
:timestamp now
:contacts (mapv (partial hash-map :identity) participants)
:last-clock-value 0}]
{:db (assoc-in db [:chats chat-id] chat)
(let [chat {:chat-id chat-id
:name chat-name
:color styles/default-chat-color
:group-chat true
:group-admin admin
:is-active true
:timestamp now
:contacts participants
:last-clock-value 0}]
{:db (assoc-in db [:chats chat-id] chat)
:data-store/save-chat chat}))
;; TODO (yenda): there should be an option to update the timestamp
@ -82,7 +81,7 @@
(let [chat (merge (or (get chats chat-id)
(create-new-chat chat-id cofx))
chat-props)]
{:db (update-in db [:chats chat-id] merge chat)
{:db (update-in db [:chats chat-id] merge chat)
:data-store/save-chat chat}))))
;; TODO (yenda): an upsert is suppose to add the entry if it doesn't
@ -98,17 +97,16 @@
(> timestamp removed-from-at)))
(defn remove-chat [chat-id {:keys [db] :as cofx}]
(let [{:keys [chat-id group-chat debug?]} (get-in db [:chats chat-id])
fx (cond-> {:db (-> db
(update :chats dissoc chat-id)
(update :deleted-chats (fnil conj #{}) chat-id))}
(or group-chat debug?)
(assoc :data-store/delete-messages chat-id)
debug?
(assoc :data-store/delete-chat chat-id)
(not debug?)
(assoc :data-store/deactivate-chat chat-id))]
(handlers/merge-fx cofx fx (transport/unsubscribe-from-chat chat-id))))
(let [{:keys [chat-id group-chat debug?]} (get-in db [:chats chat-id])]
(cond-> {:db (-> db
(update :chats dissoc chat-id)
(update :deleted-chats (fnil conj #{}) chat-id))}
(or group-chat debug?)
(assoc :data-store/delete-messages chat-id)
debug?
(assoc :data-store/delete-chat chat-id)
(not debug?)
(assoc :data-store/deactivate-chat chat-id))))
(defn bot-only-chat? [db chat-id]
(let [{:keys [group-chat contacts]} (get-in db [:chats chat-id])]

View File

@ -10,7 +10,7 @@
{}
name->ref))
(defn- is-dapp? [all-contacts {:keys [identity]}]
(defn- is-dapp? [all-contacts identity]
(get-in all-contacts [identity :dapp?]))
(defn command-name [{:keys [name]}]
@ -30,8 +30,7 @@
humans? (conj :humans)
public? (conj :public-chats))
global-access-scope (conj basic-access-scope :global)
member-access-scopes (into #{} (map (comp (partial conj basic-access-scope) :identity))
contacts)]
member-access-scopes (into #{} (map (partial conj basic-access-scope)) contacts)]
(reduce (fn [acc access-scope]
(merge acc (resolve-references all-contacts
(get-in access-scope->commands-responses [access-scope type]))))

View File

@ -61,7 +61,7 @@
:icon-opts {:color :black
:accessibility-label :chat-menu-button}
:handler #(on-options chat-id name group-chat public?)}]])]
(when-not (or public? group-chat) [add-contact-bar (-> contacts first :identity)])]))
(when-not (or public? group-chat) [add-contact-bar (first contacts)])]))
(defmulti message-row (fn [{{:keys [type]} :row}] type))
@ -82,10 +82,10 @@
timeout (if platform/android? 50 0)]
{:component-did-mount (fn [_]
(animation/start
(animation/anim-sequence
[(animation/anim-delay timeout)
(animation/spring opacity {:toValue 1
:duration duration})])))}
(animation/anim-sequence
[(animation/anim-delay timeout)
(animation/spring opacity {:toValue 1
:duration duration})])))}
[react/with-activity-indicator
{:style style/message-view-preview
:preview [react/view style/message-view-preview]}

View File

@ -46,23 +46,31 @@
{::evaluate-jail-n [{:jail-id whisper-identity
:jail-resource commands-snippet}]}))
(defn load-commands-for-bot
"This function takes coeffects, effects, bot contact and adds effects
for loading all commands/responses/subscriptions."
[cofx fx {:keys [whisper-identity bot-url]}]
(if-let [commands-resource (js-resources/get-resource bot-url)]
(merge-with into fx (evaluate-commands-in-jail cofx commands-resource whisper-identity))
(update fx :http-get-n conj {:url bot-url
:response-validator valid-network-resource?
:success-event-creator (fn [commands-resource]
[::evaluate-commands-in-jail commands-resource whisper-identity])
:failure-event-creator (fn [error-response]
[::proceed-loading whisper-identity {:error error-response}])})))
(defn load-commands
"This function takes coeffects, effects and contact and adds effects
for loading all commands/responses/subscriptions.
"This function takes coeffects and produces effects
for loading all commands/responses/subscriptions for existing bot contacts.
It's currently working only for bots, eq we are not evaluating
dapp resources in jail at all."
[cofx fx {:keys [whisper-identity bot-url]}]
(if bot-url
(if-let [commands-resource (js-resources/get-resource bot-url)]
(merge-with into fx (evaluate-commands-in-jail cofx commands-resource whisper-identity))
(update fx :http-get-n conj {:url bot-url
:response-validator valid-network-resource?
:success-event-creator (fn [commands-resource]
[::evaluate-commands-in-jail commands-resource whisper-identity])
:failure-event-creator (fn [error-response]
[::proceed-loading whisper-identity {:error error-response}])}))
fx))
[{:keys [db] :as cofx}]
(transduce (comp (map second)
(filter :bot-url))
(completing (partial load-commands-for-bot cofx))
{}
(:contacts/contacts db)))
(defn- add-exclusive-choices [initial-scope exclusive-choices]
(reduce (fn [scopes-set exclusive-choices]

View File

@ -179,4 +179,4 @@
::load-commands
[re-frame/trim-v (re-frame/inject-cofx :data-store/get-local-storage-data)]
(fn [cofx [contact]]
(loading-events/load-commands cofx {} contact)))
(loading-events/load-commands-for-bot cofx {} contact)))

View File

@ -74,41 +74,23 @@
(aset "is-active" false)
(aset "removed-at" (datetime/timestamp)))))))
(defn get-contacts
[chat-id]
(-> @realm/account-realm
(realm/get-one-by-field :chat :chat-id chat-id)
(object/get "contacts")))
(defn- save-contacts
[identities contacts added-at]
(doseq [contact-identity identities]
(if-let [contact (.find contacts (fn [object _ _]
(= contact-identity (object/get object "identity"))))]
(doto contact
(aset "is-in-chat" true)
(aset "added-at" added-at))
(.push contacts (clj->js {:identity contact-identity
:added-at added-at})))))
(defn add-contacts
[chat-id identities]
(let [contacts (get-contacts chat-id)
added-at (datetime/timestamp)]
(let [chat (get-by-id-obj chat-id)
contacts (object/get chat "contacts")]
(realm/write @realm/account-realm
#(save-contacts identities contacts added-at))))
(defn- delete-contacts
[identities contacts]
(doseq [contact-identity identities]
(when-let [contact (.find contacts (fn [object _ _]
(= contact-identity (object/get object "identity"))))]
(realm/delete @realm/account-realm contact))))
#(aset chat "contacts"
(clj->js (into #{} (concat identities
(realm/js-object->clj contacts))))))))
(defn remove-contacts
[chat-id identities]
(let [contacts (get-contacts chat-id)]
(delete-contacts identities contacts)))
(let [chat (get-by-id-obj chat-id)
contacts (object/get chat "contacts")]
(realm/write @realm/account-realm
#(aset chat "contacts"
(clj->js (remove (into #{} identities)
(realm/js-object->clj contacts)))))))
(defn save-property
[chat-id property-name value]

View File

@ -33,21 +33,15 @@
(when-let [group (realm/get-one-by-field @realm/account-realm :contact-group :group-id group-id)]
(realm/delete @realm/account-realm group)))
(defn get-contacts
(defn- get-by-id-obj
[group-id]
(-> @realm/account-realm
(realm/get-one-by-field :contact-group :group-id group-id)
(object/get "contacts")))
(defn- save-contacts
[identities contacts]
(doseq [contact-identity identities]
(when-not (.find contacts (fn [object _ _]
(= contact-identity (object/get object "identity"))))
(.push contacts (clj->js {:identity contact-identity})))))
(realm/get-one-by-field @realm/account-realm :contact-group :group-id group-id))
(defn add-contacts
[group-id identities]
(let [contacts (get-contacts group-id)]
(let [group (get-by-id-obj group-id)
contacts (object/get group "contacts")]
(realm/write @realm/account-realm
#(save-contacts identities contacts))))
#(aset group "contacts"
(clj->js (into #{} (concat identities
(realm/js-object->clj contacts))))))))

View File

@ -13,10 +13,7 @@
:optional true}
:is-active :bool
:timestamp :int
:contacts {:type :list
:objectType :chat-contact}
:unremovable? {:type :bool
:default false}
:contacts {:type "string[]"}
:removed-at {:type :int
:optional true}
:removed-from-at {:type :int

View File

@ -7,6 +7,7 @@
:name :string
:timestamp :int
:order :int
:pending? {:type :bool :default false}
:contacts {:type :list
:objectType :group-contact}}})
:contacts {:type "string[]"}}})
(defn migration [old-realm new-realm]
(log/debug "migrating group schema v1"))

View File

@ -1,13 +1,11 @@
(ns status-im.data-store.realm.schemas.account.v1.core
(:require [status-im.data-store.realm.schemas.account.v1.chat :as chat]
[status-im.data-store.realm.schemas.account.v1.transport :as transport]
[status-im.data-store.realm.schemas.account.v1.chat-contact :as chat-contact]
[status-im.data-store.realm.schemas.account.v1.contact :as contact]
[status-im.data-store.realm.schemas.account.v1.message :as message]
[status-im.data-store.realm.schemas.account.v1.request :as request]
[status-im.data-store.realm.schemas.account.v1.user-status :as user-status]
[status-im.data-store.realm.schemas.account.v1.contact-group :as contact-group]
[status-im.data-store.realm.schemas.account.v1.group-contact :as group-contact]
[status-im.data-store.realm.schemas.account.v1.local-storage :as local-storage]
[status-im.data-store.realm.schemas.account.v1.browser :as browser]
[goog.object :as object]
@ -16,14 +14,12 @@
[clojure.string :as string]))
(def schema [chat/schema
chat-contact/schema
transport/schema
contact/schema
message/schema
request/schema
user-status/schema
contact-group/schema
group-contact/schema
local-storage/schema
browser/schema])

View File

@ -15,7 +15,7 @@
(handlers/register-handler-fx
:protocol/receive-whisper-message
[re-frame/trim-v]
[re-frame/trim-v (re-frame/inject-cofx :random-id)]
(fn [cofx [js-error js-message chat-id]]
(let [{:keys [payload sig]} (js->clj js-message :keywordize-keys true)
status-message (-> payload

View File

@ -21,7 +21,7 @@
(defrecord NewGroupKey [chat-id sym-key message]
message/StatusMessage
(send [this _ cofx]
(let [public-keys (map :identity (get-in cofx [:db :chats chat-id :contacts]))]
(let [public-keys (get-in cofx [:db :chats chat-id :contacts])]
(protocol/multi-send-with-pubkey {:public-keys public-keys
:chat-id chat-id
:payload this}
@ -32,6 +32,7 @@
:sym-key sym-key
:on-success (fn [sym-key sym-key-id]
(re-frame/dispatch [::add-new-sym-key {:chat-id chat-id
:signature signature
:sym-key sym-key
:sym-key-id sym-key-id
:message message}]))}}
@ -80,20 +81,20 @@
(handlers/merge-fx cofx
(init-chat-if-new chat-id)
(send-new-group-key this chat-id)))
(receive [this chat-id signature {:keys [now db] :as cofx}]
(receive [this chat-id signature {:keys [now db random-id] :as cofx}]
(let [me (:current-public-key db)]
;; we have to check if we already have a chat, or it's a new one
(if-let [{:keys [group-admin contacts] :as chat} (get-in db [:chats chat-id])]
;; update for existing group chat
(when (= signature group-admin) ;; make sure that admin is the one making changes
(when (and (= signature group-admin) ;; make sure that admin is the one making changes
(not= (set contacts) (set participants))) ;; make sure it's actually changing something
(let [{:keys [removed added]} (participants-diff (set contacts) (set participants))
admin-name (or (get-in cofx [db :contacts/contacts group-admin :name])
group-admin)
message-id (transport.utils/message-id this)]
group-admin)]
(if (removed me) ;; we were removed
(handlers/merge-fx cofx
(models.message/receive
(models.message/system-message chat-id message-id now
(models.message/system-message chat-id random-id now
(str admin-name " " (i18n/label :t/removed-from-chat))))
(models.chat/update-chat {:chat-id chat-id
:removed-from-at now
@ -101,7 +102,7 @@
(transport/unsubscribe-from-chat chat-id))
(handlers/merge-fx cofx
(models.message/receive
(models.message/system-message chat-id message-id now
(models.message/system-message chat-id random-id now
(prepare-system-message admin-name
added
removed
@ -119,15 +120,14 @@
:payload this
:success-event [::unsubscribe-from-chat chat-id]}
cofx))
(receive [this chat-id signature {:keys [db now] :as cofx}]
(let [message-id (transport.utils/message-id this)
me (:current-public-key db)
(receive [this chat-id signature {:keys [db now random-id] :as cofx}]
(let [me (:current-public-key db)
participant-leaving-name (or (get-in db [:contacts/contacts signature :name])
signature)]
(when-not (= me signature)
(when (get-in db [:chats chat-id]) ;; chat is present
(handlers/merge-fx cofx
(models.message/receive
(models.message/system-message chat-id message-id now
(models.message/system-message chat-id random-id now
(str participant-leaving-name " " (i18n/label :t/left))))
(group/participants-removed chat-id #{signature})
(send-new-group-key nil chat-id))))))
@ -160,8 +160,8 @@
(handlers/register-handler-fx
::add-new-sym-key
[re-frame/trim-v]
(fn [{:keys [db] :as cofx} [{:keys [sym-key-id sym-key chat-id message]}]]
[re-frame/trim-v (re-frame/inject-cofx :random-id)]
(fn [{:keys [db] :as cofx} [{:keys [sym-key-id sym-key chat-id signature message]}]]
(let [{:keys [web3 current-public-key]} db
fx {:db (assoc-in db [:transport/chats chat-id :sym-key-id] sym-key-id)
:shh/add-filter {:web3 web3
@ -175,5 +175,5 @@
(assoc :sym-key sym-key))}}]
;; if new sym-key is wrapping some message, call receive on it as well, if not just update the transport layer
(if message
(handlers/merge-fx cofx fx (message/receive message chat-id chat-id))
(handlers/merge-fx cofx fx (message/receive message chat-id signature))
fx))))

View File

@ -63,11 +63,17 @@
(defn- prepare-recipients [public-keys db]
(map (fn [public-key]
(select-keys (get-in db [:transport/chats public-key]) [:topic :sym-key-id]))
(if-let [{:keys [topic sym-key-id]} (get-in db [:transport/chats public-key])]
{:topic topic
:sym-key-id sym-key-id}
{:topic (transport.utils/get-topic constants/contact-discovery)
:pub-key public-key}))
public-keys))
(defn multi-send-with-pubkey
"Sends payload to multiple participants selected by `:public-keys` key."
"Sends payload to multiple participants selected by `:public-keys` key.
If there is already established symmetric key with the participant, uses that (for efficiency),
if not, uses asymetric encryption."
[{:keys [payload public-keys success-event]} {:keys [db] :as cofx}]
(let [{:keys [current-public-key web3]} db
recipients (prepare-recipients public-keys db)]

View File

@ -82,11 +82,11 @@
:or {error-event :protocol/send-status-message-error}}]
(let [whisper-message (update message :payload (comp transport.utils/from-utf8
transit/serialize))]
(doseq [{:keys [sym-key-id topic]} recipients]
(doseq [{:keys [sym-key-id pub-key topic]} recipients]
(post-message {:web3 web3
:whisper-message (assoc whisper-message
:symKeyID sym-key-id
:topic topic)
:whisper-message (cond-> (assoc whisper-message :topic topic)
sym-key-id (assoc :symKeyID sym-key-id)
pub-key (assoc :pubKey pub-key))
:on-success (if success-event
#(re-frame/dispatch success-event)
#(log/debug :shh/post-success))

View File

@ -22,12 +22,12 @@
:on-press #(re-frame/dispatch [:navigate-to :new-chat])}]
[action-button/action-separator]
;; TODO temporary removal before everything is fixed in group chats
#_[action-button/action-button
{:label (i18n/label :t/start-group-chat)
:accessibility-label :start-group-chat-button
:icon :icons/contacts
:icon-opts {:color colors/blue}
:on-press #(re-frame/dispatch [:open-contact-toggle-list :chat-group])}]
[action-button/action-button
{:label (i18n/label :t/start-group-chat)
:accessibility-label :start-group-chat-button
:icon :icons/contacts
:icon-opts {:color colors/blue}
:on-press #(re-frame/dispatch [:open-contact-toggle-list :chat-group])}]
[action-button/action-separator]
[action-button/action-button
{:label (i18n/label :t/new-public-group-chat)

View File

@ -1,36 +1,22 @@
(ns status-im.ui.screens.contacts.events
(:require [cljs.reader :as reader]
(:require [clojure.set :as set]
[cljs.reader :as reader]
[re-frame.core :as re-frame]
[status-im.utils.handlers :as handlers]
[status-im.utils.contacts :as utils.contacts]
[status-im.utils.random :as random]
[status-im.utils.js-resources :as js-res]
[status-im.i18n :refer [label]]
[taoensso.timbre :as log]
[status-im.utils.js-resources :as js-res]
[status-im.utils.datetime :as datetime]
[status-im.constants :as constants]
[status-im.utils.identicon :as identicon]
[status-im.utils.gfycat.core :as gfycat.core]
[status-im.ui.screens.contacts.navigation]
[status-im.ui.screens.navigation :as navigation]
[status-im.ui.screens.group.events :as group.events]
[status-im.chat.console :as console-chat]
[status-im.chat.events :as chat.events]
[status-im.chat.models :as chat.models]
[status-im.commands.events.loading :as loading-events]
[status-im.js-dependencies :as js-dependencies]
[status-im.transport.message.core :as transport]
[status-im.transport.message.v1.contact :as message.v1.contact]
[status-im.ui.screens.add-new.new-chat.db :as new-chat.db]))
;;;; COFX
(re-frame/reg-cofx
::get-default-contacts-and-groups
(fn [coeffects _]
(assoc coeffects
:default-contacts js-res/default-contacts
:default-groups js-res/default-contact-groups)))
;;;; Handlers
(defn- update-contact [{:keys [whisper-identity] :as contact} {:keys [db]}]
@ -38,84 +24,6 @@
{:db (update-in db [:contacts/contacts whisper-identity] merge contact)
:data-store/save-contact contact}))
(defn- update-pending-status [old-contacts {:keys [whisper-identity pending?] :as contact}]
(let [{old-pending :pending?
:as old-contact} (get old-contacts whisper-identity)
pending?' (if old-contact (and old-pending pending?) pending?)]
(assoc contact :pending? (boolean pending?'))))
(defn- public-key->address [public-key]
(let [length (count public-key)
normalized-key (case length
132 (subs public-key 4)
130 (subs public-key 2)
128 public-key
nil)]
(when normalized-key
(subs (.sha3 js-dependencies/Web3.prototype normalized-key #js {:encoding "hex"}) 26))))
(defn- prepare-default-groups-events [groups default-groups]
[[:add-contact-groups
(for [[id {:keys [name contacts]}] default-groups
:let [id' (clojure.core/name id)]
:when (not (get groups id'))]
{:group-id id'
:name (:en name)
:order 0
:timestamp (datetime/timestamp)
:contacts (mapv #(hash-map :identity %) contacts)})]])
;; NOTE(oskarth): We now overwrite default contacts upon upgrade with default_contacts.json data.
(defn- prepare-default-contacts-events [contacts default-contacts]
(let [default-contacts
(for [[id {:keys [name photo-path public-key add-chat? pending? description
dapp? dapp-url dapp-hash bot-url unremovable? hide-contact?]}] default-contacts
:let [id' (clojure.core/name id)]]
{:whisper-identity id'
:address (public-key->address id')
:name (:en name)
:photo-path photo-path
:public-key public-key
:unremovable? (boolean unremovable?)
:hide-contact? (boolean hide-contact?)
:pending? pending?
:dapp? dapp?
:dapp-url (:en dapp-url)
:bot-url bot-url
:description description
:dapp-hash dapp-hash})
all-default-contacts (conj default-contacts console-chat/contact)]
[[:add-contacts all-default-contacts]]))
(defn- prepare-add-chat-events [contacts default-contacts]
(for [[id {:keys [name add-chat?]}] default-contacts
:let [id' (clojure.core/name id)]
:when (and (not (get contacts id')) add-chat?)]
[:add-chat id' {:name (:en name)}]))
(defn- prepare-add-contacts-to-groups-events [contacts default-contacts]
(let [groups (for [[id {:keys [groups]}] default-contacts
:let [id' (clojure.core/name id)]
:when (and (not (get contacts id')) groups)]
(for [group groups]
{:group-id group :whisper-identity id'}))
groups' (vals (group-by :group-id (flatten groups)))]
(for [contacts groups']
[:add-contacts-to-group
(:group-id (first contacts))
(mapv :whisper-identity contacts)])))
(handlers/register-handler-fx
:load-default-contacts!
[(re-frame/inject-cofx ::get-default-contacts-and-groups)]
(fn [{:keys [db default-contacts default-groups]} _]
(let [{:contacts/keys [contacts] :group/keys [contact-groups]} db]
{:dispatch-n (concat
(prepare-default-groups-events contact-groups default-groups)
(prepare-default-contacts-events contacts default-contacts)
(prepare-add-chat-events contacts default-contacts)
(prepare-add-contacts-to-groups-events contacts default-contacts))})))
(handlers/register-handler-fx
:load-contacts
[(re-frame/inject-cofx :data-store/get-all-contacts)]
@ -124,24 +32,6 @@
contacts (into {} contacts-list)]
{:db (update db :contacts/contacts #(merge contacts %))})))
(handlers/register-handler-fx
:add-contacts
[(re-frame/inject-cofx :data-store/get-local-storage-data)]
(fn [{:keys [db] :as cofx} [_ new-contacts]]
(let [{:contacts/keys [contacts]} db
new-contacts' (->> new-contacts
(map #(update-pending-status contacts %))
;; NOTE(oskarth): Overwriting default contacts here
;;(remove #(identities (:whisper-identity %)))
(map #(vector (:whisper-identity %) %))
(into {}))
fx {:db (update db :contacts/contacts merge new-contacts')
:data-store/save-contacts (vals new-contacts')}]
(transduce (map second)
(completing (partial loading-events/load-commands (assoc cofx :db (:db fx))))
fx
new-contacts'))))
(defn- add-new-contact [{:keys [whisper-identity] :as contact} {:keys [db]}]
(let [new-contact (assoc contact :pending? false)]
{:db (-> db
@ -168,7 +58,7 @@
(reader/read-string contact-info)
(or (get contacts whisper-id)
(utils.contacts/whisper-id->new-contact whisper-id)))
(assoc :address (public-key->address whisper-id))))
(assoc :address (utils.contacts/public-key->address whisper-id))))
(defn add-contact [whisper-id {:keys [db] :as cofx}]
(let [contact (build-contact whisper-id cofx)]

View File

@ -44,7 +44,7 @@
(remove #(true? (:dapp? %)) contacts)))
(defn filter-group-contacts [group-contacts contacts]
(let [group-contacts' (into #{} (map #(:identity %) group-contacts))]
(let [group-contacts' (into #{} group-contacts)]
(filter #(group-contacts' (:whisper-identity %)) contacts)))
(reg-sub :group-contacts
@ -59,7 +59,7 @@
(filter-group-contacts group-contacts contacts)))
(defn filter-not-group-contacts [group-contacts contacts]
(let [group-contacts' (into #{} (map #(:identity %) group-contacts))]
(let [group-contacts' (into #{} group-contacts)]
(remove #(group-contacts' (:whisper-identity %)) contacts)))
(reg-sub :all-not-added-group-contacts
@ -123,10 +123,7 @@
(defn chat-contacts [[chat contacts] [_ fn]]
(when chat
(let [current-participants (->> chat
:contacts
(map :identity)
set)]
(let [current-participants (-> chat :contacts set)]
(fn #(current-participants (:whisper-identity %))
(vals contacts)))))

View File

@ -275,10 +275,10 @@
{:dispatch-n (cond-> [[:initialize-account-db address]
[:initialize-protocol address]
[:initialize-sync-listener]
[:initialize-chats]
[:initialize-browsers]
[:load-contacts]
[:load-contact-groups]
[:initialize-chats]
[:initialize-browsers]
[:initialize-debugging {:address address}]
[:send-account-update-if-needed]
[:update-wallet]

View File

@ -20,9 +20,7 @@
:add-new-group-chat-participants
[(re-frame/inject-cofx :random-id)]
(fn [{{:keys [current-chat-id selected-participants] :as db} :db now :now message-id :random-id :as cofx} _]
(let [new-identities (map #(hash-map :identity %) selected-participants)
participants (concat (get-in db [:chats current-chat-id :contacts])
selected-participants)
(let [participants (concat (get-in db [:chats current-chat-id :contacts]) selected-participants)
contacts (:contacts/contacts db)
added-participants-names (map #(get-in contacts [% :name]) selected-participants)]
(handlers/merge-fx cofx
@ -39,8 +37,7 @@
:remove-group-chat-participants
[re-frame/trim-v (re-frame/inject-cofx :random-id)]
(fn [{{:keys [current-chat-id] :as db} :db now :now message-id :random-id :as cofx} [removed-participants]]
(let [participants (remove #(removed-participants (:identity %))
(get-in db [:chats current-chat-id :contacts]))
(let [participants (remove removed-participants (get-in db [:chats current-chat-id :contacts]))
contacts (:contacts/contacts db)
removed-participants-names (map #(get-in contacts [% :name]) removed-participants)]
(handlers/merge-fx cofx

View File

@ -2,7 +2,7 @@
(defn participants-added [chat-id added-participants-set {:keys [db] :as cofx}]
(when (seq added-participants-set)
{:db (update-in db [:chats chat-id :contacts] concat (mapv #(hash-map :identity %) added-participants-set))
{:db (update-in db [:chats chat-id :contacts] concat added-participants-set)
:data-store/add-chat-contacts [chat-id added-participants-set]}))
(defn participants-removed [chat-id removed-participants-set {:keys [now db] :as cofx}]
@ -10,6 +10,5 @@
(let [{:keys [is-active timestamp]} (get-in db [:chats chat-id])]
;;TODO: not sure what this condition is for
(when (and is-active (>= now timestamp))
{:db (update-in db [:chats chat-id :contacts]
(partial remove (comp removed-participants-set :identity)))
{:db (update-in db [:chats chat-id :contacts] (partial remove removed-participants-set))
:data-store/remove-chat-contacts [chat-id removed-participants-set]}))))

View File

@ -15,9 +15,7 @@
(spec/def :group/pending? boolean?)
(spec/def :group/order int?)
(spec/def :group-contact/identity :global/not-empty-string)
(spec/def :group/contact (allowed-keys :req-un [:group-contact/identity]))
(spec/def :group/contact :global/not-empty-string)
(spec/def :group/contacts (spec/nilable (spec/* :group/contact)))

View File

@ -1,109 +1,77 @@
(ns status-im.ui.screens.group.events
(:require [re-frame.core :refer [dispatch reg-fx reg-cofx inject-cofx]]
[status-im.utils.handlers :refer [register-handler-db register-handler-fx]]
[status-im.ui.components.styles :refer [default-chat-color]]
[clojure.string :as string]
[status-im.utils.random :as random]
[status-im.ui.screens.group.navigation]
[status-im.utils.datetime :as datetime]
[re-frame.core :as re-frame]))
(:require [clojure.set :as set]
[re-frame.core :as re-frame]
[status-im.utils.js-resources :as js-res]
[status-im.utils.handlers :as handlers]
[status-im.ui.screens.group.navigation]))
;;;; COFX
(re-frame/reg-cofx
:get-default-contacts-and-groups
(fn [coeffects _]
(assoc coeffects
:default-contacts js-res/default-contacts
:default-groups js-res/default-contact-groups)))
;;;; Handlers
(register-handler-db
(handlers/register-handler-db
:deselect-contact
(fn [db [_ id]]
(update db :group/selected-contacts disj id)))
(register-handler-db
(handlers/register-handler-db
:select-contact
(fn [db [_ id]]
(update db :group/selected-contacts conj id)))
(register-handler-db
(handlers/register-handler-db
:deselect-participant
(fn [db [_ id]]
(update db :selected-participants disj id)))
(register-handler-db
(handlers/register-handler-db
:select-participant
(fn [db [_ id]]
(update db :selected-participants conj id)))
(register-handler-fx
(handlers/register-handler-fx
:create-new-contact-group
[(re-frame/inject-cofx :now)]
(fn [{{:group/keys [contact-groups selected-contacts] :as db} :db
now :now} [_ group-name]]
(let [selected-contacts' (mapv #(hash-map :identity %) selected-contacts)
new-group {:group-id (random/id)
[re-frame/trim-v (re-frame/inject-cofx :now) (re-frame/inject-cofx :random-id)]
(fn [{{:group/keys [contact-groups selected-contacts] :as db} :db group-id :random-id now :now} [group-name]]
(let [new-group {:group-id group-id
:name group-name
:order (count contact-groups)
:timestamp now
:contacts selected-contacts'}]
{:db (update db :group/contact-groups merge {(:group-id new-group) new-group})
:contacts selected-contacts}]
{:db (assoc-in db [:group/contact-groups group-id] new-group)
:data-store/save-contact-group new-group})))
(register-handler-fx
::update-contact-group
(fn [{:keys [db]} [_ new-group]]
{:db (update db :group/contact-groups merge {(:group-id new-group) new-group})
:data-store/save-contact-group new-group}))
(defn add-default-groups
[{:keys [db now default-groups]}]
(let [new-groups (into {}
(map (fn [[id props]]
(let [group-id (name id)]
[group-id {:group-id group-id
:name (-> props :name :en)
:order 0
:timestamp now
:contacts (:contacts props)}])))
default-groups)
existing-groups (:group/contact-groups db)
groups-to-add (select-keys new-groups (set/difference (set (keys new-groups))
(set (keys existing-groups))))]
{:db (update db :group/contact-groups merge groups-to-add)
:data-store/save-contact-groups (vals groups-to-add)}))
(defn update-pending-status [old-groups {:keys [group-id pending?] :as group}]
(let [{old-pending :pending?
:as old-group} (get old-groups group-id)
pending?' (if old-pending (and old-pending pending?) pending?)]
(assoc group :pending? (boolean pending?'))))
(register-handler-fx
:add-contact-groups
(fn [{{:group/keys [contact-groups] :as db} :db} [_ new-groups]]
(let [identities (set (keys contact-groups))
old-groups-count (count identities)
new-groups' (->> new-groups
(map #(update-pending-status contact-groups %))
(remove #(identities (:group-id %)))
(map #(vector (:group-id %2) (assoc %2 :order %1)) (iterate inc old-groups-count))
(into {}))]
{:db (update db :group/contact-groups merge new-groups')
:data-store/save-contact-groups (into [] (vals new-groups'))})))
(register-handler-fx
(handlers/register-handler-fx
:load-contact-groups
[(inject-cofx :data-store/get-all-contact-groups)]
[(re-frame/inject-cofx :data-store/get-all-contact-groups)]
(fn [{:keys [db all-contact-groups]} _]
{:db (assoc db :group/contact-groups all-contact-groups)}))
(defn move-item [v from to]
(if (< from to)
(concat (subvec v 0 from)
(subvec v (inc from) (inc to))
[(v from)]
(subvec v (inc to)))
(concat (subvec v 0 to)
[(v from)]
(subvec v to from)
(subvec v (inc from)))))
(register-handler-db
:change-contact-group-order
(fn [{:group/keys [groups-order] :as db} [_ from to]]
(if (>= to 0)
(assoc db :group/groups-order (move-item (vec groups-order) from to))
db)))
(register-handler-fx
:save-contact-group-order
(fn [{{:group/keys [contact-groups groups-order] :as db} :db} _]
(let [new-groups (mapv #(assoc (contact-groups (second %)) :order (first %))
(map-indexed vector (reverse groups-order)))]
{:db (update db
:group/contact-groups
merge (map #(vector (:group-id %) %) new-groups))
:data-store/save-contact-groups new-groups})))
(register-handler-fx
(handlers/register-handler-fx
:set-contact-group-name
(fn [{{:keys [new-chat-name] :group/keys [contact-group-id] :as db} :db} _]
{:db (assoc-in db
@ -111,37 +79,20 @@
new-chat-name)
:data-store/save-contact-group-property [contact-group-id :name new-chat-name]}))
(register-handler-fx
(handlers/register-handler-fx
:add-selected-contacts-to-group
(fn [{{:group/keys [contact-groups contact-group-id selected-contacts] :as db} :db} _]
(let [new-identities (mapv #(hash-map :identity %) selected-contacts)]
{:db (update-in db
[:group/contact-groups contact-group-id :contacts]
#(into [] (set (concat % new-identities))))
:data-store/add-contacts-to-contact-group [contact-group-id selected-contacts]})))
{:db (update-in db
[:group/contact-groups contact-group-id :contacts]
#(into [] (set (concat % selected-contacts))))
:data-store/add-contacts-to-contact-group [contact-group-id selected-contacts]}))
(register-handler-fx
:add-contacts-to-group
(fn [{:keys [db]} [_ group-id contacts]]
(let [new-identities (mapv #(hash-map :identity %) contacts)]
(when (get-in db [:group/contact-groups group-id])
{:db (update-in db [:group/contact-groups group-id :contacts]
#(into [] (set (concat % new-identities))))
:data-store/add-contacts-to-contact-group [group-id contacts]}))))
(defn remove-contact-from-group [whisper-identity]
(fn [contacts]
(remove #(= whisper-identity (:identity %)) contacts)))
(register-handler-fx
(handlers/register-handler-fx
:remove-contact-from-group
(fn [{:keys [db]} [_ whisper-identity group-id]]
(let [{:group/keys [contact-groups]} db
group' (update (contact-groups group-id) :contacts (remove-contact-from-group whisper-identity))]
{:dispatch [::update-contact-group group']})))
(register-handler-fx
:delete-contact-group
(fn [{{:group/keys [contact-group-id] :as db} :db} _]
{:db (assoc-in db [:group/contact-groups contact-group-id :pending?] true)
:data-store/save-contact-group-property [contact-group-id :pending? true]}))
[re-frame/trim-v]
(fn [{:keys [db]} [whisper-identity group-id]]
(let [group (-> db
(get-in [:group/contact-groups group-id])
(update :contacts (partial remove #(= whisper-identity %))))]
{:db (assoc-in db [:group/contact-groups group-id] group)
:data-store/save-contact-group group})))

View File

@ -38,12 +38,10 @@
(defn actions [admin? chat-id]
(concat
;; NOTE(goranjovic) - group chat participant removal has been temporarily disabled
;; due to this bug - https://github.com/status-im/status-react/issues/3463
#_(when admin?
[{:label (i18n/label :add-members)
:icon :icons/add
:action #(re-frame/dispatch [:navigate-to :add-participants-toggle-list])}])
(when admin?
[{:label (i18n/label :add-members)
:icon :icons/add
:action #(re-frame/dispatch [:navigate-to :add-participants-toggle-list])}])
[{:label (i18n/label :t/clear-history)
:icon :icons/close
:action #(utils/show-confirmation (i18n/label :t/clear-history-title)
@ -64,16 +62,14 @@
:action #(utils/show-confirmation (i18n/label :t/leave-group-title)
(i18n/label :t/leave-group-confirmation)
(i18n/label :t/leave-group-action)
(fn [] (re-frame/dispatch [:leave-group-chat])))
(fn [] (re-frame/dispatch [:remove-chat-and-navigate-home chat-id])))
:accessibility-label :leave-chat-button}]))
(defn contact-actions [contact]
[{:action #(re-frame/dispatch [:show-profile (:whisper-identity contact)])
:label (i18n/label :t/view-profile)}
;; NOTE(goranjovic) - group chat participant removal has been temporarily disabled
;; due to this bug - https://github.com/status-im/status-react/issues/3463
#_{:action #(re-frame/dispatch [:remove-group-chat-participants #{(:whisper-identity contact)}])
:label (i18n/label :t/remove-from-chat)}])
{:action #(re-frame/dispatch [:remove-group-chat-participants #{(:whisper-identity contact)}])
:label (i18n/label :t/remove-from-chat)}])
(defn render-contact [contact admin?]
[react/view

View File

@ -1,9 +1,19 @@
(ns status-im.utils.contacts
(:require
[status-im.utils.identicon :as identicon]
[status-im.utils.gfycat.core :as gfycat]))
(:require [status-im.js-dependencies :as js-dependencies]
[status-im.utils.identicon :as identicon]
[status-im.utils.gfycat.core :as gfycat]))
(defn whisper-id->new-contact [whisper-id]
{:name (gfycat/generate-gfy whisper-id)
:photo-path (identicon/identicon whisper-id)
:whisper-identity whisper-id})
(defn public-key->address [public-key]
(let [length (count public-key)
normalized-key (case length
132 (subs public-key 4)
130 (subs public-key 2)
128 public-key
nil)]
(when normalized-key
(subs (.sha3 js-dependencies/Web3.prototype normalized-key #js {:encoding "hex"}) 26))))

View File

@ -17,14 +17,13 @@
(deftest init-console-chat
(testing "initialising console if console is already added to chats, should not modify anything"
(let [db {:chats {const/console-chat-id console-chat/chat}}
fx (chat-events/init-console-chat db)]
(is (= db (:db fx)))
(is (= #{:db} (-> fx keys set)))))
fx (chat-events/init-console-chat {:db db})]
(is (not fx))))
(testing "initialising console without existing account and console chat not initialisated"
(let [fresh-db {:chats {}
:accounts/current-account-id nil}
{:keys [db dispatch-n]} (chat-events/init-console-chat fresh-db)]
{:keys [db dispatch-n]} (chat-events/init-console-chat {:db fresh-db})]
(is (= (:current-chat-id db)
(:chat-id console-chat/chat)))
(is (= (:current-chat-id db)
@ -33,7 +32,7 @@
(testing "initialising console with existing account and console chat not initialisated"
(let [fresh-db {:chats {}
:accounts/current-account-id (:whisper-identity contact)}
{:keys [db dispatch-n]} (chat-events/init-console-chat fresh-db)]
{:keys [db dispatch-n]} (chat-events/init-console-chat {:db fresh-db})]
(is (= (:current-chat-id db)
(:chat-id console-chat/chat)))
(is (= (:current-chat-id db)

View File

@ -8,19 +8,17 @@
#{"0x1" :group-chats :anonymous :dapps} {:command {"command2" ["0x1" :command 4 "command2"]}}
#{"0x2" :personal-chats :anonymous :dapps} {:command {"command3" ["0x2" :command 2 "command3"]}}
#{"0x2" :group-chats :anonymous :dapps} {:response {"response1" ["0x2" :response 4 "response1"]}}}
:chats {"test1" {:contacts [{:identity "0x1"}]
:chats {"test1" {:contacts ["0x1"]
:requests nil
:seq-arguments ["arg1" "arg2"]}
"test2" {:contacts [{:identity "0x1"}
{:identity "0x2"}]
"test2" {:contacts ["0x1" "0x2"]
:group-chat true
:requests {"id1" {:message-id "id1"
:response "response1"}}}
"test3" {:contacts [{:identity "0x1"}]
"test3" {:contacts ["0x1"]
:requests {"id1" {:message-id "id1"
:response "request1"}}}
"test4" {:contacts [{:identity "0x1"}
{:identity "0x2"}]
"test4" {:contacts ["0x1" "0x2"]
:group-chat true
:requests {"id2" {:message-id "id2"
:response "response1"}}