Skip to content

Commit 4d01cc3

Browse files
authored
Merge pull request #412 from Countly/fw_rework_basic
[Android] Remaster feedback widgets
2 parents 167cd36 + 4fc70d1 commit 4d01cc3

File tree

6 files changed

+199
-81
lines changed

6 files changed

+199
-81
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
## XX.XX.XX
2+
* The feedback widgets now have transparent backgrounds for a cleaner look.
3+
4+
* Deprecated "presentFeedbackWidget(widgetInfo, context, closeButtonText, devCallback)", replaced with "presentFeedbackWidget(widgetInfo, context, devCallback)" in the feedbacks.
25
* Added a config method to disable server config in the initialization "disableSDKBehaviorSettings()".
36

47
## 25.4.0

sdk/src/main/java/ly/count/android/sdk/ModuleContent.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,13 @@ void fetchContentsInternal(@NonNull String[] categories) {
8686
intent.putExtra(TransparentActivity.CONFIGURATION_LANDSCAPE, placementCoordinates.get(Configuration.ORIENTATION_LANDSCAPE));
8787
intent.putExtra(TransparentActivity.CONFIGURATION_PORTRAIT, placementCoordinates.get(Configuration.ORIENTATION_PORTRAIT));
8888
intent.putExtra(TransparentActivity.ORIENTATION, _cly.context_.getResources().getConfiguration().orientation);
89+
90+
Long id = System.currentTimeMillis();
91+
intent.putExtra(TransparentActivity.ID_CALLBACK, id);
92+
if (globalContentCallback != null) {
93+
TransparentActivity.contentCallbacks.put(id, globalContentCallback);
94+
}
95+
8996
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
9097
_cly.context_.startActivity(intent);
9198

@@ -213,9 +220,6 @@ private TransparentActivityConfig extractOrientationPlacements(@NonNull JSONObje
213220

214221
TransparentActivityConfig config = new TransparentActivityConfig((int) Math.ceil(x * density), (int) Math.ceil(y * density), (int) Math.ceil(w * density), (int) Math.ceil(h * density));
215222
config.url = content;
216-
// TODO, passing callback with an intent is impossible, need to find a way to pass it
217-
// Currently, the callback is set as a static variable in TransparentActivity
218-
TransparentActivity.globalContentCallback = globalContentCallback;
219223
return config;
220224
}
221225

sdk/src/main/java/ly/count/android/sdk/ModuleFeedback.java

Lines changed: 125 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,37 @@
33
import android.app.AlertDialog;
44
import android.content.Context;
55
import android.content.DialogInterface;
6+
import android.content.Intent;
7+
import android.content.res.Configuration;
8+
import android.content.res.Resources;
69
import android.os.Handler;
710
import android.os.Looper;
11+
import android.util.DisplayMetrics;
812
import android.webkit.WebSettings;
9-
import android.webkit.WebView;
1013
import androidx.annotation.NonNull;
1114
import androidx.annotation.Nullable;
15+
import java.io.Serializable;
1216
import java.util.ArrayList;
1317
import java.util.HashMap;
1418
import java.util.Iterator;
1519
import java.util.List;
1620
import java.util.Map;
21+
import java.util.concurrent.ConcurrentHashMap;
1722
import org.json.JSONArray;
1823
import org.json.JSONException;
1924
import org.json.JSONObject;
25+
// import android.webkit.WebView;
2026

2127
public class ModuleFeedback extends ModuleBase {
2228

2329
public enum FeedbackWidgetType {survey, nps, rating}
2430

25-
public static class CountlyFeedbackWidget {
31+
public static class CountlyFeedbackWidget implements Serializable {
2632
public String widgetId;
2733
public FeedbackWidgetType type;
2834
public String name;
2935
public String[] tags;
36+
public String widgetVersion;
3037
}
3138

3239
final static String NPS_EVENT_KEY = "[CLY]_nps";
@@ -123,6 +130,7 @@ static List<CountlyFeedbackWidget> parseFeedbackList(JSONObject requestResponse)
123130
String valId = jObj.optString("_id", "");
124131
String valType = jObj.optString("type", "");
125132
String valName = jObj.optString("name", "");
133+
String widgetVersion = jObj.isNull("wv") ? null : jObj.optString("wv", null);
126134
List<String> valTagsArr = new ArrayList<String>();
127135

128136
JSONArray jTagArr = jObj.optJSONArray("tg");
@@ -161,6 +169,7 @@ static List<CountlyFeedbackWidget> parseFeedbackList(JSONObject requestResponse)
161169
se.widgetId = valId;
162170
se.name = valName;
163171
se.tags = valTagsArr.toArray(new String[0]);
172+
se.widgetVersion = widgetVersion;
164173

165174
parsedRes.add(se);
166175
} catch (Exception ex) {
@@ -247,90 +256,132 @@ void presentFeedbackWidgetInternal(@Nullable final CountlyFeedbackWidget widgetI
247256
JSONObject customObjectToSendWithTheWidget = new JSONObject();
248257
try {
249258
customObjectToSendWithTheWidget.put("tc", 1);
259+
// these are used only in case of a widget with a version
260+
if (!Utils.isNullOrEmpty(widgetInfo.widgetVersion)) {
261+
customObjectToSendWithTheWidget.put("rw", 1);
262+
customObjectToSendWithTheWidget.put("xb", 1);
263+
}
250264
} catch (JSONException e) {
251265
throw new RuntimeException(e);
252266
}
253267
widgetListUrl.append("&custom=");
254-
widgetListUrl.append(customObjectToSendWithTheWidget.toString());
255-
256-
final String preparedWidgetUrl = widgetListUrl.toString();
257-
258-
L.d("[ModuleFeedback] Using following url for widget:[" + widgetListUrl + "]");
259-
260-
//enable for chrome debugging
261-
//WebView.setWebContentsDebuggingEnabled(true);
262-
263-
final boolean useAlertDialog = true;
264-
Handler handler = new Handler(Looper.getMainLooper());
265-
handler.post(new Runnable() {
266-
public void run() {
267-
L.d("[ModuleFeedback] Calling on main thread");
268+
widgetListUrl.append(customObjectToSendWithTheWidget);
268269

269-
try {
270+
String preparedWidgetUrl = widgetListUrl.toString();
270271

271-
ModuleRatings.RatingDialogWebView webView = new ModuleRatings.RatingDialogWebView(context);
272-
webView.getSettings().setJavaScriptEnabled(true);
273-
webView.clearCache(true);
274-
webView.clearHistory();
275-
webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
276-
webView.setWebViewClient(new ModuleRatings.FeedbackDialogWebViewClient());
277-
webView.loadUrl(preparedWidgetUrl);
278-
webView.requestFocus();
279-
280-
AlertDialog.Builder builder = prepareAlertDialog(context, webView, closeButtonText, widgetInfo, devCallback);
281-
282-
if (useAlertDialog) {
283-
// use alert dialog to host the webView
284-
L.d("[ModuleFeedback] Creating standalone Alert dialog");
285-
builder.show();
286-
} else {
287-
// use dialog fragment to host the webView
288-
L.d("[ModuleFeedback] Creating Alert dialog in dialogFragment");
272+
L.d("[ModuleFeedback] Using following url for widget:[" + preparedWidgetUrl + "]");
273+
if (!Utils.isNullOrEmpty(widgetInfo.widgetVersion)) {
274+
L.d("[ModuleFeedback] Will use transparent activity for displaying the widget");
275+
showFeedbackWidget_newActivity(context, preparedWidgetUrl, widgetInfo, devCallback);
276+
} else {
277+
L.d("[ModuleFeedback] Will use dialog for displaying the widget");
278+
//enable for chrome debugging
279+
// WebView.setWebContentsDebuggingEnabled(true);
280+
Handler handler = new Handler(Looper.getMainLooper());
281+
handler.post(new Runnable() {
282+
public void run() {
283+
L.d("[ModuleFeedback] Calling on main thread");
289284

290-
//CountlyDialogFragment newFragment = CountlyDialogFragment.newInstance(builder);
291-
//newFragment.show(fragmentManager, "CountlyFragmentDialog");
292-
}
285+
try {
286+
showFeedbackWidget(context, widgetInfo, closeButtonText, devCallback, preparedWidgetUrl);
293287

294-
if (devCallback != null) {
295-
devCallback.onFinished(null);
296-
}
297-
} catch (Exception ex) {
298-
L.e("[ModuleFeedback] Failed at displaying feedback widget dialog, [" + ex.toString() + "]");
299-
if (devCallback != null) {
300-
devCallback.onFinished("Failed at displaying feedback widget dialog, [" + ex.toString() + "]");
288+
if (devCallback != null) {
289+
devCallback.onFinished(null);
290+
}
291+
} catch (Exception ex) {
292+
L.e("[ModuleFeedback] Failed at displaying feedback widget dialog, [" + ex.toString() + "]");
293+
if (devCallback != null) {
294+
devCallback.onFinished("Failed at displaying feedback widget dialog, [" + ex.toString() + "]");
295+
}
301296
}
302297
}
303-
}
304-
});
298+
});
299+
}
305300
}
306301

307-
AlertDialog.Builder prepareAlertDialog(@NonNull final Context context, @NonNull WebView webView, @Nullable String closeButtonText, @NonNull final CountlyFeedbackWidget widgetInfo, @Nullable final FeedbackCallback devCallback) {
308-
AlertDialog.Builder builder = new AlertDialog.Builder(context);
309-
builder.setView(webView);
310-
builder.setCancelable(false);
302+
private void showFeedbackWidget(Context context, CountlyFeedbackWidget widgetInfo, String closeButtonText, FeedbackCallback devCallback, String url) {
303+
ModuleRatings.RatingDialogWebView webView = new ModuleRatings.RatingDialogWebView(context);
304+
webView.getSettings().setJavaScriptEnabled(true);
305+
webView.clearCache(true);
306+
webView.clearHistory();
307+
webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
308+
ModuleRatings.FeedbackDialogWebViewClient webViewClient = new ModuleRatings.FeedbackDialogWebViewClient();
309+
webView.setWebViewClient(webViewClient);
310+
webView.loadUrl(url);
311+
webView.requestFocus();
312+
313+
AlertDialog.Builder builder = new AlertDialog.Builder(context).setView(webView).setCancelable(false);
311314
String usedCloseButtonText = closeButtonText;
312315
if (closeButtonText == null || closeButtonText.isEmpty()) {
313316
usedCloseButtonText = "Close";
314317
}
315318
builder.setNeutralButton(usedCloseButtonText, new DialogInterface.OnClickListener() {
316319
@Override public void onClick(DialogInterface dialogInterface, int i) {
317320
L.d("[ModuleFeedback] Cancel button clicked for the feedback widget");
318-
reportFeedbackWidgetCancelButton(widgetInfo, deviceInfo.mp.getAppVersion(context));
321+
reportFeedbackWidgetCancelButton(widgetInfo);
319322

320323
if (devCallback != null) {
321324
devCallback.onClosed();
322325
}
323326
}
324327
});
325-
return builder;
328+
builder.show();
326329
}
327330

328-
void reportFeedbackWidgetCancelButton(@NonNull CountlyFeedbackWidget widgetInfo, @NonNull String appVersion) {
331+
private void showFeedbackWidget_newActivity(@NonNull Context context, String url, CountlyFeedbackWidget widgetInfo, FeedbackCallback devCallback) {
332+
DisplayMetrics displayMetrics = deviceInfo.mp.getDisplayMetrics(context);
333+
Resources resources = context.getResources();
334+
int currentOrientation = resources.getConfiguration().orientation;
335+
boolean portrait = currentOrientation == Configuration.ORIENTATION_PORTRAIT;
336+
337+
int width = displayMetrics.widthPixels;
338+
int height = displayMetrics.heightPixels;
339+
340+
// this calculation needs improvement for status bar and navigation bar
341+
int portraitWidth = portrait ? width : height;
342+
int portraitHeight = portrait ? height : width;
343+
int landscapeWidth = portrait ? height : width;
344+
int landscapeHeight = portrait ? width : height;
345+
346+
Map<Integer, TransparentActivityConfig> placementCoordinates = new ConcurrentHashMap<>();
347+
TransparentActivityConfig pConfig = new TransparentActivityConfig(0, 0, portraitWidth, portraitHeight);
348+
TransparentActivityConfig lConfig = new TransparentActivityConfig(0, 0, landscapeWidth, landscapeHeight);
349+
pConfig.url = url;
350+
lConfig.url = url;
351+
placementCoordinates.put(Configuration.ORIENTATION_PORTRAIT, pConfig);
352+
placementCoordinates.put(Configuration.ORIENTATION_LANDSCAPE, lConfig);
353+
354+
Intent intent = new Intent(context, TransparentActivity.class);
355+
intent.putExtra(TransparentActivity.CONFIGURATION_LANDSCAPE, placementCoordinates.get(Configuration.ORIENTATION_LANDSCAPE));
356+
intent.putExtra(TransparentActivity.CONFIGURATION_PORTRAIT, placementCoordinates.get(Configuration.ORIENTATION_PORTRAIT));
357+
intent.putExtra(TransparentActivity.ORIENTATION, context.getResources().getConfiguration().orientation);
358+
intent.putExtra(TransparentActivity.WIDGET_INFO, widgetInfo);
359+
360+
Long id = System.currentTimeMillis();
361+
intent.putExtra(TransparentActivity.ID_CALLBACK, id);
362+
if (devCallback != null) {
363+
ContentCallback feedbackCallback = new ContentCallback() {
364+
@Override public void onContentCallback(ContentStatus contentStatus, Map<String, Object> contentData) {
365+
if (contentStatus.equals(ContentStatus.CLOSED)) {
366+
devCallback.onClosed();
367+
} else {
368+
devCallback.onFinished(null);
369+
}
370+
}
371+
};
372+
TransparentActivity.contentCallbacks.put(id, feedbackCallback);
373+
}
374+
375+
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
376+
_cly.context_.startActivity(intent);
377+
}
378+
379+
void reportFeedbackWidgetCancelButton(@NonNull CountlyFeedbackWidget widgetInfo) {
329380
L.d("[reportFeedbackWidgetCancelButton] Cancel button event");
330381
if (consentProvider.getConsent(Countly.CountlyFeatureNames.feedback)) {
331382
final Map<String, Object> segm = new HashMap<>();
332383
segm.put("platform", "android");
333-
segm.put("app_version", appVersion);
384+
segm.put("app_version", cachedAppVersion);
334385
segm.put("widget_id", "" + widgetInfo.widgetId);
335386
segm.put("closed", "1");
336387
final String key;
@@ -675,21 +726,37 @@ public void getAvailableFeedbackWidgets(@Nullable RetrieveFeedbackWidgets callba
675726
}
676727

677728
/**
678-
* Present a chosen feedback widget in an alert dialog
729+
* Present a chosen feedback widget
679730
*
680731
* @param widgetInfo
681732
* @param context
682733
* @param closeButtonText if this is null, no "close" button will be shown
683734
* @param devCallback
735+
* @deprecated use {@link #presentFeedbackWidget(CountlyFeedbackWidget, Context, FeedbackCallback)} instead
684736
*/
685737
public void presentFeedbackWidget(@Nullable CountlyFeedbackWidget widgetInfo, @Nullable Context context, @Nullable String closeButtonText, @Nullable FeedbackCallback devCallback) {
686738
synchronized (_cly) {
687-
L.i("[Feedback] Trying to present feedback widget in an alert dialog");
739+
L.i("[Feedback] Trying to present feedback widget");
688740

689741
presentFeedbackWidgetInternal(widgetInfo, context, closeButtonText, devCallback);
690742
}
691743
}
692744

745+
/**
746+
* Present a chosen feedback widget
747+
*
748+
* @param widgetInfo the widget to present
749+
* @param context the context to use for displaying the feedback widget
750+
* @param devCallback callback to be called when the feedback widget is closed
751+
*/
752+
public void presentFeedbackWidget(@Nullable CountlyFeedbackWidget widgetInfo, @Nullable Context context, @Nullable FeedbackCallback devCallback) {
753+
synchronized (_cly) {
754+
L.i("[Feedback] Trying to present feedback widget");
755+
756+
presentFeedbackWidgetInternal(widgetInfo, context, null, devCallback);
757+
}
758+
}
759+
693760
/**
694761
* Download data for a specific widget so that it can be displayed with a custom UI
695762
* When requesting this data, it will count as a shown widget (will increment that "shown" count in the dashboard)
@@ -820,4 +887,4 @@ public void presentRating(@NonNull Context context, @NonNull String nameIDorTag,
820887
}
821888
}
822889
}
823-
}
890+
}

sdk/src/main/java/ly/count/android/sdk/ModuleRatings.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,10 +566,19 @@ public boolean onCheckIsTextEditor() {
566566
}
567567

568568
static class FeedbackDialogWebViewClient extends WebViewClient {
569+
570+
WebViewUrlListener listener;
571+
569572
@Override
570573
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
571574
String url = request.getUrl().toString();
572575

576+
if (listener != null) {
577+
if (listener.onUrl(url, view)) {
578+
return true;
579+
}
580+
}
581+
573582
// Filter out outgoing calls
574583
if (url.endsWith("cly_x_int=1")) {
575584
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));

0 commit comments

Comments
 (0)