From 57a98b12e253beaf5ec89b7cb099020456e49f9a Mon Sep 17 00:00:00 2001 From: Andy Brummer Date: Sat, 4 Jun 2016 12:04:45 -0500 Subject: [PATCH 01/46] Added basic functionality, add, remove and edit buildings. Sync is enabled for the databases, but sync events are not hooked up to the GUI. --- .buckconfig | 6 + .flowconfig | 94 ++++++++++ .gitignore | 41 +++++ .watchmanconfig | 1 + android/.gitignore | 8 + android/app/.gitignore | 1 + android/app/BUCK | 66 ++++++++ android/app/build.gradle | 150 ++++++++++++++++ android/app/proguard-rules.pro | 17 ++ .../blocpoweraudit/ApplicationTest.java | 13 ++ android/app/src/main/AndroidManifest.xml | 27 +++ .../src/main/assets/fonts/MaterialIcons.ttf | Bin 0 -> 128180 bytes .../blocpoweraudit/MainActivity.java | 110 ++++++++++++ .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3418 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2206 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4842 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7718 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 10486 bytes android/app/src/main/res/values/colors.xml | 6 + android/app/src/main/res/values/strings.xml | 3 + android/app/src/main/res/values/styles.xml | 11 ++ .../blocpoweraudit/ExampleUnitTest.java | 15 ++ android/build.gradle | 31 ++++ android/gradle.properties | 20 +++ android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + android/gradlew | 160 ++++++++++++++++++ android/gradlew.bat | 90 ++++++++++ android/keystores/BUCK | 8 + android/keystores/debug.keystore.properties | 4 + android/settings.gradle | 7 + app/actions/allActions.js | 7 + app/assets/styles.js | 74 ++++++++ app/components/Toolbar.js | 40 +++++ app/routes.js | 21 +++ app/scenes/buildingEdit.js | 48 ++++++ app/scenes/buildingList.js | 84 +++++++++ app/stores/allStores.js | 7 + app/stores/buildingStore.js | 84 +++++++++ app/stores/navStore.js | 34 ++++ app/utils/utils.js | 17 ++ index.android.js | 72 ++++++++ package.json | 19 +++ sync.json | 5 + tsconfig.json | 8 + 45 files changed, 1415 insertions(+) create mode 100644 .buckconfig create mode 100644 .flowconfig create mode 100644 .gitignore create mode 100644 .watchmanconfig create mode 100644 android/.gitignore create mode 100644 android/app/.gitignore create mode 100644 android/app/BUCK create mode 100644 android/app/build.gradle create mode 100644 android/app/proguard-rules.pro create mode 100644 android/app/src/androidTest/java/com/blocpower/blocpoweraudit/ApplicationTest.java create mode 100644 android/app/src/main/AndroidManifest.xml create mode 100644 android/app/src/main/assets/fonts/MaterialIcons.ttf create mode 100644 android/app/src/main/java/com/blocpower/blocpoweraudit/MainActivity.java create mode 100644 android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 android/app/src/main/res/values/colors.xml create mode 100644 android/app/src/main/res/values/strings.xml create mode 100644 android/app/src/main/res/values/styles.xml create mode 100644 android/app/src/test/java/com/blocpower/blocpoweraudit/ExampleUnitTest.java create mode 100644 android/build.gradle create mode 100644 android/gradle.properties create mode 100644 android/gradle/wrapper/gradle-wrapper.jar create mode 100644 android/gradle/wrapper/gradle-wrapper.properties create mode 100644 android/gradlew create mode 100644 android/gradlew.bat create mode 100644 android/keystores/BUCK create mode 100644 android/keystores/debug.keystore.properties create mode 100644 android/settings.gradle create mode 100644 app/actions/allActions.js create mode 100644 app/assets/styles.js create mode 100644 app/components/Toolbar.js create mode 100644 app/routes.js create mode 100644 app/scenes/buildingEdit.js create mode 100644 app/scenes/buildingList.js create mode 100644 app/stores/allStores.js create mode 100644 app/stores/buildingStore.js create mode 100644 app/stores/navStore.js create mode 100644 app/utils/utils.js create mode 100644 index.android.js create mode 100644 package.json create mode 100644 sync.json create mode 100644 tsconfig.json diff --git a/.buckconfig b/.buckconfig new file mode 100644 index 0000000..934256c --- /dev/null +++ b/.buckconfig @@ -0,0 +1,6 @@ + +[android] + target = Google Inc.:Google APIs:23 + +[maven_repositories] + central = https://repo1.maven.org/maven2 diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 0000000..8e838d9 --- /dev/null +++ b/.flowconfig @@ -0,0 +1,94 @@ +[ignore] + +# We fork some components by platform. +.*/*.web.js +.*/*.android.js + +# Some modules have their own node_modules with overlap +.*/node_modules/node-haste/.* + +# Ugh +.*/node_modules/babel.* +.*/node_modules/babylon.* +.*/node_modules/invariant.* + +# Ignore react and fbjs where there are overlaps, but don't ignore +# anything that react-native relies on +.*/node_modules/fbjs/lib/Map.js +.*/node_modules/fbjs/lib/ErrorUtils.js + +# Flow has a built-in definition for the 'react' module which we prefer to use +# over the currently-untyped source +.*/node_modules/react/react.js +.*/node_modules/react/lib/React.js +.*/node_modules/react/lib/ReactDOM.js + +.*/__mocks__/.* +.*/__tests__/.* + +.*/commoner/test/source/widget/share.js + +# Ignore commoner tests +.*/node_modules/commoner/test/.* + +# See https://github.com/facebook/flow/issues/442 +.*/react-tools/node_modules/commoner/lib/reader.js + +# Ignore jest +.*/node_modules/jest-cli/.* + +# Ignore Website +.*/website/.* + +# Ignore generators +.*/local-cli/generator.* + +# Ignore BUCK generated folders +.*\.buckd/ + +.*/node_modules/is-my-json-valid/test/.*\.json +.*/node_modules/iconv-lite/encodings/tables/.*\.json +.*/node_modules/y18n/test/.*\.json +.*/node_modules/spdx-license-ids/spdx-license-ids.json +.*/node_modules/spdx-exceptions/index.json +.*/node_modules/resolve/test/subdirs/node_modules/a/b/c/x.json +.*/node_modules/resolve/lib/core.json +.*/node_modules/jsonparse/samplejson/.*\.json +.*/node_modules/json5/test/.*\.json +.*/node_modules/ua-parser-js/test/.*\.json +.*/node_modules/builtin-modules/builtin-modules.json +.*/node_modules/binary-extensions/binary-extensions.json +.*/node_modules/url-regex/tlds.json +.*/node_modules/joi/.*\.json +.*/node_modules/isemail/.*\.json +.*/node_modules/tr46/.*\.json + + +[include] + +[libs] +node_modules/react-native/Libraries/react-native/react-native-interface.js +node_modules/react-native/flow +flow/ + +[options] +module.system=haste + +esproposal.class_static_fields=enable +esproposal.class_instance_fields=enable + +munge_underscores=true + +module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' +module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' + +suppress_type=$FlowIssue +suppress_type=$FlowFixMe +suppress_type=$FixMe + +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-4]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-4]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy + +[version] +0.24.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ecc30e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +project.xcworkspace + +# Android/IJ +# +.idea +.gradle +local.properties + +# node.js +# +node_modules/ +npm-debug.log + +# BUCK +buck-out/ +\.buckd/ +android/app/libs +android/keystores/debug.keystore +.vscode/ diff --git a/.watchmanconfig b/.watchmanconfig new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.watchmanconfig @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..c6cbe56 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/android/app/.gitignore b/android/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/android/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/android/app/BUCK b/android/app/BUCK new file mode 100644 index 0000000..2aee001 --- /dev/null +++ b/android/app/BUCK @@ -0,0 +1,66 @@ +import re + +# To learn about Buck see [Docs](https://buckbuild.com/). +# To run your application with Buck: +# - install Buck +# - `npm start` - to start the packager +# - `cd android` +# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US` +# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck +# - `buck install -r android/app` - compile, install and run application +# + +lib_deps = [] +for jarfile in glob(['libs/*.jar']): + name = 'jars__' + re.sub(r'^.*/([^/]+)\.jar$', r'\1', jarfile) + lib_deps.append(':' + name) + prebuilt_jar( + name = name, + binary_jar = jarfile, + ) + +for aarfile in glob(['libs/*.aar']): + name = 'aars__' + re.sub(r'^.*/([^/]+)\.aar$', r'\1', aarfile) + lib_deps.append(':' + name) + android_prebuilt_aar( + name = name, + aar = aarfile, + ) + +android_library( + name = 'all-libs', + exported_deps = lib_deps +) + +android_library( + name = 'app-code', + srcs = glob([ + 'src/main/java/**/*.java', + ]), + deps = [ + ':all-libs', + ':build_config', + ':res', + ], +) + +android_build_config( + name = 'build_config', + package = 'com.test', +) + +android_resource( + name = 'res', + res = 'src/main/res', + package = 'com.test', +) + +android_binary( + name = 'app', + package_type = 'debug', + manifest = 'src/main/AndroidManifest.xml', + keystore = '//android/keystores:debug', + deps = [ + ':app-code', + ], +) diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000..df41592 --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,150 @@ +apply plugin: 'com.android.application' + +import com.android.build.OutputFile + +/** + * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets + * and bundleReleaseJsAndAssets). + * These basically call `react-native bundle` with the correct arguments during the Android build + * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the + * bundle directly from the development server. Below you can see all the possible configurations + * and their defaults. If you decide to add a configuration block, make sure to add it before the + * `apply from: "../../node_modules/react-native/react.gradle"` line. + * + * project.ext.react = [ + * // the name of the generated asset file containing your JS bundle + * bundleAssetName: "index.android.bundle", + * + * // the entry file for bundle generation + * entryFile: "index.android.js", + * + * // whether to bundle JS and assets in debug mode + * bundleInDebug: false, + * + * // whether to bundle JS and assets in release mode + * bundleInRelease: true, + * + * // whether to bundle JS and assets in another build variant (if configured). + * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants + * // The configuration property can be in the following formats + * // 'bundleIn${productFlavor}${buildType}' + * // 'bundleIn${buildType}' + * // bundleInFreeDebug: true, + * // bundleInPaidRelease: true, + * // bundleInBeta: true, + * + * // the root of your project, i.e. where "package.json" lives + * root: "../../", + * + * // where to put the JS bundle asset in debug mode + * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", + * + * // where to put the JS bundle asset in release mode + * jsBundleDirRelease: "$buildDir/intermediates/assets/release", + * + * // where to put drawable resources / React Native assets, e.g. the ones you use via + * // require('./image.png')), in debug mode + * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", + * + * // where to put drawable resources / React Native assets, e.g. the ones you use via + * // require('./image.png')), in release mode + * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", + * + * // by default the gradle tasks are skipped if none of the JS files or assets change; this means + * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to + * // 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/**"] + * ] + */ + +apply from: "../../node_modules/react-native/react.gradle" + +/** + * Set this to true to create two separate APKs instead of one: + * - An APK that only works on ARM devices + * - An APK that only works on x86 devices + * The advantage is the size of the APK is reduced by about 4MB. + * Upload all the APKs to the Play Store and people will download + * the correct one based on the CPU architecture of their device. + */ +def enableSeparateBuildPerCPUArchitecture = false + +/** + * Run Proguard to shrink the Java bytecode in release builds. + */ +def enableProguardInReleaseBuilds = false + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.2" + + defaultConfig { + applicationId "com.blocpower.blocpoweraudit" + minSdkVersion 16 + targetSdkVersion 22 + versionCode 1 + versionName "1.0" + ndk { + abiFilters "armeabi-v7a", "x86" + } + } + splits { + abi { + reset() + enable enableSeparateBuildPerCPUArchitecture + universalApk false // If true, also generate a universal APK + include "armeabi-v7a", "x86" + } + } + buildTypes { + release { + minifyEnabled enableProguardInReleaseBuilds + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + debug { + debuggable true + } + } + // applicationVariants are e.g. debug, release + applicationVariants.all { variant -> + variant.outputs.each { output -> + // For each separate APK per architecture, set a unique version code as described here: + // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits + def versionCodes = ["armeabi-v7a":1, "x86":2] + def abi = output.getFilter(OutputFile.ABI) + if (abi != null) { // null for the universal-debug, universal-release variants + output.versionCodeOverride = + versionCodes.get(abi) * 1048576 + defaultConfig.versionCode + } + } + } +// workaround for "duplicate files during packaging of APK" issue +// see https://groups.google.com/d/msg/adt-dev/bl5Rc4Szpzg/wC8cylTWuIEJ + packagingOptions { + exclude 'META-INF/ASL2.0' + exclude 'META-INF/LICENSE' + exclude 'META-INF/NOTICE' + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + testCompile 'junit:junit:4.12' + compile 'com.android.support:appcompat-v7:23.4.0' + + compile 'com.couchbase.lite:couchbase-lite-android:+' + compile 'com.couchbase.lite:couchbase-lite-java-listener:+' + compile 'com.couchbase.lite:couchbase-lite-java-javascript:+' + + compile "com.facebook.react:react-native:+" // From node_modules + compile project(':react-native-couchbase-lite') +} + +// Run this once to be able to run the application with BUCK +// puts all compile dependencies into folder libs for BUCK to use +task copyDownloadableDepsToLibs(type: Copy) { + from configurations.compile + into 'libs' +} diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..fac1251 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\Users\andy\AppData\Local\Android\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/android/app/src/androidTest/java/com/blocpower/blocpoweraudit/ApplicationTest.java b/android/app/src/androidTest/java/com/blocpower/blocpoweraudit/ApplicationTest.java new file mode 100644 index 0000000..1b7441c --- /dev/null +++ b/android/app/src/androidTest/java/com/blocpower/blocpoweraudit/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.blocpower.blocpoweraudit; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..6048b48 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + > + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/assets/fonts/MaterialIcons.ttf b/android/app/src/main/assets/fonts/MaterialIcons.ttf new file mode 100644 index 0000000000000000000000000000000000000000..7015564ad166a3e9d88c82f17829f0cc01ebe29a GIT binary patch literal 128180 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 ohyj3!`#ee%L;DTjx@8!5k5~VH0QmdE^#A|> literal 0 HcmV?d00001 diff --git a/android/app/src/main/java/com/blocpower/blocpoweraudit/MainActivity.java b/android/app/src/main/java/com/blocpower/blocpoweraudit/MainActivity.java new file mode 100644 index 0000000..9e3f645 --- /dev/null +++ b/android/app/src/main/java/com/blocpower/blocpoweraudit/MainActivity.java @@ -0,0 +1,110 @@ +package com.blocpower.blocpoweraudit; + +import android.os.Bundle; + +import com.facebook.react.ReactActivity; +import com.facebook.react.ReactPackage; +import com.facebook.react.shell.MainReactPackage; + +import com.couchbase.lite.Manager; +import com.couchbase.lite.View; +import com.couchbase.lite.android.AndroidContext; +import com.couchbase.lite.javascript.JavaScriptViewCompiler; +import com.couchbase.lite.listener.Credentials; +import com.couchbase.lite.listener.LiteListener; +import com.couchbase.lite.util.Log; + +import java.util.Arrays; +import java.util.List; + +import me.fraserxu.rncouchbaselite.ReactCBLiteManager; + + +public class MainActivity extends ReactActivity { + private final String TAG = "TodoLite"; + private static final int DEFAULT_LISTEN_PORT = 5984; + private int listenPort; + private Credentials allowedCredentials; + + /** + * Returns the name of the main component registered from JavaScript. + * This is used to schedule rendering of the component. + */ + @Override + protected String getMainComponentName() { + return "audit"; + } + + /** + * Returns whether dev mode should be enabled. + * This enables e.g. the dev menu. + */ + @Override + protected boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + /** + * A list of packages used by the app. If the app uses additional views + * or modules besides the default ones, add more packages here. + */ + @Override + protected List getPackages() { + return Arrays.asList( + new MainReactPackage(), + new ReactCBLiteManager() + ); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + initCBLite(); + } + + private void initCBLite() { + try { + + // 2 + allowedCredentials = new Credentials("", ""); + + // 3 + View.setCompiler(new JavaScriptViewCompiler()); + + // 4 + AndroidContext context = new AndroidContext(this); + Manager.enableLogging(Log.TAG, Log.VERBOSE); + Manager.enableLogging(Log.TAG_SYNC, Log.VERBOSE); + Manager.enableLogging(Log.TAG_QUERY, Log.VERBOSE); + Manager.enableLogging(Log.TAG_VIEW, Log.VERBOSE); + Manager.enableLogging(Log.TAG_CHANGE_TRACKER, Log.VERBOSE); + Manager.enableLogging(Log.TAG_BLOB_STORE, Log.VERBOSE); + Manager.enableLogging(Log.TAG_DATABASE, Log.VERBOSE); + Manager.enableLogging(Log.TAG_LISTENER, Log.VERBOSE); + Manager.enableLogging(Log.TAG_MULTI_STREAM_WRITER, Log.VERBOSE); + Manager.enableLogging(Log.TAG_REMOTE_REQUEST, Log.VERBOSE); + Manager.enableLogging(Log.TAG_ROUTER, Log.VERBOSE); + Manager manager = new Manager(context, Manager.DEFAULT_OPTIONS); + + // 5 + listenPort = startCBLListener(DEFAULT_LISTEN_PORT, manager, allowedCredentials); + + Log.i(TAG, "initCBLite() completed successfully with: " + String.format( + "http://%s:%s@localhost:%d/", + allowedCredentials.getLogin(), + allowedCredentials.getPassword(), + listenPort)); + + } catch (final Exception e) { + e.printStackTrace(); + } + } + + private int startCBLListener(int listenPort, Manager manager, Credentials allowedCredentials) { + LiteListener listener = new LiteListener(manager, listenPort, allowedCredentials); + int boundPort = listener.getListenPort(); + Thread thread = new Thread(listener); + thread.start(); + return boundPort; + } +} diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..cde69bcccec65160d92116f20ffce4fce0b5245c GIT binary patch literal 3418 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..bfa42f0e7b91d006d22352c9ff2f134e504e3c1d GIT binary patch literal 4842 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..324e72cdd7480cb983fa1bcc7ce686e51ef87fe7 GIT binary patch literal 7718 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..aee44e138434630332d88b1680f33c4b24c70ab3 GIT binary patch literal 10486 zcmai4byOU|lb&5k+^GN3bv-?^>(QkVinb zlU9`mfQEQnq$S4VGrg6fmMQ=QFarQQ0ss(?uiys&;LQU7M-~7engIZmZaH5x#UC3m z-zvYBd&I}<`b3rPHj1tDgVv1x| zQss$ELI?W?E(!7PKk$lm@;7PwPX3o43{Ccd9@_BUsL4kQzSMa&=g{>4wj9#)9wgYw;=H@gH9KK{s?Be8N1_8W< z1Rh%Lm&PAfyYb*rGB%E#3q+}riOBB~+@@X<`9mgIiAex!QP8vg-XT>=+N&y*jC-f< zGihyr7XAly+G)|_e)qA?rnKZGG(x?=lLM7nrPk&93@5eX#7I_$g8kMX`0h=}l`HH) z=bpOkBCx=z*-fyr{yp7A9F=%o*qm93t_#tB2lAM@O{fX9ju%X#0~)nRUMvrXClh9w ze8|a0|0}JJg(_@$2wItI?LUY{zF78o(P2BR7;aC^@(jOp{8RE%U3m>MV5%Lu*46b@ zw*c?Nweu!TULS~}*9mi!ejNfNa=`po1*!jiYK)osxi%b59(thEyUZ>#lX@uEXSb_x?3)0kvB?8*TAh)7}IbzSm}5Ia;_?10{}M; z7vq-OS;Ayk8%_c-gg1Ee0FsrRU5phNs#H9Lp!1t+hwyK~9W0bWCxuG$LM~wQuumEw z=fbBD@sQE%1^j z`T@`PZLRVyWjX@*tjc7r;w$H~aW&7vu?|war?84^sg!{J*RH|mhq?KTsCVQBC1~fR z>99jeR=g-Q2b=d;pKwzXwYjrG>?pd3tFSsHN4in{usYLdK;01X2BdRLFI`cuB9yI) zI_ZX?7_(bz`MX2@^mCknx7 z*f}KV@}TBBc}CXMR8T_5yInD3p`KrNROSA;HoJJtlNG3weri%utO$eeY0 z+w-NEn;(;UCBk=OM$f%=%ma24wV7$idelqyNWI>sz1>BlGwr_3UugqVjY+UYyi9P) zxCB?&rPUetoZN?|*D%=hOOJ_${JU3GRjppY%&8Ws^G6>iokr^Bmv1&*@#2#5mXu05 zhPVXaQ`qe5i0lP-1^XL45x`ertKU5d-8b_?*1+tSU!qCeqD9gZP_>ZLq9p)RKtV(B zOh&^x>gV^eqb&c~Oi0|HgGG|gjpbR`9aRdZhOimvS2Y3e?eCFiw+L#_mi9j z;nU}gih+zTn{nv_|L}IllD1Dr3~@yitI}+4C&+;SR+cEfelqJ?eUjZ%&Qz)W8S750 z+vG8Lvo}xXz2C}S-m|9*uE?NWQWT#W+p@$DkH8wVn#=gLKa13M!Yva9qsfE(5Z#0V`A0pN)Ok zP*Eq0(~e$~m@iej0#Av_z703y-7|W6`UuGDS8fpy2rUgINZs#`33@@0(S%~%XUO5G zscEp&x^dU`8syC67USOswNLq>Z_}q#gLh2x`zR)0wvor72-IW@oDpnT0x zWn%LZ_yvR*7geY6<}MC~SViD+4`S9XC|L}N0ANpsUU;50sAjL zb5h>&s<-wcdf2>}P91QgeAu~ZnB7;;FkfKJp^8ne8!-`jK0+O(^`s~#RE0@)=IWiQ z@(vh6D^4jN5ih;*c4J48FMC9MwoN(cXk1Wiq55Vi-^X#p8R_(!y81}YDdMefwdl2F zNA0n}-!P4!FaCe-jnf{^I#?5W=%9T1C|$ z`+tq*x!rEx)Bkv-eO9$mWML9_yId)A_OltKIH-X=0eJ`Opqqj&s^T;PLIZXJ!pEi!=3ZLHPGi*~?<(L&m6;{M(636VC<08tan>&c6fW z%KEuUN9x|i7Wc^-0l&Vf20kI~_XfD4hEac=&}5n&MoYL`Xsx=1po#V*6wUpwB@pu* z*@2n|zglL~zr$9&uOd9_%)GWk&0UN`<&GAm8=Ba-@MT&TH*`NHlt+CMi2Ag;LgGpm zm+ybGL-!1Z$kBYk66=39zAsErw1}|-l1npj-?3g1LE#PXU%%_{8kO=5!W!6pQ?z&i zc_MuV(xKMXSA0ga@IsiwYspm&d4|n@L_zji`zUWxsM}|=@R}BFfT2P!uJcrQf81WG z;7~y_$uMK=ih(2hrfqIGOzb(81e}^7h$dQ*w9&zG_k*kV{ml>Dkn2!p9tb_+Sa82P zf!TC+{4a(i^7UC$53;w?sleb~lFWqeCjv5msi}#JQ!wJtA>=k~`WL0M{^a9PG3%vT z6x=jB0{7wX7$gs%H}xJ&s+hHnzrl#L*=KB8OZd%sPoxKs(`;%|I$(^;nFYa4Cg|3D zmbQ)m6I_Y@t)A~{YBRo!2sYI^n!q)$tPp|m&n1BkYVmX22Z+nY#4N{Bb0!Ko=DOhh z8)8*=>e(W&-%LSWUN;u45Wex{{R747!a~45S>12$wNc{9N95&r%gU+b#-B7PcF%`_ zbDPAsmvpVBsQpf}s{igh23+1)`QSj71!|zjij@kvxgob&J{E97Lwu==Z)RY-lujF1 zts{7+jfS(K5+clZ(CY~%ks(F!=cb)YtqEu(dp_7=A?O!zz8KONrrma{eU-54%}Dm| zMb0!-=YUH?S7JzBX|TVr;=fB(8}a+Mcip|v&=pAeFMCaHj_Nkl!sWeZSb#k<%oczm z#`lGsgJHo7RywsRYYQs4O`J_C=fARQ$)B1peZk)|&ULCaa#RJ45lrml54sxO!CCv< zACe-^PSoZc!)x$#iZa*NuMlS%Jd!_x9|UdgLzlGyF0cI$EUFG4O;L+8*+s;KNL-ld z?R+O)guOt(>{+*e-+_A{1MBbRn&>53j=33ngVZ*A9^^??x8!ww@-m%DVVPmliJh;B zA?gVg!0|Rs7)?hBD^!lSxbI8;-8Q65B4DKw29-K9_w0glvBA&vz=a(hBCWqSnbKS0 zUg%$!iEY%1jOqivHBW;uSX*e&(J!Yr7cborEc&_4TQAAt(Hs@99pynWwVQc-PD)!b zEAfVEq-cX>10nj+=mUt(v;j?>9`bLJayfOcTYEOojVJwg!qg=XHGMAonnJPa; zUJ!+pYTulTHW%^S;&|h~V3suNSc{q3^zg~L0z(5QQ;Fz}<5*7QiE`G{EY!_Bq6Tf3 z#Y6<%5EL^6+vT44<%^2!TOb&Drb?#eUqR@vqcvAd=l_6n*oWcLU38eLio z&XA9a$>+}PoZ&n7&1;j$MfqAp&SK~ziPsl|%{|CWXWM9wxyVKXe0%lk}rDC8g z8X@%6X|;SG;muLTK4d!cPgVxqjvaX=-$(Q65p5S*rI%=0cH7U(J{e1RPLJ7=nOmA) zMlRB`!r37ZXhzV+&X?quSyu}sbAn^a+S992*Te=%QW1izNzH-(Fc!u`0^%jIwx-q{ zjJ$P>vDS90xVX3yM??JQE(8|%*Ent^LOWJSOM1DpOGR5rG_7xH(O_SiI zQPhe?AtaSr$aWQDFB=s4vG}6A7sKS9#`*O?Gvb$VpNFveZ{M$e6gN?k zBAf6x8lMv8irB7O2F*?SxjQ+G9(Zzcf(-v6B#Che%7km*jk@ z)2}#vcILe$u75B8OqP#aD^OyEpX+8%bA;T*9+xPtBOA56r>VBH?W|l@4D*s*oHF7b zKiEI(=9Q&zzKDNu(c_-(iYp|O=RX90e|T*1D)Vi}F|XXxwzlFY%vI5oyr@gp+zfor zE{L0=4=<&pTg$Vb2&yaL(=zg-A=-V)<6G@}QKeym;mw^FzryGI(YX6E{x5!pKKNFb zX2wUTC}&?H`qv0{Ouyp!O!9>BD+&bp+x5*hFxlEJ|Jlx!dC36CiNWcOOOUw5NPT2n zckQz+nHS7$v`1`e33@@emu_-PmpnE%>A~wldBhO+8|uKd(CXF1LguU>p-iuo+6+#A(zwt<~}iz8;e zi$`F>cJ*M;o0PM7dMP=uB26set3i}BC!lE@>Gk`4oZQIG&&(O{wh_khwAz^jz zLMdgg*JfCk1{LlNW)C?WLX_!#5OsEIb3ZPWV7*KBWoBhmt&{(fw|eI)9LZTDrF;Cm zrRI0DXcArT*)L<`{Gy!R-`j)ca2)6Ks~48Jcl^Qg{XgWYyo6RpJj`Aq>-T>){#|lR zRPY`?<2vJ#s7v8mNz1zwnz@<9ofov5TnYTqj(PJN^Hv0N1N6rZY2Q2ixJ9IY`5B)j z?o!|2DLA8bc-{QD-^}@UP_JB`BjVr};f3o#5P`$++U2>eVvNM%RKxPV7J0hzme%(z zR7M~;#x=}vL&%^k)1dkFp)ApEinI%CXma_IcfN1= zghNTqbv$mD$mXwAWysU;hUAFR0^jhAYjE}TV=j$O0>v_@{)|7er^HCFN$j4D(Rxa+ zr>@Me?gS|zVlda*cn+sM7^g8|~YJlBlxK`p<| zo$B!mr$%Z4An3pBbh@BK4Hi-E7l^3GMOiG?^~~z1Oxn$0PAR&}&*9D$O)(_>aB04e z*{ihG%K2UZE9c%O@J$1R+qtuhVW+Li7>Bw~LBLxQ_2GJ6dWmr`sMzGzRfiKQrm?9I zR~`S8uz0=lw5lTY3!?lQ|2LJNx(Ly%0Hkj_Q0C+f8>^@`ot4vM)#Bo9*u)9;#4lPQ zkD$dnQJ;T3;cR_9pRiRuc^MkgYiS>6*;09uV{z*IYw3#i;TH$m(R{*3w>BS-cM7T<{u?6<8}o91iDU^B)<6wJwL{eG{=U+MNz z>#f)F`15Bnp|A(04!41E4ixt89MvouKW88SEk-A`6{3;V9M)Ips3VNFol3u5WiBmL ze0Uor5Z+x~NDGz=5gd!i#D5L)gN!7;`5bPc*8~;4hQOzIJ_RM07TD_cA!r1XISg_x z%9r&%6tsJq$>~|UQ1|7AZe{Oeu!2V&rjYX=>T-qb@S?3(7FC=Z^XOYf24G=+FJR;^ z&+s!YCtoncOWkA~zS!&wfYTiV$WJeR&@pINr7!v$Vw3}H92S?Mj>$ckH9eSoqhxli^L9 zl6?;LH$mT|@_S}#35}P!_7@h%=&u7n2PH0zl8K6L4SX!;*Nkxnnt~qhgVoG_|@w$t9uwee?p`9loMG zr|Qqo!ws?ZaVp;+zT!zH^@xtf^zzvEF*EJK-3hdBe&e4hTya+V7cwy9k?-&u+1W$J9MsjiXQu0{sN!(0)p=yn;5R~ zm8G1M$wClU4oHZeWuEucT>8fj9@#M0kY>Zjx}{F%fX>qa5#{2}lM>g}Xnjo}l|ew8 zkXA5h=I9hvEufUW_wOT8b^(DlBKCuM+=VI>J`Ua;1OioQTVInOmu*pv>=0&M>MOS| z%x%82SVXH|##aK|&I9wXCi2Kuz8@~`}P*VwE0=zPr%s5aHvFP`FsjEx2cBo)6ex*A zWp5GPoq0Vy74R>2aPlQP>~oZKw3$U(jAdy#E}=(clqiqe%$7=zb#t-GOC`@<-LJz{!m%n21KVT2lg4>F^Qyl9E2SvvZNE^Kq<8~8z*~izg_2G$e)DWZ z&r)^t$fjc4=0*E2GgW8V@;;-uQTLpkoe4G&6_Gi{=*bj1demc_{W*z@M)N3w-y!I2 zxt>0g2bLTSCr87lvU@@?w=y0(8-&vH2iDYp1oVatM3hj{k zTI09~y|)(A+XuR&rxolH&~6OyHuw;ulgO_ zPuTLyiVw)P|B03nB7klGZ1SdadQT)(_wcJpUd5Dw*Tl^3%=>G;G`B&%wwFm(MjZi# zMzuQuU>R1Zq8as9MkmM~4%8aV4m60Cl4X`?$zw27Nx(x@)C3hiNs$loyeJV|;3R`m z=2BoxiLeZq;~pUpKfO}+8=>;xkRT&Wh?xRT*$vA=e1-1-a(LQ&8&RQ!R;p| z0{dFY6Iuv97U8}VgGV$6PB!6w5}-jehsz>M8R?2d0-?1=c9Ek)8Yhh)!3TZPk1>d^py>9{d~my1NBGJ)ypHC;!FbEqzyVi zu?k`sqbi!2$c8~?{{=5xCd5}QNx$~UD2(hV0{VWx-}##X2uo*=a!4(~o_<3lOh;=1 zGWy!R&!cXBeOPdKzslPq+FOzt2P)Y6SL*2}8s1q7(#-PEp*Wm`{7r`W-T4WD{gKfb zL=!WtyH86@TGc=5%hW+QVgF5lmp6`bUz|y3kvDq8cEX#Zcon0xK`W6icDQ>?Gb=4k zx9`mayKC`XvhQ;fwwljzxg#~7>oUV^PafLCvQ3GNmYh3%udW9gpP}zdP01_?V#F|} zu+6A+v$!2@w>!LQS}Htz#xrDTMCHF(viHn9B@`r*AN^Uh^K1dYX%OU(L;QO-NS7sm zB}n&5G=+cvZdostKMXC?^Pljs93+p|U_TbCD$_YFH_al)C6D--qOJJg^-4S{e(_Bh(hqonQpIAR3 zLn22yQovcP8^(~lYa;Iw1iN45bC1LAyPgyMn!Us#kC~Od)l{8iBF=vyb{%q5Uo|At z`GioU@7{~W>87(`5`y7oUan|z+y9y6kLnnMdpTsuWXtd+^OE@Rc1&DlS#6q{VJQ~^2R25csGlWAI6%1)G(k1hy(%a6 zP8;j(?t{iGcAAzn*N4^9x1BG`9YQD?lsKuJE}E(!LRb-C04hKL&@?*uDt+rmq#F+E zy;MAG%p~MH`3$_n9%+YIg%-3+vV)5OcqKaeQuCmrhtqvaxZ!JAr|$dSF%)+`Yvoou zOSNuZL?Y9b&gUmyj|pfc5HOzcO#wTn_4)qhXWH?-2h*_V$bXFzOAO}R;U0Utm6jK1 zARXYF88&Au<4|bU zjIqU6CietjeFXz>A`VLxAln~?Tc3Z$!7ZUwvHhxe6;yAIYyV5DChijA_*mxgWa1Hf zpMe^m_ zi=Br9$|jmRXy`ALU7%BL%h!;kp0u2jEG>Y(3_SumS4~Ap=R2K`FOb*E9xFaK2xw@q5)FC9ki5__UGG^ChH* zg8T@CWK(2ZAhn)tl(@xrQ|@?sJZYbg?wPRykjvXSzBgO!5l;~}n=Vx=*>!3~hpG!QO_vZ7nOf(H%X8Zyf5zQI9<;&VgO`J^g!d%ci*Gayzi9E zzV{ggWXFUOwfXv^Cu9g;LXloZZQq$>osapDJ&dlE+FA zOAq0EeuKAV6~J_=V4ai?3X&T(A2S-Y-bb`Ai`xZ-D`VrnQ>pAdiPR0)l-S!eWp};M zhdf*YpjTWa+F;wAvaF(x6TW7LroZ>f%xX1B>ku{kHy23f4Gr*{SyBzch&H417J0V$b=yDLEIl7<2;YbKQ&{=ZOVvMR0}AxP zsmR+tme$kQHP;7Yn9&3eFJljv567buHH|D~F|nOk<45BcE*rk)#MT#RvWplVxMlzpi*dmU?7Pzz{?ICX{O>V+&4<<0nM?7@q6?=qp|+- z^F2j+>w(o9IZ#i9MKt?we*u>AF^=)GwlEo-<8)ZNsl`DO9Ts^3mN?;` zpu-&&=Gn~8C2og^of_Emg!Z)!`}l6?zCnvZ2)$RRO7E_te3B9iY#R5%#LUxR2a$64 zRNuv={A!3W0>=Vd9-Gygqi!GqnO4Wu*hSIx$FOH*78(*CzB@93|C9L^)cR86oytQX zz(VBa;uz&eA4;0&+0T7h>1okMFU4QmpaK8N1A2wlN0S5ncCO%AcYgA${c!kFQ+TiA zSE{2T+HSjei*$%Ai4A}4W1S3}-mXNa1B^jTL+Biw<*SD;pmpz7SdmFu%Z231W zkED`=rBr|FkuV%mCW~b>XQTCw%K0Clxj&QGIm4o%6lpuc4OgwWW^N>I z$CiUaixkCEQf)R*DBF6P&%z|)%AGchvGhBH3v_5YPKL6o6gDG~@`ZoTScT$`HQPz7 zQiqtq$|yTKXN%7 zSaCG2Ucn>50Z`>XxJnz6%(tPlqY9dGm@zHtV2!nWMmS!~Ac!e66nI-(6fh>Qh>8n)+v%wQv>T#tc54h zB%~5--xs;qRhX+bIms&XJP;?K$K2_5H1EpFn-*GyZaD5sGDZ&n5P~FndmWj1xxfxb zSocm{R9OVmD?CfFE;Oebf@%V^7{ZETZUhZ?GM(@uT|gImuIH#AeMtxlE^*teXWH`b z$LnM8?Q_|vjv^u(kO-Y$cB1?ICmH@j5PY(q zaPxf3LgA{hO>D7{M2?XnUpAsX?0!P#eL3cHStcyY4^PB2N&Y`}U05UvjiREStj@u{ z|B)ET + + #3F51B5 + #303F9F + #FF4081 + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..9cd81eb --- /dev/null +++ b/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + BlocPower Audit + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..5885930 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/android/app/src/test/java/com/blocpower/blocpoweraudit/ExampleUnitTest.java b/android/app/src/test/java/com/blocpower/blocpoweraudit/ExampleUnitTest.java new file mode 100644 index 0000000..4716f4c --- /dev/null +++ b/android/app/src/test/java/com/blocpower/blocpoweraudit/ExampleUnitTest.java @@ -0,0 +1,15 @@ +package com.blocpower.blocpoweraudit; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * To work on unit tests, switch the Test Artifact in the Build Variants view. + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..107ba48 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,31 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.1.0' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + mavenLocal() + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm + url "$projectDir/../../node_modules/react-native/android" + } + maven { + url "http://files.couchbase.com/maven2/" + } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..c8d0d56 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,20 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +android.useDeprecatedNdk=true \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..13372aef5e24af05341d49695ee84e5f9b594659 GIT binary patch literal 53636 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ⅅ8zCNKueM5c!$)^mKpZNR!eIlFST57ePGQcqCqedAQ3UaUEzpjM--5V4YO zY22VxQm%$2NDnwfK+jkz=i2>NjAM6&P1DdcO<*Xs1-lzdXWn#LGSxwhPH7N%D8-zCgpFWt@`LgNYI+Fh^~nSiQmwH0^>E>*O$47MqfQza@Ce z1wBw;igLc#V2@y-*~Hp?jA1)+MYYyAt|DV_8RQCrRY@sAviO}wv;3gFdO>TE(=9o? z=S(r=0oT`w24=ihA=~iFV5z$ZG74?rmYn#eanx(!Hkxcr$*^KRFJKYYB&l6$WVsJ^ z-Iz#HYmE)Da@&seqG1fXsTER#adA&OrD2-T(z}Cwby|mQf{0v*v3hq~pzF`U`jenT z=XHXeB|fa?Ws$+9ADO0rco{#~+`VM?IXg7N>M0w1fyW1iiKTA@p$y zSiAJ%-Mg{m>&S4r#Tw@?@7ck}#oFo-iZJCWc`hw_J$=rw?omE{^tc59ftd`xq?jzf zo0bFUI=$>O!45{!c4?0KsJmZ#$vuYpZLo_O^oHTmmLMm0J_a{Nn`q5tG1m=0ecv$T z5H7r0DZGl6be@aJ+;26EGw9JENj0oJ5K0=^f-yBW2I0jqVIU};NBp*gF7_KlQnhB6 z##d$H({^HXj@il`*4^kC42&3)(A|tuhs;LygA-EWFSqpe+%#?6HG6}mE215Z4mjO2 zY2^?5$<8&k`O~#~sSc5Fy`5hg5#e{kG>SAbTxCh{y32fHkNryU_c0_6h&$zbWc63T z7|r?X7_H!9XK!HfZ+r?FvBQ$x{HTGS=1VN<>Ss-7M3z|vQG|N}Frv{h-q623@Jz*@ ziXlZIpAuY^RPlu&=nO)pFhML5=ut~&zWDSsn%>mv)!P1|^M!d5AwmSPIckoY|0u9I zTDAzG*U&5SPf+@c_tE_I!~Npfi$?gX(kn=zZd|tUZ_ez(xP+)xS!8=k(<{9@<+EUx zYQgZhjn(0qA#?~Q+EA9oh_Jx5PMfE3#KIh#*cFIFQGi)-40NHbJO&%ZvL|LAqU=Rw zf?Vr4qkUcKtLr^g-6*N-tfk+v8@#Lpl~SgKyH!+m9?T8B>WDWK22;!i5&_N=%f{__ z-LHb`v-LvKqTJZCx~z|Yg;U_f)VZu~q7trb%C6fOKs#eJosw&b$nmwGwP;Bz`=zK4 z>U3;}T_ptP)w=vJaL8EhW;J#SHA;fr13f=r#{o)`dRMOs-T;lp&Toi@u^oB_^pw=P zp#8Geo2?@!h2EYHY?L;ayT}-Df0?TeUCe8Cto{W0_a>!7Gxmi5G-nIIS;X{flm2De z{SjFG%knZoVa;mtHR_`*6)KEf=dvOT3OgT7C7&-4P#4X^B%VI&_57cBbli()(%zZC?Y0b;?5!f22UleQ=9h4_LkcA!Xsqx@q{ko&tvP_V@7epFs}AIpM{g??PA>U(sk$Gum>2Eu zD{Oy{$OF%~?B6>ixQeK9I}!$O0!T3#Ir8MW)j2V*qyJ z8Bg17L`rg^B_#rkny-=<3fr}Y42+x0@q6POk$H^*p3~Dc@5uYTQ$pfaRnIT}Wxb;- zl!@kkZkS=l)&=y|21veY8yz$t-&7ecA)TR|=51BKh(@n|d$EN>18)9kSQ|GqP?aeM ztXd9C&Md$PPF*FVs*GhoHM2L@D$(Qf%%x zwQBUt!jM~GgwluBcwkgwQ!249uPkNz3u@LSYZgmpHgX|P#8!iKk^vSKZ;?)KE$92d z2U>y}VWJ0&zjrIqddM3dz-nU%>bL&KU%SA|LiiUU7Ka|c=jF|vQ1V)Jz`JZe*j<5U6~RVuBEVJoY~ z&GE+F$f>4lN=X4-|9v*5O*Os>>r87u z!_1NSV?_X&HeFR1fOFb8_P)4lybJ6?1BWK`Tv2;4t|x1<#@17UO|hLGnrB%nu)fDk zfstJ4{X4^Y<8Lj<}g2^kksSefQTMuTo?tJLCh zC~>CR#a0hADw!_Vg*5fJwV{~S(j8)~sn>Oyt(ud2$1YfGck77}xN@3U_#T`q)f9!2 zf>Ia;Gwp2_C>WokU%(z2ec8z94pZyhaK+e>3a9sj^-&*V494;p9-xk+u1Jn#N_&xs z59OI2w=PuTErv|aNcK*>3l^W*p3}fjXJjJAXtBA#%B(-0--s;1U#f8gFYW!JL+iVG zV0SSx5w8eVgE?3Sg@eQv)=x<+-JgpVixZQNaZr}3b8sVyVs$@ndkF5FYKka@b+YAh z#nq_gzlIDKEs_i}H4f)(VQ!FSB}j>5znkVD&W0bOA{UZ7h!(FXrBbtdGA|PE1db>s z$!X)WY)u#7P8>^7Pjjj-kXNBuJX3(pJVetTZRNOnR5|RT5D>xmwxhAn)9KF3J05J; z-Mfb~dc?LUGqozC2p!1VjRqUwwDBnJhOua3vCCB-%ykW_ohSe?$R#dz%@Gym-8-RA zjMa_SJSzIl8{9dV+&63e9$4;{=1}w2=l+_j_Dtt@<(SYMbV-18&%F@Zl7F_5! z@xwJ0wiDdO%{}j9PW1(t+8P7Ud79yjY>x>aZYWJL_NI?bI6Y02`;@?qPz_PRqz(7v``20`- z033Dy|4;y6di|>cz|P-z|6c&3f&g^OAt8aN0Zd&0yZ>dq2aFCsE<~Ucf$v{sL=*++ zBxFSa2lfA+Y%U@B&3D=&CBO&u`#*nNc|PCY7XO<}MnG0VR764XrHtrb5zwC*2F!Lp zE<~Vj0;z!S-|3M4DFxuQ=`ShTf28<9p!81(0hFbGNqF%0gg*orez9!qt8e%o@Yfl@ zhvY}{@3&f??}7<`p>FyU;7?VkKbh8_=csozU=|fH&szgZ{=NDCylQ>EH^x5!K3~-V z)_2Y>0uJ`Z0Pb58y`RL+&n@m9tJ)O<%q#&u#DAIt+-rRt0eSe1MTtMl@W)H$b3D)@ z*A-1bUgZI)>HdcI4&W>P4W5{-j=s5p5`cbQ+{(g0+RDnz!TR^mxSLu_y#SDVKrj8i zA^hi6>jMGM;`$9Vfb-Yf!47b)Ow`2OKtNB=z|Kxa$5O}WPo;(Dc^`q(7X8kkeFyO8 z{XOq^07=u|7*P2`m;>PIFf=i80MKUxsN{d2cX0M+REsE*20+WQ79T9&cqT>=I_U% z{=8~^Isg(Nzo~`4iQfIb_#CVCD>#5h>=-Z#5dH}WxYzn%0)GAm6L2WdUdP=0_h>7f z(jh&7%1i(ZOn+}D8$iGK4Vs{pmHl_w4Qm-46H9>4^{3dz^DZDh+dw)6Xd@CpQNK$j z{CU;-cmpK=egplZ3y3%y=sEnCJ^eYVKXzV8H2_r*fJ*%*B;a1_lOpt6)IT1IAK2eB z{rie|uDJUrbgfUE>~C>@RO|m5ex55F{=~Bb4Cucp{ok7Yf9V}QuZ`#Gc|WaqsQlK- zKaV)iMRR__&Ak2Z=IM9R9g5$WM4u{a^C-7uX*!myEym z#_#p^T!P~#Dx$%^K>Y_nj_3J*E_LwJ60-5Xu=LkJAwcP@|0;a&+|+ZX`Jbj9P5;T% z|KOc}4*#4o{U?09`9Hz`Xo-I!P=9XfIrr*MQ}y=$!qgv?_J38^bNb4kM&_OVg^_=Eu-qG5U(fw0KMgH){C8pazq~51rN97hf#20-7=aK0)N|UM H-+%o-(+5aQ literal 0 HcmV?d00001 diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..122a0dc --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Dec 28 10:00:20 PST 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip diff --git a/android/gradlew b/android/gradlew new file mode 100644 index 0000000..9d82f78 --- /dev/null +++ b/android/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/android/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/keystores/BUCK b/android/keystores/BUCK new file mode 100644 index 0000000..15da20e --- /dev/null +++ b/android/keystores/BUCK @@ -0,0 +1,8 @@ +keystore( + name = 'debug', + store = 'debug.keystore', + properties = 'debug.keystore.properties', + visibility = [ + 'PUBLIC', + ], +) diff --git a/android/keystores/debug.keystore.properties b/android/keystores/debug.keystore.properties new file mode 100644 index 0000000..121bfb4 --- /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 new file mode 100644 index 0000000..2a9f3cb --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,7 @@ +rootProject.name = 'blocpoweraudit' + +include ':react-native-couchbase-lite' +project(':react-native-couchbase-lite').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-couchbase-lite/android') + +include ':app' + diff --git a/app/actions/allActions.js b/app/actions/allActions.js new file mode 100644 index 0000000..0128d20 --- /dev/null +++ b/app/actions/allActions.js @@ -0,0 +1,7 @@ +import Utils from '../utils/utils'; + +export default () => ({ + building: Utils.getActionsObject(['create', 'save', 'destroy', 'newObj', 'editObj', 'editField']), + data: Utils.getActionsObject(['sync']), + nav: Utils.getActionsObject(['push', 'pop', 'navigator', 'homeback', 'hardwareback']) +}); \ No newline at end of file diff --git a/app/assets/styles.js b/app/assets/styles.js new file mode 100644 index 0000000..f3f95a9 --- /dev/null +++ b/app/assets/styles.js @@ -0,0 +1,74 @@ +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + flex: 1 + }, + rowContainer: { + padding: 10, + flex: 1, + alignItems: 'flex-start', + justifyContent: 'flex-start', + flexDirection: 'row' + }, + rowAvatar: { + flex: 1 + }, + rowText: { + fontSize: 19, + flex: 8 + }, + rowDelete: { + fontSize: 19, + flex: 1 + }, + label: { + fontSize: 19 + }, + rowTitle: { + color: '#48BBEC', + fontSize: 16 + }, + + mainContainer: { + flex: 1, + padding: 30, + marginTop: 65, + flexDirection: 'column', + justifyContent: 'center', + backgroundColor: '#48BBEC' + }, + searchInput: { + height: 50, + padding: 4, + marginRight: 5, + fontSize: 23, + borderWidth: 1, + borderColor: 'white', + borderRadius: 8, + color: 'white', + margin: 5 + }, + buttonText: { + fontSize: 18, + color: '#111', + alignSelf: 'center' + }, + button: { + height: 45, + flexDirection: 'row', + backgroundColor: 'white', + borderColor: 'white', + borderWidth: 1, + borderRadius: 8, + marginBottom: 10, + marginTop: 10, + alignSelf: 'stretch', + justifyContent: 'center' + }, + actionButtonIcon: { + fontSize: 20, + height: 22, + color: 'white', + } +}); \ No newline at end of file diff --git a/app/components/Toolbar.js b/app/components/Toolbar.js new file mode 100644 index 0000000..f23b0bf --- /dev/null +++ b/app/components/Toolbar.js @@ -0,0 +1,40 @@ +import React, { Component, PropTypes } from 'react'; +import { Toolbar as MaterialToolbar } from 'react-native-material-design'; +import _ from 'lodash'; + +export default class Tb extends Component { + componentDidMount() { + this.props.stores.nav.stack.onValue(this.handleRoute.bind(this)); + } + + componentWillUnmount() { + this.props.stores.nav.stack.offValue(this.handleRoute.bind(this)); + } + + handleRoute(stack) { + this.setState({ + route: stack.get(0), + isChild: stack.size > 1 + }); + } + + getIcons() { + const routeIcons = this.state && this.state.route.actions && this.state.route.actions.map((action) => { + const actionReturn$ = _.get(this.props.actions, action.action).raw.onValue(this.props.actions.nav.homeback.handler); + return {icon: action.icon, + onPress: _.get(this.props.actions, action.action).handler + }; + }); + return routeIcons; + } + + render() { + return (); + } +} diff --git a/app/routes.js b/app/routes.js new file mode 100644 index 0000000..ee735ea --- /dev/null +++ b/app/routes.js @@ -0,0 +1,21 @@ +export default { + buildings: { + initialRoute: true, + + title: 'Buildings', + component: require('./scenes/buildingList').default, + dataSource: 'building.all', + children: { + new: { + title: "New Building", + component: require('./scenes/buildingEdit').default, + actions: [ { icon: 'save', action: 'building.create'}] + }, + edit: { + title: "Edit Building", + component: require('./scenes/buildingEdit').default, + actions: [ { icon: 'save', action: 'building.save'}] + } + } + } +} \ No newline at end of file diff --git a/app/scenes/buildingEdit.js b/app/scenes/buildingEdit.js new file mode 100644 index 0000000..b2854bc --- /dev/null +++ b/app/scenes/buildingEdit.js @@ -0,0 +1,48 @@ +import React, { Component } from 'react' +import { + Text, + View, + StyleSheet, + ScrollView, + TextInput, + TouchableOpacity, + Navigator +} from 'react-native'; + +import styles from '../assets/styles'; +import _ from 'lodash'; + +export default class BuildingEdit extends Component { + constructor(props) { + super(props); + this.boundState = this.mapState.bind(this); + } + + componentDidMount() { + this.props.stores.building.edit.onValue(this.boundState); + for(var loadAction of (this.props.route.loadActions || [])) { + let handler = _.get(this.props.actions, loadAction).handler; + handler && handler(); + } + } + + componentWillUnmount() { + this.props.stores.building.edit.offValue(this.boundState); + } + + mapState(building) { + this.setState({ + map: building + }) + } + + render() { + return ( + Name + + ); + } +} \ No newline at end of file diff --git a/app/scenes/buildingList.js b/app/scenes/buildingList.js new file mode 100644 index 0000000..24f0393 --- /dev/null +++ b/app/scenes/buildingList.js @@ -0,0 +1,84 @@ +import React, { Component } from 'react' +import { + Text, + View, + StyleSheet, + ScrollView, + TextInput, + TouchableOpacity, + Alert +} from 'react-native'; +import { Divider, Avatar } from 'react-native-material-design'; +import _ from 'lodash'; + +import ActionButton from 'react-native-action-button'; +import Icon from 'react-native-vector-icons/MaterialIcons'; +import styles from '../assets/styles'; + +export default class BuildingList extends Component { + constructor(props) { + super(props); + + this.watches = []; + } + + addWatch(stream$, watcher, bound = false) { + if (bound == false) { + stream$.onValue(watcher); + } + this.watches.push({ stream$: stream$, watcher: watcher }); + } + + componentDidMount() { + if (this.props.route.dataSource) { + this.addWatch(_.get(this.props.stores, this.props.route.dataSource ), this.mapState.bind(this)); + } + } + + componentWillUnmount() { + this.watches && this.watches.forEach((watch,i) => { + watch[0].offValue(watch[1]); + }); + } + + mapState(value) { + this.setState({ + value: value + }) + } + + render() { + var lists = this.state && this.state.value && Object.values(this.state.value.toJS()).map((item, index) => { + return ( + + + + + + + {item.name} + + + + + + ); + }); + + // const lists = this.state && { JSON.stringify(Object.values(this.state.value.toJS()))}; + + return ( + + + {lists} + + + + + + + + ); + } +} \ No newline at end of file diff --git a/app/stores/allStores.js b/app/stores/allStores.js new file mode 100644 index 0000000..48e9be8 --- /dev/null +++ b/app/stores/allStores.js @@ -0,0 +1,7 @@ +import getBuildingStore from './buildingStore'; +import getNavStore from './navStore'; + +export default (actions, routes) => ({ + building: getBuildingStore(actions.building), + nav: getNavStore(actions.nav, routes), +}) \ No newline at end of file diff --git a/app/stores/buildingStore.js b/app/stores/buildingStore.js new file mode 100644 index 0000000..5e319d7 --- /dev/null +++ b/app/stores/buildingStore.js @@ -0,0 +1,84 @@ +const Immutable = require('immutable'); +const Kefir = require('kefir'); +import uuid from 'node-uuid'; +import { Alert } from 'react-native'; + +import {manager as Manager, ReactCBLite} from 'react-native-couchbase-lite' +// init the Listener with a port and login credentials +ReactCBLite.init(5984, 'admin', 'password', e => { + //console.log('initialized'); +}); + +const database = new Manager('http://localhost:5984/', 'blocpower'); +database + .createDatabase() + .then((res) => { + database.getDesignDocument('main').then(d => { + const create = () => { + database.createDesignDocument('main', { + views: { + language: 'javascript', + by_type: {map: 'function (doc) { if (doc.type) { emit(doc.type, null); } }'}, + by_parent_id_type: {map: 'function (doc) { if (doc.parent_id && doc.type) { emit([doc.parent_id, doc.type], null); } }'} + } + }); + }; + + if (d) { + database.deleteDesignDocument('main', d._rev).then(create); + } else { + create(); + } + }); + database.replicate('http://couchbase1.sbdev.io:4984/blocpoweraudit'); + }); + +const kefirDatabase = ['createDatabase', 'deleteDatabase', 'getAllDatabases', 'createDocument', 'deleteDocument', 'updateDocument', 'getAllDocuments', 'getAllDocumentConflicts', 'replicate', 'getChanges', 'getInfo', 'getDesignDocument', 'createDesignDocument', 'deleteDesignDocument', 'queryView', 'saveAttachment', 'getAttachmentUri', 'deleteAttachment', 'latestRevision', 'activeTasks'] + .reduce((pv, cv) => { pv[cv] = (...arr) => Kefir.fromPromise(database[cv](...arr)); return pv; } , {}); + +module.exports = actions => { + const edit = Immutable.Map({}); + const editStream$ = Kefir.merge([ + actions.newObj.raw.map((initialObject) => (obj) => Immutable.Map(initialObject || {})), + actions.editObj.raw.map((current) => (obj) => Immutable.Map(current)), + actions.editField.raw.map((fieldData) => (obj) => obj.set(fieldData.key, fieldData.value)) + ]); + + const edit$ = editStream$.scan((current, change) => change(current), edit); + + const list = Immutable.OrderedMap(); + const startValue$ = kefirDatabase.queryView('main', 'by_type', {include_docs: true, key: 'building'}) + .take(1) + .map(list => list.rows.reduce((obj, field) => obj.set(field.id, field.doc), Immutable.OrderedMap())); + + const getCreate$ = edit$.sampledBy(actions.create.raw) + .map(b => b.toJS()) + .map(b => Object.assign(b, {_id: uuid.v4(), type: 'building' })); + const getEdit$ = edit$.sampledBy(actions.save.raw) + .map(b => b.toJS()); + + const inOut = (input$, output$) => Kefir.combine([input$, output$], [], (input, output) => ({input: input, output: output})) + const listStream$ = Kefir + .merge([ + inOut(getCreate$, getCreate$.flatMap(kefirDatabase.createDocument)) + .map(obj => Object.assign(obj.input, { _rev: obj.output.rev })) + .map(building => (map) => map.set(building._id, building)), + inOut(getEdit$, getEdit$.flatMap(obj => kefirDatabase.updateDocument(obj, obj._id, obj._rev))) + .map(obj => Object.assign(obj.input, { _rev: obj.rev })) + .map(building => (map) => map.set(building._id, building)), + actions.destroy.raw + .flatMap(b => kefirDatabase.deleteDocument(b._id, b._rev)) + .map(b => (map) => map.delete(b.id)), + startValue$ + .map(buildings => (map) => map.merge(buildings)) + ]); + + const listState$ = listStream$.scan((current, change) => change(current), list); + const errors$ = Kefir.merge([listStream$, editStream$]); + + return { + all: listState$, + edit: edit$, + errors: errors$ + } +}; \ No newline at end of file diff --git a/app/stores/navStore.js b/app/stores/navStore.js new file mode 100644 index 0000000..8232a84 --- /dev/null +++ b/app/stores/navStore.js @@ -0,0 +1,34 @@ +import _ from 'lodash'; +import Kefir from 'kefir'; +import Immutable from 'immutable'; +import { Alert } from 'react-native'; + +module.exports = (actions, routes) => { + const data = Immutable.List([ + Object.values(routes).find(r => r.initialRoute) + ]); + const stream$ = Kefir.merge([ + actions.push.raw.map( route => (state => state.unshift(_.get(routes, route)))), + actions.pop.raw.map( () => (state => state.shift())), + //This handles the home and back icons in the navigation bar. + actions.homeback.raw.map( () => (state) => state.size == 1 ? state : state.shift()), + ]); + + const state$ = stream$.scan((state, action) => action(state), data); + + const stateButtonSample$ = state$.sampledBy(actions.hardwareback.raw); + //Provide a close event for pressing the hardware back button on the main screens + const close$ = stateButtonSample$.filter(d => d.size == 1); + //Map hardware button presses to pop for non main screens. + actions.pop.raw.plug(stateButtonSample$.filter(d => d.size > 1)); + + + return { + stack: state$, + current: state$.map(state => state.get(0)), + depth: state$.map(state => state.size), + pop: actions.pop, + close: close$, + navigator: actions.navigator.raw, + }; +} \ No newline at end of file diff --git a/app/utils/utils.js b/app/utils/utils.js new file mode 100644 index 0000000..d075e34 --- /dev/null +++ b/app/utils/utils.js @@ -0,0 +1,17 @@ +import Kefir from 'kefir'; + +const al = stream => { stream.onValue(v => Alert.alert('stuff', JSON.stringify(v), [{text:'OK'}])); return stream; }; + +export default class Utils { + static getActionsObject(actionList) { + return actionList.reduce((state, prop) => { + state[prop] = { + raw: Kefir.pool(), + handler: v => state[prop].raw.plug(Kefir.constant(v)), + handlerConst: (v) => () => state[prop].raw.plug(Kefir.constant(v)), + handlerKV: (k) => (v) => state[prop].raw.plug(Kefir.constant({key: k, value: v})), + } + return state; + }, {}) + } +} \ No newline at end of file diff --git a/index.android.js b/index.android.js new file mode 100644 index 0000000..a09f3ff --- /dev/null +++ b/index.android.js @@ -0,0 +1,72 @@ +import React, {Component} from 'react'; +import { + AppRegistry, + StyleSheet, + Navigator, + Text, + View, + Alert, + BackAndroid +} from 'react-native'; + +import Kefir from 'kefir'; + +import Toolbar from './app/components/Toolbar'; +import routes from './app/routes'; + +import getActions from './app/actions/allActions'; +import getStores from './app/stores/allStores'; +import _ from 'lodash'; + +const actions = getActions(); +const stores = getStores(actions, routes); + +stores.building.errors.onError(e => { + Alert.alert('Error', JSON.stringify(e), [ {text: 'OK'}]); +}) + +stores.nav.navigator.onValue(navigator => { + const window$ = stores.nav.stack.slidingWindow(2, 2); + window$.filter(stacks => stacks[0].size < stacks[1].size).onValue(routes => navigator.push(routes[1].get(0))); + window$.filter(stacks => stacks[0].size > stacks[1].size).onValue(routes => navigator.pop()); +}); +actions.nav.push.raw.plug(actions.building.newObj.raw.map(() => 'buildings.children.new')); +actions.nav.push.raw.plug(actions.building.editObj.raw.map(() => 'buildings.children.edit')); + +BackAndroid.addEventListener('hardwareBackPress', () => { actions.nav.hardwareback.handler(); return true; }); + +stores.nav.close.onValue(() => { + BackAndroid.exitApp(); +}); + +class audit extends Component { + render() { + let route = null; + stores.nav.current.take(1).onValue((r) => { route = r; }); + return ( + } + renderScene={(route, navigator) => ( + + + ) + }> + + ); + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#111111' + }, + ha: { + marginTop: 100, + color: '#ffffff' + } +}); + +AppRegistry.registerComponent('audit', () => audit); diff --git a/package.json b/package.json new file mode 100644 index 0000000..7f3b1e4 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "argh", + "version": "0.0.1", + "private": true, + "scripts": { + "start": "node node_modules/react-native/local-cli/cli.js start" + }, + "dependencies": { + "immutable": "^3.8.1", + "kefir": "^3.2.3", + "lodash": "^4.13.1", + "node-uuid": "^1.4.7", + "react": "15.0.2", + "react-native": "^0.26.3", + "react-native-action-button": "^1.1.5", + "react-native-couchbase-lite": "^0.3.0", + "react-native-material-design": "^0.3.6" + } +} diff --git a/sync.json b/sync.json new file mode 100644 index 0000000..c668112 --- /dev/null +++ b/sync.json @@ -0,0 +1,5 @@ +{ + "interface":":4984", + "adminInterface":":4985", + "log":["REST", "Auth", "CRUD", "Events"] +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..c0876cb --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "allowJs": true + }, + "exclude": [ + "node_modules" + ] +} \ No newline at end of file -- GitLab From defc6c97a9f598e5e8761e95762ffd6b8ba68ba4 Mon Sep 17 00:00:00 2001 From: Andy Brummer Date: Thu, 9 Jun 2016 13:39:17 -0500 Subject: [PATCH 02/46] Adding multiple fields to building edit. --- app/scenes/buildingEdit.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/app/scenes/buildingEdit.js b/app/scenes/buildingEdit.js index b2854bc..db4fb5b 100644 --- a/app/scenes/buildingEdit.js +++ b/app/scenes/buildingEdit.js @@ -37,12 +37,23 @@ export default class BuildingEdit extends Component { } render() { + const that = this; + const TextField = (label, fieldNames) => ( + + {label} + { fieldNames.map((fieldName, index) => ())} + ); + return ( - Name - + {TextField('Name', ['name']) } + {TextField('Address', ['address1', 'address2']) } + {TextField('City', ['city']) } + {TextField('State', ['state']) } + {TextField('Zip Code', ['zipcode']) } ); } } \ No newline at end of file -- GitLab From 68ea768d05eb91797af4cba00ca9a371bcd5077e Mon Sep 17 00:00:00 2001 From: Andy Brummer Date: Fri, 10 Jun 2016 01:38:44 -0500 Subject: [PATCH 03/46] Can edit multiple entity types, and more of the UI is parameterized by the route objects. --- app/actions/allActions.js | 14 +-- app/entities.js | 4 + app/routes.js | 36 ++++++- app/scenes/buildingEdit.js | 14 +-- app/scenes/clientEdit.js | 59 +++++++++++ app/scenes/{buildingList.js => entityList.js} | 21 ++-- app/stores/allStores.js | 22 ++++- app/stores/buildingStore.js | 84 ---------------- app/stores/dataStore.js | 20 ++++ app/stores/entityStore.js | 55 +++++++++++ app/stores/mainDesignDoc.js | 7 ++ app/stores/navStore.js | 2 +- app/utils/kefirCouchbase.js | 66 +++++++++++++ app/utils/utils.js | 26 +++++ index.android.js | 97 +++++++++++++------ 15 files changed, 382 insertions(+), 145 deletions(-) create mode 100644 app/entities.js create mode 100644 app/scenes/clientEdit.js rename app/scenes/{buildingList.js => entityList.js} (79%) delete mode 100644 app/stores/buildingStore.js create mode 100644 app/stores/dataStore.js create mode 100644 app/stores/entityStore.js create mode 100644 app/stores/mainDesignDoc.js create mode 100644 app/utils/kefirCouchbase.js diff --git a/app/actions/allActions.js b/app/actions/allActions.js index 0128d20..d688558 100644 --- a/app/actions/allActions.js +++ b/app/actions/allActions.js @@ -1,7 +1,9 @@ -import Utils from '../utils/utils'; +import U from '../utils/utils'; +import Entities from '../entities'; -export default () => ({ - building: Utils.getActionsObject(['create', 'save', 'destroy', 'newObj', 'editObj', 'editField']), - data: Utils.getActionsObject(['sync']), - nav: Utils.getActionsObject(['push', 'pop', 'navigator', 'homeback', 'hardwareback']) -}); \ No newline at end of file +export default () => Entities.entityList.reduce((actions, entity) => { actions[entity] = U.getActionsObject(Entities.entityActions); return actions; }, + { + data: U.getActionsObject(['sync']), + nav: U.getActionsObject(['push', 'pop', 'navigator', 'homeback', 'hardwareback']) + } + ); \ No newline at end of file diff --git a/app/entities.js b/app/entities.js new file mode 100644 index 0000000..a67be2d --- /dev/null +++ b/app/entities.js @@ -0,0 +1,4 @@ +export default { + entityActions: ['create', 'save', 'destroy', 'newObj', 'editObj', 'editField'], + entityList: ['building', 'client', 'boiler', 'room'] +}; \ No newline at end of file diff --git a/app/routes.js b/app/routes.js index ee735ea..6badef1 100644 --- a/app/routes.js +++ b/app/routes.js @@ -1,21 +1,53 @@ export default { - buildings: { + building: { initialRoute: true, title: 'Buildings', - component: require('./scenes/buildingList').default, + single: 'Building', + newLabel: 'New Building', + component: require('./scenes/entityList').default, dataSource: 'building.all', + entityName: 'building', + listIcon: 'account-balance', children: { new: { title: "New Building", component: require('./scenes/buildingEdit').default, + entityName: 'building', actions: [ { icon: 'save', action: 'building.create'}] }, edit: { title: "Edit Building", component: require('./scenes/buildingEdit').default, + entityName: 'building', actions: [ { icon: 'save', action: 'building.save'}] } } + }, + client: { + initialRoute: true, + + title: 'Clients', + single: 'Client', + newLabel: 'New Client', + component: require('./scenes/entityList').default, + dataSource: 'client.all', + entityName: 'client', + listIcon: 'supervisor-account', + children: { + new: { + title: "New Client", + component: require('./scenes/clientEdit').default, + entityName: 'client', + actions: [ { icon: 'save', action: 'client.create'}] + }, + edit: { + title: "Edit Client", + component: require('./scenes/clientEdit').default, + entityName: 'client', + actions: [ { icon: 'save', action: 'client.save'}] + } + } } + } \ No newline at end of file diff --git a/app/scenes/buildingEdit.js b/app/scenes/buildingEdit.js index db4fb5b..4eef9d3 100644 --- a/app/scenes/buildingEdit.js +++ b/app/scenes/buildingEdit.js @@ -6,10 +6,12 @@ import { ScrollView, TextInput, TouchableOpacity, - Navigator + Navigator, + Alert } from 'react-native'; import styles from '../assets/styles'; +import U from '../utils/utils'; import _ from 'lodash'; export default class BuildingEdit extends Component { @@ -19,7 +21,7 @@ export default class BuildingEdit extends Component { } componentDidMount() { - this.props.stores.building.edit.onValue(this.boundState); + this.props.stores[this.props.route.entityName].edit.onValue(this.boundState); for(var loadAction of (this.props.route.loadActions || [])) { let handler = _.get(this.props.actions, loadAction).handler; handler && handler(); @@ -27,12 +29,12 @@ export default class BuildingEdit extends Component { } componentWillUnmount() { - this.props.stores.building.edit.offValue(this.boundState); + this.props.stores[this.props.route.entityName].edit.offValue(this.boundState); } - mapState(building) { + mapState(entity) { this.setState({ - map: building + map: entity }) } @@ -44,7 +46,7 @@ export default class BuildingEdit extends Component { { fieldNames.map((fieldName, index) => ())} ); diff --git a/app/scenes/clientEdit.js b/app/scenes/clientEdit.js new file mode 100644 index 0000000..119cf9b --- /dev/null +++ b/app/scenes/clientEdit.js @@ -0,0 +1,59 @@ +import React, { Component } from 'react' +import { + Text, + View, + StyleSheet, + ScrollView, + TextInput, + TouchableOpacity, + Navigator, + Alert +} from 'react-native'; + +import styles from '../assets/styles'; +import U from '../utils/utils'; +import _ from 'lodash'; + +export default class ClientEdit extends Component { + constructor(props) { + super(props); + this.boundState = this.mapState.bind(this); + } + + componentDidMount() { + this.props.stores[this.props.route.entityName].edit.onValue(this.boundState); + for(var loadAction of (this.props.route.loadActions || [])) { + let handler = _.get(this.props.actions, loadAction).handler; + handler && handler(); + } + } + + componentWillUnmount() { + this.props.stores[this.props.route.entityName].edit.offValue(this.boundState); + } + + mapState(obj) { + this.setState({ + map: obj + }) + } + + render() { + const that = this; + const TextField = (label, fieldNames) => ( + + {label} + { fieldNames.map((fieldName, index) => ())} + ); + + return ( + {TextField('Name', ['name']) } + {TextField('Contact', ['first', 'last']) } + {TextField('Phone', ['phone']) } + ); + } +} \ No newline at end of file diff --git a/app/scenes/buildingList.js b/app/scenes/entityList.js similarity index 79% rename from app/scenes/buildingList.js rename to app/scenes/entityList.js index 24f0393..304d4ec 100644 --- a/app/scenes/buildingList.js +++ b/app/scenes/entityList.js @@ -15,7 +15,7 @@ import ActionButton from 'react-native-action-button'; import Icon from 'react-native-vector-icons/MaterialIcons'; import styles from '../assets/styles'; -export default class BuildingList extends Component { +export default class EntityList extends Component { constructor(props) { super(props); @@ -48,24 +48,21 @@ export default class BuildingList extends Component { } render() { - var lists = this.state && this.state.value && Object.values(this.state.value.toJS()).map((item, index) => { - return ( + var lists = this.state && this.state.value && Object.values(this.state.value.toJS()).map((item, index) => ( - - + + {item.name} - + - ); - }); - - // const lists = this.state && { JSON.stringify(Object.values(this.state.value.toJS()))}; + ) + ); return ( @@ -73,8 +70,8 @@ export default class BuildingList extends Component { {lists} - + diff --git a/app/stores/allStores.js b/app/stores/allStores.js index 48e9be8..ce9ec90 100644 --- a/app/stores/allStores.js +++ b/app/stores/allStores.js @@ -1,7 +1,19 @@ -import getBuildingStore from './buildingStore'; +import getEntityStore from './entityStore'; import getNavStore from './navStore'; +import getDataStore from './dataStore'; +import Kefir from 'kefir'; +import Entities from '../entities'; -export default (actions, routes) => ({ - building: getBuildingStore(actions.building), - nav: getNavStore(actions.nav, routes), -}) \ No newline at end of file +export default (actions, routes) => { + const data = getDataStore(actions.data, 5984, 'admin', 'password', 'http://localhost:5984/', 'blocpower'); + const nav = getNavStore(actions.nav, routes); + const stores = [ + Kefir.constant(stores => stores.nav = nav), + Kefir.constant(stores => stores.data = data) + ]; + Entities.entityList.forEach(entity => stores.push(data.db.map(data => stores => stores[entity] = getEntityStore(actions[entity], data, entity)))); + + const merge$ = Kefir.combine(stores).map(arr => arr.reduce((v, m) => { m(v); return v; }, {})); + + return merge$; +} \ No newline at end of file diff --git a/app/stores/buildingStore.js b/app/stores/buildingStore.js deleted file mode 100644 index 5e319d7..0000000 --- a/app/stores/buildingStore.js +++ /dev/null @@ -1,84 +0,0 @@ -const Immutable = require('immutable'); -const Kefir = require('kefir'); -import uuid from 'node-uuid'; -import { Alert } from 'react-native'; - -import {manager as Manager, ReactCBLite} from 'react-native-couchbase-lite' -// init the Listener with a port and login credentials -ReactCBLite.init(5984, 'admin', 'password', e => { - //console.log('initialized'); -}); - -const database = new Manager('http://localhost:5984/', 'blocpower'); -database - .createDatabase() - .then((res) => { - database.getDesignDocument('main').then(d => { - const create = () => { - database.createDesignDocument('main', { - views: { - language: 'javascript', - by_type: {map: 'function (doc) { if (doc.type) { emit(doc.type, null); } }'}, - by_parent_id_type: {map: 'function (doc) { if (doc.parent_id && doc.type) { emit([doc.parent_id, doc.type], null); } }'} - } - }); - }; - - if (d) { - database.deleteDesignDocument('main', d._rev).then(create); - } else { - create(); - } - }); - database.replicate('http://couchbase1.sbdev.io:4984/blocpoweraudit'); - }); - -const kefirDatabase = ['createDatabase', 'deleteDatabase', 'getAllDatabases', 'createDocument', 'deleteDocument', 'updateDocument', 'getAllDocuments', 'getAllDocumentConflicts', 'replicate', 'getChanges', 'getInfo', 'getDesignDocument', 'createDesignDocument', 'deleteDesignDocument', 'queryView', 'saveAttachment', 'getAttachmentUri', 'deleteAttachment', 'latestRevision', 'activeTasks'] - .reduce((pv, cv) => { pv[cv] = (...arr) => Kefir.fromPromise(database[cv](...arr)); return pv; } , {}); - -module.exports = actions => { - const edit = Immutable.Map({}); - const editStream$ = Kefir.merge([ - actions.newObj.raw.map((initialObject) => (obj) => Immutable.Map(initialObject || {})), - actions.editObj.raw.map((current) => (obj) => Immutable.Map(current)), - actions.editField.raw.map((fieldData) => (obj) => obj.set(fieldData.key, fieldData.value)) - ]); - - const edit$ = editStream$.scan((current, change) => change(current), edit); - - const list = Immutable.OrderedMap(); - const startValue$ = kefirDatabase.queryView('main', 'by_type', {include_docs: true, key: 'building'}) - .take(1) - .map(list => list.rows.reduce((obj, field) => obj.set(field.id, field.doc), Immutable.OrderedMap())); - - const getCreate$ = edit$.sampledBy(actions.create.raw) - .map(b => b.toJS()) - .map(b => Object.assign(b, {_id: uuid.v4(), type: 'building' })); - const getEdit$ = edit$.sampledBy(actions.save.raw) - .map(b => b.toJS()); - - const inOut = (input$, output$) => Kefir.combine([input$, output$], [], (input, output) => ({input: input, output: output})) - const listStream$ = Kefir - .merge([ - inOut(getCreate$, getCreate$.flatMap(kefirDatabase.createDocument)) - .map(obj => Object.assign(obj.input, { _rev: obj.output.rev })) - .map(building => (map) => map.set(building._id, building)), - inOut(getEdit$, getEdit$.flatMap(obj => kefirDatabase.updateDocument(obj, obj._id, obj._rev))) - .map(obj => Object.assign(obj.input, { _rev: obj.rev })) - .map(building => (map) => map.set(building._id, building)), - actions.destroy.raw - .flatMap(b => kefirDatabase.deleteDocument(b._id, b._rev)) - .map(b => (map) => map.delete(b.id)), - startValue$ - .map(buildings => (map) => map.merge(buildings)) - ]); - - const listState$ = listStream$.scan((current, change) => change(current), list); - const errors$ = Kefir.merge([listStream$, editStream$]); - - return { - all: listState$, - edit: edit$, - errors: errors$ - } -}; \ No newline at end of file diff --git a/app/stores/dataStore.js b/app/stores/dataStore.js new file mode 100644 index 0000000..cad3d4a --- /dev/null +++ b/app/stores/dataStore.js @@ -0,0 +1,20 @@ +import Kefir from 'kefir'; +import U from '../utils/utils'; +import kc from '../utils/kefirCouchbase'; +import mainDesignDoc from './mainDesignDoc'; + +export default (actions, port, username, password, url, bucketName) => { + kc.initListener.action.call(port, username, password); + const db = kc.getDatabase(url, bucketName); + + return { + init: kc.initListener.result, + //Make sure database is created and that the database has an updated design document + db: db.getInfo() + .flatMap(info => (info.status && info.status == 404) ? db.createDatabase() : Kefir.constant()) + .flatMap(() => db.getDesignDocument('main')) + .flatMap(doc => (doc.status && doc.status == 404) ? Kefir.constant() : db.deleteDesignDocument('main', doc._rev)) + .flatMap(() => db.createDesignDocument('main', mainDesignDoc)) + .map(() => db) + }; +} \ No newline at end of file diff --git a/app/stores/entityStore.js b/app/stores/entityStore.js new file mode 100644 index 0000000..a6ddd0d --- /dev/null +++ b/app/stores/entityStore.js @@ -0,0 +1,55 @@ +import Immutable from 'immutable'; +import Kefir from 'kefir'; +import uuid from 'node-uuid'; +import U from '../utils/utils'; + +function getEdit(actions) { + const edit = Immutable.Map({}); + const editStream$ = Kefir.merge([ + actions.newObj.raw.map((initialObject) => (obj) => Immutable.Map(initialObject || {})), + actions.editObj.raw.map((current) => (obj) => Immutable.Map(current)), + actions.editField.raw.map((fieldData) => (obj) => obj.set(fieldData.key, fieldData.value)) + ]); + + const edit$ = editStream$.scan((current, change) => change(current), edit); + return [edit$, editStream$]; +} + +module.exports = (actions, kd, type) => { + const [edit$, editStream$] = getEdit(actions); + + const list = Immutable.OrderedMap(); + const startValue$ = kd.queryView('main', 'by_type', {include_docs: true, key: type}) + .take(1) + .map(list => list.rows.reduce((obj, field) => obj.set(field.id, field.doc), Immutable.OrderedMap())); + + const getCreate$ = edit$.sampledBy(actions.create.raw) + .map(b => b.toJS()) + .map(b => Object.assign(b, {_id: uuid.v4(), type: type })); + const getEdit$ = edit$.sampledBy(actions.save.raw) + .map(b => b.toJS()); + + const setSingle = stream => + stream.map(obj => Object.assign(obj.input, { _rev: obj.output.rev })) + .map(building => (map) => map.set(building._id, building)); + + const listStream$ = Kefir + .merge([ + setSingle(kd.createDocumentInOut(getCreate$)), + setSingle(kd.updateDocumentInOut(getEdit$, obj => [obj, obj._id, obj._rev])), + actions.destroy.raw + .flatMap(b => kd.deleteDocument(b._id, b._rev)) + .map(b => (map) => map.delete(b.id)), + startValue$ + .map(buildings => (map) => map.merge(buildings)) + ]); + + const listState$ = listStream$.scan((current, change) => change(current), list); + const errors$ = Kefir.merge([listStream$, editStream$]); + + return { + all: listState$, + edit: edit$, + errors: errors$ + } +}; \ No newline at end of file diff --git a/app/stores/mainDesignDoc.js b/app/stores/mainDesignDoc.js new file mode 100644 index 0000000..18ea0d0 --- /dev/null +++ b/app/stores/mainDesignDoc.js @@ -0,0 +1,7 @@ +export default { + views: { + language: 'javascript', + by_type: {map: 'function (doc) { if (doc.type) { emit(doc.type, null); } }'}, + by_parent_id_type: {map: 'function (doc) { if (doc.parent_id && doc.type) { emit([doc.parent_id, doc.type], null); } }'} + } +}; \ No newline at end of file diff --git a/app/stores/navStore.js b/app/stores/navStore.js index 8232a84..50aba8c 100644 --- a/app/stores/navStore.js +++ b/app/stores/navStore.js @@ -1,7 +1,7 @@ import _ from 'lodash'; import Kefir from 'kefir'; import Immutable from 'immutable'; -import { Alert } from 'react-native'; +import U from '../utils/utils'; module.exports = (actions, routes) => { const data = Immutable.List([ diff --git a/app/utils/kefirCouchbase.js b/app/utils/kefirCouchbase.js new file mode 100644 index 0000000..678226c --- /dev/null +++ b/app/utils/kefirCouchbase.js @@ -0,0 +1,66 @@ +import Kefir from 'kefir'; +import U from './utils'; +import {manager as Manager, ReactCBLite} from 'react-native-couchbase-lite' + +class ActionResult { + static fromMap(map) { + const pool = U.actionPool(); + return new ActionResult(pool, map(pool)); + } + constructor(action, result) { + this.action = action; + this.result = result; + } +} + +class KefirCouchbase { + constructor(url, bucketName) { + this.database = new Manager(url, bucketName); + const database = this.database; + + ['createDatabase', 'deleteDatabase', 'getAllDatabases', 'createDocument', 'deleteDocument', + 'updateDocument', 'getAllDocuments', 'getAllDocumentConflicts', 'replicate', 'getChanges', + 'getInfo', 'getDesignDocument', 'createDesignDocument', 'deleteDesignDocument', 'queryView', + 'saveAttachment', 'getAttachmentUri', 'deleteAttachment', 'latestRevision', 'activeTasks'] + .reduce((pv, cv) => { + pv[cv] = (...arr) => Kefir.fromPromise(database[cv](...arr)); + pv[cv + 'InOut'] = (stream, toParams = (x => [x])) => U.inOut(stream, stream.flatMap(v => Kefir.fromPromise(database[cv](...toParams(v))))); + return pv; + }, this); + } +} + +const store = { + initListener: ActionResult.fromMap(pool$ => pool$.flatMap((arr) => Kefir.fromCallback(cb => ReactCBLite.init(...arr, cb)))), + getDatabase(url, bucketName) { return new KefirCouchbase(url, bucketName); } +} + +export { store as default }; + + +const database = new Manager('http://localhost:5984/', 'blocpower'); +database + .createDatabase() + .then((res) => { + database.getDesignDocument('main').then(d => { + const create = () => { + database.createDesignDocument('main', { + views: { + language: 'javascript', + by_type: {map: 'function (doc) { if (doc.type) { emit(doc.type, null); } }'}, + by_parent_id_type: {map: 'function (doc) { if (doc.parent_id && doc.type) { emit([doc.parent_id, doc.type], null); } }'} + } + }); + }; + + if (d) { + database.deleteDesignDocument('main', d._rev).then(create); + } else { + create(); + } + }); + database.replicate('http://couchbase1.sbdev.io:4984/blocpoweraudit'); + }); + +const kefirDatabase = ['createDatabase', 'deleteDatabase', 'getAllDatabases', 'createDocument', 'deleteDocument', 'updateDocument', 'getAllDocuments', 'getAllDocumentConflicts', 'replicate', 'getChanges', 'getInfo', 'getDesignDocument', 'createDesignDocument', 'deleteDesignDocument', 'queryView', 'saveAttachment', 'getAttachmentUri', 'deleteAttachment', 'latestRevision', 'activeTasks'] + .reduce((pv, cv) => { pv[cv] = (...arr) => Kefir.fromPromise(database[cv](...arr)); return pv; } , {}); diff --git a/app/utils/utils.js b/app/utils/utils.js index d075e34..85f9c29 100644 --- a/app/utils/utils.js +++ b/app/utils/utils.js @@ -1,4 +1,5 @@ import Kefir from 'kefir'; +import { Alert } from 'react-native'; const al = stream => { stream.onValue(v => Alert.alert('stuff', JSON.stringify(v), [{text:'OK'}])); return stream; }; @@ -14,4 +15,29 @@ export default class Utils { return state; }, {}) } + + static inOut(input$, output$) { + return Kefir.combine([input$, output$], [], (input, output) => ({input: input, output: output})); + } + + static actionPool() { + return this.addPoolCall(Kefir.pool()); + } + + static addPoolCall(pool$) { + pool$.call = (...arr) => { pool$.plug(Kefir.constant(arr)); } + return pool$; + } + + static msgAlert(stream$, message, title='Stream Value') { + return stream$.map(value => { Alert.alert(title, message); return value; }) + } + + static jsonAlert(stream$, title='Stream Value') { + return stream$.map(value => { Alert.alert(title, JSON.stringify(value)); return value; }) + } + + static popJson(obj, title="Hai") { + Alert.alert(title, obj ? JSON.stringify(obj) : 'empty'); + } } \ No newline at end of file diff --git a/index.android.js b/index.android.js index a09f3ff..e8b1224 100644 --- a/index.android.js +++ b/index.android.js @@ -10,6 +10,8 @@ import { } from 'react-native'; import Kefir from 'kefir'; +import U from './app/utils/utils'; +import Entities from './app/entities'; import Toolbar from './app/components/Toolbar'; import routes from './app/routes'; @@ -18,39 +20,87 @@ import getActions from './app/actions/allActions'; import getStores from './app/stores/allStores'; import _ from 'lodash'; +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#111111' + }, + ha: { + marginTop: 100, + color: '#ffffff' + } +}); + const actions = getActions(); -const stores = getStores(actions, routes); +const stores$ = getStores(actions, routes); +const setup$ = stores$.map(stores => { + stores.building.errors.onError(e => { + Alert.alert('Error', JSON.stringify(e), [ {text: 'OK'}]); + }) -stores.building.errors.onError(e => { - Alert.alert('Error', JSON.stringify(e), [ {text: 'OK'}]); -}) + stores.nav.navigator.onValue(navigator => { + const window$ = stores.nav.stack.slidingWindow(2, 2); + window$.filter(stacks => stacks[0].size < stacks[1].size).onValue(routes => navigator.push(routes[1].get(0))); + window$.filter(stacks => stacks[0].size > stacks[1].size).onValue(routes => navigator.pop()); + }); -stores.nav.navigator.onValue(navigator => { - const window$ = stores.nav.stack.slidingWindow(2, 2); - window$.filter(stacks => stacks[0].size < stacks[1].size).onValue(routes => navigator.push(routes[1].get(0))); - window$.filter(stacks => stacks[0].size > stacks[1].size).onValue(routes => navigator.pop()); -}); -actions.nav.push.raw.plug(actions.building.newObj.raw.map(() => 'buildings.children.new')); -actions.nav.push.raw.plug(actions.building.editObj.raw.map(() => 'buildings.children.edit')); + for (let entity of Entities.entityList) { + actions.nav.push.raw.plug(actions[entity].newObj.raw.map(() => `${entity}.children.new`)); + actions.nav.push.raw.plug(actions[entity].editObj.raw.map(() => `${entity}.children.edit`)); + } -BackAndroid.addEventListener('hardwareBackPress', () => { actions.nav.hardwareback.handler(); return true; }); + BackAndroid.addEventListener('hardwareBackPress', () => { actions.nav.hardwareback.handler(); return true; }); -stores.nav.close.onValue(() => { - BackAndroid.exitApp(); -}); + stores.nav.close.onValue(() => { + BackAndroid.exitApp(); + }); + return stores; +}) class audit extends Component { + constructor(props) { + super(props); + + this.watches = []; + } + + addWatch(stream$, watcher, bound = false) { + if (bound == false) { + stream$.onValue(watcher); + } + this.watches.push({ stream$: stream$, watcher: watcher }); + } + + componentDidMount() { + this.addWatch(setup$, this.mapState.bind(this)); + } + + componentWillUnmount() { + this.watches && this.watches.forEach((watch,i) => { + watch[0].offValue(watch[1]); + }); + } + + mapState(stores) { + this.setState({ + stores: stores + }) + } + render() { + if (!this.state) { + return (); + } let route = null; - stores.nav.current.take(1).onValue((r) => { route = r; }); + this.state.stores.nav.current.take(1).onValue((r) => { route = r; }); return ( } + navigationBar={ } renderScene={(route, navigator) => ( - + ) }> @@ -58,15 +108,4 @@ class audit extends Component { } } -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#111111' - }, - ha: { - marginTop: 100, - color: '#ffffff' - } -}); - AppRegistry.registerComponent('audit', () => audit); -- GitLab From 5cd704bf4decd0ed06d81a881d1ca98f2577bc09 Mon Sep 17 00:00:00 2001 From: Andy Brummer Date: Fri, 10 Jun 2016 16:05:41 -0500 Subject: [PATCH 04/46] Pared down the entity edit files. Added container classes to encapsulate the entity to form mapping. --- app/components/entityEdit.js | 52 +++++++++++++++++++++++++++ app/routes.js | 32 ++++++++++++++--- app/scenes/boilerEdit.js | 21 +++++++++++ app/scenes/buildingEdit.js | 68 ++++++++---------------------------- app/scenes/clientEdit.js | 64 +++++++-------------------------- 5 files changed, 129 insertions(+), 108 deletions(-) create mode 100644 app/components/entityEdit.js create mode 100644 app/scenes/boilerEdit.js diff --git a/app/components/entityEdit.js b/app/components/entityEdit.js new file mode 100644 index 0000000..ec868e7 --- /dev/null +++ b/app/components/entityEdit.js @@ -0,0 +1,52 @@ +import React, { Component } from 'react' +import {View, Text, TextInput} from 'react-native'; +import _ from 'lodash'; + +import styles from '../assets/styles'; + +export default class EntityEdit extends Component { + constructor(props) { + super(props); + this.boundState = this.mapState.bind(this); + } + + componentDidMount() { + this.props.stores[this.props.route.entityName].edit.onValue(this.boundState); + for(var loadAction of (this.props.route.loadActions || [])) { + let handler = _.get(this.props.actions, loadAction).handler; + handler && handler(); + } + } + + componentWillUnmount() { + this.props.stores[this.props.route.entityName].edit.offValue(this.boundState); + } + + mapState(obj) { + this.setState({ + map: obj + }) + } + + render() { + if (this.state) { + return (); + } else { + return (); + } + } +} + +export class TextEdit extends Component { + render() { + return ( + + {this.props.label} + { this.props.fieldNames.map((fieldName, index) => ())} + ); + } +} diff --git a/app/routes.js b/app/routes.js index 6badef1..57e010c 100644 --- a/app/routes.js +++ b/app/routes.js @@ -1,6 +1,6 @@ export default { building: { - initialRoute: true, + initialRoute: false, title: 'Buildings', single: 'Building', @@ -25,7 +25,7 @@ export default { } }, client: { - initialRoute: true, + initialRoute: false, title: 'Clients', single: 'Client', @@ -48,6 +48,30 @@ export default { actions: [ { icon: 'save', action: 'client.save'}] } } - } - + }, + boiler: { + initialRoute: true, + + title: 'Boilers', + single: 'Boiler', + newLabel: 'New Boiler', + component: require('./scenes/entityList').default, + dataSource: 'boiler.all', + entityName: 'boiler', + listIcon: 'build', + children: { + new: { + title: "New Boiler", + component: require('./scenes/boilerEdit').default, + entityName: 'boiler', + actions: [ { icon: 'save', action: 'boiler.create'}] + }, + edit: { + title: "Edit Boiler", + component: require('./scenes/boilerEdit').default, + entityName: 'boiler', + actions: [ { icon: 'save', action: 'boiler.save'}] + } + } + } } \ No newline at end of file diff --git a/app/scenes/boilerEdit.js b/app/scenes/boilerEdit.js new file mode 100644 index 0000000..2876b81 --- /dev/null +++ b/app/scenes/boilerEdit.js @@ -0,0 +1,21 @@ +import React, { Component } from 'react' +import { Text, View } from 'react-native'; +import EntityEdit, { TextEdit } from '../components/entityEdit'; + +import styles from '../assets/styles'; + +class BoilerEditInfo extends Component { + render() { + return ( + + + + ); + } +} + +export default class BoilerEdit extends Component { + render() { + return (); + } +} diff --git a/app/scenes/buildingEdit.js b/app/scenes/buildingEdit.js index 4eef9d3..5cd7707 100644 --- a/app/scenes/buildingEdit.js +++ b/app/scenes/buildingEdit.js @@ -1,61 +1,23 @@ import React, { Component } from 'react' -import { - Text, - View, - StyleSheet, - ScrollView, - TextInput, - TouchableOpacity, - Navigator, - Alert -} from 'react-native'; +import { Text, View } from 'react-native'; +import EntityEdit, { TextEdit } from '../components/entityEdit'; import styles from '../assets/styles'; -import U from '../utils/utils'; -import _ from 'lodash'; - -export default class BuildingEdit extends Component { - constructor(props) { - super(props); - this.boundState = this.mapState.bind(this); - } - - componentDidMount() { - this.props.stores[this.props.route.entityName].edit.onValue(this.boundState); - for(var loadAction of (this.props.route.loadActions || [])) { - let handler = _.get(this.props.actions, loadAction).handler; - handler && handler(); - } - } - - componentWillUnmount() { - this.props.stores[this.props.route.entityName].edit.offValue(this.boundState); - } - - mapState(entity) { - this.setState({ - map: entity - }) - } +class BuildingEditInfo extends Component { render() { - const that = this; - const TextField = (label, fieldNames) => ( - - {label} - { fieldNames.map((fieldName, index) => ())} - ); - return ( - {TextField('Name', ['name']) } - {TextField('Address', ['address1', 'address2']) } - {TextField('City', ['city']) } - {TextField('State', ['state']) } - {TextField('Zip Code', ['zipcode']) } + + + + + ); } -} \ No newline at end of file +} + +export default class BuildingEdit extends Component { + render() { + return (); + } +} diff --git a/app/scenes/clientEdit.js b/app/scenes/clientEdit.js index 119cf9b..f11b329 100644 --- a/app/scenes/clientEdit.js +++ b/app/scenes/clientEdit.js @@ -1,59 +1,21 @@ import React, { Component } from 'react' -import { - Text, - View, - StyleSheet, - ScrollView, - TextInput, - TouchableOpacity, - Navigator, - Alert -} from 'react-native'; +import { Text, View } from 'react-native'; +import EntityEdit, { TextEdit } from '../components/entityEdit'; import styles from '../assets/styles'; -import U from '../utils/utils'; -import _ from 'lodash'; - -export default class ClientEdit extends Component { - constructor(props) { - super(props); - this.boundState = this.mapState.bind(this); - } - - componentDidMount() { - this.props.stores[this.props.route.entityName].edit.onValue(this.boundState); - for(var loadAction of (this.props.route.loadActions || [])) { - let handler = _.get(this.props.actions, loadAction).handler; - handler && handler(); - } - } - - componentWillUnmount() { - this.props.stores[this.props.route.entityName].edit.offValue(this.boundState); - } - - mapState(obj) { - this.setState({ - map: obj - }) - } +class ClientEditInfo extends Component { render() { - const that = this; - const TextField = (label, fieldNames) => ( - - {label} - { fieldNames.map((fieldName, index) => ())} - ); - return ( - {TextField('Name', ['name']) } - {TextField('Contact', ['first', 'last']) } - {TextField('Phone', ['phone']) } + + + ); } -} \ No newline at end of file +} + +export default class ClientEdit extends Component { + render() { + return (); + } +} -- GitLab From 0da58e753ce801467cad4cff3994e5612d02bbd6 Mon Sep 17 00:00:00 2001 From: Andy Brummer Date: Fri, 10 Jun 2016 17:28:51 -0500 Subject: [PATCH 05/46] Playing with the colors a little bit. --- app/assets/styles.js | 17 +++++++++++++---- app/components/Toolbar.js | 7 ++++--- app/entities.js | 5 ++++- app/routes.js | 4 ++-- app/scenes/buildingEdit.js | 6 ++++-- app/scenes/entityList.js | 17 +++++++++-------- 6 files changed, 36 insertions(+), 20 deletions(-) diff --git a/app/assets/styles.js b/app/assets/styles.js index f3f95a9..4580055 100644 --- a/app/assets/styles.js +++ b/app/assets/styles.js @@ -9,11 +9,19 @@ export default StyleSheet.create({ flex: 1, alignItems: 'flex-start', justifyContent: 'flex-start', - flexDirection: 'row' + flexDirection: 'row', + backgroundColor: '#FFFFFF', + borderWidth: 1, + borderStyle: 'solid', + borderColor: '#DDDDDD', + elevation: 2, }, - rowAvatar: { + rowAvatarContainer: { flex: 1 }, + rowAvatar: { + backgroundColor: '#f7f7f7' + }, rowText: { fontSize: 19, flex: 8 @@ -33,10 +41,11 @@ export default StyleSheet.create({ mainContainer: { flex: 1, padding: 30, - marginTop: 65, + marginTop: 56, flexDirection: 'column', justifyContent: 'center', - backgroundColor: '#48BBEC' + backgroundColor: '#F8F8F8', + elevation: 0 }, searchInput: { height: 50, diff --git a/app/components/Toolbar.js b/app/components/Toolbar.js index f23b0bf..c0a55c5 100644 --- a/app/components/Toolbar.js +++ b/app/components/Toolbar.js @@ -1,5 +1,5 @@ import React, { Component, PropTypes } from 'react'; -import { Toolbar as MaterialToolbar } from 'react-native-material-design'; +import { Toolbar as MaterialToolbar, PRIMARY_COLORS } from 'react-native-material-design'; import _ from 'lodash'; export default class Tb extends Component { @@ -30,11 +30,12 @@ export default class Tb extends Component { render() { return (); } } diff --git a/app/entities.js b/app/entities.js index a67be2d..ba85763 100644 --- a/app/entities.js +++ b/app/entities.js @@ -1,4 +1,7 @@ +const entities = {building: {children: ['boiler', 'room']}, client:{children: ['building']}, boiler: {}, room: {} }; + export default { entityActions: ['create', 'save', 'destroy', 'newObj', 'editObj', 'editField'], - entityList: ['building', 'client', 'boiler', 'room'] + entities: entities, + entityList: Object.keys(entities) }; \ No newline at end of file diff --git a/app/routes.js b/app/routes.js index 57e010c..4b4e57b 100644 --- a/app/routes.js +++ b/app/routes.js @@ -1,6 +1,6 @@ export default { building: { - initialRoute: false, + initialRoute: true, title: 'Buildings', single: 'Building', @@ -50,7 +50,7 @@ export default { } }, boiler: { - initialRoute: true, + initialRoute: false, title: 'Boilers', single: 'Boiler', diff --git a/app/scenes/buildingEdit.js b/app/scenes/buildingEdit.js index 5cd7707..2bad09f 100644 --- a/app/scenes/buildingEdit.js +++ b/app/scenes/buildingEdit.js @@ -6,7 +6,7 @@ import styles from '../assets/styles'; class BuildingEditInfo extends Component { render() { - return ( + return ( @@ -18,6 +18,8 @@ class BuildingEditInfo extends Component { export default class BuildingEdit extends Component { render() { - return (); + return ( + + ); } } diff --git a/app/scenes/entityList.js b/app/scenes/entityList.js index 304d4ec..1750e86 100644 --- a/app/scenes/entityList.js +++ b/app/scenes/entityList.js @@ -8,7 +8,7 @@ import { TouchableOpacity, Alert } from 'react-native'; -import { Divider, Avatar } from 'react-native-material-design'; +import { Divider, Avatar, Card, Subheader } from 'react-native-material-design'; import _ from 'lodash'; import ActionButton from 'react-native-action-button'; @@ -50,10 +50,9 @@ export default class EntityList extends Component { render() { var lists = this.state && this.state.value && Object.values(this.state.value.toJS()).map((item, index) => ( - - - + + {item.name} @@ -66,11 +65,13 @@ export default class EntityList extends Component { return ( - - {lists} + + + {lists} + - - + -- GitLab From 09670be51c5e4019b7e7e4e8a594014e89f11ffe Mon Sep 17 00:00:00 2001 From: Andy Brummer Date: Sat, 11 Jun 2016 02:43:48 -0500 Subject: [PATCH 06/46] Making some good progress on adding object tree forms. --- app/assets/styles.js | 4 ++-- app/components/entityEdit.js | 9 +++++++-- app/entities.js | 4 ++-- app/routes.js | 25 +++++++++++++++++++++++++ app/scenes/buildingEdit.js | 32 ++++++++++++++++++++++++++++---- app/scenes/entityList.js | 6 +++--- app/scenes/roomEdit.js | 21 +++++++++++++++++++++ app/stores/entityStore.js | 30 ++++++++++++++++++++---------- index.android.js | 15 ++++++++++++--- 9 files changed, 120 insertions(+), 26 deletions(-) create mode 100644 app/scenes/roomEdit.js diff --git a/app/assets/styles.js b/app/assets/styles.js index 4580055..2c6fb18 100644 --- a/app/assets/styles.js +++ b/app/assets/styles.js @@ -47,7 +47,7 @@ export default StyleSheet.create({ backgroundColor: '#F8F8F8', elevation: 0 }, - searchInput: { + entityInput: { height: 50, padding: 4, marginRight: 5, @@ -55,7 +55,7 @@ export default StyleSheet.create({ borderWidth: 1, borderColor: 'white', borderRadius: 8, - color: 'white', + color: '#585858', margin: 5 }, buttonText: { diff --git a/app/components/entityEdit.js b/app/components/entityEdit.js index ec868e7..cb5cb7b 100644 --- a/app/components/entityEdit.js +++ b/app/components/entityEdit.js @@ -30,7 +30,12 @@ export default class EntityEdit extends Component { render() { if (this.state) { - return (); + return (); } else { return (); } @@ -46,7 +51,7 @@ export class TextEdit extends Component { key={index} value={this.props.entityMap.get(fieldName)} onChangeText={this.props.editHandler(fieldName)} - style={styles.searchInput}/>))} + style={styles.entityInput}/>))} ); } } diff --git a/app/entities.js b/app/entities.js index ba85763..b928793 100644 --- a/app/entities.js +++ b/app/entities.js @@ -1,7 +1,7 @@ -const entities = {building: {children: ['boiler', 'room']}, client:{children: ['building']}, boiler: {}, room: {} }; +const entities = {building: {children: ['boiler', 'room']}, client:{children: ['building']}, boiler: {}, room: {}, controls: {}, dhw: {}, sensor: {}, window:{}, radiators:{}, baseboards: {}, retrofits: {} }; export default { - entityActions: ['create', 'save', 'destroy', 'newObj', 'editObj', 'editField'], + entityActions: ['create', 'save', 'destroy', 'newObj', 'editObj', 'editField', 'loadChildren'], entities: entities, entityList: Object.keys(entities) }; \ No newline at end of file diff --git a/app/routes.js b/app/routes.js index 4b4e57b..908bc67 100644 --- a/app/routes.js +++ b/app/routes.js @@ -73,5 +73,30 @@ export default { actions: [ { icon: 'save', action: 'boiler.save'}] } } + }, + room: { + initialRoute: false, + + title: 'Rooms', + single: 'Room', + newLabel: 'New Room', + component: require('./scenes/entityList').default, + dataSource: 'room.all', + entityName: 'room', + listIcon: 'build', + children: { + new: { + title: "New Room", + component: require('./scenes/roomEdit').default, + entityName: 'room', + actions: [ { icon: 'save', action: 'room.create'}] + }, + edit: { + title: "Edit Room", + component: require('./scenes/roomEdit').default, + entityName: 'room', + actions: [ { icon: 'save', action: 'room.save'}] + } + } } } \ No newline at end of file diff --git a/app/scenes/buildingEdit.js b/app/scenes/buildingEdit.js index 2bad09f..4e0f56e 100644 --- a/app/scenes/buildingEdit.js +++ b/app/scenes/buildingEdit.js @@ -1,12 +1,14 @@ import React, { Component } from 'react' -import { Text, View } from 'react-native'; +import { Text, View, ScrollView } from 'react-native'; +import ActionButton from 'react-native-action-button'; import EntityEdit, { TextEdit } from '../components/entityEdit'; +import Icon from 'react-native-vector-icons/MaterialIcons'; import styles from '../assets/styles'; class BuildingEditInfo extends Component { render() { - return ( + return ( @@ -16,10 +18,32 @@ class BuildingEditInfo extends Component { } } -export default class BuildingEdit extends Component { +class AddChildrenButton extends Component { + render() { + if (this.props.entityType && this.props.entityType.children) { + return ( + { this.props.entityType.children.map((entityName, i) => ( + + )) } + ); + } + } +} + +class BuildingEditScreen extends Component { render() { return ( - + + + + ); } } + +export default class BuildingEdit extends Component { + render() { + return (); + } +} diff --git a/app/scenes/entityList.js b/app/scenes/entityList.js index 1750e86..8a82c24 100644 --- a/app/scenes/entityList.js +++ b/app/scenes/entityList.js @@ -9,10 +9,10 @@ import { Alert } from 'react-native'; import { Divider, Avatar, Card, Subheader } from 'react-native-material-design'; -import _ from 'lodash'; - -import ActionButton from 'react-native-action-button'; import Icon from 'react-native-vector-icons/MaterialIcons'; +import ActionButton from 'react-native-action-button'; + +import _ from 'lodash'; import styles from '../assets/styles'; export default class EntityList extends Component { diff --git a/app/scenes/roomEdit.js b/app/scenes/roomEdit.js new file mode 100644 index 0000000..a4df84a --- /dev/null +++ b/app/scenes/roomEdit.js @@ -0,0 +1,21 @@ +import React, { Component } from 'react' +import { Text, View } from 'react-native'; +import EntityEdit, { TextEdit } from '../components/entityEdit'; + +import styles from '../assets/styles'; + +class RoomEditInfo extends Component { + render() { + return ( + + + + ); + } +} + +export default class RoomEdit extends Component { + render() { + return (); + } +} diff --git a/app/stores/entityStore.js b/app/stores/entityStore.js index a6ddd0d..7969fad 100644 --- a/app/stores/entityStore.js +++ b/app/stores/entityStore.js @@ -3,10 +3,13 @@ import Kefir from 'kefir'; import uuid from 'node-uuid'; import U from '../utils/utils'; -function getEdit(actions) { +function getEdit(actions, type) { const edit = Immutable.Map({}); const editStream$ = Kefir.merge([ - actions.newObj.raw.map((initialObject) => (obj) => Immutable.Map(initialObject || {})), + actions.newObj.raw + .map(initialObject => Immutable.Map(initialObject || {})) + .map(initialMap => initialMap.set('_id', uuid.v4()).set('type', type)) + .map((initialMap) => (obj) => initialMap), actions.editObj.raw.map((current) => (obj) => Immutable.Map(current)), actions.editField.raw.map((fieldData) => (obj) => obj.set(fieldData.key, fieldData.value)) ]); @@ -16,18 +19,15 @@ function getEdit(actions) { } module.exports = (actions, kd, type) => { - const [edit$, editStream$] = getEdit(actions); + const [edit$, editStream$] = getEdit(actions, type); const list = Immutable.OrderedMap(); const startValue$ = kd.queryView('main', 'by_type', {include_docs: true, key: type}) .take(1) .map(list => list.rows.reduce((obj, field) => obj.set(field.id, field.doc), Immutable.OrderedMap())); - const getCreate$ = edit$.sampledBy(actions.create.raw) - .map(b => b.toJS()) - .map(b => Object.assign(b, {_id: uuid.v4(), type: type })); - const getEdit$ = edit$.sampledBy(actions.save.raw) - .map(b => b.toJS()); + const getCreate$ = edit$.sampledBy(actions.create.raw).map(b => b.toJS()); + const getEdit$ = edit$.sampledBy(actions.save.raw).map(b => b.toJS()); const setSingle = stream => stream.map(obj => Object.assign(obj.input, { _rev: obj.output.rev })) @@ -45,11 +45,21 @@ module.exports = (actions, kd, type) => { ]); const listState$ = listStream$.scan((current, change) => change(current), list); - const errors$ = Kefir.merge([listStream$, editStream$]); + + + //Load children into a stream given an object with an _id field, an immutable map with an _id key, or just the id. + const loadChildren$ = actions.loadChildren.raw.toProperty(); + const children$ = loadChildren$.map(obj => !obj ? Kefir.constant(Immutable.OrderedMap()) : loadChildren$ + .map(obj => obj._id || (obj.get && obj.get('_id')) || obj) + .flatMap(id => kd.queryView('main', 'by_parent_id_type', {include_docs: true, key: [id]})) + .map(list => list.rows.reduce((obj, field) => obj.set(field.id, field.doc), Immutable.OrderedMap()))); + + const errors$ = Kefir.merge([listStream$, editStream$, loadChildren$]); return { all: listState$, edit: edit$, - errors: errors$ + errors: errors$, + children: loadChildren$ } }; \ No newline at end of file diff --git a/index.android.js b/index.android.js index e8b1224..d8d7a87 100644 --- a/index.android.js +++ b/index.android.js @@ -8,12 +8,11 @@ import { Alert, BackAndroid } from 'react-native'; +import Toolbar from './app/components/Toolbar'; import Kefir from 'kefir'; import U from './app/utils/utils'; import Entities from './app/entities'; - -import Toolbar from './app/components/Toolbar'; import routes from './app/routes'; import getActions from './app/actions/allActions'; @@ -47,6 +46,9 @@ const setup$ = stores$.map(stores => { for (let entity of Entities.entityList) { actions.nav.push.raw.plug(actions[entity].newObj.raw.map(() => `${entity}.children.new`)); actions.nav.push.raw.plug(actions[entity].editObj.raw.map(() => `${entity}.children.edit`)); + //Load children when we activate a new object, or edit an existing one. + actions[entity].loadChildren.raw.plug(actions[entity].newObj.raw); + actions[entity].loadChildren.raw.plug(actions[entity].editObj.raw); } BackAndroid.addEventListener('hardwareBackPress', () => { actions.nav.hardwareback.handler(); return true; }); @@ -54,6 +56,13 @@ const setup$ = stores$.map(stores => { stores.nav.close.onValue(() => { BackAndroid.exitApp(); }); + //Load a building to make it easier to work on that page. + setTimeout(() => + stores.building.all.onValue(bl => + actions.building.editObj.handlerConst(bl.first())() + ), 200 + ) + return stores; }) @@ -100,7 +109,7 @@ class audit extends Component { navigationBar={ } renderScene={(route, navigator) => ( - + ) }> -- GitLab From 8fb1a6ca84f543bff2f9f97d05b3f9e4d945af76 Mon Sep 17 00:00:00 2001 From: Andy Brummer Date: Tue, 14 Jun 2016 01:04:40 -0500 Subject: [PATCH 07/46] Navigation, save, and database startup working much better now. Added page for every major entity type. --- app/components/Toolbar.js | 39 +++++++--- app/components/entityEdit.js | 92 +++++++++++++++++++---- app/entities.js | 15 +++- app/routes.js | 124 ++++++++----------------------- app/scenes/baseboardsEdit.js | 20 +++++ app/scenes/boilerEdit.js | 3 +- app/scenes/buildingEdit.js | 34 ++------- app/scenes/clientEdit.js | 2 +- app/scenes/controlsEdit.js | 20 +++++ app/scenes/dhwEdit.js | 20 +++++ app/scenes/entityList.js | 33 ++++---- app/scenes/radiatorEdit.js | 20 +++++ app/scenes/retrofitsEdit.js | 20 +++++ app/scenes/roomEdit.js | 3 +- app/scenes/sensorEdit.js | 20 +++++ app/scenes/windowEdit.js | 20 +++++ app/stores/allStores.js | 16 ++-- app/stores/dataStore.js | 14 +++- app/stores/entityStore.js | 64 ++++++++-------- app/stores/navStore.js | 2 +- app/utils/kefirCouchbase.js | 17 ++++- app/utils/subscriptionTracker.js | 39 ++++++++++ app/utils/utils.js | 69 ++++++++++++++++- index.android.js | 57 ++++++-------- package.json | 1 + 25 files changed, 511 insertions(+), 253 deletions(-) create mode 100644 app/scenes/baseboardsEdit.js create mode 100644 app/scenes/controlsEdit.js create mode 100644 app/scenes/dhwEdit.js create mode 100644 app/scenes/radiatorEdit.js create mode 100644 app/scenes/retrofitsEdit.js create mode 100644 app/scenes/sensorEdit.js create mode 100644 app/scenes/windowEdit.js create mode 100644 app/utils/subscriptionTracker.js diff --git a/app/components/Toolbar.js b/app/components/Toolbar.js index c0a55c5..a35345a 100644 --- a/app/components/Toolbar.js +++ b/app/components/Toolbar.js @@ -1,31 +1,46 @@ import React, { Component, PropTypes } from 'react'; import { Toolbar as MaterialToolbar, PRIMARY_COLORS } from 'react-native-material-design'; +import { subscriptionTracker as tracker} from '../utils/utils'; import _ from 'lodash'; export default class Tb extends Component { + constructor(props) { + super(props); + this.tracker = tracker(); + } componentDidMount() { - this.props.stores.nav.stack.onValue(this.handleRoute.bind(this)); + this.tracker.addSub( this.props.stores.nav.stack, this.handleRoute.bind(this)); } componentWillUnmount() { - this.props.stores.nav.stack.offValue(this.handleRoute.bind(this)); + this.tracker.cleanUp(); } handleRoute(stack) { + if (!stack) { + return; + } + const route = stack.get(0); + //Connect listeners to all the response streams for the actions + if (route.actions) for (action of route.actions) { + this.tracker.addSub(_.get(this.props.stores[route.entityName], action.response), this.props.actions.nav.homeback.handler); + } + //Set the route data for the toolbar this.setState({ - route: stack.get(0), + route: route, isChild: stack.size > 1 }); } - getIcons() { - const routeIcons = this.state && this.state.route.actions && this.state.route.actions.map((action) => { - const actionReturn$ = _.get(this.props.actions, action.action).raw.onValue(this.props.actions.nav.homeback.handler); - return {icon: action.icon, - onPress: _.get(this.props.actions, action.action).handler - }; - }); - return routeIcons; + mapActions(f) { + if (!this.state || !this.state.route.actions) { + return; + } + return this.state.route.actions.map(f); + } + + getEntityAction(action) { + return _.get(this.props.actions[this.state.route.entityName], action); } render() { @@ -33,7 +48,7 @@ export default class Tb extends Component { title={(this.state && this.state.route.title) || 'Welcome' } icon={ (this.state && this.state.isChild) ? 'arrow-back' : 'group-work' } onIconPress={ this.props.actions.nav.homeback.handler } - actions={this.getIcons()} + actions={ this.mapActions((action) => ({icon: action.icon, onPress: this.getEntityAction(action.action).handler })) } rightIconStyle={{ margin: 20 }} overrides={{backgroundColor: '#585858', leftIconColor: '#f7301d'}} />); diff --git a/app/components/entityEdit.js b/app/components/entityEdit.js index cb5cb7b..8c7c4c5 100644 --- a/app/components/entityEdit.js +++ b/app/components/entityEdit.js @@ -1,47 +1,113 @@ import React, { Component } from 'react' -import {View, Text, TextInput} from 'react-native'; +import {View, Text, TextInput, ScrollView, TouchableOpacity } from 'react-native'; +import { Avatar} from 'react-native-material-design'; +import ActionButton from 'react-native-action-button'; +import Icon from 'react-native-vector-icons/MaterialIcons'; + +import Kefir from 'kefir'; import _ from 'lodash'; +import U, { subscriptionTracker, JsonText, KeyText } from '../utils/utils'; import styles from '../assets/styles'; export default class EntityEdit extends Component { constructor(props) { super(props); this.boundState = this.mapState.bind(this); + this.tracker = subscriptionTracker(); } componentDidMount() { - this.props.stores[this.props.route.entityName].edit.onValue(this.boundState); - for(var loadAction of (this.props.route.loadActions || [])) { - let handler = _.get(this.props.actions, loadAction).handler; - handler && handler(); - } + this.store = this.props.stores[this.props.route.entityName]; + this.actions = this.props.actions[this.props.route.entityName]; + + this.tracker.addSub(U.mergeToSingleObject([ + this.store.edit.map(v => ({ key: 'map', value: v})), + this.store.children.map(v => ({ key: 'children', value: v})) + ]), this.boundState); + + this.tracker.addSub( + this.store.edit.sampledBy(this.props.stores.nav.stack), + ((values) => this.actions.loadChildren.raw.plug(Kefir.constant(values))).bind(this) + ); } componentWillUnmount() { - this.props.stores[this.props.route.entityName].edit.offValue(this.boundState); + this.tracker.cleanUp(); } - mapState(obj) { - this.setState({ - map: obj - }) + mapState(state) { + this.setState(state); + } + + getEntity() { + return this.state && this.state.map; } render() { if (this.state) { - return (); } else { - return (); + return (Loading Error); } } } +class AddChildrenButton extends Component { + render() { + if (this.props.entityType && this.props.entityType.children) { + return ( + { this.props.entityType.children.map((entityName, i) => ( + + )) } + ); + } + } +} + +class EntityEditScreen extends Component { + mapChildren(f) { + if (!this.props || !this.props.children || !this.props.children.toArray) { + return; + } + const array = this.props.children.toArray(); + if (array) { + return array.map(f); + } + } + render() { + return ( + + + + {this.mapChildren(item => { + return ( + + + + + {item.name} + + + + + ); + })} + + + + ); + } +} + export class TextEdit extends Component { render() { return ( diff --git a/app/entities.js b/app/entities.js index b928793..7a07cfe 100644 --- a/app/entities.js +++ b/app/entities.js @@ -1,7 +1,18 @@ -const entities = {building: {children: ['boiler', 'room']}, client:{children: ['building']}, boiler: {}, room: {}, controls: {}, dhw: {}, sensor: {}, window:{}, radiators:{}, baseboards: {}, retrofits: {} }; +const entities = { + client:{children: ['building']}, + building: {children: ['boiler', 'room', 'controls', 'dhw', 'sensor', 'radiator', 'baseboards', 'retrofits']}, + room: {children: ['window']}, + boiler: {children: []}, + controls: {children: []}, + dhw: {children: []}, + sensor: {children: []}, + window:{children: []}, + radiator:{children: []}, + baseboards: {children: []}, + retrofits: {children: []} }; export default { - entityActions: ['create', 'save', 'destroy', 'newObj', 'editObj', 'editField', 'loadChildren'], + entityActions: ['newObj', 'editObj', 'saveNew', 'saveEdit', 'destroy', 'editField', 'loadChildren'], entities: entities, entityList: Object.keys(entities) }; \ No newline at end of file diff --git a/app/routes.js b/app/routes.js index 908bc67..448242d 100644 --- a/app/routes.js +++ b/app/routes.js @@ -1,102 +1,44 @@ -export default { - building: { - initialRoute: true, +import entityList from './scenes/entityList'; - title: 'Buildings', - single: 'Building', - newLabel: 'New Building', - component: require('./scenes/entityList').default, - dataSource: 'building.all', - entityName: 'building', - listIcon: 'account-balance', - children: { - new: { - title: "New Building", - component: require('./scenes/buildingEdit').default, - entityName: 'building', - actions: [ { icon: 'save', action: 'building.create'}] - }, - edit: { - title: "Edit Building", - component: require('./scenes/buildingEdit').default, - entityName: 'building', - actions: [ { icon: 'save', action: 'building.save'}] - } - } - }, - client: { - initialRoute: false, +function getEntityRoute (entityName, title, name, icon, childComponent, initialRoute) { + return { + initialRoute: initialRoute, - title: 'Clients', - single: 'Client', - newLabel: 'New Client', - component: require('./scenes/entityList').default, - dataSource: 'client.all', - entityName: 'client', - listIcon: 'supervisor-account', + title: title, + single: name, + newLabel: 'New ' + name, + component: entityList, + dataSource: entityName + '.all', + entityName: entityName, + listIcon: icon, children: { new: { - title: "New Client", - component: require('./scenes/clientEdit').default, - entityName: 'client', - actions: [ { icon: 'save', action: 'client.create'}] + title: 'New ' + name, + component: childComponent, + entityName: entityName, + actions: [ { icon: 'save', action: 'saveNew', response: 'newSaved'}] }, edit: { - title: "Edit Client", - component: require('./scenes/clientEdit').default, - entityName: 'client', - actions: [ { icon: 'save', action: 'client.save'}] + title: 'Edit ' + name, + component: childComponent, + entityName: entityName, + actions: [ { icon: 'save', action: 'saveEdit', response: 'editSaved'}] } } - }, - boiler: { - initialRoute: false, + }; +} - title: 'Boilers', - single: 'Boiler', - newLabel: 'New Boiler', - component: require('./scenes/entityList').default, - dataSource: 'boiler.all', - entityName: 'boiler', - listIcon: 'build', - children: { - new: { - title: "New Boiler", - component: require('./scenes/boilerEdit').default, - entityName: 'boiler', - actions: [ { icon: 'save', action: 'boiler.create'}] - }, - edit: { - title: "Edit Boiler", - component: require('./scenes/boilerEdit').default, - entityName: 'boiler', - actions: [ { icon: 'save', action: 'boiler.save'}] - } - } - }, - room: { - initialRoute: false, - title: 'Rooms', - single: 'Room', - newLabel: 'New Room', - component: require('./scenes/entityList').default, - dataSource: 'room.all', - entityName: 'room', - listIcon: 'build', - children: { - new: { - title: "New Room", - component: require('./scenes/roomEdit').default, - entityName: 'room', - actions: [ { icon: 'save', action: 'room.create'}] - }, - edit: { - title: "Edit Room", - component: require('./scenes/roomEdit').default, - entityName: 'room', - actions: [ { icon: 'save', action: 'room.save'}] - } - } - } +export default { + baseboards: getEntityRoute('baseboards', 'Baseboards', 'Baseboards', 'call-to-action', require('./scenes/baseboardsEdit').default, false), + boiler: getEntityRoute('boiler', 'Boilers', 'Boiler', 'build', require('./scenes/boilerEdit').default, false), + building: getEntityRoute('building', 'Buildings', 'Building', 'account-balance', require('./scenes/buildingEdit').default, false), + client: getEntityRoute('client', 'Clients', 'Client', 'supervisor-account', require('./scenes/clientEdit').default, true), + controls: getEntityRoute('controls', 'Controls', 'Controlls', 'power-settings-new', require('./scenes/controlsEdit').default, false), + dhw: getEntityRoute('dhw', 'Water Heaters', 'Water Heater', 'home', require('./scenes/dhwEdit').default, false), + radiator: getEntityRoute('radiator', 'Radiators', 'Radiator', 'view-week', require('./scenes/radiatorEdit').default, false), + retrofits: getEntityRoute('retrofits', 'Retrofits', 'Retrofit', 'update', require('./scenes/retrofitsEdit').default, false), + room: getEntityRoute('room', 'Rooms', 'Room', 'view-quilt', require('./scenes/roomEdit').default, false), + sensor: getEntityRoute('sensor', 'Sensors', 'Sensor', 'timeline', require('./scenes/sensorEdit').default, false), + window: getEntityRoute('window', 'Windows', 'Window', 'border-all', require('./scenes/windowEdit').default, false), } \ No newline at end of file diff --git a/app/scenes/baseboardsEdit.js b/app/scenes/baseboardsEdit.js new file mode 100644 index 0000000..b25c9c9 --- /dev/null +++ b/app/scenes/baseboardsEdit.js @@ -0,0 +1,20 @@ +import React, { Component } from 'react' +import { Text, View } from 'react-native'; +import EntityEdit, { TextEdit } from '../components/entityEdit'; + +import styles from '../assets/styles'; + +class BoilerEditInfo extends Component { + render() { + return ( + + + ); + } +} + +export default class BoilerEdit extends Component { + render() { + return (); + } +} diff --git a/app/scenes/boilerEdit.js b/app/scenes/boilerEdit.js index 2876b81..b25c9c9 100644 --- a/app/scenes/boilerEdit.js +++ b/app/scenes/boilerEdit.js @@ -9,13 +9,12 @@ class BoilerEditInfo extends Component { return ( - ); } } export default class BoilerEdit extends Component { render() { - return (); + return (); } } diff --git a/app/scenes/buildingEdit.js b/app/scenes/buildingEdit.js index 4e0f56e..40d457f 100644 --- a/app/scenes/buildingEdit.js +++ b/app/scenes/buildingEdit.js @@ -1,8 +1,8 @@ import React, { Component } from 'react' -import { Text, View, ScrollView } from 'react-native'; -import ActionButton from 'react-native-action-button'; +import { Text, View, ScrollView, TouchableOpacity } from 'react-native'; import EntityEdit, { TextEdit } from '../components/entityEdit'; -import Icon from 'react-native-vector-icons/MaterialIcons'; + +import U, {JsonText, KeyText} from '../utils/utils'; import styles from '../assets/styles'; @@ -14,36 +14,12 @@ class BuildingEditInfo extends Component { - ); - } -} - -class AddChildrenButton extends Component { - render() { - if (this.props.entityType && this.props.entityType.children) { - return ( - { this.props.entityType.children.map((entityName, i) => ( - - )) } - ); - } - } -} - -class BuildingEditScreen extends Component { - render() { - return ( - - - - - ); + ); } } export default class BuildingEdit extends Component { render() { - return (); + return (); } } diff --git a/app/scenes/clientEdit.js b/app/scenes/clientEdit.js index f11b329..877ad4e 100644 --- a/app/scenes/clientEdit.js +++ b/app/scenes/clientEdit.js @@ -16,6 +16,6 @@ class ClientEditInfo extends Component { export default class ClientEdit extends Component { render() { - return (); + return (); } } diff --git a/app/scenes/controlsEdit.js b/app/scenes/controlsEdit.js new file mode 100644 index 0000000..b25c9c9 --- /dev/null +++ b/app/scenes/controlsEdit.js @@ -0,0 +1,20 @@ +import React, { Component } from 'react' +import { Text, View } from 'react-native'; +import EntityEdit, { TextEdit } from '../components/entityEdit'; + +import styles from '../assets/styles'; + +class BoilerEditInfo extends Component { + render() { + return ( + + + ); + } +} + +export default class BoilerEdit extends Component { + render() { + return (); + } +} diff --git a/app/scenes/dhwEdit.js b/app/scenes/dhwEdit.js new file mode 100644 index 0000000..b25c9c9 --- /dev/null +++ b/app/scenes/dhwEdit.js @@ -0,0 +1,20 @@ +import React, { Component } from 'react' +import { Text, View } from 'react-native'; +import EntityEdit, { TextEdit } from '../components/entityEdit'; + +import styles from '../assets/styles'; + +class BoilerEditInfo extends Component { + render() { + return ( + + + ); + } +} + +export default class BoilerEdit extends Component { + render() { + return (); + } +} diff --git a/app/scenes/entityList.js b/app/scenes/entityList.js index 8a82c24..515711e 100644 --- a/app/scenes/entityList.js +++ b/app/scenes/entityList.js @@ -11,6 +11,7 @@ import { import { Divider, Avatar, Card, Subheader } from 'react-native-material-design'; import Icon from 'react-native-vector-icons/MaterialIcons'; import ActionButton from 'react-native-action-button'; +import U, { subscriptionTracker as tracker } from '../utils/utils'; import _ from 'lodash'; import styles from '../assets/styles'; @@ -19,43 +20,41 @@ export default class EntityList extends Component { constructor(props) { super(props); - this.watches = []; - } - - addWatch(stream$, watcher, bound = false) { - if (bound == false) { - stream$.onValue(watcher); - } - this.watches.push({ stream$: stream$, watcher: watcher }); + this.tracker = tracker(); } componentDidMount() { if (this.props.route.dataSource) { - this.addWatch(_.get(this.props.stores, this.props.route.dataSource ), this.mapState.bind(this)); + this.tracker.addSub(_.get(this.props.stores, this.props.route.dataSource ), this.handleState.bind(this)); } } componentWillUnmount() { - this.watches && this.watches.forEach((watch,i) => { - watch[0].offValue(watch[1]); - }); + this.tracker.cleanUp(); } - mapState(value) { + handleState(value) { this.setState({ value: value }) } + mapState(f) { + if (!this.state || !this.state.value) { + return; + } + return Object.values(this.state.value.toJS()).map(f); + } + render() { - var lists = this.state && this.state.value && Object.values(this.state.value.toJS()).map((item, index) => ( + var lists = this.mapState((item, index) => ( - + {item.name} - + @@ -72,7 +71,7 @@ export default class EntityList extends Component { + onPress={ this.props.actions[this.props.route.entityName].newObj.handlerConst(null) }> diff --git a/app/scenes/radiatorEdit.js b/app/scenes/radiatorEdit.js new file mode 100644 index 0000000..b25c9c9 --- /dev/null +++ b/app/scenes/radiatorEdit.js @@ -0,0 +1,20 @@ +import React, { Component } from 'react' +import { Text, View } from 'react-native'; +import EntityEdit, { TextEdit } from '../components/entityEdit'; + +import styles from '../assets/styles'; + +class BoilerEditInfo extends Component { + render() { + return ( + + + ); + } +} + +export default class BoilerEdit extends Component { + render() { + return (); + } +} diff --git a/app/scenes/retrofitsEdit.js b/app/scenes/retrofitsEdit.js new file mode 100644 index 0000000..b25c9c9 --- /dev/null +++ b/app/scenes/retrofitsEdit.js @@ -0,0 +1,20 @@ +import React, { Component } from 'react' +import { Text, View } from 'react-native'; +import EntityEdit, { TextEdit } from '../components/entityEdit'; + +import styles from '../assets/styles'; + +class BoilerEditInfo extends Component { + render() { + return ( + + + ); + } +} + +export default class BoilerEdit extends Component { + render() { + return (); + } +} diff --git a/app/scenes/roomEdit.js b/app/scenes/roomEdit.js index a4df84a..f27e2b1 100644 --- a/app/scenes/roomEdit.js +++ b/app/scenes/roomEdit.js @@ -9,13 +9,12 @@ class RoomEditInfo extends Component { return ( - ); } } export default class RoomEdit extends Component { render() { - return (); + return (); } } diff --git a/app/scenes/sensorEdit.js b/app/scenes/sensorEdit.js new file mode 100644 index 0000000..b25c9c9 --- /dev/null +++ b/app/scenes/sensorEdit.js @@ -0,0 +1,20 @@ +import React, { Component } from 'react' +import { Text, View } from 'react-native'; +import EntityEdit, { TextEdit } from '../components/entityEdit'; + +import styles from '../assets/styles'; + +class BoilerEditInfo extends Component { + render() { + return ( + + + ); + } +} + +export default class BoilerEdit extends Component { + render() { + return (); + } +} diff --git a/app/scenes/windowEdit.js b/app/scenes/windowEdit.js new file mode 100644 index 0000000..b25c9c9 --- /dev/null +++ b/app/scenes/windowEdit.js @@ -0,0 +1,20 @@ +import React, { Component } from 'react' +import { Text, View } from 'react-native'; +import EntityEdit, { TextEdit } from '../components/entityEdit'; + +import styles from '../assets/styles'; + +class BoilerEditInfo extends Component { + render() { + return ( + + + ); + } +} + +export default class BoilerEdit extends Component { + render() { + return (); + } +} diff --git a/app/stores/allStores.js b/app/stores/allStores.js index ce9ec90..95b00de 100644 --- a/app/stores/allStores.js +++ b/app/stores/allStores.js @@ -7,13 +7,15 @@ import Entities from '../entities'; export default (actions, routes) => { const data = getDataStore(actions.data, 5984, 'admin', 'password', 'http://localhost:5984/', 'blocpower'); const nav = getNavStore(actions.nav, routes); - const stores = [ - Kefir.constant(stores => stores.nav = nav), - Kefir.constant(stores => stores.data = data) - ]; - Entities.entityList.forEach(entity => stores.push(data.db.map(data => stores => stores[entity] = getEntityStore(actions[entity], data, entity)))); - const merge$ = Kefir.combine(stores).map(arr => arr.reduce((v, m) => { m(v); return v; }, {})); + const load$ = data.db.map(db => { + const store = { + nav: nav, + data: data, + } + Entities.entityList.forEach(entity => { store[entity] = getEntityStore(actions[entity], db, entity); } ); + return store; + }); - return merge$; + return load$; } \ No newline at end of file diff --git a/app/stores/dataStore.js b/app/stores/dataStore.js index cad3d4a..979b283 100644 --- a/app/stores/dataStore.js +++ b/app/stores/dataStore.js @@ -5,16 +5,22 @@ import mainDesignDoc from './mainDesignDoc'; export default (actions, port, username, password, url, bucketName) => { kc.initListener.action.call(port, username, password); + const db = kc.getDatabase(url, bucketName); return { init: kc.initListener.result, //Make sure database is created and that the database has an updated design document - db: db.getInfo() + db: db.getInfoNoError() + // .flatMap(() => db.deleteDatabase()) .flatMap(info => (info.status && info.status == 404) ? db.createDatabase() : Kefir.constant()) - .flatMap(() => db.getDesignDocument('main')) - .flatMap(doc => (doc.status && doc.status == 404) ? Kefir.constant() : db.deleteDesignDocument('main', doc._rev)) - .flatMap(() => db.createDesignDocument('main', mainDesignDoc)) + .flatMap(() => db.getDesignDocumentNoError('main')) + .flatMap(doc => (doc.status && doc.status == 404) ? db.createDesignDocument('main', mainDesignDoc) : Kefir.constant()) + + //Replacing the above line of code with the ones below will reload the design doc every time instead of just if it isn't found + + // .flatMap(doc => (doc.status && doc.status == 404) ? Kefir.constant() : db.deleteDesignDocument('main', doc._rev)) + // .flatMap(() => db.createDesignDocument('main', mainDesignDoc)) .map(() => db) }; } \ No newline at end of file diff --git a/app/stores/entityStore.js b/app/stores/entityStore.js index 7969fad..14731ec 100644 --- a/app/stores/entityStore.js +++ b/app/stores/entityStore.js @@ -15,51 +15,55 @@ function getEdit(actions, type) { ]); const edit$ = editStream$.scan((current, change) => change(current), edit); - return [edit$, editStream$]; + return edit$; } module.exports = (actions, kd, type) => { - const [edit$, editStream$] = getEdit(actions, type); + const edit$ = getEdit(actions, type); - const list = Immutable.OrderedMap(); - const startValue$ = kd.queryView('main', 'by_type', {include_docs: true, key: type}) - .take(1) - .map(list => list.rows.reduce((obj, field) => obj.set(field.id, field.doc), Immutable.OrderedMap())); + const getNew$ = edit$.sampledBy(actions.saveNew.raw).map(b => b.toJS()); + const getEdit$ = edit$.sampledBy(actions.saveEdit.raw).map(b => b.toJS()); - const getCreate$ = edit$.sampledBy(actions.create.raw).map(b => b.toJS()); - const getEdit$ = edit$.sampledBy(actions.save.raw).map(b => b.toJS()); + const newSaved$ = getNew$.flatMap(v => kd.createDocumentInOut(x=>x, v)); + const editSaved$ = getEdit$.flatMap(v => kd.updateDocumentInOut(obj => [obj[0], obj[0]._id, obj[0]._rev], v)); - const setSingle = stream => - stream.map(obj => Object.assign(obj.input, { _rev: obj.output.rev })) - .map(building => (map) => map.set(building._id, building)); - - const listStream$ = Kefir - .merge([ - setSingle(kd.createDocumentInOut(getCreate$)), - setSingle(kd.updateDocumentInOut(getEdit$, obj => [obj, obj._id, obj._rev])), - actions.destroy.raw - .flatMap(b => kd.deleteDocument(b._id, b._rev)) - .map(b => (map) => map.delete(b.id)), - startValue$ - .map(buildings => (map) => map.merge(buildings)) - ]); + const singleSaved$ = Kefir.merge([newSaved$, editSaved$]); + const deleteSaved$ = actions.destroy.raw.flatMap(b => kd.deleteDocument(b._id, b._rev)); - const listState$ = listStream$.scan((current, change) => change(current), list); + const setSingle = stream => stream + .map(obj => Object.assign(obj.input[0], { _rev: obj.output.rev })) + .map(couchObj => (map) => map.set(couchObj._id, couchObj)); + + const startValue$ = kd.queryView('main', 'by_type', {include_docs: true, key: type}) + .map(list => list.rows.reduce((obj, field) => obj.set(field.id, field.doc), Immutable.OrderedMap())); + const listState$ = U.mergeToOrderedMap([ + setSingle(singleSaved$), + deleteSaved$.map(b => (map) => map.delete(b.id)), + startValue$.map(entity => (map) => map.merge(entity)) + ]); //Load children into a stream given an object with an _id field, an immutable map with an _id key, or just the id. - const loadChildren$ = actions.loadChildren.raw.toProperty(); - const children$ = loadChildren$.map(obj => !obj ? Kefir.constant(Immutable.OrderedMap()) : loadChildren$ - .map(obj => obj._id || (obj.get && obj.get('_id')) || obj) - .flatMap(id => kd.queryView('main', 'by_parent_id_type', {include_docs: true, key: [id]})) - .map(list => list.rows.reduce((obj, field) => obj.set(field.id, field.doc), Immutable.OrderedMap()))); + //Convert the stream to a property to makes sure the last result is available regardless of timing of connecting the onValue handler + const loadChildren$ = actions.loadChildren.raw; + const childrenStart$ = loadChildren$ + .map(obj => (!obj) ? '' : obj._id || (obj.get && obj.get('_id')) || obj) + .flatMap(id => kd.queryView('main', 'by_parent_id_type', {include_docs: true, startkey: [id, 'a'], endkey:[id, 'zzzzzzzzzzzzzzz']})) + .map(list => list.rows.reduce((obj, field) => obj.set(field.id, field.doc), Immutable.OrderedMap())); + + const childrenState$ = U.mergeToOrderedMap([ + childrenStart$.map(entity => (map) => entity) + ]); - const errors$ = Kefir.merge([listStream$, editStream$, loadChildren$]); + const errors$ = Kefir.merge([listState$, edit$]).ignoreValues(); return { all: listState$, edit: edit$, + newSaved: singleSaved$, + editSaved: editSaved$, + deleteSaved: deleteSaved$, errors: errors$, - children: loadChildren$ + children: childrenState$ } }; \ No newline at end of file diff --git a/app/stores/navStore.js b/app/stores/navStore.js index 50aba8c..d87f863 100644 --- a/app/stores/navStore.js +++ b/app/stores/navStore.js @@ -14,7 +14,7 @@ module.exports = (actions, routes) => { actions.homeback.raw.map( () => (state) => state.size == 1 ? state : state.shift()), ]); - const state$ = stream$.scan((state, action) => action(state), data); + const state$ = stream$.scan((state, action) => action(state), data).log('stack'); const stateButtonSample$ = state$.sampledBy(actions.hardwareback.raw); //Provide a close event for pressing the hardware back button on the main screens diff --git a/app/utils/kefirCouchbase.js b/app/utils/kefirCouchbase.js index 678226c..1084ffd 100644 --- a/app/utils/kefirCouchbase.js +++ b/app/utils/kefirCouchbase.js @@ -13,6 +13,18 @@ class ActionResult { } } +const couchbaseStreamDef = (db, actionName, arr, outputMap) => (emitter) => { + db[actionName](...arr) + .then(out => { + if (!out || out.error) { + emitter.error(Object.assign((out || {}), {src : arr, action: actionName})) + } else { + emitter.value(outputMap(out)); + } + }) + .catch(e => emitter.error); +} + class KefirCouchbase { constructor(url, bucketName) { this.database = new Manager(url, bucketName); @@ -23,8 +35,9 @@ class KefirCouchbase { 'getInfo', 'getDesignDocument', 'createDesignDocument', 'deleteDesignDocument', 'queryView', 'saveAttachment', 'getAttachmentUri', 'deleteAttachment', 'latestRevision', 'activeTasks'] .reduce((pv, cv) => { - pv[cv] = (...arr) => Kefir.fromPromise(database[cv](...arr)); - pv[cv + 'InOut'] = (stream, toParams = (x => [x])) => U.inOut(stream, stream.flatMap(v => Kefir.fromPromise(database[cv](...toParams(v))))); + pv[cv] = (...arr) => Kefir.stream(couchbaseStreamDef(database, cv, arr, value => value)); + pv[cv + 'NoError'] = (...arr) => Kefir.fromPromise(database[cv](...arr)); + pv[cv + 'InOut'] = (toParams, ...arr) => Kefir.stream(couchbaseStreamDef(database, cv, toParams(arr), value => ({ input: arr, output: value }))); return pv; }, this); } diff --git a/app/utils/subscriptionTracker.js b/app/utils/subscriptionTracker.js new file mode 100644 index 0000000..18dde0c --- /dev/null +++ b/app/utils/subscriptionTracker.js @@ -0,0 +1,39 @@ +export default () => { + return { + subscriptions: [], + errors: [], + any: [], + addSub(stream$, action) { + if (!stream$ || !action || !(stream$.onValue)) { + return; + } + stream$.onValue(action); + this.subscriptions.push([stream$, action]); + }, + addError(stream$, action) { + if (!stream$ || !action || !(stream$.onError)) { + return; + } + stream$.onError(action); + this.subscriptions.push([stream$, action]); + }, + addAny(stream$, action) { + if (!stream$ || !action || !(stream$.onAny)) { + return; + } + stream$.onAny(action); + this.subscriptions.push([stream$, action]); + }, + cleanUp() { + for(sub of this.subscriptions) { + sub[0].offValue(sub[1]); + } + for(sub of this.errors) { + sub[0].offError(sub[1]); + } + for(sub of this.any) { + sub[0].offAndy(sub[1]); + } + } + } +} \ No newline at end of file diff --git a/app/utils/utils.js b/app/utils/utils.js index 85f9c29..5f519be 100644 --- a/app/utils/utils.js +++ b/app/utils/utils.js @@ -1,13 +1,31 @@ import Kefir from 'kefir'; -import { Alert } from 'react-native'; +import Immutable from 'immutable'; +import StringifyCircular from 'json-stringify-safe'; +import React, { Component } from 'react'; +import { Alert, Text } from 'react-native'; +import subscriptionTracker from './subscriptionTracker'; const al = stream => { stream.onValue(v => Alert.alert('stuff', JSON.stringify(v), [{text:'OK'}])); return stream; }; +class JsonText extends Component { + render() { + return ({StringifyCircular(this.props, null, '\t')}); + } +} + +class KeysText extends Component { + render() { + return ({JSON.stringify(Object.keys(this.obj))}); + } +} + +export { subscriptionTracker, JsonText, KeysText }; + export default class Utils { static getActionsObject(actionList) { return actionList.reduce((state, prop) => { state[prop] = { - raw: Kefir.pool(), + raw: Kefir.pool(), //.log(prop), handler: v => state[prop].raw.plug(Kefir.constant(v)), handlerConst: (v) => () => state[prop].raw.plug(Kefir.constant(v)), handlerKV: (k) => (v) => state[prop].raw.plug(Kefir.constant({key: k, value: v})), @@ -16,6 +34,49 @@ export default class Utils { }, {}) } + static reducerToFunction(reducer) { + if (!reducer) { + return (x) => x; + } else { + let type = typeof reducer; + if (type === 'function') { + return reducer; + } else { + return (obj) => { + if (!obj) return obj; + if (obj.setIn) { + return obj.setIn(Array.isArray(reducer.key) ? reducer.key : [reducer.key], reducer.value); + } else { + obj[reducer.key] = reducer.value; return obj; } + } + } + } + } + + static constReducer(key, value) { + return Kefir.constant({ key: key, value: value}); + } + + static mergeToSingleObject(streamsItt, initialObject = {}) { + return Kefir + .combine(Array.from(streamsItt)) + .map(reducers => reducers.map(this.reducerToFunction).reduce((o, r) => r(o), initialObject)); + } + + static mergeToOrderedMap(streamsItt, initialObject = Immutable.OrderedMap()) { + return Kefir + .merge(Array.from(streamsItt)) + .map(this.reducerToFunction) + .scan((current, change) => change(current), initialObject); + } + + static mergeToMap(streamsItt, initialObject = Immutable.Map()) { + return Kefir + .merge(Array.from(streamsItt)) + .map(this.reducerToFunction) + .scan((current, change) => change(current), initialObject); + } + static inOut(input$, output$) { return Kefir.combine([input$, output$], [], (input, output) => ({input: input, output: output})); } @@ -34,10 +95,10 @@ export default class Utils { } static jsonAlert(stream$, title='Stream Value') { - return stream$.map(value => { Alert.alert(title, JSON.stringify(value)); return value; }) + return stream$.map(value => { Alert.alert(title, StringifyCircular(value)); return value; }) } static popJson(obj, title="Hai") { - Alert.alert(title, obj ? JSON.stringify(obj) : 'empty'); + Alert.alert(title, obj ? StringifyCircular(obj) : 'empty'); } } \ No newline at end of file diff --git a/index.android.js b/index.android.js index d8d7a87..1600527 100644 --- a/index.android.js +++ b/index.android.js @@ -11,7 +11,7 @@ import { import Toolbar from './app/components/Toolbar'; import Kefir from 'kefir'; -import U from './app/utils/utils'; +import U, { subscriptionTracker as tracker } from './app/utils/utils'; import Entities from './app/entities'; import routes from './app/routes'; @@ -31,24 +31,23 @@ const styles = StyleSheet.create({ }); const actions = getActions(); -const stores$ = getStores(actions, routes); +//prevents stores from being reloaded and duplicates being attached to the databases and streams with javascript reloads +const stores$ = getStores(actions, routes).onError(e => U.popJson(e, 'Database load error')); const setup$ = stores$.map(stores => { - stores.building.errors.onError(e => { - Alert.alert('Error', JSON.stringify(e), [ {text: 'OK'}]); - }) - stores.nav.navigator.onValue(navigator => { const window$ = stores.nav.stack.slidingWindow(2, 2); - window$.filter(stacks => stacks[0].size < stacks[1].size).onValue(routes => navigator.push(routes[1].get(0))); - window$.filter(stacks => stacks[0].size > stacks[1].size).onValue(routes => navigator.pop()); + window$.filter(stacks => stacks[0].size < stacks[1].size).map(routes => routes[1].get(0)).onValue(route => navigator.push(route)); + window$.filter(stacks => stacks[0].size > stacks[1].size).log('pop').onValue(routes => navigator.pop()); }); - for (let entity of Entities.entityList) { - actions.nav.push.raw.plug(actions[entity].newObj.raw.map(() => `${entity}.children.new`)); - actions.nav.push.raw.plug(actions[entity].editObj.raw.map(() => `${entity}.children.edit`)); + for (let entityName of Entities.entityList) { + actions.nav.push.raw.plug(actions[entityName].newObj.raw.map(() => `${entityName}.children.new`)); + actions.nav.push.raw.plug(actions[entityName].editObj.raw.map(() => `${entityName}.children.edit`)); //Load children when we activate a new object, or edit an existing one. - actions[entity].loadChildren.raw.plug(actions[entity].newObj.raw); - actions[entity].loadChildren.raw.plug(actions[entity].editObj.raw); + actions[entityName].loadChildren.raw.plug(actions[entityName].newObj.raw.log('newObj')); + actions[entityName].loadChildren.raw.plug(actions[entityName].editObj.raw.log('editObj')); + + stores[entityName].errors.onError(e => U.popJson(e, 'Database Error')); } BackAndroid.addEventListener('hardwareBackPress', () => { actions.nav.hardwareback.handler(); return true; }); @@ -56,12 +55,6 @@ const setup$ = stores$.map(stores => { stores.nav.close.onValue(() => { BackAndroid.exitApp(); }); - //Load a building to make it easier to work on that page. - setTimeout(() => - stores.building.all.onValue(bl => - actions.building.editObj.handlerConst(bl.first())() - ), 200 - ) return stores; }) @@ -69,25 +62,17 @@ const setup$ = stores$.map(stores => { class audit extends Component { constructor(props) { super(props); - - this.watches = []; - } - - addWatch(stream$, watcher, bound = false) { - if (bound == false) { - stream$.onValue(watcher); - } - this.watches.push({ stream$: stream$, watcher: watcher }); + this.tracker = tracker(); } componentDidMount() { - this.addWatch(setup$, this.mapState.bind(this)); + console.log('track setup'); + this.tracker.addSub(setup$, this.mapState.bind(this)); } componentWillUnmount() { - this.watches && this.watches.forEach((watch,i) => { - watch[0].offValue(watch[1]); - }); + console.log('untrack setup'); + this.tracker.cleanUp(); } mapState(stores) { @@ -98,7 +83,7 @@ class audit extends Component { render() { if (!this.state) { - return (); + return (); } let route = null; this.state.stores.nav.current.take(1).onValue((r) => { route = r; }); @@ -107,10 +92,10 @@ class audit extends Component { ref={ actions.nav.navigator.handler } initialRoute={ route } navigationBar={ } - renderScene={(route, navigator) => ( + renderScene={(route, navigator) => { return ( - - ) + + )} }> ); diff --git a/package.json b/package.json index 7f3b1e4..9d1bd3b 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ }, "dependencies": { "immutable": "^3.8.1", + "json-stringify-safe": "^5.0.1", "kefir": "^3.2.3", "lodash": "^4.13.1", "node-uuid": "^1.4.7", -- GitLab From 63d647171fe925e48933058f98d2e83acd8cc04f Mon Sep 17 00:00:00 2001 From: Andy Brummer Date: Tue, 14 Jun 2016 01:46:12 -0500 Subject: [PATCH 08/46] Tightening up code, removed old duplicate database connection. --- app/stores/dataStore.js | 1 + app/stores/navStore.js | 2 +- app/utils/kefirCouchbase.js | 75 +++++++++++++++++++++++-------------- index.android.js | 8 ++-- 4 files changed, 52 insertions(+), 34 deletions(-) diff --git a/app/stores/dataStore.js b/app/stores/dataStore.js index 979b283..7ee539a 100644 --- a/app/stores/dataStore.js +++ b/app/stores/dataStore.js @@ -10,6 +10,7 @@ export default (actions, port, username, password, url, bucketName) => { return { init: kc.initListener.result, + replication: db.setupReplication(username, password, url, bucketName), //Make sure database is created and that the database has an updated design document db: db.getInfoNoError() // .flatMap(() => db.deleteDatabase()) diff --git a/app/stores/navStore.js b/app/stores/navStore.js index d87f863..50aba8c 100644 --- a/app/stores/navStore.js +++ b/app/stores/navStore.js @@ -14,7 +14,7 @@ module.exports = (actions, routes) => { actions.homeback.raw.map( () => (state) => state.size == 1 ? state : state.shift()), ]); - const state$ = stream$.scan((state, action) => action(state), data).log('stack'); + const state$ = stream$.scan((state, action) => action(state), data); const stateButtonSample$ = state$.sampledBy(actions.hardwareback.raw); //Provide a close event for pressing the hardware back button on the main screens diff --git a/app/utils/kefirCouchbase.js b/app/utils/kefirCouchbase.js index 1084ffd..410f60f 100644 --- a/app/utils/kefirCouchbase.js +++ b/app/utils/kefirCouchbase.js @@ -28,6 +28,8 @@ const couchbaseStreamDef = (db, actionName, arr, outputMap) => (emitter) => { class KefirCouchbase { constructor(url, bucketName) { this.database = new Manager(url, bucketName); + this.url = url; + this.bucketName = bucketName; const database = this.database; ['createDatabase', 'deleteDatabase', 'getAllDatabases', 'createDocument', 'deleteDocument', @@ -40,6 +42,51 @@ class KefirCouchbase { pv[cv + 'InOut'] = (toParams, ...arr) => Kefir.stream(couchbaseStreamDef(database, cv, toParams(arr), value => ({ input: arr, output: value }))); return pv; }, this); + + this.getInfo() + .map(res => this.database.listen({since: res.update_seq - 1, feed: 'longpoll'})); + } + + listen() { + return Kefir.fromEvents(this.database.changesEventEmitter, 'changes'); + } + + setupReplication(userId, password, remoteDatabaseUrl , remoteBucketName) { + const url = `${remoteDatabaseUrl}/${remoteBucketName}/_session`; + const self = this; + + const settings = { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({name: userId, password: password}) + }; + + return Kefir.stream(emitter => fetch(url, settings) + .then((res) => { + switch (res.status) { + case 200: { + const sessionCookie = res.headers.map['set-cookie'][0]; + + this.database.replicate( + dbName, + {headers: {Cookie: sessionCookie}, url: remoteDbUrl}, + true + ); + + this.database.replicate( + {headers: {Cookie: sessionCookie}, url: remoteDbUrl}, + dbName, + true + ); + emitter.value('replication enabled'); + } + default: { + emitter.error(res); + } + } + }) + .catch(e => emitter.error(e)) + ); } } @@ -49,31 +96,3 @@ const store = { } export { store as default }; - - -const database = new Manager('http://localhost:5984/', 'blocpower'); -database - .createDatabase() - .then((res) => { - database.getDesignDocument('main').then(d => { - const create = () => { - database.createDesignDocument('main', { - views: { - language: 'javascript', - by_type: {map: 'function (doc) { if (doc.type) { emit(doc.type, null); } }'}, - by_parent_id_type: {map: 'function (doc) { if (doc.parent_id && doc.type) { emit([doc.parent_id, doc.type], null); } }'} - } - }); - }; - - if (d) { - database.deleteDesignDocument('main', d._rev).then(create); - } else { - create(); - } - }); - database.replicate('http://couchbase1.sbdev.io:4984/blocpoweraudit'); - }); - -const kefirDatabase = ['createDatabase', 'deleteDatabase', 'getAllDatabases', 'createDocument', 'deleteDocument', 'updateDocument', 'getAllDocuments', 'getAllDocumentConflicts', 'replicate', 'getChanges', 'getInfo', 'getDesignDocument', 'createDesignDocument', 'deleteDesignDocument', 'queryView', 'saveAttachment', 'getAttachmentUri', 'deleteAttachment', 'latestRevision', 'activeTasks'] - .reduce((pv, cv) => { pv[cv] = (...arr) => Kefir.fromPromise(database[cv](...arr)); return pv; } , {}); diff --git a/index.android.js b/index.android.js index 1600527..e6e1771 100644 --- a/index.android.js +++ b/index.android.js @@ -37,15 +37,15 @@ const setup$ = stores$.map(stores => { stores.nav.navigator.onValue(navigator => { const window$ = stores.nav.stack.slidingWindow(2, 2); window$.filter(stacks => stacks[0].size < stacks[1].size).map(routes => routes[1].get(0)).onValue(route => navigator.push(route)); - window$.filter(stacks => stacks[0].size > stacks[1].size).log('pop').onValue(routes => navigator.pop()); + window$.filter(stacks => stacks[0].size > stacks[1].size).onValue(routes => navigator.pop()); }); for (let entityName of Entities.entityList) { actions.nav.push.raw.plug(actions[entityName].newObj.raw.map(() => `${entityName}.children.new`)); actions.nav.push.raw.plug(actions[entityName].editObj.raw.map(() => `${entityName}.children.edit`)); //Load children when we activate a new object, or edit an existing one. - actions[entityName].loadChildren.raw.plug(actions[entityName].newObj.raw.log('newObj')); - actions[entityName].loadChildren.raw.plug(actions[entityName].editObj.raw.log('editObj')); + actions[entityName].loadChildren.raw.plug(actions[entityName].newObj.raw); + actions[entityName].loadChildren.raw.plug(actions[entityName].editObj.raw); stores[entityName].errors.onError(e => U.popJson(e, 'Database Error')); } @@ -66,12 +66,10 @@ class audit extends Component { } componentDidMount() { - console.log('track setup'); this.tracker.addSub(setup$, this.mapState.bind(this)); } componentWillUnmount() { - console.log('untrack setup'); this.tracker.cleanUp(); } -- GitLab From aea6aa0646ca477d43fc262bcedde396b0dc7070 Mon Sep 17 00:00:00 2001 From: Chris Blackmon Date: Tue, 14 Jun 2016 10:26:07 -0500 Subject: [PATCH 09/46] Added date picker. Checkbox group on screen, still need to work on saving checked value. --- app/assets/styles.js | 65 ++++++++++++++++++++++++++++++--- app/components/entityEdit.js | 70 ++++++++++++++++++++++++++++++++++-- app/scenes/buildingEdit.js | 32 ++++++++++++----- package.json | 5 ++- 4 files changed, 154 insertions(+), 18 deletions(-) diff --git a/app/assets/styles.js b/app/assets/styles.js index 4580055..31edeab 100644 --- a/app/assets/styles.js +++ b/app/assets/styles.js @@ -1,3 +1,4 @@ +import { Dimensions } from 'react' import { StyleSheet } from 'react-native'; export default StyleSheet.create({ @@ -31,13 +32,13 @@ export default StyleSheet.create({ flex: 1 }, label: { - fontSize: 19 + fontSize: 19, + alignSelf: 'flex-start' }, rowTitle: { color: '#48BBEC', fontSize: 16 }, - mainContainer: { flex: 1, padding: 30, @@ -55,7 +56,7 @@ export default StyleSheet.create({ borderWidth: 1, borderColor: 'white', borderRadius: 8, - color: 'white', + color: 'black', margin: 5 }, buttonText: { @@ -79,5 +80,59 @@ export default StyleSheet.create({ fontSize: 20, height: 22, color: 'white', - } -}); \ No newline at end of file + }, + buildingEdit: { + marginBottom: 50 + }, + datePick: { + alignSelf: 'flex-end', + width: 330, + height: 100, + marginTop: 40 + }, + dateInstructions: { + alignSelf: 'center' + }, + + optionList: { + justifyContent: 'flex-start', + position: 'absolute', + backgroundColor: 'red', + left: 0, + right: 0, + }, + formSelect: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + margin: 5, + }, + formSelected: { + flex: 1, + height: 50, + marginTop: 25, + marginBottom: 5, + padding: 5, + justifyContent: 'center', + alignItems: 'stretch', + backgroundColor: 'red', + }, + formSelectedOption: { + width: 325, + justifyContent: 'center', + alignItems: 'stretch', + backgroundColor: 'red', + }, + selectOverlay: { + backgroundColor: 'red', + flex: 1, + opacity: 1, + justifyContent: 'flex-start', + position: 'absolute', + height: 100, + left: 0, + right: 0, + overflow: 'visible', + }, + +}); diff --git a/app/components/entityEdit.js b/app/components/entityEdit.js index ec868e7..727506d 100644 --- a/app/components/entityEdit.js +++ b/app/components/entityEdit.js @@ -1,5 +1,8 @@ import React, { Component } from 'react' -import {View, Text, TextInput} from 'react-native'; +import {View, Text, TextInput, Picker, ScrollView } from 'react-native'; +import { CheckboxGroup, RadioButton } from 'react-native-material-design'; +import DatePicker from 'react-native-datepicker'; +import DropDown, { Select, Option, OptionList } from 'react-native-selectme'; import _ from 'lodash'; import styles from '../assets/styles'; @@ -9,7 +12,7 @@ export default class EntityEdit extends Component { super(props); this.boundState = this.mapState.bind(this); } - + componentDidMount() { this.props.stores[this.props.route.entityName].edit.onValue(this.boundState); for(var loadAction of (this.props.route.loadActions || [])) { @@ -34,7 +37,7 @@ export default class EntityEdit extends Component { } else { return (); } - } + } } export class TextEdit extends Component { @@ -50,3 +53,64 @@ export class TextEdit extends Component { ); } } + +export class FormCheckbox extends Component { + + render() { + return ( + + {this.props.label} + + ); + } +} + +export class FormRadio extends Component { + render() { + return ( + + {this.props.label} + { this.props.fieldNames.map((fieldName, index) => ( console.log('I am selected', selected)} + style={styles.radioButton}/>))} + ); + } +} + + + +export class DatePick extends Component { + + constructor(props) { + super(props); + + this.state = { + date: "2016-05-01" + }; + } + + render() { + return ( + + {this.props.label} + { this.props.fieldNames.map((fieldName, index) => ())} + ); + } +} diff --git a/app/scenes/buildingEdit.js b/app/scenes/buildingEdit.js index 2bad09f..3eb4982 100644 --- a/app/scenes/buildingEdit.js +++ b/app/scenes/buildingEdit.js @@ -1,21 +1,35 @@ import React, { Component } from 'react' -import { Text, View } from 'react-native'; -import EntityEdit, { TextEdit } from '../components/entityEdit'; +import { Text, View, ScrollView } from 'react-native'; +import EntityEdit, { TextEdit, DatePick, FormSelect, FormCheckbox, FormRadio } from '../components/entityEdit'; +import DatePicker from 'react-native-datepicker'; import styles from '../assets/styles'; class BuildingEditInfo extends Component { render() { - return ( - - - - - - ); + return ( + + + + + + + + + ); } } + export default class BuildingEdit extends Component { render() { return ( diff --git a/package.json b/package.json index 7f3b1e4..b94af41 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,9 @@ "react-native": "^0.26.3", "react-native-action-button": "^1.1.5", "react-native-couchbase-lite": "^0.3.0", - "react-native-material-design": "^0.3.6" + "react-native-datepicker": "^1.2.1", + "react-native-dropdown": "0.0.6", + "react-native-material-design": "^0.3.6", + "react-native-selectme": "^1.1.0" } } -- GitLab From 9a8502f2026e0061ec74e274208233615ef231d6 Mon Sep 17 00:00:00 2001 From: Chris Blackmon Date: Tue, 14 Jun 2016 12:50:58 -0500 Subject: [PATCH 10/46] Added radio button and checkboxes functionality, currently inside Building Edit screen. --- app/assets/styles.js | 42 +-------------------------------- app/components/entityEdit.js | 45 ++++++++++++++---------------------- app/scenes/buildingEdit.js | 39 +++++++++++++++---------------- package.json | 2 +- 4 files changed, 38 insertions(+), 90 deletions(-) diff --git a/app/assets/styles.js b/app/assets/styles.js index 0ee3c1c..0d1037c 100644 --- a/app/assets/styles.js +++ b/app/assets/styles.js @@ -87,52 +87,12 @@ export default StyleSheet.create({ datePick: { alignSelf: 'flex-end', width: 330, - height: 100, + height: 80, marginTop: 40 }, dateInstructions: { alignSelf: 'center' }, - optionList: { - justifyContent: 'flex-start', - position: 'absolute', - backgroundColor: 'red', - left: 0, - right: 0, - }, - formSelect: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - margin: 5, - }, - formSelected: { - flex: 1, - height: 50, - marginTop: 25, - marginBottom: 5, - padding: 5, - justifyContent: 'center', - alignItems: 'stretch', - backgroundColor: 'red', - }, - formSelectedOption: { - width: 325, - justifyContent: 'center', - alignItems: 'stretch', - backgroundColor: 'red', - }, - selectOverlay: { - backgroundColor: 'red', - flex: 1, - opacity: 1, - justifyContent: 'flex-start', - position: 'absolute', - height: 100, - left: 0, - right: 0, - overflow: 'visible', - }, }); diff --git a/app/components/entityEdit.js b/app/components/entityEdit.js index 8e074c7..b210a5f 100644 --- a/app/components/entityEdit.js +++ b/app/components/entityEdit.js @@ -1,10 +1,7 @@ import React, { Component } from 'react' -<<<<<<< HEAD -import {View, Text, TextInput, Picker, ScrollView } from 'react-native'; -import { CheckboxGroup, RadioButton } from 'react-native-material-design'; +import {View, Text, TextInput, Picker, ScrollView, TouchableOpacity } from 'react-native'; +import { CheckboxGroup, RadioButtonGroup } from 'react-native-material-design'; import DatePicker from 'react-native-datepicker'; -import DropDown, { Select, Option, OptionList } from 'react-native-selectme'; -import {View, Text, TextInput, ScrollView, TouchableOpacity } from 'react-native'; import { Avatar} from 'react-native-material-design'; import ActionButton from 'react-native-action-button'; import Icon from 'react-native-vector-icons/MaterialIcons'; @@ -134,26 +131,26 @@ export class FormCheckbox extends Component { {this.props.label} ); } } -export class FormRadio extends Component { +export class RadioButtons extends Component { render() { return ( {this.props.label} - { this.props.fieldNames.map((fieldName, index) => ( console.log('I am selected', selected)} - style={styles.radioButton}/>))} + ); } } @@ -162,28 +159,20 @@ export class FormRadio extends Component { export class DatePick extends Component { - constructor(props) { - super(props); - - this.state = { - date: "2016-05-01" - }; - } - render() { + const {fieldName, entityMap, editHandler, label} = this.props; return ( - {this.props.label} - { this.props.fieldNames.map((fieldName, index) => ({label} + ))} + onDateChange={editHandler(fieldName)} + /> ); } } diff --git a/app/scenes/buildingEdit.js b/app/scenes/buildingEdit.js index ac5eb08..4de8298 100644 --- a/app/scenes/buildingEdit.js +++ b/app/scenes/buildingEdit.js @@ -1,37 +1,36 @@ import React, { Component } from 'react' import { Text, View, ScrollView, TouchableOpacity } from 'react-native'; -import EntityEdit, { TextEdit, DatePick, FormSelect, FormCheckbox, FormRadio } from '../components/entityEdit'; +import EntityEdit, { TextEdit, DatePick, FormSelect, FormCheckbox, RadioButtons } from '../components/entityEdit'; import DatePicker from 'react-native-datepicker'; import U, {JsonText, KeyText} from '../utils/utils'; - import styles from '../assets/styles'; class BuildingEditInfo extends Component { render() { + const {entityMap, editHandler} = this.props return ( - - - - - - - - + + + + + + + + + ); } } - export default class BuildingEdit extends Component { render() { return (); diff --git a/package.json b/package.json index a3956de..6b9d95a 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "react-native-couchbase-lite": "^0.3.0", "react-native-datepicker": "^1.2.1", "react-native-dropdown": "0.0.6", - "react-native-material-design": "^0.3.6", + "react-native-material-design": "https://github.com/cldwalker/react-native-material-design#fix-checkbox-group-on-select", "react-native-selectme": "^1.1.0" } } -- GitLab From 863a990dd6a21006279a27194e21b467ebc9fbdf Mon Sep 17 00:00:00 2001 From: Chris Blackmon Date: Tue, 14 Jun 2016 13:44:50 -0500 Subject: [PATCH 11/46] Datepicker now correctly saving Date in Building Edit form. Chose more readable format for date. --- app/components/entityEdit.js | 4 +--- app/scenes/buildingEdit.js | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/components/entityEdit.js b/app/components/entityEdit.js index b210a5f..098d2ed 100644 --- a/app/components/entityEdit.js +++ b/app/components/entityEdit.js @@ -155,8 +155,6 @@ export class RadioButtons extends Component { } } - - export class DatePick extends Component { render() { @@ -168,7 +166,7 @@ export class DatePick extends Component { style={styles.datePick} date={entityMap.get(fieldName) || new Date()} mode="date" - format="YYYY-MM-DD" + format="l" confirmBtnText="Confirm" cancelBtnText="Cancel" onDateChange={editHandler(fieldName)} diff --git a/app/scenes/buildingEdit.js b/app/scenes/buildingEdit.js index 4de8298..4f566d5 100644 --- a/app/scenes/buildingEdit.js +++ b/app/scenes/buildingEdit.js @@ -16,7 +16,7 @@ class BuildingEditInfo extends Component { - + Date: Tue, 14 Jun 2016 16:39:33 -0500 Subject: [PATCH 12/46] Created State dropdown list in Building Edit screen. --- app/components/entityEdit.js | 79 +++++++++++++++++++++++++++++++++++- app/scenes/buildingEdit.js | 9 ++-- package.json | 4 +- 3 files changed, 84 insertions(+), 8 deletions(-) diff --git a/app/components/entityEdit.js b/app/components/entityEdit.js index 098d2ed..37cc762 100644 --- a/app/components/entityEdit.js +++ b/app/components/entityEdit.js @@ -1,6 +1,9 @@ +'use strict'; + import React, { Component } from 'react' -import {View, Text, TextInput, Picker, ScrollView, TouchableOpacity } from 'react-native'; +import { View, Text, TextInput, Picker, ScrollView, TouchableOpacity } from 'react-native'; import { CheckboxGroup, RadioButtonGroup } from 'react-native-material-design'; + import DatePicker from 'react-native-datepicker'; import { Avatar} from 'react-native-material-design'; import ActionButton from 'react-native-action-button'; @@ -12,6 +15,8 @@ import _ from 'lodash'; import U, { subscriptionTracker, JsonText, KeyText } from '../utils/utils'; import styles from '../assets/styles'; +const Item = Picker.Item; + export default class EntityEdit extends Component { constructor(props) { super(props); @@ -155,6 +160,78 @@ export class RadioButtons extends Component { } } +export class StateDropdownPicker extends Component { + + render() { + const {fieldName, entityMap, editHandler, label} = this.props; + return ( + + {label} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); + } +} + export class DatePick extends Component { render() { diff --git a/app/scenes/buildingEdit.js b/app/scenes/buildingEdit.js index 4f566d5..ee1f899 100644 --- a/app/scenes/buildingEdit.js +++ b/app/scenes/buildingEdit.js @@ -1,20 +1,21 @@ import React, { Component } from 'react' -import { Text, View, ScrollView, TouchableOpacity } from 'react-native'; -import EntityEdit, { TextEdit, DatePick, FormSelect, FormCheckbox, RadioButtons } from '../components/entityEdit'; +import { Text, View, ScrollView, TouchableOpacity, Picker } from 'react-native'; +import EntityEdit, { TextEdit, DatePick, FormSelect, FormCheckbox, RadioButtons, StateDropdownPicker } from '../components/entityEdit'; import DatePicker from 'react-native-datepicker'; import U, {JsonText, KeyText} from '../utils/utils'; import styles from '../assets/styles'; +const Item = Picker.Item; + class BuildingEditInfo extends Component { render() { const {entityMap, editHandler} = this.props return ( - - + Date: Wed, 15 Jun 2016 16:15:29 -0500 Subject: [PATCH 13/46] GPS recorder on building edit form added. Needed to add to Android Manifest for it to work. Added material icon for same. --- android/app/src/main/AndroidManifest.xml | 3 ++- app/assets/styles.js | 32 ++++++++++++++++++++++-- app/components/entityEdit.js | 3 ++- app/scenes/buildingEdit.js | 4 +++ 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6048b48..5d6ab61 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ + - \ No newline at end of file + diff --git a/app/assets/styles.js b/app/assets/styles.js index 0d1037c..68ee8ca 100644 --- a/app/assets/styles.js +++ b/app/assets/styles.js @@ -59,6 +59,35 @@ export default StyleSheet.create({ color: '#585858', margin: 5 }, + geoLocator: { + marginTop: 10, + marginBottom: 10 + }, + lookupGroup: { + height: 50, + flexDirection: 'row', + flexWrap: 'wrap', + alignItems: 'stretch', + marginTop: 5, + marginBottom: 10, + marginLeft: 10 + }, + locationIcon: { + marginTop: 5, + marginRight: 5 + }, + coordinates: { + alignSelf: 'flex-start', + padding: 4, + marginTop: 10, + marginRight: 5, + fontSize: 23, + borderWidth: 1, + borderColor: '#585858', + borderRadius: 8, + color: '#585858', + margin: 5 + }, buttonText: { fontSize: 18, color: '#111', @@ -88,11 +117,10 @@ export default StyleSheet.create({ alignSelf: 'flex-end', width: 330, height: 80, - marginTop: 40 + marginTop: 15 }, dateInstructions: { alignSelf: 'center' }, - }); diff --git a/app/components/entityEdit.js b/app/components/entityEdit.js index 37cc762..b4c5131 100644 --- a/app/components/entityEdit.js +++ b/app/components/entityEdit.js @@ -232,12 +232,13 @@ export class StateDropdownPicker extends Component { } } + export class DatePick extends Component { render() { const {fieldName, entityMap, editHandler, label} = this.props; return ( - + {label} + + Date: Thu, 16 Jun 2016 12:04:01 -0500 Subject: [PATCH 14/46] Corrected use of GPS locator. Correctly grabs new location when pressing icon. Styled group labels. --- app/assets/styles.js | 28 +++++++++++++++++++---- app/components/entityEdit.js | 43 +++++++++++++++++++++++++++++++++++- app/scenes/buildingEdit.js | 8 +++++-- 3 files changed, 72 insertions(+), 7 deletions(-) diff --git a/app/assets/styles.js b/app/assets/styles.js index 68ee8ca..4506aa3 100644 --- a/app/assets/styles.js +++ b/app/assets/styles.js @@ -25,19 +25,30 @@ export default StyleSheet.create({ }, rowText: { fontSize: 19, - flex: 8 + flex: 6, + alignSelf: 'center', + marginBottom: 5 }, rowDelete: { fontSize: 19, - flex: 1 + flex: 1, + marginTop: 5 }, label: { fontSize: 19, - alignSelf: 'flex-start' + alignSelf: 'flex-start', + color: 'black' + }, + groupLabel: { + fontSize: 23, + textDecorationLine: 'underline', + color: 'black', + marginBottom: 20, + fontWeight: '700' }, rowTitle: { color: '#48BBEC', - fontSize: 16 + fontSize: 16, }, mainContainer: { flex: 1, @@ -109,10 +120,19 @@ export default StyleSheet.create({ fontSize: 20, height: 22, color: 'white', + marginLeft: 10 }, buildingEdit: { marginBottom: 50 }, + picker: { + fontSize: 26 + }, + dropdownPicker: { + fontSize: 26, + padding: 4, + color: '#585858' + }, datePick: { alignSelf: 'flex-end', width: 330, diff --git a/app/components/entityEdit.js b/app/components/entityEdit.js index b4c5131..05071fb 100644 --- a/app/components/entityEdit.js +++ b/app/components/entityEdit.js @@ -168,7 +168,7 @@ export class StateDropdownPicker extends Component { {label} @@ -232,6 +232,47 @@ export class StateDropdownPicker extends Component { } } +export class Dropdown extends Component { + + constructor() { + super(); + return { + services: ['a', 'b', 'c', 'd', 'e'], + selectedService: 'a' + } + } + + componentDidMount() { + setTimeout(() => { + this.setState({ + services: [ 'one', 'two', 'three', 'four', 'five' ] + }) + }, 3000) + } + + render() { + return ( + + Pick a service + ( this.setState({selectedService:service}) ) } > + { this.state.services.map((s, i) => { + return + }) } + + + ); + } +} + + + + + export class DatePick extends Component { diff --git a/app/scenes/buildingEdit.js b/app/scenes/buildingEdit.js index 1318271..9bd50c5 100644 --- a/app/scenes/buildingEdit.js +++ b/app/scenes/buildingEdit.js @@ -1,6 +1,6 @@ import React, { Component } from 'react' import { Text, View, ScrollView, TouchableOpacity, Picker } from 'react-native'; -import EntityEdit, { TextEdit, DatePick, FormSelect, FormCheckbox, RadioButtons, StateDropdownPicker } from '../components/entityEdit'; +import EntityEdit, { TextEdit, DatePick, FormSelect, FormCheckbox, RadioButtons, StateDropdownPicker, Dropdown } from '../components/entityEdit'; import { Geolocation } from '../components/geoLocator.js'; import DatePicker from 'react-native-datepicker'; @@ -14,14 +14,18 @@ class BuildingEditInfo extends Component { const {entityMap, editHandler} = this.props return ( + - + + # of Occupied Residential Units + + Date: Thu, 16 Jun 2016 12:57:42 -0500 Subject: [PATCH 15/46] Getting release build setup. Adding image gallery field type. Moving over to change listen model for database updates. --- android/app/build.gradle | 11 ++ android/app/my-release-key.keystore | Bin 0 -> 2258 bytes android/app/src/main/AndroidManifest.xml | 5 + .../blocpoweraudit/MainActivity.java | 4 +- android/gradle.properties | 7 +- android/settings.gradle | 3 + app/assets/4-circles.png | Bin 0 -> 4048 bytes app/assets/4-circles@2x.png | Bin 0 -> 9162 bytes app/assets/4-circles@3x.png | Bin 0 -> 14889 bytes app/components/Toolbar.js | 9 +- app/components/gallery.js | 81 ++++++++++ app/components/materialToolbar.js | 143 ++++++++++++++++++ app/scenes/clientEdit.js | 2 + app/stores/allStores.js | 2 + app/stores/dataStore.js | 1 - app/utils/kefirCouchbase.js | 3 +- index.android.js | 2 + package.json | 1 + 18 files changed, 268 insertions(+), 6 deletions(-) create mode 100644 android/app/my-release-key.keystore create mode 100644 app/assets/4-circles.png create mode 100644 app/assets/4-circles@2x.png create mode 100644 app/assets/4-circles@3x.png create mode 100644 app/components/gallery.js create mode 100644 app/components/materialToolbar.js diff --git a/android/app/build.gradle b/android/app/build.gradle index df41592..127eb9c 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -90,6 +90,14 @@ android { abiFilters "armeabi-v7a", "x86" } } + signingConfigs { + release { + storeFile file('my-release-key.keystore') + storePassword '123456' + keyAlias 'my-key-alias' + keyPassword '123456' + } + } splits { abi { reset() @@ -102,6 +110,7 @@ android { release { minifyEnabled enableProguardInReleaseBuilds proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + //signingConfig signingConfigs.release } debug { debuggable true @@ -140,6 +149,8 @@ dependencies { compile "com.facebook.react:react-native:+" // From node_modules compile project(':react-native-couchbase-lite') + + compile project(':react-native-image-picker') } // Run this once to be able to run the application with BUCK diff --git a/android/app/my-release-key.keystore b/android/app/my-release-key.keystore new file mode 100644 index 0000000000000000000000000000000000000000..dfb5aff51eef088c47bda46f4b16d6119259f79a GIT binary patch literal 2258 zcmc&#`8N~_8=lRGF~%5!$i8OK%*`@!D|;CGPO6#Ad_`m#Yh=A6L{aqFvW)F!nKbr& zFHF`FiYz7K2$}43b-(XB=l%!Z5AP4p^FHr6?|II9p7&_=XcYhefQ}3Jcky_IsNAB2 zsE|C}Nz`M~-Y%)P1pwdxV51=iSY9YM1`31#l|V=!kOKgThAfE`HMw9@HW6h?N&M$I zR)}iyX(t-W3z7YMmd16r^mF+ZwkChyqz3$ZRJhcue?o*6m|FjllhHAoC{XRSP`83H zuNAE?R=yrEMfU(rY3Cp28js2^^Imf9q3b%uPlYc1a2sWdE%S$GdY-7#mFhLumEu6O zX_s~t=8x3J@V=%@j>!3WYvi@pUF0V$$n7of-dM7x$)42{#LR({?(#5Qw%Z?E#tLJW z`;)7VT)RcIO~I8?`bX?1v4PYPFIUn`&NU znWt>Ne5kI}a^Q1+tDudy=DXa4=vQL1iqH_^P1J=_TB3e>cZO>IPjY40bX^(7`D$h{ za_{+NI226xX#VmG>$$%{ax&4adO6rzniyOjeqgPaGT8L$=0Bnus%&rz6ZS z_Bf7gf;o`>bcL*^^jBdhuzA_{)V2`%tmA9C4dfB2h5_$2ur4q-XqgOvuJUIa>>o|m zA%yx2J1)#6VO}tn4te(Myg45v8Ef8r4_F%GIiO1#t4x)ko`tl`Jo)10g7)2SO(%q{ z*2bEr=c1?j{e;wuTP(9Ch)UtlcGD);zOVm$5SKQS*BFD{!G>C>)Evks?C4(mz525h zUd30Q_5isR7||YL$=*Hxfi$#*O7YyPosEN=S+NvG0+rA-9k2pRUn^^rH`>K7N%Nap?6yBO zFK=O(x*KX(X6#m3UHX|HL3Q6*%g*oBk@lBh{wYczCAB6Sh}TXRfTlWnEvQxjqWYwD%)0N_!S5i+m^5*-p^;$!HnqGuR8mzV7@i+M9 zON60Ikg^PRgM?BdR+bjAq+8dAWLb}Qh|VWY4{$b#FqVdVUsepI7BZUjCBdR|ewr$y z%9)u#IRcN*I1^{T(o1w5-xiB3Cz(poJWf=TC3G(Cea;x>xF_A?t21pSxNC?Sm7q** zD|Yuao>u6rQM%&hkQ5&ti!YR6aJ^jAJ8EBa&2eh%kY$x}-oX<*G&vsyo{!ujTKo%L z7I@GD;Cp?t2QsK4?X>~!DbmknW=sX|ci#Q{v6*t3VH6oV;o}Kkm@>=E>1n%Dj>`2@ z+;HjEoS5%W{<345;oA$5)fqf>et+PBaRR4uu0AJ626F?oWRlSGhMOiCeWjgnt|dr= zIj-A&IPs07Z?Op7OIBrAu<yqD|Qfc{w`0!=pTR%(mO3Fc>AN50y5?$Sj>vHwm-4kuN0>^T4c zOv6Tlqp;B+_dGBN2m(PM0$JNwIFw5%);M+>4g|uE1Jf4=!*W5v_7ErvWN-UFZ~_Ig zbHX8x#m5~^6qFP|^>@FGlfa7oBDhh4RDaTKGRcoDd7VPK;YpFC`UjBRy>S9q{$BzZ z3Pz>`QartVyePN*ad0f`7Yjx~j)y|9;zDqo237;7j#E=VtA=wCf~%dy;jp;>!vDug z(Lm{c8gu+Lz|la&@jC%>gQ9^zK--5s{mG$0m#Ey7lVkzO`g1yG{bUG1H{7&&0UvJ0 z*sGjH3_|a9uGbd;S^iQVp>osZvjp39+&YtNF>DbR?}|q__5P?oxk%F?_iny-EmClT zhG&S4KioopEI)XcdfP$n5kI?PZ1QmlhnllhWy)A63q7jgsxWK;Ymz2KjJ4)B5VvF%2hOJ|^Iv%abwc1@2#G6R#Iy9MY zyC9s+F1orC!H%z!h1Gfexv4^XGLc0&n!WZ$c$FeUisKHW>KZg<5~10Q)lZM;1)@{l cn-#*6n7k}SRuwab(Q%ZGSDG + + + + + getPackages() { return Arrays.asList( new MainReactPackage(), - new ReactCBLiteManager() + new ReactCBLiteManager(), + new ImagePickerPackage() ); } diff --git a/android/gradle.properties b/android/gradle.properties index c8d0d56..9462e1d 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -17,4 +17,9 @@ # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true -android.useDeprecatedNdk=true \ No newline at end of file +android.useDeprecatedNdk=true + +MYAPP_RELEASE_STORE_FILE='my-release-key.keystore' +MYAPP_RELEASE_KEY_ALIAS='my-key-alias' +MYAPP_RELEASE_STORE_PASSWORD='123456' +MYAPP_RELEASE_KEY_PASSWORD='123456' \ No newline at end of file diff --git a/android/settings.gradle b/android/settings.gradle index 2a9f3cb..dfb2a3b 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -3,5 +3,8 @@ rootProject.name = 'blocpoweraudit' include ':react-native-couchbase-lite' project(':react-native-couchbase-lite').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-couchbase-lite/android') +include ':react-native-image-picker' +project(':react-native-image-picker').projectDir = new File(settingsDir, '../node_modules/react-native-image-picker/android') + include ':app' diff --git a/app/assets/4-circles.png b/app/assets/4-circles.png new file mode 100644 index 0000000000000000000000000000000000000000..7f88a456c3a36c18adc227aca2283c59fbcca47d GIT binary patch literal 4048 zcmV;>4=?bEP)Px^ib+I4RCodHT?=d+#TlNNyYutxJe)Maj}odpn>aQV0Rg2Ak3y9KNfDy-0knu# zDiuhDv`GLV3sDjvq!p-Cs?xMT9~2>)KpT~oBC3|65LEm~N?vLVKAWV86URP3?{25x zzvp%K*7w-m+q;`ZtaO^4nScKI{{P#ZnVp@T>k(dBdJ^HBM9Ok=CKU`wEQ0r1xX ze;DU6DcKO?ydUR>VdnuYp}lrsU*8)SAkPaBJRc<|mdvTCs3=<#;9{+0d<~S%aHLP- zU~CV5yEIMPS=-fjz)^V*g$JAKmkJ@)!G^W?T?D5)@(pY_!y+)QNHEt5+Q zSK5GNa?9y{_GD8eGMY=?n(v5|HpayImZsZqMLdKHsnM#;o4CZq#&)prv9{`iC;Pn7 zIbF-}k~#IPyrK=w)9sA$z;ucHQ_v1X(1JX;wWH^mHipK~=3$$Z)2;Itl*!;rAmeYkKZt>{aCjh^}>$O;L39@ zD`Ua8i$@5d;#FuGe-UkI+V%oto?OYKhxC!&ViBTSf(x1aM}D;9@0@qa@XE$JxZqDi zS;&sULdf7sdv$_GZ=TgYFyL1I>DHMu%VhX@Tv*q*)!{+8nAGH*)t!;;*>q(kCEC)s zQEJc2 zd0V)%_t{){qAMG2#RK6px#UWgFIjhdG_k7oP~S;2hG|Vfpi*p6nGgccg?NfC9(Hbn|O*nqX`&Soio##vdJ z2>mD>NGD)eBfpy;ET>2b@W4GP0?vv_vYYieER&EPN4hD=7pG?b>!i@EFW9=; z7wF9(@p#TE%C9#k>2XT;*+Kp%9tgHh%Iw+N7m7I-=>rY!%R;x#fYZeT{}KMT$x zCO$ATFU~!i$#Jz{IY+}Ko1jug=v;$3C5R7!Ti*4eF;z^#8Y|rfBUA;K8yv(3 zNMR{2SllQobs5a5oFEKFSZX}^)K7c}P4a{JC{dv-7Q7uI97d=DCbt-f4{>5Bav0{s z-6~O`9Bz4inX3`1fXN*O87Dr(dEIObm)JktDN&WUCuBc+g3qg~u39 zcBl(CGqSiQcbappadV!^=j4B50eG_c0$Qwae@9NVf+q!!9nDdfxiNTA%4zvHK zNI7w)=ciz%$5*Ck^1e39cOQBj>Atc9e%Jj!33`eY>h(1)AM}ecSc$)&%&5#Yg?5xL}MGW;leuIA7;x}C6D|~emCPuA*BsX zfz-5n(2QwjA=S>&DZW_K75RHMT$E38*@uW2B|=*LV%~_4K)=NyU}lA4|k8oY{5! zzjhG}G|&4~C=hxZ@4xHpmMMyyl)tI!hCCAIqb$59*%0pR*_u-p0=6AY3ga@d8lCN4JBss>!4-7|pU~ZRG~RQC zmxK@|(swi#xt#P9l>Pm;ns6G~5*69N&iUh353U&#?ePlk912 zLVLLOKyQEE`f?~3T-s14%ESYLvpc2|9r*QA&omAvBEwIFC6`kX7EG{?7w*c3;yE?k|v7`9@n?&y2htgn>k!!7ma<4f1I zcpJMG-;6Zpy4XO#AVzhsW0<>Bn_z$R;tuzO6sr^IYbvd>VsSuYbsARu;lVAWX?zGP z{{}TJ-u(ePc4Ybfz2rke#uNANbeMC&amV{vaVsz|KA$D4_ z-O;P$tdBYtPwo^`^R*Vu*OgA1FI;&$y?6%kiYR)P!JWO%Ve|DjG+(<)-hAmDeQV>l zK)~1FkbFD(_tDYsQlO*9?s)uM_XL-)qyKnFY=^cY4TmJ4=zXFP0|M&6&kZ!(i)}(S;7*_67N?zZ^zfL_68F}2erS7bo$Ws5BQ?#!%U;58 z{8Bqk^+d+uG)qoWKAw>Dgce;1T7di~Bqt~Du&#<<>>V^0nvzrQx8S7^CKBBpcxm@# zlza}O!)kXLyecK%N@%fl+2-^zKBpXAz$yd}y;5>Q$QR^D&VN7D+PK<{YxHuQy-<2W zbYm_izmk75*^-fzc{3WfW7_1xOa)7j9s<}G>GiIYx=EhOf!Uywa)p#7zmk75)sdDI z+RzhMl~V8L$pCuK9uj3Q8#_#7GRjCNIg+PxMw&7q{Hyyp4WMbHM4RicLSou2o6?jo zdJ-lHmseF*?66ZY$&tJr*;2@mpUL0Wx>HDL!%HuW-M8T`NlCXBDs>!J{*6P;O`pq# zW2m|D^CXu|rsR3>Gx@vC^0G0-`RiL7Zbf^C>1y>4%-BAb4JCGqeKcFXDtY8@^1B(& z6e%mjH`1m^nLDHDduX10 zn<0cZC{j0&v&Q{m6ZxNbAiO#$J9-K!?pIsrt2V-} zlXB{m_$$mC@uxA;azR<+ez8i9Bzc%5r702_#W#rl+o=>^>ael#ms8wMaZ2~uLH;Km z2zIjh!bw6Vw)xBsnt^=Y`puypN4no^G5ddt6#9MwZ270dM~6P4#qLk%d_CPq9X|Qp z{Qb%lDP)`$k8MQXLf?Wbe_)HhpUvz%el#6WN~ipEdFrLfMAGkPk)@DQv;WwCz}m0Q zW~+II4r5Ro(H_o*j`B$^n~Vy18(QMQaQ9>Fkqu5fM)Fk7NtuGnV#rSZ4a3jr>dN}fBYi)C7cQIA6)bid z+TML*!^4->9XaK=pP+wAt*EYk6Q4z_C^qhN^=VRW3U@@dq|2kUw0DXjOa-w?>$ee) z8EK^mab3MS!G|t$LkQuea#Wu5lmfr0&KUVK8zCf{l@t=F?u=}YYw;`51e9bZ8Jj-; zdgqQS^eyXRC56k2y@)!BdE+K@^_MJk)#zB8(KdX-2jU_!B z(Rt1^((?`Ru(wN#$2ZmN`|!Pdb=fUHwBnp)A{g8ZM|id{9D|Yp5WeMfKYP-cWVDNi zOUg8}E4BAazu6whCygsiiCGSBrqS3S;o4SnjhJ>#*uj zK^Wyi=pUkJ$7wj*jlVZIh#|&a85@5ky#K_pYFS!UIrX0B?I<-BSqxNCR2Uc-40$;z$Xh@5)<%#K-|l5q0Yz^etSdxT0;YP3 z`~U`qE>vDhT+<8o#0VpiT&xq6_ucEw<8c6F{^871qR}w(Sf2pT zJnly$FC?l75_y%D_ytlP3gMy&gAzelB$gW0nB(pnlU-1oJ;*;Hl~iN?ksu$jU4Fj9 zqIj4K#BSL!X+lOvE?A%M-kLVJzSo}w5w#+=X>U7~X_gzIBw_ymW$+aYFr|*;k1@y9 zODumw8R)JoUSfBD24n7CnnRyvq$MoNCsm0Lerep?a7WHAHgN(QCn3?35~Ix_05tz` zhRD$1m7-A6(2d^Pf;#`1IO>-)aj?pb=N)XxBt-5hN?9tD>vE$oRe-MeZxh6vgYmkT z=q~QPfzv=4T46_{KFhJ=S(iS_Nxv6uCtBFd9>&38Ng8Arrq9u6(esp;l8XG-$`A~m zfNk@&hrGMx$LrqLP_Z??R^RU-@D<`AC7=5EXK+~e zJI0$Y6q4K1y4!ss3Be#2p=I~9#FZ0JWH<7ab>|YCoyWeLyUp-Msg_55^W#<~ zuuSRu{E=wBQUG0QfQC6X=1XPi zE;g`G;&~RmI>YDc2Cblyf+V|fzlvwJ4}FzAD~rIi=DkybXEbsUZ4Rc+>;GnA&ntAu zjIk6PFaf{(xuwe0@>*BT8*RdSZZvHXn*CP@b*D0)i|UK9JIA~0qoQL&ZPzm%FI=LP z5Mq%6!36J{e)wZvqYQo^B!UWMYly>S=j?2kKWgEiE7xg|=OW}m9=A`iI=o+0^yi}M zJEQv;Q|cA^-Ly*)DtU>>-o+_k9^ED#)is&437R+8aucxBu08Rfc$1_`$KNYiG)uV5 z>B!6ZsmW(zdne4uW|+`kSRJS{Z}Ad5f;UE=Hg{7+QFZ|zaBvp~ntNV1C{9kuXa8}k zvqvh8MLJr<%=o?W{rl|luOg~dXYym2OL3=Ws-nB^QaeDrCB<#u9c(`|Q{DR6`6x5A zH*ZJXq*&9V5Woo*#*T{~Y^3Epf$-nEsTqS*mF1KD3K~;Xlb!ws*-T1h9iBa~qzj55 zdqzDb70CPyDsRPe*vV7AtLXq9`sR!L3sK)^(0}Ao>5>=Iw-V$41yrLaq4s%ENcFl~@pZ@}AA3zUK*2D7CB4S>YlmS@ZG_AImu09AF63J_b zon7L5BNe~z$!TaM+kDlDWRZzSG&_+of%>=g3@67ljrC*aL;OAc4ZHQzjkBluuodHP_8sxz-n6KbDFY?9?ba0Q{vrZ&t6Ta;mGa@2OKMiKo*@M#hlg&?D#J<>)13!$O|%;5Ii|BauBtwOzVT zYc7UR;D-=tvw~hV0M3iuuxw431NZh3w=Oq?qwhwW$-;MbNB@@~CnJb9ac&T4ou#-^ zw`d09t&dXJ@Z~(C}cC^4tnKT8ndO< z0ODRRY=I6f^)>r=O;8|3afP<%XG)7^U3VohW0aVBWp*EKoNTqu>5z3F$TYkC_S+1w+GAW%*%JE0`wS&aQEKXPD+O zQH4}lS7WgxmSgKyH;N4WELcE6#B6m>A z4QCnT>HIP{+jrT8!j}E#AdPCPvP{dljX~aI_Se17?-TgUnI^`^*tsz+ohx6nX(nr3>2!~5$iJ#|M z!O0$TNAN(z0*Bf4{%iJVjYQnDPHn5%2?Bs?Ii)?{pZ+gPv4D6%j`h@q^B;O+EkXGza(7_tA~&CKXQwXZmgY>EADO zU~+0UUEXP0)Du#peWOOM`gN`aa`-C;V@m^H1)ZaAt4lt<2pF}t z*t&|=RN)at;_WFDM!C>@59yC}vK2PUBKbwfI-&8*=p($}F{EgQKTIqg2-9NvvCy>b zZ5nRQ;Nx0(39y1gP?(SpymMdpfu!}V~5>3%Sw{Cf}UbfVJokUg-!ML zl=txowE~vduwfX{9e>SJD=49`C4Liy4|8#KdGNO8i**g~%K9kfF|gIfYq>@p9c?!x zZP{wpj|k^H3t>Sz670vLfK!-@RT!W~<%?w@RCCia9|y&n3+hgjWc|VBXiZ{_#;zzF zM>XKXnb}o(8=mwM3r~PaU(8e0#wY~Q2=`6H88p%b5=BH)#n$Go?%oTD0D3TrlrDLr zHKWdz8W>~&sU_r<`|&6Ow6=irWt0%I2U2Nypu$XK`z$-_uBoVJO1c%CIGCU%sYX3? zSDr9ZQ2P)S)}GZ|)H5wJEEo)9e_{>ASE_78Gz)e2k?GOLTyUL^76?*B#&0q6*!$`a z8HYl_FzmJjQsz(u&{oM4Z3e(RGJOZP#n<5q8V8n zjh~0V-Ck${X@1kHSMI;)nJOFO^oT^O10PB=N&-8W&Anf-dor|Rr0Q}XZGw2crNQ!c znzU~(v4=aEO}$@nz}-8cWj}o!!!I~-0qBmIQi8vE!u2{@UMc+vYRfM8}>553*%rrFsLI)C2VJ50y6?Lv}Dn)s`f# zD<1(ff%VuGCjh{K>is&C^gf9p1O#tOS)_IwYMQMqKplAT_v?vzN#8M-9|bwwKoo4z zkuEiAR-e+j=|`ZRD+A#Avl!AV#4PRlW5*|CT|u*XP&kxn4?p!2P)|b-km_Y|ndvpe zW)Vs=MLD%dLu2||DhYHVY4ONG=EqXZQcLm<4)WY)@Ap*98#xunF|geKAmjyzL>oYv zgCv2Fd-%xBn@M_y&#`CRA2L&T(N)Dx@rQr+(y0A{Yh!xIO9M$P-h8nJP)SO_Xq;b8 zlznoq2daJvAf`+Hh4$Jf)iV{QC$-EeA{NPP!!m)++s@i?c8+%R6h(m*O-J(fe<4M{ zI0v?vHgUv&;E7Qs)2?c@0;faXc228~aQM&oUGOA=eKReXoEU_Tmh<~9rv$ZrxTWDv zPUxi#L_FWwdH1(lr=IJf{KX=qIHxBSe`&~gn=Traf|^h6eT z2#_yyLr*vvlpC*{iWTN&*(HQUxA13lJ|C%Y+?7xN(UNA<%8VU2P1a>XYbRfrXsF^` z1%YG#6wI+&&2&LfGZxDuqH#vRw|Kehw~+*jnqQ)pG06Ss5QZR1oquZ3;WU56;U^Z? zHXTAk=pF_0AoS3Y(n6;W5(j3q;#4rG3N!4TGh?eAo)?e#)kUYu;L@?WY3Ah4rGa6@ z9PfRiK-}M0)YIP-BcOEM3iWE12;iunB&7c|^F=2vtdZYadH8Y)IWZxnMp#ideOVYK zPBmd8#LD!k0G=^WB(w;eD|J=2)gjN2RvhA{mAvi6kNhpfn~I&?;JZvgF%dbwO}#`n zrR8Y4&S2dxcA!OffKbJ&g&+yLb8(EDk9>c<2S793FUN0<(xSUs4qsu0ljjB9IOr)@ zl+&bL3Z_BVYv#lIqX*~Bzl7dKP~q^4n0JOod*Oqq$cd^cPVKM*1nP3?k6p9uuI^`u zKXADqYpZ-bdyh~gNk+Gf+7WRAY-$r=UNyCLtcNmwv07`&Up|o@nV7PN0R%YcS%YLz zo-2)R+6da)gY4{a6|-{+kd_K)GIY{j9oLC1?badil06p1IwLk)Ozhj?;Lh>Yrn=QI zvgfZS(JV;pL;i$!&!F@{;#o3Q3VS;?d$boVY6A#PG&{OFRxCiH}7r1J@@94zpJrK`M|Ar9J!4HXTp%zG~CiTG;Y}RT$*B}Bd`3(PqS0F!owQ?h5nt< z5JgSaoz2l-TxZ-~Q7yrLSNVI=yIO_G=(YX(e9JbH$l5OmxBAG4<)=jkP6A(agogRJ z*$1W~Ovrnhi9KyWa#i|_yl$}l1C~gSc1B9hw6oVois{icEysO@F*9}dx$*M(%tZVw z#o1o0DrLtes^`{~n53_Yhhg-gNV{Q|p{35Qm=yg)?)Sx1^fV&=G^k7p$HH7LLZVHu z7W3~5k4o=J8n&QFd-8l?oNc=V8e@377?#VkO5FE5Av-ZUDaM?x$#*3ORyqN1DlFyO z<=NoN4cf8u)vi$Yl!616`H(1bZ0TcrbG{N~U%ICaLP0m18?$~xiDPf!Y|Z1BJaD8$ zmK5V_l>`*a)N>Uhz(xq_-}n?n$ndw-<6W$;kH7!o*h0%KYvOJ+$8ne)eA9@l!~uW) z+{+{NF`^9q*fAT8xz6p4ADnPS08Y42NK25K`<_W=+h{_`Wx?L6H9GH$FOFMxqDu*D z`ENEm5V^^>*6j=bqRpjf{-uz%AaInqwGVV%@Ll75A4SUxHYy{2PN{=Q{E+YAp_os5 zZF)wv-)M}gLOgv&v-?Q8@C^B*rX*id+)II%?sV>h(&I=)P>me&PrJ7|?8qX!k5&x! zay(PjiZY+nU4dw8HM`n_MI~->d@!FeWnHul=+ygdYFG`3!9iqQiMX6Lx0``m*6-@U zgf>PZ!4U0@hv#(KO5Fus3m@C1L$>GaKiZ8v^h*{K$k51oJap!Hhy3}+=W1x@M*BKM8#19xC+>ba!L68! zYt9dD^WCy}9PU>L!*Z7-wX+x1)q@oKwr7&wep0A6jy!4pITGK1N1%jF1qKgm)q?~t zqYHyL(XTv7xP#ec{}di4$s@B|+GwH)BJn!EC=%I5jn4p9V*6S( znOg~m!?d%?r|Kwp-NX`d0o%XoA%D5^VH{9@G@*U9k@9<(21*u`zzNgas@9l zAA5+N??Q;XgQt4=udnA6!x`hJlVSw&*`mycEn(gvJF2j~XJ^x2$fu}9^wp@8crC?i zm_)(X?h`?q;|;5A#9wh1$;Xrk==S(K0b9*^JhI@S%p1Sa_7S7Yw3U#x z@u$Kc#j9PK6?HY?Qz+*og9&mePAYI(6>CV?-HG764*cnLr#< zKsId9kdqI}GFyp)sr(IX!$FueiPnX!&nTWPW{1>I5&q66&wcYbv`I4_S9Y+5JxZ;t z-nY=@>%QwtrjnU!TxfYxjG%QZirv(3gQ#=COP(5WX%fo+YotQs}0Bn&JI zD$kX!?1+ec!bDDyj*qhuJjzC4lRr1(Bec&edcD289L48%L+SXaO8g0O+?STV2~Pdo zh|woc=@I#D>*GMLTodIkTCweHfili#Ssaj2YkDNW-fo_i&$q?d=`$!;_x=9;&j;M} zFS>m$cO52uKLTTDRn4h?2{+CWq%S1!(!w(Dk|>_Gb538AOsGD?6H2pH(^L6Bi5TPS z=~}BA)EPx2CCEYRvQaZLmKOmg)KQ#`h@Azj4OniyWSuQxj0<5ZHIY)uTZ_7K>#n_xb{QNN$7WMtC zP}{G2(f5O#H2vYFjO!q#8i2k^_i?O<=T zRHQMq-Lj6Ir+9`u`b~POsv8x1Qp#Kf4=2e_fK_q+zC~eZSh7RAhud`20yfGHwp0Vjs+@9Lfb7GiTt^0tQGOy#c z&7U~F`%))QL?mGRHTGJPlN-U(Y?oEgl9IoxUc;Xon4WQVx_J)Ri;`$eNU)qIJ3~G~ zyK9_;=WT8QGHrvGve~}IbXQw1vQA57>2Ic_I?JD7^1LuY#!U}ZQ|p!w7{n9831pgc z?QX@Qa=5smkCiJs?o_g>{`mTY3E>NPO1S*e5?n6s==}{m8Nd0$bOe7W^JUVXCem$& zVVwa@JThq6cf&wj-*s1`;?bD~=WHaALN5m`N7$>$KCTo55529F+g|9KVb-{J52A z;w|~fVm@gSbYe3j_IHyZl zs*LUY!#pP$RzF-cn~--ldrfw4LI@g#3HcJAO;YvGHy6uqH%JAHR2DbWW+Yp9H z8GyS)3JkwEsIUWz<}y?}poOeEne@i$uIwgL`-%&ol+JY`i#DS1#s?yDxI;~q>5kEp z%k)03wDox+XsHVnPP5a2XR#_OdYapZX=(AqQbn_1z72)Z)XDZYRN`4ymPyHCQ zS>u)q|M;-!_)uqc!&s@=WPe*blG~U)S6N zNGwIuaC!!49B7Ph_hm{e;o`4DVVq523~PbeKd-Y7PkfpQW1iQTd-h@Jz^mk+#X5i3 zeUe}C5&!<3atN?p8H^^Nqg-P=y}L0dCh~%Y7peElK3jXvcd!&9<^zTSs4ayIFd;aY zOwv0`Ft&UYCaRR`?Yc8p{8*~jfCo1uBZ`?6Cb~gkE+_y3pc;a{w?s>2ogN(yqp^+$ zb4~b+QwOeB+FFjYsUxr2V(lOO!l7nUx|#O#DVM|dE7-dHHQ5lBjBHxkWs%h~Kxq?t z;X)wH^4sWL-7=3hJ*td$bdWVYRlg)S#?JMeoq*6deknN(4^bVF13!Fd2^bC(0?Yur zCLB98GmXM)LL>tPIMs-Oe3Ad<6vTS+0Gni99SNAQ`VBS><_Wg`MOa!Gm9>9n#AvX1 zYKyHNUcR9A2_h`hs@>E8KJOHcweb}vDM0_tsi^;ejqsWYnoaFzsqi`K`^PS9P%fmu zA!n{}L>BVsvE-|{_6}Lom=dBQn6oBb(C0Ap5EamwzE#<%mHaoiFaeu>r(J=yWB} z4t_@P79+E8XT$Efi&|CUJZdEw8a~JAsdkac+KCE&OEY;0-)&`K+O58D^;hm&6I0QF z!Mo%1o3c1u6yk(S#Tc+9)?>Xsj(FSS*f>4qf0?*4&Whv)rBHDEY&6C|seT_zooKyeY<3EptR%-A4;S^0LJC-;iMO~lw&BI! zH2K9#stcFO>_+@-ecaRO3N(oXSzGYqKE?{^{e9x#!?u6l&?TKwPhf9(j2w_EaP@P3 z_I>v6A;G>0doh`iz|l7>!d^Org~FCz#-*lN8br;OUNgSZER2T&#Mt_m& z?MR6;WMv+V(>psBMXT;2u`dyo&w-wDuXab~^VhE3n-1Oi_G1?&66`z1Xen>!&HILL z+NJ%Ya0Yd>fj3f;|HPa@|DVr&p#Sm@Y8W=+&!9|Z+^`jMFZnP}Sj+9TBHmkk%9;0F+njHYSF5RmMG6;JBll1(kLsYdpfG?td6 zvf_~KPU?J*-BykiYeIF90OL(G0)NWqX{Z!+ifGAuh`g*gP(wa``&m36A88M}#KQK8 zgyrHT?mbXXQ4kP#H5E@ZZ1Rp#(wSbeK5a*!%L6cS7X|O3>>6o~40={+r#5+v3mmg* z*mC@rGEo&1?ukHeXxAhe4qjLp8lBN2wNc$@0aV>t@$Ha zDv0{lE!~7UEwJAY48xE-*ST$9EUr)G(!;Dkt#Aas2*AukCAh(c(NvJ?c6YTu% zzw?vsR)Bv|<7i5c#z6nB2rRTd(h!RV_?E7{wKZ5aYxAX;!X94%j8d=#`*nc)lmgc8 z`XL@FFR=h(JvevF(h#5liy6n$r&hD5%TedxH3vD?%n80iB?HUvhVSA6iuhP15-}YK z7o*L^MExHBasL%&T!bRlVK<|I`efSpnn0LN@$zf3@8&Aa!Ip!zl1OeB24w^V`v$xS9A-kQf2Or$d+|Fu)q@qdd zX>s=xaMtHEIoGC+LcG3_8PikyMGbY$LJaczqXqF2x5EhPpm@OoUGY~WjZRc zjv}vEr($Yndh|7KOl`Y*0Gkj!P?;qG6Y8Rh; zfv6<5s&S&lXzmp{7L#-dU(+E5id7OAs3CpjPUw-&4rig94=fMW8h)+k<&hGos}VBY zu`gqyyQ%y(`#KDCSI6$ z|JjD7u)7n+2il5P_vFt>>O3C*GY(fj|C67bb!1m8qBp5EYL~$@rljlTrQ62LpS14O zkH34n`%zd><5SpidlzGPScgUGo0FP@+&+yB(p!x&-#y-b>$lHGIa@T<_vp*{2tSyW^+1} zfNe~Sn^ESZ%8WY36?TV5x|c5LBz&LZ4^6fIEJg@E9U*z7JR4;Ugxyk))UROq-xR+A zjiW}d#gi4*8t=QR11o+dk}2zfFt4xo!o)JwcZ}7dk0mvBnuNeM|c2-3I`^tGgesQjHwQD$>ag@WIENjK#7z05_EMy(tTx%{rx z=yq3`Y8ZVoYM@a~YH^>!Qj|I)LTIPWh@ZC8jN2}^#UyA@t=wRa#>BoDwrZsuUQsL6 zP-S6BM?h3}Q~&NuK|gTd`_hof|5KO6PjE8t*s+$~e(r%2&HiE!bb1Z@_tY!u{X{jQ zM~?%gcZ?itR98qVwNt1zH0$*p9pZ=834g5+77ic2UC48e~ zm4r=youpN|8yvhoccGC(UByrf@nMUmtd#6lXW-VNr*&AEWTf2STw-ApBQC7kB5>(s zjUz#1N!iLZ1{dkNB3%^5UKDb36n568(v0(CH7&vXIBVkYU1T4%@%`_x`lcqx?%oWy7qa0r4Rm2}3VC~X>me0BSeAtqF zOqp3gyVml4L~1ZPx=^oWi!|@d+eDdlF*8{l%ADaVN?&G4^%D*!C)Brs3h6M%1#gBx z{nSuzG*`PWX#Suzk=8K1;?PsV*on<8{)|}fW|MXG=KgGrM`?|+Sue4P*E3Z3O%%~i zSYYMgkai`oIX=K$7GIbT(K*?u!d#(Ji_-x^MsR_8Zog0 zkOx}ALs1Z8Y%K(GcLx(~fwe6$XI@U=4egiAZrw1R8go*{r(y%Ke8yVRe6Tm^Ou{OiDp10)y7@mJRJqc~y@@C#KNZUIV9 S29N>f?UI*Pk*bz34gL>(z-J5q literal 0 HcmV?d00001 diff --git a/app/assets/4-circles@3x.png b/app/assets/4-circles@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9ce08abb09d3c80c20133ddd2e57082041e737b2 GIT binary patch literal 14889 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 literal 0 HcmV?d00001 diff --git a/app/components/Toolbar.js b/app/components/Toolbar.js index a35345a..fe49401 100644 --- a/app/components/Toolbar.js +++ b/app/components/Toolbar.js @@ -1,5 +1,7 @@ import React, { Component, PropTypes } from 'react'; -import { Toolbar as MaterialToolbar, PRIMARY_COLORS } from 'react-native-material-design'; +import { Text } from 'react-native'; +import { Toolbar, PRIMARY_COLORS } from 'react-native-material-design'; +//import { Toolbar } from './materialToolbar'; import { subscriptionTracker as tracker} from '../utils/utils'; import _ from 'lodash'; @@ -44,7 +46,10 @@ export default class Tb extends Component { } render() { - return (No Toolbar); + } + return ( { + console.log('Response = ', response); + + if (response.didCancel) { + console.log('User cancelled image picker'); + } + else if (response.error) { + console.log('ImagePicker Error: ', response.error); + } + else if (response.customButton) { + console.log('User tapped custom button: ', response.customButton); + } + else { + // uri (on android) + const source = {uri: response.uri, isStatic: true}; + + this.setState({ + avatarSource: source + }); + } + }); + } + + render() { + return ( + +