From 8481c751b6fd4a6f872f5b3081e96c10ba3914b7 Mon Sep 17 00:00:00 2001 From: michaelr Date: Fri, 25 Mar 2016 17:22:13 +0300 Subject: [PATCH 1/3] send chat msg Former-commit-id: fa06aff62bcf6926e05608a6be51cb5c3f0c370c --- syng-im/images/att.png | Bin 0 -> 651 bytes syng-im/images/delivered.png | Bin 0 -> 316 bytes syng-im/images/logo.png | Bin 0 -> 526 bytes syng-im/images/mic.png | Bin 0 -> 529 bytes syng-im/images/nav-back.png | Bin 0 -> 985 bytes syng-im/images/no-photo.png | Bin 0 -> 23384 bytes syng-im/images/online.png | Bin 0 -> 307 bytes syng-im/images/play.png | Bin 0 -> 898 bytes syng-im/images/seen.png | Bin 0 -> 284 bytes syng-im/images/smile.png | Bin 0 -> 584 bytes syng-im/project.clj | 1 + syng-im/src/syng_im/android/core.cljs | 7 +- syng-im/src/syng_im/components/chat.cljs | 57 ++++++--- .../src/syng_im/components/chat_message.cljs | 121 ++++++++++++++++++ .../syng_im/components/chat_message_new.cljs | 51 ++++++++ .../components/invertible_scroll_view.cljs | 8 ++ syng-im/src/syng_im/components/react.cljs | 9 +- syng-im/src/syng_im/db.cljs | 11 +- syng-im/src/syng_im/handlers.cljs | 26 +++- syng-im/src/syng_im/models/chat.cljs | 7 + syng-im/src/syng_im/models/messages.cljs | 5 +- syng-im/src/syng_im/navigation.cljs | 9 ++ syng-im/src/syng_im/resources.cljs | 13 ++ syng-im/src/syng_im/subs.cljs | 28 +++- syng-im/src/syng_im/utils/listview.cljs | 7 + 25 files changed, 320 insertions(+), 40 deletions(-) create mode 100644 syng-im/images/att.png create mode 100644 syng-im/images/delivered.png create mode 100644 syng-im/images/logo.png create mode 100644 syng-im/images/mic.png create mode 100644 syng-im/images/nav-back.png create mode 100755 syng-im/images/no-photo.png create mode 100644 syng-im/images/online.png create mode 100644 syng-im/images/play.png create mode 100644 syng-im/images/seen.png create mode 100644 syng-im/images/smile.png create mode 100644 syng-im/src/syng_im/components/chat_message.cljs create mode 100644 syng-im/src/syng_im/components/chat_message_new.cljs create mode 100644 syng-im/src/syng_im/components/invertible_scroll_view.cljs create mode 100644 syng-im/src/syng_im/navigation.cljs create mode 100644 syng-im/src/syng_im/resources.cljs create mode 100644 syng-im/src/syng_im/utils/listview.cljs diff --git a/syng-im/images/att.png b/syng-im/images/att.png new file mode 100644 index 0000000000000000000000000000000000000000..bafc26837d8ee48cd1539646bba84521877076e2 GIT binary patch literal 651 zcmV;60(AX}P)0vJg| zK~yNum6AYB&1a{ zV?nhkutm@!5Ngvv>KmR86$Ayfun%j|XyGP5=Dl~?C}}eW{Z@y24!?W&-z%)({Mf|a z5J5Mh$AMa46BWn;=rk}afp;~VOnw89<#wvz_Mv(WFiS_Xz)Yoxb-<3LdPdb89z3xB zwE(0Xw-fp?0*(S0!yN!M`aqx&7zaN6WpY==j%))EW!3`O4mzK)?Wv3T zS6j*~v;b4Nc+5sb*UG8Oaasas2YkhUE-AlyqHtp{VfR{yPN1B$7X4Fck%ygy!X;6;3-niAM|Jtr_^LWk`ue30KvIr-8uT$R z8TjSst=86`T_bL@hV+oCMyk2Q@XLriy=mJY{^X@9odtJT-~{l}7|vvD`z`;80Gulp zYikz0C%{1<0A2v^D~Dw~n~2?8Q?w+j377pIRlN+x`7I;J! z19e;nVMZs7*%cr?C7!;n><^g4gymF2*kv{Vg-&|9IEF}Eu06Nk%c)SN?c;OXf;**! zE0=O-cvyPrbvj=#?Pz@P=EbdRuXKb~l(OsoYi#4_QwZM+{_Fd^g5hh{&-B>=CKu1E>^@}3^n-ntlCMGJ?SK71PceA9`njxg HN@xNAnD>Mv literal 0 HcmV?d00001 diff --git a/syng-im/images/logo.png b/syng-im/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..02e05bf0a9e7ffa9d97f1ce62515ba616d5f8d79 GIT binary patch literal 526 zcmV+p0`dKcP)ug zK~z}7?UuVsLQx#YzrS;@Xpuv&sYoIV+Nwd#TpWs~8fs{^DT`izNDq>`H8&>&t)ggd zjzv-QAczba1W~#7*N`-&-p6r>{Eg@S?)iPr@9{kc=-7WZvid_Tlks*d$~G8l3>vC0 zzW_MnO8G>KMoVo2*v#c-z;q5^_;(L}Vpf*5xj;cWKs=lE6T}sO{-%zMBB>Ne1cMJ| z_7&zy>9A$+Z^r>c=L4x3*vjWeS?7BT)nZnKPYVQ|rDVX+5whCcr0cUXOVDq5zL*AO z0(@8>aDJBtAdFfH09y!es6;7g0EtNC72pfg*$0xxq0pPuF=X(bsZubCLZN-L`F1p36-V;}W0|i-&t87>kK;_}VtWMmpUq6t!R@_z#4u;+{H+PJi0c0in2d z9RvpeU)59HaUw*kj+=~m#Q^|~Noe$O2LSAr0N`o%QEmPbs`Yi0s$=c8FJxnZjwdME QQ~&?~07*qoM6N<$f`KF7*Z=?k literal 0 HcmV?d00001 diff --git a/syng-im/images/mic.png b/syng-im/images/mic.png new file mode 100644 index 0000000000000000000000000000000000000000..9f5e8fc0a7d279ff73f43a1acb4cbd953739679c GIT binary patch literal 529 zcmV+s0`C2ZP)dMi-L&w2e=4YR9*@lg|vl^Zep?a?UjV6 z;8JvPDOka&qpN~a#V(b^mZ%l9@4n-p(HNiB$+O*a&-viq?`QO=IbInyF$t7`#ULhj zC-tT7jCCJc)yi3n2R(CMroM4>&-C5(j^X?|;u62>U{TSDZo_*5d&yH~`~Ft?C;h)t z)T)jg!^8W5n~mq2gSl%2$2?4& zf@8b^hQ9AyxBGV}FshH`B zd;0m7;FX!(Znl>P2WINAMZVwA@h=~v!Cgu1VQVbDV&a_4ru&lGssX$kkB><92-t~K zGkji!Y>nVUm@WgSfIFq-4;7o63qOEenYdVvRJ@M30Br3ddI@o@l+&&C}fJvmKW!y{d%rYk*U{IrTMmO@i&EA_k>j4 zk{z{L+TNVwbE{ghB}DV9yqC=srK}q>=FV^5cSvqe+6>8k^KaMqzu$X*Pv!fwJPt0{ zL8W@@+nK9c0&RF7l&+YQ!**4Qd%~pf+iw^0&gf_K+3{livi$Ao8`nNv#QSfd-}7(1 zEZhvM^j@ue#B0|S6cFF}{lJUoTyKglnkrje{IF;J=3CE#0@>_yN{=OeU~PzAx1RCG zzK}y1hd+mHzLOW+VZC3}*V%wS;m5Bn(>7~#*Ppp1zk~ah6MI-VJKL_kk{$alTwgf3 z=cqE-@c&x<%*&=t+jciZ@|Oq8@840iZ~Eiw z>T>gnHckJoTe9qWL-Xm=%AS=Uj2&vlbGZcMzQ5^QB%@$#&HSoxPjvMzjt6n#e|b+7 z`|h`jUvgc2=3K^Gm-QL%Jl3oC=9yP_kBp4V(OW0Wa<+hgfvU+&lcxltbQp}K7c2P9>L&V`*+%e2S7q)!! z@%R4D{NtkQ?#V23r$>KWtmEKykN;cU_xz=M#JfbrbIliVZ!L>?uB>tPy2x^Wel_c| zI*t>@wI4etzN@mZwc2%(X-QE`*vjqJ?V(Q!;(tvwoB<3ob?yn*_%yqgxjbcN5EY-> z{!K2Q?Vvx<=vRRvD`r(XxW^o?^S{z_p5^zxhe~sfoxJ%u%FW!CVLF3DW31B2qP4R2 zXPK5P5oDQunvI`v$H&g1*$)0!qvnFr;o@uSLMQYv%_t7^WO<*mb#Lo$OL2z2AOEaE qzPAU~2s&^~jT8{X4ss{{XJ~uOIZ>Ofm<5;#89ZJ6T-G@yGywpJ`mn12 literal 0 HcmV?d00001 diff --git a/syng-im/images/no-photo.png b/syng-im/images/no-photo.png new file mode 100755 index 0000000000000000000000000000000000000000..2889cf44207d05574338509df17a34adb93327ee GIT binary patch literal 23384 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 literal 0 HcmV?d00001 diff --git a/syng-im/images/online.png b/syng-im/images/online.png new file mode 100644 index 0000000000000000000000000000000000000000..1d5193e578586d431c0f66705c41a785b972809e GIT binary patch literal 307 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a&kwj^(N7l!{JxM1({$v_d#0*}aI z1_m)z5N7lYQuzQBWH0gbb!C6RBra@es(te26`;@oPZ!4!jq_6{8S*tb2)NGg?o^!E zcPwgMgS!En-@>)M?i&}n>N6V39W+kJRg_h0VcO4|?Qx-Ly3eNiXZ}XJH+w7+U$|uR zl63Lsdf&g^I2;nR>i?3t?VTzOA~($5PurulW6wK=oM+xj`Uz<=4Z<5{dvG;If6lYC zSvMuuSN?vXrQWq|nW+u74soK)#tt{HJqb;GdXK^IgY0ARy-_(5os34+2kXCN&14j6 xIKllvwmjjR?DP}dYkb`7U%xnN_$|AJfhYBe+QV!=MWA08JYD@<);T3K0RTmfavJ~u literal 0 HcmV?d00001 diff --git a/syng-im/images/play.png b/syng-im/images/play.png new file mode 100644 index 0000000000000000000000000000000000000000..078b1e29c7e384a5aa181f39b271d3cefafcd85d GIT binary patch literal 898 zcmV-|1AY97P)MEAf?PWzRu8q7C0=X$bZ zjjGxVl;XVS+o)bjn8d)7UBk<1Lp=2L|; zxFwH!QS53mJkaquLacdjv?|QZcCZbEUJSa4~EuWHfvArHZ}Rl@_n@<0IQ$; zk|dazYpcoL6vCUE7*e!?oz)4S$s5`bVxhgh4yYjm5~X&bz2Wq_IdQkC$?wfg%mPo5 zG3lL9Ko6C44n+;Xp1k;5nFRolr}IMQ41iG(wdI{u9yft}B-`%>$yosTtF`e3+vpAF z1crU*FMu&brpAeUHx0?;HliZOP15Melm;=|zk2NP;jgMCzvDx)4jxWFD58}HDBsR_ zdW!ZFc>8_w8sEcH5KJe5m0Jc%)rH`C%Z}hZ^c>OeuMz&{OEea9$Uv!4C~|GWdn|kv zFSEDp1FQojPP1r{0eo)%e4bzt{wl|L&u@T;+Yrh7_XtmnI&Hpe3Ct_xx{?E<5yF#K ziQb$6+jcrJ=Pes%1)kmB+m+~#*NIGyVTb-nabacF5|x{Xo=SV@@BPX0l@a2<+)9t% zaKi$ofR7wM6bYk|uxlC9R{2%{9<(>yPQ}EUKt38@S%ul2ZWVS9mOb(l`bkRki+{TT zfI`n>Q?s&^^#w4O*`=J{mIC(hV~FDZ{xGU7nIupn@0N59Mc3K8!k)`xAfIK3p*Pqy z@b!j9w_wS0PGNF~9o1ODT&QhRw=MnsaTAZ#p?Ym+V!(`A`nrscCO3ruDCiwo4DHoQ zfzMs8Ir<(Za$fy!;j_DI(Op%Q|8IYywf?9W^9tzmK-r@*AqQ9n{s4YfmAmHgO_G5N)66f;rH{GCIiO-9lthz6M<028@YGhaR~nH`nov7fcLTGs!u!j ZGR%~9yu8X~Lod+L44$rjF6*2UngCN>Yj6Mn literal 0 HcmV?d00001 diff --git a/syng-im/images/smile.png b/syng-im/images/smile.png new file mode 100644 index 0000000000000000000000000000000000000000..e64022a14fcbbae4d6ea4932efb3326e5269ec8f GIT binary patch literal 584 zcmV-O0=NB%P)N0lHaVRnVA%=y6MsMZtB%L%^dg*)z%~ zhE*&ktR-0#oY@=%K-cIik2WlOJ&CvRZxVY{Z#)W>+v=blc?DVn+OaMQfS-)XX(9;r9?&}1Nr~31#u_6`)BZzwr><0<= zF-@_!dyZX8IWm?98wGltxR)x#ga1H=Ch>c50000clj row :keywordize-keys true)])) + :style {:backgroundColor "black"}}] + [chat-message-new]])))) diff --git a/syng-im/src/syng_im/components/chat_message.cljs b/syng-im/src/syng_im/components/chat_message.cljs new file mode 100644 index 0000000000..02f449f8bb --- /dev/null +++ b/syng-im/src/syng_im/components/chat_message.cljs @@ -0,0 +1,121 @@ +(ns syng-im.components.chat-message + (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] + [syng-im.components.react :refer [android? + view + text + image + touchable-highlight + navigator + toolbar-android]] + [syng-im.utils.logging :as log] + [syng-im.navigation :refer [nav-pop]] + [syng-im.resources :as res] + [syng-im.constants :refer [text-content-type]])) + + +(defn message-date [{:keys [date]}] + [text {:style {:marginVertical 10 + :fontFamily "Avenir-Roman" + :fontSize 11 + :color "#AAB2B2" + :letterSpacing 1 + :lineHeight 15 + :textAlign "center" + :opacity 0.8}} + date]) + +(defn message-content-audio [{:keys [content-type content-type]}] + [view {:style {:flexDirection "row" + :alignItems "center"}} + [view {:style {:width 33 + :height 33 + :borderRadius 50 + :elevation 1}} + [image {:source res/play + :style {:width 33 + :height 33}}]] + [view {:style {:marginTop 10 + :marginLeft 10 + :width 120 + :height 26 + :elevation 1}} + [view {:style {:position "absolute" + :top 4 + :width 120 + :height 2 + :backgroundColor "#EC7262"}}] + [view {:style {:position "absolute" + :left 0 + :top 0 + :width 2 + :height 10 + :backgroundColor "#4A5258"}}] + [text {:style {:position "absolute" + :left 1 + :top 11 + :fontFamily "Avenir-Roman" + :fontSize 11 + :color "#4A5258" + :letterSpacing 1 + :lineHeight 15}} + "03:39"]]]) + +(defn message-content [{:keys [content-type content outgoing]}] + [view {:style (merge {:borderRadius 6} + (if (= content-type text-content-type) + {:paddingVertical 12 + :paddingHorizontal 16} + {:paddingVertical 14 + :paddingHorizontal 10}) + (if outgoing + {:backgroundColor "#D3EEEF"} + {:backgroundColor "#FBF6E3"}))} + (if (= content-type text-content-type) + [text {:style {:fontSize 14 + :fontFamily "Avenir-Roman" + :color "#4A5258"}} + content] + [message-content-audio {:content content + :content-type content-type}])]) + +(defn message-delivery-status [{:keys [delivery-status]}] + [view {:style {:flexDirection "row" + :marginTop 2}} + [image {:source (if (= (keyword delivery-status) :seen) + res/seen-icon + res/delivered-icon) + :style {:marginTop 6 + :opacity 0.6}}] + [text {:style {:fontFamily "Avenir-Roman" + :fontSize 11 + :color "#AAB2B2" + :opacity 0.8 + :marginLeft 5}} + (if (= (keyword delivery-status) :seen) + "Seen" + "Delivered")]]) + +(defn message-body [{:keys [msg-id content content-type outgoing delivery-status]}] + [view {:style (merge {:flexDirection "column" + :width 260 + :marginVertical 5} + (if outgoing + {:alignSelf "flex-end" + :alignItems "flex-end"} + {:alignSelf "flex-start" + :alignItems "flex-start"}))} + [message-content {:content-type content-type + :content content + :outgoing outgoing}] + (when (and outgoing delivery-status) + [message-delivery-status {:delivery-status delivery-status}])]) + +(defn chat-message [{:keys [msg-id content content-type outgoing delivery-status date new-day] :as msg}] + [view {:paddingHorizontal 15} + (when new-day + [message-date {:date date}]) + [message-body {:msg-id msg-id + :content content + :content-type content-type + :outgoing outgoing + :delivery-status delivery-status}]]) diff --git a/syng-im/src/syng_im/components/chat_message_new.cljs b/syng-im/src/syng_im/components/chat_message_new.cljs new file mode 100644 index 0000000000..9b5f37a404 --- /dev/null +++ b/syng-im/src/syng_im/components/chat_message_new.cljs @@ -0,0 +1,51 @@ +(ns syng-im.components.chat-message-new + (:require [re-frame.core :refer [subscribe dispatch dispatch-sync]] + [syng-im.components.react :refer [android? + view + image + text-input]] + [syng-im.utils.logging :as log] + [syng-im.resources :as res] + [reagent.core :as r])) + + +(defn chat-message-new [] + (let [text (r/atom nil) + chat-id (subscribe [:get-current-chat-id])] + (fn [] + [view {:style {:flexDirection "row" + :margin 10 + :height 40 + :backgroundColor "#E5F5F6" + :borderRadius 5}} + [image {:source res/mic + :style {:marginTop 11 + :marginLeft 14 + :width 13 + :height 20}}] + [text-input {:underlineColorAndroid "#9CBFC0" + :style {:flex 1 + :marginLeft 18 + :lineHeight 42 + :fontSize 14 + :fontFamily "Avenir-Roman" + :color "#9CBFC0"} + :autoFocus true + :placeholder "Enter your message here" + :value @text + :onChangeText (fn [new-text] + (reset! text new-text) + (r/flush)) + :onSubmitEditing (fn [e] + (dispatch [:send-chat-msg @chat-id @text]) + (reset! text nil))}] + [image {:source res/smile + :style {:marginTop 11 + :marginRight 12 + :width 18 + :height 18}}] + [image {:source res/att + :style {:marginTop 14 + :marginRight 16 + :width 17 + :height 14}}]]))) diff --git a/syng-im/src/syng_im/components/invertible_scroll_view.cljs b/syng-im/src/syng_im/components/invertible_scroll_view.cljs new file mode 100644 index 0000000000..8611247c2d --- /dev/null +++ b/syng-im/src/syng_im/components/invertible_scroll_view.cljs @@ -0,0 +1,8 @@ +(ns syng-im.components.invertible-scroll-view) + +(set! js/InvertibleScrollView (js/require "react-native-invertible-scroll-view")) + +(defn invertible-scroll-view [props] + (js/React.createElement js/InvertibleScrollView + (clj->js (merge {:inverted true} props)))) + diff --git a/syng-im/src/syng_im/components/react.cljs b/syng-im/src/syng_im/components/react.cljs index ac65dc7a4c..0c524c4e80 100644 --- a/syng-im/src/syng_im/components/react.cljs +++ b/syng-im/src/syng_im/components/react.cljs @@ -8,4 +8,11 @@ (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))) +(def text-input (r/adapt-react-class (.-TextInput js/React))) + +(def platform (.. js/React -Platform -OS)) + +(def android? (= platform "android")) \ No newline at end of file diff --git a/syng-im/src/syng_im/db.cljs b/syng-im/src/syng_im/db.cljs index 2859bd43cc..8e2ee1a97a 100644 --- a/syng-im/src/syng_im/db.cljs +++ b/syng-im/src/syng_im/db.cljs @@ -5,13 +5,14 @@ (def schema {:greeting s/Str}) ;; initial state of app-db -(def app-db {:greeting "Hello Clojure in iOS and Android!" - :identity-password "replace-me-with-user-entered-password"}) +(def app-db {:greeting "Hello Clojure in iOS and Android!" + :identity-password "replace-me-with-user-entered-password" + :chat {:current-chat-id "0x040028c500ff086ecf1cfbb3c1a7240179cde5b86f9802e6799b9bbe9cdd7ad1b05ae8807fa1f9ed19cc8ce930fc2e878738c59f030a6a2f94b3522dc1378ff154"} + :chats {}}) (def protocol-initialized-path [:protocol-initialized]) -(def simple-store-path [:simple-store]) (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 +(defn latest-msg-id-path [chat-id] + [:chats chat-id :arrived-message-id]) \ No newline at end of file diff --git a/syng-im/src/syng_im/handlers.cljs b/syng-im/src/syng_im/handlers.cljs index 859aa85a44..fa774b136f 100644 --- a/syng-im/src/syng_im/handlers.cljs +++ b/syng-im/src/syng_im/handlers.cljs @@ -7,9 +7,11 @@ [syng-im.protocol.protocol-handler :refer [make-handler]] [syng-im.models.protocol :refer [update-identity set-initialized]] - [syng-im.models.messages :refer [save-message - new-message-arrived]] - [syng-im.utils.logging :as log])) + [syng-im.models.messages :refer [save-message]] + [syng-im.models.chat :refer [set-latest-msg-id]] + [syng-im.utils.logging :as log] + [syng-im.protocol.api :as api] + [syng-im.constants :refer [text-content-type]])) ;; -- Middleware ------------------------------------------------------------ ;; @@ -47,7 +49,23 @@ (fn [db [_ {chat-id :from msg-id :msg-id :as msg}]] (save-message chat-id msg) - (new-message-arrived db chat-id msg-id))) + (set-latest-msg-id db chat-id msg-id))) + +(register-handler :send-chat-msg + (fn [db [_ chat-id text]] + (log/debug "chat-id" chat-id "text" text) + (let [{msg-id :msg-id + {from :from + to :to} :msg} (api/send-user-msg {:to chat-id + :content text}) + msg {:msg-id msg-id + :from from + :to to + :content text + :content-type text-content-type + :outgoing true}] + (save-message chat-id msg) + (set-latest-msg-id db chat-id msg-id)))) ;; -- Something -------------------------------------------------------------- diff --git a/syng-im/src/syng_im/models/chat.cljs b/syng-im/src/syng_im/models/chat.cljs index e312d3daf5..23a03e79db 100644 --- a/syng-im/src/syng_im/models/chat.cljs +++ b/syng-im/src/syng_im/models/chat.cljs @@ -6,3 +6,10 @@ (defn current-chat-id [db] (get-in db db/current-chat-id-path)) + +(defn set-latest-msg-id [db chat-id msg-id] + (assoc-in db (db/latest-msg-id-path chat-id) msg-id)) + +(defn latest-msg-id [db chat-id] + (->> (db/latest-msg-id-path chat-id) + (get-in db))) \ No newline at end of file diff --git a/syng-im/src/syng_im/models/messages.cljs b/syng-im/src/syng_im/models/messages.cljs index 291d6ded70..93b0bac8bf 100644 --- a/syng-im/src/syng_im/models/messages.cljs +++ b/syng-im/src/syng_im/models/messages.cljs @@ -26,9 +26,6 @@ (-> (get-messages* chat-id) (js->clj :keywordize-keys true))) -(defn new-message-arrived [db chat-id msg-id] - (assoc-in db (db/arrived-message-path chat-id) msg-id)) - (comment (save-message "0x040028c500ff086ecf1cfbb3c1a7240179cde5b86f9802e6799b9bbe9cdd7ad1b05ae8807fa1f9ed19cc8ce930fc2e878738c59f030a6a2f94b3522dc1378ff154" @@ -38,7 +35,7 @@ (get-messages* "0x040028c500ff086ecf1cfbb3c1a7240179cde5b86f9802e6799b9bbe9cdd7ad1b05ae8807fa1f9ed19cc8ce930fc2e878738c59f030a6a2f94b3522dc1378ff154") - (get-messages "0x043df89d36f6e3d8ade18e55ac3e2e39406ebde152f76f2f82d674681d59319ffd9880eebfb4f5f8d5c222ec485b44d6e30ba3a03c96b1c946144fdeba1caccd43") + (get-messages nil) (doseq [msg (get-messages* "0x043df89d36f6e3d8ade18e55ac3e2e39406ebde152f76f2f82d674681d59319ffd9880eebfb4f5f8d5c222ec485b44d6e30ba3a03c96b1c946144fdeba1caccd43")] (r/delete msg)) diff --git a/syng-im/src/syng_im/navigation.cljs b/syng-im/src/syng_im/navigation.cljs new file mode 100644 index 0000000000..6405c53589 --- /dev/null +++ b/syng-im/src/syng_im/navigation.cljs @@ -0,0 +1,9 @@ +(ns syng-im.navigation) + +(def ^{:dynamic true :private true} *nav-render* + "Flag to suppress navigator re-renders from outside om when pushing/popping." + true) + +(defn nav-pop [nav] + (binding [*nav-render* false] + (.pop nav))) \ No newline at end of file diff --git a/syng-im/src/syng_im/resources.cljs b/syng-im/src/syng_im/resources.cljs new file mode 100644 index 0000000000..d7e11f0aea --- /dev/null +++ b/syng-im/src/syng_im/resources.cljs @@ -0,0 +1,13 @@ +(ns syng-im.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/subs.cljs b/syng-im/src/syng_im/subs.cljs index 9bacfd9e3a..6b7b9bd30e 100644 --- a/syng-im/src/syng_im/subs.cljs +++ b/syng-im/src/syng_im/subs.cljs @@ -1,9 +1,25 @@ (ns syng-im.subs (:require-macros [reagent.ratom :refer [reaction]]) - (:require [re-frame.core :refer [register-sub]])) + (:require [re-frame.core :refer [register-sub]] + [syng-im.models.chat :refer [current-chat-id + latest-msg-id]] + [syng-im.models.messages :refer [get-messages]])) -(register-sub - :get-greeting - (fn [db _] - (reaction - (get @db :greeting)))) \ No newline at end of file +(register-sub :get-greeting (fn [db _] + (reaction + (get @db :greeting)))) + +(register-sub :get-chat-messages + (fn [db _] + (let [chat-id (-> (current-chat-id @db) + (reaction)) + latest-msg (-> (latest-msg-id @db @chat-id) + (reaction))] + ;; latest-msg signals us that a new message has been added + (reaction + (let [_ @latest-msg] + (get-messages @chat-id)))))) + +(register-sub :get-current-chat-id (fn [db _] + (-> (current-chat-id @db) + (reaction)))) \ No newline at end of file diff --git a/syng-im/src/syng_im/utils/listview.cljs b/syng-im/src/syng_im/utils/listview.cljs new file mode 100644 index 0000000000..4908974276 --- /dev/null +++ b/syng-im/src/syng_im/utils/listview.cljs @@ -0,0 +1,7 @@ +(ns syng-im.utils.listview + (:require-macros [natal-shell.data-source :refer [data-source clone-with-rows]])) + +(defn to-datasource [msgs] + (-> (data-source {:rowHasChanged (fn [row1 row2] + (not= row1 row2))}) + (clone-with-rows msgs))) \ No newline at end of file From c7b3564a29ecf5a1960416f7471ea3845fe1db05 Mon Sep 17 00:00:00 2001 From: michaelr Date: Mon, 28 Mar 2016 19:00:07 +0300 Subject: [PATCH 2/3] delivery status, scroll through messages history Former-commit-id: 8f9ee4ba31c1d3aeb03a31d7afd5501203cf10e6 --- syng-im/.re-natal | 5 +- syng-im/images/deliveryfailed.png | Bin 0 -> 124 bytes syng-im/package.json | 4 +- syng-im/src/syng_im/components/chat.cljs | 8 +-- .../src/syng_im/components/chat_message.cljs | 16 +++--- syng-im/src/syng_im/components/realm.cljs | 18 +++++++ syng-im/src/syng_im/db.cljs | 2 +- syng-im/src/syng_im/handlers.cljs | 23 +++++++-- syng-im/src/syng_im/models/chat.cljs | 15 ++++-- syng-im/src/syng_im/models/messages.cljs | 48 +++++++++++------- syng-im/src/syng_im/models/protocol.cljs | 5 ++ syng-im/src/syng_im/persistence/realm.cljs | 43 +++++++++++----- .../syng_im/protocol/protocol_handler.cljs | 8 +-- syng-im/src/syng_im/resources.cljs | 1 + syng-im/src/syng_im/utils/listview.cljs | 8 ++- 15 files changed, 145 insertions(+), 59 deletions(-) create mode 100644 syng-im/images/deliveryfailed.png create mode 100644 syng-im/src/syng_im/components/realm.cljs diff --git a/syng-im/.re-natal b/syng-im/.re-natal index 7cd8ae4ffa..20f66539ae 100644 --- a/syng-im/.re-natal +++ b/syng-im/.re-natal @@ -1,14 +1,15 @@ { "name": "SyngIm", "interface": "reagent", - "androidHost": "localhost", + "androidHost": "10.0.3.2", "modules": [ "react-native-contacts", "react-native-invertible-scroll-view", "awesome-phonenumber", "realm", "react-native-loading-spinner-overlay", - "react-native-i18n" + "react-native-i18n", + "realm/react-native" ], "imageDirs": [ "images" diff --git a/syng-im/images/deliveryfailed.png b/syng-im/images/deliveryfailed.png new file mode 100644 index 0000000000000000000000000000000000000000..921ee8c62b5fcd5a6aa47f26930c1b973de0b1d2 GIT binary patch literal 124 zcmeAS@N?(olHy`uVBq!ia0vp^oIuRZ!3HF6%}!4MQY^(zo*^7SP{WbZ0puHdx;TbN zOzrJ+FVdQ&MBb@0M)S~%m4rY literal 0 HcmV?d00001 diff --git a/syng-im/package.json b/syng-im/package.json index d344ac3d49..9a81d2f595 100644 --- a/syng-im/package.json +++ b/syng-im/package.json @@ -12,6 +12,6 @@ "react-native-i18n": "0.0.8", "react-native-invertible-scroll-view": "^0.2.0", "react-native-loading-spinner-overlay": "0.0.6", - "realm": "^0.10.0" + "realm": "^0.11.0" } -} \ No newline at end of file +} diff --git a/syng-im/src/syng_im/components/chat.cljs b/syng-im/src/syng_im/components/chat.cljs index dc3e69111b..d0b57cfb8e 100644 --- a/syng-im/src/syng_im/components/chat.cljs +++ b/syng-im/src/syng_im/components/chat.cljs @@ -6,12 +6,12 @@ image touchable-highlight navigator - toolbar-android - list-view]] + toolbar-android]] + [syng-im.components.realm :refer [list-view]] [syng-im.utils.logging :as log] [syng-im.navigation :refer [nav-pop]] [syng-im.resources :as res] - [syng-im.utils.listview :refer [to-datasource]] + [syng-im.utils.listview :refer [to-realm-datasource]] [syng-im.components.invertible-scroll-view :refer [invertible-scroll-view]] [reagent.core :as r] [syng-im.components.chat-message :refer [chat-message]] @@ -23,7 +23,7 @@ (fn [] (let [msgs @messages _ (log/debug "messages=" msgs) - datasource (to-datasource msgs)] + datasource (to-realm-datasource msgs)] [view {:style {:flex 1 :backgroundColor "white"}} (when android? diff --git a/syng-im/src/syng_im/components/chat_message.cljs b/syng-im/src/syng_im/components/chat_message.cljs index 02f449f8bb..e767e96df2 100644 --- a/syng-im/src/syng_im/components/chat_message.cljs +++ b/syng-im/src/syng_im/components/chat_message.cljs @@ -81,9 +81,10 @@ (defn message-delivery-status [{:keys [delivery-status]}] [view {:style {:flexDirection "row" :marginTop 2}} - [image {:source (if (= (keyword delivery-status) :seen) - res/seen-icon - res/delivered-icon) + [image {:source (case delivery-status + :delivered res/delivered-icon + :seen res/seen-icon + :failed res/delivery-failed-icon) :style {:marginTop 6 :opacity 0.6}}] [text {:style {:fontFamily "Avenir-Roman" @@ -91,9 +92,10 @@ :color "#AAB2B2" :opacity 0.8 :marginLeft 5}} - (if (= (keyword delivery-status) :seen) - "Seen" - "Delivered")]]) + (case delivery-status + :delivered "Delivered" + :seen "Seen" + :failed "Failed")]]) (defn message-body [{:keys [msg-id content content-type outgoing delivery-status]}] [view {:style (merge {:flexDirection "column" @@ -118,4 +120,4 @@ :content content :content-type content-type :outgoing outgoing - :delivery-status delivery-status}]]) + :delivery-status (keyword delivery-status)}]]) diff --git a/syng-im/src/syng_im/components/realm.cljs b/syng-im/src/syng_im/components/realm.cljs new file mode 100644 index 0000000000..a3e32f3a71 --- /dev/null +++ b/syng-im/src/syng_im/components/realm.cljs @@ -0,0 +1,18 @@ +(ns syng-im.components.realm + (:require [reagent.core :as r])) + +(set! js/RealmReactNative (js/require "realm/react-native")) + +(def list-view (r/adapt-react-class (.-ListView js/RealmReactNative))) + +(comment + + + ;(set! js/wat (js/require "realm.react-native.ListView")) + ;(.-Results js/Realm) + ; + ;(r/realm) + ; + ;(require '[syng-im.persistence.realm :as r]) + + ) \ No newline at end of file diff --git a/syng-im/src/syng_im/db.cljs b/syng-im/src/syng_im/db.cljs index 8e2ee1a97a..685ac8bb30 100644 --- a/syng-im/src/syng_im/db.cljs +++ b/syng-im/src/syng_im/db.cljs @@ -7,7 +7,7 @@ ;; initial state of app-db (def app-db {:greeting "Hello Clojure in iOS and Android!" :identity-password "replace-me-with-user-entered-password" - :chat {:current-chat-id "0x040028c500ff086ecf1cfbb3c1a7240179cde5b86f9802e6799b9bbe9cdd7ad1b05ae8807fa1f9ed19cc8ce930fc2e878738c59f030a6a2f94b3522dc1378ff154"} + :chat {:current-chat-id "0x0479a5ed1f38cadfad1db6cd56c4b659b0ebe052bbe9efa950f6660058519fa4ca6be2dda66afa80de96ab00eb97a2605d5267a1e8f4c2a166ab551f6826608cdd"} :chats {}}) diff --git a/syng-im/src/syng_im/handlers.cljs b/syng-im/src/syng_im/handlers.cljs index fa774b136f..f6234a4295 100644 --- a/syng-im/src/syng_im/handlers.cljs +++ b/syng-im/src/syng_im/handlers.cljs @@ -7,8 +7,10 @@ [syng-im.protocol.protocol-handler :refer [make-handler]] [syng-im.models.protocol :refer [update-identity set-initialized]] - [syng-im.models.messages :refer [save-message]] - [syng-im.models.chat :refer [set-latest-msg-id]] + [syng-im.models.messages :refer [save-message + update-message! + message-by-id]] + [syng-im.models.chat :refer [signal-chat-updated]] [syng-im.utils.logging :as log] [syng-im.protocol.api :as api] [syng-im.constants :refer [text-content-type]])) @@ -49,7 +51,20 @@ (fn [db [_ {chat-id :from msg-id :msg-id :as msg}]] (save-message chat-id msg) - (set-latest-msg-id db chat-id msg-id))) + (signal-chat-updated db chat-id))) + +(register-handler :acked-msg + (fn [db [_ from msg-id]] + (update-message! {:msg-id msg-id + :delivery-status :delivered}) + (signal-chat-updated db from))) + +(register-handler :msg-delivery-failed + (fn [db [_ msg-id]] + (update-message! {:msg-id msg-id + :delivery-status :failed}) + (let [{:keys [chat-id]} (message-by-id msg-id)] + (signal-chat-updated db chat-id)))) (register-handler :send-chat-msg (fn [db [_ chat-id text]] @@ -65,7 +80,7 @@ :content-type text-content-type :outgoing true}] (save-message chat-id msg) - (set-latest-msg-id db chat-id msg-id)))) + (signal-chat-updated db chat-id)))) ;; -- Something -------------------------------------------------------------- diff --git a/syng-im/src/syng_im/models/chat.cljs b/syng-im/src/syng_im/models/chat.cljs index 23a03e79db..28a598714b 100644 --- a/syng-im/src/syng_im/models/chat.cljs +++ b/syng-im/src/syng_im/models/chat.cljs @@ -7,9 +7,18 @@ (defn current-chat-id [db] (get-in db db/current-chat-id-path)) -(defn set-latest-msg-id [db chat-id msg-id] - (assoc-in db (db/latest-msg-id-path chat-id) msg-id)) +(defn signal-chat-updated [db chat-id] + (update-in db (db/latest-msg-id-path chat-id) (fn [current] + (if current + (inc current) + 0)))) (defn latest-msg-id [db chat-id] (->> (db/latest-msg-id-path chat-id) - (get-in db))) \ No newline at end of file + (get-in db))) + +(comment + + (swap! re-frame.db/app-db (fn [db] + (signal-chat-updated db "0x0479a5ed1f38cadfad1db6cd56c4b659b0ebe052bbe9efa950f6660058519fa4ca6be2dda66afa80de96ab00eb97a2605d5267a1e8f4c2a166ab551f6826608cdd"))) + ) \ No newline at end of file diff --git a/syng-im/src/syng_im/models/messages.cljs b/syng-im/src/syng_im/models/messages.cljs index 93b0bac8bf..36257e47c8 100644 --- a/syng-im/src/syng_im/models/messages.cljs +++ b/syng-im/src/syng_im/models/messages.cljs @@ -2,32 +2,44 @@ (:require [syng-im.persistence.realm :as r] [cljs.reader :refer [read-string]] [syng-im.utils.random :refer [timestamp]] - [syng-im.db :as db])) + [syng-im.db :as db] + [syng-im.utils.logging :as log])) (defn save-message [chat-id {:keys [from to msg-id content content-type outgoing] :or {outgoing false} :as msg}] (when-not (r/exists? :msgs :msg-id msg-id) (r/write (fn [] - (r/create :msgs {:chat-id chat-id - :msg-id msg-id - :from from - :to to - :content content - :content-type content-type - :outgoing outgoing - :timestamp (timestamp)} true))))) - -(defn get-messages* [chat-id] - (-> (r/get-by-field :msgs :chat-id chat-id) - (r/sorted :timestamp :desc) - (r/page 0 10))) + (r/create :msgs {:chat-id chat-id + :msg-id msg-id + :from from + :to to + :content content + :content-type content-type + :outgoing outgoing + :timestamp (timestamp) + :delivery-status nil} true))))) (defn get-messages [chat-id] - (-> (get-messages* chat-id) - (js->clj :keywordize-keys true))) + (-> (r/get-by-field :msgs :chat-id chat-id) + (r/sorted :timestamp :desc))) + +(defn message-by-id [msg-id] + (-> (r/get-by-field :msgs :msg-id msg-id) + (r/single-cljs))) + +(defn update-message! [msg] + (r/write + (fn [] + (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!" @@ -35,9 +47,11 @@ (get-messages* "0x040028c500ff086ecf1cfbb3c1a7240179cde5b86f9802e6799b9bbe9cdd7ad1b05ae8807fa1f9ed19cc8ce930fc2e878738c59f030a6a2f94b3522dc1378ff154") - (get-messages nil) + (get-messages "0x0479a5ed1f38cadfad1db6cd56c4b659b0ebe052bbe9efa950f6660058519fa4ca6be2dda66afa80de96ab00eb97a2605d5267a1e8f4c2a166ab551f6826608cdd") (doseq [msg (get-messages* "0x043df89d36f6e3d8ade18e55ac3e2e39406ebde152f76f2f82d674681d59319ffd9880eebfb4f5f8d5c222ec485b44d6e30ba3a03c96b1c946144fdeba1caccd43")] (r/delete msg)) + @re-frame.db/app-db + ) \ No newline at end of file diff --git a/syng-im/src/syng_im/models/protocol.cljs b/syng-im/src/syng_im/models/protocol.cljs index 47f164e6a2..bb3bb3ca9f 100644 --- a/syng-im/src/syng_im/models/protocol.cljs +++ b/syng-im/src/syng_im/models/protocol.cljs @@ -25,3 +25,8 @@ (when encrypted (-> (password-decrypt password encrypted) (read-string))))) + +(comment + + (stored-identity @re-frame.db/app-db) + ) \ No newline at end of file diff --git a/syng-im/src/syng_im/persistence/realm.cljs b/syng-im/src/syng_im/persistence/realm.cljs index 1c7201f7ec..51330b3bb9 100644 --- a/syng-im/src/syng_im/persistence/realm.cljs +++ b/syng-im/src/syng_im/persistence/realm.cljs @@ -17,14 +17,17 @@ :value "string"}} {:name :msgs :primaryKey :msg-id - :properties {:msg-id "string" - :from "string" - :to "string" - :content "string" ;; TODO make it ArrayBuffer - :content-type "string" - :timestamp "int" - :chat-id "string" - :outgoing "bool"}}]}) + :properties {:msg-id "string" + :from "string" + :to "string" + :content "string" ;; TODO make it ArrayBuffer + :content-type "string" + :timestamp "int" + :chat-id {:type "string" + :indexed true} + :outgoing "bool" + :delivery-status {:type "string" + :optional true}}}]}) (def realm (js/Realm. (clj->js opts))) @@ -34,7 +37,10 @@ (into {}))) (defn field-type [schema-name field] - (get-in schema-by-name [schema-name :properties field])) + (let [field-def (get-in schema-by-name [schema-name :properties field])] + (if (map? field-def) + (:type field-def) + field-def))) (defn write [f] (.write realm f)) @@ -52,14 +58,13 @@ (let [value (to-string value) query (str (name field) "=" (if (= "string" (field-type schema-name field)) (str "\"" value "\"") - value)) - ;_ (log/debug query) - ] + value))] query)) (defn get-by-field [schema-name field value] - (-> (.objects realm (name schema-name)) - (.filtered (to-query schema-name :eq field value)))) + (let [q (to-query schema-name :eq field value)] + (-> (.objects realm (name schema-name)) + (.filtered q)))) (defn sorted [results field-name order] (.sorted results (to-string field-name) (if (= order :asc) @@ -92,3 +97,13 @@ (defn get-list [schema-name] (vals (js->clj (.objects realm schema-name) :keywordize-keys true))) + + +(comment + + (write #(.create realm "msgs" (clj->js {:msg-id "1459175391577-a2185a35-5c49-5a6b-9c08-6eb5b87ceb7f" + :content "sdfd" + :delivery-status "seen"}) true)) + + + ) \ No newline at end of file diff --git a/syng-im/src/syng_im/protocol/protocol_handler.cljs b/syng-im/src/syng_im/protocol/protocol_handler.cljs index 2e240cf910..69e889a717 100644 --- a/syng-im/src/syng_im/protocol/protocol_handler.cljs +++ b/syng-im/src/syng_im/protocol/protocol_handler.cljs @@ -17,10 +17,10 @@ (dispatch [:protocol-initialized identity])) :new-msg (let [{:keys [from to payload]} event] (dispatch [:received-msg (assoc payload :from from :to to)])) - ;:msg-acked (let [{:keys [msg-id]} event] - ; (add-to-chat "chat" ":" (str "Message " msg-id " was acked"))) - ;:delivery-failed (let [{:keys [msg-id]} event] - ; (add-to-chat "chat" ":" (str "Delivery of message " msg-id " failed"))) + :msg-acked (let [{:keys [msg-id from]} event] + (dispatch [:acked-msg from msg-id])) + :delivery-failed (let [{:keys [msg-id]} event] + (dispatch [:msg-delivery-failed msg-id])) ;:new-group-chat (let [{:keys [from group-id identities]} event] ; (set-group-id! group-id) ; (set-group-identities identities) diff --git a/syng-im/src/syng_im/resources.cljs b/syng-im/src/syng_im/resources.cljs index d7e11f0aea..19a0c2ecb7 100644 --- a/syng-im/src/syng_im/resources.cljs +++ b/syng-im/src/syng_im/resources.cljs @@ -6,6 +6,7 @@ (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 delivery-failed-icon (js/require "./images/deliveryfailed.png")) (def play (js/require "./images/play.png")) (def mic (js/require "./images/mic.png")) (def smile (js/require "./images/smile.png")) diff --git a/syng-im/src/syng_im/utils/listview.cljs b/syng-im/src/syng_im/utils/listview.cljs index 4908974276..b6bd9ec080 100644 --- a/syng-im/src/syng_im/utils/listview.cljs +++ b/syng-im/src/syng_im/utils/listview.cljs @@ -1,7 +1,13 @@ (ns syng-im.utils.listview - (:require-macros [natal-shell.data-source :refer [data-source clone-with-rows]])) + (:require-macros [natal-shell.data-source :refer [data-source clone-with-rows]]) + (:require [syng-im.components.realm])) (defn to-datasource [msgs] (-> (data-source {:rowHasChanged (fn [row1 row2] (not= row1 row2))}) + (clone-with-rows msgs))) + +(defn to-realm-datasource [msgs] + (-> (js/RealmReactNative.ListView.DataSource. (cljs.core/clj->js {:rowHasChanged (fn [row1 row2] + (not= row1 row2))})) (clone-with-rows msgs))) \ No newline at end of file From ab11da3c9051169fd5597fa0fd4f6cad38ccf7e8 Mon Sep 17 00:00:00 2001 From: michaelr Date: Mon, 28 Mar 2016 19:17:02 +0300 Subject: [PATCH 3/3] workaround for https://github.com/drapanjanas/re-natal/issues/32 Former-commit-id: 662c6c2eab9f86aa49d78f63199adb28d232a0f9 --- syng-im/src/syng_im/components/invertible_scroll_view.cljs | 2 +- syng-im/src/syng_im/components/react.cljs | 4 ++-- syng-im/src/syng_im/components/realm.cljs | 2 +- syng-im/src/syng_im/ios/core.cljs | 2 +- syng-im/src/syng_im/persistence/realm.cljs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/syng-im/src/syng_im/components/invertible_scroll_view.cljs b/syng-im/src/syng_im/components/invertible_scroll_view.cljs index 8611247c2d..fea247863c 100644 --- a/syng-im/src/syng_im/components/invertible_scroll_view.cljs +++ b/syng-im/src/syng_im/components/invertible_scroll_view.cljs @@ -1,6 +1,6 @@ (ns syng-im.components.invertible-scroll-view) -(set! js/InvertibleScrollView (js/require "react-native-invertible-scroll-view")) +(set! js/window.InvertibleScrollView (js/require "react-native-invertible-scroll-view")) (defn invertible-scroll-view [props] (js/React.createElement js/InvertibleScrollView diff --git a/syng-im/src/syng_im/components/react.cljs b/syng-im/src/syng_im/components/react.cljs index 0c524c4e80..204bb687e5 100644 --- a/syng-im/src/syng_im/components/react.cljs +++ b/syng-im/src/syng_im/components/react.cljs @@ -1,7 +1,7 @@ (ns syng-im.components.react (:require [reagent.core :as r])) -(set! js/React (js/require "react-native")) +(set! js/window.React (js/require "react-native")) (def app-registry (.-AppRegistry js/React)) (def navigator (r/adapt-react-class (.-Navigator js/React))) @@ -15,4 +15,4 @@ (def platform (.. js/React -Platform -OS)) -(def android? (= platform "android")) \ No newline at end of file +(def android? (= platform "android")) diff --git a/syng-im/src/syng_im/components/realm.cljs b/syng-im/src/syng_im/components/realm.cljs index a3e32f3a71..2cc38c7f37 100644 --- a/syng-im/src/syng_im/components/realm.cljs +++ b/syng-im/src/syng_im/components/realm.cljs @@ -1,7 +1,7 @@ (ns syng-im.components.realm (:require [reagent.core :as r])) -(set! js/RealmReactNative (js/require "realm/react-native")) +(set! js/window.RealmReactNative (js/require "realm/react-native")) (def list-view (r/adapt-react-class (.-ListView js/RealmReactNative))) diff --git a/syng-im/src/syng_im/ios/core.cljs b/syng-im/src/syng_im/ios/core.cljs index e3f3ac88e1..02147cce96 100644 --- a/syng-im/src/syng_im/ios/core.cljs +++ b/syng-im/src/syng_im/ios/core.cljs @@ -4,7 +4,7 @@ [syng-im.handlers] [syng-im.subs])) -(set! js/React (js/require "react-native")) +(set! js/window.React (js/require "react-native")) (def app-registry (.-AppRegistry js/React)) (def text (r/adapt-react-class (.-Text js/React))) diff --git a/syng-im/src/syng_im/persistence/realm.cljs b/syng-im/src/syng_im/persistence/realm.cljs index 51330b3bb9..6e4c38c7f8 100644 --- a/syng-im/src/syng_im/persistence/realm.cljs +++ b/syng-im/src/syng_im/persistence/realm.cljs @@ -4,7 +4,7 @@ [syng-im.utils.types :refer [to-string]]) (:refer-clojure :exclude [exists?])) -(set! js/Realm (js/require "realm")) +(set! js/window.Realm (js/require "realm")) (def opts {:schema [{:name "Contact" :properties {:phone-number "string"