diff --git a/src/syng_im/android/core.cljs b/src/syng_im/android/core.cljs index 7a29cbcc74..3ffb47945e 100644 --- a/src/syng_im/android/core.cljs +++ b/src/syng_im/android/core.cljs @@ -14,6 +14,7 @@ [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.components.chat.remove-participants :refer [remove-participants]] [syng-im.utils.logging :as log] [syng-im.navigation :as nav] [syng-im.utils.encryption])) @@ -44,6 +45,7 @@ (init-back-button-handler! nav) (case view-id :add-participants (r/as-element [new-participants {:navigator nav}]) + :remove-participants (r/as-element [remove-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 709137006d..71c78f83e0 100644 --- a/src/syng_im/components/chat.cljs +++ b/src/syng_im/components/chat.cljs @@ -57,10 +57,14 @@ :elevation 2} :actions [{:title "Add Contact to chat" :icon res/add-icon + :showWithText true} + {:title "Remove Contact from chat" + :icon res/trash-icon :showWithText true}] :onActionSelected (fn [position] (case position - 0 (dispatch [:show-add-participants navigator]))) + 0 (dispatch [:show-add-participants navigator]) + 1 (dispatch [:show-remove-participants navigator]))) :onIconClicked (fn [] (nav-pop navigator))}]) [list-view {:dataSource datasource diff --git a/src/syng_im/components/chat/remove_participants.cljs b/src/syng_im/components/chat/remove_participants.cljs new file mode 100644 index 0000000000..1620ac3757 --- /dev/null +++ b/src/syng_im/components/chat/remove_participants.cljs @@ -0,0 +1,36 @@ +(ns syng-im.components.chat.remove-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 remove-participants [{:keys [navigator]}] + (let [contacts (subscribe [:current-chat-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 "Remove Participants" + :titleColor "#4A5258" + :style {:backgroundColor "white" + :height 56 + :elevation 2} + :actions [{:title "Remove" + :icon res/trash-icon + :show "always"}] + :onActionSelected (fn [position] + (dispatch [:remove-selected-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/chat_list_item.cljs b/src/syng_im/components/chats/chat_list_item.cljs index 0a7c3e3902..3f1d76fd3d 100644 --- a/src/syng_im/components/chats/chat_list_item.cljs +++ b/src/syng_im/components/chats/chat_list_item.cljs @@ -9,7 +9,7 @@ (defn chat-list-item [chat-obj navigator] [touchable-highlight {:on-press (fn [] - (dispatch [:show-chat (aget chat-obj "chat-id") navigator]))} + (dispatch [:show-chat (aget chat-obj "chat-id") navigator :push]))} [view {:style {:flexDirection "row" :width 260 :marginVertical 5}} diff --git a/src/syng_im/handlers.cljs b/src/syng_im/handlers.cljs index fb2962851b..a30f0bc35a 100644 --- a/src/syng_im/handlers.cljs +++ b/src/syng_im/handlers.cljs @@ -19,7 +19,8 @@ [syng-im.handlers.sign-up :as sign-up-service] [syng-im.models.chats :refer [create-chat - chat-add-participants]] + chat-add-participants + chat-remove-participants]] [syng-im.models.chat :refer [signal-chat-updated set-current-chat-id current-chat-id @@ -33,8 +34,11 @@ [syng-im.utils.logging :as log] [syng-im.protocol.api :as api] [syng-im.constants :refer [text-content-type]] - [syng-im.navigation :refer [nav-push]] - [syng-im.utils.crypt :refer [gen-random-bytes]])) + [syng-im.navigation :refer [nav-push + nav-replace + nav-pop]] + [syng-im.utils.crypt :refer [gen-random-bytes]] + [syng-im.utils.random :as random])) ;; -- Middleware ------------------------------------------------------------ ;; @@ -82,9 +86,11 @@ db)) (register-handler :navigate-to - (fn [db [action navigator route]] + (fn [db [action navigator route nav-type]] (log/debug action route) - (nav-push navigator route) + (case nav-type + :push (nav-push navigator route) + :replace (nav-replace navigator route)) db)) ;; -- Protocol -------------------------------------------------------------- @@ -130,6 +136,13 @@ :content (str (or inviter-name from) " invited " (or invitee-name identity)) :content-type text-content-type}))) +(defn removed-participant-msg [chat-id identity] + (let [contact-name (:name (contacts/contact-by-identity identity))] + (save-message chat-id {:from "system" + :msg-id (random/id) + :content (str "You've removed " (or contact-name identity)) + :content-type text-content-type}))) + (register-handler :group-chat-invite-acked (fn [db [action from group-id ack-msg-id]] (log/debug action from group-id ack-msg-id) @@ -250,10 +263,10 @@ ;; -- Chats -------------------------------------------------------------- (register-handler :show-chat - (fn [db [action chat-id navigator]] + (fn [db [action chat-id navigator nav-type]] (log/debug action "chat-id" chat-id) (let [db (set-current-chat-id db chat-id)] - (dispatch [:navigate-to navigator {:view-id :chat}]) + (dispatch [:navigate-to navigator {:view-id :chat} nav-type]) db))) (register-handler :set-sign-up-chat @@ -287,6 +300,25 @@ (log/debug action identity add?) (update-new-participants-selection db identity add?))) +(register-handler :show-remove-participants + (fn [db [action navigator]] + (log/debug action) + (nav-push navigator {:view-id :remove-participants}) + (clear-new-participants db))) + +(register-handler :remove-selected-participants + (fn [db [action navigator]] + (log/debug action) + (let [identities (-> (new-participants-selection db) + (vec)) + chat-id (current-chat-id db)] + (chat-remove-participants chat-id identities) + (nav-pop navigator) + (doseq [ident identities] + (api/group-remove-participant chat-id ident) + (removed-participant-msg chat-id ident)) + (signal-chat-updated db chat-id)))) + (register-handler :show-add-participants (fn [db [action navigator]] (log/debug action) @@ -300,7 +332,7 @@ (vec)) chat-id (current-chat-id db)] (chat-add-participants chat-id identities) - (dispatch [:show-chat chat-id navigator]) + (nav-pop navigator) (doseq [ident identities] (api/group-add-participant chat-id ident)) db))) @@ -323,7 +355,7 @@ (vec)) group-id (api/start-group-chat identities group-name) db (create-chat db group-id identities true group-name)] - (dispatch [:show-chat group-id navigator]) + (dispatch [:show-chat group-id navigator :replace]) db))) (register-handler :group-chat-invite-received diff --git a/src/syng_im/models/chats.cljs b/src/syng_im/models/chats.cljs index 9c3d3cfcac..13c36096cd 100644 --- a/src/syng_im/models/chats.cljs +++ b/src/syng_im/models/chats.cljs @@ -4,7 +4,8 @@ [clojure.string :refer [join blank?]] [syng-im.db :as db] [syng-im.utils.logging :as log] - [syng-im.constants :refer [group-chat-colors]])) + [syng-im.constants :refer [group-chat-colors]] + [syng-im.persistence.realm-queries :refer [include-query]])) (defn signal-chats-updated [db] (update-in db db/updated-chats-signal-path (fn [current] @@ -64,18 +65,33 @@ (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)] + (let [contacts (-> (r/get-by-field :chats :chat-id chat-id) + (r/single) + (aget "contacts")) + colors-in-use (->> (.map contacts (fn [object index collection] + {:text-color (aget object "text-color") + :background-color (aget object "background-color")})) + (set)) + colors (->> group-chat-colors + (filter (fn [color] + (not (contains? colors-in-use color))))) + 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 chat-remove-participants [chat-id identities] + (r/write + (fn [] + (let [query (include-query :identity identities) + chat (-> (r/get-by-field :chats :chat-id chat-id) + (r/single))] + (-> (aget chat "contacts") + (r/filtered query) + (r/delete)))))) + (defn active-group-chats [] (let [results (-> (r/get-all :chats) (r/filtered "group-chat = true"))] diff --git a/src/syng_im/models/contacts.cljs b/src/syng_im/models/contacts.cljs index 02b501eb2a..478f1a5e0e 100644 --- a/src/syng_im/models/contacts.cljs +++ b/src/syng_im/models/contacts.cljs @@ -4,6 +4,8 @@ [syng-im.utils.utils :refer [log toast]] [syng-im.persistence.realm :as realm] [syng-im.persistence.realm :as r] + [syng-im.persistence.realm-queries :refer [include-query + exclude-query]] [clojure.string :as s])) ;; TODO see https://github.com/rt2zz/react-native-contacts/issues/45 @@ -93,12 +95,15 @@ (r/sorted :name :asc))) (defn contacts-list-exclude [exclude-idents] - (let [exclude-query (->> exclude-idents - (map (fn [ident] - (str "whisper-identity != '" ident "'"))) - (s/join " && "))] + (let [query (exclude-query :whisper-identity exclude-idents)] (-> (r/get-all :contacts) - (r/filtered exclude-query) + (r/filtered query) + (r/sorted :name :asc)))) + +(defn contacts-list-include [include-indents] + (let [query (include-query :whisper-identity include-indents)] + (-> (r/get-all :contacts) + (r/filtered query) (r/sorted :name :asc)))) (defn contact-by-identity [identity] diff --git a/src/syng_im/persistence/realm.cljs b/src/syng_im/persistence/realm.cljs index ec83186df7..3020efeb31 100644 --- a/src/syng_im/persistence/realm.cljs +++ b/src/syng_im/persistence/realm.cljs @@ -115,8 +115,7 @@ (read-string value)) (defn delete [obj] - (write (fn [] - (.delete realm obj)))) + (.delete realm obj)) (defn exists? [schema-name field value] (> (.-length (get-by-field schema-name field value)) diff --git a/src/syng_im/persistence/realm_queries.cljs b/src/syng_im/persistence/realm_queries.cljs new file mode 100644 index 0000000000..3b8309b329 --- /dev/null +++ b/src/syng_im/persistence/realm_queries.cljs @@ -0,0 +1,19 @@ +(ns syng-im.persistence.realm-queries + (:require [clojure.string :as s] + [syng-im.utils.types :refer [to-string]])) + +(defn include-query [field-name values] + (->> values + (map (fn [val] + (str (to-string field-name) " == " (if (string? val) + (str "'" val "'") + val)))) + (s/join " || "))) + +(defn exclude-query [field-name values] + (->> values + (map (fn [val] + (str (to-string field-name) " != " (if (string? val) + (str "'" val "'") + val)))) + (s/join " && "))) \ No newline at end of file diff --git a/src/syng_im/persistence/simple_kv_store.cljs b/src/syng_im/persistence/simple_kv_store.cljs index 15ea802465..3781154d60 100644 --- a/src/syng_im/persistence/simple_kv_store.cljs +++ b/src/syng_im/persistence/simple_kv_store.cljs @@ -17,8 +17,9 @@ (contains-key? [_ key] (r/exists? :kv-store :key key)) (delete [_ key] - (-> (r/get-by-field :kv-store :key key) - (r/single) - (r/delete)))) + (r/write (fn [] + (-> (r/get-by-field :kv-store :key key) + (r/single) + (r/delete)))))) (def kv-store (->SimpleKvStore)) diff --git a/src/syng_im/resources.cljs b/src/syng_im/resources.cljs index 75b8e89b19..aa3b31e2e1 100644 --- a/src/syng_im/resources.cljs +++ b/src/syng_im/resources.cljs @@ -14,3 +14,4 @@ (def att (js/require "./images/att.png")) (def v (js/require "./images/v.png")) (def add-icon (js/require "./images/add.png")) +(def trash-icon (js/require "./images/trash.png")) diff --git a/src/syng_im/subs.cljs b/src/syng_im/subs.cljs index 083db77cea..7718291632 100644 --- a/src/syng_im/subs.cljs +++ b/src/syng_im/subs.cljs @@ -9,7 +9,8 @@ chat-by-id]] [syng-im.models.messages :refer [get-messages]] [syng-im.models.contacts :refer [contacts-list - contacts-list-exclude]] + contacts-list-exclude + contacts-list-include]] [syng-im.handlers.suggestions :refer [get-suggestions]])) ;; -- Chat -------------------------------------------------------------- @@ -114,3 +115,17 @@ :contacts (map :identity))] (contacts-list-exclude current-participants))))))) + +(register-sub :current-chat-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-include current-participants)))))))