After Width: | Height: | Size: 651 B |
After Width: | Height: | Size: 316 B |
After Width: | Height: | Size: 526 B |
After Width: | Height: | Size: 529 B |
After Width: | Height: | Size: 985 B |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 307 B |
After Width: | Height: | Size: 898 B |
After Width: | Height: | Size: 284 B |
After Width: | Height: | Size: 584 B |
|
@ -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)))
|
||||
|
|
|
@ -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]])]]]))
|
|
@ -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"]]]
|
||||
)))
|
|
@ -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)))
|
||||
(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))))
|
||||
|
|
|
@ -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"))
|
|
@ -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])
|
||||
[:chat chat-id :arrived-message-id])
|
||||
|
|
|
@ -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)))
|
||||
(assoc db :greeting value)))
|
||||
|
|
|
@ -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))
|
|
@ -6,4 +6,10 @@
|
|||
:get-greeting
|
||||
(fn [db _]
|
||||
(reaction
|
||||
(get @db :greeting))))
|
||||
(get @db :greeting))))
|
||||
|
||||
(register-sub
|
||||
:get-contacts
|
||||
(fn [db _]
|
||||
(reaction
|
||||
(get @db :contacts))))
|
||||
|
|
|
@ -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))))))))
|