diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000000..a9ce1369e6 --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["react-native"] +} diff --git a/.flowconfig b/.flowconfig index b69d071bc2..015e5545a6 100644 --- a/.flowconfig +++ b/.flowconfig @@ -24,11 +24,14 @@ .*/Libraries/react-native/ReactNative.js .*/node_modules/jest-runtime/build/__tests__/.* +; Ignore polyfills +.*/Libraries/polyfills/.* + [include] [libs] node_modules/react-native/Libraries/react-native/react-native-interface.js -node_modules/react-native/flow +node_modules/react-native/flow/ flow/ [options] @@ -46,13 +49,15 @@ module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|we suppress_type=$FlowIssue suppress_type=$FlowFixMe +suppress_type=$FlowFixMeProps +suppress_type=$FlowFixMeState suppress_type=$FixMe -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(30\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(30\\|1[0-9]\\|[1-2][0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy unsafe.enable_getters_and_setters=true [version] -^0.30.0 +^0.56.0 \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..d42ff18354 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.pbxproj -text diff --git a/.gitignore b/.gitignore index 3c7291450a..66b6702a19 100644 --- a/.gitignore +++ b/.gitignore @@ -86,3 +86,12 @@ ios/StatusIm.xcworkspace #python *.pyc *.cache + +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/ + +*/fastlane/report.xml +*/fastlane/Preview.html +*/fastlane/screenshots \ No newline at end of file diff --git a/.re-natal b/.re-natal index 6fe95ae0dd..dd0e3cd390 100644 --- a/.re-natal +++ b/.re-natal @@ -1,7 +1,16 @@ { "name": "StatusIm", "interface": "reagent", - "androidHost": "10.0.3.2", + "platforms": { + "ios": { + "host": "localhost", + "modules": [] + }, + "android": { + "host": "10.0.2.2", + "modules": [] + } + }, "modules": [ "react-native-contacts", "react-native-invertible-scroll-view", @@ -49,9 +58,8 @@ "imageDirs": [ "resources/images" ], - "iosHost": "localhost", "envRoots": { "dev": "env/dev", "prod": "env/prod" } -} \ No newline at end of file +} diff --git a/android/app/build.gradle b/android/app/build.gradle index 6ff5849c09..a753b24927 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -35,6 +35,13 @@ import com.android.build.OutputFile * // bundleInPaidRelease: true, * // bundleInBeta: true, * + * // whether to disable dev mode in custom build variants (by default only disabled in release) + * // for example: to disable dev mode in the staging build type (if configured) + * devDisabledInStaging: true, + * // The configuration property can be in the following formats + * // 'devDisabledIn${productFlavor}${buildType}' + * // 'devDisabledIn${buildType}' + * * // the root of your project, i.e. where "package.json" lives * root: "../../", * @@ -57,11 +64,18 @@ import com.android.build.OutputFile * // date; if you have any other folders that you want to ignore for performance reasons (gradle * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ * // for example, you might want to remove it from here. - * inputExcludes: ["android/**", "ios/**"] + * inputExcludes: ["android/**", "ios/**"], + * + * // override which node gets called and with what additional arguments + * nodeExecutableAndArgs: ["node"], + * + * // supply additional arguments to the packager + * extraPackagerArgs: [] * ] */ project.ext.react = [ - nodeExecutableAndArgs: ["node", "--max-old-space-size=4096"] + nodeExecutableAndArgs: ["node", "--max-old-space-size=4096"], + entryFile: "index.android.js" ] apply from: "../../node_modules/react-native/react.gradle" @@ -116,6 +130,15 @@ android { abiFilters "armeabi-v7a", "x86" } } + /** + * Fix for: (https://github.com/ReactiveX/RxJava/issues/4445) + * Execution failed for task ':app:transformResourcesWithMergeJavaResForDebug'. + * > com.android.build.api.transform.TransformException: com.android.builder.packaging.DuplicateFileException: + * Duplicate files copied in APK META-INF/rxjava.properties + */ + packagingOptions { + exclude 'META-INF/rxjava.properties' + } dexOptions { jumboMode true javaMaxHeapSize "4g" diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index 48361a9015..6e8516c8d6 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -50,6 +50,10 @@ -dontwarn com.facebook.react.** +# TextLayoutBuilder uses a non-public Android constructor within StaticLayout. +# See libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy for details. +-dontwarn android.text.StaticLayout + # okhttp -keepattributes Signature diff --git a/android/app/src/main/java/im/status/ethereum/MainActivity.java b/android/app/src/main/java/im/status/ethereum/MainActivity.java index 48cd31d6fa..bbe6875b90 100644 --- a/android/app/src/main/java/im/status/ethereum/MainActivity.java +++ b/android/app/src/main/java/im/status/ethereum/MainActivity.java @@ -14,7 +14,7 @@ import android.content.res.Configuration; import android.os.Bundle; import com.facebook.react.ReactActivity; -import com.cboy.rn.splashscreen.SplashScreen; +import org.devio.rn.splashscreen.SplashScreen; import com.testfairy.TestFairy; import java.util.Properties; diff --git a/android/app/src/main/java/im/status/ethereum/MainApplication.java b/android/app/src/main/java/im/status/ethereum/MainApplication.java index baeb662c6f..85df040481 100644 --- a/android/app/src/main/java/im/status/ethereum/MainApplication.java +++ b/android/app/src/main/java/im/status/ethereum/MainApplication.java @@ -4,7 +4,7 @@ import android.support.multidex.MultiDexApplication; import com.BV.LinearGradient.LinearGradientPackage; import com.aakashns.reactnativedialogs.ReactNativeDialogsPackage; import com.bitgo.randombytes.RandomBytesPackage; -import com.cboy.rn.splashscreen.SplashScreenReactPackage; +import org.devio.rn.splashscreen.SplashScreenReactPackage; import com.centaurwarchief.smslistener.SmsListenerPackage; import com.facebook.react.ReactApplication; import com.horcrux.svg.SvgPackage; @@ -14,9 +14,10 @@ import com.lugg.ReactNativeConfig.ReactNativeConfigPackage; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; import com.facebook.react.shell.MainReactPackage; +import com.facebook.soloader.SoLoader; import com.github.alinz.reactnativewebviewbridge.WebViewBridgePackage; import com.github.yamill.orientation.OrientationPackage; -import com.i18n.reactnativei18n.ReactNativeI18n; +import com.AlexanderZaytsev.RNI18n.RNI18nPackage; import com.instabug.reactlibrary.RNInstabugReactnativePackage; import com.lwansbrough.RCTCamera.RCTCameraPackage; import com.oblador.vectoricons.VectorIconsPackage; @@ -68,7 +69,7 @@ public class MainApplication extends MultiDexApplication implements ReactApplica new RealmReactPackage(), new VectorIconsPackage(), new ReactNativeContacts(), - new ReactNativeI18n(), + new RNI18nPackage(), new RandomBytesPackage(), new LinearGradientPackage(), new RCTCameraPackage(), @@ -88,6 +89,11 @@ public class MainApplication extends MultiDexApplication implements ReactApplica return packages; } + + @Override + protected String getJSMainModuleName() { + return "index.android"; + } }; @Override @@ -95,4 +101,9 @@ public class MainApplication extends MultiDexApplication implements ReactApplica return mReactNativeHost; } + @Override + public void onCreate() { + super.onCreate(); + SoLoader.init(this, /* native exopackage */ false); + } } diff --git a/android/build.gradle b/android/build.gradle index 3474637c41..96ae5a1beb 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -23,4 +23,4 @@ allprojects { maven { url "http://139.162.11.12:8081/artifactory/libs-release-local" } maven { url "https://jitpack.io" } } -} +} \ No newline at end of file diff --git a/android/keystores/BUCK b/android/keystores/BUCK new file mode 100644 index 0000000000..88e4c31b28 --- /dev/null +++ b/android/keystores/BUCK @@ -0,0 +1,8 @@ +keystore( + name = "debug", + properties = "debug.keystore.properties", + store = "debug.keystore", + visibility = [ + "PUBLIC", + ], +) diff --git a/android/keystores/debug.keystore.properties b/android/keystores/debug.keystore.properties new file mode 100644 index 0000000000..121bfb49f0 --- /dev/null +++ b/android/keystores/debug.keystore.properties @@ -0,0 +1,4 @@ +key.store=debug.keystore +key.alias=androiddebugkey +key.store.password=android +key.alias.password=android diff --git a/android/settings.gradle b/android/settings.gradle index f40f38b6c1..2b9bdd65b5 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -49,4 +49,4 @@ include ':react-native-webview-bridge' project(':react-native-webview-bridge').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview-bridge/android') include ':react-native-config' -project(':react-native-config').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-config/android') +project(':react-native-config').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-config/android') \ No newline at end of file diff --git a/app.json b/app.json new file mode 100644 index 0000000000..c24d965dac --- /dev/null +++ b/app.json @@ -0,0 +1,4 @@ +{ + "name": "StatusIm", + "displayName": "StatusIm" +} \ No newline at end of file diff --git a/env/dev/env/android/main.cljs b/env/dev/env/android/main.cljs index 861b43b516..43df8cbd8e 100644 --- a/env/dev/env/android/main.cljs +++ b/env/dev/env/android/main.cljs @@ -1,18 +1,23 @@ (ns ^:figwheel-no-load env.android.main (:require [reagent.core :as r] - [re-frisk-remote.core :as rr] + [re-frame.core :as re-frame] [status-im.android.core :as core] [figwheel.client :as figwheel :include-macros true] + [re-frisk-remote.core :as rr] + [env.config :as conf] [status-im.utils.handlers :as utils.handlers])) (enable-console-print!) +(assert (exists? core/init) "Fatal Error - Your core.cljs file doesn't define an 'init' function!!! - Perhaps there was a compilation failure?") +(assert (exists? core/app-root) "Fatal Error - Your core.cljs file doesn't define an 'app-root' function!!! - Perhaps there was a compilation failure?") + (def cnt (r/atom 0)) (defn reloader [] @cnt [core/app-root]) (def root-el (r/as-element [reloader])) (figwheel/watch-and-reload - :websocket-url "ws://10.0.3.2:3449/figwheel-ws" + :websocket-url "ws://localhost:3449/figwheel-ws" :heads-up-display false :jsload-callback #(swap! cnt inc)) diff --git a/env/dev/env/config.cljs b/env/dev/env/config.cljs new file mode 100644 index 0000000000..e8c642bc44 --- /dev/null +++ b/env/dev/env/config.cljs @@ -0,0 +1,6 @@ +(ns env.config) + +(def figwheel-urls { + :ios "ws://localhost:3449/figwheel-ws" + :android "ws://10.0.2.2:3449/figwheel-ws" + }) \ No newline at end of file diff --git a/env/dev/env/ios/main.cljs b/env/dev/env/ios/main.cljs index 5162d1f08a..93343c5fac 100644 --- a/env/dev/env/ios/main.cljs +++ b/env/dev/env/ios/main.cljs @@ -13,10 +13,10 @@ (def root-el (r/as-element [reloader])) (figwheel/watch-and-reload - :websocket-url "ws://localhost:3449/figwheel-ws" - :heads-up-display false - :jsload-callback #(swap! cnt inc)) + :websocket-url "ws://localhost:3449/figwheel-ws" + :heads-up-display false + :jsload-callback #(swap! cnt inc)) (utils.handlers/add-pre-event-callback rr/pre-event-callback) -(rr/enable-re-frisk-remote! {:host "localhost:4567" :on-init core/init}) \ No newline at end of file +(rr/enable-re-frisk-remote! {:host "localhost:4567" :on-init core/init}) diff --git a/figwheel-bridge.js b/figwheel-bridge.js index fe7579ecff..6f4e285077 100644 --- a/figwheel-bridge.js +++ b/figwheel-bridge.js @@ -14,28 +14,58 @@ var config = { }; var React = require('react'); +var createReactClass = require('create-react-class'); var ReactNative = require('react-native'); var WebSocket = require('WebSocket'); var self; -var scriptQueue = []; -var serverHost = null; // will be set dynamically -var fileBasePath = null; // will be set dynamically var evaluate = eval; // This is needed, direct calls to eval does not work (RN packager???) var externalModules = {}; -var evalListeners = [ // Functions to be called after each js file is loaded and evaluated - function (url) { - if (url.indexOf('jsloader') > -1) { - shimJsLoader(); - } - }, - function (url) { - if (url.indexOf('/figwheel/client/socket') > -1) { - setCorrectWebSocketImpl(); - } - }]; +var evalListeners = {}; +var asyncImportChain = new Promise(function (succ,fail) {succ(true);}); + +function fireEvalListenters(url) { + Object.values(evalListeners).forEach(function (listener) { + listener(url) + }); +} + +function formatCompileError(msg) { + var errorStr = "Figwheel Compile Exception: " + var data = msg['exception-data']; + if(data['message']) { + errorStr += data['message'] + " "; + } + if(data['file']) { + errorStr += "in file " + data['file'] + " "; + } + if(data['line']) { + errorStr += "at line " + data['line']; + } + if(data['column']) { + errorStr += ", column " + data['column']; + } + return errorStr; +} + +/* This is simply demonstrating that we can receive and react to + * arbitrary messages from Figwheel this will enable creating a nicer + * feedback system in the Figwheel top level React component. + */ +function figwheelMessageHandler(msg) { + if(msg["msg-name"] == "compile-failed") { + console.warn(formatCompileError(msg)); + } +} + +function listenToFigwheelMessages() { + if(figwheel.client.add_json_message_watch) { + figwheel.client.add_json_message_watch("ReactNativeMessageIntercept", + figwheelMessageHandler); + } +} var figwheelApp = function (platform, devHost) { - return React.createClass({ + return createReactClass({ getInitialState: function () { return {loaded: false} }, @@ -50,11 +80,13 @@ var figwheelApp = function (platform, devHost) { } return this.state.root; }, + componentDidMount: function () { var app = this; if (typeof goog === "undefined") { loadApp(platform, devHost, function (appRoot) { - app.setState({root: appRoot, loaded: true}) + app.setState({root: appRoot, loaded: true}); + listenToFigwheelMessages(); }); } } @@ -67,50 +99,30 @@ function logDebug(msg) { } } -// evaluates js code ensuring proper ordering -function customEval(url, javascript, success, error) { - if (scriptQueue.length > 0) { - if (scriptQueue[0] === url) { - try { - evaluate(javascript); - logDebug('Evaluated: ' + url); - scriptQueue.shift(); - evalListeners.forEach(function (listener) { - listener(url) - }); - success(); - } catch (e) { - console.error(e); - error(); - } - } else { - setTimeout(function () { - customEval(url, javascript, success, error) - }, 5); - } - } else { - console.error('Something bad happened...'); - error() - } -} - var isChrome = function () { return typeof importScripts === "function" }; function asyncImportScripts(url, success, error) { logDebug('(asyncImportScripts) Importing: ' + url); - scriptQueue.push(url); - fetch(url) + asyncImportChain = + asyncImportChain + .then(function (v) {return fetch(url);}) .then(function (response) { - return response.text() + if(response.ok) + return response.text(); + throw new Error("Failed to Fetch: " + url + " - Perhaps your project was cleaned and you haven't recompiled?") }) .then(function (responseText) { - return customEval(url, responseText, success, error); + evaluate(responseText); + fireEvalListenters(url); + success(); + return true; }) - .catch(function (error) { - console.error(error); - return error(); + .catch(function (e) { + console.error(e); + error(); + return true; }); } @@ -118,9 +130,7 @@ function syncImportScripts(url, success, error) { try { importScripts(url); logDebug('Evaluated: ' + url); - evalListeners.forEach(function (listener) { - listener(url) - }); + fireEvalListenters(url); success(); } catch (e) { console.error(e); @@ -130,22 +140,14 @@ function syncImportScripts(url, success, error) { // Loads js file sync if possible or async. function importJs(src, success, error) { - if (typeof success !== 'function') { - success = function () { - }; - } - if (typeof error !== 'function') { - error = function () { - }; - } - - var file = fileBasePath + '/' + src; - - logDebug('(importJs) Importing: ' + file); + var noop = function(){}; + success = (typeof success == 'function') ? success : noop; + error = (typeof error == 'function') ? error : noop; + logDebug('(importJs) Importing: ' + src); if (isChrome()) { - syncImportScripts(serverBaseUrl("localhost") + '/' + file, success, error); + syncImportScripts(src, success, error); } else { - asyncImportScripts(serverBaseUrl(serverHost) + '/' + file, success, error); + asyncImportScripts(src, success, error); } } @@ -161,66 +163,56 @@ function interceptRequire() { }; } -function compileWarningsToYellowBox() { - var log = window.console.log; - var compileWarningRx = /Figwheel: Compile/; - var compileExceptionRx = /Figwheel: Compile Exception/; - var errorInFileRx = /Error on file/; - var isBuffering = false; - var compileExceptionBuffer = ""; - window.console.log = function (msg) { - log.apply(window.console, arguments); - if (compileExceptionRx.test(msg)) { // enter buffering mode to get all the messages for exception - isBuffering = true; - compileExceptionBuffer = msg + "\n"; - } else if (errorInFileRx.test(msg) && isBuffering) { // exit buffering mode and log buffered messages to YellowBox - isBuffering = false; - console.warn(compileExceptionBuffer + msg); - compileExceptionBuffer = ""; - } else if (isBuffering) { //log messages buffering mode - compileExceptionBuffer += msg + "\n"; - } else if (compileWarningRx.test(msg)) { - console.warn(msg); - } - }; -} - function serverBaseUrl(host) { return "http://" + host + ":" + config.serverPort } -function setCorrectWebSocketImpl() { - figwheel.client.socket.get_websocket_imp = function () { - return WebSocket; - }; +function isUnDefined(x) { + return typeof x == "undefined"; } +// unlikely to happen but it happened to me a couple of times so ... +function assertRootElExists(platform) { + var basicMessage = "ClojureScript project didn't compile, or didn't load correctly."; + if(isUnDefined(env)) { + throw new Error("Critical Error: env namespace not defined - " + basicMessage); + } else if(isUnDefined(env[platform])) { + throw new Error("Critical Error: env." + platform + " namespace not defined - " + basicMessage); + } else if(isUnDefined(env[platform].main)) { + throw new Error("Critical Error: env." + platform + ".main namespace not defined - " + basicMessage); + } else if(isUnDefined(env[platform].main.root_el)) { + throw new Error("Critical Error: env." + + platform + ".main namespace doesn't define a root-el which should hold the root react node of your app."); + } + } + function loadApp(platform, devHost, onLoadCb) { - serverHost = devHost; - fileBasePath = config.basePath + platform; + var fileBasePath = serverBaseUrl((isChrome() ? "localhost" : devHost)) + "/" + config.basePath + platform; // callback when app is ready to get the reloadable component - var mainJs = '/env/' + platform + '/main.js'; - evalListeners.push(function (url) { + var mainJs = `/env/${platform}/main.js`; + evalListeners.waitForFinalEval = function (url) { if (url.indexOf(mainJs) > -1) { + assertRootElExists(platform); onLoadCb(env[platform].main.root_el); console.info('Done loading Clojure app'); + delete evalListeners.waitForFinalEval; } - }); + }; if (typeof goog === "undefined") { console.info('Loading Closure base.'); interceptRequire(); - compileWarningsToYellowBox(); - importJs('goog/base.js', function () { - shimBaseGoog(); - importJs('cljs_deps.js'); - importJs('goog/deps.js', function () { + // need to know base path here + importJs(fileBasePath + '/goog/base.js', function () { + shimBaseGoog(fileBasePath); + importJs(fileBasePath + '/cljs_deps.js'); + importJs(fileBasePath + '/goog/deps.js', function () { // This is needed because of RN packager // seriously React packager? why. var googreq = goog.require; - googreq('figwheel.connect.build_' + platform); + googreq(`env.${platform}.main`); }); }); } @@ -236,10 +228,18 @@ function withModules(moduleById) { return self; } +function figwheelImportScript(uri, callback) { + importJs(uri.toString(), + function () {callback(true);}, + function () {callback(false);}) +} + // Goog fixes -function shimBaseGoog() { +function shimBaseGoog(basePath) { console.info('Shimming goog functions.'); - goog.basePath = 'goog/'; + goog.basePath = basePath + '/' + config.googBasePath; + goog.global.FIGWHEEL_WEBSOCKET_CLASS = WebSocket; + goog.global.FIGWHEEL_IMPORT_SCRIPT = figwheelImportScript; goog.writeScriptSrcNode = importJs; goog.writeScriptTag_ = function (src, optSourceText) { importJs(src); @@ -247,44 +247,6 @@ function shimBaseGoog() { }; } -// Figwheel fixes -// Used by figwheel - uses importScript to load JS rather than