diff --git a/syng-im/images/att.png b/syng-im/images/att.png new file mode 100644 index 0000000000..bafc26837d Binary files /dev/null and b/syng-im/images/att.png differ diff --git a/syng-im/images/delivered.png b/syng-im/images/delivered.png new file mode 100644 index 0000000000..0d7a9fbc8a Binary files /dev/null and b/syng-im/images/delivered.png differ diff --git a/syng-im/images/logo.png b/syng-im/images/logo.png new file mode 100644 index 0000000000..02e05bf0a9 Binary files /dev/null and b/syng-im/images/logo.png differ diff --git a/syng-im/images/mic.png b/syng-im/images/mic.png new file mode 100644 index 0000000000..9f5e8fc0a7 Binary files /dev/null and b/syng-im/images/mic.png differ diff --git a/syng-im/images/nav-back.png b/syng-im/images/nav-back.png new file mode 100644 index 0000000000..6e7cdb7339 Binary files /dev/null and b/syng-im/images/nav-back.png differ diff --git a/syng-im/images/no-photo.png b/syng-im/images/no-photo.png new file mode 100755 index 0000000000..2889cf4420 Binary files /dev/null and b/syng-im/images/no-photo.png differ diff --git a/syng-im/images/online.png b/syng-im/images/online.png new file mode 100644 index 0000000000..1d5193e578 Binary files /dev/null and b/syng-im/images/online.png differ diff --git a/syng-im/images/play.png b/syng-im/images/play.png new file mode 100644 index 0000000000..078b1e29c7 Binary files /dev/null and b/syng-im/images/play.png differ diff --git a/syng-im/images/seen.png b/syng-im/images/seen.png new file mode 100644 index 0000000000..17ec227355 Binary files /dev/null and b/syng-im/images/seen.png differ diff --git a/syng-im/images/smile.png b/syng-im/images/smile.png new file mode 100644 index 0000000000..e64022a14f Binary files /dev/null and b/syng-im/images/smile.png differ diff --git a/syng-im/src/syng_im/android/core.cljs b/syng-im/src/syng_im/android/core.cljs index e474a9f24b..3ed4575960 100644 --- a/syng-im/src/syng_im/android/core.cljs +++ b/syng-im/src/syng_im/android/core.cljs @@ -7,10 +7,10 @@ [syng-im.handlers] [syng-im.subs] [syng-im.components.react :refer [navigator app-registry]] + [syng-im.components.contact-list.contact-list :refer [contact-list]] [syng-im.components.chat :refer [chat]] [syng-im.utils.logging :as log])) - (def ^{:dynamic true :private true} *nav-render* "Flag to suppress navigator re-renders from outside om when pushing/popping." true) @@ -18,6 +18,7 @@ (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)) @@ -32,7 +33,7 @@ (add-event-listener "hardwareBackPress" new-listener))))) (defn app-root [] - [navigator {:initial-route (clj->js {:view-id :chat}) + [navigator {:initial-route (clj->js {:view-id :contact-list}) :render-scene (fn [route nav] (log/debug "route" route) (when *nav-render* @@ -40,9 +41,12 @@ view-id (keyword view-id)] (init-back-button-handler! nav) (case view-id + :contact-list (r/as-element [contact-list + {:navigator nav}]) :chat (r/as-element [chat {:navigator nav}])))))}]) (defn init [] (dispatch-sync [:initialize-db]) (dispatch [:initialize-protocol]) + (dispatch [:load-syng-contacts]) (.registerComponent app-registry "SyngIm" #(r/reactify-component app-root))) diff --git a/syng-im/src/syng_im/components/contact_list/contact.cljs b/syng-im/src/syng_im/components/contact_list/contact.cljs new file mode 100644 index 0000000000..7bd7879948 --- /dev/null +++ b/syng-im/src/syng_im/components/contact_list/contact.cljs @@ -0,0 +1,104 @@ +(ns syng-im.components.contact-list.contact + (:require [syng-im.components.react :refer [view text image touchable-highlight]] + [syng-im.components.resources :as res] + ;; [messenger.comm.intercom :as intercom :refer [show-chat]] + ;; [messenger.components.chat.chat :refer [chat]] + )) + +(defn contact-view [contact] + (let [{:keys [name photo-path delivery-status datetime new-messages-count + online whisper-identity]} contact] + [touchable-highlight {:onPress (fn [] + ;; TODO + ;; (show-chat nav whisper-identity) + )} + [view {:style {:flexDirection "row" + :marginTop 5 + :marginBottom 5 + :paddingLeft 15 + :paddingRight 15 + :height 75}} + [view {:width 54 + :height 54} + ;;; photo + [view {:width 54 + :height 54 + :borderRadius 50 + :backgroundColor "#FFFFFF" + :elevation 6} + [image {:source (if (< 0 (count photo-path)) + {:uri photo-path} + res/user-no-photo) + :style {:borderWidth 2 + :borderColor "#FFFFFF" + :borderRadius 50 + :width 54 + :height 54 + :position "absolute"}}]] + ;;; online + (when online + [view {:position "absolute" + :top 41 + :left 36 + :width 12 + :height 12 + :borderRadius 50 + :backgroundColor "#FFFFFF" + :elevation 6} + [image {:source res/online-icon + :style {:width 12 + :height 12}}]])] + [view {:style {:flexDirection "column" + :marginLeft 7 + :marginRight 10 + :flex 1 + :position "relative"}} + ;;; name + [text {:style {:fontSize 15 + :fontFamily "Avenir-Roman"}} name] + ;;; last message + [text {:style {:color "#AAB2B2" + :fontFamily "Avenir-Roman" + :fontSize 14 + :marginTop 2 + :paddingRight 10}} + (str "Hi, I'm " name)]] + [view {:style {:flexDirection "column"}} + ;;; delivery status + [view {:style {:flexDirection "row" + :position "absolute" + :top 0 + :right 0}} + (when delivery-status + [image {:source (if (= (keyword delivery-status) :seen) + res/seen-icon + res/delivered-icon) + :style {:marginTop 5}}]) + ;;; datetime + [text {:style {:fontFamily "Avenir-Roman" + :fontSize 11 + :color "#AAB2B2" + :letterSpacing 1 + :lineHeight 15 + :marginLeft 5}} + datetime]] + ;;; new messages count + (when (< 0 new-messages-count) + [view {:style {:position "absolute" + :right 0 + :bottom 24 + :width 18 + :height 18 + :backgroundColor "#6BC6C8" + :borderColor "#FFFFFF" + :borderRadius 50 + :alignSelf "flex-end"}} + [text {:style {:width 18 + :height 17 + :fontFamily "Avenir-Roman" + :fontSize 10 + :color "#FFFFFF" + :lineHeight 19 + :textAlign "center" + :top 1}} + new-messages-count]])]]])) diff --git a/syng-im/src/syng_im/components/contact_list/contact_list.cljs b/syng-im/src/syng_im/components/contact_list/contact_list.cljs new file mode 100644 index 0000000000..31f4f6ecf3 --- /dev/null +++ b/syng-im/src/syng_im/components/contact_list/contact_list.cljs @@ -0,0 +1,96 @@ +(ns syng-im.components.contact-list.contact-list + (:require-macros + [natal-shell.data-source :refer [data-source clone-with-rows]] + [natal-shell.core :refer [with-error-view]]) + (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] + [syng-im.components.react :refer [view text image touchable-highlight + navigator list-view toolbar-android + list-item]] + [syng-im.components.resources :as res] + [syng-im.components.contact-list.contact :refer [contact-view]] + [syng-im.utils.logging :as log] + ;; [messenger.comm.intercom :as intercom] + ;; [messenger.components.contact-list.contact :refer [contact]] + ;; [messenger.components.iname :as in] + )) + +(defn render-row [row section-id row-id] + (list-item (contact-view (js->clj row :keywordize-keys true)))) + +;; (defn load-contacts [] +;; (intercom/load-syng-contacts)) + +;; (defui ContactList +;; static in/IName +;; (get-name [this] +;; :contacts/contacts) +;; static om/IQuery +;; (query [this] +;; '[:contacts-ds]) +;; Object +;; (componentDidMount [this] +;; (load-contacts)) +;; (render [this] +;; (let [{{contacts-ds :contacts-ds} :contacts/contacts} (om/props this) +;; {:keys [nav]} (om/get-computed this)] +;; (view {:style {:flex 1 +;; :backgroundColor "white"}} +;; (toolbar-android {:logo res/logo-icon +;; :title "Chats" +;; :titleColor "#4A5258" +;; :style {:backgroundColor "white" +;; :height 56 +;; :elevation 2}}) +;; (when contacts-ds +;; (list-view {:dataSource contacts-ds + ;; :renderRow (partial render-row nav) +;; :style {:backgroundColor "white"}})))))) + +;; (def contact-list (om/factory ContactList)) + +(defn get-data-source [contacts] + (clone-with-rows (data-source {:rowHasChanged (fn [row1 row2] + (not= row1 row2))}) + contacts)) + +(defn contacts-list-re-frame [contacts] + (let [contacts-ds (get-data-source contacts)] + [view {:style {:flex 1 + :backgroundColor "white"}} + [toolbar-android {:logo res/logo-icon + :title "Chats" + :titleColor "#4A5258" + :style {:backgroundColor "white" + :height 56 + :elevation 2}}] + (when contacts-ds + [list-view {:dataSource contacts-ds + :renderRow render-row + :style {:backgroundColor "white"}}] + ) + ])) + + + + + +(def logo-img (js/require "./images/cljs.png")) + + +(defn alert [title] + (.alert (.-Alert js/React) title)) + +(defn contact-list [{:keys [navigator]}] + (let [greeting (subscribe [:get-greeting]) + contacts (subscribe [:get-contacts])] + (fn [] + (contacts-list-re-frame @contacts) + + ;; [view {:style {:flex-direction "column" :margin 40 :align-items "center"}} + ;; [text {:style {:font-size 30 :font-weight "100" :margin-bottom 20 :text-align "center"}} (str @greeting " " (count @contacts))] + ;; [image {:source logo-img + ;; :style {:width 80 :height 80 :margin-bottom 30}}] + ;; [touchable-highlight {:style {:background-color "#999" :padding 10 :border-radius 5} + ;; :on-press #(alert "HELLO!")} + ;; [text {:style {:color "white" :text-align "center" :font-weight "bold"}} "press me"]]] + ))) diff --git a/syng-im/src/syng_im/components/react.cljs b/syng-im/src/syng_im/components/react.cljs index ac65dc7a4c..18ea53dd47 100644 --- a/syng-im/src/syng_im/components/react.cljs +++ b/syng-im/src/syng_im/components/react.cljs @@ -8,4 +8,43 @@ (def text (r/adapt-react-class (.-Text js/React))) (def view (r/adapt-react-class (.-View js/React))) (def image (r/adapt-react-class (.-Image js/React))) -(def touchable-highlight (r/adapt-react-class (.-TouchableHighlight js/React))) \ No newline at end of file +(def touchable-highlight (r/adapt-react-class (.-TouchableHighlight js/React))) +(def toolbar-android (r/adapt-react-class (.-ToolbarAndroid js/React))) +(def list-view (r/adapt-react-class (.-ListView js/React))) + +(defn list-item [component] + (r/as-element component)) + +;; (do +;; (def activity-indicator-ios (r/adapt-react-class (.-ActivityIndicatorIOS js/React))) +;; (def animated-image (r/adapt-react-class (.-Animated.Image js/React))) +;; (def animated-text (r/adapt-react-class (.-Animated.Text js/React))) +;; (def animated-view (r/adapt-react-class (.-Animated.View js/React))) +;; (def date-picker-ios (r/adapt-react-class (.-DatePickerIOS js/React))) +;; (def drawer-layout-android (r/adapt-react-class (.-DrawerLayoutAndroid js/React))) +;; (def image (r/adapt-react-class (.-Image js/React))) +;; (def list-view (r/adapt-react-class (.-ListView js/React))) +;; (def map-view (r/adapt-react-class (.-MapView js/React))) +;; (def modal (r/adapt-react-class (.-Modal js/React))) +;; (def navigator (r/adapt-react-class (.-Navigator js/React))) +;; (def navigator-ios (r/adapt-react-class (.-NavigatorIOS js/React))) +;; (def picker-ios (r/adapt-react-class (.-PickerIOS js/React))) +;; (def progress-bar-android (r/adapt-react-class (.-ProgressBarAndroid js/React))) +;; (def progress-view-ios (r/adapt-react-class (.-ProgressViewIOS js/React))) +;; (def pull-to-refresh-view-android (r/adapt-react-class (.-PullToRefreshViewAndroid js/React))) +;; (def scroll-view (r/adapt-react-class (.-ScrollView js/React))) +;; (def segmented-control-ios (r/adapt-react-class (.-SegmentedControlIOS js/React))) +;; (def slider-ios (r/adapt-react-class (.-SliderIOS js/React))) +;; (def switch (r/adapt-react-class (.-Switch js/React))) +;; (def tab-bar-ios (r/adapt-react-class (.-TabBarIOS js/React))) +;; (def tab-bar-ios-item (r/adapt-react-class (.-TabBarIOS.Item js/React))) +;; (def text (r/adapt-react-class (.-Text js/React))) +;; (def text-input (r/adapt-react-class (.-TextInput js/React))) +;; (def toolbar-android (r/adapt-react-class (.-ToolbarAndroid js/React))) +;; (def touchable-highlight (r/adapt-react-class (.-TouchableHighlight js/React))) +;; (def touchable-native-feedback (r/adapt-react-class (.-TouchableNativeFeedback js/React))) +;; (def touchable-opacity (r/adapt-react-class (.-TouchableOpacity js/React))) +;; (def touchable-without-feedback (r/adapt-react-class (.-TouchableWithoutFeedback js/React))) +;; (def view (r/adapt-react-class (.-View js/React))) +;; (def view-pager-android (r/adapt-react-class (.-ViewPagerAndroid js/React))) +;; (def web-view (r/adapt-react-class (.-WebView js/React)))) diff --git a/syng-im/src/syng_im/components/resources.cljs b/syng-im/src/syng_im/components/resources.cljs new file mode 100644 index 0000000000..7403226612 --- /dev/null +++ b/syng-im/src/syng_im/components/resources.cljs @@ -0,0 +1,12 @@ +(ns syng-im.components.resources) + +(def logo-icon (js/require "./images/logo.png")) +(def nav-back-icon (js/require "./images/nav-back.png")) +(def user-no-photo (js/require "./images/no-photo.png")) +(def online-icon (js/require "./images/online.png")) +(def seen-icon (js/require "./images/seen.png")) +(def delivered-icon (js/require "./images/delivered.png")) +(def play (js/require "./images/play.png")) +(def mic (js/require "./images/mic.png")) +(def smile (js/require "./images/smile.png")) +(def att (js/require "./images/att.png")) diff --git a/syng-im/src/syng_im/db.cljs b/syng-im/src/syng_im/db.cljs index 2859bd43cc..1369ed6fb5 100644 --- a/syng-im/src/syng_im/db.cljs +++ b/syng-im/src/syng_im/db.cljs @@ -6,7 +6,8 @@ ;; initial state of app-db (def app-db {:greeting "Hello Clojure in iOS and Android!" - :identity-password "replace-me-with-user-entered-password"}) + :identity-password "replace-me-with-user-entered-password" + :contacts []}) (def protocol-initialized-path [:protocol-initialized]) @@ -14,4 +15,4 @@ (def identity-password-path [:identity-password]) (def current-chat-id-path [:chat :current-chat-id]) (defn arrived-message-path [chat-id] - [:chat chat-id :arrived-message-id]) \ No newline at end of file + [:chat chat-id :arrived-message-id]) diff --git a/syng-im/src/syng_im/handlers.cljs b/syng-im/src/syng_im/handlers.cljs index 859aa85a44..24c4ff176e 100644 --- a/syng-im/src/syng_im/handlers.cljs +++ b/syng-im/src/syng_im/handlers.cljs @@ -7,6 +7,7 @@ [syng-im.protocol.protocol-handler :refer [make-handler]] [syng-im.models.protocol :refer [update-identity set-initialized]] + [syng-im.models.contacts :as contacts] [syng-im.models.messages :refer [save-message new-message-arrived]] [syng-im.utils.logging :as log])) @@ -49,8 +50,14 @@ (save-message chat-id msg) (new-message-arrived db chat-id msg-id))) +;; -- Contacts -------------------------------------------------------------- + +(register-handler :load-syng-contacts + (fn [db [_ value]] + (contacts/load-syng-contacts db))) + ;; -- Something -------------------------------------------------------------- (register-handler :set-greeting (fn [db [_ value]] - (assoc db :greeting value))) \ No newline at end of file + (assoc db :greeting value))) diff --git a/syng-im/src/syng_im/models/contacts.cljs b/syng-im/src/syng_im/models/contacts.cljs new file mode 100644 index 0000000000..8c7bfab55b --- /dev/null +++ b/syng-im/src/syng_im/models/contacts.cljs @@ -0,0 +1,83 @@ +(ns syng-im.models.contacts + (:require [cljs.core.async :as async :refer [chan put! !]] + [re-frame.core :refer [subscribe dispatch dispatch-sync]] + [syng-im.utils.utils :refer [log toast]] + [syng-im.persistence.realm :as realm])) + +(def fake-contacts? true) + +(def react-native-contacts (js/require "react-native-contacts")) + +(defn- generate-contact [n] + {:name (str "Contact " n) + :photo-path "" + :phone-numbers [{:label "mobile" :number (apply str (repeat 7 n))}] + :delivery-status (if (< (rand) 0.5) :delivered :seen) + :datetime "15:30" + :new-messages-count (rand-int 3) + :online (< (rand) 0.5)}) + +(defn- generate-contacts [n] + (map generate-contact (range 1 (inc n)))) + +(defn load-phone-contacts [] + (let [ch (chan)] + (if fake-contacts? + (put! ch {:error nil, :contacts (generate-contacts 10)}) + (.getAll react-native-contacts + (fn [error raw-contacts] + (put! ch + {:error error + :contacts + (when (not error) + (log raw-contacts) + (map (fn [contact] + ;; (toast (str contact)) + (merge contact + (generate-contact 1) + {:name (:givenName contact) + :photo-path (:thumbnailPath contact) + :phone-numbers (:phoneNumbers contact)})) + (js->clj raw-contacts :keywordize-keys true)))})))) + ch)) + +(defn- get-contacts [] + (if fake-contacts? + [{:phone-number "123" + :whisper-identity "abc" + :name "fake" + :photo-path ""}] + (realm/get-list "Contact"))) + +(defn load-syng-contacts [db] + (let [contacts (map (fn [contact] + (merge contact + {:delivery-status (if (< (rand) 0.5) :delivered :seen) + :datetime "15:30" + :new-messages-count (rand-int 3) + :online (< (rand) 0.5)})) + (get-contacts))] + (assoc db :contacts contacts))) + +(defn- create-contact [{:keys [phone-number whisper-identity name photo-path]}] + (realm/create "Contact" + {:phone-number phone-number + :whisper-identity whisper-identity + :name (or name "") + :photo-path (or photo-path "")})) + +(defn- contact-exist? [contacts contact] + (some #(= (:phone-number contact) (:phone-number %)) contacts)) + +(defn- add-contacts [contacts] + (realm/write (fn [] + (let [db-contacts (get-contacts)] + (dorun (map (fn [contact] + (if (not (contact-exist? db-contacts contact)) + (create-contact contact) + ;; TODO else override? + )) + contacts)))))) + +(defn save-syng-contacts [syng-contacts] + (add-contacts syng-contacts)) diff --git a/syng-im/src/syng_im/subs.cljs b/syng-im/src/syng_im/subs.cljs index 9bacfd9e3a..9fcace1759 100644 --- a/syng-im/src/syng_im/subs.cljs +++ b/syng-im/src/syng_im/subs.cljs @@ -6,4 +6,10 @@ :get-greeting (fn [db _] (reaction - (get @db :greeting)))) \ No newline at end of file + (get @db :greeting)))) + +(register-sub + :get-contacts + (fn [db _] + (reaction + (get @db :contacts)))) diff --git a/syng-im/src/syng_im/utils/utils.cljs b/syng-im/src/syng_im/utils/utils.cljs new file mode 100644 index 0000000000..964878c4cc --- /dev/null +++ b/syng-im/src/syng_im/utils/utils.cljs @@ -0,0 +1,38 @@ +(ns syng-im.utils.utils + (:require-macros + [natal-shell.async-storage :refer [get-item set-item]] + [natal-shell.alert :refer [alert]] + [natal-shell.toast-android :as toast])) + +;; (def server-address "http://rpc0.syng.im:20000/") +(def server-address "http://10.0.3.2:3000/") + +(defn log [obj] + (.log js/console obj)) + +(defn toast [s] + (toast/show s (toast/long))) + +(defn on-error [error] + (toast (str "error: " error))) + +(defn http-post + ([action data on-success] + (http-post action data on-success nil)) + ([action data on-success on-error] + (-> (.fetch js/window + (str server-address action) + (clj->js {:method "POST" + :headers {:accept "application/json" + :content-type "application/json"} + :body (.stringify js/JSON (clj->js data))})) + (.then (fn [response] + (log response) + (.text response))) + (.then (fn [text] + (let [json (.parse js/JSON text) + obj (js->clj json :keywordize-keys true)] + (on-success obj)))) + (.catch (or on-error + (fn [error] + (toast (str error))))))))