diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java
index 8bb5423e9..dfb826fb9 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java
@@ -252,6 +252,7 @@ public class DevSupportManagerImpl implements DevSupportManager {
// JS errors are reported here after source mapping.
if (mRedBoxHandler != null) {
mRedBoxHandler.handleRedbox(message, stack, RedBoxHandler.ErrorType.JS);
+ mRedBoxDialog.resetReporting(true);
}
mRedBoxDialog.show();
}
@@ -276,7 +277,7 @@ public class DevSupportManagerImpl implements DevSupportManager {
@Override
public void run() {
if (mRedBoxDialog == null) {
- mRedBoxDialog = new RedBoxDialog(mApplicationContext, DevSupportManagerImpl.this);
+ mRedBoxDialog = new RedBoxDialog(mApplicationContext, DevSupportManagerImpl.this, mRedBoxHandler);
mRedBoxDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
}
if (mRedBoxDialog.isShowing()) {
@@ -290,6 +291,9 @@ public class DevSupportManagerImpl implements DevSupportManager {
// inside {@link #updateJSError} after source mapping.
if (mRedBoxHandler != null && errorType == ErrorType.NATIVE) {
mRedBoxHandler.handleRedbox(message, stack, RedBoxHandler.ErrorType.NATIVE);
+ mRedBoxDialog.resetReporting(true);
+ } else {
+ mRedBoxDialog.resetReporting(false);
}
mRedBoxDialog.show();
}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/RedBoxDialog.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/RedBoxDialog.java
index 6079acc62..01c4f8032 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/RedBoxDialog.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/RedBoxDialog.java
@@ -9,10 +9,15 @@
package com.facebook.react.devsupport;
+import javax.annotation.Nullable;
+
import android.app.Dialog;
import android.content.Context;
+import android.graphics.Color;
import android.net.Uri;
import android.os.AsyncTask;
+import android.text.SpannedString;
+import android.text.method.LinkMovementMethod;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@@ -22,6 +27,7 @@ import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListView;
+import android.widget.ProgressBar;
import android.widget.TextView;
import com.facebook.common.logging.FLog;
@@ -30,6 +36,7 @@ import com.facebook.react.R;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.devsupport.StackTraceHelper.StackFrame;
+import com.facebook.react.devsupport.RedBoxHandler.ReportCompletedListener;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
@@ -44,11 +51,59 @@ import org.json.JSONObject;
private final DevSupportManager mDevSupportManager;
private final DoubleTapReloadRecognizer mDoubleTapReloadRecognizer;
+ private final @Nullable RedBoxHandler mRedBoxHandler;
private ListView mStackView;
- private Button mReloadJs;
- private Button mDismiss;
- private Button mCopyToClipboard;
+ private Button mReloadJsButton;
+ private Button mDismissButton;
+ private Button mCopyToClipboardButton;
+ private @Nullable Button mReportButton;
+ private @Nullable TextView mReportTextView;
+ private @Nullable ProgressBar mLoadingIndicator;
+ private @Nullable View mLineSeparator;
+ private boolean isReporting = false;
+
+ private ReportCompletedListener mReportCompletedListener = new ReportCompletedListener() {
+ @Override
+ public void onReportSuccess(final SpannedString spannedString) {
+ isReporting = false;
+ Assertions.assertNotNull(mReportButton).setEnabled(true);
+ Assertions.assertNotNull(mLoadingIndicator).setVisibility(View.GONE);
+ Assertions.assertNotNull(mReportTextView).setText(spannedString);
+ }
+ @Override
+ public void onReportError(final SpannedString spannedString) {
+ isReporting = false;
+ Assertions.assertNotNull(mReportButton).setEnabled(true);
+ Assertions.assertNotNull(mLoadingIndicator).setVisibility(View.GONE);
+ Assertions.assertNotNull(mReportTextView).setText(spannedString);
+ }
+ };
+
+ private View.OnClickListener mReportButtonOnClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (mRedBoxHandler == null || !mRedBoxHandler.isReportEnabled() || isReporting) {
+ return;
+ }
+ isReporting = true;
+ Assertions.assertNotNull(mReportTextView).setText("Reporting...");
+ Assertions.assertNotNull(mReportTextView).setVisibility(View.VISIBLE);
+ Assertions.assertNotNull(mLoadingIndicator).setVisibility(View.VISIBLE);
+ Assertions.assertNotNull(mLineSeparator).setVisibility(View.VISIBLE);
+ Assertions.assertNotNull(mReportButton).setEnabled(false);
+
+ String title = Assertions.assertNotNull(mDevSupportManager.getLastErrorTitle());
+ StackFrame[] stack = Assertions.assertNotNull(mDevSupportManager.getLastErrorStack());
+ String sourceUrl = mDevSupportManager.getSourceUrl();
+
+ mRedBoxHandler.reportRedbox(
+ title,
+ stack,
+ sourceUrl,
+ Assertions.assertNotNull(mReportCompletedListener));
+ }
+ };
private static class StackAdapter extends BaseAdapter {
private static final int VIEW_TYPE_COUNT = 2;
@@ -203,7 +258,10 @@ import org.json.JSONObject;
}
}
- protected RedBoxDialog(Context context, DevSupportManager devSupportManager) {
+ protected RedBoxDialog(
+ Context context,
+ DevSupportManager devSupportManager,
+ @Nullable RedBoxHandler redBoxHandler) {
super(context, R.style.Theme_Catalyst_RedBox);
requestWindowFeature(Window.FEATURE_NO_TITLE);
@@ -212,25 +270,27 @@ import org.json.JSONObject;
mDevSupportManager = devSupportManager;
mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer();
+ mRedBoxHandler = redBoxHandler;
mStackView = (ListView) findViewById(R.id.rn_redbox_stack);
mStackView.setOnItemClickListener(this);
- mReloadJs = (Button) findViewById(R.id.rn_redbox_reload_button);
- mReloadJs.setOnClickListener(new View.OnClickListener() {
+
+ mReloadJsButton = (Button) findViewById(R.id.rn_redbox_reload_button);
+ mReloadJsButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDevSupportManager.handleReloadJS();
}
});
- mDismiss = (Button) findViewById(R.id.rn_redbox_dismiss_button);
- mDismiss.setOnClickListener(new View.OnClickListener() {
+ mDismissButton = (Button) findViewById(R.id.rn_redbox_dismiss_button);
+ mDismissButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
- mCopyToClipboard = (Button) findViewById(R.id.rn_redbox_copy_button);
- mCopyToClipboard.setOnClickListener(new View.OnClickListener() {
+ mCopyToClipboardButton = (Button) findViewById(R.id.rn_redbox_copy_button);
+ mCopyToClipboardButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String title = mDevSupportManager.getLastErrorTitle();
@@ -242,12 +302,38 @@ import org.json.JSONObject;
StackTraceHelper.formatStackTrace(title, stack));
}
});
+
+ if (mRedBoxHandler != null && mRedBoxHandler.isReportEnabled()) {
+ mLoadingIndicator = (ProgressBar) findViewById(R.id.rn_redbox_loading_indicator);
+ mLineSeparator = (View) findViewById(R.id.rn_redbox_line_separator);
+ mReportTextView = (TextView) findViewById(R.id.rn_redbox_report_label);
+ mReportTextView.setMovementMethod(LinkMovementMethod.getInstance());
+ mReportTextView.setHighlightColor(Color.TRANSPARENT);
+ mReportButton = (Button) findViewById(R.id.rn_redbox_report_button);
+ mReportButton.setOnClickListener(mReportButtonOnClickListener);
+ }
}
public void setExceptionDetails(String title, StackFrame[] stack) {
mStackView.setAdapter(new StackAdapter(title, stack));
}
+ /**
+ * Show the report button, hide the report textview and the loading indicator.
+ */
+ public void resetReporting(boolean enabled) {
+ if (mRedBoxHandler == null || !mRedBoxHandler.isReportEnabled()) {
+ return;
+ }
+ isReporting = false;
+ Assertions.assertNotNull(mReportTextView).setVisibility(View.GONE);
+ Assertions.assertNotNull(mLoadingIndicator).setVisibility(View.GONE);
+ Assertions.assertNotNull(mLineSeparator).setVisibility(View.GONE);
+ Assertions.assertNotNull(mReportButton).setVisibility(
+ enabled ? View.VISIBLE : View.GONE);
+ Assertions.assertNotNull(mReportButton).setEnabled(true);
+ }
+
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
new OpenStackFrameTask(mDevSupportManager).executeOnExecutor(
diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/RedBoxHandler.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/RedBoxHandler.java
index e526257d6..2ec51cb7f 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/RedBoxHandler.java
+++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/RedBoxHandler.java
@@ -9,12 +9,14 @@
package com.facebook.react.devsupport;
+import android.text.SpannedString;
+
import com.facebook.react.devsupport.StackTraceHelper.StackFrame;
/**
* Interface used by {@link DevSupportManagerImpl} to allow interception on any redboxes
* during development and handling the information from the redbox.
- * The implementation should be passed by {@link #setRedBoxHandler} in {@link ReactInstanceManager}.
+ * The implementation should be passed by setRedBoxHandler in ReactInstanceManager.
*/
public interface RedBoxHandler {
enum ErrorType {
@@ -29,5 +31,31 @@ public interface RedBoxHandler {
return name;
}
}
+
+ /**
+ * Callback interface for {@link #reportRedbox}.
+ */
+ interface ReportCompletedListener {
+ void onReportSuccess(SpannedString spannedString);
+ void onReportError(SpannedString spannedString);
+ }
+
+ /**
+ * Handle the information from the redbox.
+ */
void handleRedbox(String title, StackFrame[] stack, ErrorType errorType);
+
+ /**
+ * Whether the report feature is enabled.
+ */
+ boolean isReportEnabled();
+
+ /**
+ * Report the information from the redbox and set up a callback listener.
+ */
+ void reportRedbox(
+ String title,
+ StackFrame[] stack,
+ String sourceUrl,
+ ReportCompletedListener reportCompletedListener);
}
diff --git a/ReactAndroid/src/main/res/devsupport/layout/redbox_view.xml b/ReactAndroid/src/main/res/devsupport/layout/redbox_view.xml
index 429cd40ee..259cc2e2c 100644
--- a/ReactAndroid/src/main/res/devsupport/layout/redbox_view.xml
+++ b/ReactAndroid/src/main/res/devsupport/layout/redbox_view.xml
@@ -11,6 +11,42 @@
android:layout_height="0dp"
android:layout_weight="1"
/>
+
+
+
+
+
+
diff --git a/ReactAndroid/src/main/res/devsupport/values/strings.xml b/ReactAndroid/src/main/res/devsupport/values/strings.xml
index 23f6bfdff..f5b190ff8 100644
--- a/ReactAndroid/src/main/res/devsupport/values/strings.xml
+++ b/ReactAndroid/src/main/res/devsupport/values/strings.xml
@@ -18,7 +18,8 @@
Unable to connect with remote debugger
Toggle Inspector
Capture Heap
- Dismiss (ESC)
- Reload (R,\u00A0R)
+ Dismiss\n(ESC)
+ Reload\n(R,\u00A0R)
Copy
+ Report