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/images/leave.png b/images/leave.png new file mode 100644 index 0000000000..17df8fc16b Binary files /dev/null and b/images/leave.png differ diff --git a/images/trash.png b/images/trash.png new file mode 100644 index 0000000000..0d83bb9685 Binary files /dev/null and b/images/trash.png differ diff --git a/src/syng_im/android/core.cljs b/src/syng_im/android/core.cljs index d7f2bf13d7..2a66ca68a9 100644 --- a/src/syng_im/android/core.cljs +++ b/src/syng_im/android/core.cljs @@ -14,6 +14,8 @@ [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.components.chat.remove-participants :refer [remove-participants]] [syng-im.utils.logging :as log] [syng-im.navigation :as nav] [syng-im.utils.encryption])) @@ -44,6 +46,8 @@ (init-back-button-handler! nav) (case view-id :discovery (r/as-element [discovery {:navigator nav}]) + :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/carousel.cljs b/src/syng_im/components/carousel.cljs index d20f514a37..f9ff306f0f 100644 --- a/src/syng_im/components/carousel.cljs +++ b/src/syng_im/components/carousel.cljs @@ -26,16 +26,20 @@ (defn calculate-gap [component props] - (let [prop-page-width (:pageWidth props) + (let [prop-page-width (get-page-width props) + page-width (page-width) sneak (get-sneak props) + gap (quot (- (- page-width (* 2 sneak)) prop-page-width) 2) _ (log "calculate-gap") _ (log component) _ (log props) + _ (log page-width) _ (log prop-page-width) - _ (log sneak)] - (if (> prop-page-width (page-width)) + _ (log sneak) + _ (log gap)] + (if (> prop-page-width page-width) (println "Invalid pageWidth")) - (reagent.core/set-state component {:gap (/ (- (- (page-width) (* 2 sneak)) prop-page-width) 2)}) + (reagent.core/set-state component {:gap gap}) )) (defn scroll-to [component x y] @@ -58,7 +62,7 @@ _ (log (str "gap: " gap)) page-offset (+ prop-page-width gap) _ (log (str "page-offset: " page-offset)) - current-position (+ (.-x (.-contentOffset (.-nativeEvent event))) (/ (page-width) 2)) + current-position (+ (.-x (.-contentOffset (.-nativeEvent event))) (quot (page-width) 2)) _ (log (str "current-position: " current-position)) current-page (quot current-position page-offset) _ (log (str "current-page: " current-page)) @@ -122,14 +126,14 @@ } [view {:style [{:width props-page-width :justifyContent "center" - :marginLeft (/ gap 2) - :marginRight (/ gap 2)} + :marginLeft (quot gap 2) + :marginRight (quot gap 2)} page-style]} child]])) children)] [view {:style {:flex 1}} - [scroll-view {:contentContainerStyle {:paddingLeft (+ sneak (/ gap 2)) - :paddingRight (+ sneak (/ gap 2))} + [scroll-view {:contentContainerStyle {:paddingLeft (+ sneak (quot gap 2)) + :paddingRight (+ sneak (quot gap 2))} :automaticallyAdjustContentInsets false :bounces false :decelerationRate 0.9 diff --git a/src/syng_im/components/chat.cljs b/src/syng_im/components/chat.cljs index 6ae060535a..18efbb4b3b 100644 --- a/src/syng_im/components/chat.cljs +++ b/src/syng_im/components/chat.cljs @@ -25,9 +25,12 @@ (into {}))) (defn add-msg-color [{:keys [from] :as msg} contact-by-identity] - (let [{:keys [text-color background-color]} (get contact-by-identity from)] - (assoc msg :text-color text-color - :background-color background-color))) + (if (= "system" from) + (assoc msg :text-color "#4A5258" + :background-color "#D3EEEF") + (let [{:keys [text-color background-color]} (get contact-by-identity from)] + (assoc msg :text-color text-color + :background-color background-color)))) (defn chat [{:keys [navigator]}] (let [messages (subscribe [:get-chat-messages]) @@ -42,18 +45,34 @@ :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 (when (and (:group-chat @chat) + (:is-active @chat)) + [{:title "Add Contact to chat" + :icon res/add-icon + :showWithText true} + {:title "Remove Contact from chat" + :icon res/trash-icon + :showWithText true} + {:title "Leave Chat" + :icon res/leave-icon + :showWithText true}]) + :onActionSelected (fn [position] + (case position + 0 (dispatch [:show-add-participants navigator]) + 1 (dispatch [:show-remove-participants navigator]) + 2 (dispatch [:leave-group-chat navigator]))) + :onIconClicked (fn [] + (nav-pop navigator))}]) [list-view {:dataSource datasource :renderScrollComponent (fn [props] (invertible-scroll-view nil)) @@ -62,4 +81,5 @@ (add-msg-color contact-by-identity))] (r/as-element [chat-message msg]))) :style {:backgroundColor "white"}}] - [chat-message-new]])))) + (when (:is-active @chat) + [chat-message-new])])))) diff --git a/src/syng_im/components/chat/chat_message.cljs b/src/syng_im/components/chat/chat_message.cljs index 8e2a3540c1..20ce00e755 100644 --- a/src/syng_im/components/chat/chat_message.cljs +++ b/src/syng_im/components/chat/chat_message.cljs @@ -86,28 +86,27 @@ content)]])) (defn message-content [{:keys [content-type content outgoing text-color background-color]}] - (let [_ (log/debug color)] - [view {:style (merge {:borderRadius 6} - (if (= content-type text-content-type) - {:paddingVertical 12 - :paddingHorizontal 16} - {:paddingVertical 14 - :paddingHorizontal 10}) - (if outgoing - {:backgroundColor "#D3EEEF"} - {:backgroundColor background-color}))} - (cond - (= content-type text-content-type) - [text {:style (merge {:fontSize 14 - :fontFamily "Avenir-Roman"} - (if outgoing - {:color "#4A5258"} - {:color text-color}))} - content] - (= content-type content-type-command) - [message-content-command content] - :else [message-content-audio {:content content - :content-type content-type}])])) + [view {:style (merge {:borderRadius 6} + (if (= content-type text-content-type) + {:paddingVertical 12 + :paddingHorizontal 16} + {:paddingVertical 14 + :paddingHorizontal 10}) + (if outgoing + {:backgroundColor "#D3EEEF"} + {:backgroundColor background-color}))} + (cond + (= content-type text-content-type) + [text {:style (merge {:fontSize 14 + :fontFamily "Avenir-Roman"} + (if outgoing + {:color "#4A5258"} + {:color text-color}))} + content] + (= content-type content-type-command) + [message-content-command content] + :else [message-content-audio {:content content + :content-type content-type}])]) (defn message-delivery-status [{:keys [delivery-status]}] [view {:style {:flexDirection "row" 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/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/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 6d1eba9c58..a1fd91e52e 100644 --- a/src/syng_im/db.cljs +++ b/src/syng_im/db.cljs @@ -9,11 +9,12 @@ :identity-password "replace-me-with-user-entered-password" :contacts [] :chat {:current-chat-id "0x0479a5ed1f38cadfad1db6cd56c4b659b0ebe052bbe9efa950f6660058519fa4ca6be2dda66afa80de96ab00eb97a2605d5267a1e8f4c2a166ab551f6826608cdd" - :command nil} + :command nil} :chats {} :chats-updated-signal 0 :name "My Name" - :new-group #{}}) + :new-group #{} + :new-participants #{}}) (def protocol-initialized-path [:protocol-initialized]) @@ -29,6 +30,7 @@ (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]) (def updated-discoveries-signal-path [:discovery-updated-signal]) (defn updated-discovery-signal-path [whisper-id] [:discoveries whisper-id :discovery-updated-signal]) diff --git a/src/syng_im/handlers.cljs b/src/syng_im/handlers.cljs index aefc61a686..5a54bc1b57 100644 --- a/src/syng_im/handlers.cljs +++ b/src/syng_im/handlers.cljs @@ -18,18 +18,28 @@ set-chat-command-content]] [syng-im.handlers.sign-up :as sign-up-service] [syng-im.handlers.discovery :as discovery] - [syng-im.models.chats :refer [create-chat]] + [syng-im.models.chats :refer [create-chat + chat-add-participants + chat-remove-participants + set-chat-active]] [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]] - [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 ------------------------------------------------------------ ;; @@ -77,9 +87,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 -------------------------------------------------------------- @@ -110,14 +122,98 @@ (save-message chat-id msg) (signal-chat-updated db chat-id))) +(defn joined-chat-msg [chat-id from msg-id] + (let [contact-name (:name (contacts/contact-by-identity from))] + (save-message chat-id {:from "system" + :msg-id msg-id + :content (str (or contact-name from) " received chat invitation") + :content-type text-content-type}))) + +(defn participant-invited-to-group-msg [chat-id identity from msg-id] + (let [inviter-name (:name (contacts/contact-by-identity from)) + invitee-name (:name (contacts/contact-by-identity identity))] + (save-message chat-id {:from "system" + :msg-id msg-id + :content (str (or inviter-name from) " invited " (or invitee-name identity)) + :content-type text-content-type}))) + +(defn participant-removed-from-group-msg [chat-id identity from msg-id] + (let [remover-name (:name (contacts/contact-by-identity from)) + removed-name (:name (contacts/contact-by-identity identity))] + (save-message chat-id {:from "system" + :msg-id msg-id + :content (str (or remover-name from) " removed " (or removed-name identity)) + :content-type text-content-type}))) + +(defn you-removed-from-group-msg [chat-id from msg-id] + (let [remover-name (:name (contacts/contact-by-identity from))] + (save-message chat-id {:from "system" + :msg-id msg-id + :content (str (or remover-name from) " removed you from group chat") + :content-type text-content-type}))) + +(defn participant-left-group-msg [chat-id from msg-id] + (let [left-name (:name (contacts/contact-by-identity from))] + (save-message chat-id {:from "system" + :msg-id msg-id + :content (str (or left-name from) " left") + :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}))) + +(defn left-chat-msg [chat-id] + (save-message chat-id {:from "system" + :msg-id (random/id) + :content "You left this chat" + :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) + (joined-chat-msg group-id from ack-msg-id) + (signal-chat-updated db group-id))) + +(register-handler :participant-removed-from-group + (fn [db [action from group-id identity msg-id]] + (log/debug action msg-id from group-id identity) + (chat-remove-participants group-id [identity]) + (participant-removed-from-group-msg group-id identity from msg-id) + (signal-chat-updated db group-id))) + +(register-handler :you-removed-from-group + (fn [db [action from group-id msg-id]] + (log/debug action msg-id from group-id) + (you-removed-from-group-msg group-id from msg-id) + (set-chat-active group-id false) + (signal-chat-updated db group-id))) + +(register-handler :participant-left-group + (fn [db [action from group-id msg-id]] + (log/debug action msg-id from group-id) + (participant-left-group-msg group-id from msg-id) + (signal-chat-updated db group-id))) + +(register-handler :participant-invited-to-group + (fn [db [action from group-id identity msg-id]] + (log/debug action msg-id from group-id identity) + (participant-invited-to-group-msg group-id identity from msg-id) + (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 msg-id) (update-message! {:msg-id msg-id :delivery-status :failed}) (let [{:keys [chat-id]} (message-by-id msg-id)] @@ -128,7 +224,7 @@ (log/debug action "chat-id" chat-id "text" text) (let [msg (if (= chat-id "console") (sign-up-service/send-console-msg text) - (let [{msg-id :msg-id + (let [{msg-id :msg-id {from :from to :to} :msg} (api/send-user-msg {:to chat-id :content text})] @@ -141,13 +237,22 @@ (save-message chat-id msg) (signal-chat-updated db chat-id)))) +(register-handler :leave-group-chat + (fn [db [action navigator]] + (log/debug action) + (let [chat-id (current-chat-id db)] + (api/leave-group-chat chat-id) + (set-chat-active chat-id false) + (left-chat-msg chat-id) + (signal-chat-updated db chat-id)))) + (register-handler :send-chat-command (fn [db [action chat-id command content]] (log/debug action "chat-id" chat-id "command" command "content" content) (let [msg (if (= chat-id "console") (sign-up-service/send-console-command command content) ;; TODO handle command, now sends as plain message - (let [{msg-id :msg-id + (let [{msg-id :msg-id {from :from to :to} :msg} (api/send-user-msg {:to chat-id :content content})] @@ -216,10 +321,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 @@ -248,6 +353,48 @@ (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-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) + (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) + (nav-pop navigator) + (doseq [ident identities] + (api/group-add-participant chat-id ident)) + db))) + (register-handler :show-group-new (fn [db [action navigator]] (log/debug action) @@ -266,7 +413,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/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..12caa0ecce 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] @@ -46,6 +47,7 @@ :background-color background :text-color text}) identities group-chat-colors)] (r/create :chats {:chat-id chat-id + :is-active true :name chat-name :group-chat group-chat? :timestamp (timestamp) @@ -61,8 +63,52 @@ (r/single-cljs) (r/list-to-array :contacts))) -(comment +(defn chat-add-participants [chat-id identities] + (r/write + (fn [] + (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 && is-active = true"))] + (->> (.map results (fn [object index collection] + (aget object "chat-id"))) + (js->clj)))) + + +(defn set-chat-active [chat-id active?] + (r/write (fn [] + (-> (r/get-by-field :chats :chat-id chat-id) + (r/single) + (aset "is-active" active?))))) + +(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 41cbd2742e..478f1a5e0e 100644 --- a/src/syng_im/models/contacts.cljs +++ b/src/syng_im/models/contacts.cljs @@ -3,7 +3,10 @@ [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] + [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 (def fake-phone-contacts? true) @@ -91,18 +94,44 @@ (-> (r/get-all :contacts) (r/sorted :name :asc))) +(defn contacts-list-exclude [exclude-idents] + (let [query (exclude-query :whisper-identity exclude-idents)] + (-> (r/get-all :contacts) + (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] + (-> (r/get-by-field :contacts :whisper-identity identity) + (r/single-cljs))) + (comment (r/write #(create-contact {:phone-number "0543072333" - :whisper-identity "0x04ed4c3797026cddeb7d64a54ca58142e57ea03cda21072358d67455b506db90c56d95033e3d221992f70d01922c3d90bf0697c49e4be118443d03ae4a1cd3c15c" + :whisper-identity "0x043e3a8344049fb48fef030084212a9d41577a5dea18aeb4c8f285c16f783aa84e43f84c32eb8601e22827b12d5f93f14e545f9023034a0521dc18484bbbc44704" :name "Mr. Bean" :photo-path ""})) (r/write #(create-contact {:phone-number "0544828649" - :whisper-identity "0x0498bcce41dbe05c6d4776ef50d12c2ef1a00d9d7f7144d174ece3dce85ca3428bf0900352abcccdc463bd2cfa4ec319cda46c2079152c4cb14d1cad9a00dd7571" + :whisper-identity "0x04e9b01298dd12c4d8f0393d7890302b25762966d825158d1fdffe124703c0efcd7f23a6cf71c466ca50b2af3d54264ea5f224a19ba7775779c1ddbcb237258c5c" :name "Mr. Batman" :photo-path ""})) + (r/write #(create-contact {:phone-number "0522222222" + :whisper-identity "0x0487954e7fa746d8cf787403c2c491aadad540b9bb1f0f7b8184792e91c33b6a394079295f5777ec6d4af9ad5ba24794b3ff1ec8be9ff6a708c85a163733192665" + :name "Mr. Eagle" + :photo-path ""})) + + (r/write #(create-contact {:phone-number "0533333333" + :whisper-identity "0x04e43e861a6dd99ad9eee7bd58af89dcaa430188ebec8698de7b7bad54573324fff4ac5cb9bb277af317efd7abfc917b91bf48cc41e40bf70062fd79400016a1f9" + :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 23dfec7c45..27483c3c86 100644 --- a/src/syng_im/persistence/realm.cljs +++ b/src/syng_im/persistence/realm.cljs @@ -41,7 +41,9 @@ :primaryKey :chat-id :properties {:chat-id "string" :name "string" - :group-chat "bool" + :group-chat {:type "bool" + :indexed true} + :is-active "bool" :timestamp "int" :contacts {:type "list" :objectType "chat-contact"}}} @@ -115,6 +117,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)) @@ -134,8 +139,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)) @@ -150,22 +154,4 @@ (comment - (write #(.create realm "msgs" (clj->js {:msg-id "12" - :content "sdfd" - :from "sdfsd" - :chat-id "56" - :content-type "fg" - :timestamp 2 - :outgoing true - :to "sfs" - :delivery-status "seen"}) true)) - - (.addListener realm "change" (fn [& args] - (log/debug args))) - - ;realm.addListener('change', () => { - ; // Update UI - ; ... - ; }); - ) \ No newline at end of file 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/protocol/protocol_handler.cljs b/src/syng_im/protocol/protocol_handler.cljs index ac1eae6668..dadeee0b5f 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)) @@ -28,20 +30,16 @@ payload :payload} event] (dispatch [:group-received-msg (assoc payload :from from :group-id group-id)])) - ;:group-chat-invite-acked (let [{:keys [from group-id]} event] - ; (add-to-chat "group-chat" ":" (str "Received ACK for group chat invitation from " from " for group-id: " group-id))) - ;:group-new-participant (let [{:keys [group-id identity from]} event] - ; (add-to-chat "group-chat" ":" (str (shorten from) " added " (shorten identity) " to group chat")) - ; (add-identity-to-group-list identity)) - ;:group-removed-participant (let [{:keys [group-id identity from]} event] - ; (add-to-chat "group-chat" ":" (str (shorten from) " removed " (shorten identity) " from group chat")) - ; (remove-identity-from-group-list identity)) - ;:removed-from-group (let [{:keys [group-id from]} event] - ; (add-to-chat "group-chat" ":" (str (shorten from) " removed you from group chat"))) - ;:participant-left-group (let [{:keys [group-id from]} event] - ; (add-to-chat "group-chat" ":" (str (shorten from) " left group chat"))) + :group-chat-invite-acked (let [{:keys [from group-id ack-msg-id]} event] + (dispatch [:group-chat-invite-acked from group-id ack-msg-id])) + :group-new-participant (let [{:keys [group-id identity from msg-id]} event] + (dispatch [:participant-invited-to-group from group-id identity msg-id])) + :group-removed-participant (let [{:keys [group-id identity from msg-id]} event] + (dispatch [:participant-removed-from-group from group-id identity msg-id])) + :removed-from-group (let [{:keys [group-id from msg-id]} event] + (dispatch [:you-removed-from-group from group-id msg-id])) + :participant-left-group (let [{:keys [group-id from msg-id]} event] + (dispatch [:participant-left-group from group-id msg-id])) :discover-response (let [{:keys [from payload]} event] (dispatch [:discovery-response-received from payload])) - ;(add-to-chat "chat" ":" (str "Don't know how to handle " event-type)) - (log/info "Don't know how to handle" event-type) - ))}) + (log/info "Don't know how to handle" event-type)))}) diff --git a/src/syng_im/resources.cljs b/src/syng_im/resources.cljs index 086e68d7da..47cc7f6521 100644 --- a/src/syng_im/resources.cljs +++ b/src/syng_im/resources.cljs @@ -13,5 +13,8 @@ (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")) +(def trash-icon (js/require "./images/trash.png")) +(def leave-icon (js/require "./images/leave.png")) (def menu (js/require "./images/ic_menu_black_24dp_1x.png")) -(def search (js.require "./images/ic_search_black_24dp_1x.png")) \ No newline at end of file +(def search (js.require "./images/ic_search_black_24dp_1x.png")) diff --git a/src/syng_im/subs.cljs b/src/syng_im/subs.cljs index b5611fc89a..ab73a06a23 100644 --- a/src/syng_im/subs.cljs +++ b/src/syng_im/subs.cljs @@ -9,7 +9,9 @@ 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 + contacts-list-include]] [syng-im.handlers.suggestions :refer [get-suggestions]])) ;; -- Chat -------------------------------------------------------------- @@ -59,10 +61,13 @@ (register-sub :get-current-chat (fn [db _] (let [current-chat-id (-> (current-chat-id @db) + (reaction)) + chat-updated (-> (chat-updated? @db @current-chat-id) (reaction))] - (-> (when-let [chat-id @current-chat-id] - (chat-by-id chat-id)) - (reaction))))) + (reaction + (let [_ @chat-updated] + (when-let [chat-id @current-chat-id] + (chat-by-id chat-id))))))) @@ -102,3 +107,31 @@ (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))))))) + +(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)))))))