refactoring

Former-commit-id: 319bb656ec
This commit is contained in:
Roman Volosovskyi 2016-05-06 14:06:58 +03:00
parent 79f912de24
commit c8890a545c
18 changed files with 1003 additions and 817 deletions

View File

@ -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}}}}
}})
}})

View File

@ -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)))

View File

@ -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])])))

View File

@ -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]])])

View File

@ -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}}]]])]))))

View File

@ -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

View File

@ -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}}]]])]]))))

View File

@ -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

View File

@ -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"))

View File

@ -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])

View File

@ -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)))

View File

@ -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 businessmans 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))))))

View File

@ -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")))))
)

View File

@ -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 %))))

View File

@ -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
)

View File

@ -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)))

View File

@ -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 {}))))

View File

@ -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))