refactored new-group events using fx and cofx

reorganized modules structure, renamed files, improved requirements
This commit is contained in:
Andrey Shovkoplyas 2017-08-01 11:48:54 +03:00 committed by Roman Volosovskyi
parent d9800fe9a7
commit b429076cad
48 changed files with 1566 additions and 1646 deletions

View File

@ -28,17 +28,16 @@
[status-im.transactions.screens.unsigned-transactions :refer [unsigned-transactions]]
[status-im.transactions.screens.transaction-details :refer [transaction-details]]
[status-im.chats-list.screen :refer [chats-list]]
[status-im.new-chat.screen :refer [new-chat]]
[status-im.new-group.screen-public :refer [new-public-group]]
[status-im.new-group.screen-private :refer [new-group
edit-group]]
[status-im.new-group.views.chat-group-settings :refer [chat-group-settings]]
[status-im.new-group.views.contact-list :refer [edit-group-contact-list
edit-chat-group-contact-list]]
[status-im.new-group.views.contact-toggle-list :refer [contact-toggle-list
add-contacts-toggle-list
add-participants-toggle-list]]
[status-im.new-group.views.reorder-groups :refer [reorder-groups]]
[status-im.chat.new-chat.view :refer [new-chat]]
[status-im.chat.new-public-chat.view :refer [new-public-chat]]
[status-im.group.views :refer [new-group edit-contact-group]]
[status-im.group.chat-settings.views :refer [chat-group-settings]]
[status-im.group.edit-contacts.views :refer [edit-contact-group-contact-list
edit-chat-group-contact-list]]
[status-im.group.add-contacts.views :refer [contact-toggle-list
add-contacts-toggle-list
add-participants-toggle-list]]
[status-im.group.reorder.views :refer [reorder-groups]]
[status-im.profile.screen :refer [profile my-profile]]
[status-im.profile.edit.screen :refer [edit-my-profile]]
[status-im.profile.photo-capture.screen :refer [profile-photo-capture]]
@ -127,13 +126,13 @@
:chat-list main-tabs
:new-chat new-chat
:new-group new-group
:edit-group edit-group
:edit-contact-group edit-contact-group
:chat-group-settings chat-group-settings
:add-contacts-toggle-list add-contacts-toggle-list
:add-participants-toggle-list add-participants-toggle-list
:edit-group-contact-list edit-group-contact-list
:edit-group-contact-list edit-contact-group-contact-list
:edit-chat-group-contact-list edit-chat-group-contact-list
:new-public-group new-public-group
:new-public-chat new-public-chat
:contact-list main-tabs
:contact-toggle-list contact-toggle-list
:group-contacts contact-list

View File

@ -1,6 +1,6 @@
(ns status-im.chat.handlers
(:require-macros [cljs.core.async.macros :as am])
(:require [re-frame.core :refer [enrich after debug dispatch]]
(:require [re-frame.core :refer [enrich after debug dispatch reg-fx]]
[status-im.models.commands :as commands]
[clojure.string :as string]
[status-im.components.styles :refer [default-chat-color]]
@ -20,7 +20,7 @@
[status-im.utils.random :as random]
[status-im.chat.sign-up :as sign-up-service]
[status-im.navigation.handlers :as nav]
[status-im.utils.handlers :refer [register-handler] :as u]
[status-im.utils.handlers :refer [register-handler register-handler-fx] :as u]
[status-im.handlers.server :as server]
[status-im.utils.phone-number :refer [format-phone-number
valid-mobile-number?]]
@ -389,6 +389,11 @@
[db [_ chat-id]]
(update db :chats dissoc chat-id))
(reg-fx
::delete-messages
(fn [id]
(messages/delete-by-chat-id id)))
(defn delete-messages!
[{:keys [current-chat-id]} [_ chat-id]]
(let [id (or chat-id current-chat-id)]
@ -530,3 +535,38 @@
(if (= network-status :offline)
(chats/inc-message-overhead chat-id)
(chats/reset-message-overhead chat-id)))))
(reg-fx
::save-public-chat
(fn [chat]
(chats/save chat)))
(reg-fx
::start-watching-group
(fn [{:keys [group-id web3 current-public-key keypair]}]
(protocol/start-watching-group!
{:web3 web3
:group-id group-id
:identity current-public-key
:keypair keypair
:callback #(dispatch [:incoming-message %1 %2])})))
(register-handler-fx
:create-new-public-chat
(fn [{:keys [db]} [_ topic]]
(let [exists? (boolean (get-in db [:chats topic]))
chat {:chat-id topic
:name topic
:color default-chat-color
:group-chat true
:public? true
:is-active true
:timestamp (random/timestamp)}]
(merge
(when-not exists?
{:db (assoc-in db [:chats (:chat-id chat)] chat)
::save-public-chat chat
::start-watching-group (merge {:group-id topic}
(select-keys db [:web3 :current-public-key]))})
{:dispatch-n [[:navigate-to-clean :chat-list]
[:navigate-to :chat topic]]}))))

View File

@ -1,4 +1,4 @@
(ns status-im.new-chat.styles
(ns status-im.chat.new-chat.styles
(:require [status-im.components.styles :as common]))
(def contacts-list-container

View File

@ -0,0 +1,73 @@
(ns status-im.chat.new-chat.view
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :refer [dispatch]]
[status-im.components.common.common :as common]
[status-im.components.renderers.renderers :as renderers]
[status-im.components.action-button.action-button :refer [action-button
action-separator]]
[status-im.components.action-button.styles :refer [actions-list]]
[status-im.components.react :refer [view text list-view list-item]]
[status-im.components.contact.contact :refer [contact-view]]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar-new.view :refer [toolbar-with-search]]
[status-im.components.drawer.view :refer [drawer-view]]
[status-im.chat.new-chat.styles :as styles]
[status-im.utils.listview :as lw]
[status-im.i18n :refer [label]]))
(defn options-list []
[view actions-list
[action-button (label :t/new-group-chat)
:private_group_big
#(dispatch [:open-contact-toggle-list :chat-group])]
[action-separator]
[action-button (label :t/new-public-group-chat)
:public_group_big
#(dispatch [:navigate-to :new-public-chat])]
[action-separator]
[action-button (label :t/add-new-contact)
:add_blue
#(dispatch [:navigate-to :new-contact])]])
(defn contact-list-row []
(fn [row _ _]
(list-item ^{:key row}
[contact-view {:contact row
:on-press #(dispatch [:open-chat-with-contact %])}])))
(defview new-chat-toolbar []
(letsubs [show-search [:get-in [:toolbar-search :show]]
search-text [:get-in [:toolbar-search :text]]]
[view
[status-bar]
(toolbar-with-search
{:show-search? (= show-search :contact-list)
:search-text search-text
:search-key :contact-list
:title (label :t/contacts-group-new-chat)
:search-placeholder (label :t/search-for)})]))
(defview new-chat []
(letsubs [contacts [:all-added-group-contacts-filtered]
params [:get :contacts/click-params]]
[drawer-view
[view styles/contacts-list-container
[new-chat-toolbar]
(when contacts
[list-view {:dataSource (lw/to-datasource contacts)
:enableEmptySections true
:renderRow (contact-list-row)
:bounces false
:keyboardShouldPersistTaps :always
:renderHeader #(list-item
[view
[options-list]
[common/bottom-shadow]
[common/form-title (label :t/choose-from-contacts)
{:count-value (count contacts)}]
[common/list-header]])
:renderSeparator renderers/list-separator-renderer
:renderFooter #(list-item [view
[common/list-footer]
[common/bottom-shadow]])
:style styles/contacts-list}])]]))

View File

@ -0,0 +1,20 @@
(ns status-im.chat.new-public-chat.db
(:require [cljs.spec.alpha :as spec]
[status-im.constants :refer [console-chat-id wallet-chat-id]]
[clojure.string :as string]
[status-im.utils.homoglyph :as utils]))
(defn legal-name? [username]
(let [username (some-> username string/trim)]
(and (not (utils/matches username console-chat-id))
(not (utils/matches username wallet-chat-id)))))
(spec/def ::legal-name legal-name?)
(spec/def ::not-empty-string (spec/and string? not-empty))
(spec/def ::name (spec/and ::not-empty-string
::legal-name))
(spec/def ::topic (spec/and ::not-empty-string
::legal-name
(partial re-matches #"[a-z0-9\-]+")))

View File

@ -0,0 +1,46 @@
(ns status-im.chat.new-public-chat.styles
(:require-macros [status-im.utils.styles :refer [defstyle]])
(:require [status-im.components.styles :as common]))
(def group-chat-name-input
{:font-size 17
:padding-bottom 0
:letter-spacing -0.2
:color common/text1-color})
(defstyle group-chat-topic-input
{:font-size 14
:line-height 16
:color common/text1-color
:padding-left 13
:ios {:padding-bottom 0}})
(defstyle topic-hash-style
{:width 10
:position :absolute
:android {:top 8 :left 3}
:ios {:top 6 :left 3}})
(def topic-hash
(merge group-chat-name-input
topic-hash-style))
(def group-chat-name-wrapper
{:padding-top 0
:height 40
:padding-bottom 0})
(def group-container
{:flex 1
:flex-direction :column
:background-color common/color-white})
(def chat-name-container
{:padding-left 16
:margin-top 10})
(defstyle members-text
{:color common/color-gray4
:ios {:letter-spacing -0.2
:font-size 16}
:android {:font-size 14}})

View File

@ -0,0 +1,57 @@
(ns status-im.chat.new-public-chat.view
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :refer [dispatch]]
[status-im.components.react :refer [view text]]
[status-im.components.text-field.view :refer [text-field]]
[status-im.components.styles :as common]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar.view :refer [toolbar]]
[status-im.chat.new-public-chat.styles :as styles]
[status-im.chat.new-public-chat.db :as v]
[status-im.i18n :refer [label]]
[cljs.spec.alpha :as spec]))
(defview new-public-chat-toolbar []
(letsubs [topic [:get :public-group-topic]]
(let [create-btn-enabled? (spec/valid? ::v/topic topic)]
[view
[status-bar]
[toolbar
{:title (label :t/new-public-group-chat)
:actions [{:image {:source {:uri (if create-btn-enabled?
:icon_ok_blue
:icon_ok_disabled)}
:style common/icon-ok}
:handler (when create-btn-enabled?
#(dispatch [:create-new-public-chat topic]))}]}]])))
(defview chat-name-input []
(letsubs [topic [:get :public-group-topic]]
[view
[text-field
{:error (cond
(not (spec/valid? ::v/not-empty-string topic))
(label :t/empty-topic)
(not (spec/valid? ::v/topic topic))
(label :t/topic-format))
:wrapper-style styles/group-chat-name-wrapper
:error-color common/color-blue
:line-color common/color-gray4
:label-hidden? true
:input-style styles/group-chat-topic-input
:auto-focus true
:on-change-text #(dispatch [:set :public-group-topic %])
:value topic
:validator #(re-matches #"[a-z\-]*" %)
:auto-capitalize :none}]
[text {:style styles/topic-hash} "#"]]))
(defn new-public-chat []
[view styles/group-container
[new-public-chat-toolbar]
[view styles/chat-name-container
[text {:style styles/members-text
:font :medium}
(label :t/public-group-topic)]
[chat-name-input]]])

View File

@ -68,7 +68,7 @@
:icon :settings
:icon-style {:width 20
:height 13}
:handler #(dispatch [:show-group-settings])})
:handler #(dispatch [:show-group-chat-settings])})
(defn group-chat-items [members public?]
(into (if public? [] [(item-members members)])

View File

@ -9,7 +9,7 @@
[status-im.components.tabs.bottom-shadow :refer [bottom-shadow-view]]
[status-im.chats-list.screen :refer [chats-list]]
[status-im.discover.screen :refer [discover]]
[status-im.contacts.views :refer [contact-list]]
[status-im.contacts.views :refer [contact-groups-list]]
[status-im.components.tabs.tabs :refer [tabs]]
[status-im.components.tabs.styles :as st]
[status-im.components.styles :as common-st]
@ -31,7 +31,7 @@
:index 1}
{:view-id :contact-list
:title (label :t/contacts)
:screen contact-list
:screen contact-groups-list
:icon-inactive :icon_contacts
:icon-active :icon_contacts_active
:index 2}])
@ -103,13 +103,13 @@
[view {:style common-st/flex}
[swiper (merge
(st/main-swiper @tabs-hidden?)
{:index (get-tab-index @view-id)
:loop false
:ref #(reset! main-swiper %)
:on-momentum-scroll-end (on-scroll-end swiped? scroll-ended @view-id)})
{:index (get-tab-index @view-id)
:loop false
:ref #(reset! main-swiper %)
:on-momentum-scroll-end (on-scroll-end swiped? scroll-ended @view-id)})
[chats-list]
[discover (= @view-id :discover)]
[contact-list (= @view-id :contact-list)]]
[contact-groups-list (= @view-id :contact-list)]]
[tabs {:selected-view-id @view-id
:prev-view-id @prev-view-id
:tab-list tab-list}]

View File

@ -56,8 +56,7 @@
(defview contact-list []
(letsubs [edit? [:get-in [:contacts/list-ui-props :edit?]]
group [:get :contacts-group]
type [:get :group-type]]
group [:get-contact-group]]
[drawer-view
[view {:flex 1}
[view

View File

@ -1,7 +1,7 @@
(ns status-im.contacts.db
(:require-macros [status-im.utils.db :refer [allowed-keys]])
(:require [cljs.spec.alpha :as s]
[clojure.string :as str]
(:require [cljs.spec.alpha :as spec]
[clojure.string :as string]
[status-im.data-store.contacts :as contacts]
[status-im.js-dependencies :as dependencies]))
@ -14,7 +14,7 @@
(.isAddress dependencies/Web3.prototype s))
(defn hex-string? [s]
(let [s' (if (str/starts-with? s "0x")
(let [s' (if (string/starts-with? s "0x")
(subs s 2)
s)]
(boolean (re-matches #"(?i)[0-9a-f]+" s'))))
@ -24,73 +24,73 @@
(and
(hex-string? identity)
(or
(and (= 128 length) (not (str/includes? identity "0x")))
(and (= 130 length) (str/starts-with? identity "0x"))
(and (= 132 length) (str/starts-with? identity "0x04"))
(and (= 128 length) (not (string/includes? identity "0x")))
(and (= 130 length) (string/starts-with? identity "0x"))
(and (= 132 length) (string/starts-with? identity "0x04"))
(is-address? identity)))))
(s/def ::not-empty-string (s/and string? not-empty))
(s/def ::public-key (s/and ::not-empty-string valid-length?))
(spec/def ::not-empty-string (spec/and string? not-empty))
(spec/def ::public-key (spec/and ::not-empty-string valid-length?))
;;;; DB
;;Contact
;we can't validate public key, because for dapps whisper-identity is just string
(s/def :contact/whisper-identity ::not-empty-string)
(s/def :contact/name ::not-empty-string)
(s/def :contact/address (s/nilable is-address?))
(s/def :contact/private-key (s/nilable string?))
(s/def :contact/public-key (s/nilable string?))
(s/def :contact/photo-path (s/nilable string?))
(s/def :contact/status (s/nilable string?))
(spec/def :contact/whisper-identity ::not-empty-string)
(spec/def :contact/name ::not-empty-string)
(spec/def :contact/address (spec/nilable is-address?))
(spec/def :contact/private-key (spec/nilable string?))
(spec/def :contact/public-key (spec/nilable string?))
(spec/def :contact/photo-path (spec/nilable string?))
(spec/def :contact/status (spec/nilable string?))
(s/def :contact/last-updated (s/nilable int?))
(s/def :contact/last-online (s/nilable int?))
(s/def :contact/pending? boolean?)
(s/def :contact/unremovable? boolean?)
(spec/def :contact/last-updated (spec/nilable int?))
(spec/def :contact/last-online (spec/nilable int?))
(spec/def :contact/pending? boolean?)
(spec/def :contact/unremovable? boolean?)
(s/def :contact/dapp? boolean?)
(s/def :contact/dapp-url (s/nilable string?))
(s/def :contact/dapp-hash (s/nilable int?))
(s/def :contact/bot-url (s/nilable string?))
(s/def :contact/global-command (s/nilable map?))
(s/def :contact/commands (s/nilable (s/map-of keyword? map?)))
(s/def :contact/responses (s/nilable (s/map-of keyword? map?)))
(s/def :contact/commands-loaded? (s/nilable boolean?))
(s/def :contact/subscriptions (s/nilable map?))
(spec/def :contact/dapp? boolean?)
(spec/def :contact/dapp-url (spec/nilable string?))
(spec/def :contact/dapp-hash (spec/nilable int?))
(spec/def :contact/bot-url (spec/nilable string?))
(spec/def :contact/global-command (spec/nilable map?))
(spec/def :contact/commands (spec/nilable (spec/map-of keyword? map?)))
(spec/def :contact/responses (spec/nilable (spec/map-of keyword? map?)))
(spec/def :contact/commands-loaded? (spec/nilable boolean?))
(spec/def :contact/subscriptions (spec/nilable map?))
;true when contact added using status-dev-cli
(s/def :contact/debug? boolean?)
(spec/def :contact/debug? boolean?)
(s/def :contact/contact (allowed-keys
:req-un [:contact/name :contact/whisper-identity]
:opt-un [:contact/address :contact/private-key :contact/public-key :contact/photo-path
:contact/status :contact/last-updated :contact/last-online :contact/pending?
:contact/unremovable? :contact/dapp? :contact/dapp-url :contact/dapp-hash
:contact/bot-url :contact/global-command :contact/commands-loaded?
:contact/commands :contact/responses :contact/debug? :contact/subscriptions]))
(spec/def :contact/contact (allowed-keys
:req-un [:contact/name :contact/whisper-identity]
:opt-un [:contact/address :contact/private-key :contact/public-key :contact/photo-path
:contact/status :contact/last-updated :contact/last-online :contact/pending?
:contact/unremovable? :contact/dapp? :contact/dapp-url :contact/dapp-hash
:contact/bot-url :contact/global-command :contact/commands-loaded?
:contact/commands :contact/responses :contact/debug? :contact/subscriptions]))
;;Contact list ui props
(s/def :contact-list-ui/edit? boolean?)
(spec/def :contact-list-ui/edit? boolean?)
;;Contacts ui props
(s/def :contacts-ui/edit? boolean?)
(spec/def :contacts-ui/edit? boolean?)
(s/def :contacts/contacts (s/nilable (s/map-of ::not-empty-string :contact/contact)))
(spec/def :contacts/contacts (spec/nilable (spec/map-of ::not-empty-string :contact/contact)))
;public key of new contact during adding this new contact
(s/def :contacts/new-identity (s/nilable string?))
(s/def :contacts/new-public-key-error (s/nilable string?))
(spec/def :contacts/new-identity (spec/nilable string?))
(spec/def :contacts/new-public-key-error (spec/nilable string?))
;on showing this contact's profile (andrey: better to move into profile ns)
(s/def :contacts/identity (s/nilable ::not-empty-string))
(s/def :contacts/list-ui-props (s/nilable (allowed-keys :opt-un [:contact-list-ui/edit?])))
(s/def :contacts/ui-props (s/nilable (allowed-keys :opt-un [:contacts-ui/edit?])))
(spec/def :contacts/identity (spec/nilable ::not-empty-string))
(spec/def :contacts/list-ui-props (spec/nilable (allowed-keys :opt-un [:contact-list-ui/edit?])))
(spec/def :contacts/ui-props (spec/nilable (allowed-keys :opt-un [:contacts-ui/edit?])))
;used in modal list (for example for wallet)
(s/def :contacts/click-handler (s/nilable fn?))
(spec/def :contacts/click-handler (spec/nilable fn?))
;used in modal list (for example for wallet)
(s/def :contacts/click-action (s/nilable #{:send :request}))
(spec/def :contacts/click-action (spec/nilable #{:send :request}))
;used in modal list (for example for wallet)
(s/def :contacts/click-params (s/nilable map?))
(spec/def :contacts/click-params (spec/nilable map?))

View File

@ -12,11 +12,11 @@
[cljs.reader :refer [read-string]]
[status-im.utils.js-resources :as js-res]
[status-im.react-native.js-dependencies :as rn-dependencies]
[status-im.contacts.navigation]
[status-im.utils.identicon :refer [identicon]]
[status-im.utils.gfycat.core :refer [generate-gfy]]
[status-im.i18n :refer [label]]
[status-im.contacts.db :as v]))
[status-im.contacts.db :as v]
[status-im.contacts.navigation]))
;;;; COFX
@ -208,7 +208,7 @@
(subs (.sha3 js/Web3.prototype normalized-key #js {:encoding "hex"}) 26))))
(defn- prepare-default-groups-events [groups default-groups]
[[:add-groups
[[:add-contact-groups
(for [[id {:keys [name contacts]}] default-groups
:let [id' (clojure.core/name id)]
:when (not (get groups id'))]
@ -412,9 +412,9 @@
(register-handler-fx
:remove-contact-from-group
(fn [{:keys [db]} [_ whisper-identity group-id]]
(let [{:keys [contact-groups]} db
(let [{:group/keys [contact-groups]} db
group' (update (contact-groups group-id) :contacts (remove-contact-from-group whisper-identity))]
{:dispatch [:update-group group']})))
{:dispatch [:update-contact-group group']})))
(register-handler-fx
:remove-contact
@ -428,8 +428,8 @@
:open-contact-toggle-list
(fn [{:keys [db]} [_ group-type]]
{:db (-> db
(assoc :group-type group-type
:selected-contacts #{}
(assoc :group/group-type group-type
:group/selected-contacts #{}
:new-chat-name "")
(assoc-in [:toolbar-search :show] nil)
(assoc-in [:toolbar-search :text] ""))

View File

@ -1,33 +1,10 @@
(ns status-im.contacts.navigation
(:require [status-im.navigation.handlers :as nav]))
(defmethod nav/preload-data! :group-contacts
[db [_ _ group show-search?]]
(-> db
(assoc :contacts-group group)
(update :toolbar-search assoc
:show (when show-search? :contact-list)
:text "")))
(defmethod nav/preload-data! :edit-group
[db [_ _ group group-type]]
(if group
(assoc db :contact-group-id (:group-id group)
:group-type group-type
:new-chat-name (:name group))
db))
(defmethod nav/preload-data! :contact-list
[db [_ _ click-handler]]
(-> db
(assoc-in [:toolbar-search :show] nil)
(assoc-in [:contacts/list-ui-props :edit?] false)
(assoc-in [:contacts/ui-props :edit?] false)
(assoc :contacts/click-handler click-handler)))
(defmethod nav/preload-data! :reorder-groups
[db [_ _]]
(assoc db :groups-order (->> (vals (:contact-groups db))
(remove :pending?)
(sort-by :order >)
(map :group-id))))
(assoc :contacts/click-handler click-handler)))

View File

@ -50,7 +50,7 @@
(reg-sub
:group-contacts
(fn [db [_ group-id]]
(get-in db [:contact-groups group-id :contacts])))
(get-in db [:group/contact-groups group-id :contacts])))
(reg-sub
:all-added-group-contacts
@ -98,16 +98,11 @@
(fn [contacts]
(count contacts)))
(reg-sub
:contact-groups
(fn [db]
(vals (:contact-groups db))))
(reg-sub
:all-added-groups
:<- [:contact-groups]
:<- [:get-contact-groups]
(fn [groups]
(->> (remove :pending? groups)
(->> (remove :pending? (vals groups))
(sort-by :order >))))
(defn search-filter [text item]
@ -133,8 +128,9 @@
(reg-sub
:contact-group-contacts
(fn [db]
(get-in db [:contact-groups (:contact-group-id db) :contacts])))
:<- [:get-contact-group]
(fn [group]
(:contacts group)))
(reg-sub
:all-not-added-contact-group-contacts

View File

@ -62,7 +62,7 @@
[common/form-title subtitle
{:count-value contacts-count
:extended? edit?
:options [{:value #(dispatch [:navigate-to :edit-group group :contact-group])
:options [{:value #(dispatch [:navigate-to :edit-contact-group group :contact-group])
:text (label :t/edit-group)}]}])
[view st/contacts-list
[common/list-footer]
@ -85,7 +85,7 @@
[touchable-highlight {:on-press #(do
(when edit?
(dispatch [:set-in [:contacts/list-ui-props :edit?] true]))
(dispatch [:navigate-to :group-contacts group]))}
(dispatch [:navigate-to :group-contacts (:group-id group)]))}
[view
[text {:style st/show-all-text
:uppercase? (get-in platform-specific [:uppercase?])
@ -112,7 +112,7 @@
[ion-icon {:name :md-create
:style create-icon}]]])
(defview contact-list [_]
(defview contact-groups-list [_]
(letsubs [contacts [:get-added-contacts-with-limit contacts-limit]
contacts-count [:added-contacts-count]
edit? [:get-in [:contacts/ui-props :edit?]]

View File

@ -10,8 +10,8 @@
:navigation-stack '()
:contacts/contacts {}
:qr-codes {}
:contact-groups {}
:selected-contacts #{}
:group/contact-groups {}
:group/selected-contacts #{}
:chats {}
:current-chat-id console-chat-id
:loading-allowed true

View File

@ -0,0 +1,102 @@
(ns status-im.group.add-contacts.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :refer [dispatch]]
[status-im.components.renderers.renderers :as renderers]
[status-im.components.react :refer [view keyboard-avoiding-view
text list-view list-item]]
[status-im.components.sticky-button :refer [sticky-button]]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar-new.view :refer [toolbar-with-search]]
[status-im.utils.listview :refer [to-datasource]]
[status-im.group.styles :as styles]
[status-im.contacts.styles :as cstyles]
[status-im.i18n :refer [label]]
[status-im.components.contact.contact :refer [toogle-contact-view]]))
(defn on-toggle [checked? whisper-identity]
(let [action (if checked? :deselect-contact :select-contact)]
(dispatch [action whisper-identity])))
(defn on-toggle-participant [checked? whisper-identity]
(let [action (if checked? :deselect-participant :select-participant)]
(dispatch [action whisper-identity])))
(defn group-toggle-contact [{:keys [whisper-identity] :as contact}]
[toogle-contact-view contact :is-contact-selected? on-toggle])
(defn group-toggle-participant [{:keys [whisper-identity] :as contact}]
[toogle-contact-view contact :is-participant-selected? on-toggle-participant])
(defn title-with-count [title count-value]
[view styles/toolbar-title-with-count
[text {:style styles/toolbar-title-with-count-text
:font :toolbar-title}
title]
(when (pos? count-value)
[view styles/toolbar-title-with-count-container
[text {:style styles/toolbar-title-with-count-text-count
:font :toolbar-title}
count-value]])])
(defview toggle-list-toolbar [title contacts-count]
(letsubs [show-search [:get-in [:toolbar-search :show]]
search-text [:get-in [:toolbar-search :text]]]
(toolbar-with-search
{:show-search? (= show-search :contact-group-list)
:search-text search-text
:search-key :contact-group-list
:custom-title (title-with-count title contacts-count)
:search-placeholder (label :t/search-contacts)})))
(defn toggle-list [contacts render-function]
[view {:flex 1}
[list-view
{:dataSource (to-datasource contacts)
:renderRow (fn [row _ _]
(list-item ^{:key row} [render-function row]))
:renderSeparator renderers/list-separator-renderer
:renderFooter renderers/list-footer-renderer
:renderHeader renderers/list-header-renderer
:style cstyles/contacts-list
:keyboardShouldPersistTaps :always}]])
(defview contact-toggle-list []
(letsubs [contacts [:all-added-group-contacts-filtered]
selected-contacts-count [:selected-contacts-count]
group-type [:get-group-type]]
[keyboard-avoiding-view {:style styles/group-container}
[status-bar]
[toggle-list-toolbar
(label (if (= group-type :contact-group)
:t/new-group
:t/new-group-chat))
selected-contacts-count]
[toggle-list contacts group-toggle-contact]
(when (pos? selected-contacts-count)
[sticky-button (label :t/next) #(dispatch [:navigate-to :new-group])])]))
(defview add-contacts-toggle-list []
(letsubs [contacts [:all-group-not-added-contacts-filtered]
group [:get-contact-group]
selected-contacts-count [:selected-contacts-count]]
[keyboard-avoiding-view {:style styles/group-container}
[status-bar]
[toggle-list-toolbar (:name group) selected-contacts-count]
[toggle-list contacts group-toggle-contact]
(when (pos? selected-contacts-count)
[sticky-button (label :t/save) #(do
(dispatch [:add-selected-contacts-to-group])
(dispatch [:navigate-back]))])]))
(defview add-participants-toggle-list []
(letsubs [contacts [:contacts-filtered :all-new-contacts]
chat-name [:chat :name]
selected-contacts-count [:selected-participants-count]]
[keyboard-avoiding-view {:style styles/group-container}
[status-bar]
[toggle-list-toolbar chat-name selected-contacts-count]
[toggle-list contacts group-toggle-participant]
(when (pos? selected-contacts-count)
[sticky-button (label :t/save) #(do
(dispatch [:add-new-group-chat-participants])
(dispatch [:navigate-back]))])]))

View File

@ -0,0 +1,177 @@
(ns status-im.group.chat-settings.events
(:require [re-frame.core :refer [dispatch reg-fx]]
[status-im.utils.handlers :refer [register-handler-fx]]
[status-im.protocol.core :as protocol]
[status-im.utils.random :as random]
[status-im.chat.handlers :as chat-events]
[status-im.data-store.contacts :as contacts]
[status-im.data-store.messages :as messages]
[status-im.data-store.chats :as chats]
[status-im.constants :refer [text-content-type]]))
;;;; COFX
;;;; FX
(reg-fx
::save-chat-property
(fn [[current-chat-id property-name value]]
(chats/save-property current-chat-id property-name value)))
(reg-fx
::add-members-to-chat
(fn [{:keys [current-chat-id selected-participants]}]
(chats/add-contacts current-chat-id selected-participants)))
(reg-fx
::remove-members-from-chat
(fn [[current-chat-id participants]]
(chats/remove-contacts current-chat-id participants)))
(defn system-message [message-id content]
{:from "system"
:message-id message-id
:content content
:content-type text-content-type})
(defn removed-participant-message [chat-id identity]
(let [contact-name (:name (contacts/get-by-id identity))]
(->> (str "You've removed " (or contact-name identity))
(system-message (random/id))
(messages/save chat-id))))
(reg-fx
::create-removing-messages
(fn [{:keys [current-chat-id participants]}]
(doseq [participant participants]
(removed-participant-message current-chat-id participant))))
(reg-fx
::notify-about-new-members
(fn [{:keys [current-chat-id selected-participants
current-public-key chats web3]}]
(let [{:keys [name contacts]} (chats current-chat-id)
identities (map :identity contacts)
{:keys [public private]
:as new-keypair} (protocol/new-keypair!)
group-message {:web3 web3
:group {:id current-chat-id
:name name
:contacts (conj identities current-public-key)
:admin current-public-key}
:message {:from current-public-key
:message-id (random/id)}}]
(dispatch [:update-chat! {:chat-id current-chat-id
:public-key public
:private-key private}])
(protocol/start-watching-group! {:web3 web3
:group-id current-chat-id
:identity current-public-key
:keypair new-keypair
:callback #(dispatch [:incoming-message %1 %2])})
(protocol/invite-to-group!
(-> group-message
(assoc-in [:group :keypair] new-keypair)
(assoc :identities selected-participants)))
(protocol/update-group!
(-> group-message
(assoc-in [:group :keypair] new-keypair)
(assoc :identities identities)))
(doseq [identity selected-participants]
(protocol/add-to-group! {:web3 web3
:group-id current-chat-id
:identity identity
:keypair new-keypair
:message {:from current-public-key
:message-id (random/id)}})))))
(reg-fx
::notify-about-removing
(fn [{:keys [web3 current-chat-id participants chats current-public-key]}]
(let [{:keys [private public] :as new-keypair} (protocol/new-keypair!)
{:keys [name private-key public-key]
:as chat} (get chats current-chat-id)
old-keypair {:private private-key
:public public-key}
contacts (get chat :contacts)
identities (-> (map :identity contacts)
set
(clojure.set/difference participants))]
(dispatch [:update-chat! {:chat-id current-chat-id
:private-key private
:public-key public}])
(doseq [participant participants]
(let [id (random/id)]
(doseq [keypair [old-keypair new-keypair]]
(protocol/remove-from-group!
{:web3 web3
:group-id current-chat-id
:identity participant
:keypair keypair
:message {:from current-public-key
:message-id id}}))))
(protocol/start-watching-group!
{:web3 web3
:group-id current-chat-id
:identity current-public-key
:keypair new-keypair
:callback #(dispatch [:incoming-message %1 %2])})
(protocol/update-group!
{:web3 web3
:group {:id current-chat-id
:name name
:contacts (conj identities current-public-key)
:admin current-public-key
:keypair new-keypair}
:identities identities
:message {:from current-public-key
:message-id (random/id)}}))))
;;;; Handlers
(register-handler-fx
:show-group-chat-settings
(fn [{{:keys [current-chat-id] :as db} :db} _]
{:db (assoc db :new-chat-name (get-in db [:chats current-chat-id :name])
:group/group-type :chat-group)
:dispatch [:navigate-to :chat-group-settings]}))
(register-handler-fx
:add-new-group-chat-participants
(fn [{{:keys [current-chat-id selected-participants] :as db} :db} _]
(let [new-identities (map #(hash-map :identity %) selected-participants)]
{:db (-> db
(update-in [:chats current-chat-id :contacts] concat new-identities)
(assoc :selected-participants #{}))
::add-members-to-chat (select-keys db [:current-chat-id :selected-participants])
::notify-about-new-members (select-keys db [:current-chat-id :selected-participants
:current-public-key :chats :web3])})))
(defn remove-identities [collection identities]
(remove #(identities (:identity %)) collection))
(register-handler-fx
:remove-group-chat-participants
(fn [{{:keys [current-chat-id] :as db} :db} [_ participants]]
{:db (update-in db [:chats current-chat-id :contacts] remove-identities participants)
::remove-members-from-chat [current-chat-id participants]
::notify-about-removing (merge {:participants participants}
(select-keys db [:web3 :current-chat-id :chats :current-public-key]))
::create-removing-messages (merge {:participants participants}
(select-keys db [:current-chat-id]))}))
(register-handler-fx
:set-chat-name
(fn [{{:keys [current-chat-id new-chat-name] :as db} :db} _]
{:db (assoc-in db [:chats current-chat-id :name] new-chat-name)
::save-chat-property [current-chat-id :name new-chat-name]}))
(register-handler-fx
:clear-history
(fn [{{:keys [current-chat-id] :as db} :db} _]
{:db (-> db
(assoc-in [:chats current-chat-id :messages] '())
(assoc-in [:chats current-chat-id :last-message] nil))
::chat-events/delete-messages current-chat-id}))

View File

@ -1,8 +1,9 @@
(ns status-im.group-settings.subs
(ns status-im.group.chat-settings.subs
(:require [re-frame.core :refer [reg-sub]]
[status-im.constants :refer [max-chat-name-length]]))
(reg-sub :selected-participant
(reg-sub
:selected-participant
(fn [db]
(let [identity (first (:selected-participants db))]
(get-in db [:contacts/contacts identity]))))
@ -14,12 +15,19 @@
(when (< max-chat-name-length (count chat-name))
"Chat name is too long"))))
(reg-sub :new-chat-name-validation-messages
(reg-sub
:new-chat-name
(fn [db]
(let [chat-name (:new-chat-name db)]
(get-chat-name-validation-messages chat-name))))
(:new-chat-name db)))
(reg-sub :new-chat-name-valid?
(fn [db]
(let [chat-name (:new-chat-name db)]
(zero? (count (get-chat-name-validation-messages chat-name))))))
(reg-sub
:new-chat-name-validation-messages
:<- [:new-chat-name]
(fn [chat-name]
(get-chat-name-validation-messages chat-name)))
(reg-sub
:new-chat-name-valid?
:<- [:new-chat-name]
(fn [chat-name]
(zero? (count (get-chat-name-validation-messages chat-name)))))

View File

@ -0,0 +1,65 @@
(ns status-im.group.chat-settings.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :refer [dispatch]]
[status-im.components.contact.contact :refer [contact-view]]
[status-im.components.common.common :as common]
[status-im.components.react :refer [view scroll-view keyboard-avoiding-view]]
[status-im.components.sticky-button :refer [sticky-button]]
[status-im.group.styles :as styles]
[status-im.group.views :refer [group-toolbar group-chat-settings-btns
group-name-view add-btn more-btn]]
[status-im.group.db :as v]
[status-im.i18n :refer [label]]
[cljs.spec.alpha :as spec]))
(def ^:const contacts-limit 3)
(defview chat-group-contacts-view [admin?]
(letsubs [contacts [:current-chat-contacts]]
(let [limited-contacts (take contacts-limit contacts)
contacts-count (count contacts)]
[view
(when (and admin? (pos? contacts-count))
[common/list-separator])
[view
(doall
(map (fn [row]
^{:key row}
[view
[contact-view
{:contact row
:extend-options [{:value #(dispatch [:remove-group-chat-participants #{(:whisper-identity row)}])
:text (label :t/remove)}]
:extended? admin?}]
(when-not (= row (last limited-contacts))
[common/list-separator])])
limited-contacts))]
(when (< contacts-limit contacts-count)
[more-btn contacts-limit contacts-count #(dispatch [:navigate-to :edit-chat-group-contact-list])])])))
(defview chat-group-members []
(letsubs [current-pk [:get :current-public-key]
group-admin [:chat :group-admin]]
(let [admin? (= current-pk group-admin)]
[view
(when admin?
[add-btn #(dispatch [:navigate-to :add-participants-toggle-list])])
[chat-group-contacts-view admin?]])))
(defview chat-group-settings []
(letsubs [new-chat-name [:get :new-chat-name]
chat-name [:chat :name]
type [:get-group-type]]
(let [save-btn-enabled? (and (spec/valid? ::v/name new-chat-name)
(not= new-chat-name chat-name))]
[keyboard-avoiding-view {:style styles/group-container}
[view {:flex 1}
[group-toolbar type true]
[scroll-view
[group-name-view]
[chat-group-members]
[view styles/separator]
[group-chat-settings-btns]]]
(when save-btn-enabled?
[sticky-button (label :t/save) #(dispatch [:set-chat-name])
true])])))

View File

@ -0,0 +1,36 @@
(ns status-im.group.db
(:require-macros [status-im.utils.db :refer [allowed-keys]])
(:require [cljs.spec.alpha :as spec]
[status-im.constants :refer [console-chat-id wallet-chat-id]]
[clojure.string :as string]
[status-im.utils.homoglyph :as utils]))
(spec/def ::not-empty-string (spec/and string? not-empty))
(spec/def ::name ::not-empty-string)
;;;; DB
(spec/def :group/group-id ::not-empty-string)
(spec/def :group/name ::not-empty-string)
(spec/def :group/timestamp int?)
(spec/def :group/pending? boolean?)
(spec/def :group/order int?)
(spec/def :group-contact/identity ::not-empty-string)
(spec/def :group/contact (allowed-keys :req-un [:group-contact/identity]))
(spec/def :group/contacts (spec/nilable (spec/* :group/contact)))
(spec/def :group/contact-group (allowed-keys
:req-un [:group/group-id :group/name :group/timestamp
:group/order :group/contacts]
:opt-un [:group/pending?]))
(spec/def :group/contact-groups (spec/nilable (spec/map-of ::not-empty-string :group/contact-group)))
;;used during editing contact group
(spec/def :group/contact-group-id (spec/nilable string?))
(spec/def :group/group-type (spec/nilable #{:chat-group :contact-group}))
(spec/def :group/selected-contacts (spec/nilable (spec/* string?)))
;;list of group ids
(spec/def :group/groups-order (spec/nilable (spec/* string?)))

View File

@ -0,0 +1,77 @@
(ns status-im.group.edit-contacts.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :refer [dispatch]]
[status-im.components.contact.contact :refer [contact-view]]
[status-im.components.renderers.renderers :as renderers]
[status-im.components.react :refer [view list-view list-item]]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar-new.view :refer [toolbar-with-search]]
[status-im.utils.listview :refer [to-datasource]]
[status-im.group.styles :as styles]
[status-im.i18n :refer [label]]))
(defview contact-list-toolbar [title]
(letsubs [show-search [:get-in [:toolbar-search :show]]
search-text [:get-in [:toolbar-search :text]]]
(toolbar-with-search
{:show-search? (= show-search :contact-list)
:search-text search-text
:search-key :contact-list
:title title
:search-placeholder (label :t/search-contacts)})))
(defn contacts-list [contacts extended? extend-options]
[view {:flex 1}
[list-view {:dataSource (to-datasource contacts)
:enableEmptySections true
:renderRow (fn [row _ _]
(list-item
^{:key row}
[contact-view {:contact row
:extended? extended?
:extend-options (extend-options row)}]))
:bounces false
:keyboardShouldPersistTaps :always
:renderSeparator renderers/list-separator-renderer
:renderFooter renderers/list-footer-renderer
:renderHeader renderers/list-header-renderer}]])
(defn chat-extended-options [item]
[{:value #(dispatch [:remove-group-chat-participants #{(:whisper-identity item)}])
:text (label :t/remove)}])
(defn contact-extended-options [group-id]
(fn [item]
[{:value #(dispatch [:remove-contact-from-group
(:whisper-identity item)
group-id])
:text (label :t/remove-from-group)}]))
(defview edit-chat-group-contact-list []
(letsubs [chat-name [:chat :name]
contacts [:contacts-filtered :current-chat-contacts]
current-pk [:get :current-public-key]
group-admin [:chat :group-admin]]
(let [admin? (= current-pk group-admin)]
[view styles/group-container
[status-bar]
[contact-list-toolbar chat-name]
[contacts-list
contacts
admin?
chat-extended-options]])))
(defview contacts-list-view [group-id]
(letsubs [contacts [:all-added-group-contacts-filtered group-id]]
[contacts-list
contacts
true
(contact-extended-options group-id)]))
(defview edit-contact-group-contact-list []
(letsubs [group [:get-contact-group]
type [:get-group-type]]
[view styles/group-container
[status-bar]
[contact-list-toolbar (:name group)]
[contacts-list-view (:group-id group)]]))

View File

@ -0,0 +1,269 @@
(ns status-im.group.events
(:require [status-im.protocol.core :as protocol]
[re-frame.core :refer [dispatch reg-fx reg-cofx inject-cofx]]
[status-im.utils.handlers :refer [register-handler-db register-handler-fx]]
[status-im.components.styles :refer [default-chat-color]]
[status-im.data-store.chats :as chats]
[status-im.data-store.contact-groups :as groups]
[clojure.string :as string]
[status-im.utils.random :as random]
[status-im.group.navigation]))
;;;; COFX
(reg-cofx
::get-all-contact-groups
(fn [coeffects _]
(let [groups (->> (groups/get-all)
(map (fn [{:keys [group-id] :as group}]
[group-id group]))
(into {}))]
(assoc coeffects :all-groups groups))))
;;;; FX
(reg-fx
::save-chat
(fn [new-chat]
(chats/save new-chat)))
(reg-fx
::save-contact-group
(fn [new-group]
(groups/save new-group)))
(reg-fx
::save-contact-groups
(fn [new-groups]
(groups/save-all new-groups)))
(reg-fx
::save-contact-group-property
(fn [[contact-group-id property-name value]]
(groups/save-property contact-group-id property-name value)))
(reg-fx
::add-contacts-to-contact-group
(fn [[contact-group-id selected-contacts]]
(groups/add-contacts contact-group-id selected-contacts)))
(reg-fx
::start-listen-group
(fn [{:keys [new-chat web3 current-public-key]}]
(let [{:keys [chat-id public-key private-key contacts name]} new-chat
identities (mapv :identity contacts)]
(protocol/invite-to-group!
{:web3 web3
:group {:id chat-id
:name name
:contacts (conj identities current-public-key)
:admin current-public-key
:keypair {:public public-key
:private private-key}}
:identities identities
:message {:from current-public-key
:message-id (random/id)}})
(protocol/start-watching-group!
{:web3 web3
:group-id chat-id
:identity current-public-key
:keypair {:public public-key
:private private-key}
:callback #(dispatch [:incoming-message %1 %2])}))))
(reg-fx
::start-watching-group
(fn [{:keys [group-id web3 current-public-key keypair]}]
(protocol/start-watching-group!
{:web3 web3
:group-id group-id
:identity current-public-key
:keypair keypair
:callback #(dispatch [:incoming-message %1 %2])})))
;;;; Handlers
(register-handler-db
:deselect-contact
(fn [db [_ id]]
(update db :group/selected-contacts disj id)))
(register-handler-db
:select-contact
(fn [db [_ id]]
(update db :group/selected-contacts conj id)))
(register-handler-db
:deselect-participant
(fn [db [_ id]]
(update db :selected-participants disj id)))
(register-handler-db
:select-participant
(fn [db [_ id]]
(update db :selected-participants conj id)))
(defn group-name-from-contacts [contacts selected-contacts username]
(->> (select-keys contacts selected-contacts)
vals
(map :name)
(cons username)
(string/join ", ")))
(defn prepare-chat [{:keys [current-public-key username]
:group/keys [selected-contacts]
:contacts/keys [contacts]} group-name]
(let [selected-contacts' (mapv #(hash-map :identity %) selected-contacts)
chat-name (if-not (string/blank? group-name)
group-name
(group-name-from-contacts contacts selected-contacts username))
{:keys [public private]} (protocol/new-keypair!)]
{:chat-id (random/id)
:public-key public
:private-key private
:name chat-name
:color default-chat-color
:group-chat true
:group-admin current-public-key
:is-active true
:timestamp (random/timestamp)
:contacts selected-contacts'}))
(register-handler-fx
:create-new-group-chat-and-open
(fn [{:keys [db]} [_ group-name]]
(let [new-chat (prepare-chat (select-keys db [:group/selected-contacts :current-public-key :username
:contacts/contacts])
group-name)]
{:db (-> db
(assoc-in [:chats (:chat-id new-chat)] new-chat)
(assoc :group/selected-contacts #{}))
::save-chat new-chat
::start-listen-group (merge {:new-chat new-chat}
(select-keys db [:web3 :current-public-key]))
:dispatch-n [[:navigate-to-clean :chat-list]
[:navigate-to :chat (:chat-id new-chat)]]})))
(register-handler-fx
:group-chat-invite-received
(fn [{{:keys [current-public-key] :as db} :db}
[_ {:keys [from]
{:keys [group-id group-name contacts keypair timestamp]} :payload}]]
(let [{:keys [private public]} keypair]
(let [contacts' (keep (fn [ident]
(when (not= ident current-public-key)
{:identity ident})) contacts)
chat {:chat-id group-id
:name group-name
:group-chat true
:group-admin from
:public-key public
:private-key private
:contacts contacts'
:added-to-at timestamp
:timestamp timestamp
:is-active true}
exists? (chats/exists? group-id)]
(when (or (not exists?) (chats/new-update? timestamp group-id))
{::start-watching-group (merge {:group-id group-id
:keypair keypair}
(select-keys db [:web3 :current-public-key]))
:dispatch (if exists?
[:update-chat! chat]
[:add-chat group-id chat])})))))
(register-handler-fx
:create-new-contact-group
(fn [{{:group/keys [contact-groups selected-contacts] :as db} :db} [_ group-name]]
(let [selected-contacts' (mapv #(hash-map :identity %) selected-contacts)
new-group {:group-id (random/id)
:name group-name
:order (count contact-groups)
:timestamp (random/timestamp)
:contacts selected-contacts'}]
{:db (update db :group/contact-groups merge {(:group-id new-group) new-group})
::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})
::save-contact-group new-group}))
(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')
::save-contact-groups (into [] (vals new-groups'))})))
(register-handler-fx
:load-contact-groups
[(inject-cofx ::get-all-contact-groups)]
(fn [{:keys [db all-groups]} _]
{:db (assoc db :group/contact-groups all-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))
::save-contact-groups new-groups})))
(register-handler-fx
:set-contact-group-name
(fn [{{:keys [new-chat-name] :group/keys [contact-group-id] :as db} :db} _]
{:db (assoc-in db [:group/contact-groups contact-group-id :name] new-chat-name)
::save-contact-group-property [contact-group-id :name new-chat-name]}))
(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))))
::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))))
::add-contacts-to-contact-group [group-id contacts]}))))
(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)
::save-contact-group-property [contact-group-id :pending? true]}))

View File

@ -0,0 +1,48 @@
(ns status-im.group.navigation
(:require [status-im.navigation.handlers :as nav]))
(defn clear-toolbar-search [db]
(-> db
(assoc-in [:toolbar-search :show] nil)
(assoc-in [:toolbar-search :text] "")))
(defmethod nav/preload-data! :add-contacts-toggle-list
[db _]
(->
(assoc db :group/selected-contacts #{})
(clear-toolbar-search)))
(defmethod nav/preload-data! :add-participants-toggle-list
[db _]
(->
(assoc db :selected-participants #{})
(clear-toolbar-search)))
(defmethod nav/preload-data! :new-public-chat
[db]
(dissoc db :public-group-topic))
(defmethod nav/preload-data! :group-contacts
[db [_ _ group-id show-search?]]
(-> db
(assoc :group/contact-group-id group-id)
(update :toolbar-search
assoc
:show (when show-search? :contact-list)
:text "")))
(defmethod nav/preload-data! :edit-contact-group
[db [_ _ group group-type]]
(if group
(assoc db :group/contact-group-id (:group-id group)
:group/group-type group-type
:new-chat-name (:name group))
db))
(defmethod nav/preload-data! :reorder-groups
[db [_ _]]
(assoc db :group/groups-order (->> (vals (:group/contact-groups db))
(remove :pending?)
(sort-by :order >)
(map :group-id))))

View File

@ -0,0 +1,60 @@
(ns status-im.group.reorder.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [reagent.core :as reagent]
[re-frame.core :refer [dispatch dispatch-sync]]
[status-im.components.react :refer [view text icon list-item]]
[status-im.components.sticky-button :refer [sticky-button]]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar-new.view :refer [toolbar]]
[status-im.components.sortable-list-view :refer [sortable-list-view sortable-item]]
[status-im.components.common.common :as common]
[status-im.group.styles :as styles]
[status-im.i18n :refer [label label-pluralize]]))
(defn toolbar-view []
[toolbar {:actions [{:image :blank}]
:title (label :t/reorder-groups)}])
(defn group-item [{:keys [name contacts] :as group}]
(let [cnt (count contacts)]
[view styles/order-item-container
[view styles/order-item-inner-container
[text {:style styles/order-item-label}
name]
[text {:style styles/order-item-contacts}
(str cnt " " (label-pluralize cnt :t/contact-s))]
[view {:flex 1}]
[view styles/order-item-icon
[icon :grab_gray]]]]))
(defn render-separator [last]
(fn [_ row-id _]
(list-item
(if (= row-id last)
^{:key "bottom-shadow"}
[common/bottom-shadow]
^{:key row-id}
[view styles/order-item-separator-wrapper
[view styles/order-item-separator]]))))
(defview reorder-groups []
(letsubs [groups [:get-contact-groups]
order [:get :group/groups-order]]
(let [this (reagent/current-component)]
[view styles/reorder-groups-container
[status-bar]
[toolbar-view]
[view styles/reorder-list-container
[common/top-shadow]
[sortable-list-view
{:data groups
:order order
:on-row-moved #(do (dispatch-sync [:change-contact-group-order (:from %) (:to %)])
(.forceUpdate this))
:render-row (fn [row]
(sortable-item [group-item row]))
:render-separator (render-separator (last order))}]]
[sticky-button (label :t/save) #(do
(dispatch [:save-contact-group-order])
(dispatch [:navigate-to-clean :contact-list]))]])))

View File

@ -1,17 +1,6 @@
(ns status-im.new-group.styles
(ns status-im.group.styles
(:require-macros [status-im.utils.styles :refer [defstyle]])
(:require [status-im.components.styles :refer [color-white
color-blue
color-black
text1-color
text2-color
color-light-blue
color-light-red
color-light-gray
selected-contact-color
color-gray4
color-gray5]]
[status-im.utils.platform :refer [platform-specific] :as p]))
(:require [status-im.components.styles :as common]))
(defn toolbar-icon [enabled?]
{:width 20
@ -21,70 +10,38 @@
(def group-container
{:flex 1
:flex-direction :column
:background-color color-white})
:background-color common/color-white})
(def reorder-groups-container
{:flex 1
:flex-direction :column
:background-color color-light-gray})
:background-color common/color-light-gray})
(defstyle reorder-list-container
{:flex 1
:android {:padding-top 16}})
(def chat-name-container
{:padding-left 16
:margin-top 10})
(def group-name-container
{:margin-top 10})
(def add-button-container
{:margin-left 16})
(def group-chat-name-input
{:font-size 17
:padding-bottom 0
:letter-spacing -0.2
:color text1-color})
(defstyle group-chat-topic-input
{:font-size 14
:line-height 16
:color text1-color
:padding-left 13
:ios {:padding-bottom 0}})
(defstyle topic-hash-style
{:width 10
:position :absolute
:android {:top 8 :left 3}
:ios {:top 6 :left 3}})
(def topic-hash
(merge group-chat-name-input
topic-hash-style))
(def group-chat-name-wrapper
{:padding-top 0
:height 40
:padding-bottom 0})
(defstyle group-name-text
{:letter-spacing -0.1
:color color-gray4
:color common/color-gray4
:ios {:font-size 13}
:android {:font-size 12}})
(defstyle members-text
{:color color-gray4
{:color common/color-gray4
:ios {:letter-spacing -0.2
:font-size 16}
:android {:font-size 14}})
(defstyle members-text-count
{:margin-left 8
:color color-gray4
:color common/color-gray4
:opacity 0.6
:ios {:letter-spacing -0.2
:font-size 16}
@ -110,8 +67,8 @@
:height 24})
(defstyle add-group-text
{:color color-light-blue
:ios {:color color-light-blue
{:color common/color-light-blue
:ios {:color common/color-light-blue
:letter-spacing -0.2
:font-size 17
:line-height 20}
@ -126,10 +83,10 @@
(def delete-group-text
(merge add-group-text
{:color color-light-red}))
{:color common/color-light-red}))
(defstyle delete-group-prompt-text
{:color color-gray4
{:color common/color-gray4
:padding-top 5
:ios {:font-size 14
:letter-spacing -0.2}
@ -162,7 +119,7 @@
:border-radius 50}})
(def order-item-container
{:background-color color-white})
{:background-color common/color-white})
(defstyle order-item-inner-container
{:flex-direction :row
@ -170,7 +127,7 @@
:android {:padding-top 17
:padding-bottom 15
:min-height 56
:background-color color-white}
:background-color common/color-white}
:ios {:padding-vertical 22
:min-height 63}})
@ -178,7 +135,7 @@
{:padding-left 16
:flex-shrink 1
:android {:font-size 16
:color color-black
:color common/color-black
:line-height 24}
:ios {:font-size 17
:line-height 20
@ -186,7 +143,7 @@
(defstyle order-item-contacts
{:padding-left 8
:color color-gray4
:color common/color-gray4
:ios {:font-size 17
:line-height 20
:letter-spacing -0.2}
@ -198,22 +155,22 @@
:ios {:padding-horizontal 20}})
(def order-item-separator-wrapper
{:background-color color-white})
{:background-color common/color-white})
(def order-item-separator
{:height 1
:background-color color-gray5
:background-color common/color-gray5
:ios {:margin-left 16
:opacity 0.5}})
(def toolbar-title-with-count-text
{:color text1-color
{:color common/text1-color
:letter-spacing -0.2
:font-size 17})
(def toolbar-title-with-count-text-count
(merge toolbar-title-with-count-text
{:color color-light-blue}))
{:color common/color-light-blue}))
(def toolbar-title-with-count
{:flex-direction :row})
@ -222,7 +179,7 @@
{:padding-left 6})
(def separator
{:background-color color-gray5
{:background-color common/color-gray5
:height 1
:opacity 0.5})

View File

@ -0,0 +1,59 @@
(ns status-im.group.subs
(:require [re-frame.core :refer [reg-sub subscribe]]
[status-im.utils.subs :as utils]))
(reg-sub
:is-contact-selected?
(utils/contains-sub :group/selected-contacts))
(reg-sub
:is-participant-selected?
(utils/contains-sub :selected-participants))
(defn filter-selected-contacts [selected-contacts contacts]
(remove #(true? (:pending? (contacts %))) selected-contacts))
(reg-sub
:selected-contacts-count
:<- [:get :group/selected-contacts]
:<- [:get-contacts]
(fn [[selected-contacts contacts]]
(count (filter-selected-contacts selected-contacts contacts))))
(reg-sub
:selected-participants-count
:<- [:get :selected-participants]
(fn [selected-participants]
(count selected-participants)))
(defn filter-contacts [selected-contacts added-contacts]
(filter #(selected-contacts (:whisper-identity %)) added-contacts))
(reg-sub
:selected-group-contacts
:<- [:get :group/selected-contacts]
:<- [:all-added-contacts]
(fn [[selected-contacts added-contacts]]
(filter-contacts selected-contacts added-contacts)))
(reg-sub
:get-contact-groups
(fn [db]
(:group/contact-groups db)))
(reg-sub
:get-contact-group-id
(fn [db]
(:group/contact-group-id db)))
(reg-sub
:get-contact-group
:<- [:get-contact-groups]
:<- [:get-contact-group-id]
(fn [[groups group-id]]
(get groups group-id)))
(reg-sub
:get-group-type
(fn [db]
(:group/group-type db)))

View File

@ -0,0 +1,169 @@
(ns status-im.group.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :refer [dispatch]]
[status-im.contacts.styles :as cstyles]
[status-im.components.common.common :as common]
[status-im.components.action-button.action-button :refer [action-button action-separator]]
[status-im.components.react :refer [view text icon touchable-highlight
keyboard-avoiding-view list-view list-item]]
[status-im.components.text-input-with-label.view :refer [text-input-with-label]]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar-new.view :refer [toolbar]]
[status-im.utils.platform :refer [platform-specific]]
[status-im.components.sticky-button :refer [sticky-button]]
[status-im.utils.listview :refer [to-datasource]]
[status-im.components.renderers.renderers :as renderers]
[status-im.components.contact.contact :refer [contact-view]]
[status-im.group.styles :as styles]
[status-im.i18n :refer [label]]
[cljs.spec.alpha :as spec]
[status-im.group.db :as v]
[status-im.utils.utils :as utils]))
(defn group-toolbar [group-type edit?]
[view
[status-bar]
[toolbar
{:title (label
(if (= group-type :contact-group)
(if edit? :t/edit-group :t/new-group)
(if edit? :t/chat-settings :t/new-group-chat)))
:actions [{:image :blank}]}]])
(defview group-name-view []
(letsubs [new-group-name [:get :new-chat-name]]
[view styles/group-name-container
[text-input-with-label
{:auto-focus true
:label (label :t/name)
:on-change-text #(dispatch [:set :new-chat-name %])
:default-value new-group-name}]]))
(defn add-btn [on-press]
[action-button (label :t/add-members)
:add_blue
on-press])
(defn delete-btn [on-press]
[view styles/settings-group-container
[touchable-highlight {:on-press on-press}
[view styles/settings-group-item
[view styles/delete-icon-container
[icon :close_red styles/add-icon]]
[view styles/settings-group-text-container
[text {:style styles/delete-group-text}
(label :t/delete-group)]
[text {:style styles/delete-group-prompt-text}
(label :t/delete-group-prompt)]]]]])
(defn group-chat-settings-btns []
[view styles/settings-group-container
[view {:opacity 0.4}
[touchable-highlight {:on-press #()}
[view styles/settings-group-item
[view styles/settings-icon-container
[icon :speaker_blue styles/add-icon]]
[view styles/settings-group-text-container
[text {:style styles/settings-group-text}
(label :t/mute-notifications)]]]]]
[action-separator]
[action-button (label :t/clear-history)
:close_blue
#(dispatch [:clear-history])]
[action-separator]
[touchable-highlight {:on-press #(dispatch [:leave-group-chat])}
[view styles/settings-group-item
[view styles/delete-icon-container
[icon :arrow_right_red styles/add-icon]]
[view styles/settings-group-text-container
[text {:style styles/delete-group-text}
(label :t/leave-chat)]]]]])
(defn more-btn [contacts-limit contacts-count on-press]
[view
[common/list-separator]
[view cstyles/show-all
[touchable-highlight {:on-press on-press}
[view
[text {:style cstyles/show-all-text
:uppercase? (get-in platform-specific [:uppercase?])
:font (get-in platform-specific [:component-styles :contacts :show-all-text-font])}
(str (- contacts-count contacts-limit) " " (label :t/more))]]]]])
(def ^:const contacts-limit 3)
(defview group-contacts-view [group]
(letsubs [contacts [:all-added-group-contacts-with-limit (:group-id group) contacts-limit]
contacts-count [:all-added-group-contacts-count (:group-id group)]]
[view
(when (pos? contacts-count)
[common/list-separator])
[view
(doall
(map (fn [row]
^{:key row}
[view
[contact-view
{:contact row
:extend-options [{:value #(dispatch [:remove-contact-from-group
(:whisper-identity row)
(:group-id group)])
:text (label :t/remove-from-group)}]
:extended? true}]
(when-not (= row (last contacts))
[common/list-separator])])
contacts))]
(when (< contacts-limit contacts-count)
[more-btn contacts-limit contacts-count #(dispatch [:navigate-to :edit-group-contact-list])])]))
(defview edit-contact-group []
(letsubs [group-name [:get :new-chat-name]
group [:get-contact-group]
type [:get-group-type]]
(let [save-btn-enabled? (and (spec/valid? ::v/name group-name)
(not= group-name (:name group)))]
[keyboard-avoiding-view {:style styles/group-container}
[group-toolbar type true]
[group-name-view]
[view styles/list-view-container
[add-btn #(dispatch [:navigate-to :add-contacts-toggle-list])]
[group-contacts-view group]
[view styles/separator]
[delete-btn #(utils/show-confirmation
(str (label :t/delete-group) "?") (label :t/delete-group-confirmation) (label :t/delete)
(fn[]
(dispatch [:delete-contact-group])
(dispatch [:navigate-to-clean :contact-list])))]]
(when save-btn-enabled?
[sticky-button (label :t/save) #(dispatch [:set-contact-group-name])])])))
(defn render-row [row _ _]
(list-item
^{:key row}
[contact-view {:contact row}]))
(defview new-group []
(letsubs [contacts [:selected-group-contacts]
group-name [:get :new-chat-name]
group-type [:get-group-type]]
(let [save-btn-enabled? (and (spec/valid? ::v/name group-name) (pos? (count contacts)))]
[keyboard-avoiding-view (merge {:behavior :padding}
styles/group-container)
[group-toolbar group-type false]
[group-name-view]
[view styles/list-view-container
[list-view {:dataSource (to-datasource contacts)
:enableEmptySections true
:renderRow render-row
:bounces false
:keyboardShouldPersistTaps :always
:renderSeparator renderers/list-separator-renderer}]]
(when save-btn-enabled?
[sticky-button (label :t/save)
(if (= group-type :contact-group)
#(do
(dispatch [:create-new-contact-group group-name])
(dispatch [:navigate-to-clean :contact-list]))
#(dispatch [:create-new-group-chat-and-open group-name]))
true])])))

View File

@ -1,198 +0,0 @@
(ns status-im.group-settings.handlers
(:require [re-frame.core :refer [dispatch after enrich]]
[status-im.utils.handlers :refer [register-handler] :as u]
[status-im.chat.handlers :refer [delete-messages!]]
[status-im.protocol.core :as protocol]
[status-im.utils.random :as random]
[status-im.data-store.contacts :as contacts]
[status-im.data-store.messages :as messages]
[status-im.data-store.chats :as chats]
[status-im.constants :refer [text-content-type]]
[status-im.navigation.handlers :as nav]))
(defn save-property!
[current-chat-id property-name value]
(chats/save-property current-chat-id property-name value))
(defn save-chat-property!
[db-name property-name]
(fn [{:keys [current-chat-id] :as db} _]
(let [property (db-name db)]
(save-property! current-chat-id property-name property))))
(defn update-chat-property
[db-name property-name]
(fn [{:keys [current-chat-id] :as db} _]
(let [property (db-name db)]
(assoc-in db [:chats current-chat-id property-name] property))))
(defn prepare-chat-settings
[{:keys [current-chat-id] :as db}]
(let [{:keys [name]} (-> db
(get-in [:chats current-chat-id])
(select-keys [:name :color]))]
(assoc db :new-chat-name name
:group-type :chat-group)))
(register-handler :show-group-settings
(after (fn [_ _] (dispatch [:navigate-to :chat-group-settings])))
prepare-chat-settings)
(register-handler :set-chat-name
(after (save-chat-property! :new-chat-name :name))
(update-chat-property :new-chat-name :name))
(register-handler :set-chat-color
(after (fn [{:keys [current-chat-id]} [_ color]]
(save-property! current-chat-id :color (name color))))
(fn [{:keys [current-chat-id] :as db} [_ color]]
(assoc-in db [:chats current-chat-id :color] color)))
(defn clear-messages
[{:keys [current-chat-id] :as db} _]
(-> db
(assoc-in [:chats current-chat-id :messages] '())
(assoc-in [:chats current-chat-id :last-message] nil)))
(register-handler :clear-history
(after delete-messages!)
clear-messages)
(defn remove-identities [collection identities]
(remove #(identities (:identity %)) collection))
(defn remove-members
[{:keys [current-chat-id selected-participants] :as db} _]
(update-in db [:chats current-chat-id :contacts]
remove-identities selected-participants))
(defn remove-members-from-chat!
[{:keys [current-chat-id selected-participants]} _]
(chats/remove-contacts current-chat-id selected-participants))
(defn notify-about-removing!
[{:keys [web3 current-chat-id selected-participants chats current-public-key]} _]
(let [{:keys [private public] :as new-keypair} (protocol/new-keypair!)
{:keys [name private-key public-key]
:as chat} (get chats current-chat-id)
old-keypair {:private private-key
:public public-key}
contacts (get chat :contacts)
identities (-> (map :identity contacts)
set
(clojure.set/difference selected-participants))]
(dispatch [:update-chat! {:chat-id current-chat-id
:private-key private
:public-key public}])
(doseq [participant selected-participants]
(let [id (random/id)]
(doseq [keypair [old-keypair new-keypair]]
(protocol/remove-from-group!
{:web3 web3
:group-id current-chat-id
:identity participant
:keypair keypair
:message {:from current-public-key
:message-id id}}))))
(protocol/start-watching-group!
{:web3 web3
:group-id current-chat-id
:identity current-public-key
:keypair new-keypair
:callback #(dispatch [:incoming-message %1 %2])})
(protocol/update-group!
{:web3 web3
:group {:id current-chat-id
:name name
:contacts (conj identities current-public-key)
:admin current-public-key
:keypair new-keypair}
:identities identities
:message {:from current-public-key
:message-id (random/id)}})))
(defn system-message [message-id content]
{:from "system"
:message-id message-id
:content content
:content-type text-content-type})
(defn removed-participant-message [chat-id identity]
(let [contact-name (:name (contacts/get-by-id identity))]
(->> (str "You've removed " (or contact-name identity))
(system-message (random/id))
(messages/save chat-id))))
(defn create-removing-messages!
[{:keys [current-chat-id selected-participants]} _]
(doseq [participant selected-participants]
(removed-participant-message current-chat-id participant)))
(defn deselect-members [db _]
(assoc db :selected-participants #{}))
(register-handler :remove-participants
;; todo check if user have rights to add/remove participants
;; todo order of operations tb
(u/handlers->
remove-members
remove-members-from-chat!
notify-about-removing!
create-removing-messages!
deselect-members))
(defn add-members
[{:keys [current-chat-id selected-participants] :as db} _]
(let [new-identities (map #(hash-map :identity %) selected-participants)]
(update-in db [:chats current-chat-id :contacts] concat new-identities)))
(defn add-members-to-chat!
[{:keys [current-chat-id selected-participants]} _]
(chats/add-contacts current-chat-id selected-participants))
(defn notify-about-new-members!
[{:keys [current-chat-id selected-participants
current-public-key chats web3]} _]
(let [{:keys [name contacts]} (chats current-chat-id)
identities (map :identity contacts)
{:keys [public private]
:as new-keypair} (protocol/new-keypair!)
group-message {:web3 web3
:group {:id current-chat-id
:name name
:contacts (conj identities current-public-key)
:admin current-public-key}
:message {:from current-public-key
:message-id (random/id)}}]
(dispatch [:update-chat! {:chat-id current-chat-id
:public-key public
:private-key private}])
(protocol/start-watching-group! {:web3 web3
:group-id current-chat-id
:identity current-public-key
:keypair new-keypair
:callback #(dispatch [:incoming-message %1 %2])})
(protocol/invite-to-group!
(-> group-message
(assoc-in [:group :keypair] new-keypair)
(assoc :identities selected-participants)))
(protocol/update-group!
(-> group-message
(assoc-in [:group :keypair] new-keypair)
(assoc :identities identities)))
(doseq [identity selected-participants]
(protocol/add-to-group! {:web3 web3
:group-id current-chat-id
:identity identity
:keypair new-keypair
:message {:from current-public-key
:message-id (random/id)}}))))
(register-handler :add-new-participants
(u/handlers->
add-members
add-members-to-chat!
notify-about-new-members!
deselect-members))

View File

@ -9,11 +9,11 @@
[status-im.components.permissions :as permissions]
[status-im.utils.handlers :refer [register-handler register-handler-fx] :as u]
status-im.chat.handlers
status-im.group-settings.handlers
status-im.group.chat-settings.events
status-im.navigation.handlers
status-im.contacts.events
status-im.discover.handlers
status-im.new-group.handlers
status-im.group.events
status-im.profile.handlers
status-im.commands.handlers.loading
status-im.commands.handlers.jail
@ -79,7 +79,7 @@
(dispatch [:initialize-sync-listener])
(dispatch [:initialize-chats])
(dispatch [:load-contacts])
(dispatch [:load-groups])
(dispatch [:load-contact-groups])
(dispatch [:init-chat])
(dispatch [:init-discoveries])
(dispatch [:init-debug-mode address])

View File

@ -25,17 +25,16 @@
[status-im.transactions.screens.unsigned-transactions :refer [unsigned-transactions]]
[status-im.transactions.screens.transaction-details :refer [transaction-details]]
[status-im.chats-list.screen :refer [chats-list]]
[status-im.new-chat.screen :refer [new-chat]]
[status-im.new-group.screen-private :refer [new-group
edit-group]]
[status-im.new-group.views.chat-group-settings :refer [chat-group-settings]]
[status-im.new-group.views.contact-list :refer [edit-group-contact-list
edit-chat-group-contact-list]]
[status-im.new-group.views.contact-toggle-list :refer [contact-toggle-list
add-contacts-toggle-list
add-participants-toggle-list]]
[status-im.new-group.views.reorder-groups :refer [reorder-groups]]
[status-im.new-group.screen-public :refer [new-public-group]]
[status-im.chat.new-chat.view :refer [new-chat]]
[status-im.chat.new-public-chat.view :refer [new-public-chat]]
[status-im.group.views :refer [new-group edit-contact-group]]
[status-im.group.chat-settings.views :refer [chat-group-settings]]
[status-im.group.edit-contacts.views :refer [edit-contact-group-contact-list
edit-chat-group-contact-list]]
[status-im.group.add-contacts.views :refer [contact-toggle-list
add-contacts-toggle-list
add-participants-toggle-list]]
[status-im.group.reorder.views :refer [reorder-groups]]
[status-im.profile.screen :refer [profile my-profile]]
[status-im.profile.edit.screen :refer [edit-my-profile]]
[status-im.profile.photo-capture.screen :refer [profile-photo-capture]]
@ -97,14 +96,14 @@
:chat-list main-tabs
:new-chat new-chat
:new-group new-group
:edit-group edit-group
:edit-contact-group edit-contact-group
:chat-group-settings chat-group-settings
:edit-group-contact-list edit-group-contact-list
:edit-group-contact-list edit-contact-group-contact-list
:edit-chat-group-contact-list edit-chat-group-contact-list
:add-contacts-toggle-list add-contacts-toggle-list
:add-participants-toggle-list add-participants-toggle-list
:reorder-groups reorder-groups
:new-public-group new-public-group
:new-public-chat new-public-chat
:contact-list main-tabs
:contact-toggle-list contact-toggle-list
:group-contacts contact-list

View File

@ -1,77 +0,0 @@
(ns status-im.new-chat.screen
(:require-macros [status-im.utils.views :refer [defview]])
(:require [re-frame.core :refer [dispatch]]
[status-im.components.common.common :as common]
[status-im.components.renderers.renderers :as renderers]
[status-im.components.action-button.action-button :refer [action-button
action-separator]]
[status-im.components.action-button.styles :refer [actions-list]]
[status-im.components.react :refer [view text
image
touchable-highlight
list-view
list-item]]
[status-im.components.contact.contact :refer [contact-view]]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar-new.view :refer [toolbar-with-search]]
[status-im.components.drawer.view :refer [drawer-view]]
[status-im.new-chat.styles :as st]
[status-im.utils.listview :as lw]
[status-im.i18n :refer [label]]))
(defn options-list []
[view actions-list
[action-button (label :t/new-group-chat)
:private_group_big
#(dispatch [:open-contact-toggle-list :chat-group])]
[action-separator]
[action-button (label :t/new-public-group-chat)
:public_group_big
#(dispatch [:navigate-to :new-public-group])]
[action-separator]
[action-button (label :t/add-new-contact)
:add_blue
#(dispatch [:navigate-to :new-contact])]])
(defn contact-list-row []
(fn [row _ _]
(list-item ^{:key row}
[contact-view {:contact row
:on-press #(dispatch [:open-chat-with-contact %])}])))
(defview new-chat-toolbar []
[show-search [:get-in [:toolbar-search :show]]
search-text [:get-in [:toolbar-search :text]]]
[view
[status-bar]
(toolbar-with-search
{:show-search? (= show-search :contact-list)
:search-text search-text
:search-key :contact-list
:title (label :t/contacts-group-new-chat)
:search-placeholder (label :t/search-for)})])
(defview new-chat []
[contacts [:all-added-group-contacts-filtered]
params [:get :contacts/click-params]]
[drawer-view
[view st/contacts-list-container
[new-chat-toolbar]
(when contacts
[list-view {:dataSource (lw/to-datasource contacts)
:enableEmptySections true
:renderRow (contact-list-row)
:bounces false
:keyboardShouldPersistTaps :always
:renderHeader #(list-item
[view
[options-list]
[common/bottom-shadow]
[common/form-title (label :t/choose-from-contacts)
{:count-value (count contacts)}]
[common/list-header]])
:renderSeparator renderers/list-separator-renderer
:renderFooter #(list-item [view
[common/list-footer]
[common/bottom-shadow]])
:style st/contacts-list}])]])

View File

@ -1,375 +0,0 @@
(ns status-im.new-group.handlers
(:require [status-im.protocol.core :as protocol]
[re-frame.core :refer [after dispatch debug enrich]]
[status-im.utils.handlers :refer [register-handler] :as u]
[status-im.components.styles :refer [default-chat-color]]
[status-im.data-store.chats :as chats]
[status-im.data-store.contact-groups :as groups]
[clojure.string :as s]
[status-im.i18n :refer [label]]
[status-im.utils.handlers :as u]
[status-im.utils.random :as random]
[taoensso.timbre :refer-macros [debug]]
[taoensso.timbre :as log]
[status-im.navigation.handlers :as nav]))
(defn clear-toolbar-search [db]
(-> db
(assoc-in [:toolbar-search :show] nil)
(assoc-in [:toolbar-search :text] "")))
(defmethod nav/preload-data! :add-contacts-toggle-list
[db _]
(->
(assoc db :selected-contacts #{})
(clear-toolbar-search)))
(defmethod nav/preload-data! :add-participants-toggle-list
[db _]
(->
(assoc db :selected-participants #{})
(clear-toolbar-search)))
(defn deselect-contact
[db [_ id]]
(update db :selected-contacts disj id))
(register-handler :deselect-contact deselect-contact)
(defn select-contact
[db [_ id]]
(update db :selected-contacts conj id))
(register-handler :select-contact select-contact)
(defn group-name-from-contacts
[{:keys [selected-contacts username]
:contacts/keys [contacts]}]
(->> (select-keys contacts selected-contacts)
vals
(map :name)
(cons username)
(s/join ", ")))
(defn prepare-chat
[{:keys [selected-contacts current-public-key] :as db} [_ group-name]]
(let [contacts (mapv #(hash-map :identity %) selected-contacts)
chat-name (if-not (s/blank? group-name)
group-name
(group-name-from-contacts db))
{:keys [public private]} (protocol/new-keypair!)]
(assoc db :new-chat {:chat-id (random/id)
:public-key public
:private-key private
:name chat-name
:color default-chat-color
:group-chat true
:group-admin current-public-key
:is-active true
:timestamp (random/timestamp)
:contacts contacts})))
(defn add-chat
[{:keys [new-chat] :as db} _]
(-> db
(assoc-in [:chats (:chat-id new-chat)] new-chat)
(assoc :selected-contacts #{})))
(defn create-chat!
[{:keys [new-chat]} _]
(chats/save new-chat))
(defn show-chat!
[{:keys [new-chat]} _]
(dispatch [:navigate-to-clean :chat-list])
(dispatch [:navigate-to :chat (:chat-id new-chat)]))
(defn start-listen-group!
[{:keys [new-chat web3 current-public-key]} _]
(let [{:keys [chat-id public-key private-key contacts name]} new-chat
identities (mapv :identity contacts)]
(protocol/invite-to-group!
{:web3 web3
:group {:id chat-id
:name name
:contacts (conj identities current-public-key)
:admin current-public-key
:keypair {:public public-key
:private private-key}}
:identities identities
:message {:from current-public-key
:message-id (random/id)}})
(protocol/start-watching-group!
{:web3 web3
:group-id chat-id
:identity current-public-key
:keypair {:public public-key
:private private-key}
:callback #(dispatch [:incoming-message %1 %2])})))
(register-handler :create-new-group-chat
(u/handlers->
prepare-chat
add-chat
create-chat!
show-chat!
start-listen-group!))
(register-handler :create-new-public-group
(after (fn [_ [_ topic]]
(dispatch [:navigate-to-clean :chat-list])
(dispatch [:navigate-to :chat topic])))
(u/side-effect!
(fn [db [_ topic]]
(let [exists? (boolean (get-in db [:chats topic]))
group {:chat-id topic
:name topic
:color default-chat-color
:group-chat true
:public? true
:is-active true
:timestamp (random/timestamp)}]
(when-not exists?
(dispatch [::add-public-group group])
(dispatch [::save-public-group group])
(dispatch [::start-watching-group topic]))))))
(register-handler ::add-public-group
(fn [db [_ {:keys [chat-id] :as group}]]
(assoc-in db [:chats chat-id] group)))
(register-handler ::save-public-group
(u/side-effect!
(fn [_ [_ group]]
(chats/save group))))
(register-handler ::start-watching-group
(u/side-effect!
(fn [{:keys [web3 current-public-key]} [_ topic]]
(protocol/start-watching-group!
{:web3 web3
:group-id topic
:identity current-public-key
:callback #(dispatch [:incoming-message %1 %2])}))))
(register-handler :group-chat-invite-received
(u/side-effect!
(fn [{:keys [current-public-key web3]}
[_ {:keys [from]
{:keys [group-id group-name contacts keypair timestamp]} :payload}]]
(let [{:keys [private public]} keypair]
(let [contacts' (keep (fn [ident]
(when (not= ident current-public-key)
{:identity ident})) contacts)
chat {:chat-id group-id
:name group-name
:group-chat true
:group-admin from
:public-key public
:private-key private
:contacts contacts'
:added-to-at timestamp
:timestamp timestamp
:is-active true}
exists? (chats/exists? group-id)]
(when (or (not exists?) (chats/new-update? timestamp group-id))
(if exists?
(dispatch [:update-chat! chat])
(dispatch [:add-chat group-id chat]))
(protocol/start-watching-group!
{:web3 web3
:group-id group-id
:identity current-public-key
:keypair keypair
:callback #(dispatch [:incoming-message %1 %2])})))))))
(defn prepare-group
[{:keys [selected-contacts contact-groups] :as db} [_ group-name]]
(let [contacts (mapv #(hash-map :identity %) selected-contacts)]
(assoc db :new-group {:group-id (random/id)
:name group-name
:order (count contact-groups)
:timestamp (random/timestamp)
:contacts contacts})))
(defn add-group
[{:keys [new-group] :as db} _]
(update db :contact-groups merge {(:group-id new-group) new-group}))
(defn update-group
[{:keys [new-group] :as db} _]
(update db :contact-groups merge {(:group-id new-group) new-group}))
(defn save-group!
[{:keys [new-group]} _]
(groups/save new-group))
(defn show-contact-list!
[_ _]
(dispatch [:navigate-to-clean :contact-list]))
(register-handler :create-new-group
(u/handlers->
prepare-group
add-group
save-group!
show-contact-list!))
(defn update-new-group
[db [_ new-group]]
(assoc db :new-group new-group))
(register-handler :update-group
(u/handlers->
update-new-group
update-group
save-group!))
(defn save-groups! [{:keys [new-groups]} _]
(groups/save-all new-groups))
(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?'))))
(defn add-new-groups
[{:keys [contact-groups] :as 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 :contact-groups merge new-groups')
(assoc :new-groups (into [] (vals new-groups'))))))
(register-handler :add-groups
(after save-groups!)
add-new-groups)
(defn load-groups! [db _]
(let [groups (->> (groups/get-all)
(map (fn [{:keys [group-id] :as group}]
[group-id group]))
(into {}))]
(assoc db :contact-groups groups)))
(register-handler :load-groups load-groups!)
(defmethod nav/preload-data! :new-public-group
[db]
(dissoc db :public-group-topic))
(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 :change-group-order
(fn [{:keys [groups-order] :as db} [_ from to]]
(if (>= to 0)
(assoc db :groups-order (move-item (vec groups-order) from to))
db)))
(register-handler :update-groups
(after save-groups!)
(fn [db [_ new-groups]]
(-> db
(update :contact-groups merge (map #(vector (:group-id %) %) new-groups))
(assoc :new-groups new-groups))))
(register-handler :save-group-order
(u/side-effect!
(fn [{:keys [groups-order contact-groups] :as db} _]
(let [new-groups (mapv #(assoc (contact-groups (second %)) :order (first %))
(map-indexed vector (reverse groups-order)))]
(dispatch [:update-groups new-groups])
(dispatch [:navigate-to-clean :contact-list])))))
(defn save-property!
[contact-group-id property-name value]
(groups/save-property contact-group-id property-name value))
(defn save-group-property!
[db-name property-name]
(fn [{:keys [contact-group-id] :as db} _]
(let [property (db-name db)]
(save-property! contact-group-id property-name property))))
(defn update-group-property
[db-name property-name]
(fn [{:keys [contact-group-id] :as db} _]
(let [property (db-name db)]
(assoc-in db [:contact-groups contact-group-id property-name] property))))
(register-handler :set-group-name
(after (save-group-property! :new-chat-name :name))
(update-group-property :new-chat-name :name))
(defn add-selected-contacts-to-group
[{:keys [selected-contacts contact-groups contact-group-id] :as db} _]
(let [new-identities (mapv #(hash-map :identity %) selected-contacts)]
(update-in db [:contact-groups contact-group-id :contacts] #(into [] (set (concat % new-identities))))))
(defn add-selected-contacts-to-group!
[{:keys [contact-group-id selected-contacts]} _]
(groups/add-contacts contact-group-id selected-contacts))
(register-handler :add-selected-contacts-to-group
(after add-selected-contacts-to-group!)
add-selected-contacts-to-group)
(defn add-contacts-to-group
[db [_ group-id contacts]]
(let [new-identities (mapv #(hash-map :identity %) contacts)]
(if (get-in db [:contact-groups group-id])
(update-in db [:contact-groups group-id :contacts] #(into [] (set (concat % new-identities))))
db)))
(defn add-contacts-to-group!
[db [_ group-id contacts]]
(when (get-in db [:contact-groups group-id])
(groups/add-contacts group-id contacts)))
(register-handler :add-contacts-to-group
(after add-contacts-to-group!)
add-contacts-to-group)
(defn delete-group []
(fn [{:keys [contact-group-id] :as db} _]
(assoc-in db [:contact-groups contact-group-id :pending?] true)))
(defn delete-group! []
(fn [{:keys [contact-group-id]} _]
(save-property! contact-group-id :pending? true)))
(register-handler :delete-group
(after (delete-group!))
(delete-group))
(defn deselect-participant
[db [_ id]]
(update db :selected-participants disj id))
(register-handler :deselect-participant deselect-participant)
(defn select-participant
[db [_ id]]
(update db :selected-participants conj id))
(register-handler :select-participant select-participant)

View File

@ -1,102 +0,0 @@
(ns status-im.new-group.screen-private
(:require-macros [status-im.utils.views :refer [defview]])
(:require [re-frame.core :refer [dispatch]]
[status-im.components.contact.contact :refer [contact-view]]
[status-im.components.common.common :as common]
[status-im.components.react :refer [view
scroll-view
keyboard-avoiding-view
list-view
list-item]]
[status-im.components.renderers.renderers :as renderers]
[status-im.components.sticky-button :refer [sticky-button]]
[status-im.utils.utils :as u]
[status-im.utils.listview :refer [to-datasource]]
[status-im.new-group.styles :as st]
[status-im.new-group.views.group :refer [group-toolbar
group-chat-settings-btns
group-name-view
add-btn
more-btn
delete-btn]]
[status-im.new-group.validations :as v]
[status-im.i18n :refer [label]]
[status-im.utils.platform :refer [ios?]]
[cljs.spec.alpha :as s]))
(def contacts-limit 3)
(defview group-contacts-view [group]
[contacts [:all-added-group-contacts-with-limit (:group-id group) contacts-limit]
contacts-count [:all-added-group-contacts-count (:group-id group)]]
[view
(when (pos? contacts-count)
[common/list-separator])
[view
(doall
(map (fn [row]
^{:key row}
[view
[contact-view
{:contact row
:extend-options [{:value #(dispatch [:remove-contact-from-group
(:whisper-identity row)
(:group-id group)])
:text (label :t/remove-from-group)}]
:extended? true}]
(when-not (= row (last contacts))
[common/list-separator])])
contacts))]
(when (< contacts-limit contacts-count)
[more-btn contacts-limit contacts-count #(dispatch [:navigate-to :edit-group-contact-list])])])
(defview edit-group []
[group-name [:get :new-chat-name]
group [:get-contact-group]
type [:get :group-type]]
(let [save-btn-enabled? (and (s/valid? ::v/name group-name)
(not= group-name (:name group)))]
[keyboard-avoiding-view {:style st/group-container}
[group-toolbar type true]
[group-name-view]
[view st/list-view-container
[add-btn #(dispatch [:navigate-to :add-contacts-toggle-list])]
[group-contacts-view group]
[view st/separator]
[delete-btn #(u/show-confirmation
(str (label :t/delete-group) "?") (label :t/delete-group-confirmation) (label :t/delete)
(fn[]
(dispatch [:delete-group])
(dispatch [:navigate-to-clean :contact-list])))]]
(when save-btn-enabled?
[sticky-button (label :t/save) #(dispatch [:set-group-name])])]))
(defn render-row [row _ _]
(list-item
^{:key row}
[contact-view {:contact row}]))
(defview new-group []
[contacts [:selected-group-contacts]
group-name [:get :new-chat-name]
group-type [:get :group-type]]
(let [save-btn-enabled? (and (s/valid? ::v/name group-name) (pos? (count contacts)))]
[(if ios? keyboard-avoiding-view view) (merge {:behavior :padding}
st/group-container)
[group-toolbar group-type false]
[group-name-view]
[view st/list-view-container
[list-view {:dataSource (to-datasource contacts)
:enableEmptySections true
:renderRow render-row
:bounces false
:keyboardShouldPersistTaps :always
:renderSeparator renderers/list-separator-renderer}]]
(when save-btn-enabled?
[sticky-button (label :t/save)
(if (= group-type :contact-group)
#(dispatch [:create-new-group group-name])
#(dispatch [:create-new-group-chat group-name]))
;; once? set to true, so once component is mounted, on-press handler
;; will be executed only once
true])]))

View File

@ -1,67 +0,0 @@
(ns status-im.new-group.screen-public
(:require-macros [status-im.utils.views :refer [defview]])
(:require [re-frame.core :refer [subscribe dispatch]]
[status-im.react-native.resources :as res]
[status-im.components.react :refer [view
text
image
icon
touchable-highlight
list-view
list-item]]
[status-im.components.text-field.view :refer [text-field]]
[status-im.components.styles :refer [icon-ok
color-blue
color-gray4]]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar.view :refer [toolbar]]
[status-im.utils.listview :refer [to-datasource]]
[status-im.new-group.styles :as st]
[status-im.new-group.validations :as v]
[status-im.i18n :refer [label]]
[cljs.spec.alpha :as s]))
(defview new-group-toolbar []
[topic [:get :public-group-topic]]
(let [create-btn-enabled? (s/valid? ::v/topic topic)]
[view
[status-bar]
[toolbar
{:title (label :t/new-public-group-chat)
:actions [{:image {:source {:uri (if create-btn-enabled?
:icon_ok_blue
:icon_ok_disabled)}
:style icon-ok}
:handler (when create-btn-enabled?
#(dispatch [:create-new-public-group topic]))}]}]]))
(defview group-name-input []
[topic [:get :public-group-topic]]
[view
[text-field
{:error (cond
(not (s/valid? ::v/not-empty-string topic))
(label :t/empty-topic)
(not (s/valid? ::v/topic topic))
(label :t/topic-format))
:wrapper-style st/group-chat-name-wrapper
:error-color color-blue
:line-color color-gray4
:label-hidden? true
:input-style st/group-chat-topic-input
:auto-focus true
:on-change-text #(dispatch [:set :public-group-topic %])
:value topic
:validator #(re-matches #"[a-z\-]*" %)
:auto-capitalize :none}]
[text {:style st/topic-hash} "#"]])
(defn new-public-group []
[view st/group-container
[new-group-toolbar]
[view st/chat-name-container
[text {:style st/members-text
:font :medium}
(label :t/public-group-topic)]
[group-name-input]]])

View File

@ -1,11 +0,0 @@
(ns status-im.new-group.specs
(:require [cljs.spec.alpha :as s]))
(s/def :group/contact-groups (s/nilable map?)) ;; {id (string) group (map)}
(s/def :group/contact-group-id (s/nilable string?)) ;;used during editing contact group
(s/def :group/group-type (s/nilable keyword?)) ;;contact group or chat group
(s/def :group/new-group (s/nilable map?)) ;;used during creating or edeting contact group
(s/def :group/new-groups (s/nilable vector?)) ;;used during creating or edeting contact groups
(s/def :group/contacts-group (s/nilable map?))
(s/def :group/selected-contacts (s/nilable set?))
(s/def :group/groups-order (s/nilable seq?)) ;;list of group ids

View File

@ -1,37 +0,0 @@
(ns status-im.new-group.subs
(:require [re-frame.core :refer [reg-sub subscribe]]
[status-im.utils.subs :as u]))
(reg-sub :is-contact-selected?
(u/contains-sub :selected-contacts))
(reg-sub :is-participant-selected?
(u/contains-sub :selected-participants))
(defn filter-selected-contacts [selected-contacts contacts]
(remove #(true? (:pending? (contacts %))) selected-contacts))
(reg-sub :selected-contacts-count
:<- [:get :selected-contacts]
:<- [:get-contacts]
(fn [[selected-contacts contacts]]
;TODO temporary, contact should be deleted from group after contact deletion from contacts
(count (filter-selected-contacts selected-contacts contacts))))
(reg-sub :selected-participants-count
:<- [:get :selected-participants]
(fn [selected-participants]
(count selected-participants)))
(defn filter-contacts [selected-contacts added-contacts]
(filter #(selected-contacts (:whisper-identity %)) added-contacts))
(reg-sub :selected-group-contacts
:<- [:get :selected-contacts]
:<- [:all-added-contacts]
(fn [[selected-contacts added-contacts]]
(filter-contacts selected-contacts added-contacts)))
(reg-sub :get-contact-group
(fn [db]
((:contact-groups db) (:contact-group-id db))))

View File

@ -1,21 +0,0 @@
(ns status-im.new-group.validations
(:require [cljs.spec.alpha :as s]
[status-im.utils.phone-number :refer [valid-mobile-number?]]
[status-im.constants :refer [console-chat-id wallet-chat-id]]
[clojure.string :as str]
[status-im.utils.homoglyph :as h]))
(defn not-illegal-name? [username]
(let [username (some-> username (str/trim))]
(and (not (h/matches username console-chat-id))
(not (h/matches username wallet-chat-id)))))
(s/def ::not-empty-string (s/and string? not-empty))
(s/def ::not-illegal-name not-illegal-name?)
(s/def ::name (s/and ::not-empty-string
::not-illegal-name))
(s/def ::topic (s/and ::not-empty-string
::not-illegal-name
(partial re-matches #"[a-z0-9\-]+")))

View File

@ -1,78 +0,0 @@
(ns status-im.new-group.views.chat-group-settings
(:require-macros [status-im.utils.views :refer [defview]])
(:require [re-frame.core :refer [dispatch]]
[status-im.components.contact.contact :refer [contact-view]]
[status-im.components.common.common :as common]
[status-im.components.react :refer [view
scroll-view
keyboard-avoiding-view
icon
touchable-highlight]]
[status-im.components.sticky-button :refer [sticky-button]]
[status-im.new-group.styles :as st]
[status-im.new-group.views.group :refer [group-toolbar
group-chat-settings-btns
group-name-view
add-btn
more-btn
delete-btn]]
[status-im.new-group.validations :as v]
[status-im.i18n :refer [label]]
[status-im.utils.platform :refer [ios?]]
[cljs.spec.alpha :as s]))
(def contacts-limit 3)
(defview chat-group-contacts-view [admin?]
[contacts [:current-chat-contacts]]
(let [limited-contacts (take contacts-limit contacts)
contacts-count (count contacts)]
[view
(when (and admin? (pos? contacts-count))
[common/list-separator])
[view
(doall
(map (fn [row]
^{:key row}
[view
[contact-view
{:contact row
:extend-options [{:value #(do
(dispatch [:set :selected-participants #{(:whisper-identity row)}])
(dispatch [:remove-participants]))
:text (label :t/remove)}]
:extended? admin?}]
(when-not (= row (last limited-contacts))
[common/list-separator])])
limited-contacts))]
(when (< contacts-limit contacts-count)
[more-btn contacts-limit contacts-count #(dispatch [:navigate-to :edit-chat-group-contact-list])])]))
(defview chat-group-members []
[current-pk [:get :current-public-key]
group-admin [:chat :group-admin]]
(let [admin? (= current-pk group-admin)]
[view
(when admin?
[add-btn #(dispatch [:navigate-to :add-participants-toggle-list])])
[chat-group-contacts-view admin?]]))
(defview chat-group-settings []
[new-chat-name [:get :new-chat-name]
chat-name [:chat :name]
type [:get :group-type]]
(let [save-btn-enabled? (and (s/valid? ::v/name new-chat-name)
(not= new-chat-name chat-name))]
[keyboard-avoiding-view {:style st/group-container}
[view {:flex 1}
[group-toolbar type true]
[scroll-view
[group-name-view]
[chat-group-members]
[view st/separator]
[group-chat-settings-btns]]]
(when save-btn-enabled?
[sticky-button (label :t/save) #(dispatch [:set-chat-name])
;; once? set to true, so once component is mounted, on-press handler
;; will be executed only once
true])]))

View File

@ -1,78 +0,0 @@
(ns status-im.new-group.views.contact-list
(:require-macros [status-im.utils.views :refer [defview]])
(:require [re-frame.core :refer [dispatch]]
[status-im.components.contact.contact :refer [contact-view]]
[status-im.components.renderers.renderers :as renderers]
[status-im.components.react :refer [view
text
list-view
list-item]]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar-new.view :refer [toolbar-with-search]]
[status-im.utils.listview :refer [to-datasource]]
[status-im.new-group.styles :as st]
[status-im.i18n :refer [label]]))
(defview contact-list-toolbar [title]
[show-search [:get-in [:toolbar-search :show]]
search-text [:get-in [:toolbar-search :text]]]
(toolbar-with-search
{:show-search? (= show-search :contact-list)
:search-text search-text
:search-key :contact-list
:title title
:search-placeholder (label :t/search-contacts)}))
(defn contacts-list [contacts renderer-function]
[view {:flex 1}
[list-view {:dataSource (to-datasource contacts)
:enableEmptySections true
:renderRow renderer-function
:bounces false
:keyboardShouldPersistTaps :always
:renderSeparator renderers/list-separator-renderer
:renderFooter renderers/list-footer-renderer
:renderHeader renderers/list-header-renderer}]])
(defview chat-contacts-list-view []
[contacts [:contacts-filtered :current-chat-contacts]
current-pk [:get :current-public-key]
group-admin [:chat :group-admin]]
(let [admin? (= current-pk group-admin)]
[contacts-list contacts (fn [row _ _]
(list-item
^{:key row}
[contact-view {:contact row
:extended? admin?
:extend-options [{:value #(do
(dispatch [:set :selected-participants
#{(:whisper-identity row)}])
(dispatch [:remove-participants]))
:text (label :t/remove)}]}]))]))
(defview contacts-list-view [group]
[contacts [:all-added-group-contacts-filtered (:group-id group)]]
[contacts-list contacts (fn [row _ _]
(list-item
^{:key row}
[contact-view {:contact row
:extended? true
:extend-options [{:value #(dispatch [:remove-contact-from-group
(:whisper-identity row)
(:group-id group)])
:text (label :t/remove-from-group)}]}]))])
(defview edit-chat-group-contact-list []
[chat-name [:chat :name]]
[view st/group-container
[status-bar]
[contact-list-toolbar chat-name]
[chat-contacts-list-view]])
(defview edit-group-contact-list []
[group [:get-contact-group]
type [:get :group-type]]
[view st/group-container
[status-bar]
[contact-list-toolbar (:name group)]
[contacts-list-view group]])

View File

@ -1,95 +0,0 @@
(ns status-im.new-group.views.contact-toggle-list
(:require-macros [status-im.utils.views :refer [defview]])
(:require [re-frame.core :refer [dispatch]]
[status-im.components.contact.contact :refer [contact-view]]
[status-im.components.renderers.renderers :as renderers]
[status-im.components.react :refer [view
keyboard-avoiding-view
text
list-view
list-item]]
[status-im.components.sticky-button :refer [sticky-button]]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar-new.view :refer [toolbar-with-search]]
[status-im.utils.listview :refer [to-datasource]]
[status-im.utils.platform :refer [ios?]]
[status-im.new-group.views.toggle-contact :refer [group-toggle-contact
group-toggle-participant]]
[status-im.new-group.styles :as st]
[status-im.contacts.styles :as cst]
[status-im.i18n :refer [label]]
[status-im.components.toolbar-new.actions :as act]))
(defn title-with-count [title count-value]
[view st/toolbar-title-with-count
[text {:style st/toolbar-title-with-count-text
:font :toolbar-title}
title]
(when (pos? count-value)
[view st/toolbar-title-with-count-container
[text {:style st/toolbar-title-with-count-text-count
:font :toolbar-title}
count-value]])])
(defview toggle-list-toolbar [title contacts-count]
[show-search [:get-in [:toolbar-search :show]]
search-text [:get-in [:toolbar-search :text]]]
(toolbar-with-search
{:show-search? (= show-search :contact-group-list)
:search-text search-text
:search-key :contact-group-list
:custom-title (title-with-count title contacts-count)
:search-placeholder (label :t/search-contacts)}))
(defn toggle-list [contacts render-function]
[view {:flex 1}
[list-view
{:dataSource (to-datasource contacts)
:renderRow (fn [row _ _]
(list-item ^{:key row} [render-function row]))
:renderSeparator renderers/list-separator-renderer
:renderFooter renderers/list-footer-renderer
:renderHeader renderers/list-header-renderer
:style cst/contacts-list
:keyboardShouldPersistTaps :always}]])
(defview contact-toggle-list []
[contacts [:all-added-group-contacts-filtered]
selected-contacts-count [:selected-contacts-count]
group-type [:get :group-type]]
[keyboard-avoiding-view {:style st/group-container}
[status-bar]
[toggle-list-toolbar
(label (if (= group-type :contact-group)
:t/new-group
:t/new-group-chat))
selected-contacts-count]
[toggle-list contacts group-toggle-contact]
(when (pos? selected-contacts-count)
[sticky-button (label :t/next) #(dispatch [:navigate-to :new-group])])])
(defview add-contacts-toggle-list []
[contacts [:all-group-not-added-contacts-filtered]
group [:get-contact-group]
selected-contacts-count [:selected-contacts-count]]
[keyboard-avoiding-view {:style st/group-container}
[status-bar]
[toggle-list-toolbar (:name group) selected-contacts-count]
[toggle-list contacts group-toggle-contact]
(when (pos? selected-contacts-count)
[sticky-button (label :t/save) #(do
(dispatch [:add-selected-contacts-to-group])
(dispatch [:navigate-back]))])])
(defview add-participants-toggle-list []
[contacts [:contacts-filtered :all-new-contacts]
chat-name [:chat :name]
selected-contacts-count [:selected-participants-count]]
[keyboard-avoiding-view {:style st/group-container}
[status-bar]
[toggle-list-toolbar chat-name selected-contacts-count]
[toggle-list contacts group-toggle-participant]
(when (pos? selected-contacts-count)
[sticky-button (label :t/save) #(do
(dispatch [:add-new-participants])
(dispatch [:navigate-back]))])])

View File

@ -1,90 +0,0 @@
(ns status-im.new-group.views.group
(:require-macros [status-im.utils.views :refer [defview]])
(:require [re-frame.core :refer [dispatch]]
[status-im.contacts.styles :as cst]
[status-im.components.common.common :as common]
[status-im.components.action-button.action-button :refer [action-button
action-button-disabled
action-separator]]
[status-im.components.react :refer [view
text
icon
touchable-highlight]]
[status-im.components.text-input-with-label.view :refer [text-input-with-label]]
[status-im.components.styles :refer [color-blue color-gray5 color-light-blue]]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar-new.view :refer [toolbar]]
[status-im.utils.platform :refer [platform-specific]]
[status-im.new-group.styles :as st]
[status-im.i18n :refer [label]]))
(defn group-toolbar [group-type edit?]
[view
[status-bar]
[toolbar
{:title (label
(if (= group-type :contact-group)
(if edit? :t/edit-group :t/new-group)
(if edit? :t/chat-settings :t/new-group-chat)))
:actions [{:image :blank}]}]])
(defview group-name-view []
[new-group-name [:get :new-chat-name]]
[view st/group-name-container
[text-input-with-label
{:auto-focus true
:label (label :t/name)
:on-change-text #(dispatch [:set :new-chat-name %])
:default-value new-group-name}]])
(defn add-btn [on-press]
[action-button (label :t/add-members)
:add_blue
on-press])
(defn delete-btn [on-press]
[view st/settings-group-container
[touchable-highlight {:on-press on-press}
[view st/settings-group-item
[view st/delete-icon-container
[icon :close_red st/add-icon]]
[view st/settings-group-text-container
[text {:style st/delete-group-text}
(label :t/delete-group)]
[text {:style st/delete-group-prompt-text}
(label :t/delete-group-prompt)]]]]])
(defn group-chat-settings-btns []
[view st/settings-group-container
[view {:opacity 0.4}
[touchable-highlight {:on-press #()}
[view st/settings-group-item
[view st/settings-icon-container
[icon :speaker_blue st/add-icon]]
[view st/settings-group-text-container
[text {:style st/settings-group-text}
(label :t/mute-notifications)]]]]]
[action-separator]
[action-button (label :t/clear-history)
:close_blue
#(dispatch [:clear-history])]
[action-separator]
[touchable-highlight {:on-press #(dispatch [:leave-group-chat])}
[view st/settings-group-item
[view st/delete-icon-container
[icon :arrow_right_red st/add-icon]]
[view st/settings-group-text-container
[text {:style st/delete-group-text}
(label :t/leave-chat)]]]]])
(defn more-btn [contacts-limit contacts-count on-press]
[view
[common/list-separator]
[view cst/show-all
[touchable-highlight {:on-press on-press}
[view
[text {:style cst/show-all-text
:uppercase? (get-in platform-specific [:uppercase?])
:font (get-in platform-specific [:component-styles :contacts :show-all-text-font])}
(str (- contacts-count contacts-limit) " " (label :t/more))]]]]])

View File

@ -1,64 +0,0 @@
(ns status-im.new-group.views.reorder-groups
(:require-macros [status-im.utils.views :refer [defview]])
(:require [re-frame.core :refer [dispatch dispatch-sync]]
[status-im.components.react :refer [view
text
icon
touchable-highlight
list-item]]
[status-im.components.sticky-button :refer [sticky-button]]
[status-im.components.status-bar :refer [status-bar]]
[status-im.components.toolbar-new.view :refer [toolbar]]
[status-im.components.sortable-list-view :refer [sortable-list-view sortable-item]]
[status-im.components.common.common :as common]
[status-im.utils.listview :refer [to-datasource]]
[status-im.utils.platform :refer [android?]]
[status-im.new-group.styles :as st]
[status-im.i18n :refer [label label-pluralize]]
[status-im.utils.platform :refer [platform-specific]]
[reagent.core :as r]))
(defn toolbar-view []
[toolbar {:actions [{:image :blank}]
:title (label :t/reorder-groups)}])
(defn group-item [{:keys [name contacts] :as group}]
(let [cnt (count contacts)]
[view st/order-item-container
[view st/order-item-inner-container
[text {:style st/order-item-label}
name]
[text {:style st/order-item-contacts}
(str cnt " " (label-pluralize cnt :t/contact-s))]
[view {:flex 1}]
[view st/order-item-icon
[icon :grab_gray]]]]))
(defn render-separator [last]
(fn [_ row-id _]
(list-item
(if (= row-id last)
^{:key "bottom-shadow"}
[common/bottom-shadow]
^{:key row-id}
[view st/order-item-separator-wrapper
[view st/order-item-separator]]))))
(defview reorder-groups []
[groups [:get :contact-groups]
order [:get :groups-order]]
(let [this (r/current-component)]
[view st/reorder-groups-container
[status-bar]
[toolbar-view]
[view st/reorder-list-container
[common/top-shadow]
[sortable-list-view
{:data groups
:order order
:on-row-moved #(do (dispatch-sync [:change-group-order (:from %) (:to %)])
(.forceUpdate this))
:render-row (fn [row]
(sortable-item [group-item row]))
:render-separator (render-separator (last order))}]]
[sticky-button (label :t/save) #(dispatch [:save-group-order])]]))

View File

@ -1,17 +0,0 @@
(ns status-im.new-group.views.toggle-contact
(:require [re-frame.core :refer [dispatch]]
[status-im.components.contact.contact :refer [toogle-contact-view]]))
(defn on-toggle [checked? whisper-identity]
(let [action (if checked? :deselect-contact :select-contact)]
(dispatch [action whisper-identity])))
(defn on-toggle-participant [checked? whisper-identity]
(let [action (if checked? :deselect-participant :select-participant)]
(dispatch [action whisper-identity])))
(defn group-toggle-contact [{:keys [whisper-identity] :as contact}]
[toogle-contact-view contact :is-contact-selected? on-toggle])
(defn group-toggle-participant [{:keys [whisper-identity] :as contact}]
[toogle-contact-view contact :is-participant-selected? on-toggle-participant])

View File

@ -35,17 +35,17 @@
(debug :send-group-message message)
(d/add-pending-message! web3 message)))
(s/def :group/message
(s/def ::message
(s/merge :protocol/message (s/keys :req-un [:chat-message/payload])))
(s/def :public-group/username (s/and string? (complement str/blank?)))
(s/def :public-group/message
(s/merge :group/message (s/keys :username :public-group/username)))
(s/merge ::message (s/keys :username :public-group/username)))
(defn send!
[{:keys [keypair message] :as options}]
{:pre [(valid? :message/keypair keypair)
(valid? :group/message message)]}
(valid? ::message message)]}
(send-group-message! options :group-message))
(defn send-to-public-group!
@ -74,16 +74,15 @@
identity)]
(send-group-message! options' :remove-group-identity)))
(s/def :group/admin :message/from)
(s/def ::identities (s/* string?))
(s/def :group/name string?)
(s/def :group/id string?)
(s/def :group/admin string?)
(s/def :group/contacts (s/* string?))
(s/def ::name string?)
(s/def ::id string?)
(s/def ::admin string?)
(s/def ::contacts (s/* string?))
(s/def ::group
(s/keys :req-un
[:group/name :group/id :group/contacts :message/keypair :group/admin]))
[::name ::id ::contacts :message/keypair ::admin]))
(s/def :invite/options
(s/keys :req-un [:options/web3 :protocol/message ::group ::identities]))

View File

@ -1,12 +1,13 @@
(ns status-im.specs
(:require-macros [status-im.utils.db :refer [allowed-keys]])
(:require [cljs.spec.alpha :as s]
(:require [cljs.spec.alpha :as spec]
[status-im.accounts.specs]
[status-im.navigation.specs]
[status-im.contacts.db]
[status-im.qr-scanner.specs]
[status-im.new-group.specs]
[status-im.group.db]
[status-im.chat.specs]
[status-im.chat.new-public-chat.db]
[status-im.profile.specs]
[status-im.transactions.specs]
[status-im.discover.specs]))
@ -14,131 +15,128 @@
;;;;GLOBAL
;;public key of current logged in account
(s/def ::current-public-key (s/nilable string?))
(spec/def ::current-public-key (spec/nilable string?))
;;true when application running at first time
(s/def ::first-run (s/nilable boolean?))
(s/def ::was-modal? (s/nilable boolean?))
(spec/def ::first-run (spec/nilable boolean?))
(spec/def ::was-modal? (spec/nilable boolean?))
;;"http://localhost:8545"
(s/def ::rpc-url (s/nilable string?))
(spec/def ::rpc-url (spec/nilable string?))
;;object? doesn't work
(s/def ::web3 (s/nilable any?))
(spec/def ::web3 (spec/nilable any?))
;;object?
(s/def ::webview-bridge (s/nilable any?))
(s/def ::status-module-initialized? (s/nilable boolean?))
(s/def ::status-node-started? (s/nilable boolean?))
(s/def ::toolbar-search (s/nilable map?))
(spec/def ::webview-bridge (spec/nilable any?))
(spec/def ::status-module-initialized? (spec/nilable boolean?))
(spec/def ::status-node-started? (spec/nilable boolean?))
(spec/def ::toolbar-search (spec/nilable map?))
;;height of native keyboard if shown
(s/def ::keyboard-height (s/nilable number?))
(s/def ::keyboard-max-height (s/nilable number?))
(spec/def ::keyboard-height (spec/nilable number?))
(spec/def ::keyboard-max-height (spec/nilable number?))
;;:unknown - not used
(s/def ::orientation (s/nilable keyword?))
(spec/def ::orientation (spec/nilable keyword?))
;;:online - presence of internet connection in the phone
(s/def ::network-status (s/nilable keyword?))
(spec/def ::network-status (spec/nilable keyword?))
;;;;NODE
(s/def ::sync-listening-started (s/nilable boolean?))
(s/def ::sync-state (s/nilable keyword?))
(s/def ::sync-data (s/nilable map?))
(spec/def ::sync-listening-started (spec/nilable boolean?))
(spec/def ::sync-state (spec/nilable keyword?))
(spec/def ::sync-data (spec/nilable map?))
;;;;NETWORK
;;network name :testnet
(s/def ::network (s/nilable keyword?))
(spec/def ::network (spec/nilable keyword?))
(s/def ::db (allowed-keys
:opt
[:contacts/contacts
:contacts/new-identity
:contacts/new-public-key-error
:contacts/identity
:contacts/ui-props
:contacts/list-ui-props
:contacts/click-handler
:contacts/click-action
:contacts/click-params]
:opt-un
[::current-public-key
::first-run
::modal
::was-modal?
::rpc-url
::web3
::webview-bridge
::status-module-initialized?
::status-node-started?
::toolbar-search
::keyboard-height
::keyboard-max-height
::orientation
::network-status
::sync-listening-started
::sync-state
::sync-data
::network
:accounts/accounts
:accounts/account-creation?
:accounts/creating-account?
:accounts/current-account-id
:accounts/recover
:accounts/login
:navigation/view-id
:navigation/navigation-stack
:navigation/prev-tab-view-id
:navigation/prev-view-id
:qr/qr-codes
:qr/qr-modal
:qr/current-qr-context
:group/contact-groups
:group/contact-group-id
:group/group-type
:group/new-group
:group/new-groups
:group/contacts-group
:group/selected-contacts
:group/groups-order
:chat/chats
:chat/current-chat-id
:chat/chat-id
:chat/new-chat
:chat/new-chat-name
:chat/chat-animations
:chat/chat-ui-props
:chat/chat-list-ui-props
:chat/layout-height
:chat/expandable-view-height-to-value
:chat/global-commands
:chat/loading-allowed
:chat/message-data
:chat/message-id->transaction-id
:chat/message-status
:chat/unviewed-messages
:chat/selected-participants
:chat/chat-loaded-callbacks
:chat/commands-callbacks
:chat/command-hash-valid?
:chat/public-group-topic
:chat/confirmation-code-sms-listener
:chat/messages
:chat/loaded-chats
:chat/bot-subscriptions
:chat/new-request
:chat/raw-unviewed-messages
:chat/bot-db
:chat/geolocation
:profile/profile-edit
:transactions/transactions
:transactions/transactions-queue
:transactions/selected-transaction
:transactions/confirm-transactions
:transactions/confirmed-transactions-count
:transactions/transactions-list-ui-props
:transactions/transaction-details-ui-props
:transactions/wrong-password-counter
:transactions/wrong-password?
:discoveries/discoveries
:discoveries/discover-search-tags
:discoveries/tags
:discoveries/current-tag
:discoveries/request-discoveries-timer
:discoveries/new-discover]))
(spec/def ::db (allowed-keys
:opt
[:contacts/contacts
:contacts/new-identity
:contacts/new-public-key-error
:contacts/identity
:contacts/ui-props
:contacts/list-ui-props
:contacts/click-handler
:contacts/click-action
:contacts/click-params
:group/contact-groups
:group/contact-group-id
:group/group-type
:group/selected-contacts
:group/groups-order]
:opt-un
[::current-public-key
::first-run
::modal
::was-modal?
::rpc-url
::web3
::webview-bridge
::status-module-initialized?
::status-node-started?
::toolbar-search
::keyboard-height
::keyboard-max-height
::orientation
::network-status
::sync-listening-started
::sync-state
::sync-data
::network
:accounts/accounts
:accounts/account-creation?
:accounts/creating-account?
:accounts/current-account-id
:accounts/recover
:accounts/login
:navigation/view-id
:navigation/navigation-stack
:navigation/prev-tab-view-id
:navigation/prev-view-id
:qr/qr-codes
:qr/qr-modal
:qr/current-qr-context
:chat/chats
:chat/current-chat-id
:chat/chat-id
:chat/new-chat
:chat/new-chat-name
:chat/chat-animations
:chat/chat-ui-props
:chat/chat-list-ui-props
:chat/layout-height
:chat/expandable-view-height-to-value
:chat/global-commands
:chat/loading-allowed
:chat/message-data
:chat/message-id->transaction-id
:chat/message-status
:chat/unviewed-messages
:chat/selected-participants
:chat/chat-loaded-callbacks
:chat/commands-callbacks
:chat/command-hash-valid?
:chat/public-group-topic
:chat/confirmation-code-sms-listener
:chat/messages
:chat/loaded-chats
:chat/bot-subscriptions
:chat/new-request
:chat/raw-unviewed-messages
:chat/bot-db
:chat/geolocation
:profile/profile-edit
:transactions/transactions
:transactions/transactions-queue
:transactions/selected-transaction
:transactions/confirm-transactions
:transactions/confirmed-transactions-count
:transactions/transactions-list-ui-props
:transactions/transaction-details-ui-props
:transactions/wrong-password-counter
:transactions/wrong-password?
:discoveries/discoveries
:discoveries/discover-search-tags
:discoveries/tags
:discoveries/current-tag
:discoveries/request-discoveries-timer
:discoveries/new-discover]))

View File

@ -2,10 +2,10 @@
(:require [re-frame.core :refer [reg-sub subscribe]]
status-im.chat.subs
status-im.chats-list.subs
status-im.group-settings.subs
status-im.group.chat-settings.subs
status-im.discover.subs
status-im.contacts.subs
status-im.new-group.subs
status-im.group.subs
status-im.transactions.subs
status-im.bots.subs))

View File

@ -127,5 +127,5 @@
([] (register-exception-handler default-alert-handler))
([f] (register-exception-handler true f))
([dev? f]
(if (or dev? (not js/goog.DEBUG))
(if (and dev? (not js/goog.DEBUG))
(.setGlobalHandler js/ErrorUtils f dev?))))