Former-commit-id: bd2767bf17
This commit is contained in:
virvar 2016-04-04 18:37:37 +03:00
commit 95f2316380
39 changed files with 592 additions and 166 deletions

View File

@ -9,7 +9,11 @@
"realm",
"react-native-loading-spinner-overlay",
"react-native-i18n",
"realm/react-native"
"realm/react-native",
"react-native-action-button",
"react-native-vector-icons/Ionicons",
"react-native-circle-checkbox",
"react-native-randombytes"
],
"imageDirs": [
"images"

View File

@ -119,6 +119,9 @@ android {
}
dependencies {
compile project(':randombytes')
compile project(':realm')
compile project(':react-native-vector-icons')
compile fileTree(dir: "libs", include: ["*.jar"])
compile "com.android.support:appcompat-v7:23.0.1"
compile "com.facebook.react:react-native:+" // From node_modules
@ -127,6 +130,5 @@ dependencies {
// compile(name:'geth', ext:'aar')
compile(group: 'syng-im', name: 'android-geth', version: '1.4.0-201603131817-92d65cf', ext: 'aar')
compile project(":realm")
compile fileTree(dir: "node_modules/realm/android/libs", include: ["*.jar"])
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,12 +1,15 @@
package com.syngim;
import com.facebook.react.ReactActivity;
import io.realm.react.RealmReactPackage;
import com.oblador.vectoricons.VectorIconsPackage;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.rt2zz.reactnativecontacts.ReactNativeContacts;
import android.os.Bundle;
import android.os.Environment;
import com.github.ethereum.go_ethereum.cmd.Geth;
import com.bitgo.randombytes.RandomBytesPackage;
import java.util.Arrays;
import java.util.List;
@ -71,9 +74,11 @@ public class MainActivity extends ReactActivity {
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new RealmReactPackage(),
new VectorIconsPackage(),
new ReactNativeContacts(),
new ReactNativeI18n(),
new RealmReactPackage()
new RandomBytesPackage()
);
}
}

View File

@ -8,5 +8,9 @@ project(':react-native-contacts').projectDir = new File(settingsDir, '../node_mo
include ':react-native-i18n'
project(':react-native-i18n').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-i18n/android')
// realm dependency
include ':react-native-vector-icons'
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
include ':realm'
project(':realm').projectDir = new File(rootProject.projectDir, '../node_modules/realm/android')
include ':randombytes'
project(':randombytes').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-randombytes/app')

BIN
syng-im/images/v.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -5,7 +5,6 @@
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; };
00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; };
@ -25,6 +24,19 @@
E343FE8E1C96F54100C01DB5 /* libRNI18n.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E343FE8B1C96F4E200C01DB5 /* libRNI18n.a */; };
E343FE8F1C96F54A00C01DB5 /* libRCTContacts.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E343FE841C96F4DA00C01DB5 /* libRCTContacts.a */; };
E343FE911C971D4200C01DB5 /* Geth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E343FE901C971D4200C01DB5 /* Geth.framework */; };
5F301739E81C4A7B92E80915 /* libRNVectorIcons.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1429DFB76DE749D59880DD64 /* libRNVectorIcons.a */; };
2171A55FE83747678065F72A /* Entypo.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 93C8E68B46DA4C0A98C39F9D /* Entypo.ttf */; };
BDF23426F8854AC2A0416A03 /* EvilIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 38E7B297EE0748008056796A /* EvilIcons.ttf */; };
221509FA6D5443C0B5215C52 /* FontAwesome.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 2CF9FE7E98A3454D980A0BED /* FontAwesome.ttf */; };
A33AC3EAFBC04C0B93BD2EC4 /* Foundation.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 5A831378BBA14830A9540B8C /* Foundation.ttf */; };
1A4121618C94428C9F0DF1FE /* Ionicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 5F085711AC4A4BA3887A4655 /* Ionicons.ttf */; };
59FCD4F0208949BE80CC72BF /* MaterialIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0063E967269B49EE80DC3A77 /* MaterialIcons.ttf */; };
4452A93C123F4392A99A7719 /* Octicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A74F40BECFF8439FB1F41D8A /* Octicons.ttf */; };
1AE91A3E2C1A43EFA90C1CD7 /* Zocial.ttf in Resources */ = {isa = PBXBuildFile; fileRef = BCEC948817B84597AC493C0B /* Zocial.ttf */; };
34535F62E77D4A75A25E2981 /* libRNRandomBytes.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2925028DE4684DDAAA7389D7 /* libRNRandomBytes.a */; };
97BD21CBE96A4223B253A1E2 /* libRealmReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2B29C182EE0942C889049F91 /* libRealmReact.a */; };
22802A9C745148D885F0AA01 /* libc++.tbd in Resources */ = {isa = PBXBuildFile; fileRef = 8003209F13C24D35AABD3933 /* libc++.tbd */; };
50EE6BCC6F1A48BBA1BDB893 /* libz.tbd in Resources */ = {isa = PBXBuildFile; fileRef = 313A77B1D7804DBDBE6FF6F5 /* libz.tbd */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -153,6 +165,22 @@
E343FE7F1C96F4DA00C01DB5 /* RCTContacts.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTContacts.xcodeproj; path = "../node_modules/react-native-contacts/ios/RCTContacts.xcodeproj"; sourceTree = "<group>"; };
E343FE851C96F4E200C01DB5 /* RNI18n.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNI18n.xcodeproj; path = "../node_modules/react-native-i18n/RNI18n.xcodeproj"; sourceTree = "<group>"; };
E343FE901C971D4200C01DB5 /* Geth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Geth.framework; path = target/Frameworks/Geth.framework; sourceTree = "<group>"; };
F04672B9D3824B768FCF6F71 /* RNVectorIcons.xcodeproj */ = {isa = PBXFileReference; name = "RNVectorIcons.xcodeproj"; path = "../node_modules/react-native-vector-icons/RNVectorIcons.xcodeproj"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; };
1429DFB76DE749D59880DD64 /* libRNVectorIcons.a */ = {isa = PBXFileReference; name = "libRNVectorIcons.a"; path = "libRNVectorIcons.a"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; };
93C8E68B46DA4C0A98C39F9D /* Entypo.ttf */ = {isa = PBXFileReference; name = "Entypo.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/Entypo.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
38E7B297EE0748008056796A /* EvilIcons.ttf */ = {isa = PBXFileReference; name = "EvilIcons.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
2CF9FE7E98A3454D980A0BED /* FontAwesome.ttf */ = {isa = PBXFileReference; name = "FontAwesome.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
5A831378BBA14830A9540B8C /* Foundation.ttf */ = {isa = PBXFileReference; name = "Foundation.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/Foundation.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
5F085711AC4A4BA3887A4655 /* Ionicons.ttf */ = {isa = PBXFileReference; name = "Ionicons.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
0063E967269B49EE80DC3A77 /* MaterialIcons.ttf */ = {isa = PBXFileReference; name = "MaterialIcons.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
A74F40BECFF8439FB1F41D8A /* Octicons.ttf */ = {isa = PBXFileReference; name = "Octicons.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
BCEC948817B84597AC493C0B /* Zocial.ttf */ = {isa = PBXFileReference; name = "Zocial.ttf"; path = "../node_modules/react-native-vector-icons/Fonts/Zocial.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
06F26555B4054594966B6B08 /* RNRandomBytes.xcodeproj */ = {isa = PBXFileReference; name = "RNRandomBytes.xcodeproj"; path = "../node_modules/react-native-randombytes/RNRandomBytes.xcodeproj"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; };
2925028DE4684DDAAA7389D7 /* libRNRandomBytes.a */ = {isa = PBXFileReference; name = "libRNRandomBytes.a"; path = "libRNRandomBytes.a"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; };
0D4A52AE301842E1B2533BD3 /* RealmReact.xcodeproj */ = {isa = PBXFileReference; name = "RealmReact.xcodeproj"; path = "../node_modules/realm/react-native/ios/RealmReact.xcodeproj"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; };
2B29C182EE0942C889049F91 /* libRealmReact.a */ = {isa = PBXFileReference; name = "libRealmReact.a"; path = "libRealmReact.a"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; };
8003209F13C24D35AABD3933 /* libc++.tbd */ = {isa = PBXFileReference; name = "libc++.tbd"; path = "usr/lib/libc++.tbd"; sourceTree = SDKROOT; fileEncoding = undefined; lastKnownFileType = sourcecode.text-based-dylib-definition; explicitFileType = undefined; includeInIndex = 0; };
313A77B1D7804DBDBE6FF6F5 /* libz.tbd */ = {isa = PBXFileReference; name = "libz.tbd"; path = "usr/lib/libz.tbd"; sourceTree = SDKROOT; fileEncoding = undefined; lastKnownFileType = sourcecode.text-based-dylib-definition; explicitFileType = undefined; includeInIndex = 0; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -180,6 +208,11 @@
832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */,
00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */,
139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */,
5F301739E81C4A7B92E80915 /* libRNVectorIcons.a in Frameworks */,
34535F62E77D4A75A25E2981 /* libRNRandomBytes.a in Frameworks */,
97BD21CBE96A4223B253A1E2 /* libRealmReact.a in Frameworks */,
22802A9C745148D885F0AA01 /* libc++.tbd in Resources */,
50EE6BCC6F1A48BBA1BDB893 /* libz.tbd in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -304,6 +337,9 @@
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */,
00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */,
139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */,
F04672B9D3824B768FCF6F71 /* RNVectorIcons.xcodeproj */,
06F26555B4054594966B6B08 /* RNRandomBytes.xcodeproj */,
0D4A52AE301842E1B2533BD3 /* RealmReact.xcodeproj */,
);
name = Libraries;
sourceTree = "<group>";
@ -324,6 +360,7 @@
832341AE1AAA6A7D00B99B32 /* Libraries */,
00E356EF1AD99517003FC87E /* MessengerTests */,
83CBBA001A601CBA00E9B192 /* Products */,
A72CA0FB822D450E98649ACB /* Resources */,
);
indentWidth = 2;
sourceTree = "<group>";
@ -359,10 +396,28 @@
isa = PBXGroup;
children = (
E343FE901C971D4200C01DB5 /* Geth.framework */,
8003209F13C24D35AABD3933 /* libc++.tbd */,
313A77B1D7804DBDBE6FF6F5 /* libz.tbd */,
);
name = Frameworks;
sourceTree = "<group>";
};
A72CA0FB822D450E98649ACB /* Resources */ = {
isa = PBXGroup;
children = (
93C8E68B46DA4C0A98C39F9D /* Entypo.ttf */,
38E7B297EE0748008056796A /* EvilIcons.ttf */,
2CF9FE7E98A3454D980A0BED /* FontAwesome.ttf */,
5A831378BBA14830A9540B8C /* Foundation.ttf */,
5F085711AC4A4BA3887A4655 /* Ionicons.ttf */,
0063E967269B49EE80DC3A77 /* MaterialIcons.ttf */,
A74F40BECFF8439FB1F41D8A /* Octicons.ttf */,
BCEC948817B84597AC493C0B /* Zocial.ttf */,
);
name = Resources;
path = "";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -408,7 +463,7 @@
83CBB9F71A601CBA00E9B192 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0720;
LastUpgradeCheck = 720;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
00E356ED1AD99517003FC87E = {
@ -594,6 +649,14 @@
files = (
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */,
2171A55FE83747678065F72A /* Entypo.ttf in Resources */,
BDF23426F8854AC2A0416A03 /* EvilIcons.ttf in Resources */,
221509FA6D5443C0B5215C52 /* FontAwesome.ttf in Resources */,
A33AC3EAFBC04C0B93BD2EC4 /* Foundation.ttf in Resources */,
1A4121618C94428C9F0DF1FE /* Ionicons.ttf in Resources */,
59FCD4F0208949BE80CC72BF /* MaterialIcons.ttf in Resources */,
4452A93C123F4392A99A7719 /* Octicons.ttf in Resources */,
1AE91A3E2C1A43EFA90C1CD7 /* Zocial.ttf in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -675,6 +738,11 @@
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Messenger.app/Messenger";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
);
};
name = Debug;
};
@ -693,6 +761,11 @@
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Messenger.app/Messenger";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
"\"$(SRCROOT)/$(TARGET_NAME)\"",
);
};
name = Release;
};
@ -709,6 +782,9 @@
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
"$(SRCROOT)/../node_modules/react-native-randombytes",
"$(SRCROOT)/../node_modules/realm/src/**",
);
INFOPLIST_FILE = Messenger/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@ -730,6 +806,9 @@
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
"$(SRCROOT)/../node_modules/react-native-randombytes",
"$(SRCROOT)/../node_modules/realm/src/**",
);
INFOPLIST_FILE = Messenger/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
@ -778,6 +857,9 @@
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
"$(SRCROOT)/../node_modules/react-native-randombytes",
"$(SRCROOT)/../node_modules/realm/src/**",
);
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
MTL_ENABLE_DEBUG_INFO = YES;
@ -818,6 +900,9 @@
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../node_modules/react-native-vector-icons/RNVectorIconsManager",
"$(SRCROOT)/../node_modules/react-native-randombytes",
"$(SRCROOT)/../node_modules/realm/src/**",
);
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
MTL_ENABLE_DEBUG_INFO = NO;

View File

@ -8,10 +8,14 @@
"dependencies": {
"awesome-phonenumber": "^1.0.12",
"react-native": "^0.22.0",
"react-native-action-button": "^1.1.3",
"react-native-circle-checkbox": "^0.1.3",
"react-native-contacts": "^0.2.1",
"react-native-i18n": "0.0.8",
"react-native-invertible-scroll-view": "^0.2.0",
"react-native-loading-spinner-overlay": "0.0.6",
"react-native-randombytes": "^2.0.0",
"react-native-vector-icons": "^1.3.3",
"realm": "^0.11.0"
}
}

View File

@ -12,8 +12,10 @@
[syng-im.components.sign-up :refer [sign-up-view]]
[syng-im.components.sign-up-confirm :refer [sign-up-confirm-view]]
[syng-im.components.chats.chats-list :refer [chats-list]]
[syng-im.components.chats.new-group :refer [new-group]]
[syng-im.utils.logging :as log]
[syng-im.navigation :as nav]))
[syng-im.navigation :as nav]
[syng-im.utils.encryption]))
(def back-button-handler (cljs/atom {:nav nil
:handler nil}))
@ -32,8 +34,7 @@
(add-event-listener "hardwareBackPress" new-listener)))))
(defn app-root []
[navigator {:initial-route (clj->js {:view-id ;:chat-list
:chat})
[navigator {:initial-route (clj->js {:view-id :chat-list})
:render-scene (fn [route nav]
(log/debug "route" route)
(when true ;; nav/*nav-render*
@ -42,6 +43,7 @@
(init-back-button-handler! nav)
(case view-id
: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}])
:sign-up (r/as-element [sign-up-view {:navigator nav}])
@ -49,6 +51,7 @@
(defn init []
(dispatch-sync [:initialize-db])
(dispatch [:initialize-crypt])
(dispatch [:initialize-protocol])
(dispatch [:load-user-phone-number])
(dispatch [:load-syng-contacts])

View File

@ -0,0 +1,7 @@
(ns syng-im.components.action-button
(:require [reagent.core :as r]))
(set! js/window.ActionButton (js/require "react-native-action-button"))
(def action-button (r/adapt-react-class (.-default js/ActionButton)))
(def action-button-item (r/adapt-react-class (.. js/ActionButton -default -Item)))

View File

@ -19,7 +19,8 @@
(defn chat [{:keys [navigator]}]
(let [messages (subscribe [:get-chat-messages])]
(let [messages (subscribe [:get-chat-messages])
chat (subscribe [:get-current-chat])]
(fn []
(let [msgs @messages
_ (log/debug "messages=" msgs)
@ -29,7 +30,8 @@
(when android?
;; TODO add IOS version
[toolbar-android {:logo res/logo-icon
:title "Chat name"
:title (or (@chat :name)
"Chat name")
:titleColor "#4A5258"
:subtitle "Last seen just now"
:subtitleColor "#AAB2B2"

View File

@ -12,7 +12,7 @@
(defn plain-message-input-view []
(let [text (r/atom "")
chat-id (subscribe [:get-current-chat-id])]
chat (subscribe [:get-current-chat])]
(dispatch [:generate-suggestions @text])
(fn []
[view {:style {:flexDirection "column"}}
@ -42,7 +42,10 @@
(reset! text new-text)
(r/flush))
:onSubmitEditing (fn [e]
(dispatch [:send-chat-msg @chat-id @text])
(let [{:keys [group-chat chat-id]} (:chat-id @chat)]
(if group-chat
(dispatch [:send-group-chat-msg chat-id @text])
(dispatch [:send-chat-msg chat-id @text])))
(reset! text nil))}]
[image {:source res/smile
:style {:marginTop 11

View File

@ -13,7 +13,10 @@
[syng-im.resources :as res]
[syng-im.utils.listview :refer [to-realm-datasource]]
[reagent.core :as r]
[syng-im.components.chats.chat-list-item :refer [chat-list-item]]))
[syng-im.components.chats.chat-list-item :refer [chat-list-item]]
[syng-im.components.action-button :refer [action-button
action-button-item]]
[syng-im.components.icons.ionicons :refer [icon]]))
(defn chats-list [{:keys [navigator]}]
@ -40,4 +43,21 @@
[list-view {:dataSource datasource
:renderRow (fn [row section-id row-id]
(r/as-element [chat-list-item row navigator]))
:style {:backgroundColor "white"}}]]))))
:style {:backgroundColor "white"}}]
[action-button {:buttonColor "rgba(231,76,60,1)"}
[action-button-item {:title "New Chat"
:buttonColor "#9b59b6"
:onPress (fn []
(dispatch [:show-contacts navigator]))}
[icon {:name "android-create"
:style {:fontSize 20
:height 22
:color "white"}}]]
[action-button-item {:title "New Group Chat"
:buttonColor "#1abc9c"
:onPress (fn []
(dispatch [:show-group-new navigator]))}
[icon {:name "person-stalker"
:style {:fontSize 20
:height 22
:color "white"}}]]]]))))

View File

@ -0,0 +1,52 @@
(ns syng-im.components.chats.new-group
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.resources :as res]
[syng-im.components.react :refer [view toolbar-android android? text-input]]
[syng-im.components.realm :refer [list-view]]
[syng-im.utils.listview :refer [to-realm-datasource]]
[syng-im.components.chats.new-group-contact :refer [new-group-contact]]
[reagent.core :as r]
[syng-im.navigation :refer [nav-pop]]))
(defn new-group [{:keys [navigator]}]
(let [contacts (subscribe [:all-contacts])
group-name (atom nil)]
(fn []
(let [contacts-ds (to-realm-datasource @contacts)]
[view {:style {:flex 1
:backgroundColor "white"}}
(when android?
;; TODO add IOS version
[toolbar-android {:logo res/logo-icon
:title "New Group Chat"
:titleColor "#4A5258"
:style {:backgroundColor "white"
:height 56
:elevation 2}
:actions [{:title "Create"
:icon res/v
:show "always"}]
:onActionSelected (fn [position]
(dispatch [:create-new-group @group-name navigator]))
:navIcon res/nav-back-icon
:onIconClicked (fn []
(nav-pop navigator))}])
[text-input {:underlineColorAndroid "#9CBFC0"
:style {:marginLeft 5
:marginRight 5
:fontSize 14
:fontFamily "Avenir-Roman"
:color "#9CBFC0"}
:autoFocus true
:placeholder "Group Name"
:value @group-name
:onChangeText (fn [new-text]
(reset! group-name new-text)
(r/flush))
:onSubmitEditing (fn [e]
;(dispatch [:send-chat-msg @chat-id @text])
(reset! group-name nil))}]
[list-view {:dataSource contacts-ds
:renderRow (fn [row section-id row-id]
(r/as-element [new-group-contact (js->clj row :keywordize-keys true) navigator]))
:style {:backgroundColor "white"}}]]))))

View File

@ -0,0 +1,24 @@
(ns syng-im.components.chats.new-group-contact
(:require [syng-im.resources :as res]
[re-frame.core :refer [subscribe dispatch dispatch-sync]]
[syng-im.components.react :refer [view]]
[syng-im.components.contact-list.contact-inner :refer [contact-inner-view]]
[syng-im.components.item-checkbox :refer [item-checkbox]]
[syng-im.utils.logging :as log]
[reagent.core :as r]))
(defn new-group-contact [{:keys [whisper-identity] :as contact} navigator]
(let [checked (r/atom false)]
(fn []
[view {:style {:flexDirection "row"
:marginTop 5
:marginBottom 5
:paddingLeft 15
:paddingRight 15
:height 75}}
[item-checkbox {:onToggle (fn [checked?]
(reset! checked checked?)
(dispatch [:select-for-new-group whisper-identity checked?]))
:checked @checked
:size 30}]
[contact-inner-view contact]])))

View File

@ -1,103 +1,14 @@
(ns syng-im.components.contact-list.contact
(:require [syng-im.components.react :refer [view text image touchable-highlight]]
[syng-im.resources :as res]
[syng-im.navigation :as nav]))
[syng-im.navigation :as nav]
[syng-im.components.contact-list.contact-inner :refer [contact-inner-view]]))
(defn show-chat [navigator whisper-identity]
(nav/nav-push navigator {:view-id :chat}))
(defn contact-view [{:keys [navigator contact]}]
(let [{:keys [name photo-path delivery-status datetime new-messages-count
online whisper-identity]} contact]
(let [{:keys [whisper-identity]} contact]
[touchable-highlight {:onPress (fn []
(show-chat navigator 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]])]]]))
[contact-inner-view contact]]))

View File

@ -0,0 +1,104 @@
(ns syng-im.components.contact-list.contact-inner
(:require [clojure.string :as s]
[syng-im.components.react :refer [view image text]]
[syng-im.resources :as res]))
(defn contact-photo [{:keys [photo-path]}]
[view {:width 54
:height 54
:borderRadius 50
:backgroundColor "#FFFFFF"
:elevation 6}
[image {:source (if (s/blank? photo-path)
res/user-no-photo
{:uri photo-path})
:style {:borderWidth 2
:borderColor "#FFFFFF"
:borderRadius 50
:width 54
:height 54
:position "absolute"}}]])
(defn contact-online [{:keys [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}}]]))
(defn contact-inner-view [{:keys [name photo-path delivery-status datetime new-messages-count
online whisper-identity]}]
[view {:style {:flexDirection "row"
:marginTop 5
:marginBottom 5
:paddingLeft 15
:paddingRight 15
:height 75}}
[view {:width 54
:height 54}
;;; photo
[contact-photo {:photo-path photo-path}]
;;; online
[contact-online {:online online}]]
[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]])]])

View File

@ -26,7 +26,7 @@
[view {:style {:flex 1
:backgroundColor "white"}}
[toolbar-android {:logo res/logo-icon
:title "Chats"
:title "Contacts"
:titleColor "#4A5258"
:style {:backgroundColor "white"
:height 56

View File

@ -0,0 +1,6 @@
(ns syng-im.components.icons.ionicons
(:require [reagent.core :as r]))
(set! js/window.Ionicons (js/require "react-native-vector-icons/Ionicons"))
(def icon (r/adapt-react-class js/Ionicons))

View File

@ -0,0 +1,7 @@
(ns syng-im.components.item-checkbox
(:require [reagent.core :as r]))
(set! js/window.ItemCheckbox (js/require "react-native-circle-checkbox"))
(def item-checkbox (r/adapt-react-class js/ItemCheckbox))

View File

@ -12,7 +12,8 @@
:suggestions []
:command nil}
:chats {}
:chats-updated-signal 0})
:chats-updated-signal 0
:new-group #{}})
(def protocol-initialized-path [:protocol-initialized])
@ -23,3 +24,5 @@
(def updated-chats-signal-path [:chats-updated-signal])
(defn updated-chat-signal-path [chat-id]
[:chats chat-id :chat-updated-signal])
(def new-group-path [:new-group])

View File

@ -1,6 +1,6 @@
(ns syng-im.handlers
(:require
[re-frame.core :refer [register-handler after]]
[re-frame.core :refer [register-handler after dispatch]]
[schema.core :as s :include-macros true]
[syng-im.db :refer [app-db schema]]
[syng-im.protocol.api :refer [init-protocol]]
@ -20,11 +20,15 @@
[syng-im.models.chats :refer [create-chat]]
[syng-im.models.chat :refer [signal-chat-updated
set-current-chat-id]]
set-current-chat-id
update-new-group-selection
clear-new-group
new-group-selection]]
[syng-im.utils.logging :as log]
[syng-im.protocol.api :as api]
[syng-im.constants :refer [text-content-type]]
[syng-im.navigation :refer [nav-push]]))
[syng-im.navigation :refer [nav-push]]
[syng-im.utils.crypt :refer [gen-random-bytes]]))
;; -- Middleware ------------------------------------------------------------
;;
@ -50,6 +54,33 @@
(fn [db [_ value]]
(assoc db :loading value)))
(register-handler :initialize-crypt
(fn [db _]
(log/debug "initializing crypt")
(gen-random-bytes 1024 (fn [{:keys [error buffer]}]
(if error
(do
(log/error "Failed to generate random bytes to initialize sjcl crypto")
(dispatch [:notify-user {:type :error
:error error}]))
(do
(->> (.toString buffer "hex")
(.toBits (.. js/ecc -sjcl -codec -hex))
(.addEntropy (.. js/ecc -sjcl -random)))
(dispatch [:crypt-initialized])))))
db))
(register-handler :crypt-initialized
(fn [db _]
(log/debug "crypt initialized")
db))
(register-handler :navigate-to
(fn [db [action navigator route]]
(log/debug action route)
(nav-push navigator route)
db))
;; -- Protocol --------------------------------------------------------------
(register-handler :initialize-protocol
@ -69,9 +100,15 @@
(log/debug action "msg" msg)
(save-message chat-id msg)
(-> db
(create-chat chat-id [chat-id])
(create-chat chat-id [chat-id] false)
(signal-chat-updated chat-id))))
(register-handler :group-received-msg
(fn [db [action {chat-id :group-id :as msg}]]
(log/debug action "msg" msg)
(save-message chat-id msg)
(signal-chat-updated db chat-id)))
(register-handler :acked-msg
(fn [db [_ from msg-id]]
(update-message! {:msg-id msg-id
@ -122,6 +159,21 @@
(save-message chat-id msg)
(signal-chat-updated db chat-id))))
(register-handler :send-group-chat-msg
(fn [db [action chat-id text]]
(log/debug action "chat-id" chat-id "text" text)
(let [{msg-id :msg-id
{from :from} :msg} (api/send-group-user-msg {:group-id chat-id
:content text})
msg {:msg-id msg-id
:from from
:to nil
:content text
:content-type text-content-type
:outgoing true}]
(save-message chat-id msg)
(signal-chat-updated db chat-id))))
;; -- User data --------------------------------------------------------------
(register-handler :set-user-phone-number
@ -165,8 +217,9 @@
(register-handler :show-chat
(fn [db [action chat-id navigator]]
(log/debug action "chat-id" chat-id)
(nav-push navigator {:view-id :chat})
(set-current-chat-id db chat-id)))
(let [db (set-current-chat-id db chat-id)]
(dispatch [:navigate-to navigator {:view-id :chat}])
db)))
(register-handler :set-sign-up-chat
(fn [db [_]]
@ -183,3 +236,39 @@
(register-handler :set-input-command
(fn [db [_ command]]
(commands-service/set-input-command db command)))
(register-handler :show-contacts
(fn [db [action navigator]]
(log/debug action)
(nav-push navigator {:view-id :contact-list})
db))
(register-handler :show-group-new
(fn [db [action navigator]]
(log/debug action)
(nav-push navigator {:view-id :new-group})
(clear-new-group db)))
(register-handler :select-for-new-group
(fn [db [action identity add?]]
(log/debug action identity add?)
(update-new-group-selection db identity add?)))
(register-handler :create-new-group
(fn [db [action group-name navigator]]
(log/debug action)
(let [identities (-> (new-group-selection db)
(vec))
group-id (api/start-group-chat identities group-name)
db (create-chat db group-id identities true group-name)]
(dispatch [:show-chat group-id navigator])
db)))
(register-handler :group-chat-invite-received
(fn [db [action from group-id identities group-name]]
(log/debug action from group-id identities)
(create-chat db group-id identities true group-name)))
(comment
)

View File

@ -17,8 +17,22 @@
(->> (db/updated-chat-signal-path chat-id)
(get-in db)))
(defn update-new-group-selection [db identity add?]
(update-in db db/new-group-path (fn [new-group]
(if add?
(conj new-group identity)
(disj new-group identity)))))
(defn new-group-selection [db]
(get-in db db/new-group-path))
(defn clear-new-group [db]
(assoc-in db db/new-group-path #{}))
(comment
(swap! re-frame.db/app-db (fn [db]
(signal-chat-updated db "0x0479a5ed1f38cadfad1db6cd56c4b659b0ebe052bbe9efa950f6660058519fa4ca6be2dda66afa80de96ab00eb97a2605d5267a1e8f4c2a166ab551f6826608cdd")))
(current-chat-id @re-frame.db/app-db)
)

View File

@ -30,9 +30,9 @@
chat-id))
(defn create-chat
([db chat-id identities]
(create-chat db chat-id identities nil))
([db chat-id identities chat-name]
([db chat-id identities group-chat?]
(create-chat db chat-id identities group-chat? nil))
([db chat-id identities group-chat? chat-name]
(if (r/exists? :chats :chat-id chat-id)
db
(let [chat-name (or chat-name
@ -40,8 +40,7 @@
_ (log/debug "creating chat" chat-name)]
(r/write
(fn []
(let [group-chat? (> (count identities) 1)
contacts (mapv (fn [ident]
(let [contacts (mapv (fn [ident]
{:identity ident}) identities)]
(r/create :chats {:chat-id chat-id
:name chat-name
@ -54,8 +53,12 @@
(-> (r/get-all :chats)
(r/sorted :timestamp :desc)))
(defn chat-by-id [chat-id]
(-> (r/get-by-field :chats :chat-id chat-id)
(r/single-cljs)))
(comment
(chat-by-id "1459693168208-31d4942e-ca3b-5c03-a397-cd7a29f777d4")
(chats-list)
(r/delete (chats-list))
@ -65,4 +68,11 @@
(create-chat "0x0479a5ed1f38cadfad1db6cd56c4b659b0ebe052bbe9efa950f6660058519fa4ca6be2dda66afa80de96ab00eb97a2605d5267a1e8f4c2a166ab551f6826608cdd"
["0x0479a5ed1f38cadfad1db6cd56c4b659b0ebe052bbe9efa950f6660058519fa4ca6be2dda66afa80de96ab00eb97a2605d5267a1e8f4c2a166ab551f6826608cdd"])
(+ 1 1)
(swap! re-frame.db/app-db (fn [db]
(create-chat db "A group chat")))
)

View File

@ -2,7 +2,8 @@
(: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]))
[syng-im.persistence.realm :as realm]
[syng-im.persistence.realm :as r]))
;; TODO see https://github.com/rt2zz/react-native-contacts/issues/45
(def fake-phone-contacts? true)
@ -82,3 +83,28 @@
(defn save-syng-contacts [syng-contacts]
(add-contacts syng-contacts))
;;;;;;;;;;;;;;;;;;;;----------------------------------------------
(defn contacts-list []
(-> (r/get-all :contacts)
(r/sorted :name :asc)))
(comment
(r/write #(create-contact {:phone-number "0543072333"
:whisper-identity "0x04b6552945c18ebca487c8a829365d3812a246e1cd00d775f47248a21a61bad0912409b8bd18dc0604d1df494cea001cce85098906df231d2a431067734ecc5a21"
:name "Mr. Bean"
:photo-path ""}))
(r/write #(create-contact {:phone-number "0544828649"
:whisper-identity "0x043d9e25c6cf89941849cf5e4439084a93002f757cfd49fef411d4793d888b408dfa5bc54ac5989f65da8d764dc332f06b646f3cfae194a0801f6090b272a0c56e"
:name "Mr. Batman"
:photo-path ""}))
(contacts-list)
(:new-group @re-frame.db/app-db)
)

View File

@ -5,7 +5,8 @@
[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}]
(defn save-message [chat-id {:keys [from to msg-id content content-type outgoing] :or {outgoing false
to nil} :as msg}]
(when-not (r/exists? :msgs :msg-id msg-id)
(r/write
(fn []

View File

@ -23,7 +23,8 @@
:primaryKey :msg-id
:properties {:msg-id "string"
:from "string"
:to "string"
:to {:type "string"
:optional true}
:content "string" ;; TODO make it ArrayBuffer
:content-type "string"
:timestamp "int"
@ -119,8 +120,22 @@
(comment
(write #(.create realm "msgs" (clj->js {:msg-id "1459175391577-a2185a35-5c49-5a6b-9c08-6eb5b87ceb7f"
(write #(.create realm "msgs" (clj->js {:msg-id "12"
:content "sdfd"
:from "sdfsd"
:chat-id "56"
:content-type "fg"
:timestamp 2
:outgoing true
:to "sfs"
:delivery-status "seen"}) true))
(.addListener realm "change" (fn [& args]
(log/debug args)))
;realm.addListener('change', () => {
; // Update UI
; ...
; });
)

View File

@ -21,15 +21,15 @@
(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)
; (add-to-chat "group-chat" ":" (str "Received group chat invitation from " from " for group-id: " group-id)))
:new-group-chat (let [{:keys [from group-id identities group-name]} event]
(dispatch [:group-chat-invite-received from group-id identities group-name]))
:new-group-msg (let [{from :from
group-id :group-id
payload :payload} event]
(dispatch [:group-received-msg (assoc payload :from from
:group-id group-id)]))
;:group-chat-invite-acked (let [{:keys [from group-id]} event]
; (add-to-chat "group-chat" ":" (str "Received ACK for group chat invitation from " from " for group-id: " group-id)))
;:new-group-msg (let [{from :from
; {content :content} :payload} event]
; (add-to-chat "group-chat" from content))
;:group-new-participant (let [{:keys [group-id identity from]} event]
; (add-to-chat "group-chat" ":" (str (shorten from) " added " (shorten identity) " to group chat"))
; (add-identity-to-group-list identity))

View File

@ -12,3 +12,4 @@
(def mic (js/require "./images/mic.png"))
(def smile (js/require "./images/smile.png"))
(def att (js/require "./images/att.png"))
(def v (js/require "./images/v.png"))

View File

@ -5,8 +5,10 @@
[syng-im.models.chat :refer [current-chat-id
chat-updated?]]
[syng-im.models.chats :refer [chats-list
chats-updated?]]
[syng-im.models.messages :refer [get-messages]]))
chats-updated?
chat-by-id]]
[syng-im.models.messages :refer [get-messages]]
[syng-im.models.contacts :refer [contacts-list]]))
;; -- Chat --------------------------------------------------------------
@ -43,6 +45,14 @@
(let [_ @chats-updated]
(chats-list))))))
(register-sub :get-current-chat
(fn [db _]
(let [current-chat-id (-> (current-chat-id @db)
(reaction))]
(-> (when-let [chat-id @current-chat-id]
(chat-by-id chat-id))
(reaction)))))
;; -- User data --------------------------------------------------------------
(register-sub
@ -74,3 +84,9 @@
(fn [db _]
(reaction
(get @db :contacts))))
(register-sub :all-contacts
(fn [db _]
(reaction
(contacts-list))))

View File

@ -2,6 +2,8 @@
(:require [goog.crypt :refer [byteArrayToHex]])
(:import goog.crypt.Sha256))
(set! js/window.RnRandomBytes (js/require "react-native-randombytes"))
(def sha-256 (Sha256.))
(defn bytes-to-str [arr]
@ -15,3 +17,9 @@
(.update sha-256 s)
(-> (.digest sha-256)
byteArrayToHex))
(defn gen-random-bytes [length cb]
(.randomBytes js/window.RnRandomBytes length (fn [& [err buf]]
(if err
(cb {:error err})
(cb {:buffer buf})))))

View File

@ -2,12 +2,12 @@
(:require-macros [natal-shell.data-source :refer [data-source clone-with-rows]])
(:require [syng-im.components.realm]))
(defn to-datasource [msgs]
(defn to-datasource [items]
(-> (data-source {:rowHasChanged (fn [row1 row2]
(not= row1 row2))})
(clone-with-rows msgs)))
(clone-with-rows items)))
(defn to-realm-datasource [msgs]
(defn to-realm-datasource [items]
(-> (js/RealmReactNative.ListView.DataSource. (cljs.core/clj->js {:rowHasChanged (fn [row1 row2]
(not= row1 row2))}))
(clone-with-rows msgs)))
(clone-with-rows items)))