diff --git a/README.md b/README.md index f568faa..36bf00b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ + # helpshift-react-native ## Getting started @@ -21,7 +22,7 @@ #### Android 1. Open up `android/app/src/main/java/[...]/MainActivity.java` - - Add `import com.reactlibrary.RNHelpshiftPackage;` to the imports at the top of the file + - Add `import com.helpshift.reactlibrary.RNHelpshiftPackage;` to the imports at the top of the file - Add `new RNHelpshiftPackage()` to the list returned by the `getPackages()` method 2. Append the following lines to `android/settings.gradle`: ``` @@ -84,6 +85,29 @@ render() { } ``` +### IMPORTANT FOR USING COMPONENT ON ANDROID +You must inherit style from Helpshift Themes to use the `` component in your app like so: + +``` + + + + +``` + +The options for default themes are: + +- Helpshift.Theme.Light.DarkActionBar +- Helpshift.Theme.Light +- Helpshift.Theme.Dark +- Helpshift.Theme.HighContrast +- Helpshift.Theme.DayNight.DarkActionBar +- Helpshift.Theme.DayNight.Light +- Helpshift.Theme.DayNight.HighContrast + +If you would like to customize these themes please refer to Helpshift style guide [here](https://developers.helpshift.com/android/design/#skinning) + ## API Usage #### Initialize ```javascript @@ -157,4 +181,3 @@ async _getUnreadMessagesCount(){ this.setState({ unreadMessages: count }); } ``` - \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index 2dba9d3..43e5ee4 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -12,7 +12,7 @@ buildscript { apply plugin: 'com.android.library' android { - compileSdkVersion 28 + compileSdkVersion 30 buildToolsVersion "28.0.3" defaultConfig { @@ -36,6 +36,6 @@ dependencies { implementation 'com.android.support:design:28.0.0' implementation 'com.android.support:recyclerview-v7:28.0.0' implementation 'com.android.support:cardview-v7:28.0.0' - implementation 'com.helpshift:android-helpshift-aar:7.6.2' + implementation 'com.helpshift:android-helpshift-aar:7.9.0' } \ No newline at end of file diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 41a92ab..a8c510e 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ + package="com.helpshift.reactlibrary"> \ No newline at end of file diff --git a/android/src/main/java/com/reactlibrary/RNHelpshiftModule.java b/android/src/main/java/com/reactlibrary/RNHelpshiftModule.java index 22cc48c..f4df1ab 100644 --- a/android/src/main/java/com/reactlibrary/RNHelpshiftModule.java +++ b/android/src/main/java/com/reactlibrary/RNHelpshiftModule.java @@ -1,157 +1,237 @@ -package com.reactlibrary; +package com.helpshift.reactlibrary; + +import android.app.Activity; +import android.app.Application; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.Log; + +import androidx.annotation.Nullable; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; -import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMapKeySetIterator; - -import java.util.Map; -import java.util.HashMap; - import com.facebook.react.bridge.WritableMap; import com.facebook.react.modules.core.DeviceEventManagerModule; import com.helpshift.Core; -import com.helpshift.exceptions.InstallException; -import com.helpshift.support.Support; +import com.helpshift.CoreInternal; import com.helpshift.HelpshiftUser; +import com.helpshift.InstallConfig; +import com.helpshift.delegate.AuthenticationFailureReason; +import com.helpshift.exceptions.InstallException; import com.helpshift.support.ApiConfig; +import com.helpshift.support.Support; -import android.app.Activity; -import android.app.Application; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; - -import androidx.annotation.Nullable; +import java.io.File; +import java.util.HashMap; +import java.util.Map; -public class RNHelpshiftModule extends ReactContextBaseJavaModule { - - private final Application app; - private ReactApplicationContext mReactContext; - private Handler countSuccessHandler; - private Handler countErrorHandler; - - public RNHelpshiftModule(ReactApplicationContext reactContext) { - super(reactContext); - - mReactContext= reactContext; - - this.app = (Application)reactContext.getApplicationContext(); - } - - @ReactMethod - public void init(String key, String domain, String appid) throws InstallException { - Core.init(Support.getInstance()); - Core.install(this.app, key, domain, appid); - } - - @ReactMethod - public void login(ReadableMap user){ - HelpshiftUser userBuilder; - String email = user.hasKey("email") ? user.getString("email") : null; - String indentifier = user.hasKey("indentifier") ? user.getString("indentifier") : null; - if(user.hasKey("name") && user.hasKey("authToken")) { - userBuilder = new HelpshiftUser.Builder(indentifier, email) - .setName(user.getString("name")) - .setAuthToken(user.getString("authToken")) - .build(); - } else if (user.hasKey("name")) { - userBuilder = new HelpshiftUser.Builder(indentifier, email) - .setName(user.getString("name")) - .build(); - } else if (user.hasKey("authToken")) { - userBuilder = new HelpshiftUser.Builder(indentifier, email) - .setAuthToken(user.getString("authToken")) - .build(); - } else { - userBuilder = new HelpshiftUser.Builder(indentifier, email).build(); - } - - Core.login(userBuilder); - } - - @ReactMethod - public void logout(){ - Core.logout(); - } - - @ReactMethod - public void showConversation(){ - final Activity activity = getCurrentActivity(); - Support.showConversation(activity); - } - - @ReactMethod - public void showConversationWithCIFs(ReadableMap cifs){ - final Activity activity = getCurrentActivity(); - ApiConfig apiConfig = new ApiConfig.Builder().setCustomIssueFields(getCustomIssueFields(cifs)).build(); - Support.showConversation(activity, apiConfig); - } - - @ReactMethod - public void showFAQs(){ - final Activity activity = getCurrentActivity(); - Support.showFAQs(activity); - } - - @ReactMethod - public void showFAQsWithCIFs(ReadableMap cifs){ - final Activity activity = getCurrentActivity(); - ApiConfig apiConfig = new ApiConfig.Builder().setCustomIssueFields(getCustomIssueFields(cifs)).build(); - Support.showFAQs(activity, apiConfig); - } - - @ReactMethod - public void requestUnreadMessagesCount(){ - - // TODO: Is the correct place to create these? - countErrorHandler = new Handler() { - public void handleMessage(Message msg) { - super.handleMessage(msg); - } - }; - - countSuccessHandler = new Handler() { - public void handleMessage(Message msg) { - super.handleMessage(msg); - Bundle countData = (Bundle) msg.obj; - Integer count = countData.getInt("value"); - WritableMap params = Arguments.createMap(); - params.putInt("count", count); - sendEvent(mReactContext, "didReceiveUnreadMessagesCount", params); - } - }; - - Support.getNotificationCount(countSuccessHandler, countErrorHandler); - } - - private void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params) { - reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit(eventName, params); - } - - - - private Map getCustomIssueFields(ReadableMap cifs) { - ReadableMapKeySetIterator iterator = cifs.keySetIterator(); - Map customIssueFields = new HashMap<>(); - - while (iterator.hasNextKey()) { - String key = iterator.nextKey(); - ReadableArray array = cifs.getArray(key); - customIssueFields.put(key, new String[]{array.getString(0), array.getString(1)}); - } - - return customIssueFields; - } - - @Override - public String getName() { - return "RNHelpshift"; - } +public class RNHelpshiftModule extends ReactContextBaseJavaModule implements Support.Delegate { + + private final Application app; + private ReactApplicationContext mReactContext; + private Handler countSuccessHandler; + private Handler countErrorHandler; + + public RNHelpshiftModule(ReactApplicationContext reactContext) { + super(reactContext); + + mReactContext= reactContext; + + this.app = (Application)reactContext.getApplicationContext(); + } + @Override + public String getName() { + return "RNHelpshift"; + } + + @ReactMethod + public void init(String key, String domain, String appid) throws InstallException { + Map extras = new HashMap<>(); + extras.put("manualLifecycleTracking", true); + extras.put("enableDefaultConversationalFiling", true); + InstallConfig installConfig = new InstallConfig.Builder().setExtras(extras).build(); + + Core.init(Support.getInstance()); + Core.install(this.app, key, domain, appid,installConfig); + CoreInternal.onAppForeground(); + Support.setDelegate(this); + } + + @ReactMethod + public void login(ReadableMap user){ + HelpshiftUser userBuilder; + String email = user.hasKey("email") ? user.getString("email") : null; + String identifier = user.hasKey("identifier") ? user.getString("identifier") : null; + if(user.hasKey("name") && user.hasKey("authToken")) { + userBuilder = new HelpshiftUser.Builder(identifier, email) + .setName(user.getString("name")) + .setAuthToken(user.getString("authToken")) + .build(); + } else if (user.hasKey("name")) { + userBuilder = new HelpshiftUser.Builder(identifier, email) + .setName(user.getString("name")) + .build(); + } else if (user.hasKey("authToken")) { + userBuilder = new HelpshiftUser.Builder(identifier, email) + .setAuthToken(user.getString("authToken")) + .build(); + } else { + userBuilder = new HelpshiftUser.Builder(identifier, email).build(); + } + + Core.login(userBuilder); + } + + @ReactMethod + public void logout(){ + Core.logout(); + } + + @ReactMethod + public void showConversation(){ + + final Activity activity = getCurrentActivity(); + Support.showConversation(activity); + } + + @ReactMethod + public void showConversationWithCIFs(ReadableMap cifs){ + + final Activity activity = getCurrentActivity(); + ApiConfig apiConfig = new ApiConfig.Builder().setCustomIssueFields(getCustomIssueFields(cifs)).build(); + Support.showConversation(activity, apiConfig); + } + + @ReactMethod + public void showFAQs(){ + final Activity activity = getCurrentActivity(); + Support.showFAQs(activity); + } + + @ReactMethod + public void showFAQsWithCIFs(ReadableMap cifs){ + final Activity activity = getCurrentActivity(); + ApiConfig apiConfig = new ApiConfig.Builder().setCustomIssueFields(getCustomIssueFields(cifs)).build(); + Support.showFAQs(activity, apiConfig); + } + + @ReactMethod + public void requestUnreadMessagesCount(){ + + // TODO: Is the correct place to create these? + countErrorHandler = new Handler() { + public void handleMessage(Message msg) { + super.handleMessage(msg); + } + }; + + countSuccessHandler = new Handler() { + public void handleMessage(Message msg) { + super.handleMessage(msg); + Bundle countData = (Bundle) msg.obj; + Integer count = countData.getInt("value"); + WritableMap params = Arguments.createMap(); + params.putInt("count", count); + sendEvent(mReactContext, "Helpshift/DidReceiveUnreadMessagesCount", params); + } + }; + + Support.getNotificationCount(countSuccessHandler, countErrorHandler); + } + + private void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params) { + reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit(eventName, params); + } + + private Map getCustomIssueFields(ReadableMap cifs) { + ReadableMapKeySetIterator iterator = cifs.keySetIterator(); + Map customIssueFields = new HashMap<>(); + + while (iterator.hasNextKey()) { + String key = iterator.nextKey(); + ReadableArray array = cifs.getArray(key); + customIssueFields.put(key, new String[]{array.getString(0), array.getString(1)}); + } + + return customIssueFields; + } + + @Override + public void sessionBegan() { + Log.d("Helpshift", "sessionBegan"); + sendEvent(mReactContext, "Helpshift/SessionBegan", Arguments.createMap()); + } + + @Override + public void sessionEnded() { + Log.d("Helpshift", "sessionEnded"); + sendEvent(mReactContext, "Helpshift/SessionEnded", Arguments.createMap()); + } + + @Override + public void newConversationStarted(String newConversationMessage) { + Log.d("Helpshift", "newConversationStarted"); + WritableMap params = Arguments.createMap(); + params.putString("newConversationMessage", newConversationMessage); + sendEvent(mReactContext, "Helpshift/NewConversationStarted", params); + } + + @Override + public void conversationEnded() { + Log.d("Helpshift", "conversationEnded"); + sendEvent(mReactContext, "Helpshift/ConversationEnded", Arguments.createMap()); + } + + @Override + public void userRepliedToConversation(String newMessage) { + Log.d("Helpshift", "userRepliedToConversation"); + WritableMap params = Arguments.createMap(); + params.putString("newMessage", newMessage); + sendEvent(mReactContext, "Helpshift/UserRepliedToConversation", params); + } + + @Override + public void userCompletedCustomerSatisfactionSurvey(int rating, String feedback) { + Log.d("Helpshift", "userCompletedCustomerSatisfactionSurvey"); + WritableMap params = Arguments.createMap(); + params.putInt("rating", rating); + params.putString("feedback", feedback); + sendEvent(mReactContext, "Helpshift/UserCompletedCustomerSatisfactionSurvey", params); + } + + + //TODO: determine if File can be sent by React Native bridge + @Override + public void displayAttachmentFile(Uri attachmentFile) { } + + // TODO: determine if File can be sent by React Native bridge + @Override + public void displayAttachmentFile(File attachmentFile) {} + + @Override + public void didReceiveNotification(int count) { + Log.d("Helpshift", "didReceiveNotification"); + WritableMap params = Arguments.createMap(); + params.putInt("count", count); + sendEvent(mReactContext, "Helpshift/DidReceiveNotification", params); + } + + @Override + public void authenticationFailed(HelpshiftUser user, AuthenticationFailureReason reason) { + Log.d("Helpshift", "authenticationFailed"); + WritableMap params = Arguments.createMap(); + params.putString("user", user.toString()); + params.putString("reason", reason.toString()); + sendEvent(mReactContext, "Helpshift/AuthenticationFailed", params); + } } \ No newline at end of file diff --git a/android/src/main/java/com/reactlibrary/RNHelpshiftPackage.java b/android/src/main/java/com/reactlibrary/RNHelpshiftPackage.java index 8c7fa76..f5cabc9 100644 --- a/android/src/main/java/com/reactlibrary/RNHelpshiftPackage.java +++ b/android/src/main/java/com/reactlibrary/RNHelpshiftPackage.java @@ -1,5 +1,5 @@ -package com.reactlibrary; +package com.helpshift.reactlibrary; import java.util.Arrays; import java.util.Collections; diff --git a/android/src/main/java/com/reactlibrary/RNHelpshiftView.java b/android/src/main/java/com/reactlibrary/RNHelpshiftView.java index 48e2afd..96a6983 100644 --- a/android/src/main/java/com/reactlibrary/RNHelpshiftView.java +++ b/android/src/main/java/com/reactlibrary/RNHelpshiftView.java @@ -1,12 +1,18 @@ -package com.reactlibrary; +package com.helpshift.reactlibrary; +import android.app.Activity; import android.app.Application; -import android.content.Context; -import android.graphics.Color; +import android.net.Uri; import android.util.Log; -import android.widget.FrameLayout; +import android.view.View; +import android.view.ViewTreeObserver; import android.widget.LinearLayout; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; + import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReadableArray; @@ -14,12 +20,14 @@ import com.facebook.react.bridge.ReadableMapKeySetIterator; import com.facebook.react.bridge.WritableMap; import com.facebook.react.modules.core.DeviceEventManagerModule; -import com.facebook.react.uimanager.SimpleViewManager; import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.ViewGroupManager; import com.facebook.react.uimanager.annotations.ReactProp; +import com.facebook.react.views.view.ReactViewGroup; import com.helpshift.Core; +import com.helpshift.CoreInternal; import com.helpshift.HelpshiftUser; -import com.helpshift.activities.MainActivity; +import com.helpshift.InstallConfig; import com.helpshift.delegate.AuthenticationFailureReason; import com.helpshift.exceptions.InstallException; import com.helpshift.support.ApiConfig; @@ -29,13 +37,7 @@ import java.util.HashMap; import java.util.Map; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AppCompatActivity; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentActivity; - -public class RNHelpshiftView extends SimpleViewManager implements Support.Delegate { +public class RNHelpshiftView extends ViewGroupManager implements Support.Delegate { public static final String REACT_CLASS = "RNTHelpshift"; @@ -48,40 +50,65 @@ public String getName() { } @Override - public FrameLayout createViewInstance(ThemedReactContext context) { - FrameLayout frameLayout = new FrameLayout(context); - + public ReactViewGroup createViewInstance(ThemedReactContext context) { + final ReactViewGroup reactView = new ReactViewGroup(context); mReactContext = context; - mApplication = (Application)context.getApplicationContext(); - - return frameLayout; + return reactView; } @ReactProp(name = "config") - public void setConfig(FrameLayout frameLayout, ReadableMap config) throws InstallException { - Support.setDelegate(this); + public void setConfig(final ReactViewGroup reactView, ReadableMap config) { + Map extras = new HashMap<>(); + extras.put("manualLifecycleTracking", true); + extras.put("enableDefaultConversationalFiling", true); + InstallConfig installConfig = new InstallConfig.Builder().setExtras(extras).build(); + + Core.init(Support.getInstance()); - Core.install(mApplication, config.getString("apiKey"), config.getString("domain"), config.getString("appId")); + + try { + Core.install(mApplication, config.getString("apiKey"), config.getString("domain"), config.getString("appId"), installConfig); + CoreInternal.onAppForeground(); + } catch (InstallException e) { + Log.e("Helpshift", "invalid install credentials : ", e); + } if (config.hasKey("user")) { this.login(config.getMap("user")); } - - - // TODO: USE FRAGMENT INSTEAD + Support.setDelegate(this); + + Activity activity = mReactContext.getCurrentActivity(); + final FragmentManager fragmentManager = ((AppCompatActivity)activity).getSupportFragmentManager(); + final Fragment helpshiftFragment; +; + if (config.hasKey("cifs")) { ApiConfig apiConfig = new ApiConfig.Builder().setCustomIssueFields(getCustomIssueFields(config.getMap("cifs"))).build(); Support.showConversation(mReactContext.getCurrentActivity(), apiConfig); + } else { Support.showConversation(mReactContext.getCurrentActivity()); + } - // Fragment helpshiftFragment = Support.getConversationFragment(mReactContext.getCurrentActivity()); - // mReactContext.getCurrentActivity().getFragmentManager().beginTransaction().add(frameLayout.getId(), helpshiftFragment).commit(); - // LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(0, 0); - // frameLayout.setLayoutParams(lp); - // frameLayout.setBackgroundColor(Color.parseColor("purple")); + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(0, 0); + + + reactView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + fragmentManager.executePendingTransactions(); + for (int i = 0; i < reactView.getChildCount(); i++) { + View child = reactView.getChildAt(i); + child.measure(View.MeasureSpec.makeMeasureSpec(reactView.getMeasuredWidth(), View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(reactView.getMeasuredHeight(), View.MeasureSpec.EXACTLY)); + child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight()); + } + } + }); + } private void login(ReadableMap user){ @@ -154,7 +181,7 @@ public void conversationEnded() { @Override public void userRepliedToConversation(String newMessage) { - Log.d("Helpshift", "newConversationStarted"); + Log.d("Helpshift", "userRepliedToConversation"); WritableMap params = Arguments.createMap(); params.putString("newMessage", newMessage); sendEvent(mReactContext, "Helpshift/UserRepliedToConversation", params); @@ -170,14 +197,13 @@ public void userCompletedCustomerSatisfactionSurvey(int rating, String feedback) } -// TODO: determine if File can be sent by React Native bridge + //TODO: determine if File can be sent by React Native bridge @Override - public void displayAttachmentFile(File attachmentFile) { -// Log.d("Helpshift", "displayAttachmentFile"); -// WritableMap params = Arguments.createMap(); -// params.putString("attachmentFile", attachmentFile.toString()); -// sendEvent(mReactContext, "Helpshift/DisplayAttachmentFile", params); - } + public void displayAttachmentFile(Uri attachmentFile) { } + + // TODO: determine if File can be sent by React Native bridge + @Override + public void displayAttachmentFile(File attachmentFile) {} @Override public void didReceiveNotification(int newMessagesCount) { diff --git a/android/src/main/res/values/styles.xml b/android/src/main/res/values/styles.xml new file mode 100644 index 0000000..e50c259 --- /dev/null +++ b/android/src/main/res/values/styles.xml @@ -0,0 +1,10 @@ + + +