From ae1d299edb7a97808920e6ad981a271c7c9468ff Mon Sep 17 00:00:00 2001 From: Frederic Barthelemy Date: Wed, 24 Jun 2020 17:11:42 -0700 Subject: [PATCH 01/14] Add implementation group: option for ANdroid Manual Install --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index d5b9389..8fbb238 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,14 @@ project(':react-native-zendesk-chat').projectDir = new File(rootProject.projectD 3. Insert the following lines inside the dependencies block in `android/app/build.gradle`: +For RN >= 0.60: + +```gradle +implementation group: "com.zopim.android", name: "sdk", version: '1.4.9' +``` + +For RN < 0.60: + ```gradle compile project(':react-native-zendesk-chat') ``` From a76e3e5b0856429e5e13d8dad170479838c93a3f Mon Sep 17 00:00:00 2001 From: Frederic Barthelemy Date: Thu, 2 Jul 2020 16:45:13 -0700 Subject: [PATCH 02/14] ZendeskChatSDK V2 - iOS WIP --- .vscode/settings.json | 8 +- RNZendeskChat.d.ts | 66 ++++++++++--- RNZendeskChat.podspec | 2 +- ios/RNZendeskChatModule.m | 200 ++++++++++++++++++++++++++++++++------ package.json | 2 +- 5 files changed, 231 insertions(+), 47 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 997137e..e729147 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,5 +9,11 @@ "zanechua", "zendesk", "zopim" - ] + ], + "files.exclude": { + "**/.classpath": true, + "**/.project": true, + "**/.settings": true, + "**/.factorypath": true + } } diff --git a/RNZendeskChat.d.ts b/RNZendeskChat.d.ts index 2d759d3..1a49e86 100644 --- a/RNZendeskChat.d.ts +++ b/RNZendeskChat.d.ts @@ -3,29 +3,71 @@ declare module "react-native-zendesk-chat" { name?: string; email?: string; phone?: string; + } + + interface MessagingOptions_iOS { + /** Set this to set the bot's displayName */ + botName?: string; + /** Will be loaded using [UIImage imageWithName:…] ) */ + botImageName?: string; + /** Will be loaded using [UIImage imageWithUrl:…] */ + botImageUrl?: string; + } - /** only implemented on iOS */ - shouldPersist?: boolean; + const enum PreChatFormFieldOptionVisibility { + hidden = "hidden", + optional = "optional", + required = "required", } - interface StartChatOptions_iOS extends VisitorInfoOptions { + + interface StartChatOptions extends VisitorInfoOptions { department?: string; tags?: string[]; - // Flags to disable some fields collected by default. - emailNotRequired?: boolean; - phoneNotRequired?: boolean; - departmentNotRequired?: boolean; - messageNotRequired?: boolean; + behaviorFlags?: { + /** if omitted, the form is enabled */ + showPreChatForm?: boolean; + /** if omitted, the prompt is enabled */ + showChatTranscriptPrompt?: boolean; + /** if omitted, the form is enabled */ + showOfflineForm?: boolean; + /** if omitted, the agent availability message is enabled */ + showAgentAvailability?: boolean; + }; + + /** If omitted, the preChatForm will be left as default in Zendesk */ + preChatFormOptions?: { + /** Should we ask the user for a contact email? */ + email?: PreChatFormFieldOptionVisibility; + /** Should we ask the user their name? */ + name?: PreChatFormFieldOptionVisibility; + /** Should we ask the user for their phone number? */ + phoneNumber?: PreChatFormFieldOptionVisibility; + /** Should we ask the user which department? */ + department?: PreChatFormFieldOptionVisibility; + }; + + /** + * Configure the Chat-Bot (if any) + */ + messagingOptions?: MessagingOptions_iOS; + + /** + * If not provided, this will be "Close" -- not localized! + */ + localizedDismissButtonTitle?: string; } - type StartChatOptions_Android = VisitorInfoOptions; - type StartChatOptions = StartChatOptions_iOS & StartChatOptions_Android; class RNZendeskChatModuleImpl { init: (zendeskAccountKey: string) => void; - setVisitorInfo: (options: VisitorInfoOptions) => void; - startChat: (options: StartChatOptions) => void; + /** + * Backwards Compatibility! + * - You can pass all these parameters to RNZendeskChatModule.startChat + * - So you should probably prefer that method + */ + setVisitorInfo: (options: VisitorInfoOptions) => void; } const RNZendeskChatModule: RNZendeskChatModuleImpl; diff --git a/RNZendeskChat.podspec b/RNZendeskChat.podspec index b208b60..bdf2d3d 100644 --- a/RNZendeskChat.podspec +++ b/RNZendeskChat.podspec @@ -18,5 +18,5 @@ Pod::Spec.new do |s| s.framework = 'UIKit' s.dependency 'React' - s.dependency 'ZDCChat' + s.dependency 'ZendeskChatSDK' end \ No newline at end of file diff --git a/ios/RNZendeskChatModule.m b/ios/RNZendeskChatModule.m index 4bde920..7cd3aad 100644 --- a/ios/RNZendeskChatModule.m +++ b/ios/RNZendeskChatModule.m @@ -3,53 +3,189 @@ // Tasker // // Created by Jean-Richard Lai on 11/23/15. -// Copyright © 2015 Facebook. All rights reserved. // + #import "RNZendeskChatModule.h" -#import + +#import +#import + +#import +#import +#import +#import + + +@implementation RCTConvert (ZDKChatFormFieldStatus) + +RCT_ENUM_CONVERTER(ZDKFormFieldStatus, + (@{ + @"required": @(ZDKFormFieldStatusRequired), + @"optional": @(ZDKFormFieldStatusOptional), + @"hidden": @(ZDKFormFieldStatusHidden), + }), + ZDKFormFieldStatusOptional, + integerValue); + +@end + +@interface RNZendeskChatModule () +@end @implementation RNZendeskChatModule +// Backwards compatibility with the unnecessary setVisitorInfo method +ZDKChatAPIConfiguration *_visitorAPIConfig; + RCT_EXPORT_MODULE(RNZendeskChatModule); RCT_EXPORT_METHOD(setVisitorInfo:(NSDictionary *)options) { - [ZDCChat updateVisitor:^(ZDCVisitorInfo *visitor) { - if (options[@"name"]) { - visitor.name = options[@"name"]; - } - if (options[@"email"]) { - visitor.email = options[@"email"]; - } - if (options[@"phone"]) { - visitor.phone = options[@"phone"]; - } - visitor.shouldPersist = [options[@"shouldPersist"] boolValue] || NO; - }]; + if (!NSThread.isMainThread) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self setVisitorInfo:options]; + }); + return; + } + + ZDKChat.instance.configuration = _visitorAPIConfig = [self applyVisitorInfo:options intoConfig: _visitorAPIConfig ?: [[ZDKChatAPIConfiguration alloc] init]]; +} + +- (ZDKChatAPIConfiguration*)applyVisitorInfo:(NSDictionary*)options intoConfig:(ZDKChatAPIConfiguration*)config { + if (options[@"department"]) { + config.department = options[@"department"]; + } + if (options[@"tags"]) { + config.tags = options[@"tags"]; + } + config.visitorInfo = [[ZDKVisitorInfo alloc] initWithName:options[@"name"] + email:options[@"email"] + phoneNumber:options[@"phone"]]; + + NSLog(@"[RNZendeskChatModule] Applied visitor info: department: %@ tags: %@, email: %@, name: %@, phone: %@", config.department, config.tags, config.visitorInfo.email, config.visitorInfo.name, config.visitorInfo.phoneNumber); + return config; +} + +#define RNZDKConfigHashErrorLog(options, what)\ +if (!!options) {\ + NSLog(@"[RNZendeskChatModule] Invalid %@ -- expected a config hash", what);\ +} + +- (ZDKMessagingConfiguration *)messagingConfigurationFromConfig:(NSDictionary*)options { + ZDKMessagingConfiguration *config = [[ZDKMessagingConfiguration alloc] init]; + if (!options || ![options isKindOfClass:NSDictionary.class]) { + RNZDKConfigHashErrorLog(options, @"MessagingConfiguration config options"); + return config; + } + if (options[@"name"]) { + config.name = options[@"name"]; + } + + if (options[@"botImageName"]) { + config.botAvatar = [UIImage imageNamed:@"botImageName"]; + } else if (options[@"botImageUrl"]) { + config.botAvatar = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:options[@"botImageUrl"]]]]; + } + + return config; +} + +- (ZDKChatFormConfiguration * _Nullable)preChatFormConfigurationFromConfig:(NSDictionary*)options { + if (!options || ![options isKindOfClass:NSDictionary.class]) { + RNZDKConfigHashErrorLog(options, @"pre-Chat-Form Configuration Options"); + return nil; + } +#define ParseFormFieldStatus(key)\ + ZDKFormFieldStatus key = [RCTConvert ZDKFormFieldStatus:options[@"" #key]] + ParseFormFieldStatus(name); + ParseFormFieldStatus(email); + ParseFormFieldStatus(phoneNumber); + ParseFormFieldStatus(department); +#undef ParseFormFieldStatus + ZDKChatFormConfiguration * config = [[ZDKChatFormConfiguration alloc] initWithName:name + email:email + phoneNumber:phoneNumber + department:department]; + return config; +} +- (ZDKChatConfiguration *)chatConfigurationFromConfig:(NSDictionary*)options { + options = options ?: @{}; + + ZDKChatConfiguration* config = [[ZDKChatConfiguration alloc] init]; + if (![options isKindOfClass:NSDictionary.class]){ + RNZDKConfigHashErrorLog(options, @"Chat Configuration Options"); + return config; + } + NSDictionary * behaviorFlags = options[@"behaviorFlags"]; + if (!behaviorFlags || ![behaviorFlags isKindOfClass:NSDictionary.class]) { + RNZDKConfigHashErrorLog(behaviorFlags, @"BehaviorFlags -- expected a config hash"); + behaviorFlags = NSDictionary.dictionary; + } + +#define ParseBehaviorFlag(key, target)\ +config.target = [RCTConvert BOOL: behaviorFlags[@"" #key] ?: @YES] + ParseBehaviorFlag(showPreChatForm, isPreChatFormEnabled); + ParseBehaviorFlag(showChatTranscriptPrompt, isChatTranscriptPromptEnabled); + ParseBehaviorFlag(showOfflineForm, isOfflineFormEnabled); + ParseBehaviorFlag(showAgentAvailability, isAgentAvailabilityEnabled); +#undef ParseBehaviorFlag + + if (config.isPreChatFormEnabled) { + config.preChatFormConfiguration = [self preChatFormConfigurationFromConfig:options[@"preChatFormOptions"]]; + } + return config; } RCT_EXPORT_METHOD(startChat:(NSDictionary *)options) { - [self setVisitorInfo:options]; - - dispatch_sync(dispatch_get_main_queue(), ^{ - [ZDCChat startChat:^(ZDCConfig *config) { - if (options[@"department"]) { - config.department = options[@"department"]; - } - if (options[@"tags"]) { - config.tags = options[@"tags"]; - } - config.preChatDataRequirements.name = ZDCPreChatDataRequired; - config.preChatDataRequirements.email = options[@"emailNotRequired"] ? ZDCPreChatDataNotRequired : ZDCPreChatDataRequired; - config.preChatDataRequirements.phone = options[@"phoneNotRequired"] ? ZDCPreChatDataNotRequired : ZDCPreChatDataRequired; - config.preChatDataRequirements.department = options[@"departmentNotRequired"] ? ZDCPreChatDataNotRequired : ZDCPreChatDataRequiredEditable; - config.preChatDataRequirements.message = options[@"messageNotRequired"] ? ZDCPreChatDataNotRequired : ZDCPreChatDataRequired; - }]; - }); + if (!options || ![options isKindOfClass: NSDictionary.class]) { + if (!!options){ + NSLog(@"[RNZendeskChatModule] Invalid JS startChat Configuration Options -- expected a config hash"); + } + options = NSDictionary.dictionary; + } + + dispatch_sync(dispatch_get_main_queue(), ^{ + + ZDKChat.instance.configuration = [self applyVisitorInfo:options + intoConfig: _visitorAPIConfig ?: [[ZDKChatAPIConfiguration alloc] init]]; + + ZDKChatConfiguration * chatConfig = [self chatConfigurationFromConfig:options]; + + NSError *error = nil; + NSArray *engines = @[ + [ZDKChatEngine engineAndReturnError:&error] + ]; + if (!!error) { + NSLog(@"[RNZendeskChatModule] Internal Error loading ZDKChatEngine %@", error); + return; + } + + ZDKMessagingConfiguration *messagingConfig = [self messagingConfigurationFromConfig: options[@"messagingOptions"]]; + + UIViewController *viewController = [ZDKMessaging.instance buildUIWithEngines:engines + configs:@[chatConfig, messagingConfig] + error:&error]; + if (!!error) { + NSLog(@"[RNZendeskChatModule] Internal Error building ZDKMessagingUI %@",error); + return; + } + + viewController.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle: options[@"localizedDismissButtonTitle"] ?: @"Close" + style: UIBarButtonItemStylePlain + target: self + action: @selector(dismissChatUI)]; + + UINavigationController *chatController = [[UINavigationController alloc] initWithRootViewController: viewController]; + [RCTPresentedViewController() presentViewController:chatController animated:YES completion:nil]; + }); +} + +- (void) dismissChatUI { + [RCTPresentedViewController() dismissViewControllerAnimated:YES completion:nil]; } RCT_EXPORT_METHOD(init:(NSString *)zenDeskKey) { - [ZDCChat initializeWithAccountKey:zenDeskKey]; + [ZDKChat initializeWithAccountKey:zenDeskKey queue:dispatch_get_main_queue()]; } @end diff --git a/package.json b/package.json index 8dbbb29..98289db 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,6 @@ "devDependencies": { "prettier": "2.0.x", "eslint": "7.3.x" }, "peerDependencies": { "react": "^16.11.0", - "react-native": "^0.60.0" + "react-native": "0.60.x | 0.61.x | 0.62.x" } } From b37fb843fdde3e7bdb53a536907ec40d661c30f6 Mon Sep 17 00:00:00 2001 From: Frederic Barthelemy Date: Sat, 4 Jul 2020 11:21:48 -0700 Subject: [PATCH 03/14] Zendesk Swift Code isn't nil-safe --- ios/RNZendeskChatModule.m | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/ios/RNZendeskChatModule.m b/ios/RNZendeskChatModule.m index 7cd3aad..8fe1f35 100644 --- a/ios/RNZendeskChatModule.m +++ b/ios/RNZendeskChatModule.m @@ -102,11 +102,10 @@ - (ZDKChatFormConfiguration * _Nullable)preChatFormConfigurationFromConfig:(NSDi ParseFormFieldStatus(phoneNumber); ParseFormFieldStatus(department); #undef ParseFormFieldStatus - ZDKChatFormConfiguration * config = [[ZDKChatFormConfiguration alloc] initWithName:name - email:email - phoneNumber:phoneNumber - department:department]; - return config; + return [[ZDKChatFormConfiguration alloc] initWithName:name + email:email + phoneNumber:phoneNumber + department:department]; } - (ZDKChatConfiguration *)chatConfigurationFromConfig:(NSDictionary*)options { options = options ?: @{}; @@ -131,7 +130,11 @@ - (ZDKChatConfiguration *)chatConfigurationFromConfig:(NSDictionary*)options { #undef ParseBehaviorFlag if (config.isPreChatFormEnabled) { - config.preChatFormConfiguration = [self preChatFormConfigurationFromConfig:options[@"preChatFormOptions"]]; + ZDKChatFormConfiguration * formConfig = [self preChatFormConfigurationFromConfig:options[@"preChatFormOptions"]]; + if (!!formConfig) { + // Zendesk Swift Code crashes if you provide a nil form + config.preChatFormConfiguration = formConfig; + } } return config; } From a3c892a68fafd2c14d49cbb4335d1158e0b816d6 Mon Sep 17 00:00:00 2001 From: Frederic Barthelemy Date: Sun, 5 Jul 2020 12:46:17 -0700 Subject: [PATCH 04/14] Avoid unnecessary breaking change --- ios/RNZendeskChatModule.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/RNZendeskChatModule.m b/ios/RNZendeskChatModule.m index 8fe1f35..0b7ca0e 100644 --- a/ios/RNZendeskChatModule.m +++ b/ios/RNZendeskChatModule.m @@ -99,12 +99,12 @@ - (ZDKChatFormConfiguration * _Nullable)preChatFormConfigurationFromConfig:(NSDi ZDKFormFieldStatus key = [RCTConvert ZDKFormFieldStatus:options[@"" #key]] ParseFormFieldStatus(name); ParseFormFieldStatus(email); - ParseFormFieldStatus(phoneNumber); + ParseFormFieldStatus(phone); ParseFormFieldStatus(department); #undef ParseFormFieldStatus return [[ZDKChatFormConfiguration alloc] initWithName:name email:email - phoneNumber:phoneNumber + phoneNumber:phone department:department]; } - (ZDKChatConfiguration *)chatConfigurationFromConfig:(NSDictionary*)options { From bdd6cd0916a238ce8a97c0fbe7ffd4f495d6fbc8 Mon Sep 17 00:00:00 2001 From: Frederic Barthelemy Date: Sun, 5 Jul 2020 12:47:06 -0700 Subject: [PATCH 05/14] Android SDK V2, first pass -- not working yet! --- android/build.gradle | 4 +- .../zendesk/RNZendeskChatModule.java | 193 ++++++++++++++++-- 2 files changed, 178 insertions(+), 19 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 1b58419..fb926cd 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -36,5 +36,7 @@ repositories { dependencies { implementation "com.facebook.react:react-native:+" - implementation group: 'com.zopim.android', name: 'sdk', version: '1.4.8' + + api group: 'com.zendesk', name: 'chat', version: '2.2.0' + api group: 'com.zendesk', name: 'messaging', version: '4.3.1' } diff --git a/android/src/main/java/com/taskrabbit/zendesk/RNZendeskChatModule.java b/android/src/main/java/com/taskrabbit/zendesk/RNZendeskChatModule.java index 6a2b6a4..4dc4b2c 100644 --- a/android/src/main/java/com/taskrabbit/zendesk/RNZendeskChatModule.java +++ b/android/src/main/java/com/taskrabbit/zendesk/RNZendeskChatModule.java @@ -7,6 +7,7 @@ import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.zopim.android.sdk.api.ZopimChat; import com.zopim.android.sdk.prechat.PreChatForm; @@ -14,8 +15,101 @@ import com.zopim.android.sdk.prechat.ZopimChatActivity; import java.lang.String; +import java.util.ArrayList; public class RNZendeskChatModule extends ReactContextBaseJavaModule { + private static final String TAG = "[RNZendeskChatModule]"; + + private ArrayList currentUserTags = new ArrayList(); + + // private class Converters { + public static ArrayList getArrayListOfStrings(ReadableMap options, String key, String functionHint) { + ArrayList result = new ArrayList(); + + if (!options.hasKey(key)) { + return result; + } + if (options.getType(key) != ReadableArray) { + Log.e(RNZendeskChatModule.TAG, "wrong type for key '" + key + "' when processing " + functionHint + + ", expected an Array of Strings."); + return result; + } + ReadableArray arr = options.getArray(key); + for (int i = 0; i < arr.size(); i++) { + if (arr.isNull(i)) { + continue; + } + if (arr.getType() != String) { + Log.e(RNZendeskChatModule.TAG, "wrong type for key '" + key + "[" + i + "]' when processing " + + functionHint + ", expected entry to be a String."); + } + result.add(arr.getString(i)); + } + return result; + } + + public static String getStringOrNull(ReadableMap options, String key, String functionHint) { + if (!options.hasKey(key)) { + return null; + } + if (options.getType(key) != String) { + Log.e(RNZendeskChatModule.TAG, + "wrong type for key '" + key + "' when processing " + functionHint + ", expected a String."); + return null; + } + return options.getString(key); + } + + public static boolean getBooleanOrDefault(ReadableMap options, String key, String functionHint, + boolean defaultValue) { + if (!options.hasKey(key)) { + return defaultValue; + } + if (options.getType(key) != Boolean) { + Log.e(RNZendeskChatModule.TAG, + "wrong type for key '" + key + "' when processing " + functionHint + ", expected a Boolean."); + return defaultValue; + } + return options.getBoolean(key); + } + + public static PreChatFormFieldStatus getFieldStatusOrDefault(ReadableMap options, String key, + PreChatFormFieldStatus defaultValue) { + if (!options.hasKey(key)) { + return defaultValue; + } + if (options.getType(key) != String) { + Log.e(RNZendeskChatModule.TAG, "wrong type for key '" + key + + "' when processing startChat(preChatFormOptions), expected one of ('required' | 'optional' | 'hidden')."); + return defaultValue; + } + switch (options.getString(key)) { + case "required": + return PreChatFormFieldStatus.REQUIRED; + case "optional": + return PreChatFormFieldStatus.OPTIONAL; + case "hidden": + return PreChatFormFieldStatus.HIDDEN; + default: + Log.e(RNZendeskChatModule.TAG, "wrong type for key '" + key + + "' when processing startChat(preChatFormOptions), expected one of ('required' | 'optional' | 'hidden')."); + return defaultValue; + } + } + + public static ReadableMap getReadableMap(ReadableMap options, String key, String functionHint) { + if (!options.hasKey(key)) { + return new ReadableMap(); + } + if (options.getType(key) != ReadableMap) { + Log.e(RNZendeskChatModule.TAG, + "wrong type for key '" + key + "' when processing " + functionHint + ", expected a config hash."); + return new ReadableMap(); + } + return options.getMap(key); + } + // } + private ReactContext mReactContext; public RNZendeskChatModule(ReactApplicationContext reactContext) { @@ -32,41 +126,104 @@ public String getName() { public void setVisitorInfo(ReadableMap options) { VisitorInfo.Builder builder = new VisitorInfo.Builder(); - if (options.hasKey("name")) { - builder.name(options.getString("name")); + String name = getStringOrNull(options, "name", "visitorInfo"); + if (name != null) { + builder = builder.withName(name); } - if (options.hasKey("email")) { - builder.email(options.getString("email")); + String email = getStringOrNull(options, "email", "visitorInfo"); + if (email != null) { + builder = builder.withEmail(name); } - if (options.hasKey("phone")) { - builder.phoneNumber(options.getString("phone")); + String phone = getStringOrNull(options, "phone", "visitorInfo"); + if (phone != null) { + builder = builder.withPhoneNumber(phone); } VisitorInfo visitorData = builder.build(); - ZopimChat.setVisitorInfo(visitorData); + Chat.INSTANCE.providers().profileProvider().setVisitorInfo(visitorData); } @ReactMethod public void init(String key) { - PreChatForm defaultPreChat = new PreChatForm.Builder() - .name(PreChatForm.Field.REQUIRED) - .email(PreChatForm.Field.REQUIRED) - .phoneNumber(PreChatForm.Field.REQUIRED) - .department(PreChatForm.Field.REQUIRED_EDITABLE) - .message(PreChatForm.Field.REQUIRED) - .build(); - ZopimChat.init(key) - .preChatForm(defaultPreChat) - .build(); + Chat.INSTANCE.init(mReactContext, key); + } + + private ChatConfiguration.Builder loadBehaviorFlags(ChatConfiguration.Builder b, ReadableMap options) { + boolean defaultValue = true; + String logHint = "startChat(behaviorFlags)"; + + return b.withPreChatFormEnabled(getBooleanOrDefault(options, "showPreChatForm", logHint, defaultValue)) + .withTranscriptEnabled(getBooleanOrDefault(options, "showChatTranscriptPrompt", logHint, defaultValue)) + .withOfflineFormEnabled(getBooleanOrDefault(options, "showOfflineForm", logHint, defaultValue)) + .withAgentAvailabilityEnabled( + getBooleanOrDefault(options, "showAgentAvailability", logHint, defaultValue)); + } + + private ChatConfiguration.Builder loadPreChatFormConfiguration(ChatConfiguration.Builder b, ReadableMap options) { + PreChatFormFieldStatus defaultValue = preChatFormFieldStatus.OPTIONAL; + return b.withNameFieldStatus(getFieldStatusOrDefault(options, "name", defaultValue)) + .withEmailFieldStatus(getFieldStatusOrDefault(options, "email", defaultValue)) + .withPhoneFieldStatus(getFieldStatusOrDefault(options, "phone", defaultValue)) + .withDepartmentFieldStatus(getFieldStatusOrDefault(options, "department", defaultValue)); + } + + private void loadTags(ReadableMap options) { + // ZendeskChat Android treats the tags persistently, so you have to add/remove + // as things change -- aka doing a diff :-( + // ZendeskChat iOS just lets you override the full array so this isn't + // necessary on that side. + + ChatProfileProvider profileProvider = Chat.INSTANCE.providers().profileProvider(); + ArrayList activeTags = currentUserTags.clone(); + + ArrayList allProvidedTags = RNZendeskChatModule.getArrayListOfStrings(options, "tags", "startChat"); + ArrayList newlyIntroducedTags = allProvidedTags.clone(); + + newlyIntroducedTags.remove(activeTags); // Now just includes tags to add + currentUserTags.removeAll(allProvidedTags); // Now just includes tags to delete + + if (!currentUserTags.isEmpty()) { + profileProvider.removeVisitorTags(currentUserTags); + } + if (!newlyIntroducedTags.isEmpty()) { + profileProvider.addVisitorTags(newlyIntroducedTags); + } + + currentUserTags = allProvidedTags; + } + + private Object loadBotSettings(ReadableMap options, Object builder) { + return builder; } @ReactMethod public void startChat(ReadableMap options) { setVisitorInfo(options); + + ReadableMap flagHash = RNZendeskChatModule.getReadableMap(options, "behaviorFlags", "startChat"); + boolean showPreChatForm = getBooleanOrDefault(flagHash, "showPreChatForm", "startChat(behaviorFlags)", true); + + ChatConfiguration.Builder chatBuilder = loadBehaviorFlags(ChatConfiguration.builder(), flagHash); + if (showPreChatForm) { + chatBuilder = loadPreChatFormConfiguration(chatBuilder, + getReadableMap(options, "preChatFormOptions", "startChat")); + } + ChatConfiguration = chatBuilder.build(); + + String department = RNZendeskChatModule.getStringOrNull(options, "department", "startChat"); + if (department) { + Chat.INSTANCE.providers().chatProvider().setDepartment(department, null); + } + + loadTags(options); + + Object messagingBuilder = loadBotSettings(getReadableMap(options, "messagingOptions", "startChat"), + MessagingActivity.builder()); + Activity activity = getCurrentActivity(); if (activity != null) { - activity.startActivity(new Intent(mReactContext, ZopimChatActivity.class)); + messagingBuilder.withEngines(ChatEngine.engine()).show(view.getContext()); } } } From 34fe08028391a20f73abfd118dcdd40cbf6a7748 Mon Sep 17 00:00:00 2001 From: Frederic Barthelemy Date: Mon, 6 Jul 2020 10:18:30 -0700 Subject: [PATCH 06/14] Android: Compilation Fixes --- .../zendesk/RNZendeskChatModule.java | 103 +++++++++++++----- 1 file changed, 76 insertions(+), 27 deletions(-) diff --git a/android/src/main/java/com/taskrabbit/zendesk/RNZendeskChatModule.java b/android/src/main/java/com/taskrabbit/zendesk/RNZendeskChatModule.java index 4dc4b2c..7f43633 100644 --- a/android/src/main/java/com/taskrabbit/zendesk/RNZendeskChatModule.java +++ b/android/src/main/java/com/taskrabbit/zendesk/RNZendeskChatModule.java @@ -1,7 +1,7 @@ package com.taskrabbit.zendesk; import android.app.Activity; -import android.content.Intent; +import android.util.Log; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; @@ -9,10 +9,16 @@ import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; -import com.zopim.android.sdk.api.ZopimChat; -import com.zopim.android.sdk.prechat.PreChatForm; -import com.zopim.android.sdk.model.VisitorInfo; -import com.zopim.android.sdk.prechat.ZopimChatActivity; +import com.facebook.react.bridge.ReadableType; +import com.facebook.react.bridge.WritableNativeMap; +import zendesk.chat.Chat; +import zendesk.chat.ChatConfiguration; +import zendesk.chat.ProfileProvider; +import zendesk.chat.PreChatFormFieldStatus; +import zendesk.chat.ChatEngine; +import zendesk.chat.VisitorInfo; +import zendesk.messaging.MessagingActivity; +import zendesk.messaging.MessagingConfiguration; import java.lang.String; import java.util.ArrayList; @@ -29,7 +35,7 @@ public static ArrayList getArrayListOfStrings(ReadableMap options, Strin if (!options.hasKey(key)) { return result; } - if (options.getType(key) != ReadableArray) { + if (options.getType(key) != ReadableType.Array) { Log.e(RNZendeskChatModule.TAG, "wrong type for key '" + key + "' when processing " + functionHint + ", expected an Array of Strings."); return result; @@ -39,7 +45,7 @@ public static ArrayList getArrayListOfStrings(ReadableMap options, Strin if (arr.isNull(i)) { continue; } - if (arr.getType() != String) { + if (arr.getType(i) != ReadableType.String) { Log.e(RNZendeskChatModule.TAG, "wrong type for key '" + key + "[" + i + "]' when processing " + functionHint + ", expected entry to be a String."); } @@ -52,7 +58,7 @@ public static String getStringOrNull(ReadableMap options, String key, String fun if (!options.hasKey(key)) { return null; } - if (options.getType(key) != String) { + if (options.getType(key) != ReadableType.String) { Log.e(RNZendeskChatModule.TAG, "wrong type for key '" + key + "' when processing " + functionHint + ", expected a String."); return null; @@ -60,12 +66,24 @@ public static String getStringOrNull(ReadableMap options, String key, String fun return options.getString(key); } + public static int getIntOrDefault(ReadableMap options, String key, String functionHint, int defaultValue) { + if (!options.hasKey(key)) { + return defaultValue; + } + if (options.getType(key) != ReadableType.String) { + Log.e(RNZendeskChatModule.TAG, + "wrong type for key '" + key + "' when processing " + functionHint + ", expected an Integer."); + return defaultValue; + } + return options.getInt(key); + } + public static boolean getBooleanOrDefault(ReadableMap options, String key, String functionHint, boolean defaultValue) { if (!options.hasKey(key)) { return defaultValue; } - if (options.getType(key) != Boolean) { + if (options.getType(key) != ReadableType.Boolean) { Log.e(RNZendeskChatModule.TAG, "wrong type for key '" + key + "' when processing " + functionHint + ", expected a Boolean."); return defaultValue; @@ -78,7 +96,7 @@ public static PreChatFormFieldStatus getFieldStatusOrDefault(ReadableMap options if (!options.hasKey(key)) { return defaultValue; } - if (options.getType(key) != String) { + if (options.getType(key) != ReadableType.String) { Log.e(RNZendeskChatModule.TAG, "wrong type for key '" + key + "' when processing startChat(preChatFormOptions), expected one of ('required' | 'optional' | 'hidden')."); return defaultValue; @@ -99,12 +117,12 @@ public static PreChatFormFieldStatus getFieldStatusOrDefault(ReadableMap options public static ReadableMap getReadableMap(ReadableMap options, String key, String functionHint) { if (!options.hasKey(key)) { - return new ReadableMap(); + return new WritableNativeMap(); } - if (options.getType(key) != ReadableMap) { + if (options.getType(key) != ReadableType.Map) { Log.e(RNZendeskChatModule.TAG, "wrong type for key '" + key + "' when processing " + functionHint + ", expected a config hash."); - return new ReadableMap(); + return new WritableNativeMap(); } return options.getMap(key); } @@ -124,7 +142,7 @@ public String getName() { @ReactMethod public void setVisitorInfo(ReadableMap options) { - VisitorInfo.Builder builder = new VisitorInfo.Builder(); + VisitorInfo.Builder builder = VisitorInfo.builder(); String name = getStringOrNull(options, "name", "visitorInfo"); if (name != null) { @@ -141,12 +159,18 @@ public void setVisitorInfo(ReadableMap options) { VisitorInfo visitorData = builder.build(); - Chat.INSTANCE.providers().profileProvider().setVisitorInfo(visitorData); + if (Chat.INSTANCE.providers() == null) { + Log.e(TAG, + "Zendesk Internals are undefined -- did you forget to call RNZendeskModule.init()?"); + return; + } + Chat.INSTANCE.providers().profileProvider().setVisitorInfo(visitorData, null); } @ReactMethod public void init(String key) { Chat.INSTANCE.init(mReactContext, key); + Log.d(TAG, "Chat.INSTANCE was properly initialized from JS."); } private ChatConfiguration.Builder loadBehaviorFlags(ChatConfiguration.Builder b, ReadableMap options) { @@ -161,7 +185,7 @@ private ChatConfiguration.Builder loadBehaviorFlags(ChatConfiguration.Builder b, } private ChatConfiguration.Builder loadPreChatFormConfiguration(ChatConfiguration.Builder b, ReadableMap options) { - PreChatFormFieldStatus defaultValue = preChatFormFieldStatus.OPTIONAL; + PreChatFormFieldStatus defaultValue = PreChatFormFieldStatus.OPTIONAL; return b.withNameFieldStatus(getFieldStatusOrDefault(options, "name", defaultValue)) .withEmailFieldStatus(getFieldStatusOrDefault(options, "email", defaultValue)) .withPhoneFieldStatus(getFieldStatusOrDefault(options, "phone", defaultValue)) @@ -173,32 +197,55 @@ private void loadTags(ReadableMap options) { // as things change -- aka doing a diff :-( // ZendeskChat iOS just lets you override the full array so this isn't // necessary on that side. + if (Chat.INSTANCE.providers() == null) { + Log.e(TAG, + "Zendesk Internals are undefined -- did you forget to call RNZendeskModule.init()?"); + return; + } - ChatProfileProvider profileProvider = Chat.INSTANCE.providers().profileProvider(); - ArrayList activeTags = currentUserTags.clone(); + ProfileProvider profileProvider = Chat.INSTANCE.providers().profileProvider(); + ArrayList activeTags = (ArrayList) currentUserTags.clone(); ArrayList allProvidedTags = RNZendeskChatModule.getArrayListOfStrings(options, "tags", "startChat"); - ArrayList newlyIntroducedTags = allProvidedTags.clone(); + ArrayList newlyIntroducedTags = (ArrayList) allProvidedTags.clone(); newlyIntroducedTags.remove(activeTags); // Now just includes tags to add currentUserTags.removeAll(allProvidedTags); // Now just includes tags to delete if (!currentUserTags.isEmpty()) { - profileProvider.removeVisitorTags(currentUserTags); + profileProvider.removeVisitorTags(currentUserTags, null); } if (!newlyIntroducedTags.isEmpty()) { - profileProvider.addVisitorTags(newlyIntroducedTags); + profileProvider.addVisitorTags(newlyIntroducedTags, null); } currentUserTags = allProvidedTags; } - private Object loadBotSettings(ReadableMap options, Object builder) { + private MessagingConfiguration.Builder loadBotSettings(ReadableMap options, + MessagingConfiguration.Builder builder) { + if (options == null) { + return builder; + } + String botName = getStringOrNull(options, "botName", "loadBotSettings"); + if (botName != null) { + builder = builder.withBotLabelString(botName); + } + int avatarDrawable = getIntOrDefault(options, "botAvatarDrawableId", "loadBotSettings", -1); + if (avatarDrawable != -1) { + builder = builder.withBotAvatarDrawable(avatarDrawable); + } + return builder; } @ReactMethod public void startChat(ReadableMap options) { + if (Chat.INSTANCE.providers() == null) { + Log.e(TAG, + "Zendesk Internals are undefined -- did you forget to call RNZendeskModule.init()?"); + return; + } setVisitorInfo(options); ReadableMap flagHash = RNZendeskChatModule.getReadableMap(options, "behaviorFlags", "startChat"); @@ -209,21 +256,23 @@ public void startChat(ReadableMap options) { chatBuilder = loadPreChatFormConfiguration(chatBuilder, getReadableMap(options, "preChatFormOptions", "startChat")); } - ChatConfiguration = chatBuilder.build(); + ChatConfiguration chatConfig = chatBuilder.build(); String department = RNZendeskChatModule.getStringOrNull(options, "department", "startChat"); - if (department) { + if (department != null) { Chat.INSTANCE.providers().chatProvider().setDepartment(department, null); } loadTags(options); - Object messagingBuilder = loadBotSettings(getReadableMap(options, "messagingOptions", "startChat"), - MessagingActivity.builder()); + MessagingConfiguration.Builder messagingBuilder = loadBotSettings( + getReadableMap(options, "messagingOptions", "startChat"), MessagingActivity.builder()); Activity activity = getCurrentActivity(); if (activity != null) { - messagingBuilder.withEngines(ChatEngine.engine()).show(view.getContext()); + messagingBuilder.withEngines(ChatEngine.engine()).show(activity, chatConfig); + } else { + Log.e(TAG, "Could not load getCurrentActivity -- no UI can be displayed without it."); } } } From 3ab7705ed58be3c0997b5a56f66a525b3a00b9a4 Mon Sep 17 00:00:00 2001 From: Frederic Barthelemy Date: Mon, 6 Jul 2020 15:02:18 -0700 Subject: [PATCH 07/14] Option Consistency --- RNZendeskChat.d.ts | 27 ++++++++++++++++----------- ios/RNZendeskChatModule.m | 12 ++++++------ 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/RNZendeskChat.d.ts b/RNZendeskChat.d.ts index 1a49e86..b300289 100644 --- a/RNZendeskChat.d.ts +++ b/RNZendeskChat.d.ts @@ -5,21 +5,24 @@ declare module "react-native-zendesk-chat" { phone?: string; } - interface MessagingOptions_iOS { + interface MessagingOptionsCommon { /** Set this to set the bot's displayName */ botName?: string; + } + interface MessagingOptions_iOS extends MessagingOptionsCommon { /** Will be loaded using [UIImage imageWithName:…] ) */ - botImageName?: string; - /** Will be loaded using [UIImage imageWithUrl:…] */ - botImageUrl?: string; + botAvatarName?: string; + /** Will be loaded using [UIImage imageWithData:[NSData dataWithContentsOfUrl:…]] */ + botAvatarUrl?: string; } - - const enum PreChatFormFieldOptionVisibility { - hidden = "hidden", - optional = "optional", - required = "required", + interface MessagingOptions_Android extends MessagingOptionsCommon { + /** Will be loaded from your native asset resources */ + botAvatarDrawableId?: number; } + /** Current default is "optional" */ + type PreChatFormFieldOptionVisibility = "hidden" | "optional" | "required"; + interface StartChatOptions extends VisitorInfoOptions { department?: string; tags?: string[]; @@ -42,7 +45,7 @@ declare module "react-native-zendesk-chat" { /** Should we ask the user their name? */ name?: PreChatFormFieldOptionVisibility; /** Should we ask the user for their phone number? */ - phoneNumber?: PreChatFormFieldOptionVisibility; + phone?: PreChatFormFieldOptionVisibility; /** Should we ask the user which department? */ department?: PreChatFormFieldOptionVisibility; }; @@ -50,10 +53,12 @@ declare module "react-native-zendesk-chat" { /** * Configure the Chat-Bot (if any) */ - messagingOptions?: MessagingOptions_iOS; + messagingOptions?: MessagingOptions_iOS & MessagingOptions_Android; /** * If not provided, this will be "Close" -- not localized! + * + * -- iOS Only (Android: shows just a Back Button) */ localizedDismissButtonTitle?: string; } diff --git a/ios/RNZendeskChatModule.m b/ios/RNZendeskChatModule.m index 0b7ca0e..6384d9d 100644 --- a/ios/RNZendeskChatModule.m +++ b/ios/RNZendeskChatModule.m @@ -77,14 +77,14 @@ - (ZDKMessagingConfiguration *)messagingConfigurationFromConfig:(NSDictionary*)o RNZDKConfigHashErrorLog(options, @"MessagingConfiguration config options"); return config; } - if (options[@"name"]) { - config.name = options[@"name"]; + if (options[@"botName"]) { + config.name = options[@"botName"]; } - if (options[@"botImageName"]) { - config.botAvatar = [UIImage imageNamed:@"botImageName"]; - } else if (options[@"botImageUrl"]) { - config.botAvatar = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:options[@"botImageUrl"]]]]; + if (options[@"botAvatarName"]) { + config.botAvatar = [UIImage imageNamed:@"botAvatarName"]; + } else if (options[@"botAvatarUrl"]) { + config.botAvatar = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:options[@"botAvatarUrl"]]]]; } return config; From 5ca4d64e694b9149b309b0a2e375f90a7d9fa1e9 Mon Sep 17 00:00:00 2001 From: Frederic Barthelemy Date: Mon, 6 Jul 2020 15:07:42 -0700 Subject: [PATCH 08/14] Documentation Improvements --- .vscode/settings.json | 4 ++++ README.md | 42 +++++++++++++++++++++++++++++++++++------- RNZendeskChat.d.ts | 7 +++++++ index.js | 5 +++++ 4 files changed, 51 insertions(+), 7 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e729147..5de39be 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,9 @@ { "cSpell.words": [ + "autodetect", "barthelemy", + "bot", + "bot's", "chua", "cocoapod", "cocoapods", @@ -8,6 +11,7 @@ "kachanovskyi", "zanechua", "zendesk", + "zendesk's", "zopim" ], "files.exclude": { diff --git a/README.md b/README.md index 8fbb238..9b3e21e 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,12 @@ # react-native-zendesk-chat -Simple module that allows displaying Zopim Chat from Zendesk for React Native. +Simple module that supports displaying Zendesk Chat within a React Native Application. + +This library assumes you're familiar with Zendesk's Official Documentation: [iOS](https://developer.zendesk.com/embeddables/docs/chat-sdk-v-2-for-ios/introduction) and [Android](https://developer.zendesk.com/embeddables/docs/chat-sdk-v-2-for-android/introductionn). ## VERSIONS +- For **Zendesk Chat v2** use version >= 0.4.0 (this requires RN 0.59 or later!) - For RN version higher than 0.59 use version >= 0.3.0 (Zendesk Chat v1) - For RN version lower than 0.59 use version <= 0.2.2 (Zendesk Chat v1) @@ -11,8 +14,6 @@ Simple module that allows displaying Zopim Chat from Zendesk for React Native. ## Getting Started -Follow the instructions to install the SDK for [iOS](https://developer.zendesk.com/embeddables/docs/ios-chat-sdk/introduction) and [Android](https://developer.zendesk.com/embeddables/docs/android-chat-sdk/introduction). - With npm: `npm install react-native-zendesk-chat --save` @@ -54,9 +55,32 @@ ZendeskChat.startChat({ phone: user.mobile_phone, tags: ["tag1", "tag2"], department: "Your department", + behaviorFlags: { + // This is optional + showAgentAvailability: true, + showChatTranscriptPrompt: true, + showPreChatForm: true, + showOfflineForm: true, + }, + preChatFormOptions: { + // This is optional + name: !user.full_name ? "required" : "optional", + email: "optional", + phone: "optional", + department: "required", + }, + localizedDismissButtonTitle: "Dismiss", }); ``` +### Styling + +Changing the UI Styling is mostly achieved through native techniques. + +On Android, this is the [official documentation](https://developer.zendesk.com/embeddables/docs/android-unified-sdk/customize_the_look#how-theming-works) -- and an example might be adding these [3 lines to your app theme](https://github.com/zendesk/sdk_demo_app_android/blob/ae4c551f78911e983b0aac06967628f46be15e54/app/src/main/res/values/styles.xml#L5-L7) + +While on iOS, the options are more minimal -- check the [official doc page](https://developer.zendesk.com/embeddables/docs/chat-sdk-v-2-for-ios/customize_the_look#styling-the-chat-screen) + ### Advanced Setup Advanced users, or users running on older versions of react-native may want to initialize things in native. @@ -113,7 +137,10 @@ project(':react-native-zendesk-chat').projectDir = new File(rootProject.projectD For RN >= 0.60: ```gradle -implementation group: "com.zopim.android", name: "sdk", version: '1.4.9' +dependencies { + // + api group: 'com.zendesk', name: 'chat', version: '2.2.0' + api group: 'com.zendesk', name: 'messaging', version: '4.3.1' ``` For RN < 0.60: @@ -122,12 +149,13 @@ For RN < 0.60: compile project(':react-native-zendesk-chat') ``` -4. Configure `ZopimChat` in `android/app/main/java/[...]/MainActivity.java` +4. Configure `Chat` in `android/app/main/java/[...]/MainActivity.java` ```java -// Note: there is a JS method to do this! +// Note: there is a JS method to do this -- prefer doing that! -- This is for advanced users only. + // Call this once in your Activity's bootup lifecycle -ZopimChat.init("YOUR_ZENDESK_ACCOUNT_KEY").build(); +Chat.INSTANCE.init(mReactContext, key); ``` ## TODO diff --git a/RNZendeskChat.d.ts b/RNZendeskChat.d.ts index b300289..c78d336 100644 --- a/RNZendeskChat.d.ts +++ b/RNZendeskChat.d.ts @@ -64,8 +64,15 @@ declare module "react-native-zendesk-chat" { } class RNZendeskChatModuleImpl { + /** + * Must be called before calling startChat/setVisitorInfo + * - (Advanced users may configure this natively instead of calling this from JS) + */ init: (zendeskAccountKey: string) => void; + /** + * Presents the Zendesk Chat User Interface + */ startChat: (options: StartChatOptions) => void; /** * Backwards Compatibility! diff --git a/index.js b/index.js index 0d580a3..b4e9094 100644 --- a/index.js +++ b/index.js @@ -2,4 +2,9 @@ import { NativeModules } from "react-native"; const RNZendeskChatModule = NativeModules.RNZendeskChatModule; +/** + * TypeScript Documentation for this Module describes the available methods & parameters + * + * @see { ./RNZendeskChat.d.ts } + */ export default RNZendeskChatModule; From 50695eb0a7487e30f37354a76d21636fdf7de9eb Mon Sep 17 00:00:00 2001 From: Frederic Barthelemy Date: Mon, 6 Jul 2020 15:56:34 -0700 Subject: [PATCH 09/14] Add a .npmignore --- .npmignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .npmignore diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..0d6f20b --- /dev/null +++ b/.npmignore @@ -0,0 +1,4 @@ +.vscode/* +.gitignore +*.log +.prettierrc.json From 63dfcb958873afda2b7b5d26aa326d3b2b1db9d7 Mon Sep 17 00:00:00 2001 From: Frederic Barthelemy Date: Mon, 6 Jul 2020 17:19:21 -0700 Subject: [PATCH 10/14] migration instructions --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 9b3e21e..5162d4e 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,14 @@ On Android, this is the [official documentation](https://developer.zendesk.com/e While on iOS, the options are more minimal -- check the [official doc page](https://developer.zendesk.com/embeddables/docs/chat-sdk-v-2-for-ios/customize_the_look#styling-the-chat-screen) +### Migrating + +_From react-native-zendesk-chat <= 0.3.0_ + +To migrate from previous versions of the library, you should probably remove all integration steps you applied, and start over from the [Quick Start](#quickstart--usage). + +The JS API calls are very similar, with mostly additive changes. + ### Advanced Setup Advanced users, or users running on older versions of react-native may want to initialize things in native. From 421dd88a5b91ae24502640bd97283cd5160155f9 Mon Sep 17 00:00:00 2001 From: Frederic Barthelemy Date: Mon, 6 Jul 2020 17:36:08 -0700 Subject: [PATCH 11/14] Android: support overriding Zendesk versions using buildscript.ext.zendeskVersion --- android/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index fb926cd..60848d9 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -37,6 +37,6 @@ repositories { dependencies { implementation "com.facebook.react:react-native:+" - api group: 'com.zendesk', name: 'chat', version: '2.2.0' - api group: 'com.zendesk', name: 'messaging', version: '4.3.1' + api group: 'com.zendesk', name: 'chat', version: safeExtGet(zendeskChatVersion, '2.2.0') + api group: 'com.zendesk', name: 'messaging', version: safeExtGet(zendeskMessagingVersion, '4.3.1') } From d2b2ac8a15fe6d9e7ea2866a1022abe0e3c24856 Mon Sep 17 00:00:00 2001 From: Frederic Barthelemy Date: Mon, 6 Jul 2020 18:10:16 -0700 Subject: [PATCH 12/14] Readme: "optional" clarification --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5162d4e..017ab54 100644 --- a/README.md +++ b/README.md @@ -55,15 +55,15 @@ ZendeskChat.startChat({ phone: user.mobile_phone, tags: ["tag1", "tag2"], department: "Your department", + // The behaviorFlags are optional, and each default to 'true' if omitted behaviorFlags: { - // This is optional showAgentAvailability: true, showChatTranscriptPrompt: true, showPreChatForm: true, showOfflineForm: true, }, + // The preChatFormOptions are optional & each defaults to "optional" if omitted preChatFormOptions: { - // This is optional name: !user.full_name ? "required" : "optional", email: "optional", phone: "optional", From 22a317238fb5b0d47136a4f7ae6b5ec6d532a162 Mon Sep 17 00:00:00 2001 From: Frederic Barthelemy Date: Mon, 6 Jul 2020 18:13:41 -0700 Subject: [PATCH 13/14] Further readme tweaks --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 017ab54..fc9550d 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ This library assumes you're familiar with Zendesk's Official Documentation: [iOS ## VERSIONS - For **Zendesk Chat v2** use version >= 0.4.0 (this requires RN 0.59 or later!) -- For RN version higher than 0.59 use version >= 0.3.0 (Zendesk Chat v1) -- For RN version lower than 0.59 use version <= 0.2.2 (Zendesk Chat v1) +- For RN version >= 0.59 use version >= 0.3.0 (Zendesk Chat v1) +- For RN version < 0.59 use version <= 0.2.2 (Zendesk Chat v1) ## Known Issues @@ -34,7 +34,7 @@ $ (cd ios; pod install) # and see if there are any errors If you're on older react-native versions, please see the [Advanced Setup](#advanced-setup) section below -**Android** If you're on react-native >= 0.60, Android should autodetect this dependency. You may need to call `react-native link` +**Android** If you're on react-native >= 0.60, Android should autodetect this dependency. If you're on 0.59, you may need to call `react-native link` 2. Call the JS Initializer: From 5c3cbc24584072d9f2397f2a0bc2b24981681718 Mon Sep 17 00:00:00 2001 From: Frederic Barthelemy Date: Mon, 6 Jul 2020 18:34:47 -0700 Subject: [PATCH 14/14] build.gradle -- put quotes around the keys passed to safeExtGet --- android/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 60848d9..ed86571 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -37,6 +37,6 @@ repositories { dependencies { implementation "com.facebook.react:react-native:+" - api group: 'com.zendesk', name: 'chat', version: safeExtGet(zendeskChatVersion, '2.2.0') - api group: 'com.zendesk', name: 'messaging', version: safeExtGet(zendeskMessagingVersion, '4.3.1') + api group: 'com.zendesk', name: 'chat', version: safeExtGet('zendeskChatVersion', '2.2.0') + api group: 'com.zendesk', name: 'messaging', version: safeExtGet('zendeskMessagingVersion', '4.3.1') }