Skip to content

Commit 75996c3

Browse files
authored
Merge 9315f7b into 8696c89
2 parents 8696c89 + 9315f7b commit 75996c3

File tree

23 files changed

+1754
-9
lines changed

23 files changed

+1754
-9
lines changed

CHANGELOG.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,50 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Features
6+
7+
- Add New User Feedback form ([#4384](https://github.com/getsentry/sentry-java/pull/4384))
8+
- We now introduce SentryUserFeedbackDialog, which extends AlertDialog, inheriting the show() and cancel() methods, among others.
9+
- The dialog integrates with the current dialog theme, so it's compatible with dark mode, and can be customized with a custom xml style.
10+
- ```styles.xml or themes.xml
11+
<!-- Application theme. -->
12+
<style name="MyAppTheme" parent="Theme.AppCompat.DayNight.DarkActionBar">
13+
...
14+
current theme customizations
15+
...
16+
<!-- Set a dialog theme if not already done. -->
17+
<item name="android:dialogTheme">@style/MyAppDialogTheme</item>
18+
</style>
19+
20+
<!-- Edit application dialog theme. -->
21+
<style name="MyAppDialogTheme" parent="Theme.AppCompat.DayNight.Dialog">
22+
<!-- Set the style of the feedback dialog title. -->
23+
<item name="android:windowTitleStyle">@style/FeedbackFormTitleStyle</item>
24+
25+
<!-- Set the color of title, cancel button text, and non editable texts. -->
26+
<item name="android:textColor">@color/colorPrimary</item>
27+
<!-- Set the color of editable texts. -->
28+
<item name="android:editTextColor">@color/colorPrimaryDark</item>
29+
<!-- Set the color of the hint of editable texts. -->
30+
<item name="android:textColorHint">@color/colorPrimaryDark</item>
31+
<!-- Set the color of the send button text. -->
32+
<item name="android:textColorPrimaryInverse">@android:color/white</item>
33+
34+
<!-- Set the background color of the send button. -->
35+
<item name="android:colorPrimary">@color/colorPrimary</item>
36+
<!-- Set the background color of the cancel button. -->
37+
<item name="android:colorBackground">@android:color/black</item>
38+
<!-- Set the color tint of the image logo. -->
39+
<item name="android:colorForeground">@color/colorPrimary</item>
40+
</style>
41+
42+
<style name="FeedbackFormTitleStyle">
43+
<!-- Customize your theme here. -->
44+
<item name="android:textAppearance">@style/TextAppearance.AppCompat.Title</item>
45+
</style>
46+
```
47+
348
## 8.12.0
449

550
### Features

sentry-android-core/api/sentry-android-core.api

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,14 @@ public final class io/sentry/android/core/SentryPerformanceProvider {
385385
public fun shutdown ()V
386386
}
387387

388+
public final class io/sentry/android/core/SentryUserFeedbackDialog : android/app/AlertDialog {
389+
public fun <init> (Landroid/content/Context;)V
390+
public fun <init> (Landroid/content/Context;I)V
391+
public fun <init> (Landroid/content/Context;ZLandroid/content/DialogInterface$OnCancelListener;)V
392+
public fun setCancelable (Z)V
393+
public fun show ()V
394+
}
395+
388396
public class io/sentry/android/core/SpanFrameMetricsCollector : io/sentry/IPerformanceContinuousCollector, io/sentry/android/core/internal/util/SentryFrameMetricsCollector$FrameMetricsCollectorListener {
389397
protected final field lock Lio/sentry/util/AutoClosableReentrantLock;
390398
public fun <init> (Lio/sentry/android/core/SentryAndroidOptions;Lio/sentry/android/core/internal/util/SentryFrameMetricsCollector;)V

sentry-android-core/src/main/AndroidManifest.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
33
<uses-permission android:name="android.permission.INTERNET"/>
44

5-
<application>
5+
<application
6+
android:supportsRtl="true">
67
<!-- 'android:authorities' must be unique in the device, across all apps -->
78
<provider
89
android:name=".SentryInitProvider"

sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import io.sentry.ILogger;
77
import io.sentry.InitPriority;
88
import io.sentry.ProfileLifecycle;
9+
import io.sentry.SentryFeedbackOptions;
910
import io.sentry.SentryIntegrationPackageStorage;
1011
import io.sentry.SentryLevel;
1112
import io.sentry.protocol.SdkVersion;
@@ -126,6 +127,18 @@ final class ManifestMetadataReader {
126127
static final String ENABLE_AUTO_TRACE_ID_GENERATION =
127128
"io.sentry.traces.enable-auto-id-generation";
128129

130+
static final String FEEDBACK_NAME_REQUIRED = "io.sentry.feedback.is-name-required";
131+
132+
static final String FEEDBACK_SHOW_NAME = "io.sentry.feedback.show-name";
133+
134+
static final String FEEDBACK_EMAIL_REQUIRED = "io.sentry.feedback.is-email-required";
135+
136+
static final String FEEDBACK_SHOW_EMAIL = "io.sentry.feedback.show-email";
137+
138+
static final String FEEDBACK_USE_SENTRY_USER = "io.sentry.feedback.use-sentry-user";
139+
140+
static final String FEEDBACK_SHOW_BRANDING = "io.sentry.feedback.show-branding";
141+
129142
/** ManifestMetadataReader ctor */
130143
private ManifestMetadataReader() {}
131144

@@ -477,6 +490,21 @@ static void applyMetadata(
477490
options
478491
.getLogs()
479492
.setEnabled(readBool(metadata, logger, ENABLE_LOGS, options.getLogs().isEnabled()));
493+
494+
final @NotNull SentryFeedbackOptions feedbackOptions = options.getFeedbackOptions();
495+
feedbackOptions.setNameRequired(
496+
readBool(metadata, logger, FEEDBACK_NAME_REQUIRED, feedbackOptions.isNameRequired()));
497+
feedbackOptions.setShowName(
498+
readBool(metadata, logger, FEEDBACK_SHOW_NAME, feedbackOptions.isShowName()));
499+
feedbackOptions.setEmailRequired(
500+
readBool(metadata, logger, FEEDBACK_EMAIL_REQUIRED, feedbackOptions.isEmailRequired()));
501+
feedbackOptions.setShowEmail(
502+
readBool(metadata, logger, FEEDBACK_SHOW_EMAIL, feedbackOptions.isShowEmail()));
503+
feedbackOptions.setUseSentryUser(
504+
readBool(
505+
metadata, logger, FEEDBACK_USE_SENTRY_USER, feedbackOptions.isUseSentryUser()));
506+
feedbackOptions.setShowBranding(
507+
readBool(metadata, logger, FEEDBACK_SHOW_BRANDING, feedbackOptions.isShowBranding()));
480508
}
481509
options
482510
.getLogger()
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
package io.sentry.android.core;
2+
3+
import android.app.AlertDialog;
4+
import android.content.Context;
5+
import android.os.Bundle;
6+
import android.view.View;
7+
import android.widget.Button;
8+
import android.widget.EditText;
9+
import android.widget.ImageView;
10+
import android.widget.TextView;
11+
import android.widget.Toast;
12+
import io.sentry.IScopes;
13+
import io.sentry.Sentry;
14+
import io.sentry.SentryFeedbackOptions;
15+
import io.sentry.SentryLevel;
16+
import io.sentry.SentryOptions;
17+
import io.sentry.protocol.Feedback;
18+
import io.sentry.protocol.SentryId;
19+
import io.sentry.protocol.User;
20+
import org.jetbrains.annotations.NotNull;
21+
import org.jetbrains.annotations.Nullable;
22+
23+
public final class SentryUserFeedbackDialog extends AlertDialog {
24+
25+
private boolean isCancelable = false;
26+
27+
public SentryUserFeedbackDialog(final @NotNull Context context) {
28+
super(context);
29+
}
30+
31+
public SentryUserFeedbackDialog(
32+
final @NotNull Context context,
33+
final boolean cancelable,
34+
@Nullable final OnCancelListener cancelListener) {
35+
super(context, cancelable, cancelListener);
36+
isCancelable = cancelable;
37+
}
38+
39+
public SentryUserFeedbackDialog(final @NotNull Context context, final int themeResId) {
40+
super(context, themeResId);
41+
}
42+
43+
@Override
44+
public void setCancelable(boolean cancelable) {
45+
super.setCancelable(cancelable);
46+
isCancelable = cancelable;
47+
}
48+
49+
@Override
50+
protected void onCreate(Bundle savedInstanceState) {
51+
super.onCreate(savedInstanceState);
52+
setContentView(R.layout.sentry_dialog_user_feedback);
53+
setCancelable(isCancelable);
54+
55+
final @NotNull SentryFeedbackOptions feedbackOptions =
56+
Sentry.getCurrentScopes().getOptions().getFeedbackOptions();
57+
final @NotNull TextView lblTitle = findViewById(R.id.sentry_dialog_user_feedback_title);
58+
final @NotNull ImageView imgLogo = findViewById(R.id.sentry_dialog_user_feedback_logo);
59+
final @NotNull TextView lblName = findViewById(R.id.sentry_dialog_user_feedback_txt_name);
60+
final @NotNull EditText edtName = findViewById(R.id.sentry_dialog_user_feedback_edt_name);
61+
final @NotNull TextView lblEmail = findViewById(R.id.sentry_dialog_user_feedback_txt_email);
62+
final @NotNull EditText edtEmail = findViewById(R.id.sentry_dialog_user_feedback_edt_email);
63+
final @NotNull TextView lblMessage =
64+
findViewById(R.id.sentry_dialog_user_feedback_txt_description);
65+
final @NotNull EditText edtMessage =
66+
findViewById(R.id.sentry_dialog_user_feedback_edt_description);
67+
final @NotNull Button btnSend = findViewById(R.id.sentry_dialog_user_feedback_btn_send);
68+
final @NotNull Button btnCancel = findViewById(R.id.sentry_dialog_user_feedback_btn_cancel);
69+
70+
if (feedbackOptions.isShowBranding()) {
71+
imgLogo.setVisibility(View.VISIBLE);
72+
} else {
73+
imgLogo.setVisibility(View.GONE);
74+
}
75+
76+
if (!feedbackOptions.isShowName() && !feedbackOptions.isNameRequired()) {
77+
lblName.setVisibility(View.GONE);
78+
edtName.setVisibility(View.GONE);
79+
} else {
80+
lblName.setVisibility(View.VISIBLE);
81+
edtName.setVisibility(View.VISIBLE);
82+
lblName.setText(feedbackOptions.getNameLabel());
83+
edtName.setHint(feedbackOptions.getNamePlaceholder());
84+
if (feedbackOptions.isNameRequired()) {
85+
lblName.append(feedbackOptions.getIsRequiredLabel());
86+
}
87+
}
88+
89+
if (!feedbackOptions.isShowEmail() && !feedbackOptions.isEmailRequired()) {
90+
lblEmail.setVisibility(View.GONE);
91+
edtEmail.setVisibility(View.GONE);
92+
} else {
93+
lblEmail.setVisibility(View.VISIBLE);
94+
edtEmail.setVisibility(View.VISIBLE);
95+
lblEmail.setText(feedbackOptions.getEmailLabel());
96+
edtEmail.setHint(feedbackOptions.getEmailPlaceholder());
97+
if (feedbackOptions.isEmailRequired()) {
98+
lblEmail.append(feedbackOptions.getIsRequiredLabel());
99+
}
100+
}
101+
102+
if (feedbackOptions.isUseSentryUser()) {
103+
final @Nullable User user = Sentry.getCurrentScopes().getScope().getUser();
104+
if (user != null) {
105+
edtName.setText(user.getName());
106+
edtEmail.setText(user.getEmail());
107+
}
108+
}
109+
110+
lblMessage.setText(feedbackOptions.getMessageLabel());
111+
lblMessage.append(feedbackOptions.getIsRequiredLabel());
112+
edtMessage.setHint(feedbackOptions.getMessagePlaceholder());
113+
lblTitle.setText(feedbackOptions.getFormTitle());
114+
115+
btnSend.setText(feedbackOptions.getSubmitButtonLabel());
116+
btnSend.setOnClickListener(
117+
v -> {
118+
final @NotNull String name = edtName.getText().toString().trim();
119+
final @NotNull String email = edtEmail.getText().toString().trim();
120+
final @NotNull String message = edtMessage.getText().toString().trim();
121+
final @NotNull Feedback feedback = new Feedback(message);
122+
123+
if (name.isEmpty() && feedbackOptions.isNameRequired()) {
124+
edtName.setError(lblName.getText());
125+
return;
126+
}
127+
128+
if (email.isEmpty() && feedbackOptions.isEmailRequired()) {
129+
edtEmail.setError(lblEmail.getText());
130+
return;
131+
}
132+
133+
if (message.isEmpty()) {
134+
edtMessage.setError(lblMessage.getText());
135+
return;
136+
}
137+
138+
feedback.setName(name);
139+
feedback.setContactEmail(email);
140+
141+
SentryId id = Sentry.captureFeedback(feedback);
142+
if (!id.equals(SentryId.EMPTY_ID)) {
143+
Toast.makeText(
144+
getContext(), feedbackOptions.getSuccessMessageText(), Toast.LENGTH_SHORT)
145+
.show();
146+
final @Nullable SentryFeedbackOptions.SentryFeedbackCallback onSubmitSuccess =
147+
feedbackOptions.getOnSubmitSuccess();
148+
if (onSubmitSuccess != null) {
149+
onSubmitSuccess.call(feedback);
150+
}
151+
} else {
152+
final @Nullable SentryFeedbackOptions.SentryFeedbackCallback onSubmitError =
153+
feedbackOptions.getOnSubmitError();
154+
if (onSubmitError != null) {
155+
onSubmitError.call(feedback);
156+
}
157+
}
158+
cancel();
159+
});
160+
161+
btnCancel.setText(feedbackOptions.getCancelButtonLabel());
162+
btnCancel.setOnClickListener(v -> cancel());
163+
164+
final @Nullable Runnable onFormClose = feedbackOptions.getOnFormClose();
165+
if (onFormClose != null) {
166+
setOnDismissListener(dialog -> onFormClose.run());
167+
}
168+
}
169+
170+
@Override
171+
protected void onStart() {
172+
super.onStart();
173+
final @NotNull SentryFeedbackOptions feedbackOptions =
174+
Sentry.getCurrentScopes().getOptions().getFeedbackOptions();
175+
final @Nullable Runnable onFormOpen = feedbackOptions.getOnFormOpen();
176+
if (onFormOpen != null) {
177+
onFormOpen.run();
178+
}
179+
}
180+
181+
@Override
182+
public void show() {
183+
final @NotNull IScopes scopes = Sentry.getCurrentScopes();
184+
final @NotNull SentryOptions options = scopes.getOptions();
185+
if (!scopes.isEnabled() || !options.isEnabled()) {
186+
options
187+
.getLogger()
188+
.log(SentryLevel.WARNING, "Sentry is disabled. Feedback dialog won't be shown.");
189+
return;
190+
}
191+
super.show();
192+
}
193+
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<shape xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:shape="rectangle">
4+
5+
<solid android:color="@android:color/transparent" />
6+
<stroke
7+
android:width="1dp"
8+
android:color="#FFAAAAAA" /> <!-- border color -->
9+
<corners android:radius="4dp" />
10+
<padding
11+
android:left="8dp"
12+
android:top="8dp"
13+
android:right="8dp"
14+
android:bottom="8dp" />
15+
</shape>

0 commit comments

Comments
 (0)