Onboarding + Login
3
.babelrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets": ["module:metro-react-native-babel-preset"]
|
||||
}
|
||||
6
.buckconfig
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
[android]
|
||||
target = Google Inc.:Google APIs:23
|
||||
|
||||
[maven_repositories]
|
||||
central = https://repo1.maven.org/maven2
|
||||
9
.eslintrc
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "rallycoding",
|
||||
"rules": {
|
||||
"max-len": 0
|
||||
},
|
||||
"globals": {
|
||||
"fetch": true
|
||||
}
|
||||
}
|
||||
70
.flowconfig
Normal file
@ -0,0 +1,70 @@
|
||||
[ignore]
|
||||
; We fork some components by platform
|
||||
.*/*[.]android.js
|
||||
|
||||
; Ignore "BUCK" generated dirs
|
||||
<PROJECT_ROOT>/\.buckd/
|
||||
|
||||
; Ignore unexpected extra "@providesModule"
|
||||
.*/node_modules/.*/node_modules/fbjs/.*
|
||||
|
||||
; Ignore duplicate module providers
|
||||
; For RN Apps installed via npm, "Libraries" folder is inside
|
||||
; "node_modules/react-native" but in the source repo it is in the root
|
||||
.*/Libraries/react-native/React.js
|
||||
|
||||
; Ignore polyfills
|
||||
.*/Libraries/polyfills/.*
|
||||
|
||||
; Ignore metro
|
||||
.*/node_modules/metro/.*
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
node_modules/react-native/Libraries/react-native/react-native-interface.js
|
||||
node_modules/react-native/flow/
|
||||
node_modules/react-native/flow-github/
|
||||
|
||||
[options]
|
||||
emoji=true
|
||||
|
||||
esproposal.optional_chaining=enable
|
||||
esproposal.nullish_coalescing=enable
|
||||
|
||||
module.system=haste
|
||||
module.system.haste.use_name_reducers=true
|
||||
# get basename
|
||||
module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1'
|
||||
# strip .js or .js.flow suffix
|
||||
module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1'
|
||||
# strip .ios suffix
|
||||
module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1'
|
||||
module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1'
|
||||
module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1'
|
||||
module.system.haste.paths.blacklist=.*/__tests__/.*
|
||||
module.system.haste.paths.blacklist=.*/__mocks__/.*
|
||||
module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/Animated/src/polyfills/.*
|
||||
module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/Libraries/.*
|
||||
|
||||
munge_underscores=true
|
||||
|
||||
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'
|
||||
|
||||
module.file_ext=.js
|
||||
module.file_ext=.jsx
|
||||
module.file_ext=.json
|
||||
module.file_ext=.native.js
|
||||
|
||||
suppress_type=$FlowIssue
|
||||
suppress_type=$FlowFixMe
|
||||
suppress_type=$FlowFixMeProps
|
||||
suppress_type=$FlowFixMeState
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
|
||||
[version]
|
||||
^0.78.0
|
||||
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.pbxproj -text
|
||||
56
.gitignore
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
# 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/IntelliJ
|
||||
#
|
||||
build/
|
||||
.idea
|
||||
.gradle
|
||||
local.properties
|
||||
*.iml
|
||||
|
||||
# node.js
|
||||
#
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# BUCK
|
||||
buck-out/
|
||||
\.buckd/
|
||||
*.keystore
|
||||
|
||||
# fastlane
|
||||
#
|
||||
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
|
||||
# screenshots whenever they are needed.
|
||||
# For more information about the recommended setup visit:
|
||||
# https://docs.fastlane.tools/best-practices/source-control/
|
||||
|
||||
*/fastlane/report.xml
|
||||
*/fastlane/Preview.html
|
||||
*/fastlane/screenshots
|
||||
|
||||
# Bundle artifact
|
||||
*.jsbundle
|
||||
1
.watchmanconfig
Normal file
@ -0,0 +1 @@
|
||||
{}
|
||||
9
App.js
Normal file
@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
import AppNavigator from './src/config/router';
|
||||
|
||||
export default class App extends React.Component {
|
||||
render() {
|
||||
return <AppNavigator />;
|
||||
}
|
||||
};
|
||||
65
android/app/BUCK
Normal file
@ -0,0 +1,65 @@
|
||||
# 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__' + jarfile[jarfile.rindex('/') + 1: jarfile.rindex('.jar')]
|
||||
lib_deps.append(':' + name)
|
||||
prebuilt_jar(
|
||||
name = name,
|
||||
binary_jar = jarfile,
|
||||
)
|
||||
|
||||
for aarfile in glob(['libs/*.aar']):
|
||||
name = 'aars__' + aarfile[aarfile.rindex('/') + 1: aarfile.rindex('.aar')]
|
||||
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.tagfer",
|
||||
)
|
||||
|
||||
android_resource(
|
||||
name = "res",
|
||||
package = "com.tagfer",
|
||||
res = "src/main/res",
|
||||
)
|
||||
|
||||
android_binary(
|
||||
name = "app",
|
||||
keystore = "//android/keystores:debug",
|
||||
manifest = "src/main/AndroidManifest.xml",
|
||||
package_type = "debug",
|
||||
deps = [
|
||||
":app-code",
|
||||
],
|
||||
)
|
||||
153
android/app/build.gradle
Normal file
@ -0,0 +1,153 @@
|
||||
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,
|
||||
*
|
||||
* // whether to disable dev mode in custom build variants (by default only disabled in release)
|
||||
* // for example: to disable dev mode in the staging build type (if configured)
|
||||
* devDisabledInStaging: true,
|
||||
* // The configuration property can be in the following formats
|
||||
* // 'devDisabledIn${productFlavor}${buildType}'
|
||||
* // 'devDisabledIn${buildType}'
|
||||
*
|
||||
* // the root of your project, i.e. where "package.json" lives
|
||||
* root: "../../",
|
||||
*
|
||||
* // 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/**"],
|
||||
*
|
||||
* // override which node gets called and with what additional arguments
|
||||
* nodeExecutableAndArgs: ["node"],
|
||||
*
|
||||
* // supply additional arguments to the packager
|
||||
* extraPackagerArgs: []
|
||||
* ]
|
||||
*/
|
||||
|
||||
project.ext.react = [
|
||||
entryFile: "index.js"
|
||||
]
|
||||
|
||||
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 rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.tagfer"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
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"
|
||||
}
|
||||
}
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':react-native-vector-icons')
|
||||
compile project(':react-native-gesture-handler')
|
||||
compile project(':realm')
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
|
||||
implementation "com.facebook.react:react-native:+" // From node_modules
|
||||
}
|
||||
|
||||
// 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'
|
||||
}
|
||||
17
android/app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /usr/local/Cellar/android-sdk/24.3.3/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 *;
|
||||
#}
|
||||
26
android/app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,26 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.tagfer">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:allowBackup="false"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
BIN
android/app/src/main/assets/fonts/Entypo.ttf
Normal file
BIN
android/app/src/main/assets/fonts/EvilIcons.ttf
Normal file
BIN
android/app/src/main/assets/fonts/Feather.ttf
Executable file
BIN
android/app/src/main/assets/fonts/FontAwesome.ttf
Normal file
BIN
android/app/src/main/assets/fonts/Foundation.ttf
Normal file
BIN
android/app/src/main/assets/fonts/Ionicons.ttf
Normal file
BIN
android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf
Normal file
BIN
android/app/src/main/assets/fonts/MaterialIcons.ttf
Normal file
BIN
android/app/src/main/assets/fonts/Octicons.ttf
Normal file
BIN
android/app/src/main/assets/fonts/SimpleLineIcons.ttf
Normal file
BIN
android/app/src/main/assets/fonts/Zocial.ttf
Normal file
15
android/app/src/main/java/com/tagfer/MainActivity.java
Normal file
@ -0,0 +1,15 @@
|
||||
package com.tagfer;
|
||||
|
||||
import com.facebook.react.ReactActivity;
|
||||
|
||||
public class MainActivity extends ReactActivity {
|
||||
|
||||
/**
|
||||
* Returns the name of the main component registered from JavaScript.
|
||||
* This is used to schedule rendering of the component.
|
||||
*/
|
||||
@Override
|
||||
protected String getMainComponentName() {
|
||||
return "Tagfer";
|
||||
}
|
||||
}
|
||||
51
android/app/src/main/java/com/tagfer/MainApplication.java
Normal file
@ -0,0 +1,51 @@
|
||||
package com.tagfer;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import com.facebook.react.ReactApplication;
|
||||
import com.oblador.vectoricons.VectorIconsPackage;
|
||||
import com.swmansion.gesturehandler.react.RNGestureHandlerPackage;
|
||||
import io.realm.react.RealmReactPackage;
|
||||
import com.facebook.react.ReactNativeHost;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.shell.MainReactPackage;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class MainApplication extends Application implements ReactApplication {
|
||||
|
||||
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
|
||||
@Override
|
||||
public boolean getUseDeveloperSupport() {
|
||||
return BuildConfig.DEBUG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ReactPackage> getPackages() {
|
||||
return Arrays.<ReactPackage>asList(
|
||||
new MainReactPackage(),
|
||||
new VectorIconsPackage(),
|
||||
new RNGestureHandlerPackage(),
|
||||
new RealmReactPackage()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getJSMainModuleName() {
|
||||
return "index";
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public ReactNativeHost getReactNativeHost() {
|
||||
return mReactNativeHost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
SoLoader.init(this, /* native exopackage */ false);
|
||||
}
|
||||
}
|
||||
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
3
android/app/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">Tagfer</string>
|
||||
</resources>
|
||||
8
android/app/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
39
android/build.gradle
Normal file
@ -0,0 +1,39 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext {
|
||||
buildToolsVersion = "27.0.3"
|
||||
minSdkVersion = 16
|
||||
compileSdkVersion = 27
|
||||
targetSdkVersion = 26
|
||||
supportLibVersion = "27.1.1"
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.1.4'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
google()
|
||||
jcenter()
|
||||
maven {
|
||||
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
|
||||
url "$rootDir/../node_modules/react-native/android"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '4.4'
|
||||
distributionUrl = distributionUrl.replace("bin", "all")
|
||||
}
|
||||
18
android/gradle.properties
Normal file
@ -0,0 +1,18 @@
|
||||
# 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
|
||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
5
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
|
||||
172
android/gradlew
vendored
Executable file
@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# 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
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# 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
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
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" -a "$nonstop" = "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
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
84
android/gradlew.bat
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
@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
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@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=
|
||||
|
||||
@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 Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_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=%*
|
||||
|
||||
: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
|
||||
8
android/keystores/BUCK
Normal file
@ -0,0 +1,8 @@
|
||||
keystore(
|
||||
name = "debug",
|
||||
properties = "debug.keystore.properties",
|
||||
store = "debug.keystore",
|
||||
visibility = [
|
||||
"PUBLIC",
|
||||
],
|
||||
)
|
||||
4
android/keystores/debug.keystore.properties
Normal file
@ -0,0 +1,4 @@
|
||||
key.store=debug.keystore
|
||||
key.alias=androiddebugkey
|
||||
key.store.password=android
|
||||
key.alias.password=android
|
||||
9
android/settings.gradle
Normal file
@ -0,0 +1,9 @@
|
||||
rootProject.name = 'Tagfer'
|
||||
include ':react-native-vector-icons'
|
||||
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
|
||||
include ':react-native-gesture-handler'
|
||||
project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gesture-handler/android')
|
||||
include ':realm'
|
||||
project(':realm').projectDir = new File(rootProject.projectDir, '../node_modules/realm/android')
|
||||
|
||||
include ':app'
|
||||
BIN
assets/logo-horizontal.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
assets/logo-round.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
assets/onboarding_screen_1.jpg
Normal file
|
After Width: | Height: | Size: 173 KiB |
BIN
assets/onboarding_screen_2.jpg
Normal file
|
After Width: | Height: | Size: 224 KiB |
BIN
assets/onboarding_screen_3.jpg
Normal file
|
After Width: | Height: | Size: 126 KiB |
BIN
assets/onboarding_screen_4.jpg
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
assets/onboarding_screen_5.jpg
Normal file
|
After Width: | Height: | Size: 249 KiB |
5
index.js
Normal file
@ -0,0 +1,5 @@
|
||||
import { AppRegistry } from 'react-native';
|
||||
import App from './App';
|
||||
import { name as appName } from './app.json';
|
||||
|
||||
AppRegistry.registerComponent(appName, () => App);
|
||||
54
ios/Tagfer-tvOS/Info.plist
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string></string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<!--See http://ste.vn/2015/06/10/configuring-app-transport-security-ios-9-osx-10-11/ -->
|
||||
<dict>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>localhost</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
24
ios/Tagfer-tvOSTests/Info.plist
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
1653
ios/Tagfer.xcodeproj/project.pbxproj
Normal file
129
ios/Tagfer.xcodeproj/xcshareddata/xcschemes/Tagfer-tvOS.xcscheme
Normal file
@ -0,0 +1,129 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0940"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "NO"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2D2A28121D9B038B00D4039D"
|
||||
BuildableName = "libReact.a"
|
||||
BlueprintName = "React-tvOS"
|
||||
ReferencedContainer = "container:../node_modules/react-native/React/React.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2D02E47A1E0B4A5D006451C7"
|
||||
BuildableName = "Tagfer-tvOS.app"
|
||||
BlueprintName = "Tagfer-tvOS"
|
||||
ReferencedContainer = "container:Tagfer.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2D02E48F1E0B4A5D006451C7"
|
||||
BuildableName = "Tagfer-tvOSTests.xctest"
|
||||
BlueprintName = "Tagfer-tvOSTests"
|
||||
ReferencedContainer = "container:Tagfer.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2D02E48F1E0B4A5D006451C7"
|
||||
BuildableName = "Tagfer-tvOSTests.xctest"
|
||||
BlueprintName = "Tagfer-tvOSTests"
|
||||
ReferencedContainer = "container:Tagfer.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2D02E47A1E0B4A5D006451C7"
|
||||
BuildableName = "Tagfer-tvOS.app"
|
||||
BlueprintName = "Tagfer-tvOS"
|
||||
ReferencedContainer = "container:Tagfer.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2D02E47A1E0B4A5D006451C7"
|
||||
BuildableName = "Tagfer-tvOS.app"
|
||||
BlueprintName = "Tagfer-tvOS"
|
||||
ReferencedContainer = "container:Tagfer.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2D02E47A1E0B4A5D006451C7"
|
||||
BuildableName = "Tagfer-tvOS.app"
|
||||
BlueprintName = "Tagfer-tvOS"
|
||||
ReferencedContainer = "container:Tagfer.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
129
ios/Tagfer.xcodeproj/xcshareddata/xcschemes/Tagfer.xcscheme
Normal file
@ -0,0 +1,129 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0940"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "NO"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "83CBBA2D1A601D0E00E9B192"
|
||||
BuildableName = "libReact.a"
|
||||
BlueprintName = "React"
|
||||
ReferencedContainer = "container:../node_modules/react-native/React/React.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "Tagfer.app"
|
||||
BlueprintName = "Tagfer"
|
||||
ReferencedContainer = "container:Tagfer.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "00E356ED1AD99517003FC87E"
|
||||
BuildableName = "TagferTests.xctest"
|
||||
BlueprintName = "TagferTests"
|
||||
ReferencedContainer = "container:Tagfer.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "00E356ED1AD99517003FC87E"
|
||||
BuildableName = "TagferTests.xctest"
|
||||
BlueprintName = "TagferTests"
|
||||
ReferencedContainer = "container:Tagfer.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "Tagfer.app"
|
||||
BlueprintName = "Tagfer"
|
||||
ReferencedContainer = "container:Tagfer.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "Tagfer.app"
|
||||
BlueprintName = "Tagfer"
|
||||
ReferencedContainer = "container:Tagfer.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "Tagfer.app"
|
||||
BlueprintName = "Tagfer"
|
||||
ReferencedContainer = "container:Tagfer.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
14
ios/Tagfer/AppDelegate.h
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
||||
|
||||
@property (nonatomic, strong) UIWindow *window;
|
||||
|
||||
@end
|
||||
39
ios/Tagfer/AppDelegate.m
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import "AppDelegate.h"
|
||||
|
||||
#import <React/RCTBundleURLProvider.h>
|
||||
#import <React/RCTRootView.h>
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
NSURL *jsCodeLocation;
|
||||
|
||||
#ifdef DEBUG
|
||||
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
|
||||
#else
|
||||
jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
||||
#endif
|
||||
|
||||
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
|
||||
moduleName:@"Tagfer"
|
||||
initialProperties:nil
|
||||
launchOptions:launchOptions];
|
||||
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
|
||||
|
||||
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
UIViewController *rootViewController = [UIViewController new];
|
||||
rootViewController.view = rootView;
|
||||
self.window.rootViewController = rootViewController;
|
||||
[self.window makeKeyAndVisible];
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
42
ios/Tagfer/Base.lproj/LaunchScreen.xib
Normal file
@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7702" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7701"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Powered by React Native" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
|
||||
<rect key="frame" x="20" y="439" width="441" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Tagfer" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
|
||||
<rect key="frame" x="20" y="140" width="441" height="43"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
|
||||
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
|
||||
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
|
||||
<constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
|
||||
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
|
||||
</constraints>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<point key="canvasLocation" x="548" y="455"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
||||
38
ios/Tagfer/Images.xcassets/AppIcon.appiconset/Contents.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
6
ios/Tagfer/Images.xcassets/Contents.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
71
ios/Tagfer/Info.plist
Normal file
@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Tagfer</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>localhost</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>Entypo.ttf</string>
|
||||
<string>EvilIcons.ttf</string>
|
||||
<string>Feather.ttf</string>
|
||||
<string>FontAwesome.ttf</string>
|
||||
<string>Foundation.ttf</string>
|
||||
<string>Ionicons.ttf</string>
|
||||
<string>MaterialCommunityIcons.ttf</string>
|
||||
<string>MaterialIcons.ttf</string>
|
||||
<string>Octicons.ttf</string>
|
||||
<string>SimpleLineIcons.ttf</string>
|
||||
<string>Zocial.ttf</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
16
ios/Tagfer/main.m
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "AppDelegate.h"
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
|
||||
}
|
||||
}
|
||||
24
ios/TagferTests/Info.plist
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
68
ios/TagferTests/TagferTests.m
Normal file
@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <React/RCTLog.h>
|
||||
#import <React/RCTRootView.h>
|
||||
|
||||
#define TIMEOUT_SECONDS 600
|
||||
#define TEXT_TO_LOOK_FOR @"Welcome to React Native!"
|
||||
|
||||
@interface TagferTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation TagferTests
|
||||
|
||||
- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test
|
||||
{
|
||||
if (test(view)) {
|
||||
return YES;
|
||||
}
|
||||
for (UIView *subview in [view subviews]) {
|
||||
if ([self findSubviewInView:subview matching:test]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)testRendersWelcomeScreen
|
||||
{
|
||||
UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController];
|
||||
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
|
||||
BOOL foundElement = NO;
|
||||
|
||||
__block NSString *redboxError = nil;
|
||||
RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
|
||||
if (level >= RCTLogLevelError) {
|
||||
redboxError = message;
|
||||
}
|
||||
});
|
||||
|
||||
while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
|
||||
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
||||
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
||||
|
||||
foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) {
|
||||
if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}];
|
||||
}
|
||||
|
||||
RCTSetLogFunction(RCTDefaultLogFunction);
|
||||
|
||||
XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
|
||||
XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
10905
package-lock.json
generated
Normal file
32
package.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "tagfer",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node node_modules/react-native/local-cli/cli.js start",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"acorn": "^6.0.4",
|
||||
"i18n-js": "^3.1.0",
|
||||
"react": "16.6.3",
|
||||
"react-native": "0.57.8",
|
||||
"react-native-elements": "^0.19.1",
|
||||
"react-native-flash-message": "^0.1.10",
|
||||
"react-native-gesture-handler": "^1.0.12",
|
||||
"react-native-vector-icons": "^4.6.0",
|
||||
"react-navigation": "^3.0.9",
|
||||
"realm": "^2.21.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-jest": "23.6.0",
|
||||
"eslint": "^3.19.0",
|
||||
"eslint-config-rallycoding": "^3.2.0",
|
||||
"jest": "23.6.0",
|
||||
"metro-react-native-babel-preset": "0.51.1",
|
||||
"react-test-renderer": "16.6.3"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "react-native"
|
||||
}
|
||||
}
|
||||
7
src/components/AttendeeStatusTypes.js
Normal file
@ -0,0 +1,7 @@
|
||||
/** Contains all string key/value pairs to prevent typos **/
|
||||
import { strings } from '../locales/i18n';
|
||||
|
||||
export const GOING = strings('event.attendeeStatus_going');
|
||||
export const INTERESTED = strings('event.attendeeStatus_interested');
|
||||
export const INVITED = strings('event.attendeeStatus_invited');
|
||||
export const NOT_GOING = strings('event.attendeeStatus_notGoing');
|
||||
892
src/components/BizCard.js
Normal file
@ -0,0 +1,892 @@
|
||||
/** Contains the base fields for a contact reuse **/
|
||||
import React, { Component } from 'react';
|
||||
import { Alert, Dimensions, Linking, View } from 'react-native';
|
||||
import { Button, Tile } from 'react-native-elements';
|
||||
import { connect } from 'react-redux';
|
||||
import { ImagePicker, Permissions } from 'expo';
|
||||
import { contactUpdate, profileUpdate } from '../actions';
|
||||
import { PROFILE_FORM, CONTACT_FORM, SOCIAL_FORM } from './FormTypes';
|
||||
import { Spacer } from './common';
|
||||
import privateKeys from '../../private_keys.json';
|
||||
import colors from '../config/colors.json';
|
||||
import { strings } from '../locales/i18n';
|
||||
|
||||
const width = Dimensions.get('window').width * 0.9;
|
||||
const height = width * 0.57; // ( 2 / 3.5 = 0.57) Business card dimensions
|
||||
const regexObj = {};
|
||||
const visionObj = {};
|
||||
// Since we have duplicate keys between contact and profile we can't use this.props
|
||||
// so we track what has been populated here
|
||||
const propsAlreadySet = {};
|
||||
|
||||
class BizCard extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
cameraRollStatus: '',
|
||||
text: null,
|
||||
company: null,
|
||||
jobTitle: null,
|
||||
city: null,
|
||||
region: null
|
||||
};
|
||||
this.setCameraRollStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Component Will Mount
|
||||
**/
|
||||
componentWillMount() {
|
||||
if (this.props.isScanBizCardScreen) {
|
||||
this.onTakePhotoButtonPress();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends user to the gallery to upload image from device
|
||||
**/
|
||||
onPickImageButtonPress = async () => {
|
||||
if (this.state.cameraRollStatus !== 'granted') {
|
||||
await Permissions.askAsync(Permissions.CAMERA_ROLL)
|
||||
.then((data) => {
|
||||
if (data.status !== 'granted') {
|
||||
Alert.alert(strings('common.error.permissionDenied_cameraRoll'));
|
||||
Linking.openURL('app-settings:');
|
||||
return;
|
||||
}
|
||||
}).catch(err => console.error('askAsync camera roll error ', err));
|
||||
this.setCameraRollStatus();
|
||||
}
|
||||
|
||||
const result = await ImagePicker.launchImageLibraryAsync({
|
||||
allowsEditing: true,
|
||||
aspect: [3.5, 2],
|
||||
base64: true,
|
||||
mediaTypes: ImagePicker.MediaTypeOptions.Images
|
||||
});
|
||||
|
||||
if (!result.cancelled) {
|
||||
this.processImage(result);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens the camera for the user to take a new photo
|
||||
**/
|
||||
onTakePhotoButtonPress = async () => {
|
||||
const result = await ImagePicker.launchCameraAsync({
|
||||
allowsEditing: true,
|
||||
aspect: [4, 3],
|
||||
base64: true
|
||||
});
|
||||
|
||||
if (!result.cancelled) {
|
||||
this.processImage(result);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the current permission state of the app for camera roll/photos
|
||||
**/
|
||||
setCameraRollStatus = async () => {
|
||||
const { status } = await Permissions.getAsync(Permissions.CAMERA_ROLL);
|
||||
this.setState({ cameraRollStatus: status });
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the chosen image in base64 and saves the base64 and submits it for text recognition
|
||||
**/
|
||||
processImage = async (response) => {
|
||||
this.setState({ text: strings('bizCard.loading') });
|
||||
propsAlreadySet.base64 = true;
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'contactBizCardImg', value: response.uri });
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'profileBizCardImg', value: response.uri });
|
||||
}
|
||||
|
||||
const body = {
|
||||
requests: [
|
||||
{
|
||||
image: {
|
||||
content: response.base64,
|
||||
},
|
||||
features: [
|
||||
{
|
||||
type: 'DOCUMENT_TEXT_DETECTION',
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const visionResponse = await fetch(`https://vision.googleapis.com/v1/images:annotate?key=${privateKeys.googleapis.GOOGLE_API_KEY}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
const visionParsed = await visionResponse.json();
|
||||
// Checks that some text was found before trying to read it
|
||||
if (visionParsed.responses[0].fullTextAnnotation && visionParsed.responses[0].fullTextAnnotation.text) {
|
||||
this.setState({
|
||||
text: visionParsed.responses[0].fullTextAnnotation.text,
|
||||
});
|
||||
console.log('STATE.TEXT\n', this.state.text);
|
||||
this.extractImageContext(this.state.text);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts information from the OCR text and separates it based on context
|
||||
**/
|
||||
extractImageContext = async (imageText) => {
|
||||
const body = {
|
||||
encodingType: 'UTF8',
|
||||
document: {
|
||||
type: 'PLAIN_TEXT',
|
||||
content: imageText
|
||||
}
|
||||
};
|
||||
|
||||
const languageResponse = await fetch(`https://language.googleapis.com/v1/documents:analyzeEntities?key=${privateKeys.googleapis.GOOGLE_API_KEY}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
const languageParsed = await languageResponse.json();
|
||||
// console.log('languageParsed,entities is\n', languageParsed.entities);
|
||||
this.populatePhoneField(imageText);
|
||||
this.extractFullAddress(imageText);
|
||||
|
||||
// Try fullAddress first
|
||||
if (regexObj.fullAddress) {
|
||||
this.populateFieldsFromFullAddress();
|
||||
}
|
||||
|
||||
this.performVisionParsing(languageParsed.entities);
|
||||
|
||||
if (!propsAlreadySet.street || !propsAlreadySet.city || !propsAlreadySet.region || !propsAlreadySet.postalCode) {
|
||||
this.extractSecondLine(imageText);
|
||||
this.populateAddressByLine(imageText);
|
||||
}
|
||||
|
||||
if (!propsAlreadySet.email) {
|
||||
this.extractEmail(imageText);
|
||||
}
|
||||
|
||||
if (!propsAlreadySet.website) {
|
||||
this.populateWebsiteField(imageText);
|
||||
}
|
||||
|
||||
if (!propsAlreadySet.company) {
|
||||
this.populateCompanyField();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates fields based on the common data assignment based on the data below
|
||||
* 0 - PROPER 1 - PERSON === NAME
|
||||
* 0 - PROPER 1 - ORGANIZATION === ADDRESS OR COMPANY
|
||||
* 0 - PROPER 1 - LOCATION === ADDRESS
|
||||
* 0 - PROPER 1 - OTHER === EMAIL OR WEBSITE
|
||||
* 0 - PROPER 1 - WORKOFART === ADDRESS
|
||||
* 0 - COMMON 1 - PERSON === TITLE
|
||||
**/
|
||||
performVisionParsing = (languageEntities) => {
|
||||
languageEntities.forEach((entity) => {
|
||||
for (let i = 0; i < entity.mentions.length; i++) {
|
||||
switch (entity.mentions[i].type) {
|
||||
case 'PROPER':
|
||||
// PROPER - PERSON is the name
|
||||
// We don't get the name from regex so just set it here
|
||||
if (entity.type === 'PERSON' && !propsAlreadySet.firstName) {
|
||||
this.populateNameField(entity.name);
|
||||
// PROPER - LOCATION is the address
|
||||
} else if (entity.type === 'LOCATION') {
|
||||
this.extractAddressFromVisionFields(entity.name);
|
||||
// PROPER - ORGANIZATION is the address or company
|
||||
} else if (entity.type === 'ORGANIZATION') {
|
||||
if (this.isAddressField(entity.name)) {
|
||||
this.extractAddressFromVisionFields(entity.name);
|
||||
} else {
|
||||
this.extractAddressOrCompany(entity.name);
|
||||
}
|
||||
// PROPER - WORKOFART is the address
|
||||
} else if (entity.type === 'WORKOFART') {
|
||||
this.extractAddressFromVisionFields(entity.name);
|
||||
// PROPER - OTHER is the email or website
|
||||
} else if (entity.type === 'OTHER') {
|
||||
this.extractEmailOrWebsite(entity.name);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'COMMON':
|
||||
// COMMON - PERSON is the jobTitle
|
||||
// We don't get the jobTitle from regex so just set it here
|
||||
if (entity.type === 'PERSON' && !propsAlreadySet.jobTitle) {
|
||||
propsAlreadySet.jobTitle = true;
|
||||
this.setState({ jobTitle: entity.name });
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.combineTitleAndCompany();
|
||||
this.combineLocation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses regex to find phone number from the imageText.
|
||||
* Since phone isn't typically extracted from the
|
||||
* vision process we set the phone now
|
||||
**/
|
||||
populatePhoneField(imageText) {
|
||||
try {
|
||||
const phoneRegex = /\(?\d{3}\)?-?\.?\s?\d{3}-?.?\s?\d{4}/;
|
||||
let extractedPhone = '';
|
||||
|
||||
if (Array.isArray(imageText)) {
|
||||
extractedPhone = imageText[0].match(phoneRegex);
|
||||
} else {
|
||||
extractedPhone = imageText.match(phoneRegex);
|
||||
}
|
||||
|
||||
if (extractedPhone) {
|
||||
propsAlreadySet.primaryPhone = true;
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'primaryPhone', value: extractedPhone[0] });
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'primaryPhone', value: extractedPhone[0] });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Could not populate phone from '${imageText}': ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the firstName and lastName fields
|
||||
**/
|
||||
populateNameField(proposedName) {
|
||||
const nameArray = proposedName.split(' ');
|
||||
let lastNameStr = '';
|
||||
|
||||
switch (true) {
|
||||
case (nameArray.length > 3):
|
||||
return;
|
||||
case (nameArray.length === 3):
|
||||
lastNameStr = `${nameArray[1]} ${nameArray[2]}`;
|
||||
break;
|
||||
case (nameArray.length === 2):
|
||||
lastNameStr = nameArray[1];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
propsAlreadySet.firstName = true;
|
||||
let fullName = '';
|
||||
if (lastNameStr) {
|
||||
fullName = `${nameArray[0]} ${lastNameStr}`;
|
||||
} else {
|
||||
fullName = nameArray[0];
|
||||
}
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'firstName', value: nameArray[0] });
|
||||
this.props.contactUpdate({ prop: 'fullName', value: fullName });
|
||||
if (lastNameStr) {
|
||||
this.props.contactUpdate({ prop: 'lastName', value: lastNameStr });
|
||||
}
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'firstName', value: nameArray[0] });
|
||||
this.props.profileUpdate({ prop: 'fullName', value: fullName });
|
||||
if (lastNameStr) {
|
||||
this.props.profileUpdate({ prop: 'lastName', value: lastNameStr });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs regex and populates fields if found
|
||||
**/
|
||||
extractEmail(textToParse) {
|
||||
try {
|
||||
const emailRegex = /[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+/i;
|
||||
let extractedEmail = '';
|
||||
|
||||
if (Array.isArray(textToParse)) {
|
||||
extractedEmail = textToParse[0].match(emailRegex);
|
||||
} else {
|
||||
extractedEmail = textToParse.match(emailRegex);
|
||||
}
|
||||
|
||||
if (extractedEmail) {
|
||||
propsAlreadySet.email = true;
|
||||
regexObj.email = extractedEmail;
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'contactEmail', value: extractedEmail[0] });
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'profileEmail', value: extractedEmail[0] });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Could not extract email from '${textToParse}': ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vision extraction has already been attempted
|
||||
* Extracts the website from image text first and if unsuccessful
|
||||
* then just get it from the email domain
|
||||
**/
|
||||
populateWebsiteField(imageText) {
|
||||
try {
|
||||
if (!propsAlreadySet.website) {
|
||||
this.extractWebsite(imageText);
|
||||
}
|
||||
if (!propsAlreadySet.website && propsAlreadySet.email) {
|
||||
this.extractWebsite(regexObj.email);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Could not populate website', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs regex and populates fields if found
|
||||
**/
|
||||
extractWebsite(textToParse) {
|
||||
try {
|
||||
const websiteRegex = /w?w?w?\.?[a-z-]{2,}\.[a-z]{2,}\.?[a-z]*/i;
|
||||
let extractedWebsite = '';
|
||||
|
||||
if (Array.isArray(textToParse)) {
|
||||
extractedWebsite = textToParse[0].match(websiteRegex);
|
||||
} else {
|
||||
extractedWebsite = textToParse.match(websiteRegex);
|
||||
}
|
||||
|
||||
if (extractedWebsite) {
|
||||
propsAlreadySet.website = true;
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'website', value: extractedWebsite[0] });
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'website', value: extractedWebsite[0] });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Could not extract website from '${textToParse}': ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the company from vision first and from image text second
|
||||
* If both of those fail then just get it from the email domain
|
||||
**/
|
||||
populateCompanyField() {
|
||||
try {
|
||||
if (propsAlreadySet.company) {
|
||||
return;
|
||||
}
|
||||
if (visionObj.company) {
|
||||
this.extractCompanyFromVision();
|
||||
} else if (propsAlreadySet.email) {
|
||||
this.extractCompanyFromEmail();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Could not populate company', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the company from the vision response
|
||||
**/
|
||||
extractCompanyFromVision() {
|
||||
propsAlreadySet.company = true;
|
||||
this.setState({ company: visionObj.company });
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the company from the email
|
||||
**/
|
||||
extractCompanyFromEmail() {
|
||||
const { email } = regexObj;
|
||||
// Sets the company to be anything between the '@' and '.'
|
||||
let atIndex;
|
||||
let dotIndex;
|
||||
let foundAt = false;
|
||||
for (let i = 0; i < email.length; i++) {
|
||||
if (email[i] === '@') {
|
||||
atIndex = i + 1;
|
||||
foundAt = true;
|
||||
} else if (foundAt && email[i] === '.') {
|
||||
dotIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
const company = email.slice(atIndex, dotIndex);
|
||||
propsAlreadySet.company = true;
|
||||
this.setState({ company });
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines the jobTitle and Company fields into one field for the details section
|
||||
**/
|
||||
combineTitleAndCompany() {
|
||||
const company = this.state.company || '';
|
||||
const jobTitle = this.state.jobTitle || '';
|
||||
if (!company && !jobTitle) {
|
||||
return;
|
||||
}
|
||||
|
||||
const titleAndCompany = `${jobTitle} ${company}`;
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'titleAndCompany', value: titleAndCompany });
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'titleAndCompany', value: titleAndCompany });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines the region and state fields into one field for the details section
|
||||
**/
|
||||
combineLocation() {
|
||||
const city = this.state.city || '';
|
||||
const region = this.state.region || '';
|
||||
if (!city && !region) {
|
||||
return;
|
||||
}
|
||||
const location = region ? `${city}, ${region}` : city;
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'location', value: location });
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'location', value: location });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses regex to find the full address for comparison to vision response
|
||||
**/
|
||||
extractFullAddress(imageText) {
|
||||
try {
|
||||
const fullAddressRegex = /\d+ [a-zA-Z,.# 0-9]+[\s |][a-zA-Z ]+, [A-Z]{2}\s\d{5}/;
|
||||
const extractedFullAddress = imageText.match(fullAddressRegex);
|
||||
if (extractedFullAddress) {
|
||||
regexObj.fullAddress = extractedFullAddress;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Could not extract full address from '${imageText}': ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses regex to find the first line of address for comparison to vision response
|
||||
**/
|
||||
extractFirstLine(textToParse) {
|
||||
try {
|
||||
regexObj.firstLine = '';
|
||||
const firstLineRegex = /\d+ [a-zA-Z.,# 0-9]+\s/;
|
||||
let extractedFirstLine = '';
|
||||
|
||||
if (Array.isArray(textToParse)) {
|
||||
extractedFirstLine = textToParse[0].match(firstLineRegex);
|
||||
} else {
|
||||
extractedFirstLine = textToParse.match(firstLineRegex);
|
||||
}
|
||||
|
||||
if (extractedFirstLine && !propsAlreadySet.street) {
|
||||
propsAlreadySet.street = true;
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'street', value: extractedFirstLine[0] });
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'street', value: extractedFirstLine[0] });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Could not extract first line of address from'${textToParse}': ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses regex to find the second line of address for comparison to vision response
|
||||
**/
|
||||
extractSecondLine(textToParse) {
|
||||
try {
|
||||
regexObj.secondLine = '';
|
||||
const secondLineRegex = /[a-zA-Z ]+, [A-Z]{2}\s\d{5}/;
|
||||
let extractedSecondLine = '';
|
||||
|
||||
if (Array.isArray(textToParse)) {
|
||||
extractedSecondLine = textToParse[0].match(secondLineRegex);
|
||||
} else {
|
||||
extractedSecondLine = textToParse.match(secondLineRegex);
|
||||
}
|
||||
|
||||
if (extractedSecondLine) {
|
||||
regexObj.secondLine = extractedSecondLine[0];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.log(`Could not extract second line of address from '${textToParse}': ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses regex to find the city
|
||||
**/
|
||||
extractCity(textToParse) {
|
||||
try {
|
||||
regexObj.city = '';
|
||||
const cityRegex = /[a-zA-Z ]+,/;
|
||||
let extractedCity = '';
|
||||
|
||||
if (Array.isArray(textToParse)) {
|
||||
extractedCity = textToParse[0].match(cityRegex);
|
||||
} else {
|
||||
extractedCity = textToParse.match(cityRegex);
|
||||
}
|
||||
|
||||
if (extractedCity) {
|
||||
extractedCity = extractedCity[0].replace(/,/, '');
|
||||
propsAlreadySet.city = true;
|
||||
this.setState({ city: extractedCity });
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'city', value: extractedCity });
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'city', value: extractedCity });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Could not extract city from '${textToParse}': ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses regex to find the region (state)
|
||||
**/
|
||||
extractRegion(textToParse) {
|
||||
try {
|
||||
regexObj.region = '';
|
||||
const regionRegex = /, [A-Z]{2}/;
|
||||
let extractedRegion = '';
|
||||
|
||||
if (Array.isArray(textToParse)) {
|
||||
extractedRegion = textToParse[0].match(regionRegex);
|
||||
} else {
|
||||
extractedRegion = textToParse.match(regionRegex);
|
||||
}
|
||||
|
||||
if (extractedRegion) {
|
||||
extractedRegion = extractedRegion[0].replace(/, /, '');
|
||||
propsAlreadySet.region = true;
|
||||
this.setState({ region: extractedRegion });
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'region', value: extractedRegion });
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'region', value: extractedRegion });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Could not extract region from ${textToParse}' ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses regex to find the postalCode
|
||||
**/
|
||||
extractPostalCode(textToParse) {
|
||||
try {
|
||||
regexObj.postalCode = '';
|
||||
const zipRegex = /\d{5}/;
|
||||
let extractedPostalCode = '';
|
||||
|
||||
if (Array.isArray(textToParse)) {
|
||||
extractedPostalCode = textToParse[0].match(zipRegex);
|
||||
} else {
|
||||
extractedPostalCode = textToParse.match(zipRegex);
|
||||
}
|
||||
|
||||
if (extractedPostalCode) {
|
||||
propsAlreadySet.postalCode = true;
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'postalCode', value: extractedPostalCode[0] });
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'postalCode', value: extractedPostalCode[0] });
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Could not extract postalCode from '${textToParse}': ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a field is an address field of some form
|
||||
**/
|
||||
isAddressField(textToParse) {
|
||||
const fullAddressRegex = /\d+ [a-zA-Z,.# 0-9]+[\s |][a-zA-Z ]+, [A-Z]{2}\s\d{5}/;
|
||||
const foundFullAddress = textToParse.match(fullAddressRegex);
|
||||
|
||||
const firstLineRegex = /\d+ [a-zA-Z.,# 0-9]+\s/;
|
||||
const foundFirstLine = textToParse.toString().match(firstLineRegex);
|
||||
|
||||
const secondLineRegex = /[a-zA-Z ]+, [A-Z]{2}\s\d{5}/;
|
||||
const foundSecondLine = textToParse.match(secondLineRegex);
|
||||
|
||||
const cityRegex = /[a-zA-Z ]+,/;
|
||||
const foundCity = textToParse.match(cityRegex);
|
||||
|
||||
const regionRegex = /, [A-Z]{2}/;
|
||||
const foundRegion = textToParse.match(regionRegex);
|
||||
|
||||
const zipRegex = /\d{5}/;
|
||||
const foundPostalCode = textToParse.match(zipRegex);
|
||||
|
||||
return foundFullAddress || foundFirstLine || foundSecondLine || foundCity || foundRegion || foundPostalCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Since the regex for full address is so strict if we found something that
|
||||
* likely means we have the full address so we'll just use that and not
|
||||
* look at the vision response since address isn't that reliable
|
||||
**/
|
||||
populateFieldsFromFullAddress() {
|
||||
// Populate the street
|
||||
this.extractFirstLine(regexObj.fullAddress[0]);
|
||||
|
||||
// Get the second line then split into city, region, and zip
|
||||
this.extractSecondLine(regexObj.fullAddress[0]);
|
||||
if (regexObj.secondLine) {
|
||||
// Populate the city, region, and postalCode fields
|
||||
this.extractCity(regexObj.secondLine);
|
||||
this.extractRegion(regexObj.secondLine);
|
||||
this.extractPostalCode(regexObj.secondLine);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the vision response to find remaining address fields
|
||||
**/
|
||||
extractAddressFromVisionFields(textToParse) {
|
||||
// Check for Street if not found
|
||||
if (!propsAlreadySet.street) {
|
||||
this.extractFirstLine(textToParse);
|
||||
}
|
||||
|
||||
// Check for city if not found
|
||||
if (!propsAlreadySet.city) {
|
||||
this.extractCity(textToParse);
|
||||
}
|
||||
|
||||
// Check for region if not found
|
||||
if (!propsAlreadySet.region) {
|
||||
this.extractRegion(textToParse);
|
||||
}
|
||||
|
||||
// Check for postalCode if not found
|
||||
if (!propsAlreadySet.postalCode) {
|
||||
this.extractPostalCode(textToParse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PROPER - ORGANIZATION is address or company
|
||||
* This tries to determine which one it is
|
||||
**/
|
||||
extractAddressOrCompany(textToParse) {
|
||||
const firstLineRegex = /\d+ [a-zA-Z.,# 0-9]+\s/;
|
||||
const extractedFirstLine = textToParse.toString().match(firstLineRegex);
|
||||
|
||||
const secondLineRegex = /[a-zA-Z ]+, [A-Z]{2}\s\d{5}/;
|
||||
const extractedSecondLine = textToParse.match(secondLineRegex);
|
||||
|
||||
// If we didn't find either address line then assume it's company
|
||||
if (!extractedFirstLine && !extractedSecondLine && !propsAlreadySet.company) {
|
||||
visionObj.company = textToParse;
|
||||
this.populateCompanyField();
|
||||
}
|
||||
// If we found either line then extract the info from it
|
||||
if (extractedFirstLine || extractedSecondLine) {
|
||||
this.populateAddressByLine(textToParse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PROPER - OTHER is email or website
|
||||
* This tries to determine which one it is
|
||||
**/
|
||||
extractEmailOrWebsite(textToParse) {
|
||||
if (textToParse.indexOf('@') > 1) {
|
||||
if (!propsAlreadySet.email) {
|
||||
this.extractEmail(textToParse);
|
||||
}
|
||||
} else if (!propsAlreadySet.website) {
|
||||
this.extractWebsite(textToParse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks at the remaining fields and try to populate them using regex
|
||||
**/
|
||||
populateAddressByLine(textToParse) {
|
||||
const { secondLine } = regexObj;
|
||||
if (!propsAlreadySet.street) {
|
||||
this.extractFirstLine(textToParse);
|
||||
}
|
||||
if (secondLine) {
|
||||
if (!propsAlreadySet.city) {
|
||||
this.extractCity(secondLine);
|
||||
}
|
||||
if (!propsAlreadySet.region) {
|
||||
this.extractRegion(secondLine);
|
||||
}
|
||||
if (!propsAlreadySet.postalCode) {
|
||||
this.extractPostalCode(secondLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the business card tile
|
||||
**/
|
||||
renderBusinessCardTile() {
|
||||
let base64 = '';
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
base64 = this.props.contactBizCardImg;
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
base64 = this.props.profileBizCardImg;
|
||||
} else if (this.props.formType === SOCIAL_FORM) {
|
||||
base64 = this.props.socialBizCardImg;
|
||||
}
|
||||
if (base64) {
|
||||
return (
|
||||
<Tile
|
||||
imageSrc={{ uri: base64 }}
|
||||
featured
|
||||
containerStyle={styles.tileContainerStyle}
|
||||
imageContainerStyle={styles.tileStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Tile
|
||||
featured
|
||||
title={strings('bizCard.photoTile_noCard')}
|
||||
containerStyle={styles.tileContainerStyle}
|
||||
imageContainerStyle={styles.tileStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the photo buttons
|
||||
**/
|
||||
renderPhotoButtons() {
|
||||
if (this.props.formType === SOCIAL_FORM || !this.props.isEditMode) {
|
||||
return <View style={styles.buttonContainerStyle} />;
|
||||
}
|
||||
return (
|
||||
<View style={styles.buttonContainerStyle}>
|
||||
<Button
|
||||
onPress={this.onPickImageButtonPress.bind(this)}
|
||||
title={strings('common.buttons.uploadImage')}
|
||||
buttonStyle={styles.halfButtonStyle}
|
||||
disabled={this.props.loading}
|
||||
icon={{ name: 'folder' }}
|
||||
/>
|
||||
|
||||
<Button
|
||||
onPress={this.onTakePhotoButtonPress.bind(this)}
|
||||
title={strings('common.buttons.takePhoto')}
|
||||
buttonStyle={styles.halfButtonStyle}
|
||||
disabled={this.props.loading}
|
||||
icon={{ name: 'photo-camera' }}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
if (this.props.isEditMode) {
|
||||
return (
|
||||
<View style={styles.viewStyle}>
|
||||
{this.renderBusinessCardTile()}
|
||||
{this.renderPhotoButtons()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.viewStyle}>
|
||||
<Spacer space={45} />
|
||||
{this.renderBusinessCardTile()}
|
||||
{this.renderPhotoButtons()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
tileContainerStyle: {
|
||||
height,
|
||||
width,
|
||||
marginTop: 15,
|
||||
marginBottom: 15
|
||||
},
|
||||
tileStyle: {
|
||||
height,
|
||||
width
|
||||
},
|
||||
buttonContainerStyle: {
|
||||
flex: 2,
|
||||
marginTop: 15,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
halfButtonStyle: {
|
||||
backgroundColor: colors.buttonBlue,
|
||||
flex: 1,
|
||||
borderRadius: 5
|
||||
},
|
||||
viewStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Map State To Props
|
||||
**/
|
||||
const mapStateToProps = (state) => {
|
||||
const {
|
||||
profileBizCardImg,
|
||||
} = state.profileForm;
|
||||
|
||||
const {
|
||||
contactBizCardImg,
|
||||
} = state.contactForm;
|
||||
|
||||
const {
|
||||
socialBizCardImg,
|
||||
} = state.socialContacts;
|
||||
|
||||
return { profileBizCardImg, contactBizCardImg, socialBizCardImg };
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
contactUpdate, profileUpdate
|
||||
})(BizCard);
|
||||
23
src/components/EmptyNavigator.js
Normal file
@ -0,0 +1,23 @@
|
||||
/** Used as a placeholder with an empty title for the drawerNavigator reset action from the welcomScreen **/
|
||||
import React, { Component } from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import { strings } from '../locales/i18n';
|
||||
|
||||
class EmptyNavigator extends Component {
|
||||
static navigationOptions = {
|
||||
title: '',
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
return (
|
||||
<Text>
|
||||
{strings('common.error.navigatedToEmpty')}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EmptyNavigator;
|
||||
5
src/components/EventDateFilterTypes.js
Normal file
@ -0,0 +1,5 @@
|
||||
/** Contains all string key/value pairs to prevent typos **/
|
||||
export const NEAR_YOU = 'near_you';
|
||||
export const THIS_WEEK = 'this_week';
|
||||
export const TODAY = 'today';
|
||||
export const TOMORROW = 'tomorrow';
|
||||
4
src/components/EventFilterTypes.js
Normal file
@ -0,0 +1,4 @@
|
||||
/** Contains all string key/value pairs to prevent typos **/
|
||||
export const THIS_WEEK = 'this_week';
|
||||
export const TODAY = 'today';
|
||||
export const TOMORROW = 'tomorrow';
|
||||
6
src/components/FormTypes.js
Normal file
@ -0,0 +1,6 @@
|
||||
/** Contains all string key/value pairs to prevent typos **/
|
||||
export const PROFILE_FORM = 'profile';
|
||||
export const CONTACT_FORM = 'contact';
|
||||
export const EVENT_FORM = 'event';
|
||||
export const SOCIAL_FORM = 'social';
|
||||
export const TASK_FORM = 'task';
|
||||
5
src/components/NotificationListTypes.js
Normal file
@ -0,0 +1,5 @@
|
||||
/** Contains all string key/value pairs to prevent typos **/
|
||||
export const REFERRAL_RECEIVED = 'referralReceived';
|
||||
export const REQUEST_ACCEPTED = 'requestAccepted';
|
||||
export const REQUEST_RECEIVED = 'requestReceived';
|
||||
export const REQUEST_SENT = 'requestSent';
|
||||
866
src/components/ProfileAvatar.js
Normal file
@ -0,0 +1,866 @@
|
||||
/** Contains the base fields for a contact reuse **/
|
||||
import _ from 'lodash';
|
||||
import { ImagePicker, MailComposer, Permissions } from 'expo';
|
||||
import React, { Component } from 'react';
|
||||
import { Alert, Dimensions, Image, Linking, Modal, ScrollView, Share, Text, TouchableOpacity, TouchableWithoutFeedback, View } from 'react-native';
|
||||
import { Button, Icon } from 'react-native-elements';
|
||||
import ModalDropdown from 'react-native-modal-dropdown';
|
||||
import { connect } from 'react-redux';
|
||||
import { contactUpdate, profileToggleDisabled, profileSave, profileUpdate, socialRemoveFriend } from '../actions';
|
||||
import { PROFILE_FORM, CONTACT_FORM, SOCIAL_FORM } from './FormTypes';
|
||||
import { Confirm, Spacer } from './common';
|
||||
import colors from '../config/colors.json';
|
||||
import privateKeys from '../../private_keys.json';
|
||||
import { strings } from '../locales/i18n';
|
||||
|
||||
const GREEN_CHECK_IMAGE = require('../../assets/GreenCheck.png');
|
||||
|
||||
const SCREEN_WIDTH = Dimensions.get('window').width;
|
||||
const DEV_LINKING_BASE_URL = privateKeys.deepLinking.devBaseUrl;
|
||||
const TAGFER_BASE_URL = privateKeys.deepLinking.tagferBaseUrl;
|
||||
const profileKebabOptions = [
|
||||
strings('profile.kebabOptions_shareQR'),
|
||||
strings('profile.kebabOptions_shareLink'),
|
||||
strings('profile.kebabOptions_edit'),
|
||||
strings('profile.kebabOptions_duplicate'),
|
||||
strings('profile.kebabOptions_disable')
|
||||
];
|
||||
const friendKebabOptions = [
|
||||
strings('profile.kebabOptions_shareQR'),
|
||||
strings('profile.kebabOptions_shareLink'),
|
||||
// strings('profile.kebabOptions_unfollow'),
|
||||
strings('profile.kebabOptions_merge'),
|
||||
strings('profile.kebabOptions_remove'),
|
||||
strings('profile.kebabOptions_report')
|
||||
];
|
||||
let profileNames = [];
|
||||
let copyFromIndex = 0;
|
||||
let copyToIndex = 0;
|
||||
|
||||
class ProfileAvatar extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
cameraRollStatus: '',
|
||||
isDuplicateModalVisible: false,
|
||||
isDisableModaleVisible: false,
|
||||
isRemoveModalVisible: false
|
||||
};
|
||||
this.setCameraRollStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Component Will Mount
|
||||
**/
|
||||
componentWillMount() {
|
||||
this.populateKebabLastOption();
|
||||
profileNames = [];
|
||||
for (let i = 0; i < this.props.profiles.length; i++) {
|
||||
profileNames.push(this.props.profiles[i].profileName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the profile data from the copyFromIndex to the copyToIndex profile
|
||||
**/
|
||||
onDuplicateButtonPress() {
|
||||
if (copyFromIndex === copyToIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
profileBizCardImg,
|
||||
profileAvatarImg,
|
||||
firstName,
|
||||
lastName,
|
||||
fullName,
|
||||
headline,
|
||||
location,
|
||||
primaryPhone,
|
||||
secondaryPhone,
|
||||
extension,
|
||||
titleAndCompany,
|
||||
about,
|
||||
howIHelp,
|
||||
whatINeed,
|
||||
experience,
|
||||
education,
|
||||
workPhone,
|
||||
fax,
|
||||
profileEmail,
|
||||
website,
|
||||
street,
|
||||
address2,
|
||||
city,
|
||||
region,
|
||||
postalCode,
|
||||
country,
|
||||
keywords,
|
||||
referralId,
|
||||
isDisabled,
|
||||
} = this.props.profiles[copyFromIndex];
|
||||
|
||||
const { profileName, uid } = this.props.profiles[copyToIndex];
|
||||
|
||||
this.props.profileSave({
|
||||
profileBizCardImg,
|
||||
profileAvatarImg,
|
||||
firstName,
|
||||
lastName,
|
||||
fullName,
|
||||
headline,
|
||||
location,
|
||||
primaryPhone,
|
||||
secondaryPhone,
|
||||
extension,
|
||||
titleAndCompany,
|
||||
about,
|
||||
howIHelp,
|
||||
whatINeed,
|
||||
experience,
|
||||
education,
|
||||
workPhone,
|
||||
fax,
|
||||
profileEmail,
|
||||
website,
|
||||
street,
|
||||
address2,
|
||||
city,
|
||||
region,
|
||||
postalCode,
|
||||
country,
|
||||
keywords,
|
||||
referralId,
|
||||
profileName,
|
||||
isDisabled,
|
||||
uid // DON'T DELETE THIS PROPERTY WHEN UPDATING LIST
|
||||
});
|
||||
this.setState({ isDuplicateModalVisible: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends user to the gallery to upload image from device
|
||||
**/
|
||||
onPickImageButtonPress = async () => {
|
||||
if (this.state.cameraRollStatus !== 'granted') {
|
||||
await Permissions.askAsync(Permissions.CAMERA_ROLL)
|
||||
.then((data) => {
|
||||
if (data.status !== 'granted') {
|
||||
Alert.alert(strings('common.error.permissionDenied_cameraRoll'));
|
||||
Linking.openURL('app-settings:');
|
||||
return;
|
||||
}
|
||||
}).catch(err => console.error('askAsync camera roll error ', err));
|
||||
this.setCameraRollStatus();
|
||||
}
|
||||
|
||||
const result = await ImagePicker.launchImageLibraryAsync({
|
||||
allowsEditing: true,
|
||||
aspect: [3.5, 2],
|
||||
base64: true,
|
||||
mediaTypes: ImagePicker.MediaTypeOptions.Images
|
||||
});
|
||||
|
||||
if (!result.cancelled) {
|
||||
this.processImage(result.uri);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens the camera for the user to take a new photo
|
||||
**/
|
||||
onTakePhotoButtonPress = async () => {
|
||||
const result = await ImagePicker.launchCameraAsync({
|
||||
allowsEditing: true,
|
||||
aspect: [4, 3],
|
||||
base64: true
|
||||
});
|
||||
|
||||
if (!result.cancelled) {
|
||||
this.processImage(result.uri);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Disables the profile
|
||||
**/
|
||||
onDisableAccept() {
|
||||
this.toggleProfileEnabled();
|
||||
this.setState({ isDisableModaleVisible: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the modal
|
||||
**/
|
||||
onDisableDecline() {
|
||||
this.setState({ isDisableModaleVisible: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the friend upon button press
|
||||
**/
|
||||
onRemoveAccept() {
|
||||
const { myFriendId, userId, otherFriendId } = this.props.navigation.state.params;
|
||||
this.props.socialRemoveFriend({ myFriendId, userId, otherFriendId });
|
||||
this.setState({ isRemoveModalVisible: false });
|
||||
this.props.navigation.goBack();
|
||||
this.props.friendsFetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents friend removal and closes modal upon button press
|
||||
**/
|
||||
onRemoveDecline() {
|
||||
this.setState({ isRemoveModalVisible: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current permission state of the app for camera roll/photos
|
||||
**/
|
||||
setCameraRollStatus = async () => {
|
||||
const { status } = await Permissions.getAsync(Permissions.CAMERA_ROLL);
|
||||
this.setState({ cameraRollStatus: status });
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles whether or not the profile is enabled
|
||||
**/
|
||||
async toggleProfileEnabled() {
|
||||
this.props.profileToggleDisabled({ isDisabled: !this.props.isDisabled, uid: this.props.uid });
|
||||
await this.props.profileUpdate({ prop: 'isDisabled', value: !this.props.isDisabled });
|
||||
this.populateKebabLastOption();
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the chosen image in base64 and saves the base64
|
||||
**/
|
||||
processImage = async (uri) => {
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
this.props.contactUpdate({ prop: 'contactAvatarImg', value: uri });
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
this.props.profileUpdate({ prop: 'profileAvatarImg', value: uri });
|
||||
} else if (this.props.formType === SOCIAL_FORM) {
|
||||
this.props.profileUpdate({ prop: 'socialAvatarImg', value: uri });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates last option of the kebab menu based on disabled or not
|
||||
**/
|
||||
populateKebabLastOption() {
|
||||
profileKebabOptions.pop();
|
||||
profileKebabOptions.push(this.props.isDisabled ? strings('profile.kebabOptions_enable') : strings('profile.kebabOptions_disable'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an action based on the option selected from the kebab menu
|
||||
**/
|
||||
async processProfileKebabSelection(idx) {
|
||||
const { profileAvatarImg, fullName, titleAndCompany, tagferId, uid } = this.props;
|
||||
switch (idx) {
|
||||
case '0':
|
||||
this.props.profileUpdate({ prop: 'success', value: false });
|
||||
this.props.profileUpdate({ prop: 'loading', value: false });
|
||||
this.props.navigation.navigate('shareProfileQRCodeScreen', { profileAvatarImg, fullName, titleAndCompany, tagferId, uid });
|
||||
break;
|
||||
|
||||
case '1':
|
||||
this.props.profileUpdate({ prop: 'success', value: false });
|
||||
this.props.profileUpdate({ prop: 'loading', value: false });
|
||||
this.shareLink(tagferId, uid);
|
||||
break;
|
||||
|
||||
case '2':
|
||||
this.props.profileUpdate({ prop: 'success', value: false });
|
||||
this.props.profileUpdate({ prop: 'loading', value: false });
|
||||
this.props.navigation.navigate('profileEdit');
|
||||
break;
|
||||
|
||||
case '3':
|
||||
this.props.profileUpdate({ prop: 'success', value: false });
|
||||
this.props.profileUpdate({ prop: 'loading', value: false });
|
||||
this.setState({ isDuplicateModalVisible: true });
|
||||
break;
|
||||
|
||||
case '4':
|
||||
if (this.props.isDisabled) {
|
||||
this.toggleProfileEnabled();
|
||||
} else {
|
||||
this.setState({ isDisableModaleVisible: true });
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an action based on the option selected from the kebab menu
|
||||
**/
|
||||
async processSocialKebabSelection(idx) {
|
||||
const { profileAvatarImg, fullName, titleAndCompany, otherTagferId, otherUserId, otherProfileId, myFriendId } = this.props.navigation.state.params;
|
||||
switch (idx) {
|
||||
case '0':
|
||||
this.props.profileUpdate({ prop: 'success', value: false });
|
||||
this.props.profileUpdate({ prop: 'loading', value: false });
|
||||
this.props.navigation.navigate('shareProfileQRCodeScreen', { profileAvatarImg, fullName, titleAndCompany, otherTagferId, otherUserId });
|
||||
break;
|
||||
|
||||
case '1':
|
||||
this.props.profileUpdate({ prop: 'success', value: false });
|
||||
this.props.profileUpdate({ prop: 'loading', value: false });
|
||||
this.shareLink(otherTagferId, otherUserId);
|
||||
break;
|
||||
|
||||
case '2':
|
||||
this.props.navigation.navigate('socialMergeScreen', { fullName, titleAndCompany, otherTagferId, otherUserId, myFriendId });
|
||||
break;
|
||||
|
||||
case '3':
|
||||
this.setState({ isRemoveModalVisible: !this.state.isRemoveModalVisible });
|
||||
break;
|
||||
|
||||
case '4':
|
||||
MailComposer.composeAsync({
|
||||
recipients: ['abuse@tagfer.com'],
|
||||
subject: strings('common.forms.reportContact_emailHeader'),
|
||||
body: strings('common.forms.reportContact_emailMessage', { otherTagferId, otherProfileId })
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the normal hardware process for sharing something via social, sms, etc
|
||||
* uid - the uid for the profile to be shared
|
||||
**/
|
||||
shareLink(tagferId, uid) {
|
||||
Share.share(
|
||||
{
|
||||
message: strings('common.linking.shareProfile_body', { tagferId, DEV_LINKING_BASE_URL, uid }),
|
||||
title: strings('common.linking.shareProfile_title'),
|
||||
url: `${TAGFER_BASE_URL}${tagferId}/${uid}`
|
||||
},
|
||||
{
|
||||
tintColor: 0x056ecf,
|
||||
excludedActivityTypes: [
|
||||
'com.apple.UIKit.activity.PostToWeibo',
|
||||
'com.apple.UIKit.activity.Print',
|
||||
'com.apple.UIKit.activity.CopyToPasteboard',
|
||||
'com.apple.UIKit.activity.AssignToContact',
|
||||
'com.apple.UIKit.activity.SaveToCameraRoll',
|
||||
'com.apple.UIKit.activity.AddToReadingList',
|
||||
'com.apple.UIKit.activity.PostToFlickr',
|
||||
'com.apple.UIKit.activity.PostToVimeo',
|
||||
'com.apple.UIKit.activity.PostToTencentWeibo',
|
||||
'com.apple.UIKit.activity.AirDrop',
|
||||
'com.apple.UIKit.activity.OpenInIBooks',
|
||||
'com.apple.UIKit.activity.MarkupAsPDF',
|
||||
'com.apple.reminders.RemindersEditorExtension', //Reminders
|
||||
'com.apple.mobilenotes.SharingExtension', // Notes
|
||||
'com.apple.mobileslideshow.StreamShareService', // iCloud Photo Sharing - This also does nothing :{
|
||||
|
||||
// Not supported
|
||||
'com.linkedin.LinkedIn.ShareExtension', //LinkedIn
|
||||
'pinterest.ShareExtension', //Pinterest
|
||||
'com.google.GooglePlus.ShareExtension', //Google +
|
||||
'com.tumblr.tumblr.Share-With-Tumblr', //Tumblr
|
||||
'wefwef.YammerShare', //Yammer
|
||||
'com.hootsuite.hootsuite.HootsuiteShareExt', //HootSuite
|
||||
'net.naan.TwitterFonPro.ShareExtension-Pro', //Echofon
|
||||
'net.whatsapp.WhatsApp.ShareExtension' //WhatsApp
|
||||
]
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the avatar cirle on profiles
|
||||
**/
|
||||
renderAvatarPhoto() {
|
||||
let base64 = '';
|
||||
if (this.props.formType === CONTACT_FORM) {
|
||||
base64 = this.props.contactAvatarImg;
|
||||
} else if (this.props.formType === PROFILE_FORM) {
|
||||
base64 = this.props.profileAvatarImg;
|
||||
} else if (this.props.formType === SOCIAL_FORM) {
|
||||
base64 = this.props.socialAvatarImg;
|
||||
}
|
||||
if (base64) {
|
||||
if (this.props.isEditMode) {
|
||||
return (
|
||||
<View style={styles.avatarEditStyle}>
|
||||
<Image
|
||||
source={{ uri: base64 }}
|
||||
style={styles.avatarStyle}
|
||||
/>
|
||||
{this.renderPhotoButtons()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
// Not edit mode but has avatar image
|
||||
return (
|
||||
<View>
|
||||
<Image
|
||||
source={{ uri: base64 }}
|
||||
style={styles.avatarStyle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// No profile Image
|
||||
if (this.props.isEditMode) {
|
||||
return (
|
||||
<View style={styles.noAvatarEditStyle}>
|
||||
<Icon
|
||||
name='account-circle'
|
||||
size={150}
|
||||
color={colors.lightTeal}
|
||||
/>
|
||||
{this.renderPhotoButtons()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View>
|
||||
<Icon
|
||||
name='account-circle'
|
||||
size={150}
|
||||
color={colors.lightTeal}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the photo buttons
|
||||
**/
|
||||
renderPhotoButtons() {
|
||||
if (this.props.formType === SOCIAL_FORM) {
|
||||
return <View style={styles.buttonContainerStyle} />;
|
||||
}
|
||||
return (
|
||||
<View style={styles.buttonContainerStyle}>
|
||||
<TouchableOpacity
|
||||
onPress={this.onPickImageButtonPress.bind(this)}
|
||||
disabled={this.props.loading}
|
||||
>
|
||||
<Text style={styles.textButtonStyle}>{strings('common.buttons.uploadImage')}</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<Text style={styles.photoButtonsOrTextStyle}>{strings('profile.or')}</Text>
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={this.onTakePhotoButtonPress.bind(this)}
|
||||
disabled={this.props.loading}
|
||||
>
|
||||
<Text style={styles.textButtonStyle}>{strings('common.buttons.takePhoto')}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an icon on the right of the profile row if it is the selected profile
|
||||
**/
|
||||
renderHightlightedIcon(highlighted) {
|
||||
return (
|
||||
<Image
|
||||
style={styles.highlightedImageStyle}
|
||||
mode='stretch'
|
||||
source={highlighted ? GREEN_CHECK_IMAGE : null}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an icon to the left of the profile name in the profile row
|
||||
**/
|
||||
renderLeftIcon(rowID) {
|
||||
const base64 = this.props.profiles[rowID].profileAvatarImg;
|
||||
|
||||
if (base64) {
|
||||
return (
|
||||
<View style={styles.leftIconStyle}>
|
||||
<Image
|
||||
source={{ uri: base64 }}
|
||||
style={styles.avatarSmallImageStyle}
|
||||
/>
|
||||
<Text style={[styles.modalDropdownDropdownTextStyle, { color: this.props.profiles[rowID].isDisabled ? colors.lightGrey : colors.black }]}>
|
||||
{profileNames[rowID]}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.leftIconStyle}>
|
||||
<Icon
|
||||
name={'account-circle'}
|
||||
size={30}
|
||||
color={colors.middleGrey}
|
||||
/>
|
||||
<Text style={[styles.modalDropdownDropdownTextStyle, { color: this.props.profiles[rowID].isDisabled ? colors.lightGrey : colors.black }]}>
|
||||
{profileNames[rowID]}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for custom styling of the modal dropdown
|
||||
**/
|
||||
renderDuplicateModalDropdownRow(rowData, rowID, highlighted) {
|
||||
return (
|
||||
<View style={{ backgroundColor: this.props.profiles[rowID].isDisabled ? colors.offWhite : colors.white }}>
|
||||
<View style={styles.modalDropdownDropdownViewStyle}>
|
||||
{this.renderLeftIcon(rowID)}
|
||||
|
||||
{this.renderHightlightedIcon(highlighted)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the kebab menu
|
||||
**/
|
||||
renderKebab() {
|
||||
if (!this.props.isEditMode) {
|
||||
if (this.props.formType === PROFILE_FORM) {
|
||||
return (
|
||||
<View style={styles.kebabViewStyle}>
|
||||
<ModalDropdown
|
||||
style={styles.modalDropdownStyle}
|
||||
dropdownStyle={styles.profileModalDropsdownDropdownStyle}
|
||||
dropdownTextStyle={styles.modalDropsdownDropdownTextStyle}
|
||||
options={profileKebabOptions}
|
||||
onSelect={(idx) => this.processProfileKebabSelection(idx)}
|
||||
>
|
||||
<Icon
|
||||
name={'more-vert'}
|
||||
size={40}
|
||||
containerStyle={{ paddingLeft: 10, paddingTop: 10 }}
|
||||
/>
|
||||
</ModalDropdown>
|
||||
</View>
|
||||
);
|
||||
} else if (this.props.formType === SOCIAL_FORM) {
|
||||
return (
|
||||
<View style={styles.kebabViewStyle}>
|
||||
<ModalDropdown
|
||||
style={styles.modalDropdownStyle}
|
||||
dropdownStyle={styles.socialModalDropdownDropdownStyle}
|
||||
dropdownTextStyle={styles.modalDropsdownDropdownTextStyle}
|
||||
options={friendKebabOptions}
|
||||
onSelect={(idx) => this.processSocialKebabSelection(idx)}
|
||||
>
|
||||
<Icon
|
||||
name={'more-vert'}
|
||||
size={40}
|
||||
containerStyle={{ paddingLeft: 10, paddingTop: 10 }}
|
||||
/>
|
||||
</ModalDropdown>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
const name = `${this.props.fullName}`;
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View style={styles.avatarViewStyle}>
|
||||
{this.renderAvatarPhoto()}
|
||||
</View>
|
||||
{this.renderKebab()}
|
||||
|
||||
<Modal
|
||||
transparent
|
||||
animationType="slide"
|
||||
visible={this.state.isDuplicateModalVisible}
|
||||
onRequestClose={() => {}}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={styles.modalOpacityStyle}
|
||||
activeOpacity={1}
|
||||
onPressOut={() => this.setState({ isDuplicateModalVisible: false })}
|
||||
>
|
||||
<ScrollView
|
||||
directionalLockEnabled
|
||||
centerContent
|
||||
contentContainerStyle={styles.modalScrollViewStyle}
|
||||
>
|
||||
<TouchableWithoutFeedback>
|
||||
<View>
|
||||
<View style={styles.duplicateMainViewStyle}>
|
||||
<View />
|
||||
<View>
|
||||
<Spacer />
|
||||
<Text style={styles.leftAlignedTextStyle}>{strings('profile.duplicateScreen_duplicateBasicInfo')}</Text>
|
||||
<Spacer />
|
||||
<Text style={styles.leftAlignedTextStyle}>{strings('profile.duplicateScreen_duplicateFrom')}</Text>
|
||||
<Spacer />
|
||||
<ModalDropdown
|
||||
style={styles.duplicateModalDropdownStyle}
|
||||
dropdownStyle={styles.modalDropdownDropdownStyle}
|
||||
options={profileNames}
|
||||
renderRow={this.renderDuplicateModalDropdownRow.bind(this)}
|
||||
onSelect={(idx) => {
|
||||
copyFromIndex = idx;
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<Spacer />
|
||||
<Text style={styles.leftAlignedTextStyle}>{strings('profile.duplicateScreen_duplicateTo')}</Text>
|
||||
<Spacer />
|
||||
<ModalDropdown
|
||||
style={styles.duplicateModalDropdownStyle}
|
||||
dropdownStyle={styles.modalDropdownDropdownStyle}
|
||||
options={profileNames}
|
||||
renderRow={this.renderDuplicateModalDropdownRow.bind(this)}
|
||||
onSelect={(idx) => {
|
||||
copyToIndex = idx;
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Button
|
||||
onPress={() => this.onDuplicateButtonPress()}
|
||||
title={strings('common.buttons.duplicate')}
|
||||
buttonStyle={styles.buttonStyle}
|
||||
fontWeight="bold"
|
||||
fontSize={25}
|
||||
/>
|
||||
<Spacer />
|
||||
</View>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</ScrollView>
|
||||
</TouchableOpacity>
|
||||
</Modal>
|
||||
|
||||
<Confirm
|
||||
visible={this.state.isDisableModaleVisible}
|
||||
onAccept={this.onDisableAccept.bind(this)}
|
||||
onDecline={this.onDisableDecline.bind(this)}
|
||||
>
|
||||
{strings('profile.kebabOptions_disableInfo')}
|
||||
</Confirm>
|
||||
|
||||
<Confirm
|
||||
visible={this.state.isRemoveModalVisible}
|
||||
onAccept={this.onRemoveAccept.bind(this)}
|
||||
onDecline={this.onRemoveDecline.bind(this)}
|
||||
>
|
||||
{strings('common.modal.confirm_delete', { name })}
|
||||
</Confirm>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
buttonContainerStyle: {
|
||||
flex: 2,
|
||||
marginTop: 15,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
textButtonStyle: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: colors.labelBlue,
|
||||
marginTop: 5,
|
||||
marginBottom: 5,
|
||||
marginLeft: 15
|
||||
},
|
||||
photoButtonsOrTextStyle: {
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
color: colors.lightGrey,
|
||||
marginTop: 5,
|
||||
marginBottom: 5,
|
||||
marginLeft: 15
|
||||
},
|
||||
kebabViewStyle: {
|
||||
position: 'absolute'
|
||||
},
|
||||
modalDropdownStyle: {
|
||||
alignSelf: 'center',
|
||||
},
|
||||
profileModalDropsdownDropdownStyle: {
|
||||
width: SCREEN_WIDTH * 0.6,
|
||||
height: 215,
|
||||
borderColor: colors.lightgrey,
|
||||
borderWidth: 1,
|
||||
marginLeft: 40,
|
||||
marginTop: -30
|
||||
},
|
||||
socialModalDropdownDropdownStyle: {
|
||||
width: SCREEN_WIDTH * 0.6,
|
||||
height: 210,
|
||||
borderColor: colors.lightgrey,
|
||||
borderWidth: 1,
|
||||
marginLeft: 40,
|
||||
marginTop: -30
|
||||
},
|
||||
modalDropsdownDropdownTextStyle: {
|
||||
fontSize: 18
|
||||
},
|
||||
modalTextStyle: {
|
||||
flex: 1,
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
lineHeight: 40
|
||||
},
|
||||
avatarViewStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginTop: 10
|
||||
},
|
||||
avatarEditStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginLeft: 25
|
||||
},
|
||||
noAvatarEditStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginLeft: 10
|
||||
},
|
||||
avatarStyle: {
|
||||
width: 125,
|
||||
height: 125,
|
||||
borderWidth: 1,
|
||||
borderRadius: 60,
|
||||
marginTop: 15
|
||||
},
|
||||
modalOpacityStyle: {
|
||||
backgroundColor: colors.modalTransparency,
|
||||
position: 'relative',
|
||||
flex: 1,
|
||||
justifyContent: 'center'
|
||||
},
|
||||
modalScrollViewStyle: {
|
||||
backgroundColor: colors.white,
|
||||
},
|
||||
modalCardStyle: {
|
||||
justifyContent: 'center'
|
||||
},
|
||||
duplicateMainViewStyle: {
|
||||
backgroundColor: colors.white,
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
paddingRight: 10
|
||||
},
|
||||
leftAlignedTextStyle: {
|
||||
textAlign: 'left',
|
||||
marginLeft: 20,
|
||||
marginRight: 20,
|
||||
fontSize: 15
|
||||
},
|
||||
duplicateModalDropdownStyle: {
|
||||
marginLeft: 20
|
||||
},
|
||||
modalDropdownDropdownViewStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
height: 40,
|
||||
paddingLeft: 10
|
||||
},
|
||||
leftIconStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start'
|
||||
},
|
||||
modalDropdownDropdownStyle: {
|
||||
width: SCREEN_WIDTH,
|
||||
borderColor: colors.lightGrey,
|
||||
borderWidth: 1,
|
||||
marginLeft: -10
|
||||
},
|
||||
modalDropdownDropdownTextStyle: {
|
||||
fontSize: 18,
|
||||
marginLeft: 10
|
||||
},
|
||||
highlightedImageStyle: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
borderRadius: 15,
|
||||
marginRight: 10
|
||||
},
|
||||
avatarSmallImageStyle: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
borderWidth: 1,
|
||||
borderRadius: 15
|
||||
},
|
||||
buttonStyle: {
|
||||
width: SCREEN_WIDTH * 0.9,
|
||||
marginTop: 15,
|
||||
marginBottom: 30,
|
||||
borderRadius: 5,
|
||||
backgroundColor: colors.buttonBlue
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Map State To Props
|
||||
**/
|
||||
const mapStateToProps = (state) => {
|
||||
// converts Profile objects into profile arrays
|
||||
const profiles = _.map(state.profiles, (val, uid) => {
|
||||
return { ...val, uid };
|
||||
});
|
||||
|
||||
const {
|
||||
contactAvatarImg,
|
||||
} = state.contactForm;
|
||||
|
||||
const {
|
||||
socialAvatarImg
|
||||
} = state.socialContacts;
|
||||
|
||||
const {
|
||||
isDisabled,
|
||||
profileAvatarImg,
|
||||
uid
|
||||
} = state.profileForm;
|
||||
|
||||
return {
|
||||
profiles,
|
||||
contactAvatarImg,
|
||||
socialAvatarImg,
|
||||
isDisabled,
|
||||
profileAvatarImg,
|
||||
uid
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
contactUpdate, profileToggleDisabled, profileSave, profileUpdate, socialRemoveFriend
|
||||
})(ProfileAvatar);
|
||||
6
src/components/TransactionTypes.js
Normal file
@ -0,0 +1,6 @@
|
||||
/** Contains all string key/value pairs to prevent typos **/
|
||||
export const EVENT_TRANSACTION = 'event';
|
||||
export const EARNED_TRANSACTION = 'earned';
|
||||
export const RECEIVED_TRANSACTION = 'received';
|
||||
export const REQUESTED_TRANSACTION = 'requested';
|
||||
export const SENT_TRANSACTION = 'sent';
|
||||
57
src/components/UserLogout.js
Normal file
@ -0,0 +1,57 @@
|
||||
/** Used for handling logging out a user **/
|
||||
import React, { Component } from 'react';
|
||||
import { AsyncStorage } from 'react-native';
|
||||
import { NavigationActions } from 'react-navigation';
|
||||
import { purgeStoredState } from 'redux-persist';
|
||||
import firebase from 'firebase';
|
||||
import { connect } from 'react-redux';
|
||||
import { Spinner } from '../components/common';
|
||||
import { profileListReset, propertiesReset, userFormReset } from '../actions';
|
||||
import { strings } from '../locales/i18n';
|
||||
|
||||
class UserLogout extends Component {
|
||||
static navigationOptions = {
|
||||
title: strings('logout.title'),
|
||||
}
|
||||
|
||||
/**
|
||||
* Component Will Mount
|
||||
**/
|
||||
async componentWillMount() {
|
||||
firebase.auth().signOut();
|
||||
this.props.userFormReset();
|
||||
this.props.propertiesReset();
|
||||
this.props.profileListReset();
|
||||
purgeStoredState({ storage: AsyncStorage });
|
||||
this.navigateToLoginScreen();
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to login screen and resets the navigation so no back option
|
||||
**/
|
||||
navigateToLoginScreen() {
|
||||
const resetAction = NavigationActions.reset({
|
||||
index: 0,
|
||||
actions: [
|
||||
NavigationActions.navigate({ routeName: 'userLogin' })
|
||||
]
|
||||
});
|
||||
|
||||
this.props.navigation.dispatch(resetAction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
return (
|
||||
<Spinner />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(null, {
|
||||
profileListReset,
|
||||
propertiesReset,
|
||||
userFormReset
|
||||
})(UserLogout);
|
||||
128
src/components/common/Confirm.js
Normal file
@ -0,0 +1,128 @@
|
||||
import React from 'react';
|
||||
import { Modal, ScrollView, TouchableOpacity, TouchableWithoutFeedback, View } from 'react-native';
|
||||
import { Button, Card, FormLabel } from 'react-native-elements';
|
||||
import colors from '../../config/colors.json';
|
||||
import { strings } from '../../locales/i18n';
|
||||
|
||||
const Confirm = ({ children, visible, onAccept, onDecline, onOkay }) => {
|
||||
if (onOkay) {
|
||||
return (
|
||||
<Modal
|
||||
animationType="slide"
|
||||
onRequestClose={() => {}}
|
||||
transparent
|
||||
visible={visible}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={styles.opacityStyle}
|
||||
activeOpacity={1}
|
||||
onPressOut={onOkay}
|
||||
>
|
||||
<ScrollView
|
||||
directionalLockEnabled
|
||||
centerContent
|
||||
contentContainerStyle={styles.scrollViewStyle}
|
||||
>
|
||||
<TouchableWithoutFeedback>
|
||||
<View style={styles.containerStyle}>
|
||||
<Card style={styles.cardStyle}>
|
||||
<FormLabel style={styles.textStyle}>{children}</FormLabel>
|
||||
<View>
|
||||
<Button
|
||||
onPress={onOkay}
|
||||
title={strings('confirm.okay')}
|
||||
buttonStyle={styles.buttonStyle}
|
||||
fontWeight="bold"
|
||||
fontSize={25}
|
||||
/>
|
||||
</View>
|
||||
</Card>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</ScrollView>
|
||||
</TouchableOpacity>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
animationType="slide"
|
||||
onRequestClose={() => {}}
|
||||
transparent
|
||||
visible={visible}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={styles.opacityStyle}
|
||||
activeOpacity={1}
|
||||
onPressOut={onDecline}
|
||||
>
|
||||
<ScrollView
|
||||
directionalLockEnabled
|
||||
centerContent
|
||||
contentContainerStyle={styles.scrollViewStyle}
|
||||
>
|
||||
<TouchableWithoutFeedback>
|
||||
<View style={styles.containerStyle}>
|
||||
<Card style={styles.cardStyle}>
|
||||
<FormLabel style={styles.textStyle}>{children}</FormLabel>
|
||||
<View>
|
||||
<Button
|
||||
onPress={onAccept}
|
||||
title={strings('confirm.yes')}
|
||||
buttonStyle={styles.buttonStyle}
|
||||
fontWeight="bold"
|
||||
fontSize={25}
|
||||
/>
|
||||
<Button
|
||||
onPress={onDecline}
|
||||
title={strings('confirm.no')}
|
||||
buttonStyle={styles.buttonStyle}
|
||||
fontWeight="bold"
|
||||
fontSize={25}
|
||||
/>
|
||||
</View>
|
||||
</Card>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</ScrollView>
|
||||
</TouchableOpacity>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
opacityStyle: {
|
||||
backgroundColor: colors.modalTransparency,
|
||||
position: 'relative',
|
||||
flex: 1,
|
||||
justifyContent: 'center'
|
||||
},
|
||||
scrollViewStyle: {
|
||||
backgroundColor: colors.white,
|
||||
},
|
||||
containerStyle: {
|
||||
backgroundColor: colors.modalTransparency,
|
||||
position: 'relative',
|
||||
flex: 1,
|
||||
justifyContent: 'center'
|
||||
},
|
||||
cardStyle: {
|
||||
justifyContent: 'center'
|
||||
},
|
||||
textStyle: {
|
||||
flex: 1,
|
||||
fontSize: 18,
|
||||
textAlign: 'center',
|
||||
lineHeight: 40
|
||||
},
|
||||
buttonStyle: {
|
||||
backgroundColor: colors.buttonBlue,
|
||||
left: 0,
|
||||
right: 0,
|
||||
marginTop: 15,
|
||||
borderRadius: 5
|
||||
}
|
||||
};
|
||||
|
||||
export { Confirm };
|
||||
323
src/components/common/Footer.js
Normal file
@ -0,0 +1,323 @@
|
||||
/** Footer for showing main options **/
|
||||
import _ from 'lodash';
|
||||
import React, { Component } from 'react';
|
||||
import { Dimensions, Image, Text, View } from 'react-native';
|
||||
import { NavigationActions } from 'react-navigation';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import ModalDropdown from 'react-native-modal-dropdown';
|
||||
import { connect } from 'react-redux';
|
||||
import colors from '../../config/colors.json';
|
||||
import { profileFetch } from '../../actions';
|
||||
|
||||
const GREEN_CHECK_IMAGE = require('../../../assets/GreenCheck.png');
|
||||
|
||||
const SCREEN_WIDTH = Dimensions.get('window').width;
|
||||
let profileNames = [];
|
||||
|
||||
class Footer extends Component {
|
||||
/**
|
||||
* Component Will Mount
|
||||
**/
|
||||
componentWillMount() {
|
||||
this.props.profileFetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Component Will Receive Props
|
||||
**/
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.profiles) {
|
||||
profileNames = [];
|
||||
for (let i = 0; i < this.props.profiles.length; i++) {
|
||||
profileNames.push(this.props.profiles[i].profileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color of the icon based on whether it was selected or not
|
||||
**/
|
||||
getColor(index) {
|
||||
return index === this.props.index ? colors.buttonBlue : colors.middleGrey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to the events page if not on that page
|
||||
**/
|
||||
processEventPress() {
|
||||
if (this.props.index === 0) {
|
||||
return;
|
||||
}
|
||||
this.props.navigation.navigate('eventHomeScreen');
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to the contacts page if not on that page
|
||||
**/
|
||||
processContactsPress() {
|
||||
if (this.props.index === 1) {
|
||||
return;
|
||||
}
|
||||
this.props.navigation.navigate('contactTabs');
|
||||
}
|
||||
/**
|
||||
* Navigates to the profile share page if not on that page
|
||||
**/
|
||||
processProfileSharePress() {
|
||||
if (this.props.index === 2) {
|
||||
return;
|
||||
}
|
||||
this.props.navigation.navigate('profileShareOptionsScreen');
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to the conversation list page if not on that page
|
||||
**/
|
||||
processConversationPress() {
|
||||
if (this.props.index === 3) {
|
||||
return;
|
||||
}
|
||||
this.props.navigation.navigate('conversationList');
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to the profile home screen for the given profile
|
||||
**/
|
||||
processProfileSelection(idx) {
|
||||
const resetAction = NavigationActions.reset({
|
||||
index: 0,
|
||||
actions: [
|
||||
NavigationActions.navigate({ routeName: 'mainOptionsMenu', params: { profileIndex: idx, profileName: this.props.profiles[idx].profileName } })
|
||||
]
|
||||
});
|
||||
|
||||
this.props.navigation.dispatch(resetAction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an icon on the right of the profile row if it is the selected profile
|
||||
**/
|
||||
renderHightlightedIcon(highlighted) {
|
||||
if (highlighted) {
|
||||
return (
|
||||
<Image
|
||||
style={styles.dropdownImageStyle}
|
||||
mode='stretch'
|
||||
source={GREEN_CHECK_IMAGE}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Image
|
||||
style={styles.dropdownImageStyle}
|
||||
mode='stretch'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an icon to the left of the profile name in the profile row
|
||||
**/
|
||||
renderLeftIcon(rowID) {
|
||||
const base64 = this.props.profiles[rowID].profileAvatarImg;
|
||||
|
||||
if (base64) {
|
||||
return (
|
||||
<View style={styles.leftIconStyle}>
|
||||
<Image
|
||||
source={{ uri: base64 }}
|
||||
style={styles.avatarSmallImageStyle}
|
||||
/>
|
||||
<Text style={[styles.modalDropdownDropdownTextStyle, { color: this.props.profiles[rowID].isDisabled ? colors.lightGrey : colors.black }]}>
|
||||
{profileNames[rowID]}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.leftIconStyle}>
|
||||
<Icon
|
||||
name={'account-circle'}
|
||||
size={20}
|
||||
color={colors.middleGrey}
|
||||
containerStyle={styles.editIconWithAvatar}
|
||||
/>
|
||||
<Text style={[styles.modalDropdownDropdownTextStyle, { color: this.props.profiles[rowID].isDisabled ? colors.lightGrey : colors.black }]}>
|
||||
{profileNames[rowID]}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for custom styling of the modal dropdown
|
||||
**/
|
||||
renderModalDropdownRow(rowData, rowID, highlighted) {
|
||||
return (
|
||||
<View style={{ backgroundColor: this.props.profiles[rowID].isDisabled ? colors.offWhite : colors.white }}>
|
||||
<View style={styles.modalDropdownDropdownViewStyle}>
|
||||
{this.renderLeftIcon(rowID)}
|
||||
|
||||
{this.renderHightlightedIcon(highlighted)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the last icon or the avatar
|
||||
**/
|
||||
renderFinalIcon() {
|
||||
const profileIndex = parseInt(this.props.profileIndex, 10);
|
||||
|
||||
const base64 = this.props.profileAvatarImg;
|
||||
|
||||
if (base64) {
|
||||
return (
|
||||
<ModalDropdown
|
||||
style={styles.modalDropdownStyle}
|
||||
dropdownStyle={styles.modalDropdownDropdownStyle}
|
||||
options={profileNames}
|
||||
renderRow={this.renderModalDropdownRow.bind(this)}
|
||||
onSelect={(idx) => this.processProfileSelection(idx)}
|
||||
defaultIndex={profileIndex}
|
||||
>
|
||||
<Image
|
||||
source={{ uri: base64 }}
|
||||
style={styles.avatarImageStyle}
|
||||
/>
|
||||
</ModalDropdown>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ModalDropdown
|
||||
style={styles.modalDropdownStyle}
|
||||
dropdownStyle={styles.modalDropdownDropdownStyle}
|
||||
options={profileNames}
|
||||
renderRow={this.renderModalDropdownRow.bind(this)}
|
||||
onSelect={(idx) => this.processProfileSelection(idx)}
|
||||
defaultIndex={profileIndex}
|
||||
>
|
||||
<Icon
|
||||
name={'account-circle'}
|
||||
size={40}
|
||||
color={this.getColor(4)}
|
||||
containerStyle={styles.editIconWithAvatar}
|
||||
/>
|
||||
</ModalDropdown>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Method
|
||||
**/
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.mainViewStyle}>
|
||||
<Icon
|
||||
name={'event'}
|
||||
size={30}
|
||||
color={this.getColor(0)}
|
||||
containerStyle={styles.editIconWithAvatar}
|
||||
onPress={() => this.processEventPress()}
|
||||
/>
|
||||
<Icon
|
||||
name={'group'}
|
||||
size={30}
|
||||
color={this.getColor(1)}
|
||||
containerStyle={styles.editIconWithAvatar}
|
||||
onPress={() => this.processContactsPress()}
|
||||
/>
|
||||
<Icon
|
||||
reverse
|
||||
name={'handshake-o'}
|
||||
type='font-awesome'
|
||||
size={20}
|
||||
color={this.getColor(2)}
|
||||
containerStyle={styles.editIconWithAvatar}
|
||||
onPress={() => this.processProfileSharePress()}
|
||||
/>
|
||||
<Icon
|
||||
name={'comment'}
|
||||
size={30}
|
||||
color={this.getColor(3)}
|
||||
containerStyle={styles.editIconWithAvatar}
|
||||
onPress={() => this.processConversationPress()}
|
||||
/>
|
||||
{this.renderFinalIcon()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
mainViewStyle: {
|
||||
backgroundColor: colors.white,
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: 10,
|
||||
borderTopColor: colors.middleGrey,
|
||||
borderTopWidth: 1
|
||||
},
|
||||
modalDropdownStyle: {
|
||||
alignSelf: 'center'
|
||||
},
|
||||
modalDropdownDropdownViewStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
height: 40,
|
||||
marginLeft: 10
|
||||
},
|
||||
leftIconStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
},
|
||||
modalDropdownDropdownStyle: {
|
||||
width: SCREEN_WIDTH,
|
||||
borderColor: colors.lightGrey,
|
||||
borderWidth: 1,
|
||||
marginRight: -10
|
||||
},
|
||||
modalDropdownDropdownTextStyle: {
|
||||
fontSize: 18,
|
||||
marginLeft: 10
|
||||
},
|
||||
dropdownImageStyle: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
borderRadius: 15,
|
||||
marginRight: 10
|
||||
},
|
||||
avatarImageStyle: {
|
||||
width: 35,
|
||||
height: 35,
|
||||
borderWidth: 1,
|
||||
borderRadius: 18
|
||||
},
|
||||
avatarSmallImageStyle: {
|
||||
width: 30,
|
||||
height: 30,
|
||||
borderWidth: 1,
|
||||
borderRadius: 15
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Map State to Props
|
||||
**/
|
||||
const mapStateToProps = state => {
|
||||
// converts Profile objects into profile arrays
|
||||
const profiles = _.map(state.profiles, (val, uid) => {
|
||||
return { ...val, uid };
|
||||
});
|
||||
|
||||
return { profiles };
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, { profileFetch })(Footer);
|
||||
57
src/components/common/FormLabel.js
Normal file
@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import colors from '../../config/colors.json';
|
||||
|
||||
const FormLabel = ({ bold, required, labelText, textStyle }) => {
|
||||
if (bold && required) {
|
||||
return (
|
||||
<View style={styles.formLabelStyle}>
|
||||
<Text style={styles.requiredTextNotationStyle}> * </Text>
|
||||
<Text style={[styles.labelTextStyle, { fontWeight: 'bold', color: colors.black }, textStyle]}>
|
||||
{labelText}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
} else if (required) {
|
||||
return (
|
||||
<View style={styles.formLabelStyle}>
|
||||
<Text style={styles.requiredTextNotationStyle}> * </Text>
|
||||
<Text style={[styles.labelTextStyle, textStyle]}>{labelText}</Text>
|
||||
</View>
|
||||
);
|
||||
} else if (bold) {
|
||||
return (
|
||||
<View style={styles.formLabelStyle}>
|
||||
<Text style={[styles.labelTextStyle, { fontWeight: 'bold', color: colors.black }, textStyle]}>
|
||||
{labelText}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.formLabelStyle}>
|
||||
<Text style={[styles.labelTextStyle, textStyle]}>{labelText}</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
formLabelStyle: {
|
||||
flexDirection: 'row',
|
||||
paddingTop: 5,
|
||||
paddingLeft: 15,
|
||||
paddingBottom: 5
|
||||
},
|
||||
requiredTextNotationStyle: {
|
||||
fontSize: 15,
|
||||
color: colors.red,
|
||||
textAlign: 'left'
|
||||
},
|
||||
labelTextStyle: {
|
||||
fontSize: 16,
|
||||
textAlign: 'left'
|
||||
}
|
||||
};
|
||||
|
||||
export { FormLabel };
|
||||
41
src/components/common/FormMessage.js
Normal file
@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import colors from '../../config/colors.json';
|
||||
|
||||
const FormMessage = ({ error, successMsg }) => {
|
||||
if (error) {
|
||||
return (
|
||||
<View>
|
||||
<Text style={styles.errorTextStyle}>
|
||||
{error}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
} else if (successMsg) {
|
||||
return (
|
||||
<View>
|
||||
<Text style={styles.successTextStyle}>
|
||||
{successMsg}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return <View style={{ height: 40 }} />;
|
||||
};
|
||||
|
||||
const styles = {
|
||||
errorTextStyle: {
|
||||
fontSize: 20,
|
||||
alignSelf: 'center',
|
||||
color: colors.red,
|
||||
marginVertical: 10
|
||||
},
|
||||
successTextStyle: {
|
||||
fontSize: 20,
|
||||
alignSelf: 'center',
|
||||
color: colors.darkGreen,
|
||||
marginVertical: 10
|
||||
}
|
||||
};
|
||||
|
||||
export { FormMessage };
|
||||
48
src/components/common/HeaderRight.js
Normal file
@ -0,0 +1,48 @@
|
||||
/** Custom header right component **/
|
||||
import React, { Component } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import colors from '../../config/colors.json';
|
||||
|
||||
class HeaderRight extends Component {
|
||||
/**
|
||||
* Render Method
|
||||
**/
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.mainViewStyle}>
|
||||
<Icon
|
||||
name={'wallet'}
|
||||
type='entypo'
|
||||
color={colors.middleGrey}
|
||||
size={30}
|
||||
containerStyle={styles.iconStyle}
|
||||
onPress={() => this.props.navigation.navigate('walletHomeScreen')}
|
||||
/>
|
||||
<Icon
|
||||
name='notifications'
|
||||
color={colors.middleGrey}
|
||||
size={30}
|
||||
containerStyle={styles.iconStyle}
|
||||
onPress={() => this.props.navigation.navigate('notificationsListScreen')}
|
||||
backgroundColor={colors.transparent}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
mainViewStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
marginLeft: 20,
|
||||
marginRight: 20,
|
||||
},
|
||||
iconStyle: {
|
||||
marginLeft: 10,
|
||||
}
|
||||
};
|
||||
|
||||
export default HeaderRight;
|
||||
48
src/components/common/Keyword.js
Normal file
@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import { Icon } from 'react-native-elements';
|
||||
import colors from '../../config/colors.json';
|
||||
|
||||
const Keyword = ({ title, onPressX, displayX }) => {
|
||||
const keywordStyle = styles.row;
|
||||
|
||||
if (displayX) {
|
||||
return (
|
||||
<View style={[styles.keywordViewStyle, keywordStyle]}>
|
||||
<Text style={styles.keywordTextStyle}>{title}</Text>
|
||||
<Icon
|
||||
name='cancel'
|
||||
onPress={onPressX}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View style={[styles.keywordViewStyle, keywordStyle]}>
|
||||
<Text style={styles.keywordTextStyle}>{title}</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
keywordViewStyle: {
|
||||
flexDirection: 'row',
|
||||
paddingTop: 5,
|
||||
paddingBottom: 5,
|
||||
paddingLeft: 10,
|
||||
paddingRight: 5,
|
||||
margin: 5,
|
||||
borderWidth: 2,
|
||||
borderRadius: 25,
|
||||
borderColor: colors.darkBlue
|
||||
},
|
||||
keywordTextStyle: {
|
||||
fontSize: 15,
|
||||
textAlign: 'left',
|
||||
alignItems: 'center',
|
||||
paddingRight: 10,
|
||||
color: colors.black,
|
||||
}
|
||||
};
|
||||
|
||||
export { Keyword };
|
||||
157
src/components/common/Slides.js
Normal file
@ -0,0 +1,157 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Animated, Dimensions, Image, ImageBackground, ScrollView, Text, View } from 'react-native';
|
||||
import { Button } from 'react-native-elements';
|
||||
import { Spacer } from '../../components/common';
|
||||
import colors from '../../config/colors.json';
|
||||
import { strings } from '../../locales/i18n';
|
||||
|
||||
const SCREEN_WIDTH = Dimensions.get('window').width;
|
||||
const scrollX = new Animated.Value(0);
|
||||
const LOGO = require('../../../assets/logo-horizontal.png');
|
||||
|
||||
const BACKGROUNG_IMAGES = [
|
||||
require('../../../assets/onboarding_screen_1.jpg'),
|
||||
require('../../../assets/onboarding_screen_2.jpg'),
|
||||
require('../../../assets/onboarding_screen_3.jpg'),
|
||||
require('../../../assets/onboarding_screen_4.jpg'),
|
||||
require('../../../assets/onboarding_screen_5.jpg')
|
||||
];
|
||||
|
||||
class Slides extends Component {
|
||||
state = { counter: 0 };
|
||||
|
||||
renderSlides() {
|
||||
const position = Animated.divide(scrollX, SCREEN_WIDTH);
|
||||
|
||||
return this.props.data.map((slide) => {
|
||||
const imageSource = BACKGROUNG_IMAGES[slide.index];
|
||||
|
||||
return (
|
||||
<View
|
||||
key={slide.text1}
|
||||
style={styles.slideStyle}
|
||||
>
|
||||
<ImageBackground
|
||||
source={imageSource}
|
||||
style={styles.backgroundImage}
|
||||
>
|
||||
<Spacer />
|
||||
<View>
|
||||
<Image
|
||||
source={LOGO}
|
||||
style={styles.logoStyle}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<Text key={slide.text1} style={styles.majorTextStyle}>{slide.text1}</Text>
|
||||
<Text key={slide.text2} style={styles.minorTextStyle}>{slide.text2}</Text>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<View style={{ height: 10, marginBottom: 20 }}>
|
||||
<View style={{ flex: 1, flexDirection: 'row', justifyContent: 'center' }}>
|
||||
{this.props.data.map((_, i) => {
|
||||
const opacity = position.interpolate({
|
||||
inputRange: [i - 1, i, i + 1],
|
||||
outputRange: [0.3, 1, 0.3],
|
||||
extrapolate: 'clamp'
|
||||
});
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
key={i}
|
||||
style={{ opacity, height: 10, width: 10, backgroundColor: '#595959', margin: 8, borderRadius: 5 }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<Button
|
||||
title={strings('slides.button')}
|
||||
buttonStyle={styles.buttonStyle}
|
||||
onPress={this.props.onComplete}
|
||||
fontWeight="bold"
|
||||
fontSize={25}
|
||||
color={colors.white}
|
||||
/>
|
||||
</View>
|
||||
</ImageBackground>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ScrollView
|
||||
contentContainerStyle={{ flexGrow: 1, backgroundColor: colors.white }}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
<ScrollView
|
||||
horizontal
|
||||
pagingEnabled
|
||||
showsHorizontalScrollIndicator={false}
|
||||
onScroll={Animated.event(
|
||||
[{ nativeEvent: { contentOffset: { x: scrollX } } }]
|
||||
)}
|
||||
scrollEventThrottle={16}
|
||||
>
|
||||
{this.renderSlides()}
|
||||
</ScrollView>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
slideStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
width: SCREEN_WIDTH,
|
||||
backgroundColor: colors.transparent
|
||||
},
|
||||
majorTextStyle: {
|
||||
fontSize: 30,
|
||||
fontWeight: 'bold',
|
||||
color: colors.black,
|
||||
textAlign: 'center',
|
||||
marginHorizontal: 30,
|
||||
marginBottom: 30
|
||||
},
|
||||
minorTextStyle: {
|
||||
fontSize: 25,
|
||||
color: colors.black,
|
||||
textAlign: 'center',
|
||||
marginHorizontal: 25,
|
||||
},
|
||||
logoStyle: {
|
||||
height: SCREEN_WIDTH * 0.23,
|
||||
width: SCREEN_WIDTH * 0.8,
|
||||
marginVertical: 15,
|
||||
alignSelf: 'center'
|
||||
},
|
||||
backgroundImage: {
|
||||
flex: 1,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
alignSelf: 'stretch',
|
||||
justifyContent: 'space-between'
|
||||
},
|
||||
buttonStyle: {
|
||||
width: SCREEN_WIDTH * 0.9,
|
||||
marginTop: 15,
|
||||
marginBottom: 30,
|
||||
borderWidth: 2,
|
||||
borderRadius: 5,
|
||||
backgroundColor: colors.buttonBlue,
|
||||
borderColor: colors.buttonBlue
|
||||
}
|
||||
};
|
||||
|
||||
export { Slides };
|
||||
12
src/components/common/Spacer.js
Normal file
@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
const Spacer = ({ space }) => {
|
||||
return (
|
||||
<View
|
||||
style={{ height: space || 15 }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { Spacer };
|
||||
20
src/components/common/Spinner.js
Normal file
@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import { ActivityIndicator, View } from 'react-native';
|
||||
|
||||
const Spinner = ({ size }) => {
|
||||
return (
|
||||
<View style={styles.spinnerStyle}>
|
||||
<ActivityIndicator size={size || 'large'} />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
spinnerStyle: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
}
|
||||
};
|
||||
|
||||
export { Spinner };
|
||||
39
src/components/common/TextButton.js
Normal file
@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { Text, TouchableOpacity, View } from 'react-native';
|
||||
import colors from '../../config/colors.json';
|
||||
|
||||
const TextButton = ({ leftText, leftTextStyle, rightText, rightTextStyle, containerStyle, onPress, disabled }) => (
|
||||
<View style={[styles.textButtonStyle, containerStyle]} >
|
||||
<Text style={[styles.textStyle, leftTextStyle]}>
|
||||
{leftText}
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
onPress={onPress}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Text style={[styles.textButtonTitleStyle, rightTextStyle]} >
|
||||
{rightText}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
|
||||
const styles = {
|
||||
textButtonStyle: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start',
|
||||
left: 20,
|
||||
marginTop: 10,
|
||||
},
|
||||
textStyle: {
|
||||
color: colors.black,
|
||||
fontSize: 17
|
||||
},
|
||||
textButtonTitleStyle: {
|
||||
color: colors.buttonBlue,
|
||||
fontSize: 17,
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
};
|
||||
|
||||
export { TextButton };
|
||||
8
src/components/common/index.js
Normal file
@ -0,0 +1,8 @@
|
||||
export * from './Confirm';
|
||||
export * from './FormMessage';
|
||||
export * from './FormLabel';
|
||||
export * from './Keyword';
|
||||
export * from './Spacer';
|
||||
export * from './Spinner';
|
||||
export * from './Slides';
|
||||
export * from './TextButton';
|
||||
498
src/components/forms/ContactForm.js
Normal file
@ -0,0 +1,498 @@
|
||||
/** Contains the base fields for a contact reuse **/
|
||||
import React, { Component } from 'react';
|
||||
import { Animated, Dimensions, Platform, ScrollView, View } from 'react-native';
|
||||
import { Divider, FormInput } from 'react-native-elements';
|
||||
import { connect } from 'react-redux';
|
||||
import { contactFormReset, contactUpdate } from '../../actions';
|
||||
import BizCard from '../BizCard';
|
||||
import ProfileAvatar from '../ProfileAvatar';
|
||||
import { FormMessage, FormLabel, Spacer } from '../common';
|
||||
import { CONTACT_FORM } from '../FormTypes';
|
||||
import colors from '../../config/colors.json';
|
||||
import { strings } from '../../locales/i18n';
|
||||
|
||||
const SCREEN_WIDTH = Dimensions.get('window').width;
|
||||
const DOTS_SIZE = ['first', 'second'];
|
||||
const scrollX = new Animated.Value(0);
|
||||
|
||||
class ContactForm extends Component {
|
||||
/**
|
||||
* Component Will Unmount
|
||||
**/
|
||||
componentWillUnmount() {
|
||||
this.props.contactFormReset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Breaks the fullName into a first and last name for contact saving
|
||||
**/
|
||||
splitNameField(value) {
|
||||
this.props.contactUpdate({ prop: 'fullName', value });
|
||||
|
||||
const nameArr = this.props.fullName.split(' ');
|
||||
const firstName = nameArr[0];
|
||||
this.props.contactUpdate({ prop: 'firstName', value: firstName });
|
||||
|
||||
if (nameArr.length > 0) {
|
||||
const lastName = nameArr[1];
|
||||
this.props.contactUpdate({ prop: 'lastName', value: lastName });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the avatar and biz card slider
|
||||
**/
|
||||
renderAvatarBizCardSlider() {
|
||||
const position = Animated.divide(scrollX, SCREEN_WIDTH);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<ScrollView
|
||||
horizontal
|
||||
pagingEnabled
|
||||
showsHorizontalScrollIndicator={false}
|
||||
onScroll={Animated.event(
|
||||
[{ nativeEvent: { contentOffset: { x: scrollX } } }]
|
||||
)}
|
||||
scrollEventThrottle={16}
|
||||
>
|
||||
<View style={styles.slideStyle}>
|
||||
<View style={{ flex: 1, justifyContent: 'center' }}>
|
||||
<ProfileAvatar
|
||||
{...this.props}
|
||||
formType={CONTACT_FORM}
|
||||
isEditMode
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View style={styles.slideStyle}>
|
||||
<BizCard
|
||||
formType={CONTACT_FORM}
|
||||
isEditMode
|
||||
isScanBizCardScreen={this.props.isScanBizCardScreen}
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
<View style={styles.dotsStyle}>
|
||||
{DOTS_SIZE.map((_, i) => {
|
||||
const opacity = position.interpolate({
|
||||
inputRange: [i - 1, i, i + 1],
|
||||
outputRange: [0.3, 1, 0.3],
|
||||
extrapolate: 'clamp'
|
||||
});
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
key={i}
|
||||
style={{ opacity, height: 10, width: 10, backgroundColor: '#595959', margin: 8, borderRadius: 5 }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
</View>
|
||||
{this.renderDetailsSection()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the top section under the avatar
|
||||
**/
|
||||
renderDetailsSection() {
|
||||
return (
|
||||
<View>
|
||||
<Divider style={styles.dividerStyle} />
|
||||
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.fullName_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.fullName}
|
||||
placeholder={strings('common.contactFields.fullName_placeholder')}
|
||||
onChangeText={value => this.splitNameField(value)}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.inputTextStyle}
|
||||
/>
|
||||
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.headline_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.headline}
|
||||
placeholder={strings('common.contactFields.headline_placeholder')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'headline', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
multiline
|
||||
numberOfLines={4}
|
||||
inputStyle={styles.inputTextStyle}
|
||||
/>
|
||||
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.titleAndCompany_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.titleAndCompany}
|
||||
placeholder={strings('common.contactFields.titleAndCompany_placeholder')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'titleAndCompany', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.inputTextStyle}
|
||||
/>
|
||||
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.location_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.location}
|
||||
placeholder={strings('common.contactFields.location_placeholder')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'location', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.inputTextStyle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the primary phone section
|
||||
**/
|
||||
renderPrimaryPhoneSection() {
|
||||
return (
|
||||
<View>
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.phone_primary_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.primaryPhone}
|
||||
placeholder={strings('common.contactFields.phone_placeholder')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'primaryPhone', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the secondary phone section
|
||||
**/
|
||||
renderSecondaryPhoneSection() {
|
||||
return (
|
||||
<View>
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.phone_secondary_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.secondaryPhone}
|
||||
placeholder={strings('common.contactFields.phone_placeholder')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'secondaryPhone', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the work phone section
|
||||
**/
|
||||
renderWorkPhoneSection() {
|
||||
return (
|
||||
<View>
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.phone_work_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.workPhone}
|
||||
placeholder={strings('common.contactFields.phone_placeholder')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'workPhone', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.extension_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.extension}
|
||||
placeholder={strings('common.contactFields.extension_placeholder')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'extension', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the fax phone section
|
||||
**/
|
||||
renderFaxSection() {
|
||||
return (
|
||||
<View>
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.fax_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.fax}
|
||||
placeholder={strings('common.contactFields.phone_placeholder')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'fax', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the email section
|
||||
**/
|
||||
renderEmailSection() {
|
||||
return (
|
||||
<View>
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.email_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.contactEmail}
|
||||
placeholder={strings('common.contactFields.email_placeholder')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'contactEmail', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the website section
|
||||
**/
|
||||
renderWebsiteSection() {
|
||||
return (
|
||||
<View>
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.website_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.website}
|
||||
placeholder={strings('common.contactFields.website_placeholder')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'website', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the address section
|
||||
**/
|
||||
renderAddressSection() {
|
||||
return (
|
||||
<View>
|
||||
<Spacer />
|
||||
<FormLabel bold labelText={strings('common.contactFields.address_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.street}
|
||||
placeholder={strings('common.contactFields.address_placeholder_street')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'street', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
<FormInput
|
||||
value={this.props.address2}
|
||||
placeholder={strings('common.contactFields.address_placeholder_address2')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'address2', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
<FormInput
|
||||
value={this.props.city}
|
||||
placeholder={strings('common.contactFields.address_placeholder_city')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'city', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
<FormInput
|
||||
value={this.props.region}
|
||||
placeholder={strings('common.contactFields.address_placeholder_region')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'region', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
<FormInput
|
||||
value={this.props.postalCode}
|
||||
placeholder={strings('common.contactFields.address_placeholder_postalCode')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'postalCode', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
<FormInput
|
||||
value={this.props.country}
|
||||
placeholder={strings('common.contactFields.address_placeholder_country')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'country', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.formInputStyle}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<View>
|
||||
{this.renderAvatarBizCardSlider()}
|
||||
</View>
|
||||
|
||||
{this.renderPrimaryPhoneSection()}
|
||||
{this.renderSecondaryPhoneSection()}
|
||||
{this.renderWorkPhoneSection()}
|
||||
{this.renderFaxSection()}
|
||||
|
||||
{this.renderEmailSection()}
|
||||
{this.renderWebsiteSection()}
|
||||
|
||||
{this.renderAddressSection()}
|
||||
|
||||
<Spacer />
|
||||
<FormLabel labelText={strings('common.contactFields.notes_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.notes}
|
||||
placeholder={strings('common.contactFields.notes_placeholder')}
|
||||
onChangeText={value => this.props.contactUpdate({ prop: 'notes', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
multiline
|
||||
numberOfLines={4}
|
||||
containerStyle={{ minHeight: 200 }}
|
||||
inputStyle={styles.textBoxStyles}
|
||||
/>
|
||||
<FormMessage error={this.props.error} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
slideStyle: {
|
||||
width: SCREEN_WIDTH,
|
||||
},
|
||||
dividerStyle: {
|
||||
backgroundColor: colors.middleGrey,
|
||||
marginTop: 10,
|
||||
marginBottom: 15
|
||||
},
|
||||
dotsStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
centeredInputContainerStyle: {
|
||||
borderBottomWidth: 0,
|
||||
marginLeft: 15,
|
||||
marginRight: 0
|
||||
},
|
||||
centeredInputTextStyle: {
|
||||
textAlign: 'center',
|
||||
color: colors.black
|
||||
},
|
||||
inputTextStyle: {
|
||||
width: '100%',
|
||||
color: colors.black
|
||||
},
|
||||
centeredTextBoxStyles: {
|
||||
textAlign: 'center',
|
||||
flex: 1,
|
||||
width: '100%',
|
||||
color: colors.black
|
||||
},
|
||||
nameTextStyle: {
|
||||
fontSize: 22,
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
textBoxStyles: {
|
||||
flex: 1,
|
||||
width: '100%',
|
||||
borderBottomWidth: Platform.OS === 'android' ? 1 : 0,
|
||||
borderColor: colors.middleGrey,
|
||||
color: colors.black
|
||||
},
|
||||
formInputStyle: {
|
||||
color: colors.black
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* If you add any items here be sure to update them in the ContactCreateScreen,
|
||||
* ContactEditScreen, ContactImportScreen, ContactActions, and ContactFormReducer
|
||||
**/
|
||||
const mapStateToProps = (state) => {
|
||||
const {
|
||||
contactBizCardImg,
|
||||
contactAvatarImg,
|
||||
firstName,
|
||||
lastName,
|
||||
fullName,
|
||||
headline,
|
||||
location,
|
||||
titleAndCompany,
|
||||
primaryPhone,
|
||||
secondaryPhone,
|
||||
workPhone,
|
||||
extension,
|
||||
fax,
|
||||
contactEmail,
|
||||
website,
|
||||
street,
|
||||
address2,
|
||||
city,
|
||||
region,
|
||||
postalCode,
|
||||
country,
|
||||
notes,
|
||||
contactCreateDate,
|
||||
error
|
||||
} = state.contactForm;
|
||||
|
||||
return {
|
||||
contactBizCardImg,
|
||||
contactAvatarImg,
|
||||
firstName,
|
||||
lastName,
|
||||
fullName,
|
||||
headline,
|
||||
location,
|
||||
titleAndCompany,
|
||||
primaryPhone,
|
||||
secondaryPhone,
|
||||
workPhone,
|
||||
extension,
|
||||
fax,
|
||||
contactEmail,
|
||||
website,
|
||||
street,
|
||||
address2,
|
||||
city,
|
||||
region,
|
||||
postalCode,
|
||||
country,
|
||||
notes,
|
||||
contactCreateDate,
|
||||
error
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
contactFormReset, contactUpdate
|
||||
})(ContactForm);
|
||||
1673
src/components/forms/EventForm.js
Normal file
1493
src/components/forms/ProfileForm.js
Normal file
714
src/components/forms/SocialForm.js
Normal file
@ -0,0 +1,714 @@
|
||||
/** Contains the base fields for Social Display **/
|
||||
import React, { Component } from 'react';
|
||||
import { Animated, Dimensions, Linking, ScrollView, Share, View } from 'react-native';
|
||||
import { Card, Divider, Icon } from 'react-native-elements';
|
||||
import { connect } from 'react-redux';
|
||||
import { socialFormReset } from '../../actions';
|
||||
import ProfileAvatar from '../ProfileAvatar';
|
||||
import BizCard from '../BizCard';
|
||||
import { FormLabel, Keyword, Spacer } from '../common';
|
||||
import { SOCIAL_FORM } from '../FormTypes';
|
||||
import colors from '../../config/colors.json';
|
||||
import privateKeys from '../../../private_keys.json';
|
||||
import { strings } from '../../locales/i18n';
|
||||
|
||||
const SCREEN_WIDTH = Dimensions.get('window').width;
|
||||
const DOTS_SIZE = ['first', 'second'];
|
||||
const DEV_LINKING_BASE_URL = privateKeys.deepLinking.devBaseUrl;
|
||||
const TAGFER_BASE_URL = privateKeys.deepLinking.tagferBaseUrl;
|
||||
const scrollX = new Animated.Value(0);
|
||||
|
||||
class SocialForm extends Component {
|
||||
|
||||
/**
|
||||
* Component Will Unmount
|
||||
**/
|
||||
componentWillUnmount() {
|
||||
this.props.socialFormReset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the normal hardware process for sharing something via social, sms, etc
|
||||
* uid - the uid for the profile to be shared
|
||||
**/
|
||||
shareLink(tagferId, uid) {
|
||||
Share.share(
|
||||
{
|
||||
message: `Add me on Tagfer! ${DEV_LINKING_BASE_URL}${tagferId}/${uid}`,
|
||||
title: 'Add me on Tagfer!',
|
||||
url: `${TAGFER_BASE_URL}${tagferId}/${uid}`
|
||||
},
|
||||
{
|
||||
tintColor: 0x056ecf,
|
||||
excludedActivityTypes: [
|
||||
'com.apple.UIKit.activity.PostToWeibo',
|
||||
'com.apple.UIKit.activity.Print',
|
||||
'com.apple.UIKit.activity.CopyToPasteboard',
|
||||
'com.apple.UIKit.activity.AssignToContact',
|
||||
'com.apple.UIKit.activity.SaveToCameraRoll',
|
||||
'com.apple.UIKit.activity.AddToReadingList',
|
||||
'com.apple.UIKit.activity.PostToFlickr',
|
||||
'com.apple.UIKit.activity.PostToVimeo',
|
||||
'com.apple.UIKit.activity.PostToTencentWeibo',
|
||||
'com.apple.UIKit.activity.AirDrop',
|
||||
'com.apple.UIKit.activity.OpenInIBooks',
|
||||
'com.apple.UIKit.activity.MarkupAsPDF',
|
||||
'com.apple.reminders.RemindersEditorExtension', //Reminders
|
||||
'com.apple.mobilenotes.SharingExtension', // Notes
|
||||
'com.apple.mobileslideshow.StreamShareService', // iCloud Photo Sharing - This also does nothing :{
|
||||
|
||||
// Not supported
|
||||
'com.linkedin.LinkedIn.ShareExtension', //LinkedIn
|
||||
'pinterest.ShareExtension', //Pinterest
|
||||
'com.google.GooglePlus.ShareExtension', //Google +
|
||||
'com.tumblr.tumblr.Share-With-Tumblr', //Tumblr
|
||||
'wefwef.YammerShare', //Yammer
|
||||
'com.hootsuite.hootsuite.HootsuiteShareExt', //HootSuite
|
||||
'net.naan.TwitterFonPro.ShareExtension-Pro', //Echofon
|
||||
'net.whatsapp.WhatsApp.ShareExtension' //WhatsApp
|
||||
]
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the avatar and biz card slider
|
||||
**/
|
||||
renderAvatarBizCardSlider() {
|
||||
const position = Animated.divide(scrollX, SCREEN_WIDTH);
|
||||
|
||||
if (this.props.isAddMode) {
|
||||
return (
|
||||
<View>
|
||||
<Spacer />
|
||||
{this.renderDetailsSection()}
|
||||
<Spacer />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View>
|
||||
<ScrollView
|
||||
horizontal
|
||||
pagingEnabled
|
||||
showsHorizontalScrollIndicator={false}
|
||||
onScroll={Animated.event(
|
||||
[{ nativeEvent: { contentOffset: { x: scrollX } } }]
|
||||
)}
|
||||
scrollEventThrottle={16}
|
||||
>
|
||||
<View style={styles.slideStyle}>
|
||||
<ProfileAvatar
|
||||
{...this.props}
|
||||
formType={SOCIAL_FORM}
|
||||
isEditMode={this.props.isEditMode}
|
||||
/>
|
||||
{this.renderDetailsSection()}
|
||||
</View>
|
||||
|
||||
<View style={styles.slideStyle}>
|
||||
<BizCard
|
||||
formType={SOCIAL_FORM}
|
||||
isEditMode={this.props.isEditMode}
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
<View style={styles.dotsStyle}>
|
||||
{DOTS_SIZE.map((_, i) => {
|
||||
const opacity = position.interpolate({
|
||||
inputRange: [i - 1, i, i + 1],
|
||||
outputRange: [0.3, 1, 0.3],
|
||||
extrapolate: 'clamp'
|
||||
});
|
||||
|
||||
return (
|
||||
<Animated.View
|
||||
key={i}
|
||||
style={{ opacity, height: 10, width: 10, backgroundColor: '#595959', margin: 8, borderRadius: 5 }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
</View>
|
||||
|
||||
{this.renderConnectionButtons()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the top section under the avatar
|
||||
**/
|
||||
renderDetailsSection() {
|
||||
if (this.props.isAddMode) {
|
||||
return (
|
||||
<View style={{ flex: 1, flexDirection: 'column', alignItems: 'center', marginLeft: -10 }}>
|
||||
<FormLabel bold labelText={`@${this.props.navigation.state.params.tagferId}`} textStyle={{ color: colors.darkGreen, fontSize: 18 }} />
|
||||
<FormLabel bold labelText={this.props.fullName} textStyle={{ fontSize: 22 }} />
|
||||
<FormLabel bold labelText={this.props.headline} textStyle={{ fontSize: 15 }} />
|
||||
<FormLabel bold labelText={this.props.titleAndCompany} textStyle={{ fontSize: 15 }} />
|
||||
<FormLabel bold labelText={this.props.location} textStyle={{ fontSize: 15 }} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View style={{ flex: 1, flexDirection: 'column', alignItems: 'center', marginLeft: -10 }}>
|
||||
<FormLabel bold labelText={`@${this.props.navigation.state.params.otherTagferId}`} textStyle={{ color: colors.darkGreen, fontSize: 18 }} />
|
||||
<FormLabel bold labelText={this.props.fullName} textStyle={{ fontSize: 22 }} />
|
||||
<FormLabel bold labelText={this.props.headline} textStyle={{ fontSize: 15 }} />
|
||||
<FormLabel bold labelText={this.props.titleAndCompany} textStyle={{ fontSize: 15 }} />
|
||||
<FormLabel bold labelText={this.props.location} textStyle={{ fontSize: 15 }} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the various buttons for this connection
|
||||
**/
|
||||
renderConnectionButtons() {
|
||||
if (this.props.isAddMode) {
|
||||
return <Divider style={styles.dividerStyle} />;
|
||||
}
|
||||
const { otherUserId, myFriendId, otherFriendId } = this.props.navigation.state.params;
|
||||
// NORMAL MODE
|
||||
return (
|
||||
<View>
|
||||
<Divider style={styles.dividerStyle} />
|
||||
|
||||
<View style={styles.iconViewContainerStyle}>
|
||||
<Icon
|
||||
name={'phone'}
|
||||
type="simple-line-icon"
|
||||
size={30}
|
||||
color={colors.darkGreen}
|
||||
containerStyle={styles.iconStyle}
|
||||
onPress={() => Linking.openURL(`tel:+${this.props.primaryPhone}`)}
|
||||
/>
|
||||
<Icon
|
||||
name={'action-redo'}
|
||||
type="simple-line-icon"
|
||||
size={30}
|
||||
color={colors.buttonBlue}
|
||||
containerStyle={styles.iconStyle}
|
||||
onPress={() => this.shareLink(this.props.tagferId, this.props.userId)}
|
||||
/>
|
||||
<Icon
|
||||
name={'speech'}
|
||||
type="simple-line-icon"
|
||||
size={30}
|
||||
color={colors.darkBlue}
|
||||
containerStyle={styles.iconStyle}
|
||||
onPress={() => this.props.navigation.navigate('messageScreen', { otherUserId, myFriendId, otherFriendId })}
|
||||
/>
|
||||
<Icon
|
||||
name={'note'}
|
||||
type="simple-line-icon"
|
||||
size={30}
|
||||
color={colors.orange}
|
||||
containerStyle={styles.iconStyle}
|
||||
onPress={() => this.props.navigation.navigate('socialNotesScreen', { friendId: myFriendId })}
|
||||
/>
|
||||
</View>
|
||||
<Divider style={styles.dividerStyle} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the About section
|
||||
**/
|
||||
renderAboutSection() {
|
||||
if (this.props.isAddMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NORMAL MODE
|
||||
return (
|
||||
<Card>
|
||||
<FormLabel bold labelText={strings('common.contactFields.about_labelText')} />
|
||||
<FormLabel labelText={this.props.about} />
|
||||
|
||||
<FormLabel bold labelText={strings('common.contactFields.howIHelp_labelText_social')} />
|
||||
<FormLabel labelText={this.props.howIHelp} />
|
||||
|
||||
<FormLabel bold labelText={strings('common.contactFields.whatINeed_labelText_social')} />
|
||||
<FormLabel labelText={this.props.whatINeed} />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the Experience section
|
||||
**/
|
||||
renderExperienceSection() {
|
||||
if (this.props.isAddMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NORMAL MODE
|
||||
let isAddedExperienceEmpty = true;
|
||||
for (let i = 0; i < this.props.education.length; i++) {
|
||||
if (!isAddedExperienceEmpty) {
|
||||
break;
|
||||
}
|
||||
for (const property in this.props.education[i]) {
|
||||
if (this.props.education[i][property].length > 0) {
|
||||
isAddedExperienceEmpty = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAddedExperienceEmpty) {
|
||||
const experienceList = this.props.education.map((data, index) => {
|
||||
if (index > 0) {
|
||||
return (
|
||||
<View key={index}>
|
||||
<Divider style={styles.dividerStyle} />
|
||||
<FormLabel labelText={data.jobTitle} />
|
||||
<FormLabel labelText={data.jobCompany} />
|
||||
<FormLabel labelText={data.jobDates} />
|
||||
<FormLabel labelText={data.jobLocation} />
|
||||
<FormLabel labelText={data.jobSummary} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View key={index}>
|
||||
<FormLabel labelText={data.jobTitle} />
|
||||
<FormLabel labelText={data.jobCompany} />
|
||||
<FormLabel labelText={data.jobDates} />
|
||||
<FormLabel labelText={data.jobLocation} />
|
||||
<FormLabel labelText={data.jobSummary} />
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<FormLabel bold labelText={strings('common.contactFields.experience_labelText')} />
|
||||
{experienceList}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the Education section
|
||||
**/
|
||||
renderEducationSection() {
|
||||
if (this.props.isAddMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NORMAL MODE
|
||||
let isAddedEducationEmpty = true;
|
||||
for (let i = 0; i < this.props.education.length; i++) {
|
||||
if (!isAddedEducationEmpty) {
|
||||
break;
|
||||
}
|
||||
for (const property in this.props.education[i]) {
|
||||
if (this.props.education[i][property].length > 0) {
|
||||
isAddedEducationEmpty = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAddedEducationEmpty) {
|
||||
const educationList = this.props.education.map((data, index) => {
|
||||
if (index > 0) {
|
||||
return (
|
||||
<View key={index}>
|
||||
<Divider style={styles.dividerStyle} />
|
||||
<FormLabel labelText={data.school} />
|
||||
<FormLabel labelText={data.degree} />
|
||||
<FormLabel labelText={data.fieldOfStudy} />
|
||||
<FormLabel labelText={data.schoolDates} />
|
||||
<FormLabel labelText={data.educationSummary} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View key={index}>
|
||||
<FormLabel labelText={data.school} />
|
||||
<FormLabel labelText={data.degree} />
|
||||
<FormLabel labelText={data.fieldOfStudy} />
|
||||
<FormLabel labelText={data.schoolDates} />
|
||||
<FormLabel labelText={data.educationSummary} />
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<FormLabel bold labelText={strings('common.contactFields.education_labelText')} />
|
||||
{educationList}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the primary phone section
|
||||
**/
|
||||
renderPrimaryPhoneSection() {
|
||||
if (this.props.isAddMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NORMAL MODE
|
||||
const { primaryPhone } = this.props;
|
||||
if (!primaryPhone) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<FormLabel bold labelText={strings('common.contactFields.phone_primary_labelText')} />
|
||||
<FormLabel labelText={this.props.primaryPhone} />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the secondary phone section
|
||||
**/
|
||||
renderSecondaryPhoneSection() {
|
||||
if (this.props.isAddMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NORMAL MODE
|
||||
const { secondaryPhone } = this.props;
|
||||
if (!secondaryPhone) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<FormLabel bold labelText={strings('common.contactFields.phone_secondary_labelText')} />
|
||||
<FormLabel labelText={this.props.secondaryPhone} />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the work phone section
|
||||
**/
|
||||
renderWorkPhoneSection() {
|
||||
if (this.props.isAddMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NORMAL MODE
|
||||
const { workPhone, extension } = this.props;
|
||||
if (!workPhone && !extension) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<FormLabel bold labelText={strings('common.contactFields.phone_work_labelText')} />
|
||||
<FormLabel labelText={this.props.workPhone} />
|
||||
|
||||
<FormLabel bold labelText={strings('common.contactFields.extension_labelText')} />
|
||||
<FormLabel labelText={this.props.extension} />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the fax phone section
|
||||
**/
|
||||
renderFaxSection() {
|
||||
if (this.props.isAddMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NORMAL MODE
|
||||
const { fax } = this.props;
|
||||
if (!fax) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<FormLabel bold labelText={strings('common.contactFields.fax_labelText')} />
|
||||
<FormLabel labelText={this.props.fax} />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the email section
|
||||
**/
|
||||
renderEmailSection() {
|
||||
if (this.props.isAddMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NORMAL MODE
|
||||
const { profileEmail } = this.props;
|
||||
if (!profileEmail) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<FormLabel bold labelText={strings('common.contactFields.email_labelText')} />
|
||||
<FormLabel labelText={this.props.profileEmail} />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the website section
|
||||
**/
|
||||
renderWebsiteSection() {
|
||||
if (this.props.isAddMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NORMAL MODE
|
||||
const { website } = this.props;
|
||||
if (!website) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<FormLabel bold labelText={strings('common.contactFields.website_labelText')} />
|
||||
<FormLabel labelText={this.props.website} />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the address section
|
||||
**/
|
||||
renderAddressSection() {
|
||||
if (this.props.isAddMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NORMAL MODE
|
||||
const { street, address2, city, region, postalCode, country } = this.props;
|
||||
if (!street && !address2 && !city && !region && !postalCode && !country) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<FormLabel bold labelText={strings('common.contactFields.address_labelText')} />
|
||||
<FormLabel labelText={this.props.street} />
|
||||
<FormLabel labelText={this.props.address2} />
|
||||
<FormLabel labelText={this.props.city} />
|
||||
<FormLabel labelText={this.props.region} />
|
||||
<FormLabel labelText={this.props.postalCode} />
|
||||
<FormLabel labelText={this.props.country} />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the keyword section
|
||||
**/
|
||||
renderKeywordSection() {
|
||||
if (!this.props.navigation.state.params.keywords || this.props.navigation.state.params.keywords.length === 0) {
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<Card>
|
||||
<View style={styles.keywordViewStyle}>
|
||||
{this.renderKeywords()}
|
||||
</View>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders each keyword onto the screen
|
||||
**/
|
||||
renderKeywords() {
|
||||
if (!this.props.keywords || !this.props.navigation.state.params.keywords) {
|
||||
return;
|
||||
}
|
||||
let keywordArray = [];
|
||||
if (this.props.isAddMode) {
|
||||
keywordArray = this.props.navigation.state.params.keywords.map((data, index) => {
|
||||
return (
|
||||
<Keyword
|
||||
key={index}
|
||||
title={data}
|
||||
index={index}
|
||||
/>
|
||||
);
|
||||
});
|
||||
} else {
|
||||
keywordArray = Object.values(this.props.keywords).map((data, index) => {
|
||||
return (
|
||||
<Keyword
|
||||
key={index}
|
||||
title={data}
|
||||
index={index}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
return keywordArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<View style={styles.whiteBackground}>
|
||||
{this.renderAvatarBizCardSlider()}
|
||||
</View>
|
||||
{this.renderKeywordSection()}
|
||||
|
||||
{this.renderAboutSection()}
|
||||
{this.renderExperienceSection()}
|
||||
{this.renderEducationSection()}
|
||||
|
||||
{this.renderPrimaryPhoneSection()}
|
||||
{this.renderSecondaryPhoneSection()}
|
||||
{this.renderWorkPhoneSection()}
|
||||
{this.renderFaxSection()}
|
||||
|
||||
{this.renderEmailSection()}
|
||||
{this.renderWebsiteSection()}
|
||||
|
||||
{this.renderAddressSection()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
slideStyle: {
|
||||
width: SCREEN_WIDTH,
|
||||
},
|
||||
whiteBackground: {
|
||||
backgroundColor: colors.white
|
||||
},
|
||||
dotsStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: colors.white
|
||||
},
|
||||
dividerStyle: {
|
||||
backgroundColor: colors.offWhite,
|
||||
marginTop: 10,
|
||||
marginBottom: 15
|
||||
},
|
||||
iconViewContainerStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
},
|
||||
iconStyle: {
|
||||
marginLeft: 20,
|
||||
marginRight: 20
|
||||
},
|
||||
keywordViewStyle: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* If you add any items here be sure to update the lists in
|
||||
* SocialAddScreen, SocialEditScreen, SocialActions, and SocialReducer
|
||||
**/
|
||||
const mapStateToProps = (state) => {
|
||||
const {
|
||||
socialBizCardImg,
|
||||
socialAvatarImg,
|
||||
profileName,
|
||||
titleAndCompany,
|
||||
firstName,
|
||||
lastName,
|
||||
fullName,
|
||||
headline,
|
||||
location,
|
||||
profileEmail,
|
||||
primaryPhone,
|
||||
secondaryPhone,
|
||||
extension,
|
||||
about,
|
||||
howIHelp,
|
||||
whatINeed,
|
||||
experience,
|
||||
education,
|
||||
workPhone,
|
||||
fax,
|
||||
website,
|
||||
street,
|
||||
address2,
|
||||
city,
|
||||
region,
|
||||
postalCode,
|
||||
country,
|
||||
keywords,
|
||||
referralId,
|
||||
userId,
|
||||
profileId,
|
||||
friendId,
|
||||
tagferId,
|
||||
loading,
|
||||
error,
|
||||
success
|
||||
} = state.socialContacts;
|
||||
|
||||
return {
|
||||
socialBizCardImg,
|
||||
socialAvatarImg,
|
||||
profileName,
|
||||
titleAndCompany,
|
||||
firstName,
|
||||
lastName,
|
||||
fullName,
|
||||
headline,
|
||||
location,
|
||||
profileEmail,
|
||||
primaryPhone,
|
||||
secondaryPhone,
|
||||
extension,
|
||||
about,
|
||||
howIHelp,
|
||||
whatINeed,
|
||||
experience,
|
||||
education,
|
||||
workPhone,
|
||||
fax,
|
||||
website,
|
||||
street,
|
||||
address2,
|
||||
city,
|
||||
region,
|
||||
postalCode,
|
||||
country,
|
||||
keywords,
|
||||
referralId,
|
||||
userId,
|
||||
profileId,
|
||||
friendId,
|
||||
tagferId,
|
||||
loading,
|
||||
error,
|
||||
success
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
socialFormReset
|
||||
})(SocialForm);
|
||||
85
src/components/forms/TaskForm.js
Normal file
@ -0,0 +1,85 @@
|
||||
/** Contains the base fields for Task reuse **/
|
||||
import React, { Component } from 'react';
|
||||
import { Platform, View } from 'react-native';
|
||||
import { FormInput } from 'react-native-elements';
|
||||
import { connect } from 'react-redux';
|
||||
import { FormMessage, FormLabel } from '../common/';
|
||||
import { taskFormReset, taskUpdate } from '../../actions';
|
||||
import colors from '../../config/colors.json';
|
||||
import { strings } from '../../locales/i18n';
|
||||
|
||||
class TaskForm extends Component {
|
||||
/**
|
||||
* Component Will Unmount
|
||||
**/
|
||||
componentWillUnmount() {
|
||||
this.props.taskFormReset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<FormLabel required labelText={strings('forms.task.taskName_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.taskName}
|
||||
placeholder={strings('forms.task.taskName_placeholder')}
|
||||
onChangeText={value => this.props.taskUpdate({ prop: 'taskName', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
inputStyle={styles.inputTextStyle}
|
||||
/>
|
||||
|
||||
<FormLabel required labelText={strings('forms.task.taskDescription_labelText')} />
|
||||
<FormInput
|
||||
value={this.props.taskDescription}
|
||||
placeholder={strings('forms.task.taskDescription_placeholder')}
|
||||
onChangeText={value => this.props.taskUpdate({ prop: 'taskDescription', value })}
|
||||
autoCorrect={false}
|
||||
autoCapitalize='none'
|
||||
multiline
|
||||
numberOfLines={4}
|
||||
inputStyle={styles.textBoxStyles}
|
||||
/>
|
||||
<FormMessage error={this.props.error} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
textBoxStyles: {
|
||||
flex: 1,
|
||||
width: '100%',
|
||||
borderBottomWidth: Platform.OS === 'android' ? 1 : 0,
|
||||
borderColor: colors.middleGrey,
|
||||
color: colors.black
|
||||
},
|
||||
inputTextStyle: {
|
||||
color: colors.black
|
||||
}
|
||||
};
|
||||
|
||||
// If you add any items here be sure to update the lists in
|
||||
// TaskEditScreen, TaskCreateScreen, TaskActions, and TaskFormReducer
|
||||
const mapStateToProps = (state) => {
|
||||
const {
|
||||
taskName,
|
||||
taskDescription,
|
||||
taskCreateDate,
|
||||
error
|
||||
} = state.taskForm;
|
||||
|
||||
return {
|
||||
taskName,
|
||||
taskDescription,
|
||||
taskCreateDate,
|
||||
error
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
taskFormReset, taskUpdate
|
||||
})(TaskForm);
|
||||
87
src/components/lists/CoHostListItem.js
Normal file
@ -0,0 +1,87 @@
|
||||
/** Pure component for each Co-Host Item **/
|
||||
import React from 'react';
|
||||
import { Image, Text, View } from 'react-native';
|
||||
import { CheckBox, ListItem } from 'react-native-elements';
|
||||
import colors from '../../config/colors.json';
|
||||
|
||||
class CoHostListItem extends React.PureComponent {
|
||||
/**
|
||||
* Creates the contact avatar
|
||||
**/
|
||||
renderAvatar({ item }) {
|
||||
return (
|
||||
<Image
|
||||
source={{ uri: item.profileAvatarImg }}
|
||||
style={styles.avatarImageStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
const { isChecked, item, onPress } = this.props;
|
||||
|
||||
const title = `@${item.tagferId}`;
|
||||
const subTitle = (
|
||||
<View>
|
||||
<Text>{item.fullName || ''}</Text>
|
||||
<Text>{item.titleAndCompany || ''}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
if (item.profileAvatarImg) {
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={item.uid}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
titleStyle={{ fontWeight: 'bold', marginBottom: 5, color: colors.darkGreen }}
|
||||
leftIcon={this.renderAvatar({ item })}
|
||||
onPress={onPress}
|
||||
rightIcon={
|
||||
<CheckBox
|
||||
checked={isChecked}
|
||||
containerStyle={{ backgroundColor: colors.transparent, borderWidth: 0 }}
|
||||
onPress={onPress}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={item.key}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
titleStyle={{ fontWeight: 'bold', marginBottom: 5, color: colors.darkGreen }}
|
||||
leftIcon={{ name: 'account-circle', size: 50, color: colors.lightGrey }}
|
||||
onPress={onPress}
|
||||
rightIcon={
|
||||
<CheckBox
|
||||
checked={isChecked}
|
||||
containerStyle={{ backgroundColor: colors.transparent, borderWidth: 0 }}
|
||||
onPress={onPress}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
avatarImageStyle: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderWidth: 1,
|
||||
borderRadius: 20,
|
||||
marginLeft: 5,
|
||||
marginRight: 10
|
||||
}
|
||||
};
|
||||
|
||||
export default CoHostListItem;
|
||||
73
src/components/lists/ContactImportListItem.js
Normal file
@ -0,0 +1,73 @@
|
||||
/** Pure component for each Contact Import Item **/
|
||||
import React from 'react';
|
||||
import { Image } from 'react-native';
|
||||
import { CheckBox, ListItem } from 'react-native-elements';
|
||||
import colors from '../../config/colors.json';
|
||||
|
||||
class ContactImportListItem extends React.PureComponent {
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
const { isChecked, item, onPress } = this.props;
|
||||
|
||||
const title = `${item.fullName}`;
|
||||
const subTitle = `${item.jobTitle} ${item.company}`;
|
||||
|
||||
if (item.contactAvatarImg) {
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={item.key}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
leftIcon={
|
||||
<Image
|
||||
source={{ uri: item.contactAvatarImg }}
|
||||
style={styles.avatarImageStyle}
|
||||
/>
|
||||
}
|
||||
onPress={onPress}
|
||||
rightIcon={
|
||||
<CheckBox
|
||||
checked={isChecked}
|
||||
containerStyle={{ backgroundColor: colors.transparent, borderWidth: 0 }}
|
||||
onPress={onPress}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={item.key}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
leftIcon={{ name: 'account-circle', size: 50, color: colors.lightGrey }}
|
||||
onPress={onPress}
|
||||
rightIcon={
|
||||
<CheckBox
|
||||
checked={isChecked}
|
||||
containerStyle={{ backgroundColor: colors.transparent, borderWidth: 0 }}
|
||||
onPress={onPress}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
avatarImageStyle: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderWidth: 1,
|
||||
borderRadius: 20,
|
||||
marginLeft: 5,
|
||||
marginRight: 10
|
||||
}
|
||||
};
|
||||
|
||||
export default ContactImportListItem;
|
||||
117
src/components/lists/ContactItem.js
Normal file
@ -0,0 +1,117 @@
|
||||
/** Pure component for each Contact Item **/
|
||||
import React from 'react';
|
||||
import { Image, Text, View } from 'react-native';
|
||||
import { Icon, ListItem } from 'react-native-elements';
|
||||
import colors from '../../config/colors.json';
|
||||
|
||||
class ContactItem extends React.PureComponent {
|
||||
/**
|
||||
* When a contact is selected it goes to the edit contact screen
|
||||
**/
|
||||
onContactSelect(rowData) {
|
||||
this.props.navigation.navigate('contactEdit', rowData);
|
||||
}
|
||||
|
||||
/**
|
||||
* When a friend is selected it goes to the edit friend screen
|
||||
**/
|
||||
onFriendSelect(rowData) {
|
||||
this.props.navigation.navigate('socialEditScreen', rowData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the contact avatar
|
||||
**/
|
||||
renderAvatar({ item }) {
|
||||
if (item.otherProfileId) {
|
||||
if (item.profileAvatarImg) {
|
||||
return (
|
||||
<Image
|
||||
source={{ uri: item.profileAvatarImg }}
|
||||
style={styles.avatarImageStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Icon
|
||||
name='account-circle'
|
||||
size={50}
|
||||
color={colors.lightGrey}
|
||||
/>
|
||||
);
|
||||
}
|
||||
// Phone contact
|
||||
if (item.contactAvatarImg) {
|
||||
return (
|
||||
<Image
|
||||
source={{ uri: item.contactAvatarImg }}
|
||||
style={styles.avatarImageStyle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Icon
|
||||
name='account-circle'
|
||||
size={50}
|
||||
color={colors.lightGrey}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render method
|
||||
**/
|
||||
render() {
|
||||
const item = this.props.item;
|
||||
let title = '';
|
||||
let subTitle = '';
|
||||
|
||||
if (item.otherProfileId) {
|
||||
title = `@${item.tagferId}`;
|
||||
subTitle = (
|
||||
<View>
|
||||
<Text>{item.fullName || ''}</Text>
|
||||
<Text>{item.titleAndCompany || ''}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={item.uid}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
leftIcon={this.renderAvatar({ item })}
|
||||
onPress={() => this.onFriendSelect(item)}
|
||||
titleStyle={{ fontWeight: 'bold', marginBottom: 5, color: colors.darkGreen }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
title = `${item.fullName}`;
|
||||
subTitle = `${item.titleAndCompany || ''}`;
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
roundAvatar
|
||||
key={item.uid}
|
||||
title={title}
|
||||
subtitle={subTitle}
|
||||
leftIcon={this.renderAvatar({ item })}
|
||||
onPress={() => this.onContactSelect(item)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = {
|
||||
avatarImageStyle: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderWidth: 1,
|
||||
borderRadius: 20,
|
||||
marginLeft: 5,
|
||||
marginRight: 10
|
||||
}
|
||||
};
|
||||
|
||||
export default ContactItem;
|
||||