From 319bb656ec6848954fe9afb34a191cd4c04dfca7 Mon Sep 17 00:00:00 2001 From: Roman Volosovskyi Date: Fri, 6 May 2016 14:06:58 +0300 Subject: [PATCH 1/4] refactoring --- project.clj | 6 +- src/syng_im/android/core.cljs | 59 ++- src/syng_im/components/chat.cljs | 386 +++++++++++------- src/syng_im/components/chat/chat_message.cljs | 297 ++++++++------ .../components/chat/input/simple_command.cljs | 69 ++-- .../chat/input/simple_command_staged.cljs | 14 +- .../components/chat/plain_message_input.cljs | 57 ++- src/syng_im/components/chats/chats_list.cljs | 4 +- src/syng_im/components/react.cljs | 2 +- src/syng_im/db.cljs | 13 +- src/syng_im/handlers.cljs | 362 ++++++++++------ src/syng_im/handlers/sign_up.cljs | 258 ++++++------ src/syng_im/models/chats.cljs | 56 +-- src/syng_im/models/commands.cljs | 128 +++--- src/syng_im/models/messages.cljs | 52 ++- src/syng_im/persistence/realm.cljs | 7 +- src/syng_im/subs.cljs | 42 +- src/syng_im/utils/listview.cljs | 8 + 18 files changed, 1003 insertions(+), 817 deletions(-) diff --git a/project.clj b/project.clj index 37ed02bf72..6c68c58041 100644 --- a/project.clj +++ b/project.clj @@ -8,7 +8,9 @@ [reagent "0.5.1" :exclusions [cljsjs/react]] [re-frame "0.6.0"] [prismatic/schema "1.0.4"] - [syng-im/protocol "0.1.1"] + ^{:voom {:repo "https://github.com/status-im/status-lib.git" + :branch "master"}} + [syng-im/protocol "0.1.1-20160430_080316-gf359cb7"] [natal-shell "0.1.6"]] :plugins [[lein-cljsbuild "1.1.1"] [lein-figwheel "0.5.0-2"]] @@ -44,4 +46,4 @@ :main "env.android.main" :output-dir "target/android" :optimizations :simple}}}} - }}) \ No newline at end of file + }}) diff --git a/src/syng_im/android/core.cljs b/src/syng_im/android/core.cljs index d417df1007..28085cad7c 100644 --- a/src/syng_im/android/core.cljs +++ b/src/syng_im/android/core.cljs @@ -21,50 +21,39 @@ (def back-button-handler (cljs/atom {:nav nil :handler nil})) -(defn init-back-button-handler! [nav] - (let [handler @back-button-handler] - (when-not (= nav (:nav handler)) - (remove-event-listener "hardwareBackPress" (:handler handler)) - (let [new-listener (fn [] - (binding [nav/*nav-render* false] - (when (< 1 (.-length (.getCurrentRoutes nav))) - (nav/nav-pop nav) - true)))] - (reset! back-button-handler {:nav nav - :handler new-listener}) - (add-event-listener "hardwareBackPress" new-listener))))) +(defn init-back-button-handler! [] + (let [new-listener (fn [] + ;; todo: it might be better always return false from + ;; this listener and handle application's closing + ;; in handlers + (let [stack (subscribe [:navigation-stack])] + (when (< 1 (count stack)) + (dispatch [:navigate-back]) + true)))] + (add-event-listener "hardwareBackPress" new-listener))) (defn app-root [] - (let [signed-up-atom (subscribe [:signed-up])] + (let [signed-up (subscribe [:signed-up]) + view-id (subscribe [:view-id])] (fn [] - (let [signed-up @signed-up-atom] - [navigator {:initial-route (clj->js {:view-id - :chat-list - ;:chat - }) - :render-scene (fn [route nav] - (log/debug "route" route) - (when true ;; nav/*nav-render* - (if signed-up - (let [{:keys [view-id]} (js->clj route :keywordize-keys true) - view-id (keyword view-id)] - (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}]) - :chat (r/as-element [chat {:navigator nav}]))) - (r/as-element [chat {:navigator nav}]))))}])))) + (case (if @signed-up @view-id :chat) + :add-participants [new-participants] + :remove-participants [remove-participants] + :chat-list [chats-list] + :new-group [new-group] + :contact-list [contact-list] + :chat [chat])))) (defn init [] (dispatch-sync [:initialize-db]) (dispatch [:initialize-crypt]) + (dispatch [:initialize-chats]) (dispatch [:initialize-protocol]) (dispatch [:load-user-phone-number]) (dispatch [:load-syng-contacts]) ;; load commands from remote server (todo: uncomment) ;; (dispatch [:load-commands]) - (dispatch-sync [:init-console-chat]) - (.registerComponent app-registry "SyngIm" #(r/reactify-component app-root))) + (dispatch [:init-console-chat]) + #_(dispatch [:init-chat]) + #_(init-back-button-handler!) + #_(.registerComponent app-registry "SyngIm" #(r/reactify-component app-root))) diff --git a/src/syng_im/components/chat.cljs b/src/syng_im/components/chat.cljs index cffdf19a9b..dda9f69d58 100644 --- a/src/syng_im/components/chat.cljs +++ b/src/syng_im/components/chat.cljs @@ -5,8 +5,11 @@ text image navigator - touchable-highlight]] - [syng-im.components.realm :refer [list-view]] + touchable-highlight + toolbar-android + list-view + list-item + android?]] [syng-im.components.styles :refer [font title-font color-white @@ -18,11 +21,11 @@ text2-color toolbar-background1]] [syng-im.utils.logging :as log] - [syng-im.navigation :refer [nav-pop]] [syng-im.resources :as res] - [syng-im.utils.listview :refer [to-realm-datasource]] + [syng-im.constants :refer [content-type-status]] + [syng-im.utils.listview :refer [to-datasource + to-datasource2]] [syng-im.components.invertible-scroll-view :refer [invertible-scroll-view]] - [reagent.core :as r] [syng-im.components.chat.chat-message :refer [chat-message]] [syng-im.components.chat.chat-message-new :refer [chat-message-new]])) @@ -78,12 +81,12 @@ :backgroundColor color-white}]])) (defn typing [member] - [view {:style {:width 260 - :marginTop 10 - :paddingLeft 8 - :paddingRight 8 - :alignItems "flex-start" - :alignSelf "flex-start"}} + [view {:style {:width 260 + :marginTop 10 + :paddingLeft 8 + :paddingRight 8 + :alignItems "flex-start" + :alignSelf "flex-start"}} [view {:style {:borderRadius 14 :padding 12 :height 38 @@ -99,6 +102,89 @@ (for [member ["Geoff" "Justas"]] ^{:key member} [typing member])]) +(defn toolbar-content-chat [group-chat] + (let + [contacts (subscribe [:chat :contacts]) + name (subscribe [:chat :name])] + (fn [group-chat] + [view {:style {:flex 1 + :flexDirection "row" + :backgroundColor "transparent"}} + [view {:style {:flex 1 + :alignItems "flex-start" + :justifyContent "center" + :marginRight 112}} + [text {:style {:marginTop -2.5 + :color text1-color + :fontSize 16 + :fontFamily font}} + (or @name "Chat name")] + (if group-chat + [view {:style {:flexDirection "row"}} + [image {:source {:uri :icon_group} + :style {:marginTop 4 + :width 14 + :height 9}}] + [text {:style {:marginTop -0.5 + :marginLeft 4 + :fontFamily font + :fontSize 12 + :color text2-color}} + (let [cnt (count @contacts)] + (str cnt + (if (< 1 cnt) + ;; TODO https://github.com/r0man/inflections-clj + " members" + " member") + ", " cnt " active"))]] + [text {:style {:marginTop 1 + :color text2-color + :fontSize 12 + :fontFamily font}} + "Active a minute ago"])] + (when-not group-chat + [view {:style {:position "absolute" + :top 10 + :right 66}} + [chat-photo {}] + [contact-online {:online true}]])]))) + +(defn message-row [contact-by-identity group-chat] + (fn [row _ _] + (let [msg (-> row + (add-msg-color contact-by-identity) + (assoc :group-chat group-chat))] + (list-item [chat-message msg])))) + +(def group-caht-actions + [{: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}]) + +(defn on-action-selected [position] + (case position + 0 (dispatch [:show-add-participants #_navigator]) + 1 (dispatch [:show-remove-participants #_navigator]) + 2 (dispatch [:leave-group-chat #_navigator]))) + +(defn overlay [{:keys [on-click-outside]} items] + [view {:position :absolute + :top 0 + :bottom 0 + :left 0 + :right 0} + [touchable-highlight {:on-press on-click-outside + :underlay-color :transparent + :style {:flex 1}} + [view nil]] + items]) + (defn action-view [action] [touchable-highlight {:on-press (fn [] (dispatch [:set-show-actions false]) @@ -127,156 +213,140 @@ :fontFamily font}} subtitle])]]]) -(defn actions-list-view [navigator chat] - (when-let [actions (when (and (:group-chat chat) - (:is-active chat)) - [{:title "Add Contact to chat" - :icon "icon_menu_group" - :icon-style {:width 25 - :height 19} - :handler #(dispatch [:show-add-participants navigator])} - {:title "Remove Contact from chat" - :subtitle "Alex, John" - :icon "icon_search_gray_copy" - :icon-style {:width 17 - :height 17} - :handler #(dispatch [:show-remove-participants navigator])} - {:title "Leave Chat" - :icon "icon_muted" - :icon-style {:width 18 - :height 21} - :handler #(dispatch [:leave-group-chat navigator])} - {:title "Settings" - :subtitle "Not implemented" - :icon "icon_settings" - :icon-style {:width 20 - :height 13} - :handler (fn [] )}])] - [view {:style {:backgroundColor toolbar-background1 - :elevation 2 - :position "absolute" - :top 56 - :left 0 - :right 0}} - [view {:style {:marginLeft 16 - :height 1.5 - :backgroundColor separator-color}}] - [view {:style {:marginVertical 10}} - (for [action actions] - ^{:key action} [action-view action])]])) +(defn actions-list-view [] + (let [{:keys [group-chat active]} + (subscribe [:chat-properties [:group-chat :name :contacts :active]])] + (when-let [actions (when (and @group-chat @active) + [{:title "Add Contact to chat" + :icon :icon_menu_group + :icon-style {:width 25 + :height 19} + :handler nil #_#(dispatch [:show-add-participants + navigator])} + {:title "Remove Contact from chat" + :subtitle "Alex, John" + :icon :icon_search_gray_copy + :icon-style {:width 17 + :height 17} + :handler nil #_#(dispatch + [:show-remove-participants navigator])} + {:title "Leave Chat" + :icon :icon_muted + :icon-style {:width 18 + :height 21} + :handler nil #_#(dispatch [:leave-group-chat + navigator])} + {:title "Settings" + :subtitle "Not implemented" + :icon :icon_settings + :icon-style {:width 20 + :height 13} + :handler (fn [])}])] + [view {:style {:backgroundColor toolbar-background1 + :elevation 2 + :position :absolute + :top 56 + :left 0 + :right 0}} + [view {:style {:marginLeft 16 + :height 1.5 + :backgroundColor separator-color}}] + [view {:style {:marginVertical 10}} + (for [action actions] + ^{:key action} [action-view action])]]))) -(defn overlay [{:keys [on-click-outside]} items] - [view {:position "absolute" - :top 0 - :bottom 0 - :left 0 - :right 0} - [touchable-highlight {:on-press on-click-outside - :underlay-color :transparent - :style {:flex 1}} - [view nil]] - items]) +(defn actions-view [] + [overlay {:on-click-outside #(dispatch [:set-show-actions false])} + [actions-list-view]]) -(defn actions-view [navigator chat] - [overlay {:on-click-outside (fn [] - (dispatch [:set-show-actions false]))} - [actions-list-view navigator chat]]) +(defn toolbar [] + (let [{:keys [group-chat name contacts]} + (subscribe [:chat-properties [:group-chat :name :contacts]]) + show-actions (subscribe [:show-actions])] + (fn [] + [view {:style {:flexDirection "row" + :height 56 + :backgroundColor toolbar-background1 + :elevation 2}} + (when (not @show-actions) + [touchable-highlight {:on-press #(dispatch [:navigate-back]) + :underlay-color :transparent} + [view {:width 56 + :height 56} + [image {:source {:uri "icon_back"} + :style {:marginTop 21 + :marginLeft 23 + :width 8 + :height 14}}]]]) + [view {:style {:flex 1 + :marginLeft (if @show-actions 16 0) + :alignItems "flex-start" + :justifyContent "center"}} + [text {:style {:marginTop -2.5 + :color text1-color + :fontSize 16 + :fontFamily font}} + (or @name "Chat name")] + (if @group-chat + [view {:style {:flexDirection :row}} + [image {:source {:uri :icon_group} + :style {:marginTop 4 + :width 14 + :height 9}}] + [text {:style {:marginTop -0.5 + :marginLeft 4 + :fontFamily font + :fontSize 12 + :color text2-color}} + (let [cnt (count @contacts)] + (str cnt + (if (< 1 cnt) + " members" + " member") + ", " cnt " active"))]] + [text {:style {:marginTop 1 + :color text2-color + :fontSize 12 + :fontFamily font}} + "Active a minute ago"])] + (if @show-actions + [touchable-highlight + {:on-press #(dispatch [:set-show-actions false]) + :underlay-color :transparent} + [view {:style {:width 56 + :height 56}} + [image {:source {:uri :icon_up} + :style {:marginTop 23 + :marginLeft 21 + :width 14 + :height 8}}]]] + [touchable-highlight + {:on-press #(dispatch [:set-show-actions true]) + :underlay-color :transparent} + [view {:style {:width 56 + :height 56}} + [chat-photo {}] + [contact-online {:online true}]]])]))) -(defn toolbar [navigator chat show-actions] - [view {:style {:flexDirection "row" - :height 56 - :backgroundColor toolbar-background1 - :elevation 2}} - (when (not show-actions) - [touchable-highlight {:on-press (fn [] - (nav-pop navigator)) - :underlay-color :transparent} - [view {:width 56 - :height 56} - [image {:source {:uri "icon_back"} - :style {:marginTop 21 - :marginLeft 23 - :width 8 - :height 14}}]]]) - [view {:style {:flex 1 - :marginLeft (if show-actions 16 0) - :alignItems "flex-start" - :justifyContent "center"}} - [text {:style {:marginTop -2.5 - :color text1-color - :fontSize 16 - :fontFamily font}} - (or (chat :name) - "Chat name")] - (if (:group-chat chat) - [view {:style {:flexDirection "row"}} - [image {:source {:uri "icon_group"} - :style {:marginTop 4 - :width 14 - :height 9}}] - [text {:style {:marginTop -0.5 - :marginLeft 4 - :fontFamily font - :fontSize 12 - :color text2-color}} - (str (count (:contacts chat)) - (if (< 1 (count (:contacts chat))) - " members" - " member") - ", " (count (:contacts chat)) " active")]] - [text {:style {:marginTop 1 - :color text2-color - :fontSize 12 - :fontFamily font}} - "Active a minute ago"])] - (if show-actions - [touchable-highlight {:on-press (fn [] - (dispatch [:set-show-actions false])) - :underlay-color :transparent} - [view {:style {:width 56 - :height 56}} - [image {:source {:uri "icon_up"} - :style {:marginTop 23 - :marginLeft 21 - :width 14 - :height 8}}]]] - [touchable-highlight {:on-press (fn [] - (dispatch [:set-show-actions true])) - :underlay-color :transparent} - [view {:style {:width 56 - :height 56}} - [chat-photo {}] - [contact-online {:online true}]]])]) +(defn messages-view [group-chat] + (let [messages (subscribe [:chat :messages]) + contacts (subscribe [:chat :contacts])] + (fn [group-chat] + (let [contacts' (contacts-by-identity @contacts)] + [list-view {:renderRow (message-row contacts' group-chat) + :renderScrollComponent #(invertible-scroll-view (js->clj %)) + :onEndReached #(dispatch [:load-more-messages]) + :dataSource (to-datasource2 @messages)}])))) -(defn chat [{:keys [navigator]}] - (let [messages (subscribe [:get-chat-messages]) - chat (subscribe [:get-current-chat]) +(defn chat [] + (let [is-active (subscribe [:chat :is-active]) + group-chat (subscribe [:chat :group-chat]) show-actions-atom (subscribe [:show-actions])] (fn [] - (let [msgs @messages - ;_ (log/debug "messages=" msgs) - ;; temp - typing (:group-chat @chat) - ;; end temp - datasource (to-realm-datasource msgs) - contacts (:contacts @chat) - contact-by-identity (contacts-by-identity contacts)] - [view {:style {:flex 1 - :backgroundColor chat-background}} - [toolbar navigator @chat @show-actions-atom] - (let [last-msg-id (:last-msg-id @chat)] - [list-view {:dataSource datasource - :renderScrollComponent (fn [props] - (invertible-scroll-view (js->clj props))) - :renderRow (fn [row section-id row-id] - (let [msg (-> (js->clj row :keywordize-keys true) - (add-msg-color contact-by-identity) - (assoc :group-chat (:group-chat @chat)) - (assoc :typing typing))] - (r/as-element [chat-message msg last-msg-id])))}]) - (when (:group-chat @chat) - [typing-all]) - (when (:is-active @chat) - [chat-message-new]) - (when @show-actions-atom - [actions-view navigator @chat])])))) + [view {:style {:flex 1 + :backgroundColor chat-background}} + [toolbar] + [messages-view @group-chat] + (when @group-chat [typing-all]) + (when is-active [chat-message-new]) + (when @show-actions-atom [actions-view])]))) diff --git a/src/syng_im/components/chat/chat_message.cljs b/src/syng_im/components/chat/chat_message.cljs index 1127525b45..7b1b8a3726 100644 --- a/src/syng_im/components/chat/chat_message.cljs +++ b/src/syng_im/components/chat/chat_message.cljs @@ -43,13 +43,12 @@ [view {:style {:backgroundColor color-light-blue-transparent :height 24 :borderRadius 50 - :alignSelf "center" + :alignSelf :center :marginTop 20 :marginBottom 20 :paddingTop 5 :paddingHorizontal 12}} - [text {:style (merge style-sub-text - {:textAlign "center"})} + [text {:style (assoc style-sub-text :textAlign :center)} date]]]) (defn contact-photo [{:keys [photo-path]}] @@ -63,7 +62,7 @@ (defn contact-online [{:keys [online]}] (when online - [view {:position "absolute" + [view {:position :absolute :top 44 :left 44 :width 24 @@ -72,14 +71,14 @@ :backgroundColor online-color :borderWidth 2 :borderColor color-white} - [view {:position "absolute" + [view {:position :absolute :top 8 :left 5 :width 4 :height 4 :borderRadius 50 :backgroundColor color-white}] - [view {:position "absolute" + [view {:position :absolute :top 8 :left 11 :width 4 @@ -88,11 +87,11 @@ :backgroundColor color-white}]])) -(defn message-content-status [from content] - [view {:style {:flex 1 - :alignSelf "center" - :alignItems "center" - :width 249}} +(defn message-content-status [{:keys [from content]}] + [view {:style {:flex 1 + :alignSelf :center + :alignItems :center + :width 249}} [view {:style {:marginTop 20}} [contact-photo {}] [contact-online {:online true}]] @@ -105,13 +104,13 @@ :fontFamily font :fontSize 14 :lineHeight 20 - :textAlign "center" + :textAlign :center :color text2-color}} content]]) -(defn message-content-audio [{:keys [content-type content-type]}] - [view {:style {:flexDirection "row" - :alignItems "center"}} +(defn message-content-audio [_] + [view {:style {:flexDirection :row + :alignItems :center}} [view {:style {:width 33 :height 33 :borderRadius 50 @@ -124,18 +123,18 @@ :width 120 :height 26 :elevation 1}} - [view {:style {:position "absolute" + [view {:style {:position :absolute :top 4 :width 120 :height 2 :backgroundColor "#EC7262"}}] - [view {:style {:position "absolute" + [view {:style {:position :absolute :left 0 :top 0 :width 2 :height 10 :backgroundColor "#4A5258"}}] - [text {:style {:position "absolute" + [text {:style {:position :absolute :left 1 :top 11 :fontFamily font @@ -150,25 +149,26 @@ (let [commands-atom (subscribe [:get-commands])] (fn [content] (let [commands @commands-atom - {:keys [command content]} (parse-command-msg-content commands content)] - [view {:style {:flexDirection "column"}} - [view {:style {:flexDirection "row" + {:keys [command content]} + (parse-command-msg-content commands content)] + [view {:style {:flexDirection :column}} + [view {:style {:flexDirection :row :marginRight 32}} [view {:style {:backgroundColor (:color command) :height 24 :borderRadius 50 :paddingTop 3 :paddingHorizontal 12}} - [text {:style {:fontSize 12 - :fontFamily font - :color color-white}} + [text {:style {:fontSize 12 + :fontFamily font + :color color-white}} (:text command)]]] [image {:source (:icon command) - :style {:position "absolute" - :top 4 - :right 0 - :width 12 - :height 13}}] + :style {:position :absolute + :top 4 + :right 0 + :width 12 + :height 13}}] [text {:style (merge style-message-text {:marginTop 8 :marginHorizontal 0})} @@ -180,13 +180,14 @@ (defn set-chat-command [msg-id command] (dispatch [:set-response-chat-command msg-id (:command command)])) -(defn message-content-command-request [msg-id from content outgoing group-chat] + +(defn message-content-command-request + [{:keys [msg-id content outgoing group-chat from]}] (let [commands-atom (subscribe [:get-commands])] - (fn [msg-id from content outgoing group-chat] + (fn [{:keys [msg-id content outgoing group-chat from]}] (let [commands @commands-atom {:keys [command content]} (parse-command-request-msg-content commands content)] - [touchable-highlight {:onPress (fn [] - (set-chat-command msg-id command)) + [touchable-highlight {:onPress #(set-chat-command msg-id command) :underlay-color :transparent} [view {:style {:paddingRight 16}} [view {:style (merge {:borderRadius 14 @@ -199,7 +200,7 @@ from]) [text {:style style-message-text} content]] - [view {:style {:position "absolute" + [view {:style {:position :absolute :top 12 :right 0 :width 32 @@ -207,14 +208,14 @@ :borderRadius 50 :backgroundColor (:color command)}} [image {:source (:request-icon command) - :style {:position "absolute" + :style {:position :absolute :top 9 :left 10 :width 12 :height 13}}]] (when (:request-text command) - [view {:style {:marginTop 4 - :height 14}} + [view {:style {:marginTop 4 + :height 14}} [text {:style style-sub-text} (:request-text command)]])]])))) @@ -227,41 +228,105 @@ {:color color-white}))} content]) -(defn message-content [{:keys [msg-id from content-type content outgoing group-chat selected]}] - (if (= content-type content-type-command-request) - [message-content-command-request msg-id from content outgoing group-chat] - [view {:style (merge {:borderRadius 14 - :padding 12 - :backgroundColor color-white} - (when (= content-type content-type-command) - {:paddingTop 10 - :paddingBottom 14}) - (if outgoing - (when (and group-chat (= content-type text-content-type)) - {:backgroundColor color-blue}) - (when selected - {:backgroundColor selected-message-color})))} - (when (and group-chat (not outgoing)) - [text {:style (merge style-sub-text - {:marginBottom 2})} - from]) - (cond - (or (= content-type text-content-type) - (= content-type content-type-status)) - [message-content-plain content outgoing group-chat] - (= content-type content-type-command) - [message-content-command content] - :else [message-content-audio {:content content - :content-type content-type}])])) + +#_(defn message-content [{:keys [msg-id from content-type content outgoing + group-chat selected]}] + (if (= content-type content-type-command-request) + [message-content-command-request msg-id from content outgoing group-chat] + [view {:style (merge {:borderRadius 14 + :padding 12 + :backgroundColor color-white} + (when (= content-type content-type-command) + {:paddingTop 10 + :paddingBottom 14}) + (if outgoing + (when (and group-chat (= content-type text-content-type)) + {:backgroundColor color-blue}) + (when selected + {:backgroundColor selected-message-color})))} + (when (and group-chat (not outgoing)) + [text {:style (merge style-sub-text + {:marginBottom 2})} + from]) + (cond + (or (= content-type text-content-type) + (= content-type content-type-status)) + [message-content-plain content outgoing group-chat] + (= content-type content-type-command) + [message-content-command content] + :else [message-content-audio {:content content + :content-type content-type}])])) + +(defn message-view + [{:keys [content-type outgoing background-color group-chat selected]} content] + [view {:style (merge {:borderRadius 14 + :padding 12} + (if outgoing + (if (and group-chat (= content-type text-content-type)) + {:backgroundColor color-blue} + {:backgroundColor color-white}) + (if selected + {:backgroundColor selected-message-color} + {:backgroundColor background-color})))} + #_(when (and group-chat (not outgoing)) + [text {:style {:marginTop 0 + :fontSize 12 + :fontFamily font}} + "Justas"]) + content]) + +(defmulti message-content (fn [_ message] + (message :content-type))) + +(defmethod message-content content-type-command-request + [wrapper message] + [wrapper message [message-content-command-request message]]) + +(defn text-message + [{:keys [content outgoing text-color group-chat] :as message}] + [message-view message + [text {:style {:marginTop (if (and group-chat (not outgoing)) + 4 + 0) + :fontSize 14 + :fontFamily font + :color (cond + (and outgoing group-chat) color-white + outgoing text1-color + :else text-color)}} + content]]) + +(defmethod message-content text-content-type + [wrapper message] + [wrapper message [text-message message]]) + +(defmethod message-content content-type-status + [_ message] + ;; todo should it be rendered as text message? + [message-content-status message] + #_[text-message message]) + +(defmethod message-content content-type-command + [wrapper {:keys [content] :as message}] + [wrapper message + [message-view message [message-content-command content]]]) + +(defmethod message-content :default + [wrapper {:keys [content-type content] :as message}] + [wrapper message + [message-view message + [message-content-audio {:content content + :content-type content-type}]]]) (defn message-delivery-status [{:keys [delivery-status]}] - [view {:style {:flexDirection "row" + [view {:style {:flexDirection :row :marginTop 2}} + [image {:source (case delivery-status - :delivered {:uri "icon_ok_small"} - :seen {:uri "icon_ok_small"} - :seen-by-everyone {:uri "icon_ok_small"} - :failed res/delivery-failed-icon) + :delivered {:uri :icon_ok_small} + :seen {:uri :icon_ok_small} + :seen-by-everyone {:uri :icon_ok_small} + :failed res/delivery-failed-icon) :style {:marginTop 6 :width 9 :height 7}}] @@ -284,11 +349,12 @@ :width 24 :height 24}}]]) -(defn incoming-group-message-body [{:keys [msg-id from content content-type outgoing - delivery-status selected new-day same-author - same-direction last-msg typing]}] + +(defn incoming-group-message-body + [{:keys [selected new-day same-author same-direction last-msg typing]} + content] (let [delivery-status :seen-by-everyone] - [view {:style {:flexDirection "column"}} + [view {:style {:flexDirection :column}} (when selected [text {:style {:marginTop 18 :marginLeft 40 @@ -296,13 +362,13 @@ :fontSize 12 :color text2-color}} "Mar 7th, 15:22"]) - [view {:style (merge {:flexDirection "row" - :alignSelf "flex-start" + [view {:style (merge {:flexDirection :row + :alignSelf :flex-start :marginTop (cond - new-day 0 - same-author 4 + new-day 0 + same-author 4 same-direction 20 - :else 10) + :else 10) :paddingRight 8 :paddingLeft 8} (when (and last-msg (not typing)) @@ -310,71 +376,50 @@ [view {:style {:width 24}} (when (not same-author) [member-photo {}])] - [view {:style {:flexDirection "column" + [view {:style {:flexDirection :column :width 260 :paddingLeft 8 - :alignItems "flex-start"}} - [message-content {:msg-id msg-id - :from from - :content-type content-type - :content content - :outgoing outgoing - :group-chat true - :selected selected}] + :alignItems :flex-start}} + content ;; TODO show for last or selected (when (and selected delivery-status) [message-delivery-status {:delivery-status delivery-status}])]]])) -(defn message-body [{:keys [msg-id content content-type outgoing delivery-status - group-chat new-day same-author same-direction last-msg typing]}] - (let [delivery-status :seen] - [view {:style (merge {:flexDirection "column" +(defn message-body + [{:keys [outgoing new-day same-author same-direction last-msg typing]} + content] + (let [delivery-status :seen + align (if outgoing :flex-end :flex-start)] + [view {:style (merge {:flexDirection :column :width 260 :paddingTop (cond - new-day 0 - same-author 4 + new-day 0 + same-author 4 same-direction 20 - :else 10) + :else 10) :paddingRight 8 - :paddingLeft 8} - (if outgoing - {:alignSelf "flex-end" - :alignItems "flex-end"} - {:alignItems "flex-start" - :alignSelf "flex-start"}) + :paddingLeft 8 + :alignSelf align + :alignItems align} (when (and last-msg (not typing)) {:paddingBottom 20}))} - [message-content {:msg-id msg-id - :content-type content-type - :content content - :outgoing outgoing - :group-chat group-chat}] + content (when (and outgoing delivery-status) [message-delivery-status {:delivery-status delivery-status}])])) -(defn chat-message [{:keys [msg-id from content content-type outgoing delivery-status - date new-day group-chat selected same-author same-direction - last-msg typing] :as msg} - last-msg-id] +(defn chat-message + [{:keys [msg-id outgoing delivery-status date new-day group-chat] + :as message} + last-msg-id] [view {} (when new-day [message-date {:date date}]) - (let [msg-data {:msg-id msg-id - :from from - :content content - :content-type content-type - :outgoing outgoing - :delivery-status (keyword delivery-status) - :group-chat group-chat - :selected selected - :new-day new-day - :same-author same-author - :same-direction same-direction - :last-msg (= last-msg-id msg-id) - :typing typing}] + (let [msg-data + (merge message {:delivery-status (keyword delivery-status) + :last-msg (= last-msg-id msg-id)})] [view {} - (when (= content-type content-type-status) - [message-content-status from content]) - (if (and group-chat (not outgoing)) - [incoming-group-message-body msg-data] - [message-body msg-data])])]) + [message-content + (if (and group-chat (not outgoing)) + incoming-group-message-body + message-body) + msg-data]])]) diff --git a/src/syng_im/components/chat/input/simple_command.cljs b/src/syng_im/components/chat/input/simple_command.cljs index f80c4ce73c..82be064403 100644 --- a/src/syng_im/components/chat/input/simple_command.cljs +++ b/src/syng_im/components/chat/input/simple_command.cljs @@ -12,31 +12,27 @@ text1-color text2-color]] [syng-im.utils.utils :refer [log toast http-post]] - [syng-im.utils.logging :as log] - [syng-im.resources :as res] - [reagent.core :as r])) + [syng-im.resources :as res])) (defn cancel-command-input [] - (dispatch [:set-chat-command nil])) + (dispatch [:cancel-command])) (defn set-input-message [message] (dispatch [:set-chat-command-content message])) -(defn send-command [chat-id command text] - (dispatch [:stage-command chat-id command text]) +(defn send-command [] + (dispatch [:stage-command]) (cancel-command-input)) (defn simple-command-input-view [command input-options] - (let [chat-id-atom (subscribe [:get-current-chat-id]) - message-atom (subscribe [:get-chat-command-content])] + (let [message-atom (subscribe [:get-chat-command-content])] (fn [command input-options] - (let [chat-id @chat-id-atom - message @message-atom] - [view {:style {:flexDirection "row" - :height 56 - :backgroundColor color-white - :elevation 4}} - [view {:style {:flexDirection "column" + (let [message @message-atom] + [view {:style {:flexDirection :row + :height 56 + :backgroundColor color-white + :elevation 4}} + [view {:style {:flexDirection :column :marginTop 16 :marginBottom 16 :marginLeft 16 @@ -50,7 +46,7 @@ :fontFamily font :color color-white}} (:text command)]] - [text-input (merge {:underlineColorAndroid "transparent" + [text-input (merge {:underlineColorAndroid :transparent :style {:flex 1 :marginLeft 8 :marginTop 7 @@ -60,36 +56,33 @@ :autoFocus true :placeholder "Type" :placeholderTextColor text2-color - :onChangeText (fn [new-text] - (set-input-message new-text)) - :onSubmitEditing (fn [e] - (send-command chat-id command message))} + :onChangeText set-input-message + :onSubmitEditing send-command} input-options) message] (if (pos? (count message)) - [touchable-highlight {:on-press (fn [] - (send-command chat-id command message)) - :underlay-color :transparent} + [touchable-highlight + {:on-press send-command + :underlay-color :transparent} [view {:style {:marginTop 10 :marginRight 10 :width 36 :height 36 :borderRadius 50 :backgroundColor color-blue}} - [image {:source {:uri "icon_send"} - :style {:marginTop 10.5 - :marginLeft 12 - :width 15 - :height 15}}]]] - [touchable-highlight {:on-press (fn [] - (cancel-command-input)) + [image {:source {:uri :icon_send} + :style {:marginTop 10.5 + :marginLeft 12 + :width 15 + :height 15}}]]] + [touchable-highlight {:on-press cancel-command-input :underlay-color :transparent} - [view {:style {:marginTop 10 - :marginRight 10 - :width 36 - :height 36}} + [view {:style {:marginTop 10 + :marginRight 10 + :width 36 + :height 36}} [image {:source res/icon-close-gray - :style {:marginTop 10.5 - :marginLeft 12 - :width 12 - :height 12}}]]])])))) + :style {:marginTop 10.5 + :marginLeft 12 + :width 12 + :height 12}}]]])])))) diff --git a/src/syng_im/components/chat/input/simple_command_staged.cljs b/src/syng_im/components/chat/input/simple_command_staged.cljs index ab92aaf958..ba2f0f5a10 100644 --- a/src/syng_im/components/chat/input/simple_command_staged.cljs +++ b/src/syng_im/components/chat/input/simple_command_staged.cljs @@ -15,8 +15,8 @@ [syng-im.resources :as res] [reagent.core :as r])) -(defn cancel-command-input [chat-id staged-command] - (dispatch [:unstage-command chat-id staged-command])) +(defn cancel-command-input [staged-command] + (dispatch [:unstage-command staged-command])) (defn simple-command-staged-view [staged-command] (let [chat-id-atom (subscribe [:get-current-chat-id])] @@ -43,11 +43,11 @@ :fontFamily font :color color-white}} (:text command)]] - [touchable-highlight {:style {:position "absolute" - :top 7 - :right 4} - :onPress (fn [] - (cancel-command-input chat-id staged-command)) + [touchable-highlight {:style {:position "absolute" + :top 7 + :right 4} + :onPress #(cancel-command-input + staged-command) :underlay-color :transparent} [image {:source res/icon-close-gray :style {:width 10 diff --git a/src/syng_im/components/chat/plain_message_input.cljs b/src/syng_im/components/chat/plain_message_input.cljs index c851ed1bed..14d5600cce 100644 --- a/src/syng_im/components/chat/plain_message_input.cljs +++ b/src/syng_im/components/chat/plain_message_input.cljs @@ -4,17 +4,13 @@ view image touchable-highlight - text-input - dismiss-keyboard]] + text-input]] [syng-im.components.styles :refer [font text2-color color-white color-blue]] [syng-im.components.chat.suggestions :refer [suggestions-view]] - [syng-im.utils.utils :refer [log toast http-post]] - [syng-im.utils.logging :as log] - [syng-im.resources :as res] - [reagent.core :as r])) + [syng-im.utils.utils :refer [log toast http-post]])) (defn set-input-message [message] (dispatch [:set-chat-input-text message])) @@ -22,32 +18,30 @@ (defn send [chat input-message] (let [{:keys [group-chat chat-id]} chat] (if group-chat - (dispatch [:send-group-chat-msg chat-id - input-message]) - (dispatch [:send-chat-msg chat-id - input-message]))) - (set-input-message nil) - (dismiss-keyboard)) + ;; todo how much are different both events? is there real reason + ;; for differentiation here? + (dispatch [:send-group-chat-msg chat-id input-message]) + (dispatch [:send-chat-msg])))) (defn plain-message-input-view [] - (let [chat (subscribe [:get-current-chat]) - input-message-atom (subscribe [:get-chat-input-text]) + (let [chat (subscribe [:get-current-chat]) + input-message-atom (subscribe [:get-chat-input-text]) staged-commands-atom (subscribe [:get-chat-staged-commands])] (fn [] (let [input-message @input-message-atom] - [view {:style {:flexDirection "column"}} + [view {:style {:flexDirection :column}} [suggestions-view] - [view {:style {:flexDirection "row" - :height 56 - :backgroundColor color-white}} - [image {:source {:uri "icon_list"} + [view {:style {:flexDirection :row + :height 56 + :backgroundColor color-white}} + [image {:source {:uri :icon_list} :style {:marginTop 22 :marginRight 6 :marginBottom 6 :marginLeft 21 :width 13 :height 12}}] - [text-input {:underlineColorAndroid "transparent" + [text-input {:underlineColorAndroid :transparent :style {:flex 1 :marginLeft 16 :marginTop -2 @@ -55,23 +49,20 @@ :fontSize 14 :fontFamily font :color text2-color} - :autoFocus (< 0 (count @staged-commands-atom)) + :autoFocus (pos? (count @staged-commands-atom)) :placeholder "Type" :placeholderTextColor text2-color - :onChangeText (fn [new-text] - (set-input-message new-text)) - :onSubmitEditing (fn [e] - (send @chat input-message))} + :onChangeText set-input-message + :onSubmitEditing #(send @chat input-message)} input-message] - [image {:source {:uri "icon_smile"} + [image {:source {:uri :icon_smile} :style {:marginTop 18 :marginRight 18 :width 20 :height 20}}] (when (or (pos? (count input-message)) (pos? (count @staged-commands-atom))) - [touchable-highlight {:on-press (fn [] - (send @chat input-message)) + [touchable-highlight {:on-press #(send @chat input-message) :underlay-color :transparent} [view {:style {:marginTop 10 :marginRight 10 @@ -79,8 +70,8 @@ :height 36 :borderRadius 50 :backgroundColor color-blue}} - [image {:source {:uri "icon_send"} - :style {:marginTop 10.5 - :marginLeft 12 - :width 15 - :height 15}}]]])]])))) + [image {:source {:uri :icon_send} + :style {:marginTop 10.5 + :marginLeft 12 + :width 15 + :height 15}}]]])]])))) diff --git a/src/syng_im/components/chats/chats_list.cljs b/src/syng_im/components/chats/chats_list.cljs index b41a54fbaa..45d207a255 100644 --- a/src/syng_im/components/chats/chats_list.cljs +++ b/src/syng_im/components/chats/chats_list.cljs @@ -52,8 +52,8 @@ [action-button {:buttonColor color-blue} [action-button-item {:title "New Chat" :buttonColor "#9b59b6" - :onPress (fn [] - (dispatch [:show-contacts navigator]))} + :onPress #(dispatch [:navigate-to + :contact-list])} [icon {:name "android-create" :style {:fontSize 20 :height 22 diff --git a/src/syng_im/components/react.cljs b/src/syng_im/components/react.cljs index b3b047b0b8..c71953dff0 100644 --- a/src/syng_im/components/react.cljs +++ b/src/syng_im/components/react.cljs @@ -20,7 +20,7 @@ (defn list-item [component] (r/as-element component)) -(def dismiss-keyboard (js/require "dismissKeyboard")) +(def dismiss-keyboard! (js/require "dismissKeyboard")) (comment (.-width (.get (.. js/React -Dimensions) "window")) diff --git a/src/syng_im/db.cljs b/src/syng_im/db.cljs index 7cfaae514f..2e5e3baf63 100644 --- a/src/syng_im/db.cljs +++ b/src/syng_im/db.cljs @@ -4,24 +4,29 @@ ;; schema of app-db (def schema {:greeting s/Str}) +(def default-view :chat-list) + ;; initial state of app-db (def app-db {:greeting "Hello Clojure in iOS and Android!" :identity-password "replace-me-with-user-entered-password" + :identity "me" :contacts [] - :chat {:current-chat-id "0x0479a5ed1f38cadfad1db6cd56c4b659b0ebe052bbe9efa950f6660058519fa4ca6be2dda66afa80de96ab00eb97a2605d5267a1e8f4c2a166ab551f6826608cdd" - :command nil + :current-chat-id "console" + :chat {:command nil :last-message nil} :chats {} :chats-updated-signal 0 :show-actions false :new-group #{} :new-participants #{} - :signed-up false}) + :signed-up false + :view-id default-view + :navigation-stack (list default-view)}) (def protocol-initialized-path [:protocol-initialized]) (def identity-password-path [:identity-password]) -(def current-chat-id-path [:chat :current-chat-id]) +(def current-chat-id-path [:current-chat-id]) (def updated-chats-signal-path [:chats-updated-signal]) (defn updated-chat-signal-path [chat-id] [:chats chat-id :chat-updated-signal]) diff --git a/src/syng_im/handlers.cljs b/src/syng_im/handlers.cljs index fd43a4528c..672d0f001b 100644 --- a/src/syng_im/handlers.cljs +++ b/src/syng_im/handlers.cljs @@ -1,6 +1,6 @@ (ns syng-im.handlers (:require - [re-frame.core :refer [register-handler after dispatch]] + [re-frame.core :refer [register-handler after dispatch debug enrich]] [schema.core :as s :include-macros true] [syng-im.db :as db :refer [app-db schema]] [syng-im.protocol.api :refer [init-protocol]] @@ -11,7 +11,8 @@ [syng-im.models.contacts :as contacts] [syng-im.models.messages :refer [save-message update-message! - message-by-id]] + message-by-id + get-messages]] [syng-im.models.commands :as commands :refer [set-chat-command set-response-chat-command set-chat-command-content @@ -28,13 +29,13 @@ apply-staged-commands check-suggestion]] [syng-im.handlers.sign-up :as sign-up-service] - [syng-im.models.chats :refer [chat-exists? create-chat chat-add-participants chat-remove-participants set-chat-active - re-join-group-chat]] + re-join-group-chat + chat-by-id2] :as chats] [syng-im.models.chat :refer [signal-chat-updated set-current-chat-id current-chat-id @@ -53,7 +54,9 @@ nav-replace nav-pop]] [syng-im.utils.crypt :refer [gen-random-bytes]] - [syng-im.utils.random :as random])) + [syng-im.utils.random :as random] + [clojure.string :as str] + [syng-im.components.react :as r])) ;; -- Middleware ------------------------------------------------------------ ;; @@ -72,8 +75,7 @@ ;; -- Common -------------------------------------------------------------- (register-handler :initialize-db - (fn [_ _] - app-db)) + (fn [_ _] app-db)) (register-handler :set-loading (fn [db [_ value]] @@ -100,14 +102,6 @@ (log/debug "crypt initialized") db)) -(register-handler :navigate-to - (fn [db [action navigator route nav-type]] - (log/debug action route) - (case nav-type - :push (nav-push navigator route) - :replace (nav-replace navigator route)) - db)) - (register-handler :load-commands (fn [db [action]] (log/debug action) @@ -137,13 +131,30 @@ (update-identity identity) (set-initialized true)))) +(defn gen-messages [n] + (mapv (fn [_] + (let [id (random-uuid)] + {:msg-id id + :content (str id + "ooops sdfg dsfg" + "s dfg\ndsfg dfg\ndsfgdsfgdsfg") + :content-type text-content-type + :outgoing false + :from "console" + :to "me"})) (range n))) + +(defn store-message! + [_ [_ {chat-id :from :as msg}]] + (save-message chat-id msg)) + +(defn receive-message + [db [_ {chat-id :from :as msg}]] + (let [messages [:chats chat-id :messages]] + (update-in db messages conj msg))) + (register-handler :received-msg - (fn [db [action {chat-id :from - msg-id :msg-id :as msg}]] - (log/debug action "msg" msg) - (let [db (create-chat db chat-id [chat-id] false)] - (save-message chat-id msg) - (signal-chat-updated db chat-id)))) + (-> receive-message + ((after store-message!)))) (register-handler :group-received-msg (fn [db [action {chat-id :group-id :as msg}]] @@ -151,6 +162,12 @@ (save-message chat-id msg) (signal-chat-updated db chat-id))) +(defn system-message [msg-id content] + {:from "system" + :msg-id msg-id + :content content + :content-type text-content-type}) + (defn joined-chat-msg [chat-id from msg-id] (let [contact-name (:name (contacts/contact-by-identity from))] (save-message chat-id {:from "system" @@ -171,31 +188,27 @@ (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}))) + (->> (str (or remover-name from) " removed " (or removed-name identity)) + (system-message msg-id) + (save-message chat-id)))) (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}))) + (->> (str (or remover-name from) " removed you from group chat") + (system-message msg-id) + (save-message chat-id)))) (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}))) + (->> (str (or left-name from) " left") + (system-message msg-id) + (save-message chat-id)))) (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}))) + (->> (str "You've removed " (or contact-name identity)) + (system-message (random/id)) + (save-message chat-id)))) (defn left-chat-msg [chat-id] (save-message chat-id {:from "system" @@ -252,56 +265,105 @@ (let [{:keys [chat-id]} (message-by-id msg-id)] (signal-chat-updated db chat-id)))) -(defn send-staged-commands [db chat-id] - (let [staged-commands (get-in db (db/chat-staged-commands-path chat-id))] - (dorun - (map - (fn [staged-command] - (let [command-key (get-in staged-command [:command :command]) - content (commands/format-command-msg-content command-key - (:content staged-command)) - msg (if (= chat-id "console") - (sign-up-service/send-console-command db command-key content) - ;; TODO handle command, now sends as plain message - (let [{msg-id :msg-id - {from :from - to :to} :msg} (api/send-user-msg {:to chat-id - :content content})] - {:msg-id msg-id - :from from - :to to - :content content - :content-type content-type-command - :outgoing true}))] - (save-message chat-id msg))) - staged-commands)) +(defn console? [s] + (= "console" s)) + +(def not-console? + (complement console?)) + +(defn prepare-message + [{:keys [identity current-chat-id] :as db} _] + (let [text (get-in db [:chats current-chat-id :input-text]) + {:keys [command]} (check-suggestion db (str text " "))] + (if command + (set-chat-command db command) + (assoc db :new-message (when-not (str/blank? text) + {:msg-id (random/id) + :chat-id current-chat-id + :content text + :to current-chat-id + :from identity + :content-type text-content-type + :outgoing true}))))) + +(defn prepare-command [identity chat-id staged-command] + (let [command-key (get-in staged-command [:command :command]) + content {:command (name command-key) + :content (:content staged-command)}] + {:msg-id (random/id) + :from identity + :to chat-id + :content content + :content-type content-type-command + :outgoing true + :handler (:handler staged-command)})) + +(defn prepare-staged-commans + [{:keys [current-chat-id identity] :as db} _] + (let [staged-commands (get-in db [:chats current-chat-id :staged-commands])] + (->> staged-commands + (map #(prepare-command identity current-chat-id %)) + (assoc db :new-commands)))) + +(defn add-message + [{:keys [new-message current-chat-id] :as db}] + (if new-message + (update-in db [:chats current-chat-id :messages] conj new-message) db)) +(defn add-commands + [{:keys [new-commands current-chat-id] :as db}] + (reduce + #(update-in %1 [:chats current-chat-id :messages] conj %2) + db + new-commands)) + +(defn clear-input + [{:keys [current-chat-id new-message] :as db} _] + (if new-message + (assoc-in db [:chats current-chat-id :input-text] nil) + db)) + +(defn clear-staged-commands + [{:keys [current-chat-id] :as db} _] + (assoc-in db [:chats current-chat-id :staged-commands] [])) + +(defn send-message! + [{:keys [new-message current-chat-id]} _] + (when (and new-message (not-console? current-chat-id)) + (api/send-user-msg {:to current-chat-id + :content (:content new-message)}))) + +(defn save-message-to-realm! + [{:keys [new-message current-chat-id]} _] + (when new-message + (save-message current-chat-id new-message))) + +(defn save-commands-to-realm! + [{:keys [new-commands current-chat-id]} _] + (doseq [new-command new-commands] + (save-message current-chat-id (dissoc new-command :handler)))) + +(defn handle-commands + [{:keys [new-commands]}] + (println new-commands) + (doseq [{{content :content} :content + handler :handler} new-commands] + (when handler + (handler content)))) + (register-handler :send-chat-msg - (fn [db [action chat-id text]] - (log/debug action "chat-id" chat-id "text" text) - (if-let [command (get-command db text)] - (do (dispatch [:set-chat-command (:command command)]) - db) - (let [msg (when (pos? (count text)) - (if (= chat-id "console") - (sign-up-service/send-console-msg text) - (let [{msg-id :msg-id - {from :from - to :to} :msg} (api/send-user-msg {:to chat-id - :content text})] - {:msg-id msg-id - :from from - :to to - :content text - :content-type text-content-type - :outgoing true})))] - (when msg - (save-message chat-id msg)) - (-> db - (send-staged-commands chat-id) - (apply-staged-commands) - (signal-chat-updated chat-id)))))) + (-> prepare-message + ((enrich prepare-staged-commans)) + ((enrich add-message)) + ((enrich add-commands)) + ((enrich clear-input)) + ((enrich clear-staged-commands)) + ((after (fn [_ _] (r/dismiss-keyboard!)))) + ((after send-message!)) + ((after save-message-to-realm!)) + ((after save-commands-to-realm!)) + ((after handle-commands)))) (register-handler :leave-group-chat (fn [db [action navigator]] @@ -312,28 +374,6 @@ (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 [db (set-chat-input-text db nil) - msg (if (= chat-id "console") - (sign-up-service/send-console-command db command content) - ;; TODO handle command, now sends as plain message - (let [{msg-id :msg-id - {from :from - to :to} :msg} (api/send-user-msg {:to chat-id - :content content})] - {:msg-id msg-id - :from from - :to to - :content content - :content-type text-content-type - :outgoing true}))] - (save-message chat-id msg) - (-> db - (handle-command command content) - (signal-chat-updated chat-id))))) - (register-handler :send-group-chat-msg (fn [db [action chat-id text]] (log/debug action "chat-id" chat-id "text" text) @@ -363,12 +403,13 @@ ;; -- Sign up -------------------------------------------------------------- (register-handler :sign-up - (fn [db [_ phone-number handler]] - (server/sign-up db phone-number handler))) + (-> (fn [db [_ phone-number]] + (assoc db :user-phone-number phone-number)) + ((after (fn [& _] (sign-up-service/on-sign-up-response)))))) (register-handler :sign-up-confirm - (fn [db [_ confirmation-code handler]] - (server/sign-up-confirm confirmation-code handler) + (fn [db [_ confirmation-code]] + (sign-up-service/on-send-code-response confirmation-code) db)) (register-handler :sync-contacts @@ -401,29 +442,33 @@ ;; -- Chat -------------------------------------------------------------- +(defn update-text [db [_ text]] + (set-chat-input-text db text)) + +(defn update-command [db [_ text]] + (let [{:keys [command]} (check-suggestion db text)] + (set-chat-command db command))) + (register-handler :set-chat-input-text - (fn [db [_ text]] - (let [{:keys [command]} (check-suggestion db text)] - (-> db - (set-chat-input-text text) - (set-chat-command command))))) + ((enrich update-command) update-text)) (register-handler :set-chat-command (fn [db [_ command-key]] + ;; todo what is going on there?! (set-chat-command db command-key))) (register-handler :stage-command - (fn [db [action chat-id command content]] - (log/debug action "chat-id" chat-id "command" command "content" content) + (fn [{:keys [current-chat-id] :as db} _] (let [db (set-chat-input-text db nil) + {:keys [command content]} + (get-in db [:chats current-chat-id :command-input]) command-info {:command command :content content - :handler (get-command-handler db (:command command) content)}] + :handler (:handler command)}] (stage-command db command-info)))) (register-handler :unstage-command - (fn [db [action chat-id staged-command]] - (log/debug action "chat-id" chat-id "staged-command" staged-command) + (fn [db [_ staged-command]] (let [] (unstage-command db staged-command)))) @@ -441,7 +486,6 @@ (register-handler :show-contacts (fn [db [action navigator]] - (log/debug action) (nav-push navigator {:view-id :contact-list}) db)) @@ -512,6 +556,76 @@ (re-join-group-chat db group-id identities group-name) (create-chat db group-id identities true group-name)))) -(comment - (dispatch [:set-signed-up true]) - ) +(register-handler :navigate-to + (fn [db [_ view-id]] + (-> db + (assoc :view-id view-id) + (update :navigation-stack conj view-id)))) + +(register-handler :navigate-back + (fn [{:keys [navigation-stack] :as db} _] + (log/debug :navigate-back) + (if (>= 1 (count navigation-stack)) + db + (let [[view-id :as navigation-stack'] (pop navigation-stack)] + (-> db + (assoc :view-id view-id) + (assoc :navigation-stack navigation-stack')))))) + +(register-handler :load-more-messages + (fn [db _] + db + ;; TODO implement + #_(let [chat-id (get-in db [:chat :current-chat-id]) + messages [:chats chat-id :messages] + new-messages (gen-messages 10)] + (update-in db messages concat new-messages)))) + +(defn load-messages! + [db _] + db + (->> (current-chat-id db) + get-messages + (assoc db :messages))) + +(defn init-chat + [{:keys [messages] :as db} _] + (let [id (current-chat-id db)] + (assoc-in db [:chats id :messages] messages))) + +(register-handler :init-chat + (-> load-messages! + ((enrich init-chat)) + debug)) + +(defn initialize-chats + [{:keys [loaded-chats] :as db} _] + (let [chats (->> loaded-chats + (map (fn [{:keys [chat-id] :as chat}] + [chat-id chat])) + (into {}))] + (-> db + (assoc :chats chats) + (dissoc :loaded-chats)))) + +(defn load-chats! + [db _] + (assoc db :loaded-chats (chats/chats-list))) + +(register-handler :initialize-chats + ((enrich initialize-chats) load-chats!)) + +(defn safe-trim [s] + (when (string? s) + (str/trim s))) + +(register-handler :cancel-command + (fn [{:keys [current-chat-id] :as db} _] + (-> db + (assoc-in [:chats current-chat-id :command-input] {}) + (update-in [:chats current-chat-id :input-text] safe-trim)))) + +(register-handler :save-password + (fn [db [_ password]] + (sign-up-service/save-password password) + (assoc db :password-saved true))) diff --git a/src/syng_im/handlers/sign_up.cljs b/src/syng_im/handlers/sign_up.cljs index 4ecd275a3d..014d8532b8 100644 --- a/src/syng_im/handlers/sign_up.cljs +++ b/src/syng_im/handlers/sign_up.cljs @@ -2,16 +2,15 @@ (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] [syng-im.persistence.simple-kv-store :as kv] [syng-im.protocol.state.storage :as s] - [syng-im.db :as db] [syng-im.models.chat :refer [set-current-chat-id]] - [syng-im.models.commands :as commands] + [syng-im.models.chats :as c] [syng-im.utils.utils :refer [log on-error http-post toast]] - [syng-im.utils.logging :as log] [syng-im.utils.random :as random] [syng-im.utils.phone-number :refer [format-phone-number]] [syng-im.constants :refer [text-content-type content-type-command - content-type-command-request]])) + content-type-command-request + content-type-status]])) (defn send-console-msg [text] {:msg-id (random/id) @@ -29,174 +28,173 @@ ;; -- Send confirmation code and synchronize contacts--------------------------- (defn on-sync-contacts [] (dispatch [:received-msg - {:msg-id (random/id) - :content (str "Your contacts have been synchronized") + {:msg-id (random/id) + :content (str "Your contacts have been synchronized") :content-type text-content-type - :outgoing false - :from "console" - :to "me"}]) + :outgoing false + :from "console" + :to "me"}]) (dispatch [:set-signed-up true])) (defn sync-contacts [] (dispatch [:sync-contacts on-sync-contacts])) -(defn on-send-code-response [msg-id body] - (if (:confirmed body) - (do (dispatch [:set-chat-command-request msg-id nil]) - (dispatch [:received-msg - {:msg-id (random/id) - :content "Confirmed" - :content-type text-content-type - :outgoing false - :from "console" - :to "me"}]) - (sync-contacts)) - (dispatch [:received-msg - {:msg-id (random/id) - :content "Wrong code" - :content-type text-content-type - :outgoing false - :from "console" - :to "me"}]))) +(defn on-send-code-response [body] + (dispatch [:received-msg + {:msg-id (random/id) + ;; todo replace by real check + :content (if (= "1111" body) + "Confirmed" + "Wrong code") + :content-type text-content-type + :outgoing false + :from "console" + :to "me"}])) -(defn send-code [msg-id code] - (dispatch [:sign-up-confirm code (partial on-send-code-response msg-id)])) - -(defn- handle-confirmation-code [msg-id command-key content] - (when (= command-key :confirmation-code) - (send-code msg-id content))) +; todo fn name is not too smart, but... +(defn command-content + [command content] + {:command (name command) + :content content}) ;; -- Send phone number ---------------------------------------- (defn on-sign-up-response [] (let [msg-id (random/id)] (dispatch [:received-msg - {:msg-id msg-id - :content (commands/format-command-request-msg-content - :confirmation-code - (str "Thanks! We've sent you a text message with a confirmation " - "code. Please provide that code to confirm your phone number")) + {:msg-id msg-id + :content (command-content + :confirmation-code + (str "Thanks! We've sent you a text message with a confirmation " + "code. Please provide that code to confirm your phone number")) :content-type content-type-command-request - :outgoing false - :from "console" - :to "me"}]) - (dispatch [:set-chat-command-request msg-id handle-confirmation-code]))) - -(defn- handle-phone [msg-id command-key content] - (dispatch [:set-chat-command-request msg-id nil]) - (when (= command-key :phone) - (let [phone-number (format-phone-number content)] - (dispatch [:sign-up phone-number on-sign-up-response])))) - + :outgoing false + :from "console" + :to "me"}]))) ;; -- Saving password ---------------------------------------- -(defn- save-password [password] +(defn save-password [password] ;; TODO validate and save password (dispatch [:received-msg - {:msg-id (random/id) - :content (str "OK great! Your password has been saved. Just to let you " - "know, you can always change it in the Console, by the way, " - "it's me, the Console, nice to meet you!") + {:msg-id (random/id) + :content (str "OK great! Your password has been saved. Just to let you " + "know you can always change it in the Console by the way " + "it's me the Console nice to meet you!") :content-type text-content-type - :outgoing false - :from "console" - :to "me"}]) + :outgoing false + :from "console" + :to "me"}]) (dispatch [:received-msg - {:msg-id (random/id) - :content (str "I'll generate a passphrase for you so you can restore your " - "access or log in from another device") + {:msg-id (random/id) + :content (str "I'll generate a passphrase for you so you can restore your " + "access or log in from another device") :content-type text-content-type - :outgoing false - :from "console" - :to "me"}]) + :outgoing false + :from "console" + :to "me"}]) (dispatch [:received-msg - {:msg-id (random/id) - :content "Here's your passphrase:" + {:msg-id (random/id) + :content "Here's your passphrase:" :content-type text-content-type - :outgoing false - :from "console" - :to "me"}]) + :outgoing false + :from "console" + :to "me"}]) ;; TODO generate passphrase (let [passphrase (str "The brash businessman's braggadocio and public squabbing with " "candidates in the US presidential election")] (dispatch [:received-msg - {:msg-id (random/id) - :content passphrase + {:msg-id (random/id) + :content passphrase :content-type text-content-type - :outgoing false - :from "console" - :to "me"}])) + :outgoing false + :from "console" + :to "me"}])) (dispatch [:received-msg - {:msg-id "8" - :content "Make sure you had securely written it down" + {:msg-id "8" + :content "Make sure you had securely written it down" :content-type text-content-type - :outgoing false - :from "console" - :to "me"}]) + :outgoing false + :from "console" + :to "me"}]) ;; TODO highlight '!phone' (let [msg-id (random/id)] (dispatch [:received-msg - {:msg-id msg-id - :content (commands/format-command-request-msg-content - :phone - (str "Your phone number is also required to use the app. Type the " - "exclamation mark or hit the icon to open the command list " - "and choose the !phone command")) - :content-type content-type-command-request - :outgoing false - :from "console" - :to "me"}]) - (dispatch [:set-chat-command-request msg-id handle-phone]))) + {:msg-id msg-id + :content (command-content + :phone + (str "Your phone number is also required to use the app. Type the " + "exclamation mark or hit the icon to open the command list " + "and choose the !phone command")) + :content-type content-type-command-request + :outgoing false + :from "console" + :to "me"}]))) -(defn- handle-password [msg-id command-key content] - (dispatch [:set-chat-command-request msg-id nil]) - (when (= command-key :keypair-password) - (save-password content))) +(def intro-status + {:msg-id "intro-status" + :content (str "The brash businessman’s braggadocio " + "and public exchange with candidates " + "in the US presidential election") + :delivery-status "seen" + :from "console" + :chat-id "console" + :content-type content-type-status + :outgoing false + :to "me"}) (defn intro [db] + (dispatch [:received-msg intro-status]) (dispatch [:received-msg - {:msg-id "intro-message1" - :content "Hello there! It's Syng, a Dapp browser in your phone." + {:msg-id "intro-message1" + :content "Hello there! It's Syng a Dapp browser in your phone." :content-type text-content-type - :outgoing false - :from "console" - :to "me"}]) + :outgoing false + :from "console" + :to "me"}]) (dispatch [:received-msg - {:msg-id "intro-message2" - :content (str "Syng uses a highly secure key-pair authentication type " - "to provide you a reliable way to access your account") + {:msg-id "intro-message2" + :content (str "Syng uses a highly secure key-pair authentication type " + "to provide you a reliable way to access your account") :content-type text-content-type - :outgoing false - :from "console" - :to "me"}]) + :outgoing false + :from "console" + :to "me"}]) (let [msg-id "into-message3"] (dispatch [:received-msg - {:msg-id msg-id - :content (commands/format-command-request-msg-content - :keypair-password - (str "A key pair has been generated and saved to your device. " - "Create a password to secure your key")) + {:msg-id msg-id + :content (command-content + :keypair-password + (str "A key pair has been generated and saved to your device. " + "Create a password to secure your key")) :content-type content-type-command-request - :outgoing false - :from "console" - :to "me"}]) - (dispatch [:set-chat-command-request msg-id handle-password])) - ;; (dispatch [:set-chat-command :keypair-password]) + :outgoing false + :from "console" + :to "me"}])) db) -;; TODO store command key in a separate field -(defn send-console-command [db command-key content] - {:msg-id (random/id) - :from "me" - :to "console" - :content content ;; (commands/format-command-msg-content command-key content) - :content-type content-type-command - :outgoing true}) +(def console-chat + {:chat-id "console" + :name "console" + :group-chat false + :is-active true + :timestamp (.getTime (js/Date.)) + :contacts [{:identity "console" + :text-color "#FFFFFF" + :background-color "#AB7967"}]}) -(defn init [db] - (let [signed-up (s/get kv/kv-store :signed-up) - db (if signed-up - db - (-> db - (set-current-chat-id "console") - intro))] - (assoc db :signed-up signed-up))) +(defn create-chat [handler] + (fn [db] + (let [{:keys [new-chat] :as db'} (handler db)] + (when new-chat + (c/create-chat new-chat)) + (dissoc db' :new-chat)))) + +(def init + (create-chat + (fn [{:keys [chats] :as db}] + (if (chats "console") + db + (-> db + (assoc-in [:chats "console"] console-chat) + (assoc :new-chat console-chat) + (set-current-chat-id "console") + (intro)))))) diff --git a/src/syng_im/models/chats.cljs b/src/syng_im/models/chats.cljs index 9b0acd5b0d..ade0679521 100644 --- a/src/syng_im/models/chats.cljs +++ b/src/syng_im/models/chats.cljs @@ -50,6 +50,9 @@ :outgoing false})) (defn create-chat + ([{:keys [last-msg-id] :as chat}] + (let [chat (assoc chat :last-msg-id (or last-msg-id ""))] + (r/write #(r/create :chats chat)))) ([db chat-id identities group-chat?] (create-chat db chat-id identities group-chat? nil)) ([db chat-id identities group-chat? chat-name] @@ -96,14 +99,26 @@ (-> (signal-chats-updated db) (signal-chat-updated group-id))) +(defn normalize-contacts + [chats] + (map #(update % :contacts vals) chats)) + (defn chats-list [] - (r/sorted (r/get-all :chats) :timestamp :desc)) + (-> (r/get-all :chats) + (r/sorted :timestamp :desc) + r/collection->map + normalize-contacts)) (defn chat-by-id [chat-id] (-> (r/get-by-field :chats :chat-id chat-id) (r/single-cljs) (r/list-to-array :contacts))) +(defn chat-by-id2 [chat-id] + (-> (r/get-by-field :chats :chat-id chat-id) + r/collection->map + first)) + (defn chat-add-participants [chat-id identities] (r/write (fn [] @@ -136,42 +151,3 @@ (-> (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") - (r/single) - (aget "contacts") - (.map (fn [object index collection] - object))) - - (-> (chat-by-id "0x04ed4c3797026cddeb7d64a54ca58142e57ea03cda21072358d67455b506db90c56d95033e3d221992f70d01922c3d90bf0697c49e4be118443d03ae4a1cd3c15c") - :contacts - vals - vec) - - - (-> (aget (aget (chats-list) 0) "contacts") - (r/cljs-list)) - - (r/write (fn [] (r/delete (chats-list)))) - - (swap! re-frame.db/app-db signal-chats-updated) - - (create-chat "0x0479a5ed1f38cadfad1db6cd56c4b659b0ebe052bbe9efa950f6660058519fa4ca6be2dda66afa80de96ab00eb97a2605d5267a1e8f4c2a166ab551f6826608cdd" - ["0x0479a5ed1f38cadfad1db6cd56c4b659b0ebe052bbe9efa950f6660058519fa4ca6be2dda66afa80de96ab00eb97a2605d5267a1e8f4c2a166ab551f6826608cdd"]) - - (+ 1 1) - - - - (swap! re-frame.db/app-db (fn [db] - (create-chat db "A group chat"))) - - - (-> (chats-list) - (.find (fn [object index collection] - (= "console1" (aget object "chat-id"))))) - ) diff --git a/src/syng_im/models/commands.cljs b/src/syng_im/models/commands.cljs index 8967833487..f5a6a69c4e 100644 --- a/src/syng_im/models/commands.cljs +++ b/src/syng_im/models/commands.cljs @@ -4,63 +4,63 @@ [cljs.core.async :as async :refer [chan put! !]] [re-frame.core :refer [subscribe dispatch dispatch-sync]] [syng-im.db :as db] - [syng-im.resources :as res] [syng-im.models.chat :refer [current-chat-id]] [syng-im.components.styles :refer [color-blue color-dark-mint]] - [syng-im.utils.utils :refer [log toast]] - [syng-im.utils.logging :as log] - [syng-im.persistence.realm :as realm])) + [syng-im.utils.utils :refer [log toast]])) ;; todo delete -(def commands [{:command :money - :text "!money" - :description "Send money" - :color color-dark-mint +(def commands [{:command :money + :text "!money" + :description "Send money" + :color color-dark-mint :request-icon {:uri "icon_lock_white"} - :icon {:uri "icon_lock_gray"} - :suggestion true} - {:command :location - :text "!location" + :icon {:uri "icon_lock_gray"} + :suggestion true} + {:command :location + :text "!location" :description "Send location" - :color "#9a5dcf" - :suggestion true} - {:command :phone - :text "!phone" - :description "Send phone number" + :color "#9a5dcf" + :suggestion true} + {:command :phone + :text "!phone" + :description "Send phone number" + :color color-dark-mint :request-text "Phone number request" - :color color-dark-mint - :suggestion true} - {:command :confirmation-code - :text "!confirmationCode" - :description "Send confirmation code" + :suggestion true + :handler #(dispatch [:sign-up %])} + {:command :confirmation-code + :text "!confirmationCode" + :description "Send confirmation code" :request-text "Confirmation code request" - :color color-blue + :color color-blue :request-icon {:uri "icon_lock_white"} - :icon {:uri "icon_lock_gray"} - :suggestion true} - {:command :send - :text "!send" + :icon {:uri "icon_lock_gray"} + :suggestion true + :handler #(dispatch [:sign-up-confirm %])} + {:command :send + :text "!send" :description "Send location" - :color "#9a5dcf" - :suggestion true} - {:command :request - :text "!request" + :color "#9a5dcf" + :suggestion true} + {:command :request + :text "!request" :description "Send request" - :color "#48ba30" - :suggestion true} - {:command :keypair-password - :text "!keypairPassword" - :description "" - :color color-blue + :color "#48ba30" + :suggestion true} + {:command :keypair-password + :text "!keypairPassword" + :description "" + :color color-blue :request-icon {:uri "icon_lock_white"} - :icon {:uri "icon_lock_gray"} - :suggestion false} - {:command :help - :text "!help" + :icon {:uri "icon_lock_gray"} + :suggestion false + :handler #(dispatch [:save-password %])} + {:command :help + :text "!help" :description "Help" - :color "#9a5dcf" - :suggestion true}]) + :color "#9a5dcf" + :suggestion true}]) (defn get-commands [db] ;; todo: temp. must be '(get db :commands)' @@ -83,19 +83,20 @@ (get-in db (db/chat-command-content-path (current-chat-id db)))) (defn set-chat-command-content [db content] - (assoc-in db (db/chat-command-content-path (get-in db db/current-chat-id-path)) + (assoc-in db + [:chats (get-in db db/current-chat-id-path) :command-input :content] content)) (defn get-chat-command [db] (get-in db (db/chat-command-path (current-chat-id db)))) (defn set-response-chat-command [db msg-id command-key] - (-> db - (set-chat-command-content nil) - (assoc-in (db/chat-command-path (current-chat-id db)) - (get-command db command-key)) - (assoc-in (db/chat-command-to-msg-id-path (current-chat-id db)) - msg-id))) + (let [chat-id (current-chat-id db)] + (-> db + (assoc-in [:chats chat-id :command-input :content] nil) + (assoc-in [:chats chat-id :command-input :command] + (get-command db command-key)) + (assoc-in [:chats chat-id :command-input :to-msg-id] msg-id)))) (defn set-chat-command [db command-key] (set-response-chat-command db nil command-key)) @@ -124,31 +125,10 @@ (defn set-chat-command-request [db msg-id handler] (update-in db (db/chat-command-requests-path (current-chat-id db)) - (fn [requests] - (if requests - (assoc requests msg-id handler) - {msg-id handler})))) - - -(defn- map-to-str - [m] - (join ";" (map #(join "=" %) (stringify-keys m)))) - -(defn- str-to-map - [s] - (keywordize-keys (apply hash-map (split s #"[;=]")))) - -;; TODO store command key in separate field -(defn format-command-msg-content [command content] - (map-to-str {:command (name command) :content content})) + #(assoc % msg-id handler))) (defn parse-command-msg-content [commands content] - (log/info content) - (log/info (update (str-to-map content) :command #(find-command commands (keyword %)))) - (update (str-to-map content) :command #(find-command commands (keyword %)))) - -(defn format-command-request-msg-content [command content] - (map-to-str {:command (name command) :content content})) + (update content :command #(find-command commands (keyword %)))) (defn parse-command-request-msg-content [commands content] - (update (str-to-map content) :command #(find-command commands (keyword %)))) + (update content :command #(find-command commands (keyword %)))) diff --git a/src/syng_im/models/messages.cljs b/src/syng_im/models/messages.cljs index e0ebfea898..117c6436a5 100644 --- a/src/syng_im/models/messages.cljs +++ b/src/syng_im/models/messages.cljs @@ -3,7 +3,18 @@ [cljs.reader :refer [read-string]] [syng-im.utils.random :refer [timestamp]] [syng-im.db :as db] - [syng-im.utils.logging :as log])) + [syng-im.utils.logging :as log] + [clojure.string :refer [join split]] + [clojure.walk :refer [stringify-keys keywordize-keys]] + [syng-im.constants :as c])) + +(defn- map-to-str + [m] + (join ";" (map #(join "=" %) (stringify-keys m)))) + +(defn- str-to-map + [s] + (keywordize-keys (apply hash-map (split s #"[;=]")))) (defn select-chat-last-message [chat] (when-let [last-msg-id (:last-msg-id chat)] @@ -16,7 +27,10 @@ (r/write (fn [] (let [chat (r/single-cljs (r/get-by-field :chats :chat-id chat-id)) - last-message (select-chat-last-message chat)] + last-message (select-chat-last-message chat) + content (if (string? content) + content + (map-to-str content))] (r/create :msgs {:chat-id chat-id :msg-id msg-id :from from @@ -37,7 +51,15 @@ true)))))) (defn get-messages [chat-id] - (r/sorted (r/get-by-field :msgs :chat-id chat-id) :timestamp :desc)) + (->> (-> (r/get-by-field :msgs :chat-id chat-id) + (r/sorted :timestamp :asc) + (r/collection->map)) + (into '()) + (map (fn [{:keys [content-type] :as message}] + (if (#{c/content-type-command c/content-type-command-request} + content-type) + (update message :content str-to-map) + message))))) (defn message-by-id [msg-id] (r/single-cljs (r/get-by-field :msgs :msg-id msg-id))) @@ -48,27 +70,3 @@ (fn [] (when (r/exists? :msgs :msg-id msg-id) (r/create :msgs msg true))))) - -(comment - - (update-message! {:msg-id "1459175391577-a2185a35-5c49-5a6b-9c08-6eb5b87ceb7f" - :delivery-status "seen2"}) - - (r/get-by-field :msgs :msg-id "1459175391577-a2185a35-5c49-5a6b-9c08-6eb5b87ceb7f") - - - (save-message "0x040028c500ff086ecf1cfbb3c1a7240179cde5b86f9802e6799b9bbe9cdd7ad1b05ae8807fa1f9ed19cc8ce930fc2e878738c59f030a6a2f94b3522dc1378ff154" - {:msg-id "153" - :content "hello!" - :content-type "text/plain"}) - - (get-messages* "0x040028c500ff086ecf1cfbb3c1a7240179cde5b86f9802e6799b9bbe9cdd7ad1b05ae8807fa1f9ed19cc8ce930fc2e878738c59f030a6a2f94b3522dc1378ff154") - - (get-messages "0x0479a5ed1f38cadfad1db6cd56c4b659b0ebe052bbe9efa950f6660058519fa4ca6be2dda66afa80de96ab00eb97a2605d5267a1e8f4c2a166ab551f6826608cdd") - - (doseq [msg (get-messages* "0x043df89d36f6e3d8ade18e55ac3e2e39406ebde152f76f2f82d674681d59319ffd9880eebfb4f5f8d5c222ec485b44d6e30ba3a03c96b1c946144fdeba1caccd43")] - (r/delete msg)) - - @re-frame.db/app-db - - ) diff --git a/src/syng_im/persistence/realm.cljs b/src/syng_im/persistence/realm.cljs index dcb5601e10..7fb0dd45d3 100644 --- a/src/syng_im/persistence/realm.cljs +++ b/src/syng_im/persistence/realm.cljs @@ -131,7 +131,6 @@ (defn get-list [schema-name] (vals (js->clj (.objects realm (to-string schema-name)) :keywordize-keys true))) - -(comment - - ) +(defn collection->map [collection] + (-> (.map collection (fn [object _ _] object)) + (js->clj :keywordize-keys true))) diff --git a/src/syng_im/subs.cljs b/src/syng_im/subs.cljs index 19b0ab404f..59671cf35a 100644 --- a/src/syng_im/subs.cljs +++ b/src/syng_im/subs.cljs @@ -15,7 +15,6 @@ get-chat-command get-chat-command-content get-chat-command-request - parse-command-msg-content parse-command-request-msg-content]] [syng-im.handlers.suggestions :refer [get-suggestions]])) @@ -23,11 +22,8 @@ (register-sub :get-chat-messages (fn [db _] - (let [chat-id (reaction (current-chat-id @db)) - chat-updated (reaction (chat-updated? @db @chat-id))] - (reaction - (let [_ @chat-updated] - (get-messages @chat-id)))))) + (let [chat-id (current-chat-id @db)] + (reaction (get-in @db [:chats chat-id :messages]))))) (register-sub :get-current-chat-id (fn [db _] @@ -76,12 +72,8 @@ (register-sub :get-current-chat (fn [db _] - (let [current-chat-id (reaction (current-chat-id @db)) - chat-updated (reaction (chat-updated? @db @current-chat-id))] - (reaction - (let [_ @chat-updated] - (when-let [chat-id @current-chat-id] - (chat-by-id chat-id))))))) + (let [current-chat-id (current-chat-id @db)] + (reaction (get-in @db [:chats current-chat-id]))))) ;; -- User data -------------------------------------------------------------- @@ -148,3 +140,29 @@ :contacts (map :identity))] (contacts-list-include current-participants))))))) + +(register-sub :view-id + (fn [db _] + (reaction (@db :view-id)))) + +(register-sub :chat + (fn [db [_ k]] + (-> @db + (get-in [:chats (current-chat-id @db) k]) + (reaction)))) + +(register-sub :navigation-stack + (fn [db _] + (:navigation-stack @db))) + +(register-sub :db + (fn [db _] (reaction @db))) + +(register-sub :chat-properties + (fn [{:keys [current-chat-id] :as db} [_ properties]] + (->> properties + (map (fn [k] + [k (-> @db + (get-in [:cgats current-chat-id k]) + (reaction))])) + (into {})))) diff --git a/src/syng_im/utils/listview.cljs b/src/syng_im/utils/listview.cljs index 0fe93724f7..d52b674394 100644 --- a/src/syng_im/utils/listview.cljs +++ b/src/syng_im/utils/listview.cljs @@ -9,3 +9,11 @@ (-> (cljs.core/clj->js {:rowHasChanged not=}) (js/RealmReactNative.ListView.DataSource.) (clone-with-rows items))) + + +(defn clone-with-rows2 [ds rows] + (.cloneWithRows ds (reduce (fn [ac el] (.push ac el) ac) + (clj->js []) rows))) + +(defn to-datasource2 [items] + (clone-with-rows2 (data-source {:rowHasChanged not=}) items)) From 9548dd23eee058745b8685cd20248a124ef146f2 Mon Sep 17 00:00:00 2001 From: Roman Volosovskyi Date: Fri, 6 May 2016 14:30:34 +0300 Subject: [PATCH 2/4] background color --- src/syng_im/android/core.cljs | 6 +-- src/syng_im/components/chat/chat_message.cljs | 40 +++++++++---------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/src/syng_im/android/core.cljs b/src/syng_im/android/core.cljs index 28085cad7c..a8cf5785f9 100644 --- a/src/syng_im/android/core.cljs +++ b/src/syng_im/android/core.cljs @@ -54,6 +54,6 @@ ;; load commands from remote server (todo: uncomment) ;; (dispatch [:load-commands]) (dispatch [:init-console-chat]) - #_(dispatch [:init-chat]) - #_(init-back-button-handler!) - #_(.registerComponent app-registry "SyngIm" #(r/reactify-component app-root))) + (dispatch [:init-chat]) + (init-back-button-handler!) + (.registerComponent app-registry "SyngIm" #(r/reactify-component app-root))) diff --git a/src/syng_im/components/chat/chat_message.cljs b/src/syng_im/components/chat/chat_message.cljs index 7b1b8a3726..726e55e249 100644 --- a/src/syng_im/components/chat/chat_message.cljs +++ b/src/syng_im/components/chat/chat_message.cljs @@ -192,7 +192,6 @@ [view {:style {:paddingRight 16}} [view {:style (merge {:borderRadius 14 :padding 12 - :paddingRight 28 :backgroundColor color-white})} (when (and group-chat (not outgoing)) [text {:style (merge style-sub-text @@ -259,15 +258,17 @@ (defn message-view [{:keys [content-type outgoing background-color group-chat selected]} content] - [view {:style (merge {:borderRadius 14 - :padding 12} + [view {:style (merge {:borderRadius 14 + :padding 12 + :backgroundColor color-white} + (when (= content-type content-type-command) + {:paddingTop 10 + :paddingBottom 14}) (if outgoing - (if (and group-chat (= content-type text-content-type)) - {:backgroundColor color-blue} - {:backgroundColor color-white}) - (if selected - {:backgroundColor selected-message-color} - {:backgroundColor background-color})))} + (when (and group-chat (= content-type text-content-type)) + {:backgroundColor color-blue}) + (when selected + {:backgroundColor selected-message-color})))} #_(when (and group-chat (not outgoing)) [text {:style {:marginTop 0 :fontSize 12 @@ -283,17 +284,14 @@ [wrapper message [message-content-command-request message]]) (defn text-message - [{:keys [content outgoing text-color group-chat] :as message}] + [{:keys [content outgoing group-chat] :as message}] [message-view message - [text {:style {:marginTop (if (and group-chat (not outgoing)) - 4 - 0) - :fontSize 14 - :fontFamily font - :color (cond - (and outgoing group-chat) color-white - outgoing text1-color - :else text-color)}} + [text {:style (merge style-message-text + {:marginTop (if (and group-chat (not outgoing)) + 4 + 0)} + (when (and outgoing group-chat) + {:color color-white}))} content]]) (defmethod message-content text-content-type @@ -302,9 +300,7 @@ (defmethod message-content content-type-status [_ message] - ;; todo should it be rendered as text message? - [message-content-status message] - #_[text-message message]) + [message-content-status message]) (defmethod message-content content-type-command [wrapper {:keys [content] :as message}] From 827a354c1e8f603b8c541353ed3452814997da22 Mon Sep 17 00:00:00 2001 From: Roman Volosovskyi Date: Fri, 6 May 2016 15:17:17 +0300 Subject: [PATCH 3/4] same-author/direction --- src/syng_im/handlers.cljs | 33 ++++++++++++++++------- src/syng_im/models/messages.cljs | 46 ++++++++++++++------------------ 2 files changed, 43 insertions(+), 36 deletions(-) diff --git a/src/syng_im/handlers.cljs b/src/syng_im/handlers.cljs index 672d0f001b..75bb1983ab 100644 --- a/src/syng_im/handlers.cljs +++ b/src/syng_im/handlers.cljs @@ -144,8 +144,18 @@ :to "me"})) (range n))) (defn store-message! - [_ [_ {chat-id :from :as msg}]] - (save-message chat-id msg)) + [db [_ {chat-id :from + outgoing :outgoing + :as msg}]] + (let [previous-message (peek (get-in db [:chats chat-id :messages])) + msg (merge msg + {:same-author (if previous-message + (= (:from previous-message) outgoing) + true) + :same-direction (if previous-message + (= (:outgoing previous-message) outgoing) + true)})] + (save-message chat-id msg))) (defn receive-message [db [_ {chat-id :from :as msg}]] @@ -273,18 +283,21 @@ (defn prepare-message [{:keys [identity current-chat-id] :as db} _] - (let [text (get-in db [:chats current-chat-id :input-text]) + (let [text (get-in db [:chats current-chat-id :input-text]) {:keys [command]} (check-suggestion db (str text " "))] (if command (set-chat-command db command) (assoc db :new-message (when-not (str/blank? text) - {:msg-id (random/id) - :chat-id current-chat-id - :content text - :to current-chat-id - :from identity - :content-type text-content-type - :outgoing true}))))) + {:msg-id (random/id) + :chat-id current-chat-id + :content text + :to current-chat-id + :from identity + :content-type text-content-type + :outgoing true + ;; todo should be refactored + :same-author false + :same-direction false}))))) (defn prepare-command [identity chat-id staged-command] (let [command-key (get-in staged-command [:command :command]) diff --git a/src/syng_im/models/messages.cljs b/src/syng_im/models/messages.cljs index 117c6436a5..b8fc9cff57 100644 --- a/src/syng_im/models/messages.cljs +++ b/src/syng_im/models/messages.cljs @@ -20,35 +20,29 @@ (when-let [last-msg-id (:last-msg-id chat)] (r/single-cljs (r/get-by-field :msgs :msg-id last-msg-id)))) -(defn save-message [chat-id {:keys [from to msg-id content content-type outgoing] :or {outgoing false - to nil} :as msg}] +(defn save-message + [chat-id {:keys [from to msg-id content content-type outgoing + same-author same-direction] + :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 [] - (let [chat (r/single-cljs (r/get-by-field :chats :chat-id chat-id)) - last-message (select-chat-last-message chat) - content (if (string? content) - content - (map-to-str content))] - (r/create :msgs {:chat-id chat-id - :msg-id msg-id - :from from - :to to - :content content - :content-type content-type - :outgoing outgoing - :timestamp (timestamp) - :delivery-status nil - :same-author (if last-message - (= (:from last-message) from) - true) - :same-direction (if last-message - (= (:outgoing last-message) outgoing) - true)} true) - (r/create :chats {:chat-id (:chat-id chat) - :last-msg-id msg-id} - true)))))) + (fn [] + (let [content (if (string? content) + content + (map-to-str content))] + (r/create :msgs {:chat-id chat-id + :msg-id msg-id + :from from + :to to + :content content + :content-type content-type + :outgoing outgoing + :timestamp (timestamp) + :delivery-status nil + :same-author same-author + :same-direction same-direction} true)))))) (defn get-messages [chat-id] (->> (-> (r/get-by-field :msgs :chat-id chat-id) From 357249a49b9e36d6eafb73e219410e6468155306 Mon Sep 17 00:00:00 2001 From: Roman Volosovskyi Date: Fri, 6 May 2016 16:11:45 +0300 Subject: [PATCH 4/4] same-author/direction --- src/syng_im/handlers.cljs | 58 ++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/src/syng_im/handlers.cljs b/src/syng_im/handlers.cljs index 75bb1983ab..560337de56 100644 --- a/src/syng_im/handlers.cljs +++ b/src/syng_im/handlers.cljs @@ -144,23 +144,35 @@ :to "me"})) (range n))) (defn store-message! - [db [_ {chat-id :from + [db [_ {chat-id :from outgoing :outgoing - :as msg}]] - (let [previous-message (peek (get-in db [:chats chat-id :messages])) - msg (merge msg - {:same-author (if previous-message - (= (:from previous-message) outgoing) - true) - :same-direction (if previous-message - (= (:outgoing previous-message) outgoing) - true)})] + :as msg}]] + (let [previous-message (first (get-in db [:chats chat-id :messages])) + msg (merge msg + {:same-author (if previous-message + (= (:from previous-message) outgoing) + true) + :same-direction (if previous-message + (= (:outgoing previous-message) outgoing) + true)})] (save-message chat-id msg))) +(defn add-message-to-db + [db chat-id {:keys [from outgoing] :as message}] + (let [messages [:chats chat-id :messages] + previous-message (first (get-in db [:chats chat-id :messages])) + message (merge message + {:same-author (if previous-message + (= (:from previous-message) from) + true) + :same-direction (if previous-message + (= (:outgoing previous-message) outgoing) + true)})] + (update-in db messages conj message))) + (defn receive-message [db [_ {chat-id :from :as msg}]] - (let [messages [:chats chat-id :messages]] - (update-in db messages conj msg))) + (add-message-to-db db chat-id msg)) (register-handler :received-msg (-> receive-message @@ -283,7 +295,7 @@ (defn prepare-message [{:keys [identity current-chat-id] :as db} _] - (let [text (get-in db [:chats current-chat-id :input-text]) + (let [text (get-in db [:chats current-chat-id :input-text]) {:keys [command]} (check-suggestion db (str text " "))] (if command (set-chat-command db command) @@ -303,13 +315,15 @@ (let [command-key (get-in staged-command [:command :command]) content {:command (name command-key) :content (:content staged-command)}] - {:msg-id (random/id) - :from identity - :to chat-id - :content content - :content-type content-type-command - :outgoing true - :handler (:handler staged-command)})) + {:msg-id (random/id) + :from identity + :to chat-id + :content content + :content-type content-type-command + :outgoing true + :handler (:handler staged-command) + :same-author false + :same-direction false})) (defn prepare-staged-commans [{:keys [current-chat-id identity] :as db} _] @@ -321,13 +335,13 @@ (defn add-message [{:keys [new-message current-chat-id] :as db}] (if new-message - (update-in db [:chats current-chat-id :messages] conj new-message) + (add-message-to-db db current-chat-id new-message) db)) (defn add-commands [{:keys [new-commands current-chat-id] :as db}] (reduce - #(update-in %1 [:chats current-chat-id :messages] conj %2) + #(add-message-to-db %1 current-chat-id %2) db new-commands))