diff --git a/images/add.png b/images/add.png new file mode 100644 index 0000000000..59eebce404 Binary files /dev/null and b/images/add.png differ diff --git a/src/syng_im/android/core.cljs b/src/syng_im/android/core.cljs index 83fbd67e02..7a29cbcc74 100644 --- a/src/syng_im/android/core.cljs +++ b/src/syng_im/android/core.cljs @@ -13,6 +13,7 @@ [syng-im.components.sign-up-confirm :refer [sign-up-confirm-view]] [syng-im.components.chats.chats-list :refer [chats-list]] [syng-im.components.chats.new-group :refer [new-group]] + [syng-im.components.chat.new-participants :refer [new-participants]] [syng-im.utils.logging :as log] [syng-im.navigation :as nav] [syng-im.utils.encryption])) @@ -42,6 +43,7 @@ view-id (keyword view-id)] (init-back-button-handler! nav) (case view-id + :add-participants (r/as-element [new-participants {:navigator nav}]) :chat-list (r/as-element [chats-list {:navigator nav}]) :new-group (r/as-element [new-group {:navigator nav}]) :contact-list (r/as-element [contact-list {:navigator nav}]) diff --git a/src/syng_im/components/chat.cljs b/src/syng_im/components/chat.cljs index 8ddec07d9d..709137006d 100644 --- a/src/syng_im/components/chat.cljs +++ b/src/syng_im/components/chat.cljs @@ -45,18 +45,24 @@ :backgroundColor "#eef2f5"}} (when android? ;; TODO add IOS version - [toolbar-android {:logo res/logo-icon - :title (or (@chat :name) - "Chat name") - :titleColor "#4A5258" - :subtitle "Last seen just now" - :subtitleColor "#AAB2B2" - :navIcon res/nav-back-icon - :style {:backgroundColor "white" - :height 56 - :elevation 2} - :onIconClicked (fn [] - (nav-pop navigator))}]) + [toolbar-android {:logo res/logo-icon + :title (or (@chat :name) + "Chat name") + :titleColor "#4A5258" + :subtitle "Last seen just now" + :subtitleColor "#AAB2B2" + :navIcon res/nav-back-icon + :style {:backgroundColor "white" + :height 56 + :elevation 2} + :actions [{:title "Add Contact to chat" + :icon res/add-icon + :showWithText true}] + :onActionSelected (fn [position] + (case position + 0 (dispatch [:show-add-participants navigator]))) + :onIconClicked (fn [] + (nav-pop navigator))}]) [list-view {:dataSource datasource :renderScrollComponent (fn [props] (invertible-scroll-view nil)) diff --git a/src/syng_im/components/chat/new_participants.cljs b/src/syng_im/components/chat/new_participants.cljs new file mode 100644 index 0000000000..dedff2578a --- /dev/null +++ b/src/syng_im/components/chat/new_participants.cljs @@ -0,0 +1,36 @@ +(ns syng-im.components.chat.new-participants + (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] + [syng-im.resources :as res] + [syng-im.components.react :refer [view toolbar-android android? text-input]] + [syng-im.components.realm :refer [list-view]] + [syng-im.utils.listview :refer [to-realm-datasource]] + [syng-im.components.chats.new-participant-contact :refer [new-participant-contact]] + [reagent.core :as r] + [syng-im.navigation :refer [nav-pop]])) + +(defn new-participants [{:keys [navigator]}] + (let [contacts (subscribe [:all-new-contacts])] + (fn [] + (let [contacts-ds (to-realm-datasource @contacts)] + [view {:style {:flex 1 + :backgroundColor "white"}} + (when android? + ;; TODO add IOS version + [toolbar-android {:logo res/logo-icon + :title "Add Participants" + :titleColor "#4A5258" + :style {:backgroundColor "white" + :height 56 + :elevation 2} + :actions [{:title "Add" + :icon res/v + :show "always"}] + :onActionSelected (fn [position] + (dispatch [:add-new-participants navigator])) + :navIcon res/nav-back-icon + :onIconClicked (fn [] + (nav-pop navigator))}]) + [list-view {:dataSource contacts-ds + :renderRow (fn [row section-id row-id] + (r/as-element [new-participant-contact (js->clj row :keywordize-keys true) navigator])) + :style {:backgroundColor "white"}}]])))) diff --git a/src/syng_im/components/chats/new_participant_contact.cljs b/src/syng_im/components/chats/new_participant_contact.cljs new file mode 100644 index 0000000000..97985c6468 --- /dev/null +++ b/src/syng_im/components/chats/new_participant_contact.cljs @@ -0,0 +1,24 @@ +(ns syng-im.components.chats.new-participant-contact + (:require [syng-im.resources :as res] + [re-frame.core :refer [subscribe dispatch dispatch-sync]] + [syng-im.components.react :refer [view]] + [syng-im.components.contact-list.contact-inner :refer [contact-inner-view]] + [syng-im.components.item-checkbox :refer [item-checkbox]] + [syng-im.utils.logging :as log] + [reagent.core :as r])) + +(defn new-participant-contact [{:keys [whisper-identity] :as contact} navigator] + (let [checked (r/atom false)] + (fn [] + [view {:style {:flexDirection "row" + :marginTop 5 + :marginBottom 5 + :paddingLeft 15 + :paddingRight 15 + :height 75}} + [item-checkbox {:onToggle (fn [checked?] + (reset! checked checked?) + (dispatch [:select-new-participant whisper-identity checked?])) + :checked @checked + :size 30}] + [contact-inner-view contact]]))) diff --git a/src/syng_im/db.cljs b/src/syng_im/db.cljs index 3f404f3b33..b34cfcd49e 100644 --- a/src/syng_im/db.cljs +++ b/src/syng_im/db.cljs @@ -9,10 +9,11 @@ :identity-password "replace-me-with-user-entered-password" :contacts [] :chat {:current-chat-id "0x0479a5ed1f38cadfad1db6cd56c4b659b0ebe052bbe9efa950f6660058519fa4ca6be2dda66afa80de96ab00eb97a2605d5267a1e8f4c2a166ab551f6826608cdd" - :command nil} + :command nil} :chats {} :chats-updated-signal 0 - :new-group #{}}) + :new-group #{} + :new-participants #{}}) (def protocol-initialized-path [:protocol-initialized]) @@ -28,3 +29,4 @@ (defn chat-command-content-path [chat-id] [:chats chat-id :command-input :content]) (def new-group-path [:new-group]) +(def new-participants-path [:new-participants]) diff --git a/src/syng_im/handlers.cljs b/src/syng_im/handlers.cljs index 1d8ad2f762..17f845784c 100644 --- a/src/syng_im/handlers.cljs +++ b/src/syng_im/handlers.cljs @@ -18,13 +18,18 @@ set-chat-command-content]] [syng-im.handlers.sign-up :as sign-up-service] - [syng-im.models.chats :refer [create-chat]] + [syng-im.models.chats :refer [create-chat + chat-add-participants]] [syng-im.models.chat :refer [signal-chat-updated set-current-chat-id + current-chat-id update-new-group-selection + update-new-participants-selection clear-new-group + clear-new-participants new-group-selection - set-chat-input-text]] + set-chat-input-text + new-participants-selection]] [syng-im.utils.logging :as log] [syng-im.protocol.api :as api] [syng-im.constants :refer [text-content-type]] @@ -124,13 +129,15 @@ (signal-chat-updated db group-id))) (register-handler :acked-msg - (fn [db [_ from msg-id]] + (fn [db [action from msg-id]] + (log/debug action from msg-id) (update-message! {:msg-id msg-id :delivery-status :delivered}) (signal-chat-updated db from))) (register-handler :msg-delivery-failed - (fn [db [_ msg-id]] + (fn [db [action msg-id]] + (log/debug action from msg-id) (update-message! {:msg-id msg-id :delivery-status :failed}) (let [{:keys [chat-id]} (message-by-id msg-id)] @@ -261,6 +268,29 @@ (nav-push navigator {:view-id :contact-list}) db)) +(register-handler :select-new-participant + (fn [db [action identity add?]] + (log/debug action identity add?) + (update-new-participants-selection db identity add?))) + +(register-handler :show-add-participants + (fn [db [action navigator]] + (log/debug action) + (nav-push navigator {:view-id :add-participants}) + (clear-new-participants db))) + +(register-handler :add-new-participants + (fn [db [action navigator]] + (log/debug action) + (let [identities (-> (new-participants-selection db) + (vec)) + chat-id (current-chat-id db)] + (chat-add-participants chat-id identities) + (dispatch [:show-chat chat-id navigator]) + (doseq [ident identities] + (api/group-add-participant chat-id ident)) + db))) + (register-handler :show-group-new (fn [db [action navigator]] (log/debug action) diff --git a/src/syng_im/models/chat.cljs b/src/syng_im/models/chat.cljs index 0abed387e5..d522c440fb 100644 --- a/src/syng_im/models/chat.cljs +++ b/src/syng_im/models/chat.cljs @@ -19,9 +19,15 @@ (defn update-new-group-selection [db identity add?] (update-in db db/new-group-path (fn [new-group] - (if add? - (conj new-group identity) - (disj new-group identity))))) + (if add? + (conj new-group identity) + (disj new-group identity))))) + +(defn update-new-participants-selection [db identity add?] + (update-in db db/new-participants-path (fn [new-participants] + (if add? + (conj new-participants identity) + (disj new-participants identity))))) (defn new-group-selection [db] (get-in db db/new-group-path)) @@ -29,6 +35,12 @@ (defn clear-new-group [db] (assoc-in db db/new-group-path #{})) +(defn new-participants-selection [db] + (get-in db db/new-participants-path)) + +(defn clear-new-participants [db] + (assoc-in db db/new-participants-path #{})) + (defn set-chat-input-text [db text] (assoc-in db (db/chat-input-text-path (current-chat-id db)) text)) @@ -37,5 +49,5 @@ (swap! re-frame.db/app-db (fn [db] (signal-chat-updated db "0x0479a5ed1f38cadfad1db6cd56c4b659b0ebe052bbe9efa950f6660058519fa4ca6be2dda66afa80de96ab00eb97a2605d5267a1e8f4c2a166ab551f6826608cdd"))) -(current-chat-id @re-frame.db/app-db) + (current-chat-id @re-frame.db/app-db) ) diff --git a/src/syng_im/models/chats.cljs b/src/syng_im/models/chats.cljs index 8aef29e8a0..9c3d3cfcac 100644 --- a/src/syng_im/models/chats.cljs +++ b/src/syng_im/models/chats.cljs @@ -61,8 +61,30 @@ (r/single-cljs) (r/list-to-array :contacts))) -(comment +(defn chat-add-participants [chat-id identities] + (r/write + (fn [] + (let [chat (-> (r/get-by-field :chats :chat-id chat-id) + (r/single)) + contacts (aget chat "contacts") + contacts-count (aget contacts "length") + colors (drop contacts-count group-chat-colors) + new-contacts (mapv (fn [ident {:keys [background text]}] + {:identity ident + :background-color background + :text-color text}) identities colors)] + (doseq [contact new-contacts] + (.push contacts (clj->js contact))))))) +(defn active-group-chats [] + (let [results (-> (r/get-all :chats) + (r/filtered "group-chat = true"))] + (->> (.map results (fn [object index collection] + (aget object "chat-id"))) + (js->clj)))) + +(comment + (active-group-chats) (-> (r/get-by-field :chats :chat-id "0x04ed4c3797026cddeb7d64a54ca58142e57ea03cda21072358d67455b506db90c56d95033e3d221992f70d01922c3d90bf0697c49e4be118443d03ae4a1cd3c15c") diff --git a/src/syng_im/models/contacts.cljs b/src/syng_im/models/contacts.cljs index be6c25ffc7..f3cce7ed0c 100644 --- a/src/syng_im/models/contacts.cljs +++ b/src/syng_im/models/contacts.cljs @@ -3,7 +3,8 @@ [re-frame.core :refer [subscribe dispatch dispatch-sync]] [syng-im.utils.utils :refer [log toast]] [syng-im.persistence.realm :as realm] - [syng-im.persistence.realm :as r])) + [syng-im.persistence.realm :as r] + [clojure.string :as s])) ;; TODO see https://github.com/rt2zz/react-native-contacts/issues/45 (def fake-phone-contacts? true) @@ -91,6 +92,15 @@ (-> (r/get-all :contacts) (r/sorted :name :asc))) +(defn contacts-list-exclude [exclude-idents] + (let [exclude-query (->> exclude-idents + (map (fn [ident] + (str "whisper-identity != '" ident "'"))) + (s/join " && "))] + (-> (r/get-all :contacts) + (r/filtered exclude-query) + (r/sorted :name :asc)))) + (defn contatct-by-identity [identity] (-> (r/get-by-field :contacts :whisper-identity identity) (r/single-cljs))) @@ -98,15 +108,25 @@ (comment (r/write #(create-contact {:phone-number "0543072333" - :whisper-identity "0x04ed4c3797026cddeb7d64a54ca58142e57ea03cda21072358d67455b506db90c56d95033e3d221992f70d01922c3d90bf0697c49e4be118443d03ae4a1cd3c15c" + :whisper-identity "0x045326d94f999c3e17d11d6a09e13cf9a34b30f62687a970053887b8f8d1fefac3f185e6ba6f02f8217b6947227c7dbfa8d4ee3a4a2864b0cac9968c819ba9c17c" :name "Mr. Bean" :photo-path ""})) (r/write #(create-contact {:phone-number "0544828649" - :whisper-identity "0x0498bcce41dbe05c6d4776ef50d12c2ef1a00d9d7f7144d174ece3dce85ca3428bf0900352abcccdc463bd2cfa4ec319cda46c2079152c4cb14d1cad9a00dd7571" + :whisper-identity "0x0496152b9bb6640fddb3a156747baa961792762264ab459c8d7c9ac179ddceb2abb701357a9b6691ef858ef8ce2eb306754825b8267add96e7a01d854a576b9fda" :name "Mr. Batman" :photo-path ""})) + (r/write #(create-contact {:phone-number "0522222222" + :whisper-identity "0x04ef1f45303a14cb30af0458fabc2c7b1fd1bc9bb69fae5bb4ec63ae5b52cd53c2a194398b6674cc335899bbf6a24bfe48813a134ce9b8b85877690c1a99f0b19f" + :name "Mr. Eagle" + :photo-path ""})) + + (r/write #(create-contact {:phone-number "0533333333" + :whisper-identity "0x04e9e31a92ab16dbac9cdce47f59545ac646175087f4eeb6e4442e36d37b09e20dcb6239ac743ddd3f1fa17377c1789dc60a1ebb8774d247f3df69ebd0082d46fe" + :name "Mr. PiggyBear" + :photo-path ""})) + (contacts-list) (:new-group @re-frame.db/app-db) diff --git a/src/syng_im/models/messages.cljs b/src/syng_im/models/messages.cljs index da53dad990..9517d14783 100644 --- a/src/syng_im/models/messages.cljs +++ b/src/syng_im/models/messages.cljs @@ -7,6 +7,7 @@ (defn save-message [chat-id {:keys [from to msg-id content content-type outgoing] :or {outgoing false to nil} :as msg}] + (log/debug "save-message" chat-id msg) (when-not (r/exists? :msgs :msg-id msg-id) (r/write (fn [] @@ -28,10 +29,12 @@ (-> (r/get-by-field :msgs :msg-id msg-id) (r/single-cljs))) -(defn update-message! [msg] +(defn update-message! [{:keys [msg-id] :as msg}] + (log/debug "update-message!" msg) (r/write (fn [] - (r/create :msgs msg true)))) + (when (r/exists? :msgs :msg-id msg-id) + (r/create :msgs msg true))))) (comment diff --git a/src/syng_im/persistence/realm.cljs b/src/syng_im/persistence/realm.cljs index 694ff7af16..ec83186df7 100644 --- a/src/syng_im/persistence/realm.cljs +++ b/src/syng_im/persistence/realm.cljs @@ -41,7 +41,8 @@ :primaryKey :chat-id :properties {:chat-id "string" :name "string" - :group-chat "bool" + :group-chat {:type "bool" + :indexed true} :timestamp "int" :contacts {:type "list" :objectType "chat-contact"}}}]}) @@ -92,6 +93,9 @@ false true))) +(defn filtered [results filter-query] + (.filtered results filter-query)) + (defn page [results from to] (js/Array.prototype.slice.call results from to)) diff --git a/src/syng_im/protocol/protocol_handler.cljs b/src/syng_im/protocol/protocol_handler.cljs index dd40a08d2c..cd0c64cfa8 100644 --- a/src/syng_im/protocol/protocol_handler.cljs +++ b/src/syng_im/protocol/protocol_handler.cljs @@ -3,12 +3,14 @@ [syng-im.constants :refer [ethereum-rpc-url]] [re-frame.core :refer [dispatch]] [syng-im.models.protocol :refer [stored-identity]] - [syng-im.persistence.simple-kv-store :as kv])) + [syng-im.persistence.simple-kv-store :as kv] + [syng-im.models.chats :refer [active-group-chats]])) (defn make-handler [db] {:ethereum-rpc-url ethereum-rpc-url :identity (stored-identity db) + :active-group-ids (active-group-chats) :storage kv/kv-store :handler (fn [{:keys [event-type] :as event}] (log/info "Event:" (clj->js event)) diff --git a/src/syng_im/resources.cljs b/src/syng_im/resources.cljs index 52a956d3f6..75b8e89b19 100644 --- a/src/syng_im/resources.cljs +++ b/src/syng_im/resources.cljs @@ -13,3 +13,4 @@ (def smile (js/require "./images/smile.png")) (def att (js/require "./images/att.png")) (def v (js/require "./images/v.png")) +(def add-icon (js/require "./images/add.png")) diff --git a/src/syng_im/subs.cljs b/src/syng_im/subs.cljs index 2b13739689..083db77cea 100644 --- a/src/syng_im/subs.cljs +++ b/src/syng_im/subs.cljs @@ -8,7 +8,8 @@ chats-updated? chat-by-id]] [syng-im.models.messages :refer [get-messages]] - [syng-im.models.contacts :refer [contacts-list]] + [syng-im.models.contacts :refer [contacts-list + contacts-list-exclude]] [syng-im.handlers.suggestions :refer [get-suggestions]])) ;; -- Chat -------------------------------------------------------------- @@ -99,3 +100,17 @@ (fn [db _] (reaction (contacts-list)))) + +(register-sub :all-new-contacts + (fn [db _] + (let [current-chat-id (-> (current-chat-id @db) + (reaction)) + chat (-> (when-let [chat-id @current-chat-id] + (chat-by-id chat-id)) + (reaction))] + (reaction + (when @chat + (let [current-participants (->> @chat + :contacts + (map :identity))] + (contacts-list-exclude current-participants)))))))