From 58ce7fcaf35564e81f3ca016ecf72575d9714802 Mon Sep 17 00:00:00 2001 From: Jared Poelman Date: Fri, 7 Oct 2016 13:31:34 -0400 Subject: [PATCH] Release React SDK version 1.0.0 --- .eslintrc.json | 34 + .gitignore | 6 +- AppboyProject/.buckconfig | 6 + AppboyProject/.flowconfig | 63 +- AppboyProject/.gitignore | 8 + AppboyProject/AppboyProject.js | 259 +++++-- AppboyProject/android/app/BUCK | 66 ++ AppboyProject/android/app/build.gradle | 33 +- AppboyProject/android/app/proguard-rules.pro | 17 +- .../android/app/src/main/AndroidManifest.xml | 26 +- .../AppboyBroadcastReceiver.java | 84 --- .../java/com/appboyproject/MainActivity.java | 90 +-- .../com/appboyproject/MainApplication.java | 48 ++ .../app/src/main/res/values/appboy.xml | 4 +- .../app/src/main/res/values/strings.xml | 2 +- AppboyProject/android/build.gradle | 2 + AppboyProject/android/keystores/BUCK | 8 + AppboyProject/android/settings.gradle | 2 +- AppboyProject/index.android.js | 8 +- AppboyProject/index.ios.js | 8 +- .../AppboyProject.xcodeproj/project.pbxproj | 167 +++-- .../ios/AppboyProject/ABKReactBridgeTest.h | 6 - .../ios/AppboyProject/ABKReactBridgeTest.m | 13 - AppboyProject/ios/AppboyProject/AppDelegate.h | 3 +- AppboyProject/ios/AppboyProject/AppDelegate.m | 85 ++- .../AppboyProject/AppboyProject.entitlements | 8 + .../AppIcon.appiconset/Contents.json | 10 + AppboyProject/ios/AppboyProject/Info.plist | 37 +- .../AppboyProjectTests/AppboyProjectTests.m | 2 +- .../ios/AppboyProjectTests/Info.plist | 2 +- AppboyProject/ios/Podfile | 5 +- AppboyProject/ios/Podfile.lock | 18 +- AppboyProject/ios/fastlane/Appfile | 7 + AppboyProject/ios/fastlane/Fastfile | 30 + AppboyProject/ios/fastlane/Matchfile | 13 + AppboyProject/package.json | 4 +- CHANGELOG.md | 11 + android/build.gradle | 2 +- .../appboy/reactbridge/AppboyReactBridge.java | 281 ++++++-- .../project.pbxproj | 10 +- .../AppboyReactBridge/AppboyReactBridge.h | 2 +- .../AppboyReactBridge/AppboyReactBridge.m | 145 +++- .../AppboyReactBridge/AppboyReactUtils.h | 11 + .../AppboyReactBridge/AppboyReactUtils.m | 33 + index.js | 648 +++++++++++------- package.json | 16 +- 46 files changed, 1580 insertions(+), 763 deletions(-) create mode 100644 .eslintrc.json create mode 100644 AppboyProject/.buckconfig create mode 100644 AppboyProject/android/app/BUCK delete mode 100644 AppboyProject/android/app/src/main/java/com/appboyproject/AppboyBroadcastReceiver.java create mode 100644 AppboyProject/android/app/src/main/java/com/appboyproject/MainApplication.java create mode 100644 AppboyProject/android/keystores/BUCK delete mode 100644 AppboyProject/ios/AppboyProject/ABKReactBridgeTest.h delete mode 100644 AppboyProject/ios/AppboyProject/ABKReactBridgeTest.m create mode 100644 AppboyProject/ios/AppboyProject/AppboyProject.entitlements create mode 100644 AppboyProject/ios/fastlane/Appfile create mode 100644 AppboyProject/ios/fastlane/Fastfile create mode 100644 AppboyProject/ios/fastlane/Matchfile create mode 100644 iOS/AppboyReactBridge/AppboyReactBridge/AppboyReactUtils.h create mode 100644 iOS/AppboyReactBridge/AppboyReactBridge/AppboyReactUtils.m diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..d22e2f7 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,34 @@ +{ + "extends": [ + "standard", + "standard-react" + ], + "plugins": [ + "standard", + "promise", + "react" + ], + "parserOptions": { + "ecmaFeatures": { + "jsx": true + } + }, + "rules": { + // Override default settings + "semi": [ + "warn", + "always" + ], + "no-unused-vars": [ + "warn", + { + "vars": "local", + "args": "none" + } + ], + "space-before-function-paren": [ + "warn", + "never" + ] + } +} diff --git a/.gitignore b/.gitignore index 4f46c89..82df1cf 100644 --- a/.gitignore +++ b/.gitignore @@ -32,5 +32,9 @@ node_modules # CocoaPods Pods +#fastlane +AppboyProject/ios/fastlane/README.md +AppboyProject/ios/fastlane/report.xml + # Emacs -*~ \ No newline at end of file +*~ diff --git a/AppboyProject/.buckconfig b/AppboyProject/.buckconfig new file mode 100644 index 0000000..934256c --- /dev/null +++ b/AppboyProject/.buckconfig @@ -0,0 +1,6 @@ + +[android] + target = Google Inc.:Google APIs:23 + +[maven_repositories] + central = https://repo1.maven.org/maven2 diff --git a/AppboyProject/.flowconfig b/AppboyProject/.flowconfig index 245c23a..b69d071 100644 --- a/AppboyProject/.flowconfig +++ b/AppboyProject/.flowconfig @@ -1,65 +1,58 @@ [ignore] # We fork some components by platform. -.*/*.web.js .*/*.android.js -# Some modules have their own node_modules with overlap -.*/node_modules/node-haste/.* +# Ignore templates with `@flow` in header +.*/local-cli/generator.* -# Ugh -.*/node_modules/babel.* -.*/node_modules/babylon.* -.*/node_modules/invariant.* +# Ignore malformed json +.*/node_modules/y18n/test/.*\.json -# Ignore react and fbjs where there are overlaps, but don't ignore -# anything that react-native relies on -.*/node_modules/fbjs/lib/Map.js -.*/node_modules/fbjs/lib/Promise.js -.*/node_modules/fbjs/lib/fetch.js -.*/node_modules/fbjs/lib/ExecutionEnvironment.js -.*/node_modules/fbjs/lib/isEmpty.js -.*/node_modules/fbjs/lib/crc32.js -.*/node_modules/fbjs/lib/ErrorUtils.js +# Ignore the website subdir +/website/.* -# Flow has a built-in definition for the 'react' module which we prefer to use -# over the currently-untyped source -.*/node_modules/react/react.js -.*/node_modules/react/lib/React.js -.*/node_modules/react/lib/ReactDOM.js +# Ignore BUCK generated dirs +/\.buckd/ -# Ignore commoner tests -.*/node_modules/commoner/test/.* +# Ignore unexpected extra @providesModule +.*/node_modules/commoner/test/source/widget/share.js -# See https://github.com/facebook/flow/issues/442 -.*/react-tools/node_modules/commoner/lib/reader.js - -# Ignore jest -.*/node_modules/jest-cli/.* - -# Ignore Website -.*/website/.* +# Ignore 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 +.*/Libraries/react-native/ReactNative.js +.*/node_modules/jest-runtime/build/__tests__/.* [include] [libs] node_modules/react-native/Libraries/react-native/react-native-interface.js +node_modules/react-native/flow +flow/ [options] module.system=haste +esproposal.class_static_fields=enable +esproposal.class_instance_fields=enable + +experimental.strict_type_args=true + munge_underscores=true module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' -module.name_mapper='^[./a-zA-Z0-9$_-]+\.png$' -> 'RelativeImageStub' +module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' suppress_type=$FlowIssue suppress_type=$FlowFixMe suppress_type=$FixMe -suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-0]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) -suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-0]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(30\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(30\\|1[0-9]\\|[1-2][0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy +unsafe.enable_getters_and_setters=true + [version] -0.20.1 +^0.30.0 diff --git a/AppboyProject/.gitignore b/AppboyProject/.gitignore index 94fc867..d80bdca 100644 --- a/AppboyProject/.gitignore +++ b/AppboyProject/.gitignore @@ -24,6 +24,7 @@ project.xcworkspace # Android/IJ # +*.iml .idea .gradle local.properties @@ -32,3 +33,10 @@ local.properties # node_modules/ npm-debug.log + +# BUCK +buck-out/ +\.buckd/ +android/app/libs +android/keystores/debug.keystore +android/keystores/debug.keystore.properties diff --git a/AppboyProject/AppboyProject.js b/AppboyProject/AppboyProject.js index 556bf07..cb67da6 100644 --- a/AppboyProject/AppboyProject.js +++ b/AppboyProject/AppboyProject.js @@ -1,97 +1,185 @@ 'use strict'; -import React, { - AppRegistry, - Component, +import React, { Component } from 'react'; +import { StyleSheet, Text, - View + View, + TouchableHighlight, + Linking, + Alert } from 'react-native'; -var Button = require('react-native-button'); - const ReactAppboy = require('react-native-appboy-sdk'); class AppboyProject extends Component { + constructor(props) { + super(props); + this.state = {}; + this._updateCardCount = this._updateCardCount.bind(this); + } + + componentDidMount() { + console.log('componentDidMount called'); + + // Listen to the `url` event to handle incoming deep links + Linking.addEventListener('url', this._handleOpenUrl); + + // No `url` event is triggered on application start, so this handles + // the case where a deep link launches the application + Linking.getInitialURL().then((url) => { + if (url) { + console.log('Linking.getInitialURL is ' + url); + this._handleOpenUrl({url}); + } + }).catch(err => console.error('Error getting initial URL', err)); + + // Handles deep links when an iOS app is launched from hard close via push click. + // Note that this isn't handled by Linking.getInitialURL(), as the app is + // launched not from a deep link, but from clicking on the push notification. + // For more detail, see `ReactAppboy.getInitialURL` in `index.js`. + var that = this; + ReactAppboy.getInitialURL(function(url) { + if (url) { + console.log('ReactAppboy.getInitialURL is ' + url); + that._handleOpenUrl({url}); + } + }); + } + + componentWillUnmount() { + // Remove `url` event handler + Linking.removeEventListener('url', this._handleOpenUrl); + } + + _handleOpenUrl(event) { + console.log('handleOpenUrl called on url ' + event.url); + Alert.alert( + 'Deep Link', + event.url, + [ + {text: 'OK', onPress: () => console.log('OK Pressed')} + ] + ); + } + + _updateCardCount() { + ReactAppboy.getUnreadCardCountForCategories(ReactAppboy.CardCategory.ALL, (err, res) => { + if (err) { + console.log('getUnreadCardCountForCategories returned error ' + err); + } else if (res != null) { + this.setState({unreadCardCount: res}); + } + }); + ReactAppboy.getCardCountForCategories(ReactAppboy.CardCategory.ALL, (err, res) => { + if (err) { + console.log('getCardCountForCategories returned error ' + err); + } else if (res != null) { + this.setState({cardCount: res}); + } + }); + } + render() { return ( - - - - - - - - - - - + Remove From Custom Attribute Array + + + Increment Custom Attribute Array + + + Set Twitter Data + + + Set Facebook Data + + + Unread Cards (Click to Refresh): {this.state.unreadCardCount} / {this.state.cardCount} + ); } _changeUserPress(event) { - ReactAppboy.changeUser("theAppboyTestUser"); + ReactAppboy.changeUser('theAppboyTestUser'); } _logCustomEventPress(event) { - ReactAppboy.logCustomEvent("reactCustomEvent", {"p1" : "p2"}); + ReactAppboy.logCustomEvent('reactCustomEvent', {'p1': 'p2'}); } _logPurchasePress(event) { - ReactAppboy.logPurchase("reactProductIdentifier", "1.2", "USD", 2, {"pp1" : "pp2"}); + ReactAppboy.logPurchase('reactProductIdentifier', '1.2', 'USD', 2, {'pp1': 'pp2'}); } _submitFeedbackPress(event) { - ReactAppboy.submitFeedback("test@test.com", "great app asdf", true); + ReactAppboy.submitFeedback('test@test.com', 'great app asdf', true); } _logCustomAttributePress(event) { - ReactAppboy.setCustomUserAttribute("sk", "sv"); - ReactAppboy.setCustomUserAttribute("doubleattr", 4.5); - ReactAppboy.setCustomUserAttribute("intattr", 88); - ReactAppboy.setCustomUserAttribute("booleanattr", true); - ReactAppboy.setCustomUserAttribute("dateattr", new Date()); - ReactAppboy.setCustomUserAttribute("arrayattr", ["a", "b"]); + ReactAppboy.setCustomUserAttribute('sk', 'sv'); + ReactAppboy.setCustomUserAttribute('doubleattr', 4.5); + ReactAppboy.setCustomUserAttribute('intattr', 88); + ReactAppboy.setCustomUserAttribute('booleanattr', true); + ReactAppboy.setCustomUserAttribute('dateattr', new Date()); + ReactAppboy.setCustomUserAttribute('arrayattr', ['a', 'b']); } _logUserPropertiesPress(event) { - ReactAppboy.setFirstName("Brian"); - ReactAppboy.setLastName("Wheeler"); - ReactAppboy.setEmail("brian+react@appboy.com"); + ReactAppboy.setFirstName('Brian'); + ReactAppboy.setLastName('Wheeler'); + ReactAppboy.setEmail('brian+react@appboy.com'); ReactAppboy.setDateOfBirth(1987, 9, 21); - ReactAppboy.setCountry("USA"); - ReactAppboy.setHomeCity("New York"); - ReactAppboy.setGender(ReactAppboy.Genders.MALE); - ReactAppboy.setPhoneNumber("9085555555"); - ReactAppboy.setAvatarImageUrl("https://raw.githubusercontent.com/Appboy/appboy-android-sdk/master/Appboy_Logo_400x100.png"); + ReactAppboy.setCountry('USA'); + ReactAppboy.setHomeCity('New York'); + ReactAppboy.setGender(ReactAppboy.Genders.MALE, (err, res) => { + if (err) { + console.log('Example callback error is ' + err); + } else { + console.log('Example callback result is ' + res); + } + }); + ReactAppboy.setPhoneNumber('9085555555'); + ReactAppboy.setAvatarImageUrl('https://raw.githubusercontent.com/Appboy/appboy-android-sdk/master/Appboy_Logo_400x100.png'); ReactAppboy.setEmailNotificationSubscriptionType(ReactAppboy.NotificationSubscriptionTypes.UNSUBSCRIBED); ReactAppboy.setPushNotificationSubscriptionType(ReactAppboy.NotificationSubscriptionTypes.SUBSCRIBED); } @@ -102,14 +190,53 @@ class AppboyProject extends Component { ReactAppboy.launchFeedback(); } _unsetCustomUserAttributePress(event) { - ReactAppboy.unsetCustomUserAttribute("sk"); + ReactAppboy.unsetCustomUserAttribute('sk'); } _addToCustomAttributeArrayPress(event) { - ReactAppboy.addToCustomUserAttributeArray("myArray", "arrayValue1"); - ReactAppboy.addToCustomUserAttributeArray("myArray", "arrayValue2"); + ReactAppboy.addToCustomUserAttributeArray('myArray', 'arrayValue1'); + ReactAppboy.addToCustomUserAttributeArray('myArray', 'arrayValue2'); } _removeFromCustomAttributeArrayPress(event) { - ReactAppboy.removeFromCustomUserAttributeArray("myArray", "arrayValue1"); + ReactAppboy.removeFromCustomUserAttributeArray('myArray', 'arrayValue1'); + } + _incrementCustomAttributePress(event) { + ReactAppboy.incrementCustomUserAttribute('intattr', 5); + } + _setTwitterData(event) { + ReactAppboy.setTwitterData(6253282, 'billmag', 'Bill', 'Adventurer', 700, 200, 1000, + 'https://si0.twimg.com/profile_images/2685532587/fa47382ad67a0135acc62d4c6b49dbdc_bigger.jpeg'); + } + _setFacebookData(event) { + var profile = { + id: '708379', + first_name: 'Bill', + last_name: 'Mag', + location: { + name: 'new york' + }, + age_range: { + min: 21, max: 31 + }, + email: 'bill@m.ag', + bio: 'adventurer', + gender: 'male', + birthday: '01/01/2016' + }; + // May also be a list of strings, e.g. below: + // var likes = ["Messiaen", "Durufle", "Buxtehude"]; + var likes = [ + { + 'name': 'Hot Rabbit', + 'id': '199600656843963', + 'created_time': '2016-09-25T17:05:01+0000' + }, + { + 'name': 'This Bridge Called Our Health', + 'id': '543928779075283', + 'created_time': '2016-09-24T20:43:01+0000' + } + ]; + ReactAppboy.setFacebookData(profile, 500, likes); } } @@ -118,18 +245,18 @@ const styles = StyleSheet.create({ flex: 1, justifyContent: 'center', alignItems: 'center', - backgroundColor: '#F5FCFF', + backgroundColor: '#F5FCFF' }, welcome: { fontSize: 20, textAlign: 'center', - margin: 10, + margin: 10 }, instructions: { textAlign: 'center', color: '#333333', - marginBottom: 5, - }, + marginBottom: 5 + } }); -export default AppboyProject; \ No newline at end of file +export default AppboyProject; diff --git a/AppboyProject/android/app/BUCK b/AppboyProject/android/app/BUCK new file mode 100644 index 0000000..2d2d823 --- /dev/null +++ b/AppboyProject/android/app/BUCK @@ -0,0 +1,66 @@ +import re + +# To learn about Buck see [Docs](https://buckbuild.com/). +# To run your application with Buck: +# - install Buck +# - `npm start` - to start the packager +# - `cd android` +# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` +# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck +# - `buck install -r android/app` - compile, install and run application +# + +lib_deps = [] +for jarfile in glob(['libs/*.jar']): + name = 'jars__' + re.sub(r'^.*/([^/]+)\.jar$', r'\1', jarfile) + lib_deps.append(':' + name) + prebuilt_jar( + name = name, + binary_jar = jarfile, + ) + +for aarfile in glob(['libs/*.aar']): + name = 'aars__' + re.sub(r'^.*/([^/]+)\.aar$', r'\1', aarfile) + lib_deps.append(':' + name) + android_prebuilt_aar( + name = name, + aar = aarfile, + ) + +android_library( + name = 'all-libs', + exported_deps = lib_deps +) + +android_library( + name = 'app-code', + srcs = glob([ + 'src/main/java/**/*.java', + ]), + deps = [ + ':all-libs', + ':build_config', + ':res', + ], +) + +android_build_config( + name = 'build_config', + package = 'com.appboyproject', +) + +android_resource( + name = 'res', + res = 'src/main/res', + package = 'com.appboyproject', +) + +android_binary( + name = 'app', + package_type = 'debug', + manifest = 'src/main/AndroidManifest.xml', + keystore = '//android/keystores:debug', + deps = [ + ':app-code', + ], +) diff --git a/AppboyProject/android/app/build.gradle b/AppboyProject/android/app/build.gradle index 8526f39..db0fded 100644 --- a/AppboyProject/android/app/build.gradle +++ b/AppboyProject/android/app/build.gradle @@ -9,7 +9,7 @@ import com.android.build.OutputFile * 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: "react.gradle"` line. + * `apply from: "../../node_modules/react-native/react.gradle"` line. * * project.ext.react = [ * // the name of the generated asset file containing your JS bundle @@ -26,7 +26,9 @@ import com.android.build.OutputFile * * // 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 is in the format 'bundleIn${productFlavor}${buildType}' + * // The configuration property can be in the following formats + * // 'bundleIn${productFlavor}${buildType}' + * // 'bundleIn${buildType}' * // bundleInFreeDebug: true, * // bundleInPaidRelease: true, * // bundleInBeta: true, @@ -53,11 +55,17 @@ import com.android.build.OutputFile * // date; if you have any other folders that you want to ignore for performance reasons (gradle * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ * // for example, you might want to remove it from here. - * inputExcludes: ["android/**", "ios/**"] + * inputExcludes: ["android/**", "ios/**"], + * + * // override which node gets called and with what additional arguments + * nodeExecutableAndArgs: ["node"] + * + * // supply additional arguments to the packager + * extraPackagerArgs: [] * ] */ -apply from: "react.gradle" +apply from: "../../node_modules/react-native/react.gradle" /** * Set this to true to create two separate APKs instead of one: @@ -72,7 +80,7 @@ def enableSeparateBuildPerCPUArchitecture = false /** * Run Proguard to shrink the Java bytecode in release builds. */ -def enableProguardInReleaseBuilds = true +def enableProguardInReleaseBuilds = false android { compileSdkVersion 23 @@ -90,9 +98,9 @@ android { } splits { abi { - enable enableSeparateBuildPerCPUArchitecture - universalApk false // Also generate an universal APK reset() + enable enableSeparateBuildPerCPUArchitecture + universalApk false // If true, also generate a universal APK include "armeabi-v7a", "x86" } } @@ -118,8 +126,15 @@ android { } dependencies { + compile project(':react-native-appboy-sdk') compile fileTree(dir: "libs", include: ["*.jar"]) compile "com.android.support:appcompat-v7:23.0.1" - compile "com.facebook.react:react-native:0.19.+" - compile project(':react-native-appboy-sdk') + compile "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' } diff --git a/AppboyProject/android/app/proguard-rules.pro b/AppboyProject/android/app/proguard-rules.pro index 3584026..48361a9 100644 --- a/AppboyProject/android/app/proguard-rules.pro +++ b/AppboyProject/android/app/proguard-rules.pro @@ -26,11 +26,14 @@ # See http://sourceforge.net/p/proguard/bugs/466/ -keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip -keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters +-keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip # Do not strip any method/class that is annotated with @DoNotStrip -keep @com.facebook.proguard.annotations.DoNotStrip class * +-keep @com.facebook.common.internal.DoNotStrip class * -keepclassmembers class * { @com.facebook.proguard.annotations.DoNotStrip *; + @com.facebook.common.internal.DoNotStrip *; } -keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { @@ -42,8 +45,8 @@ -keep class * extends com.facebook.react.bridge.NativeModule { *; } -keepclassmembers,includedescriptorclasses class * { native ; } -keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; } --keepclassmembers class * { @com.facebook.react.uimanager.ReactProp ; } --keepclassmembers class * { @com.facebook.react.uimanager.ReactPropGroup ; } +-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; } +-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; } -dontwarn com.facebook.react.** @@ -51,9 +54,9 @@ -keepattributes Signature -keepattributes *Annotation* --keep class com.squareup.okhttp.** { *; } --keep interface com.squareup.okhttp.** { *; } --dontwarn com.squareup.okhttp.** +-keep class okhttp3.** { *; } +-keep interface okhttp3.** { *; } +-dontwarn okhttp3.** # okio @@ -61,7 +64,3 @@ -dontwarn java.nio.file.* -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement -dontwarn okio.** - -# stetho - --dontwarn com.facebook.stetho.** diff --git a/AppboyProject/android/app/src/main/AndroidManifest.xml b/AppboyProject/android/app/src/main/AndroidManifest.xml index 6ff0345..10b1960 100644 --- a/AppboyProject/android/app/src/main/AndroidManifest.xml +++ b/AppboyProject/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,7 @@ + package="com.appboyproject" + android:versionCode="1" + android:versionName="1.0"> @@ -8,20 +10,20 @@ + + + + - - - - - - - @@ -33,6 +35,14 @@ android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenSize"> + + + 
 + + 
 + 
 + 
 + diff --git a/AppboyProject/android/app/src/main/java/com/appboyproject/AppboyBroadcastReceiver.java b/AppboyProject/android/app/src/main/java/com/appboyproject/AppboyBroadcastReceiver.java deleted file mode 100644 index 00d4d3d..0000000 --- a/AppboyProject/android/app/src/main/java/com/appboyproject/AppboyBroadcastReceiver.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.appboyproject; -import android.content.ActivityNotFoundException; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.support.v4.app.TaskStackBuilder; -import android.util.Log; -import android.widget.Toast; -import com.appboy.AppboyGcmReceiver; -import com.appboy.Constants; -import com.appboy.push.AppboyNotificationUtils; - -public class AppboyBroadcastReceiver extends BroadcastReceiver { - private static final String TAG = String.format("%s.%s", Constants.APPBOY_LOG_TAG_PREFIX, AppboyBroadcastReceiver.class.getName()); - public static final String SOURCE_KEY = "source"; - public static final String DESTINATION_VIEW = "destination"; - public static final String HOME = "home"; - public static final String FEED = "feed"; - public static final String FEEDBACK = "feedback"; - - @Override - public void onReceive(Context context, Intent intent) { - String packageName = context.getPackageName(); - String pushReceivedAction = packageName + AppboyNotificationUtils.APPBOY_NOTIFICATION_RECEIVED_SUFFIX; - String notificationOpenedAction = packageName + AppboyNotificationUtils.APPBOY_NOTIFICATION_OPENED_SUFFIX; - String action = intent.getAction(); - Log.d(TAG, String.format("Received intent with action %s", action)); - - if (pushReceivedAction.equals(action)) { - Log.d(TAG, "Received push notification."); - if (AppboyNotificationUtils.isUninstallTrackingPush(intent.getExtras())) { - Log.d(TAG, "Got uninstall tracking push"); - } - } else if (notificationOpenedAction.equals(action)) { - if (intent.getBooleanExtra(Constants.APPBOY_ACTION_IS_CUSTOM_ACTION_KEY, false)) { - Toast.makeText(context, "You clicked a Droidboy custom action!", Toast.LENGTH_LONG).show(); - } else { - Bundle extras = getPushExtrasBundle(intent); - - // If a deep link exists, start an ACTION_VIEW intent pointing at the deep link. - // The intent returned from getStartActivityIntent() is placed on the back stack. - // Otherwise, start the intent defined in getStartActivityIntent(). - String deepLink = intent.getStringExtra(Constants.APPBOY_PUSH_DEEP_LINK_KEY); - if (deepLink != null && deepLink.length() > 0) { - Intent uriIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(deepLink)) - .putExtras(extras); - TaskStackBuilder stackBuilder = TaskStackBuilder.create(context); - stackBuilder.addNextIntent(getStartActivityIntent(context, extras)); - stackBuilder.addNextIntent(uriIntent); - try { - stackBuilder.startActivities(extras); - } catch (ActivityNotFoundException e) { - Log.w(TAG, String.format("Could not find appropriate activity to open for deep link %s.", deepLink)); - } - } else { - context.startActivity(getStartActivityIntent(context, extras)); - } - } - } else { - Log.d(TAG, String.format("Ignoring intent with unsupported action %s", action)); - } - } - - private Intent getStartActivityIntent(Context context, Bundle extras) { - Intent startActivityIntent = new Intent(context, com.appboyproject.MainActivity.class); - startActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); - if (extras != null) { - startActivityIntent.putExtras(extras); - } - return startActivityIntent; - } - - private Bundle getPushExtrasBundle(Intent intent) { - Bundle extras = intent.getBundleExtra(Constants.APPBOY_PUSH_EXTRAS_KEY); - if (extras == null) { - extras = new Bundle(); - } - extras.putString(AppboyGcmReceiver.CAMPAIGN_ID_KEY, intent.getStringExtra(AppboyGcmReceiver.CAMPAIGN_ID_KEY)); - extras.putString(SOURCE_KEY, Constants.APPBOY); - return extras; - } -} \ No newline at end of file diff --git a/AppboyProject/android/app/src/main/java/com/appboyproject/MainActivity.java b/AppboyProject/android/app/src/main/java/com/appboyproject/MainActivity.java index 1f36e99..6b45201 100644 --- a/AppboyProject/android/app/src/main/java/com/appboyproject/MainActivity.java +++ b/AppboyProject/android/app/src/main/java/com/appboyproject/MainActivity.java @@ -1,86 +1,40 @@ package com.appboyproject; -import com.appboy.reactbridge.AppboyReactPackage; -import com.appboy.ui.inappmessage.AppboyInAppMessageManager; +import com.appboy.Constants; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; +import android.widget.Toast; + import com.facebook.react.ReactActivity; import com.facebook.react.ReactPackage; import com.facebook.react.shell.MainReactPackage; import java.util.Arrays; import java.util.List; -import com.appboy.*; public class MainActivity extends ReactActivity { - - private boolean mRefreshData; + private static final String TAG = String.format("%s.%s", Constants.APPBOY_LOG_TAG_PREFIX, MainActivity.class.getName()); @Override - public void onStart() { - super.onStart(); - // Opens (or reopens) an Appboy session. - // Note: This must be called in the onStart lifecycle method of EVERY Activity. Failure to do so - // will result in incomplete and/or erroneous analytics. - if (Appboy.getInstance(this).openSession(this)) { - mRefreshData = true; + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Intent intent = getIntent(); + Uri data = intent.getData(); + if (data != null) { + Toast.makeText(this, "Activity opened by deep link: " + data.toString(), Toast.LENGTH_LONG); + Log.i(TAG, "Deep link is " + data.toString()); } } - @Override - public void onResume() { - super.onResume(); - // Registers the AppboyInAppMessageManager for the current Activity. This Activity will now listen for - // in-app messages from Appboy. - AppboyInAppMessageManager.getInstance().registerInAppMessageManager(this); - if (mRefreshData) { - Appboy.getInstance(this).requestInAppMessageRefresh(); - mRefreshData = false; - } - } - - @Override - public void onPause() { - super.onPause(); - // Unregisters the AppboyInAppMessageManager. - AppboyInAppMessageManager.getInstance().unregisterInAppMessageManager(this); - } - - @Override - public void onStop() { - super.onStop(); - // Closes the current Appboy session. - // Note: This must be called in the onStop lifecycle method of EVERY Activity. Failure to do so - // will result in incomplete and/or erroneous analytics. - Appboy.getInstance(this).closeSession(this); - } - - /** - * Returns the name of the main component registered from JavaScript. - * This is used to schedule rendering of the component. - */ - @Override - protected String getMainComponentName() { - return "AppboyProject"; - } - - /** - * Returns whether dev mode should be enabled. - * This enables e.g. the dev menu. - */ - @Override - protected boolean getUseDeveloperSupport() { - return BuildConfig.DEBUG; - } - - /** - * A list of packages used by the app. If the app uses additional views - * or modules besides the default ones, add more packages here. + * Returns the name of the main component registered from JavaScript. + * This is used to schedule rendering of the component. */ - @Override - protected List getPackages() { - return Arrays.asList( - new MainReactPackage(), - new AppboyReactPackage() - ); - } + @Override + protected String getMainComponentName() { + return "AppboyProject"; + } } diff --git a/AppboyProject/android/app/src/main/java/com/appboyproject/MainApplication.java b/AppboyProject/android/app/src/main/java/com/appboyproject/MainApplication.java new file mode 100644 index 0000000..ae33d59 --- /dev/null +++ b/AppboyProject/android/app/src/main/java/com/appboyproject/MainApplication.java @@ -0,0 +1,48 @@ +package com.appboyproject; + +import com.appboy.AppboyLifecycleCallbackListener; +import com.appboy.support.AppboyLogger; + +import android.app.Application; +import android.util.Log; + +import com.appboy.reactbridge.AppboyReactPackage; + +import com.facebook.react.ReactApplication; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.ReactNativeHost; +import com.facebook.react.ReactPackage; +import com.facebook.react.shell.MainReactPackage; + +import java.util.Arrays; +import java.util.List; + +public class MainApplication extends Application implements ReactApplication { + + private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { + @Override + protected boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + @Override + protected List getPackages() { + return Arrays.asList( + new MainReactPackage(), + new AppboyReactPackage() + ); + } + }; + + @Override + public ReactNativeHost getReactNativeHost() { + return mReactNativeHost; + } + + @Override + public void onCreate() { + super.onCreate(); + registerActivityLifecycleCallbacks(new AppboyLifecycleCallbackListener()); + AppboyLogger.LogLevel = Log.VERBOSE; + } +} diff --git a/AppboyProject/android/app/src/main/res/values/appboy.xml b/AppboyProject/android/app/src/main/res/values/appboy.xml index 7ecf38a..194d34b 100644 --- a/AppboyProject/android/app/src/main/res/values/appboy.xml +++ b/AppboyProject/android/app/src/main/res/values/appboy.xml @@ -12,4 +12,6 @@ true - \ No newline at end of file + + true + diff --git a/AppboyProject/android/app/src/main/res/values/strings.xml b/AppboyProject/android/app/src/main/res/values/strings.xml index 66bf586..284bf1a 100644 --- a/AppboyProject/android/app/src/main/res/values/strings.xml +++ b/AppboyProject/android/app/src/main/res/values/strings.xml @@ -1,3 +1,3 @@ - AppboyProject + AppboyProject diff --git a/AppboyProject/android/build.gradle b/AppboyProject/android/build.gradle index 00c1ac6..33dd0a4 100644 --- a/AppboyProject/android/build.gradle +++ b/AppboyProject/android/build.gradle @@ -7,6 +7,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:1.3.1' + // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -16,6 +17,7 @@ allprojects { repositories { mavenLocal() jcenter() + maven { url "$rootDir/../node_modules/react-native/android" } maven { url "http://appboy.github.io/appboy-android-sdk/sdk" } } } diff --git a/AppboyProject/android/keystores/BUCK b/AppboyProject/android/keystores/BUCK new file mode 100644 index 0000000..15da20e --- /dev/null +++ b/AppboyProject/android/keystores/BUCK @@ -0,0 +1,8 @@ +keystore( + name = 'debug', + store = 'debug.keystore', + properties = 'debug.keystore.properties', + visibility = [ + 'PUBLIC', + ], +) diff --git a/AppboyProject/android/settings.gradle b/AppboyProject/android/settings.gradle index 30e6f20..46bf780 100644 --- a/AppboyProject/android/settings.gradle +++ b/AppboyProject/android/settings.gradle @@ -1,5 +1,5 @@ rootProject.name = 'AppboyProject' include ':app' -include ':react-native-appboy-sdk', ':app' +include ':react-native-appboy-sdk' project(':react-native-appboy-sdk').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-appboy-sdk/android') diff --git a/AppboyProject/index.android.js b/AppboyProject/index.android.js index 9d86a13..316b350 100644 --- a/AppboyProject/index.android.js +++ b/AppboyProject/index.android.js @@ -1,8 +1,6 @@ 'use strict'; -import React, { - AppRegistry, - Component, -} from 'react-native'; -import AppboyProject from './AppboyProject.js' +import React, { Component } from 'react'; +import { AppRegistry } from 'react-native'; +import AppboyProject from './AppboyProject.js'; AppRegistry.registerComponent('AppboyProject', () => AppboyProject); diff --git a/AppboyProject/index.ios.js b/AppboyProject/index.ios.js index 9d86a13..316b350 100644 --- a/AppboyProject/index.ios.js +++ b/AppboyProject/index.ios.js @@ -1,8 +1,6 @@ 'use strict'; -import React, { - AppRegistry, - Component, -} from 'react-native'; -import AppboyProject from './AppboyProject.js' +import React, { Component } from 'react'; +import { AppRegistry } from 'react-native'; +import AppboyProject from './AppboyProject.js'; AppRegistry.registerComponent('AppboyProject', () => AppboyProject); diff --git a/AppboyProject/ios/AppboyProject.xcodeproj/project.pbxproj b/AppboyProject/ios/AppboyProject.xcodeproj/project.pbxproj index 1174470..5ffd021 100644 --- a/AppboyProject/ios/AppboyProject.xcodeproj/project.pbxproj +++ b/AppboyProject/ios/AppboyProject.xcodeproj/project.pbxproj @@ -20,11 +20,12 @@ 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 140ED2AC1D01E1AD002B40FF /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; + 1DA5D9E2AA7F49CA60EC2670 /* libPods-AppboyProject.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7ECEFC4BC79A5D45D47A89D4 /* libPods-AppboyProject.a */; }; + 7D5B300ECB3A4CF691D5583A /* libAppboyReactBridge.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C1D4AEF94E4C4F97B933C154 /* libAppboyReactBridge.a */; }; 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; - B12F8F47477087D8753A79BE /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 76C6C42EBC65368551414ED8 /* libPods.a */; }; - E3D0A7151C6675ED0027828A /* ABKReactBridgeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E3D0A7141C6675ED0027828A /* ABKReactBridgeTest.m */; }; - E3D0A7781C668CFE0027828A /* libAppboyReactBridge.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E3D0A76B1C668BD00027828A /* libAppboyReactBridge.a */; }; + D4019E771D8A354400846FA6 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D4019E761D8A354400846FA6 /* UserNotifications.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -105,9 +106,9 @@ remoteGlobalIDString = 58B5119B1A9E6C1200147676; remoteInfo = RCTText; }; - E3D0A76A1C668BD00027828A /* PBXContainerItemProxy */ = { + D48C6D1B1D8A28D200E8A9D8 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = E3D0A7661C668BD00027828A /* AppboyReactBridge.xcodeproj */; + containerPortal = 2984E68720884F9D90152271 /* AppboyReactBridge.xcodeproj */; proxyType = 2; remoteGlobalIDString = E3D0A75A1C668BB90027828A; remoteInfo = AppboyReactBridge; @@ -124,7 +125,6 @@ 00E356EE1AD99517003FC87E /* AppboyProjectTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AppboyProjectTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* AppboyProjectTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppboyProjectTests.m; sourceTree = ""; }; - 075D35F998776457EBB108CE /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = "../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj"; sourceTree = ""; }; 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = "../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj"; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* AppboyProject.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppboyProject.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -135,13 +135,20 @@ 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = AppboyProject/Info.plist; sourceTree = ""; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = AppboyProject/main.m; sourceTree = ""; }; 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "../node_modules/react-native/React/React.xcodeproj"; sourceTree = ""; }; - 76C6C42EBC65368551414ED8 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 2984E68720884F9D90152271 /* AppboyReactBridge.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = AppboyReactBridge.xcodeproj; path = "../node_modules/react-native-appboy-sdk/iOS/AppboyReactBridge/AppboyReactBridge.xcodeproj"; sourceTree = ""; }; 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; }; - 7B9ADEEED13F2DC01C3C4973 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; + 7ECEFC4BC79A5D45D47A89D4 /* libPods-AppboyProject.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AppboyProject.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; }; + D43518111D822335001F8197 /* AppboyProject.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = AppboyProject.entitlements; path = AppboyProject/AppboyProject.entitlements; sourceTree = ""; }; + E21FD3253E3B26792BB515DB /* Pods-AppboyProject.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppboyProject.release.xcconfig"; path = "Pods/Target Support Files/Pods-AppboyProject/Pods-AppboyProject.release.xcconfig"; sourceTree = ""; }; E3D0A7131C6675ED0027828A /* ABKReactBridgeTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ABKReactBridgeTest.h; path = AppboyProject/ABKReactBridgeTest.h; sourceTree = ""; }; E3D0A7141C6675ED0027828A /* ABKReactBridgeTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ABKReactBridgeTest.m; path = AppboyProject/ABKReactBridgeTest.m; sourceTree = ""; }; E3D0A7661C668BD00027828A /* AppboyReactBridge.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = AppboyReactBridge.xcodeproj; path = ../../iOS/AppboyReactBridge/AppboyReactBridge.xcodeproj; sourceTree = ""; }; + 89AF20DA22F2FB68F8B3B394 /* Pods-AppboyProject.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppboyProject.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AppboyProject/Pods-AppboyProject.debug.xcconfig"; sourceTree = ""; }; + AF8878D90BC4A6E3BAF2FDDA /* Pods-AppboyProject.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppboyProject.release.xcconfig"; path = "Pods/Target Support Files/Pods-AppboyProject/Pods-AppboyProject.release.xcconfig"; sourceTree = ""; }; + C1D4AEF94E4C4F97B933C154 /* libAppboyReactBridge.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libAppboyReactBridge.a; sourceTree = ""; }; + D4019E761D8A354400846FA6 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; + D426E1191D8A1BA1000AE162 /* AppboyProject.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = AppboyProject.entitlements; path = AppboyProject/AppboyProject.entitlements; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -149,6 +156,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 140ED2AC1D01E1AD002B40FF /* libReact.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -156,6 +164,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D4019E771D8A354400846FA6 /* UserNotifications.framework in Frameworks */, 146834051AC3E58100842450 /* libReact.a in Frameworks */, 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */, 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */, @@ -166,8 +175,8 @@ 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */, 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */, 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */, - E3D0A7781C668CFE0027828A /* libAppboyReactBridge.a in Frameworks */, - B12F8F47477087D8753A79BE /* libPods.a in Frameworks */, + 1DA5D9E2AA7F49CA60EC2670 /* libPods-AppboyProject.a in Frameworks */, + 7D5B300ECB3A4CF691D5583A /* libAppboyReactBridge.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -250,6 +259,7 @@ 13B07FAE1A68108700A75B9A /* AppboyProject */ = { isa = PBXGroup; children = ( + D426E1191D8A1BA1000AE162 /* AppboyProject.entitlements */, 008F07F21AC5B25A0029DE68 /* main.jsbundle */, 13B07FAF1A68108700A75B9A /* AppDelegate.h */, 13B07FB01A68108700A75B9A /* AppDelegate.m */, @@ -257,8 +267,6 @@ 13B07FB61A68108700A75B9A /* Info.plist */, 13B07FB11A68108700A75B9A /* LaunchScreen.xib */, 13B07FB71A68108700A75B9A /* main.m */, - E3D0A7131C6675ED0027828A /* ABKReactBridgeTest.h */, - E3D0A7141C6675ED0027828A /* ABKReactBridgeTest.m */, ); name = AppboyProject; sourceTree = ""; @@ -271,10 +279,11 @@ name = Products; sourceTree = ""; }; - 584D1B49863EB46E0BCA69D7 /* Frameworks */ = { + 3248FC2E1DBB174C0EC1770B /* Frameworks */ = { isa = PBXGroup; children = ( - 76C6C42EBC65368551414ED8 /* libPods.a */, + D4019E761D8A354400846FA6 /* UserNotifications.framework */, + 7ECEFC4BC79A5D45D47A89D4 /* libPods-AppboyProject.a */, ); name = Frameworks; sourceTree = ""; @@ -290,7 +299,6 @@ 832341AE1AAA6A7D00B99B32 /* Libraries */ = { isa = PBXGroup; children = ( - E3D0A7661C668BD00027828A /* AppboyReactBridge.xcodeproj */, 146833FF1AC3E56700842450 /* React.xcodeproj */, 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */, 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */, @@ -301,6 +309,7 @@ 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */, 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */, 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */, + 2984E68720884F9D90152271 /* AppboyReactBridge.xcodeproj */, ); name = Libraries; sourceTree = ""; @@ -320,8 +329,8 @@ 832341AE1AAA6A7D00B99B32 /* Libraries */, 00E356EF1AD99517003FC87E /* AppboyProjectTests */, 83CBBA001A601CBA00E9B192 /* Products */, - C3FD105F924D150662132845 /* Pods */, - 584D1B49863EB46E0BCA69D7 /* Frameworks */, + E73349AAB4AC7801A8CC9224 /* Pods */, + 3248FC2E1DBB174C0EC1770B /* Frameworks */, ); indentWidth = 2; sourceTree = ""; @@ -336,21 +345,21 @@ name = Products; sourceTree = ""; }; - C3FD105F924D150662132845 /* Pods */ = { + D48C6D181D8A28D200E8A9D8 /* Products */ = { isa = PBXGroup; children = ( - 075D35F998776457EBB108CE /* Pods.debug.xcconfig */, - 7B9ADEEED13F2DC01C3C4973 /* Pods.release.xcconfig */, + D48C6D1C1D8A28D200E8A9D8 /* libAppboyReactBridge.a */, ); - name = Pods; + name = Products; sourceTree = ""; }; - E3D0A7671C668BD00027828A /* Products */ = { + E73349AAB4AC7801A8CC9224 /* Pods */ = { isa = PBXGroup; children = ( - E3D0A76B1C668BD00027828A /* libAppboyReactBridge.a */, + 89AF20DA22F2FB68F8B3B394 /* Pods-AppboyProject.debug.xcconfig */, + AF8878D90BC4A6E3BAF2FDDA /* Pods-AppboyProject.release.xcconfig */, ); - name = Products; + name = Pods; sourceTree = ""; }; /* End PBXGroup section */ @@ -378,13 +387,13 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "AppboyProject" */; buildPhases = ( - 13CD1B56ADC8A6F004BF5D4E /* Check Pods Manifest.lock */, + 9D446A5F1782E915B9AEBBF1 /* [CP] Check Pods Manifest.lock */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - CDD4A9243BB74869836ABD31 /* Embed Pods Frameworks */, - C68288D869D146C9AD5DA46D /* Copy Pods Resources */, + A23C2291CE03B1B39602EA70 /* [CP] Embed Pods Frameworks */, + 59FE1BD0D9D18651C9F10C6B /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -401,13 +410,22 @@ 83CBB9F71A601CBA00E9B192 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0610; + LastUpgradeCheck = 610; ORGANIZATIONNAME = Facebook; TargetAttributes = { 00E356ED1AD99517003FC87E = { CreatedOnToolsVersion = 6.2; TestTargetID = 13B07F861A680F5B00A75B9A; }; + 13B07F861A680F5B00A75B9A = { + DevelopmentTeam = 5GLZKGNWQ3; + ProvisioningStyle = Manual; + SystemCapabilities = { + com.apple.Push = { + enabled = 1; + }; + }; + }; }; }; buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "AppboyProject" */; @@ -423,8 +441,8 @@ projectDirPath = ""; projectReferences = ( { - ProductGroup = E3D0A7671C668BD00027828A /* Products */; - ProjectRef = E3D0A7661C668BD00027828A /* AppboyReactBridge.xcodeproj */; + ProductGroup = D48C6D181D8A28D200E8A9D8 /* Products */; + ProjectRef = 2984E68720884F9D90152271 /* AppboyReactBridge.xcodeproj */; }, { ProductGroup = 00C302A81ABCB8CE00DB3ED1 /* Products */; @@ -546,11 +564,11 @@ remoteRef = 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - E3D0A76B1C668BD00027828A /* libAppboyReactBridge.a */ = { + D48C6D1C1D8A28D200E8A9D8 /* libAppboyReactBridge.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; path = libAppboyReactBridge.a; - remoteRef = E3D0A76A1C668BD00027828A /* PBXContainerItemProxy */; + remoteRef = D48C6D1B1D8A28D200E8A9D8 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXReferenceProxy section */ @@ -589,49 +607,49 @@ shellPath = /bin/sh; shellScript = "export NODE_BINARY=node\n../node_modules/react-native/packager/react-native-xcode.sh"; }; - 13CD1B56ADC8A6F004BF5D4E /* Check Pods Manifest.lock */ = { + 59FE1BD0D9D18651C9F10C6B /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Check Pods Manifest.lock"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AppboyProject/Pods-AppboyProject-resources.sh\"\n"; showEnvVarsInLog = 0; }; - C68288D869D146C9AD5DA46D /* Copy Pods Resources */ = { + 9D446A5F1782E915B9AEBBF1 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; - CDD4A9243BB74869836ABD31 /* Embed Pods Frameworks */ = { + A23C2291CE03B1B39602EA70 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Embed Pods Frameworks"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n"; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-AppboyProject/Pods-AppboyProject-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -649,7 +667,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E3D0A7151C6675ED0027828A /* ABKReactBridgeTest.m in Sources */, 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */, 13B07FC11A68108700A75B9A /* main.m in Sources */, ); @@ -682,10 +699,6 @@ isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", @@ -693,7 +706,13 @@ INFOPLIST_FILE = AppboyProjectTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.2; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + ); + PRODUCT_BUNDLE_IDENTIFIER = ""; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AppboyProject.app/AppboyProject"; }; name = Debug; @@ -703,64 +722,78 @@ buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; COPY_PHASE_STRIP = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - ); INFOPLIST_FILE = AppboyProjectTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.2; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + ); + PRODUCT_BUNDLE_IDENTIFIER = ""; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AppboyProject.app/AppboyProject"; }; name = Release; }; 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 075D35F998776457EBB108CE /* Pods.debug.xcconfig */; + baseConfigurationReference = 89AF20DA22F2FB68F8B3B394 /* Pods-AppboyProject.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = AppboyProject/AppboyProject.entitlements; + CODE_SIGN_IDENTITY = "iPhone Developer: Appboy Appboy (QWFN3H2ZPW)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer: Appboy Appboy (QWFN3H2ZPW)"; DEAD_CODE_STRIPPING = NO; + DEVELOPMENT_TEAM = 5GLZKGNWQ3; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/Libraries/**", "$(SRCROOT)/../node_modules/react-native/React/**", + "$(SRCROOT)/../node_modules/react-native-appboy-sdk/iOS/AppboyReactBridge/AppboyReactBridge", ); INFOPLIST_FILE = AppboyProject/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/AppboyKit", - ); OTHER_LDFLAGS = ( - "-ObjC", "$(inherited)", + "-ObjC", + "-lc++", ); + PRODUCT_BUNDLE_IDENTIFIER = com.appboy.helloreact; PRODUCT_NAME = AppboyProject; + PROVISIONING_PROFILE = "ce7fcee4-9587-44d5-87ec-ca5381589a5d"; + PROVISIONING_PROFILE_SPECIFIER = "match Development com.appboy.helloreact"; }; name = Debug; }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7B9ADEEED13F2DC01C3C4973 /* Pods.release.xcconfig */; + baseConfigurationReference = AF8878D90BC4A6E3BAF2FDDA /* Pods-AppboyProject.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = AppboyProject/AppboyProject.entitlements; + CODE_SIGN_IDENTITY = "iPhone Distribution: Appboy Inc. (5GLZKGNWQ3)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution: Appboy Inc. (5GLZKGNWQ3)"; + DEVELOPMENT_TEAM = 5GLZKGNWQ3; HEADER_SEARCH_PATHS = ( "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/Libraries/**", "$(SRCROOT)/../node_modules/react-native/React/**", + "$(SRCROOT)/../node_modules/react-native-appboy-sdk/iOS/AppboyReactBridge/AppboyReactBridge", ); INFOPLIST_FILE = AppboyProject/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/AppboyKit", - ); OTHER_LDFLAGS = ( - "-ObjC", "$(inherited)", + "-ObjC", + "-lc++", ); + PRODUCT_BUNDLE_IDENTIFIER = com.appboy.helloreact; PRODUCT_NAME = AppboyProject; + PROVISIONING_PROFILE = "7b0a29d5-45f0-46bc-887f-dab1022629a4"; + PROVISIONING_PROFILE_SPECIFIER = "match AdHoc com.appboy.helloreact"; }; name = Release; }; @@ -781,7 +814,8 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_IDENTITY = "iPhone Developer: Appboy Appboy (QWFN3H2ZPW)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer: Appboy Appboy (QWFN3H2ZPW)"; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; @@ -802,10 +836,12 @@ "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/../node_modules/react-native/React/**", + "$(SRCROOT)/../node_modules/react-native-appboy-sdk/iOS/AppboyReactBridge/AppboyReactBridge", ); IPHONEOS_DEPLOYMENT_TARGET = 7.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; + PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = iphoneos; }; name = Debug; @@ -827,7 +863,8 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_IDENTITY = "iPhone Developer: Appboy Appboy (QWFN3H2ZPW)"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer: Appboy Appboy (QWFN3H2ZPW)"; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -842,9 +879,11 @@ "$(inherited)", /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, "$(SRCROOT)/../node_modules/react-native/React/**", + "$(SRCROOT)/../node_modules/react-native-appboy-sdk/iOS/AppboyReactBridge/AppboyReactBridge", ); IPHONEOS_DEPLOYMENT_TARGET = 7.0; MTL_ENABLE_DEBUG_INFO = NO; + PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; }; diff --git a/AppboyProject/ios/AppboyProject/ABKReactBridgeTest.h b/AppboyProject/ios/AppboyProject/ABKReactBridgeTest.h deleted file mode 100644 index 07adec7..0000000 --- a/AppboyProject/ios/AppboyProject/ABKReactBridgeTest.h +++ /dev/null @@ -1,6 +0,0 @@ -#import -#import "RCTBridgeModule.h" - -@interface ABKReactBridgeTest : NSObject - -@end diff --git a/AppboyProject/ios/AppboyProject/ABKReactBridgeTest.m b/AppboyProject/ios/AppboyProject/ABKReactBridgeTest.m deleted file mode 100644 index 64da787..0000000 --- a/AppboyProject/ios/AppboyProject/ABKReactBridgeTest.m +++ /dev/null @@ -1,13 +0,0 @@ -#import "ABKReactBridgeTest.h" -#import "RCTLog.h" - -@implementation ABKReactBridgeTest - -RCT_EXPORT_METHOD(callBridge:(NSString *)value) -{ - RCTLogInfo(@"Called bridge with value %@", value); -} - -RCT_EXPORT_MODULE(); - -@end diff --git a/AppboyProject/ios/AppboyProject/AppDelegate.h b/AppboyProject/ios/AppboyProject/AppDelegate.h index a9654d5..f4be412 100644 --- a/AppboyProject/ios/AppboyProject/AppDelegate.h +++ b/AppboyProject/ios/AppboyProject/AppDelegate.h @@ -8,8 +8,9 @@ */ #import +#import -@interface AppDelegate : UIResponder +@interface AppDelegate : UIResponder @property (nonatomic, strong) UIWindow *window; diff --git a/AppboyProject/ios/AppboyProject/AppDelegate.m b/AppboyProject/ios/AppboyProject/AppDelegate.m index 0f3e9a4..7931c5a 100644 --- a/AppboyProject/ios/AppboyProject/AppDelegate.m +++ b/AppboyProject/ios/AppboyProject/AppDelegate.m @@ -8,68 +8,66 @@ */ #import "AppDelegate.h" - +#import "RCTLinkingManager.h" +#import "RCTBundleURLProvider.h" #import "RCTRootView.h" #import "AppboyKit.h" +#import "AppboyReactUtils.h" @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - NSURL *jsCodeLocation; - - /** - * Loading JavaScript code - uncomment the one you want. - * - * OPTION 1 - * Load from development server. Start the server from the repository root: - * - * $ npm start - * - * To run on device, change `localhost` to the IP address of your computer - * (you can get this by typing `ifconfig` into the terminal and selecting the - * `inet` value under `en0:`) and make sure your computer and iOS device are - * on the same Wi-Fi network. - */ - - jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"]; - - /** - * OPTION 2 - * Load from pre-bundled file on disk. The static bundle is automatically - * generated by "Bundle React Native code and images" build step. - */ - -// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; - + NSURL *jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil]; + RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"AppboyProject" initialProperties:nil launchOptions:launchOptions]; - + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; UIViewController *rootViewController = [UIViewController new]; rootViewController.view = rootView; self.window.rootViewController = rootViewController; [self.window makeKeyAndVisible]; - - if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1) { + + // Register for user notifications + if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_9_x_Max) { + UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; + center.delegate = self; + [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge) + completionHandler:^(BOOL granted, NSError * _Nullable error) { + NSLog(@"Permission granted."); + }]; + [[UIApplication sharedApplication] registerForRemoteNotifications]; + } else if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_7_1) { + UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIUserNotificationTypeSound) categories:nil]; + [[UIApplication sharedApplication] registerForRemoteNotifications]; + [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; + } else { [[UIApplication sharedApplication] registerForRemoteNotificationTypes: (UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)]; - } else { - UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge|UIUserNotificationTypeAlert | UIUserNotificationTypeSound) categories:nil]; - [[UIApplication sharedApplication] registerForRemoteNotifications]; - [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; } [Appboy startWithApiKey:@"80bc1ed4-a2b4-4372-a715-7b3695175f9f" inApplication:application withLaunchOptions:launchOptions]; + + [[AppboyReactUtils sharedInstance] populateInitialUrlFromLaunchOptions:launchOptions]; + return YES; } +- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { + [[Appboy sharedInstance] registerApplication:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; +} + +- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler { + [[Appboy sharedInstance] userNotificationCenter:center didReceiveNotificationResponse:response withCompletionHandler:completionHandler]; +} + - (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { [[Appboy sharedInstance] registerApplication:application didReceiveRemoteNotification:userInfo]; } @@ -78,4 +76,23 @@ - (void) application:(UIApplication *)application didRegisterForRemoteNotificati [[Appboy sharedInstance] registerPushToken:[NSString stringWithFormat:@"%@", deviceToken]]; } + +// Deep linking +- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url + sourceApplication:(NSString *)sourceApplication annotation:(id)annotation +{ + NSLog(@"Calling RCTLinkingManager with url %@", url); + return [RCTLinkingManager application:application openURL:url + sourceApplication:sourceApplication annotation:annotation]; +} + +// Universal links +- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity + restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler +{ + return [RCTLinkingManager application:application + continueUserActivity:userActivity + restorationHandler:restorationHandler]; +} + @end diff --git a/AppboyProject/ios/AppboyProject/AppboyProject.entitlements b/AppboyProject/ios/AppboyProject/AppboyProject.entitlements new file mode 100644 index 0000000..903def2 --- /dev/null +++ b/AppboyProject/ios/AppboyProject/AppboyProject.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/AppboyProject/ios/AppboyProject/Images.xcassets/AppIcon.appiconset/Contents.json b/AppboyProject/ios/AppboyProject/Images.xcassets/AppIcon.appiconset/Contents.json index 118c98f..b8236c6 100644 --- a/AppboyProject/ios/AppboyProject/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/AppboyProject/ios/AppboyProject/Images.xcassets/AppIcon.appiconset/Contents.json @@ -1,5 +1,15 @@ { "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, { "idiom" : "iphone", "size" : "29x29", diff --git a/AppboyProject/ios/AppboyProject/Info.plist b/AppboyProject/ios/AppboyProject/Info.plist index 792e1b3..07842a7 100644 --- a/AppboyProject/ios/AppboyProject/Info.plist +++ b/AppboyProject/ios/AppboyProject/Info.plist @@ -2,12 +2,27 @@ + LSApplicationQueriesSchemes + + helloreact + + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + helloreact + + + CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.appboy.helloreact + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -22,6 +37,19 @@ 1 LSRequiresIPhoneOS + NSAppTransportSecurity + + NSExceptionDomains + + localhost + + NSTemporaryExceptionAllowsInsecureHTTPLoads + + + + + NSLocationWhenInUseUsageDescription + UILaunchStoryboardName LaunchScreen UIRequiredDeviceCapabilities @@ -36,12 +64,5 @@ UIViewControllerBasedStatusBarAppearance - NSLocationWhenInUseUsageDescription - - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - diff --git a/AppboyProject/ios/AppboyProjectTests/AppboyProjectTests.m b/AppboyProject/ios/AppboyProjectTests/AppboyProjectTests.m index 7a21156..d8e8e7a 100644 --- a/AppboyProject/ios/AppboyProjectTests/AppboyProjectTests.m +++ b/AppboyProject/ios/AppboyProjectTests/AppboyProjectTests.m @@ -13,7 +13,7 @@ #import "RCTLog.h" #import "RCTRootView.h" -#define TIMEOUT_SECONDS 240 +#define TIMEOUT_SECONDS 600 #define TEXT_TO_LOOK_FOR @"Welcome to React Native!" @interface AppboyProjectTests : XCTestCase diff --git a/AppboyProject/ios/AppboyProjectTests/Info.plist b/AppboyProject/ios/AppboyProjectTests/Info.plist index cdd73c1..886825c 100644 --- a/AppboyProject/ios/AppboyProjectTests/Info.plist +++ b/AppboyProject/ios/AppboyProjectTests/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.appboy.helloreact + org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/AppboyProject/ios/Podfile b/AppboyProject/ios/Podfile index 64cfbb5..2d3819f 100644 --- a/AppboyProject/ios/Podfile +++ b/AppboyProject/ios/Podfile @@ -1 +1,4 @@ -pod 'Appboy-iOS-SDK' \ No newline at end of file + +target 'AppboyProject' do + pod 'Appboy-iOS-SDK' +end \ No newline at end of file diff --git a/AppboyProject/ios/Podfile.lock b/AppboyProject/ios/Podfile.lock index 83dcae1..90d617b 100644 --- a/AppboyProject/ios/Podfile.lock +++ b/AppboyProject/ios/Podfile.lock @@ -1,15 +1,17 @@ PODS: - - Appboy-iOS-SDK (2.18.2): - - SDWebImage (~> 3.7.0) - - SDWebImage (3.7.5): - - SDWebImage/Core (= 3.7.5) - - SDWebImage/Core (3.7.5) + - Appboy-iOS-SDK (2.24.2): + - SDWebImage (~> 3.7) + - SDWebImage (3.8.2): + - SDWebImage/Core (= 3.8.2) + - SDWebImage/Core (3.8.2) DEPENDENCIES: - Appboy-iOS-SDK SPEC CHECKSUMS: - Appboy-iOS-SDK: 1e36c56c59eb51569ad02234ea340b5a97173890 - SDWebImage: 69c6303e3348fba97e03f65d65d4fbc26740f461 + Appboy-iOS-SDK: c9fdf98edb3a61e0418a0bb941011a8d50f5b811 + SDWebImage: 098e97e6176540799c27e804c96653ee0833d13c -COCOAPODS: 0.39.0 +PODFILE CHECKSUM: 4a8044a2dc577ba6222e659d250842e0517dbda0 + +COCOAPODS: 1.0.1 diff --git a/AppboyProject/ios/fastlane/Appfile b/AppboyProject/ios/fastlane/Appfile new file mode 100644 index 0000000..dda687f --- /dev/null +++ b/AppboyProject/ios/fastlane/Appfile @@ -0,0 +1,7 @@ +app_identifier "com.appboy.helloreact" # The bundle identifier of your app +apple_id "ios-certs@appboy.com" # Your Apple email address + +team_id "5GLZKGNWQ3" # Developer Portal Team ID + +# you can even provide different app identifiers, Apple IDs and team names per lane: +# More information: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Appfile.md diff --git a/AppboyProject/ios/fastlane/Fastfile b/AppboyProject/ios/fastlane/Fastfile new file mode 100644 index 0000000..cc4f59b --- /dev/null +++ b/AppboyProject/ios/fastlane/Fastfile @@ -0,0 +1,30 @@ +# Customise this file, documentation can be found here: +# https://github.com/fastlane/fastlane/tree/master/fastlane/docs +# All available actions: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Actions.md +# can also be listed using the `fastlane actions` command + +# Change the syntax highlighting to Ruby +# All lines starting with a # are ignored when running `fastlane` + +# If you want to automatically update fastlane if a new version is available: +# update_fastlane + +# This is the minimum version number required. +# Update this, if you use features of a newer version +fastlane_version "1.102.0" + +lane :matchFullAccess do + match(type: "development", force_for_new_devices: true) + match(type: "adhoc", force_for_new_devices: true) + end + + lane :matchReadOnly do + match(type: "development", readonly: true, force_for_new_devices: true) + match(type: "adhoc", readonly: true, force_for_new_devices: true) + end + +# More information about multiple platforms in fastlane: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Platforms.md +# All available actions: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Actions.md + +# fastlane reports which actions are used +# No personal data is recorded. Learn more at https://github.com/fastlane/enhancer diff --git a/AppboyProject/ios/fastlane/Matchfile b/AppboyProject/ios/fastlane/Matchfile new file mode 100644 index 0000000..c1a0a70 --- /dev/null +++ b/AppboyProject/ios/fastlane/Matchfile @@ -0,0 +1,13 @@ +git_url "git@github.com:Appboy/ios-certs-profiles.git" + +type "development" # The default type, can be: appstore, adhoc or development + +# app_identifier "tools.fastlane.app" +# username "user@fastlane.tools" # Your Apple Developer Portal username + +# For all available options run `match --help` +# Remove the # in the beginning of the line to enable the other options + +app_identifier "com.appboy.helloreact" +username "ios-certs@appboy.com" # Your Apple Developer Portal username + diff --git a/AppboyProject/package.json b/AppboyProject/package.json index 41009fd..dd8a107 100644 --- a/AppboyProject/package.json +++ b/AppboyProject/package.json @@ -6,8 +6,8 @@ "start": "node node_modules/react-native/local-cli/cli.js start" }, "dependencies": { - "react-native": "^0.19.0", - "react-native-button": "^1.4.2", + "react": "15.3.1", + "react-native": "^0.33.0", "react-native-appboy-sdk": "file:../" } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f186b6..db5a99c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## 1.0.0 +- Targets [Appboy Android SDK version 1.15.3](https://github.com/Appboy/appboy-android-sdk/blob/master/CHANGELOG.md#1153) and [Appboy iOS SDK version 2.24.2](https://github.com/Appboy/appboy-ios-sdk/blob/master/CHANGELOG.md#2242). +- **Update Required** — Updates iOS push handling in the AppboyProject sample project to be compatible with iOS 10. For more information, refer to the CHANGELOG for [Appboy iOS SDK v2.24.0](https://github.com/Appboy/appboy-ios-sdk/blob/master/CHANGELOG.md#2240). +- Adds callbacks to the native bindings to provide function call results to React Native. +- Exposes `ReactAppboy.getCardCountForCategories()` and `ReactAppboy.getUnreadCardCountForCategories()` for retrieving News Feed card counts. +- Adds `ReactAppboy.getInitialURL()` for handling deep links when an iOS application is launched from the suspended state by clicking on a push notification with a deep link. See `componentDidMount()` in `AppboyProject.js` for a sample implementation. +- Exposes `ReactAppboy.setTwitterData()` and `ReactAppboy.setFacebookData()` for Twitter and Facebook integration. +- Removes `AppboyBroadcastReceiver.java` from the AppboyProject sample project, as Appboy Android SDK v1.15.0 removes the need for a custom `AppboyBroadcastReceiver` for Appboy push notifications. +- Updates the AppboyProject sample project to integrate session handling and in-app message manager registration using an [AppboyLifecycleCallbackListener](https://github.com/Appboy/appboy-android-sdk/blob/master/android-sdk-ui/src/com/appboy/AppboyLifecycleCallbackListener.java), as introduced in Appboy Android SDK v1.15.0. +- Updates the AppboyProject sample application to React Native v0.33.0. + ## 0.3.0 - Renames Android module to conform to rnpm standard. diff --git a/android/build.gradle b/android/build.gradle index c421587..992a292 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -13,6 +13,6 @@ android { } dependencies { - compile 'com.appboy:android-sdk-ui:1.12+' + compile 'com.appboy:android-sdk-ui:1.15.3' compile 'com.facebook.react:react-native:0.19.+' } diff --git a/android/src/main/java/com/appboy/reactbridge/AppboyReactBridge.java b/android/src/main/java/com/appboy/reactbridge/AppboyReactBridge.java index 440b996..5253b51 100644 --- a/android/src/main/java/com/appboy/reactbridge/AppboyReactBridge.java +++ b/android/src/main/java/com/appboy/reactbridge/AppboyReactBridge.java @@ -7,7 +7,12 @@ import com.appboy.enums.Gender; import com.appboy.enums.Month; import com.appboy.enums.NotificationSubscriptionType; +import com.appboy.enums.CardCategory; +import com.appboy.events.FeedUpdatedEvent; +import com.appboy.events.IEventSubscriber; import com.appboy.models.outgoing.AppboyProperties; +import com.appboy.models.outgoing.FacebookUser; +import com.appboy.models.outgoing.TwitterUser; import com.appboy.support.AppboyLogger; import com.appboy.ui.activities.AppboyFeedActivity; import com.facebook.react.bridge.ReactApplicationContext; @@ -17,16 +22,26 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMapKeySetIterator; import com.facebook.react.bridge.ReadableType; +import com.facebook.react.bridge.Callback; import org.json.JSONObject; +import java.lang.Integer; import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; public class AppboyReactBridge extends ReactContextBaseJavaModule { private static final String TAG = String.format("Appboy.%s", AppboyReactBridge.class.getName()); - + private static final String CARD_COUNT_TAG = "card count"; + private static final String UNREAD_CARD_COUNT_TAG = "unread card count"; private static final String DURATION_SHORT_KEY = "SHORT"; private static final String DURATION_LONG_KEY = "LONG"; + private final Object mCallbackWasCalledMapLock = new Object(); + private Map> mFeedSubscriberMap = new ConcurrentHashMap>(); + private Map mCallbackWasCalledMap = new ConcurrentHashMap(); public AppboyReactBridge(ReactApplicationContext reactContext) { super(reactContext); @@ -37,6 +52,22 @@ public String getName() { return "AppboyReactBridge"; } + // Note that for non-primitive or non-String arguments, Callbacks must be invoked with `Writable` + // components from the `com.facebook.react.bridge` package (e.g., `WritableArray` or `WritableMap`). + // Attempting to pass other types will result in a "Cannot convert argument of type class X" error. + // For reference: https://github.com/facebook/react-native/issues/3101#issuecomment-143954448 + private void reportResultWithCallback(Callback callback, String error, Object result) { + if (callback != null) { + if (error != null) { + callback.invoke(error); + } else { + callback.invoke(null, result); + } + } else { + AppboyLogger.w(TAG, "Warning: AppboyReactBridge callback was null."); + } + } + @ReactMethod public void changeUser(String userName) { Appboy.getInstance(getReactApplicationContext()).changeUser(userName); @@ -59,7 +90,6 @@ private AppboyProperties populateEventPropertiesFromReadableMap(ReadableMap even } else if (readableType == ReadableType.Boolean) { properties.addProperty(key, eventProperties.getBoolean(key)); } else if (readableType == ReadableType.Number) { - // TODO (Brian) - is there a better way to descern double from int? try { properties.addProperty(key, eventProperties.getDouble(key)); } catch (Exception e) { @@ -70,7 +100,7 @@ private AppboyProperties populateEventPropertiesFromReadableMap(ReadableMap even } } } else { - AppboyLogger.e(TAG, "Could map ReadableType to an AppboyProperty value"); + AppboyLogger.e(TAG, "Could not map ReadableType to an AppboyProperty value"); } } } @@ -83,63 +113,74 @@ public void logPurchase(String productIdentifier, String price, String currencyC } @ReactMethod - public void submitFeedback(String replyToEmail, String message, boolean isReportingABug) { - Appboy.getInstance(getReactApplicationContext()).submitFeedback(replyToEmail, message, isReportingABug); + public void submitFeedback(String replyToEmail, String message, boolean isReportingABug, Callback callback) { + boolean result = Appboy.getInstance(getReactApplicationContext()).submitFeedback(replyToEmail, message, isReportingABug); + reportResultWithCallback(callback, null, result); } @ReactMethod - public void setStringCustomUserAttribute(String key, String value) { - Appboy.getInstance(getReactApplicationContext()).getCurrentUser().setCustomUserAttribute(key, value); + public void setStringCustomUserAttribute(String key, String value, Callback callback) { + boolean result = Appboy.getInstance(getReactApplicationContext()).getCurrentUser().setCustomUserAttribute(key, value); + reportResultWithCallback(callback, null, result); } @ReactMethod - public void setBoolCustomUserAttribute(String key, Boolean value) { - Appboy.getInstance(getReactApplicationContext()).getCurrentUser().setCustomUserAttribute(key, value); + public void setBoolCustomUserAttribute(String key, Boolean value, Callback callback) { + boolean result = Appboy.getInstance(getReactApplicationContext()).getCurrentUser().setCustomUserAttribute(key, value); + reportResultWithCallback(callback, null, result); } @ReactMethod - public void setIntCustomUserAttribute(String key, int value) { - Appboy.getInstance(getReactApplicationContext()).getCurrentUser().setCustomUserAttribute(key, value); + public void setIntCustomUserAttribute(String key, int value, Callback callback) { + boolean result = Appboy.getInstance(getReactApplicationContext()).getCurrentUser().setCustomUserAttribute(key, value); + reportResultWithCallback(callback, null, result); } @ReactMethod - public void setDoubleCustomUserAttribute(String key, float value) { - Appboy.getInstance(getReactApplicationContext()).getCurrentUser().setCustomUserAttribute(key, value); + public void setDoubleCustomUserAttribute(String key, float value, Callback callback) { + boolean result = Appboy.getInstance(getReactApplicationContext()).getCurrentUser().setCustomUserAttribute(key, value); + reportResultWithCallback(callback, null, result); } @ReactMethod - public void setDateCustomUserAttribute(String key, int timeStamp) { - Appboy.getInstance(getReactApplicationContext()).getCurrentUser().setCustomUserAttributeToSecondsFromEpoch(key, timeStamp); + public void setDateCustomUserAttribute(String key, int timeStamp, Callback callback) { + boolean result = Appboy.getInstance(getReactApplicationContext()).getCurrentUser().setCustomUserAttributeToSecondsFromEpoch(key, timeStamp); + reportResultWithCallback(callback, null, result); } @ReactMethod - public void incrementCustomUserAttribute(String key, int incrementValue) { - Appboy.getInstance(getReactApplicationContext()).getCurrentUser().incrementCustomUserAttribute(key, incrementValue); + public void incrementCustomUserAttribute(String key, int incrementValue, Callback callback) { + boolean result = Appboy.getInstance(getReactApplicationContext()).getCurrentUser().incrementCustomUserAttribute(key, incrementValue); + reportResultWithCallback(callback, null, result); } @ReactMethod - public void unsetCustomUserAttribute(String key) { - Appboy.getInstance(getReactApplicationContext()).getCurrentUser().unsetCustomUserAttribute(key); + public void unsetCustomUserAttribute(String key, Callback callback) { + boolean result = Appboy.getInstance(getReactApplicationContext()).getCurrentUser().unsetCustomUserAttribute(key); + reportResultWithCallback(callback, null, result); } @ReactMethod - public void setCustomUserAttributeArray(String key, ReadableArray value) { + public void setCustomUserAttributeArray(String key, ReadableArray value, Callback callback) { int size = value.size(); String[] attributeArray = new String[size]; for (int i = 0; i < size; i++) { attributeArray[i] = value.getString(i); } - Appboy.getInstance(getReactApplicationContext()).getCurrentUser().setCustomAttributeArray(key, attributeArray); + boolean result = Appboy.getInstance(getReactApplicationContext()).getCurrentUser().setCustomAttributeArray(key, attributeArray); + reportResultWithCallback(callback, null, result); } @ReactMethod - public void addToCustomAttributeArray(String key, String value) { - Appboy.getInstance(getReactApplicationContext()).getCurrentUser().addToCustomAttributeArray(key, value); + public void addToCustomAttributeArray(String key, String value, Callback callback) { + boolean result = Appboy.getInstance(getReactApplicationContext()).getCurrentUser().addToCustomAttributeArray(key, value); + reportResultWithCallback(callback, null, result); } @ReactMethod - public void removeFromCustomAttributeArray(String key, String value) { - Appboy.getInstance(getReactApplicationContext()).getCurrentUser().removeFromCustomAttributeArray(key, value); + public void removeFromCustomAttributeArray(String key, String value, Callback callback) { + boolean result = Appboy.getInstance(getReactApplicationContext()).getCurrentUser().removeFromCustomAttributeArray(key, value); + reportResultWithCallback(callback, null, result); } @ReactMethod @@ -158,16 +199,21 @@ public void setEmail(String email) { } @ReactMethod - public void setGender(String gender) { + public void setGender(String gender, Callback callback) { Gender genderEnum; - if (gender.toUpperCase().startsWith("M")) { + if (gender == null) { + reportResultWithCallback(callback, "Input Gender was null. Gender not set.", null); + return; + } else if (gender.toUpperCase().startsWith("M")) { genderEnum = Gender.MALE; } else if (gender.toUpperCase().startsWith("F")) { genderEnum = Gender.FEMALE; } else { + reportResultWithCallback(callback, "Invalid input " + gender + ". Gender not set.", null); return; } - Appboy.getInstance(getReactApplicationContext()).getCurrentUser().setGender(genderEnum); + boolean result = Appboy.getInstance(getReactApplicationContext()).getCurrentUser().setGender(genderEnum); + reportResultWithCallback(callback, null, result); } @ReactMethod @@ -196,33 +242,102 @@ public void setAvatarImageUrl(String avatarImageUrl) { } @ReactMethod - public void setPushNotificationSubscriptionType(String subscriptionType) { + public void setPushNotificationSubscriptionType(String subscriptionType, Callback callback) { NotificationSubscriptionType notificationSubscriptionType; - if (subscriptionType == "subscribed") { + if (subscriptionType == null) { + reportResultWithCallback(callback, "Input subscription type was null. Push notification subscription type not set.", null); + return; + } else if (subscriptionType.equals("subscribed")) { notificationSubscriptionType = NotificationSubscriptionType.SUBSCRIBED; - } else if (subscriptionType == "unsubscribed") { + } else if (subscriptionType.equals("unsubscribed")) { notificationSubscriptionType = NotificationSubscriptionType.UNSUBSCRIBED; - } else if (subscriptionType == "optedin") { + } else if (subscriptionType.equals("optedin")) { notificationSubscriptionType = NotificationSubscriptionType.OPTED_IN; } else { + reportResultWithCallback(callback, "Invalid subscription type " + subscriptionType + ". Push notification subscription type not set.", null); return; } - Appboy.getInstance(getReactApplicationContext()).getCurrentUser().setPushNotificationSubscriptionType(notificationSubscriptionType); + boolean result = Appboy.getInstance(getReactApplicationContext()).getCurrentUser().setPushNotificationSubscriptionType(notificationSubscriptionType); + reportResultWithCallback(callback, null, result); } @ReactMethod - public void setEmailNotificationSubscriptionType(String subscriptionType) { + public void setEmailNotificationSubscriptionType(String subscriptionType, Callback callback) { NotificationSubscriptionType notificationSubscriptionType; - if (subscriptionType == "subscribed") { + if (subscriptionType == null) { + reportResultWithCallback(callback, "Input subscription type was null. Email notification subscription type not set.", null); + return; + } else if (subscriptionType.equals("subscribed")) { notificationSubscriptionType = NotificationSubscriptionType.SUBSCRIBED; - } else if (subscriptionType == "unsubscribed") { + } else if (subscriptionType.equals("unsubscribed")) { notificationSubscriptionType = NotificationSubscriptionType.UNSUBSCRIBED; - } else if (subscriptionType == "optedin") { + } else if (subscriptionType.equals("optedin")) { notificationSubscriptionType = NotificationSubscriptionType.OPTED_IN; } else { + reportResultWithCallback(callback, "Invalid subscription type " + subscriptionType + ". Email notification subscription type not set.", null); return; } - Appboy.getInstance(getReactApplicationContext()).getCurrentUser().setEmailNotificationSubscriptionType(notificationSubscriptionType); + boolean result = Appboy.getInstance(getReactApplicationContext()).getCurrentUser().setEmailNotificationSubscriptionType(notificationSubscriptionType); + reportResultWithCallback(callback, null, result); + } + + @ReactMethod + public void setTwitterData(Integer id, String screenName, String name, String description, Integer followersCount, Integer friendsCount, Integer statusesCount, String profileImageUrl) { + TwitterUser twitterUser = new TwitterUser(id, screenName, name, description, followersCount, friendsCount, statusesCount, profileImageUrl); + Appboy.getInstance(getReactApplicationContext()).getCurrentUser().setTwitterData(twitterUser); + } + + @ReactMethod + public void setFacebookData(ReadableMap facebookUserDictionary, Integer numberOfFriends, ReadableArray likes) { + List likesList = new ArrayList(); + if (likes != null) { + for (int i = 0; i < likes.size(); i++) { + ReadableType readableType = likes.getType(i); + if (readableType == ReadableType.String) { + likesList.add(likes.getString(i)); + } else if (readableType == ReadableType.Map) { + ReadableMap like = likes.getMap(i); + String likeString = like.getString("name"); + if (likeString != null) { + likesList.add(likeString); + } + } + } + } + + String facebookId = null; + String firstName = null; + String lastName = null; + String email = null; + String bio = null; + String birthday = null; + Gender genderEnum = null; + String cityName = null; + + if (facebookUserDictionary != null) { + facebookId = facebookUserDictionary.getString("id"); + firstName = facebookUserDictionary.getString("first_name"); + lastName = facebookUserDictionary.getString("last_name"); + email = facebookUserDictionary.getString("email"); + bio = facebookUserDictionary.getString("bio"); + birthday = facebookUserDictionary.getString("birthday"); + + String genderString = facebookUserDictionary.getString("gender"); + if (genderString != null && genderString.length() > 0) { + if (genderString.toUpperCase().startsWith("M")) { + genderEnum = Gender.MALE; + } else if (genderString.toUpperCase().startsWith("F")) { + genderEnum = Gender.FEMALE; + } + } + ReadableMap location = facebookUserDictionary.getMap("location"); + if (location != null) { + cityName = location.getString("name"); + } + } + FacebookUser facebookUser = new FacebookUser(facebookId, firstName, lastName, email, + bio, cityName, genderEnum, numberOfFriends, likesList, birthday); + Appboy.getInstance(getReactApplicationContext()).getCurrentUser().setFacebookData(facebookUser); } @ReactMethod @@ -232,6 +347,94 @@ public void launchNewsFeed() { this.getReactApplicationContext().startActivity(intent); } + private CardCategory getCardCategoryFromString(String categoryString) { + CardCategory cardCategory = null; + for (CardCategory category : CardCategory.values()) { + if (categoryString != null && categoryString.toUpperCase().equals(category.name())) { + cardCategory = category; + } + } + return cardCategory; + } + + // Registers a short-lived FeedUpdatedEvent subscriber, requests a feed refresh from cache, and and returns the requested card count in the callback + private void getCardCountForTag(final String category, final Callback callback, String cardCountTag) { + final CardCategory cardCategory = getCardCategoryFromString(category); + // Note that Android does not have a CardCategory.ALL enum, while iOS does + if (category == null || (cardCategory == null && !category.equals("all"))) { + reportResultWithCallback(callback, "Invalid card category " + category + ", could not retrieve" + cardCountTag, null); + return; + } + + // Register FeedUpdatedEvent subscriber + IEventSubscriber feedUpdatedSubscriber = null; + boolean requestingFeedUpdateFromCache = false; + final Appboy mAppboy = Appboy.getInstance(getReactApplicationContext()); + + if (cardCountTag.equals(CARD_COUNT_TAG)) { + // getCardCount + feedUpdatedSubscriber = new IEventSubscriber() { + @Override + public void trigger(FeedUpdatedEvent feedUpdatedEvent) { + // Callback blocks (error or result) may only be invoked once, else React Native throws an error. + synchronized (mCallbackWasCalledMapLock) { + if (mCallbackWasCalledMap.get(callback) == null || mCallbackWasCalledMap.get(callback) != null && !mCallbackWasCalledMap.get(callback).booleanValue()) { + mCallbackWasCalledMap.put(callback, new Boolean(true)); + if (category.equals("all")) { + reportResultWithCallback(callback, null, feedUpdatedEvent.getCardCount()); + } else { + reportResultWithCallback(callback, null, feedUpdatedEvent.getCardCount(cardCategory)); + } + } + } + // Remove this listener from the feed subscriber map and from Appboy + mAppboy.removeSingleSubscription(mFeedSubscriberMap.get(callback), FeedUpdatedEvent.class); + mFeedSubscriberMap.remove(callback); + } + }; + requestingFeedUpdateFromCache = true; + } else if (cardCountTag.equals(UNREAD_CARD_COUNT_TAG)) { + // getUnreadCardCount + feedUpdatedSubscriber = new IEventSubscriber() { + @Override + public void trigger(FeedUpdatedEvent feedUpdatedEvent) { + // Callback blocks (error or result) may only be invoked once, else React Native throws an error. + synchronized (mCallbackWasCalledMapLock) { + if (mCallbackWasCalledMap.get(callback) == null || mCallbackWasCalledMap.get(callback)!= null && !mCallbackWasCalledMap.get(callback).booleanValue()) { + mCallbackWasCalledMap.put(callback, new Boolean(true)); + if (category.equals("all")) { + reportResultWithCallback(callback, null, feedUpdatedEvent.getUnreadCardCount()); + } else { + reportResultWithCallback(callback, null, feedUpdatedEvent.getUnreadCardCount(cardCategory)); + } + } + } + // Remove this listener from the feed subscriber map and from Appboy + mAppboy.removeSingleSubscription(mFeedSubscriberMap.get(callback), FeedUpdatedEvent.class); + mFeedSubscriberMap.remove(callback); + } + }; + requestingFeedUpdateFromCache = true; + } + + if (requestingFeedUpdateFromCache) { + // Put the subscriber into a map so we can remove it later from future subscriptions + mFeedSubscriberMap.put(callback, feedUpdatedSubscriber); + mAppboy.subscribeToFeedUpdates(feedUpdatedSubscriber); + mAppboy.requestFeedRefreshFromCache(); + } + } + + @ReactMethod + public void getCardCountForCategories(String category, Callback callback) { + getCardCountForTag(category, callback, CARD_COUNT_TAG); + } + + @ReactMethod + public void getUnreadCardCountForCategories(String category, Callback callback) { + getCardCountForTag(category, callback, UNREAD_CARD_COUNT_TAG); + } + @ReactMethod public void launchFeedback() { Log.i(TAG, "Launch feedback actions are not currently supported on Android. Doing nothing."); @@ -267,4 +470,4 @@ private Month parseMonth(int monthInt) { return null; } } -} \ No newline at end of file +} diff --git a/iOS/AppboyReactBridge/AppboyReactBridge.xcodeproj/project.pbxproj b/iOS/AppboyReactBridge/AppboyReactBridge.xcodeproj/project.pbxproj index acaf36e..208ea6d 100644 --- a/iOS/AppboyReactBridge/AppboyReactBridge.xcodeproj/project.pbxproj +++ b/iOS/AppboyReactBridge/AppboyReactBridge.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + D4DB659B1D8C973800EFA6B6 /* AppboyReactUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = D4DB659A1D8C973800EFA6B6 /* AppboyReactUtils.m */; }; E3D0A75E1C668BB90027828A /* AppboyReactBridge.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = E3D0A75D1C668BB90027828A /* AppboyReactBridge.h */; }; E3D0A7601C668BB90027828A /* AppboyReactBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = E3D0A75F1C668BB90027828A /* AppboyReactBridge.m */; }; /* End PBXBuildFile section */ @@ -25,6 +26,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + D4DB65991D8C973800EFA6B6 /* AppboyReactUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppboyReactUtils.h; sourceTree = ""; }; + D4DB659A1D8C973800EFA6B6 /* AppboyReactUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppboyReactUtils.m; sourceTree = ""; }; E3D0A75A1C668BB90027828A /* libAppboyReactBridge.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAppboyReactBridge.a; sourceTree = BUILT_PRODUCTS_DIR; }; E3D0A75D1C668BB90027828A /* AppboyReactBridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppboyReactBridge.h; sourceTree = ""; }; E3D0A75F1C668BB90027828A /* AppboyReactBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppboyReactBridge.m; sourceTree = ""; }; @@ -62,6 +65,8 @@ children = ( E3D0A75D1C668BB90027828A /* AppboyReactBridge.h */, E3D0A75F1C668BB90027828A /* AppboyReactBridge.m */, + D4DB65991D8C973800EFA6B6 /* AppboyReactUtils.h */, + D4DB659A1D8C973800EFA6B6 /* AppboyReactUtils.m */, ); path = AppboyReactBridge; sourceTree = ""; @@ -122,6 +127,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D4DB659B1D8C973800EFA6B6 /* AppboyReactUtils.m in Sources */, E3D0A7601C668BB90027828A /* AppboyReactBridge.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -165,7 +171,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.2; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -202,7 +208,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.2; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/iOS/AppboyReactBridge/AppboyReactBridge/AppboyReactBridge.h b/iOS/AppboyReactBridge/AppboyReactBridge/AppboyReactBridge.h index 637534c..1044e5e 100644 --- a/iOS/AppboyReactBridge/AppboyReactBridge/AppboyReactBridge.h +++ b/iOS/AppboyReactBridge/AppboyReactBridge/AppboyReactBridge.h @@ -4,4 +4,4 @@ // TODO (Brian) - Modify header/library search paths to be more generic or assume node installation @interface AppboyReactBridge : NSObject -@end \ No newline at end of file +@end diff --git a/iOS/AppboyReactBridge/AppboyReactBridge/AppboyReactBridge.m b/iOS/AppboyReactBridge/AppboyReactBridge/AppboyReactBridge.m index 66f6c2c..e980bd8 100644 --- a/iOS/AppboyReactBridge/AppboyReactBridge/AppboyReactBridge.m +++ b/iOS/AppboyReactBridge/AppboyReactBridge/AppboyReactBridge.m @@ -3,10 +3,11 @@ #import "RCTConvert.h" #import "AppboyKit.h" #import "ABKUser.h" +#import "AppboyReactUtils.h" @implementation RCTConvert (AppboySubscriptionType) RCT_ENUM_CONVERTER(ABKNotificationSubscriptionType, - (@{@"subscribed":@(ABKSubscribed), @"unsubscribed":@(ABKUnsubscribed),@"optedin":@(ABKOptedIn)}), + (@{@"subscribed":@(ABKSubscribed), @"unsubscribed":@(ABKUnsubscribed),@"optedin":@(ABKOptedIn)}), ABKSubscribed, integerValue); @end @@ -23,16 +24,38 @@ - (NSDictionary *)constantsToExport return @{@"subscribed":@(ABKSubscribed), @"unsubscribed":@(ABKUnsubscribed),@"optedin":@(ABKOptedIn)}; }; +- (void)reportResultWithCallback:(RCTResponseSenderBlock)callback andError:(NSString *)error andResult:(id)result { + if (callback != nil) { + if (error != nil) { + callback(@[error, [NSNull null]]); + } else { + callback(@[[NSNull null], result]); + } + } else { + RCTLogInfo(@"Warning: AppboyReactBridge callback was null."); + } +} + +// Returns the deep link from the push dictionary in application:didFinishLaunchingWithOptions: launchOptions, if one exists +// For more context see getInitialURL() in index.js +RCT_EXPORT_METHOD(getInitialUrl:(RCTResponseSenderBlock)callback) { + if ([AppboyReactUtils sharedInstance].initialUrlString != nil) { + [self reportResultWithCallback:callback andError:nil andResult:[AppboyReactUtils sharedInstance].initialUrlString]; + } else { + [self reportResultWithCallback:callback andError:@"Initial URL string was nil." andResult:nil]; + } +} + RCT_EXPORT_METHOD(changeUser:(NSString *)userId) { RCTLogInfo(@"[Appboy sharedInstance] changeUser with value %@", userId); [[Appboy sharedInstance] changeUser:userId]; } -RCT_EXPORT_METHOD(submitFeedback:(NSString *)replyToEmail message:(NSString *)message isReportingABug:(BOOL)isReportingABug) +RCT_EXPORT_METHOD(submitFeedback:(NSString *)replyToEmail message:(NSString *)message isReportingABug:(BOOL)isReportingABug callback:(RCTResponseSenderBlock)callback) { RCTLogInfo(@"[Appboy sharedInstance] submitFeedback with values %@ %@ %@", replyToEmail, message, isReportingABug ? @"true" : @"false"); - [[Appboy sharedInstance] submitFeedback:replyToEmail message:message isReportingABug:isReportingABug]; + [self reportResultWithCallback:callback andError:nil andResult:@([[Appboy sharedInstance] submitFeedback:replyToEmail message:message isReportingABug:isReportingABug])]; } RCT_EXPORT_METHOD(logCustomEvent:(NSString *)eventName withProperties:(nullable NSDictionary *)properties) { @@ -82,12 +105,14 @@ - (NSDictionary *)constantsToExport [Appboy sharedInstance].user.homeCity = homeCity; } -RCT_EXPORT_METHOD(setGender:(NSString *)gender) { +RCT_EXPORT_METHOD(setGender:(NSString *)gender callback:(RCTResponseSenderBlock)callback) { RCTLogInfo(@"[Appboy sharedInstance].gender = %@", gender); if ([[gender capitalizedString] hasPrefix:@"M"]) { - [Appboy sharedInstance].user.gender = ABKUserGenderMale; + [self reportResultWithCallback:callback andError:nil andResult:@([[Appboy sharedInstance].user setGender:ABKUserGenderMale])]; + } else if ([[gender capitalizedString] hasPrefix:@"F"]) { + [self reportResultWithCallback:callback andError:nil andResult:@([[Appboy sharedInstance].user setGender:ABKUserGenderFemale])]; } else { - [Appboy sharedInstance].user.gender = ABKUserGenderFemale; + [self reportResultWithCallback:callback andError:[NSString stringWithFormat:@"Invalid input %@. Gender not set.", gender] andResult:nil]; } } @@ -101,64 +126,86 @@ - (NSDictionary *)constantsToExport [Appboy sharedInstance].user.avatarImageURL = avatarImageURL; } -RCT_EXPORT_METHOD(setEmailNotificationSubscriptionType:(ABKNotificationSubscriptionType)emailNotificationSubscriptionType) { +RCT_EXPORT_METHOD(setEmailNotificationSubscriptionType:(ABKNotificationSubscriptionType)emailNotificationSubscriptionType callback:(RCTResponseSenderBlock)callback) { RCTLogInfo(@"[Appboy sharedInstance].user.emailNotificationSubscriptionType = %@", @"enum"); - [Appboy sharedInstance].user.emailNotificationSubscriptionType = emailNotificationSubscriptionType; + [self reportResultWithCallback:callback andError:nil andResult:@([Appboy sharedInstance].user.emailNotificationSubscriptionType = emailNotificationSubscriptionType)]; } -RCT_EXPORT_METHOD(setPushNotificationSubscriptionType:(ABKNotificationSubscriptionType)pushNotificationSubscriptionType) { +RCT_EXPORT_METHOD(setPushNotificationSubscriptionType:(ABKNotificationSubscriptionType)pushNotificationSubscriptionType callback:(RCTResponseSenderBlock)callback) { RCTLogInfo(@"[Appboy sharedInstance].pushNotificationSubscriptionType = %@", @"enum"); - [Appboy sharedInstance].user.pushNotificationSubscriptionType = pushNotificationSubscriptionType; + [self reportResultWithCallback:callback andError:nil andResult:@([Appboy sharedInstance].user.pushNotificationSubscriptionType = pushNotificationSubscriptionType)]; } -RCT_EXPORT_METHOD(setBoolCustomUserAttribute:(NSString *)key andValue:(BOOL)value) { +RCT_EXPORT_METHOD(setBoolCustomUserAttribute:(NSString *)key andValue:(BOOL)value callback:(RCTResponseSenderBlock)callback) { RCTLogInfo(@"[Appboy sharedInstance].user setCustomAttributeWithKey:AndBoolValue: = %@", key); - [[Appboy sharedInstance].user setCustomAttributeWithKey:key andBOOLValue:value]; + [self reportResultWithCallback:callback andError:nil andResult:@([[Appboy sharedInstance].user setCustomAttributeWithKey:key andBOOLValue:value])]; } -RCT_EXPORT_METHOD(setStringCustomUserAttribute:(NSString *)key andValue:(NSString *)value) { +RCT_EXPORT_METHOD(setStringCustomUserAttribute:(NSString *)key andValue:(NSString *)value callback:(RCTResponseSenderBlock)callback) { RCTLogInfo(@"[Appboy sharedInstance].user setCustomAttributeWithKey:AndStringValue: = %@", key); - [[Appboy sharedInstance].user setCustomAttributeWithKey:key andStringValue:value]; + [self reportResultWithCallback:callback andError:nil andResult:@([[Appboy sharedInstance].user setCustomAttributeWithKey:key andStringValue:value])]; } -RCT_EXPORT_METHOD(setDoubleCustomUserAttribute:(NSString *)key andValue:(double)value) { +RCT_EXPORT_METHOD(setDoubleCustomUserAttribute:(NSString *)key andValue:(double)value callback:(RCTResponseSenderBlock)callback) { RCTLogInfo(@"[Appboy sharedInstance].user setCustomAttributeWithKey:AndDoubleValue: = %@", key); - [[Appboy sharedInstance].user setCustomAttributeWithKey:key andDoubleValue:value]; + [self reportResultWithCallback:callback andError:nil andResult:@([[Appboy sharedInstance].user setCustomAttributeWithKey:key andDoubleValue:value])]; } -RCT_EXPORT_METHOD(setDateCustomUserAttribute:(NSString *)key andValue:(NSDate *)value) { +RCT_EXPORT_METHOD(setDateCustomUserAttribute:(NSString *)key andValue:(NSDate *)value callback:(RCTResponseSenderBlock)callback) { RCTLogInfo(@"[Appboy sharedInstance].user setCustomAttributeWithKey:AndDateValue: = %@", key); - [[Appboy sharedInstance].user setCustomAttributeWithKey:key andDateValue:value]; + [self reportResultWithCallback:callback andError:nil andResult:@([[Appboy sharedInstance].user setCustomAttributeWithKey:key andDateValue:value])]; } -RCT_EXPORT_METHOD(setIntCustomUserAttribute:(NSString *)key andValue:(int)value) { +RCT_EXPORT_METHOD(setIntCustomUserAttribute:(NSString *)key andValue:(int)value callback:(RCTResponseSenderBlock)callback) { RCTLogInfo(@"[Appboy sharedInstance].user setCustomAttributeWithKey:AndIntValue: = %@", key); - [[Appboy sharedInstance].user setCustomAttributeWithKey:key andIntegerValue:value]; + [self reportResultWithCallback:callback andError:nil andResult:@([[Appboy sharedInstance].user setCustomAttributeWithKey:key andIntegerValue:value])]; } -RCT_EXPORT_METHOD(setCustomUserAttributeArray:(NSString *)key andValue:(NSArray *)value) { +RCT_EXPORT_METHOD(setCustomUserAttributeArray:(NSString *)key andValue:(NSArray *)value callback:(RCTResponseSenderBlock)callback) { RCTLogInfo(@"[Appboy sharedInstance].user setCustomAttributeArrayWithKey:array:: = %@", key); - [[Appboy sharedInstance].user setCustomAttributeArrayWithKey:key array:value]; + [self reportResultWithCallback:callback andError:nil andResult:@([[Appboy sharedInstance].user setCustomAttributeArrayWithKey:key array:value])]; } -RCT_EXPORT_METHOD(unsetCustomUserAttribute:(NSString *)key) { +RCT_EXPORT_METHOD(unsetCustomUserAttribute:(NSString *)key callback:(RCTResponseSenderBlock)callback) { RCTLogInfo(@"[Appboy sharedInstance].user unsetCustomUserAttribute: = %@", key); - [[Appboy sharedInstance].user unsetCustomAttributeWithKey:key]; + [self reportResultWithCallback:callback andError:nil andResult:@([[Appboy sharedInstance].user unsetCustomAttributeWithKey:key])]; } -RCT_EXPORT_METHOD(incrementCustomUserAttribute:(NSString *)key by:(NSInteger)incrementValue) { +RCT_EXPORT_METHOD(incrementCustomUserAttribute:(NSString *)key by:(NSInteger)incrementValue callback:(RCTResponseSenderBlock)callback) { RCTLogInfo(@"[Appboy sharedInstance].user incrementCustomUserAttribute: = %@", key); - [[Appboy sharedInstance].user incrementCustomUserAttribute:key by:incrementValue]; + [self reportResultWithCallback:callback andError:nil andResult:@([[Appboy sharedInstance].user incrementCustomUserAttribute:key by:incrementValue])]; } - -RCT_EXPORT_METHOD(addToCustomAttributeArray:(NSString *)key value:(NSString *)value) { + +RCT_EXPORT_METHOD(addToCustomAttributeArray:(NSString *)key value:(NSString *)value callback:(RCTResponseSenderBlock)callback) { RCTLogInfo(@"[Appboy sharedInstance].user addToCustomAttributeArray: = %@", key); - [[Appboy sharedInstance].user addToCustomAttributeArrayWithKey:key value:value]; + [self reportResultWithCallback:callback andError:nil andResult:@([[Appboy sharedInstance].user addToCustomAttributeArrayWithKey:key value:value])]; } -RCT_EXPORT_METHOD(removeFromCustomAttributeArray:(NSString *)key value:(NSString *)value) { +RCT_EXPORT_METHOD(removeFromCustomAttributeArray:(NSString *)key value:(NSString *)value callback:(RCTResponseSenderBlock)callback) { RCTLogInfo(@"[Appboy sharedInstance].user removeFromCustomAttributeArrayWithKey: = %@", key); - [[Appboy sharedInstance].user removeFromCustomAttributeArrayWithKey:key value:value]; + [self reportResultWithCallback:callback andError:nil andResult:@([[Appboy sharedInstance].user removeFromCustomAttributeArrayWithKey:key value:value])]; +} + +RCT_EXPORT_METHOD(setTwitterData:(NSUInteger)twitterId withScreenName:(NSString *)screenName withName:(NSString *)name withDescription:(NSString *)description withFollowersCount:(NSUInteger)followersCount withFriendsCount:(NSUInteger)friendsCount withStatusesCount:(NSUInteger)statusesCount andProfileImageUrl:(NSString *)profileImageUrl) { + RCTLogInfo(@"[Appboy sharedInstance].user setTwitterData with screenName %@", screenName); + ABKTwitterUser *twitterUser = [[ABKTwitterUser alloc] init]; + twitterUser.userDescription = description; + twitterUser.twitterID = twitterId; + twitterUser.twitterName = name; + twitterUser.profileImageUrl = profileImageUrl; + twitterUser.friendsCount = friendsCount; + twitterUser.followersCount = followersCount; + twitterUser.screenName = screenName; + twitterUser.statusesCount = statusesCount; + [Appboy sharedInstance].user.twitterUser = twitterUser; +} + +RCT_EXPORT_METHOD(setFacebookData:(nullable NSDictionary *)facebookUserDictionary withNumberOfFriends:(NSUInteger)numberOfFriends withLikes:(NSArray *)likes) { + RCTLogInfo(@"[Appboy sharedInstance].user setFacebookData"); + ABKFacebookUser *facebookUser = [[ABKFacebookUser alloc] initWithFacebookUserDictionary:facebookUserDictionary + numberOfFriends:numberOfFriends + likes:likes]; + [Appboy sharedInstance].user.facebookUser = facebookUser; } RCT_EXPORT_METHOD(launchNewsFeed) { @@ -171,6 +218,42 @@ - (NSDictionary *)constantsToExport [mainViewController presentViewController:feedModal animated:YES completion:nil]; } +- (ABKCardCategory)getCardCategoryForString:(NSString *)category { + ABKCardCategory cardCategory = 0; + if ([[category lowercaseString] isEqualToString:@"advertising"]) { + cardCategory = ABKCardCategoryAdvertising; + } else if ([[category lowercaseString] isEqualToString:@"announcements"]) { + cardCategory = ABKCardCategoryAnnouncements; + } else if ([[category lowercaseString] isEqualToString:@"news"]) { + cardCategory = ABKCardCategoryNews; + } else if ([[category lowercaseString] isEqualToString:@"social"]) { + cardCategory = ABKCardCategorySocial; + } else if ([[category lowercaseString] isEqualToString:@"no_category"]) { + cardCategory = ABKCardCategoryNoCategory; + } else if ([[category lowercaseString] isEqualToString:@"all"]) { + cardCategory = ABKCardCategoryAll; + } + return cardCategory; +} + +RCT_EXPORT_METHOD(getCardCountForCategories:(NSString *)category callback:(RCTResponseSenderBlock)callback) { + ABKCardCategory cardCategory = [self getCardCategoryForString:category]; + if (cardCategory == 0) { + [self reportResultWithCallback:callback andError:[NSString stringWithFormat:@"Invalid card category %@, could not retrieve card count.", category] andResult:nil]; + } else { + [self reportResultWithCallback:callback andError:nil andResult:@([[Appboy sharedInstance].feedController cardCountForCategories:cardCategory])]; + } +} + +RCT_EXPORT_METHOD(getUnreadCardCountForCategories:(NSString *)category callback:(RCTResponseSenderBlock)callback) { + ABKCardCategory cardCategory = [self getCardCategoryForString:category]; + if (cardCategory == 0) { + [self reportResultWithCallback:callback andError:[NSString stringWithFormat:@"Invalid card category %@, could not retrieve unread card count.", category] andResult:nil]; + } else { + [self reportResultWithCallback:callback andError:nil andResult:@([[Appboy sharedInstance].feedController unreadCardCountForCategories:cardCategory])]; + } +} + RCT_EXPORT_METHOD(launchFeedback) { RCTLogInfo(@"launchFeedback called"); ABKFeedbackViewControllerModalContext *feedbackModal = [[ABKFeedbackViewControllerModalContext alloc] init]; diff --git a/iOS/AppboyReactBridge/AppboyReactBridge/AppboyReactUtils.h b/iOS/AppboyReactBridge/AppboyReactBridge/AppboyReactUtils.h new file mode 100644 index 0000000..05b99db --- /dev/null +++ b/iOS/AppboyReactBridge/AppboyReactBridge/AppboyReactUtils.h @@ -0,0 +1,11 @@ +#import +#import + +@interface AppboyReactUtils : NSObject + ++ (AppboyReactUtils *)sharedInstance; +- (BOOL)populateInitialUrlFromLaunchOptions:(NSDictionary *)launchOptions; + +@property NSString *initialUrlString; + +@end diff --git a/iOS/AppboyReactBridge/AppboyReactBridge/AppboyReactUtils.m b/iOS/AppboyReactBridge/AppboyReactBridge/AppboyReactUtils.m new file mode 100644 index 0000000..1d372f6 --- /dev/null +++ b/iOS/AppboyReactBridge/AppboyReactBridge/AppboyReactUtils.m @@ -0,0 +1,33 @@ +#import "AppboyReactUtils.h" +#import "RCTLog.h" + +@implementation AppboyReactUtils + +static AppboyReactUtils *sharedInstance; + +- (instancetype)init { + self = [super init]; + self.initialUrlString = nil; + return self; +} + ++ (AppboyReactUtils *)sharedInstance { + if (!sharedInstance) { + sharedInstance = [[AppboyReactUtils alloc] init]; + } + return sharedInstance; +} + +// If the push dictionary from application:didFinishLaunchingWithOptions: launchOptions has an Appboy deep link (ab_uri), we store it in initialUrlString +- (BOOL)populateInitialUrlFromLaunchOptions:(NSDictionary *)launchOptions { + NSDictionary *pushDictionary = [launchOptions valueForKey:UIApplicationLaunchOptionsRemoteNotificationKey]; + if (pushDictionary && pushDictionary[@"aps"] && pushDictionary[@"ab_uri"]) { + sharedInstance.initialUrlString = pushDictionary[@"ab_uri"]; + RCTLogInfo(@"[AppboyReactUtils sharedInstance].initialUrlString set to %@.", sharedInstance.initialUrlString); + return true; + } + sharedInstance.initialUrlString = nil; + return false; +} + +@end diff --git a/index.js b/index.js index 336d54e..1a5e45a 100644 --- a/index.js +++ b/index.js @@ -1,289 +1,425 @@ const AppboyReactBridge = require('react-native').NativeModules.AppboyReactBridge; -var ReactAppboy = { /** - * When a user first uses Appboy on a device they are considered "anonymous". Use this method to identify a user - * with a unique ID, which enables the following: - * - * - If the same user is identified on another device, their user profile, usage history and event history will - * be shared across devices. - * - If your app is used on the same device by multiple people, you can assign each of them a unique identifier - * to track them separately. Only the most recent user on a particular browser will receive push - * notifications and in-app messages. - * - * When you request a user switch (which is any call to changeUser where the new user ID is not the same as the - * existing user ID), the current session for the previous user (anonymous or not) is automatically ended and - * a new session is started. Similarly, following a call to changeUser, any events which fire are guaranteed to - * be for the new user -- if an in-flight server request completes for the old user after the user switch no - * events will fire, so you do not need to worry about filtering out events from Appboy for old users. - * - * Additionally, if you identify a user which has never been identified on another device, the entire history of - * that user as an "anonymous" user on this device will be preserved and associated with the newly identified - * user. However, if you identify a user which *has* been identified in another app, any history which was - * already flushed to the server for the anonymous user on this device will become orphaned and will not be - * associated with any future users. These orphaned users are not considered in your user counts and will not - * be messaged. - * - * Note: Once you identify a user, you cannot revert to the "anonymous" user. The transition from anonymous to - * identified tracking is only allowed once because the initial anonymous user receives special treatment to - * allow for preservation of their history. As a result, we recommend against changing the user ID just because - * your app has entered a "logged out" state because it makes you unable to target the previously logged out user - * with re-engagement campaigns. If you anticipate multiple users on the same device, but only want to target one - * of them when your app is in a logged out state, we recommend separately keeping track of the user ID you want - * to target while logged out and switching back to that user ID as part of your app's logout process. - * - * @param {string} userId - A unique identifier for this user. - */ -changeUser: function (userId) { - AppboyReactBridge.changeUser(userId); -}, +* This default callback logs errors and null or false results. AppboyReactBridge methods with callbacks will +* default to this if there is no user-provided callback. +* @callback appboyCallback +* @param {object} error - The callback error object +* @param {object} result - The method return value +*/ +function appboyCallback(error, result) { + if (error) { + console.log(error); + } else if (result == null || result === false) { + console.log('Appboy API method returned null or false.'); + } +} /** - * Reports that the current user performed a custom named event. - * @param {string} eventName - The identifier for the event to track. Best practice is to track generic events - * useful for segmenting, instead of specific user actions (i.e. track watched_sports_video instead of - * watched_video_adrian_peterson_td_mnf). Value is limited to 255 characters in length, cannot begin with a $, - * and can only contain alphanumeric characters and punctuation. - * @param {object} [eventProperties] - Hash of properties for this event. Keys are limited to 255 - * characters in length, cannot begin with a $, and can only contain alphanumeric characters and punctuation. - * Values can be numeric, boolean, or strings 255 characters or shorter. - */ +* A helper method that wraps calls to AppboyReactBridge and passes in the +* default Appboy callback if no callback is provided. +* @param {function} methodName - The AppboyReactBridge function to call +* @param {array} argsArray - An array of arguments to pass into methodName +* @param {function(error, result)} callback - The user-provided callback +*/ +function callFunctionWithCallback(methodName, argsArray, callback) { + // If user-provided callback is null, undefined, or not a function, use default Appboy callback + if (typeof callback === 'undefined' || callback == null || typeof callback !== 'function') { + callback = appboyCallback; + } + argsArray.push(callback); + methodName.apply(this, argsArray); +} -logCustomEvent: function (eventName, eventProperties) { - AppboyReactBridge.logCustomEvent(eventName, eventProperties); -}, +var ReactAppboy = { + /** + * When launching an iOS application that has previously been force closed, React Native's Linking API doesn't + * support handling deep links embedded in push notifications. This is due to a race condition on startup between + * the native call to RCTLinkingManager and React's loading of its JavaScript. This function provides a workaround: + * If an application is launched from a push notification click, we return any Appboy deep links in the push payload. + * @param {function(string)} callback - A callback that retuns the deep link as a string. If there is no deep link, returns null. + */ + getInitialURL: function(callback) { + if (AppboyReactBridge.getInitialUrl) { + AppboyReactBridge.getInitialUrl((err, res) => { + if (err) { + console.log(err); + callback(null); + } else { + callback(res); + } + }); + } else { + // AppboyReactBridge.getInitialUrl not implemented on Android + callback(null); + } + }, -/** - * Reports that the current user made an in-app purchase. Useful for tracking and segmenting users. - * @param {string} productId - A string identifier for the product purchased, e.g. an SKU. Value is limited to - * 255 characters in length, cannot begin with a $, and can only contain alphanumeric characters and punctuation. - * @param {float} price - The price paid. Base units depend on the currency. As an example, USD should be - * reported as Dollars.Cents, whereas JPY should be reported as a whole number of Yen. All provided - * values will be rounded to two digits with toFixed(2) - * @param {string} [currencyCode=USD] - Currencies should be represented as an ISO 4217 currency code. Supported - * currency symbols include: AED, AFN, ALL, AMD, ANG, AOA, ARS, AUD, AWG, AZN, BAM, BBD, BDT, BGN, BHD, BIF, - * BMD, BND, BOB, BRL, BSD, BTC, BTN, BWP, BYR, BZD, CAD, CDF, CHF, CLF, CLP, CNY, COP, CRC, CUC, CUP, CVE, - * CZK, DJF, DKK, DOP, DZD, EEK, EGP, ERN, ETB, EUR, FJD, FKP, GBP, GEL, GGP, GHS, GIP, GMD, GNF, GTQ, GYD, - * HKD, HNL, HRK, HTG, HUF, IDR, ILS, IMP, INR, IQD, IRR, ISK, JEP, JMD, JOD, JPY, KES, KGS, KHR, KMF, KPW, - * KRW, KWD, KYD, KZT, LAK, LBP, LKR, LRD, LSL, LTL, LVL, LYD, MAD, MDL, MGA, MKD, MMK, MNT, MOP, MRO, MTL, - * MUR, MVR, MWK, MXN, MYR, MZN, NAD, NGN, NIO, NOK, NPR, NZD, OMR, PAB, PEN, PGK, PHP, PKR, PLN, PYG, QAR, - * RON, RSD, RUB, RWF, SAR, SBD, SCR, SDG, SEK, SGD, SHP, SLL, SOS, SRD, STD, SVC, SYP, SZL, THB, TJS, TMT, - * TND, TOP, TRY, TTD, TWD, TZS, UAH, UGX, USD, UYU, UZS, VEF, VND, VUV, WST, XAF, XAG, XAU, XCD, XDR, XOF, - * XPD, XPF, XPT, YER, ZAR, ZMK, ZMW, and ZWL. Any other provided currency symbol will result in a logged - * warning and no other action taken by the SDK. - * @param {integer} [quantity=1] - The quantity of items purchased expressed as a whole number. Must be at least 1 - * and at most 100. - * @param {object} [purchaseProperties] - Hash of properties for this purchase. Keys are limited to 255 - * characters in length, cannot begin with a $, and can only contain alphanumeric characters and punctuation. - * Values can be numeric, boolean, or strings 255 characters or shorter. - */ -logPurchase: function (productId, price, currencyCode, quantity, purchaseProperties) { - AppboyReactBridge.logPurchase(productId, price, currencyCode, quantity, purchaseProperties); -}, + /** + * When a user first uses Appboy on a device they are considered "anonymous". Use this method to identify a user + * with a unique ID, which enables the following: + * + * - If the same user is identified on another device, their user profile, usage history and event history will + * be shared across devices. + * - If your app is used on the same device by multiple people, you can assign each of them a unique identifier + * to track them separately. Only the most recent user on a particular browser will receive push + * notifications and in-app messages. + * + * When you request a user switch (which is any call to changeUser where the new user ID is not the same as the + * existing user ID), the current session for the previous user (anonymous or not) is automatically ended and + * a new session is started. Similarly, following a call to changeUser, any events which fire are guaranteed to + * be for the new user -- if an in-flight server request completes for the old user after the user switch no + * events will fire, so you do not need to worry about filtering out events from Appboy for old users. + * + * Additionally, if you identify a user which has never been identified on another device, the entire history of + * that user as an "anonymous" user on this device will be preserved and associated with the newly identified + * user. However, if you identify a user which *has* been identified in another app, any history which was + * already flushed to the server for the anonymous user on this device will become orphaned and will not be + * associated with any future users. These orphaned users are not considered in your user counts and will not + * be messaged. + * + * Note: Once you identify a user, you cannot revert to the "anonymous" user. The transition from anonymous to + * identified tracking is only allowed once because the initial anonymous user receives special treatment to + * allow for preservation of their history. As a result, we recommend against changing the user ID just because + * your app has entered a "logged out" state because it makes you unable to target the previously logged out user + * with re-engagement campaigns. If you anticipate multiple users on the same device, but only want to target one + * of them when your app is in a logged out state, we recommend separately keeping track of the user ID you want + * to target while logged out and switching back to that user ID as part of your app's logout process. + * + * @param {string} userId - A unique identifier for this user. + */ + changeUser: function(userId) { + AppboyReactBridge.changeUser(userId); + }, -/** - * Submits feedback to Appboy. - * @param {string} email - The email of the user submitting feedback. - * @param {string} feedback - The content of the user feedback. - * @param {boolean} isBug - If the feedback is reporting a bug or not. - */ -submitFeedback: function (email, feedback, isBug) { - AppboyReactBridge.submitFeedback(email, feedback, isBug); -}, - -// Appboy user methods -/** - * Sets a custom user attribute. This can be any key/value pair and is used to collect extra information about the - * user. - * @param {string} key - The identifier of the custom attribute. Limited to 255 characters in length, cannot begin with - * a $, and can only contain alphanumeric characters and punctuation. - * @param value - Can be numeric, boolean, a Date object, a string, or an array of strings. Strings are limited to - * 255 characters in length, cannot begin with a $, and can only contain alphanumeric characters and punctuation. - * Passing a null value will remove this custom attribute from the user. - */ -setCustomUserAttribute: function (key, value) { - var valueType = typeof(value); - if (value instanceof Date) { - AppboyReactBridge.setDateCustomUserAttribute(key, Math.floor(value.getTime() / 1000)); + /** + * Reports that the current user performed a custom named event. + * @param {string} eventName - The identifier for the event to track. Best practice is to track generic events + * useful for segmenting, instead of specific user actions (i.e. track watched_sports_video instead of + * watched_video_adrian_peterson_td_mnf). Value is limited to 255 characters in length, cannot begin with a $, + * and can only contain alphanumeric characters and punctuation. + * @param {object} [eventProperties] - Hash of properties for this event. Keys are limited to 255 + * characters in length, cannot begin with a $, and can only contain alphanumeric characters and punctuation. + * Values can be numeric, boolean, or strings 255 characters or shorter. + */ + logCustomEvent: function(eventName, eventProperties) { + AppboyReactBridge.logCustomEvent(eventName, eventProperties); + }, + + /** + * Reports that the current user made an in-app purchase. Useful for tracking and segmenting users. + * @param {string} productId - A string identifier for the product purchased, e.g. an SKU. Value is limited to + * 255 characters in length, cannot begin with a $, and can only contain alphanumeric characters and punctuation. + * @param {float} price - The price paid. Base units depend on the currency. As an example, USD should be + * reported as Dollars.Cents, whereas JPY should be reported as a whole number of Yen. All provided + * values will be rounded to two digits with toFixed(2) + * @param {string} [currencyCode=USD] - Currencies should be represented as an ISO 4217 currency code. Supported + * currency symbols include: AED, AFN, ALL, AMD, ANG, AOA, ARS, AUD, AWG, AZN, BAM, BBD, BDT, BGN, BHD, BIF, + * BMD, BND, BOB, BRL, BSD, BTC, BTN, BWP, BYR, BZD, CAD, CDF, CHF, CLF, CLP, CNY, COP, CRC, CUC, CUP, CVE, + * CZK, DJF, DKK, DOP, DZD, EEK, EGP, ERN, ETB, EUR, FJD, FKP, GBP, GEL, GGP, GHS, GIP, GMD, GNF, GTQ, GYD, + * HKD, HNL, HRK, HTG, HUF, IDR, ILS, IMP, INR, IQD, IRR, ISK, JEP, JMD, JOD, JPY, KES, KGS, KHR, KMF, KPW, + * KRW, KWD, KYD, KZT, LAK, LBP, LKR, LRD, LSL, LTL, LVL, LYD, MAD, MDL, MGA, MKD, MMK, MNT, MOP, MRO, MTL, + * MUR, MVR, MWK, MXN, MYR, MZN, NAD, NGN, NIO, NOK, NPR, NZD, OMR, PAB, PEN, PGK, PHP, PKR, PLN, PYG, QAR, + * RON, RSD, RUB, RWF, SAR, SBD, SCR, SDG, SEK, SGD, SHP, SLL, SOS, SRD, STD, SVC, SYP, SZL, THB, TJS, TMT, + * TND, TOP, TRY, TTD, TWD, TZS, UAH, UGX, USD, UYU, UZS, VEF, VND, VUV, WST, XAF, XAG, XAU, XCD, XDR, XOF, + * XPD, XPF, XPT, YER, ZAR, ZMK, ZMW, and ZWL. Any other provided currency symbol will result in a logged + * warning and no other action taken by the SDK. + * @param {integer} [quantity=1] - The quantity of items purchased expressed as a whole number. Must be at least 1 + * and at most 100. + * @param {object} [purchaseProperties] - Hash of properties for this purchase. Keys are limited to 255 + * characters in length, cannot begin with a $, and can only contain alphanumeric characters and punctuation. + * Values can be numeric, boolean, or strings 255 characters or shorter. + */ + logPurchase: function(productId, price, currencyCode, quantity, purchaseProperties) { + AppboyReactBridge.logPurchase(productId, price, currencyCode, quantity, purchaseProperties); + }, + + /** + * Submits feedback to Appboy. + * @param {string} email - The email of the user submitting feedback. + * @param {string} feedback - The content of the user feedback. + * @param {boolean} isBug - If the feedback is reporting a bug or not. + * @param {function(error, result)} callback - A callback that receives the function call result. + */ + submitFeedback: function(email, feedback, isBug, callback) { + callFunctionWithCallback(AppboyReactBridge.submitFeedback, [email, feedback, isBug], callback); + }, + + // Appboy user methods + /** + * Sets a custom user attribute. This can be any key/value pair and is used to collect extra information about the + * user. + * @param {string} key - The identifier of the custom attribute. Limited to 255 characters in length, cannot begin with + * a $, and can only contain alphanumeric characters and punctuation. + * @param value - Can be numeric, boolean, a Date object, a string, or an array of strings. Strings are limited to + * 255 characters in length, cannot begin with a $, and can only contain alphanumeric characters and punctuation. + * Passing a null value will remove this custom attribute from the user. + * @param {function(error, result)} callback - A callback that receives the function call result. + */ + setCustomUserAttribute: function(key, value, callback) { + var valueType = typeof (value); + if (value instanceof Date) { + callFunctionWithCallback(AppboyReactBridge.setDateCustomUserAttribute, [key, Math.floor(value.getTime() / 1000)], callback); } else if (value instanceof Array) { - AppboyReactBridge.setCustomUserAttributeArray(key, value); - } else if (valueType === "boolean") { - AppboyReactBridge.setBoolCustomUserAttribute(key, value); - } else if (valueType === "string") { - AppboyReactBridge.setStringCustomUserAttribute(key, value); - } else if (valueType === "number") { + callFunctionWithCallback(AppboyReactBridge.setCustomUserAttributeArray, [key, value], callback); + } else if (valueType === 'boolean') { + callFunctionWithCallback(AppboyReactBridge.setBoolCustomUserAttribute, [key, value], callback); + } else if (valueType === 'string') { + callFunctionWithCallback(AppboyReactBridge.setStringCustomUserAttribute, [key, value], callback); + } else if (valueType === 'number') { if (parseInt(value) === parseFloat(value)) { - AppboyReactBridge.setIntCustomUserAttribute(key, value); + callFunctionWithCallback(AppboyReactBridge.setIntCustomUserAttribute, [key, value], callback); } else { - AppboyReactBridge.setDoubleCustomUserAttribute(key, value); + callFunctionWithCallback(AppboyReactBridge.setDoubleCustomUserAttribute, [key, value], callback); } } -}, + }, -/** - * Increment/decrement the value of a custom attribute. Only numeric custom attributes can be incremented. Attempts to - * increment a custom attribute that is not numeric be ignored. If you increment a custom attribute that has not - * previously been set, a custom attribute will be created and assigned the value of incrementValue. To decrement - * the value of a custom attribute, use a negative incrementValue. - * @param {string} key - The identifier of the custom attribute. Limited to 255 characters in length, cannot begin with - * a $, and can only contain alphanumeric characters and punctuation. - * @param {integer} - May be negative to decrement. - */ -incrementCustomUserAttribute: function (key, value) { - AppboyReactBridge.incrementCustomUserAttribute(key, value); -}, + /** + * Increment/decrement the value of a custom attribute. Only numeric custom attributes can be incremented. Attempts to + * increment a custom attribute that is not numeric be ignored. If you increment a custom attribute that has not + * previously been set, a custom attribute will be created and assigned the value of incrementValue. To decrement + * the value of a custom attribute, use a negative incrementValue. + * @param {string} key - The identifier of the custom attribute. Limited to 255 characters in length, cannot begin with + * a $, and can only contain alphanumeric characters and punctuation. + * @param {integer} value - May be negative to decrement. + * @param {function(error, result)} callback - A callback that receives the function call result. + */ + incrementCustomUserAttribute: function(key, value, callback) { + callFunctionWithCallback(AppboyReactBridge.incrementCustomUserAttribute, [key, value], callback); + }, -/** - * Sets the first name of the user. - * @param {string} firstName - Limited to 255 characters in length. - */ -setFirstName: function (firstName) { - AppboyReactBridge.setFirstName(firstName); -}, + /** + * Sets the first name of the user. + * @param {string} firstName - Limited to 255 characters in length. + */ + setFirstName: function(firstName) { + AppboyReactBridge.setFirstName(firstName); + }, -/** - * Sets the last name of the user. - * @param {string} lastName - Limited to 255 characters in length. - */ -setLastName: function (lastName) { - AppboyReactBridge.setLastName(lastName); -}, + /** + * Sets the last name of the user. + * @param {string} lastName - Limited to 255 characters in length. + */ + setLastName: function(lastName) { + AppboyReactBridge.setLastName(lastName); + }, -/** - * Sets the email address of the user. - * @param {string} email - Must pass RFC-5322 email address validation. - */ -setEmail: function (email) { - AppboyReactBridge.setEmail(email); -}, + /** + * Sets the email address of the user. + * @param {string} email - Must pass RFC-5322 email address validation. + */ + setEmail: function(email) { + AppboyReactBridge.setEmail(email); + }, -/** - * Sets the gender of the user. - * @param {ab.User.Genders} gender - Generally 'm' or 'f'. - */ -setGender: function (gender) { - AppboyReactBridge.setGender(gender); -}, + /** + * Sets the gender of the user. + * @param {Genders} gender - Limited to m or f + * @param {function(error, result)} callback - A callback that receives the function call result. + */ + setGender: function(gender, callback) { + callFunctionWithCallback(AppboyReactBridge.setGender, [gender], callback); + }, -/** - * Sets the country for the user. - * @param {string} country - Limited to 255 characters in length. - */ -setCountry: function (country) { - AppboyReactBridge.setCountry(country); -}, + /** + * Sets the country for the user. + * @param {string} country - Limited to 255 characters in length. + */ + setCountry: function(country) { + AppboyReactBridge.setCountry(country); + }, -/** - * Sets the home city for the user. - * @param {string} homeCity - Limited to 255 characters in length. - */ -setHomeCity: function (homeCity) { - AppboyReactBridge.setHomeCity(homeCity); -}, + /** + * Sets the home city for the user. + * @param {string} homeCity - Limited to 255 characters in length. + */ + setHomeCity: function(homeCity) { + AppboyReactBridge.setHomeCity(homeCity); + }, -/** - * Sets the phone number of the user. - * @param {string} phoneNumber - A phone number is considered valid if it is no more than 255 characters in length and - * contains only numbers, whitespace, and the following special characters +.-() - */ -setPhoneNumber: function (phoneNumber) { - AppboyReactBridge.setPhoneNumber(phoneNumber); -}, + /** + * Sets the phone number of the user. + * @param {string} phoneNumber - A phone number is considered valid if it is no more than 255 characters in length and + * contains only numbers, whitespace, and the following special characters +.-() + */ + setPhoneNumber: function(phoneNumber) { + AppboyReactBridge.setPhoneNumber(phoneNumber); + }, -/** - * Sets the url for the avatar image for the user, which will be displayed on the user profile and throughout the Appboy - * dashboard. - * @param {string} avatarImageUrl - */ -setAvatarImageUrl: function (avatarImageUrl) { - AppboyReactBridge.setAvatarImageUrl(avatarImageUrl); -}, + /** + * Sets the url for the avatar image for the user, which will be displayed on the user profile and throughout the Appboy + * dashboard. + * @param {string} avatarImageUrl + */ + setAvatarImageUrl: function(avatarImageUrl) { + AppboyReactBridge.setAvatarImageUrl(avatarImageUrl); + }, -/** - * Sets the date of birth of the user. - * @param {integer} year - * @param {integer} month - 1-12 - * @param {integer} day - */ -setDateOfBirth: function (year, month, day) { - AppboyReactBridge.setDateOfBirth(year, month, day); -}, + /** + * Sets the date of birth of the user. + * @param {integer} year + * @param {integer} month - 1-12 + * @param {integer} day + */ + setDateOfBirth: function(year, month, day) { + AppboyReactBridge.setDateOfBirth(year, month, day); + }, -/** - * Sets whether the user should be sent push campaigns. - * @param {NotificationSubscriptionTypes} notificationSubscriptionType - Notification setting (explicitly - * opted-in, subscribed, or unsubscribed). - */ -setPushNotificationSubscriptionType: function (notificationSubscriptionType) { - AppboyReactBridge.setPushNotificationSubscriptionType(notificationSubscriptionType); -}, + /** + * Sets whether the user should be sent push campaigns. + * @param {NotificationSubscriptionTypes} notificationSubscriptionType - Notification setting (explicitly + * opted-in, subscribed, or unsubscribed). + * @param {function(error, result)} callback - A callback that receives the function call result. + */ + setPushNotificationSubscriptionType: function(notificationSubscriptionType, callback) { + callFunctionWithCallback(AppboyReactBridge.setPushNotificationSubscriptionType, [notificationSubscriptionType], callback); + }, -/** - * Sets whether the user should be sent email campaigns. - * @param {NotificationSubscriptionTypes} notificationSubscriptionType - Notification setting (explicitly - * opted-in, subscribed, or unsubscribed). - */ -setEmailNotificationSubscriptionType: function (notificationSubscriptionType) { - AppboyReactBridge.setEmailNotificationSubscriptionType(notificationSubscriptionType); -}, + /** + * Sets whether the user should be sent email campaigns. + * @param {NotificationSubscriptionTypes} notificationSubscriptionType - Notification setting (explicitly + * opted-in, subscribed, or unsubscribed). + * @param {function(error, result)} callback - A callback that receives the function call result. + */ + setEmailNotificationSubscriptionType: function(notificationSubscriptionType, callback) { + callFunctionWithCallback(AppboyReactBridge.setEmailNotificationSubscriptionType, [notificationSubscriptionType], callback); + }, -/** - * Adds a string to a custom atttribute string array, or creates that array if one doesn't exist. - * @param {string} key - The identifier of the custom attribute. Limited to 255 characters in length, cannot begin with - * a $, and can only contain alphanumeric characters and punctuation. - * @param {string} value - The string to be added to the array. Strings are limited to 255 characters in length, cannot - * begin with a $, and can only contain alphanumeric characters and punctuation. - */ -addToCustomUserAttributeArray: function (key, value) { - AppboyReactBridge.addToCustomAttributeArray(key, value); -}, + /** + * Adds a string to a custom atttribute string array, or creates that array if one doesn't exist. + * @param {string} key - The identifier of the custom attribute. Limited to 255 characters in length, cannot begin with + * a $, and can only contain alphanumeric characters and punctuation. + * @param {string} value - The string to be added to the array. Strings are limited to 255 characters in length, cannot + * begin with a $, and can only contain alphanumeric characters and punctuation. + * @param {function(error, result)} callback - A callback that receives the function call result. + */ + addToCustomUserAttributeArray: function(key, value, callback) { + callFunctionWithCallback(AppboyReactBridge.addToCustomAttributeArray, [key, value], callback); + }, -/** - * Removes a string from a custom attribute string array. - * @param {string} key - The identifier of the custom attribute. Limited to 255 characters in length, cannot begin with - * a $, and can only contain alphanumeric characters and punctuation. - * @param {string} value - The string to be removed from the array. Strings are limited to 255 characters in length, - * cannot beging with a $, and can only contain alphanumeric characters and punctuation. - */ -removeFromCustomUserAttributeArray: function (key, value) { - AppboyReactBridge.removeFromCustomAttributeArray(key, value); -}, + /** + * Removes a string from a custom attribute string array. + * @param {string} key - The identifier of the custom attribute. Limited to 255 characters in length, cannot begin with + * a $, and can only contain alphanumeric characters and punctuation. + * @param {string} value - The string to be removed from the array. Strings are limited to 255 characters in length, + * cannot beging with a $, and can only contain alphanumeric characters and punctuation. + * @param {function(error, result)} callback - A callback that receives the function call result. + */ + removeFromCustomUserAttributeArray: function(key, value, callback) { + callFunctionWithCallback(AppboyReactBridge.removeFromCustomAttributeArray, [key, value], callback); + }, -/** - * Unsets a custom user attribute. - * @param {string} key - The identifier of the custom attribute. Limited to 255 characters in length, cannot begin with - * a $, and can only contain alphanumeric characters and punctuation. - */ -unsetCustomUserAttribute: function (key) { - AppboyReactBridge.unsetCustomUserAttribute(key); -}, - -// Other -/** - * Launches the News Feed UI element. - */ -launchNewsFeed: function () { - AppboyReactBridge.launchNewsFeed(); -}, + /** + * Unsets a custom user attribute. + * @param {string} key - The identifier of the custom attribute. Limited to 255 characters in length, cannot begin with + * a $, and can only contain alphanumeric characters and punctuation. + * @param {function(error, result)} callback - A callback that receives the function call result. + */ + unsetCustomUserAttribute: function(key, callback) { + callFunctionWithCallback(AppboyReactBridge.unsetCustomUserAttribute, [key], callback); + }, -/** - * Launches the Feedback UI element. Not currently supported on Android. - */ -launchFeedback: function () { - AppboyReactBridge.launchFeedback(); -}, - -NotificationSubscriptionTypes: { - "OPTED_IN": 'opted_in', - "SUBSCRIBED": 'subscribed', - "UNSUBSCRIBED": 'unsubscribed' -}, - -Genders: { - "MALE": 'm', - "FEMALE": 'f' + /** + * Sets user Twitter data. + * + * @param {integer} id - The Twitter user Id. May not be null. + * @param {string} screenName - The user's Twitter handle + * @param {string} name - The user's name + * @param {string} description - A description of the user + * @param {integer} followersCount - Number of Twitter users following this user. May not be null. + * @param {integer} friendsCount - Number of Twitter users this user is following. May not be null. + * @param {integer} statusesCount - Number of Tweets by this user. May not be null. + * @param {string} profileImageUrl - Link to profile image + */ + setTwitterData: function(id, screenName, name, description, followersCount, friendsCount, statusesCount, profileImageUrl) { + if (id !== null && followersCount !== null && friendsCount !== null && statusesCount !== null) { + AppboyReactBridge.setTwitterData(id, screenName, name, description, followersCount, friendsCount, statusesCount, profileImageUrl); + } else { + console.log('Invalid null argument(s) passed to ReactAppboy.setTwitterData(). Twitter data not set.'); + } + }, + + /** + * Sets user Facebook data. + * + * @param {object} facebookUserDictionary - The dictionary returned from facebook with facebook graph api endpoint "/me". Please + * refer to https://developers.facebook.com/docs/graph-api/reference/v2.7/user for more information. + * Note: Android only supports the firstName, lastName, email, bio, cityName, gender, and birthday fields. All other user fields will be dropped. + * @param {integer} numberOfFriends - The length of the friends array from facebook. You can get the array from the dictionary returned + * from facebook with facebook graph api endpoint "/me/friends", under the key "data". Please refer to + * https://developers.facebook.com/docs/graph-api/reference/v2.7/user/friends for more information. May not be null. + * @param {Array} likes - The array of user's facebook likes from facebook. You can get the array from the dictionary returned + * from facebook with facebook graph api endpoint "/me/likes", under the key "data"; Please refer to + * https://developers.facebook.com/docs/graph-api/reference/v2.7/user/likes for more information. + */ + setFacebookData: function(facebookUserDictionary, numberOfFriends, likes) { + if (numberOfFriends !== null) { + AppboyReactBridge.setFacebookData(facebookUserDictionary, numberOfFriends, likes); + } else { + console.log('Invalid null argument(s) passed to ReactAppboy.setFacebookData(). Facebook data not set.'); + } + }, + + // News Feed + /** + * Launches the News Feed UI element. + */ + launchNewsFeed: function() { + AppboyReactBridge.launchNewsFeed(); + }, + + /** + * Returns the current number of News Feed cards for the given category. + * @param {CardCategory} category - Card category. Use ReactAppboy.CardCategory.ALL to get the total card count. + * @param {function(error, result)} callback - A callback that receives the function call result. + * Note that for Android, a successful result relies on a FeedUpdatedEvent being posted at least once. There is also a slight + * race condition around calling changeUser, which requests a feed refresh, so the counts may not always be accurate. + */ + getCardCountForCategories: function(category, callback) { + callFunctionWithCallback(AppboyReactBridge.getCardCountForCategories, [category], callback); + }, + + /** + * Returns the number of unread News Feed cards for the given category. + * @param {CardCategory} category - Card category. Use ReactAppboy.CardCategory.ALL to get the total unread card count. + * @param {function(error, result)} callback - A callback that receives the function call result. + * Note that for Android, a successful result relies on a FeedUpdatedEvent being posted at least once. There is also a slight + * race condition around calling changeUser, which requests a feed refresh, so the counts may not always be accurate. + */ + getUnreadCardCountForCategories: function(category, callback) { + callFunctionWithCallback(AppboyReactBridge.getUnreadCardCountForCategories, [category], callback); + }, + + // Feedback + /** + * Launches the Feedback UI element. Not currently supported on Android. + */ + launchFeedback: function() { + AppboyReactBridge.launchFeedback(); + }, + + // Enums + CardCategory: { + 'ADVERTISING': 'advertising', + 'ANNOUNCEMENTS': 'announcements', + 'NEWS': 'news', + 'SOCIAL': 'social', + 'NO_CATEGORY': 'no_category', + 'ALL': 'all' + }, + + NotificationSubscriptionTypes: { + 'OPTED_IN': 'opted_in', + 'SUBSCRIBED': 'subscribed', + 'UNSUBSCRIBED': 'unsubscribed' + }, + + Genders: { + 'MALE': 'm', + 'FEMALE': 'f' } -} +}; module.exports = ReactAppboy; diff --git a/package.json b/package.json index 7689e1e..83e8bd2 100644 --- a/package.json +++ b/package.json @@ -27,5 +27,19 @@ "bugs": { "url": "https://github.com/Appboy/appboy-react-sdk/issues" }, - "homepage": "https://github.com/Appboy/appboy-react-sdk#readme" + "homepage": "https://github.com/Appboy/appboy-react-sdk#readme", + "devDependencies": { + "eslint": "^3.4.0", + "eslint-config-standard": "^6.0.0", + "eslint-config-standard-jsx": "^3.0.0", + "eslint-config-standard-react": "^4.0.0", + "eslint-plugin-promise": "^2.0.1", + "eslint-plugin-react": "^6.2.0", + "eslint-plugin-standard": "^2.0.0", + "i": "^0.3.5" + }, + "scripts": { + "lint": "./node_modules/.bin/eslint index.js */**.js", + "lint-fix": "./node_modules/.bin/eslint --fix index.js */**.js" + } }