Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
## XX.XX.XX
* The feedback widgets now have transparent backgrounds for a cleaner look.

* Deprecated "presentFeedbackWidget(widgetInfo, context, closeButtonText, devCallback)", replaced with "presentFeedbackWidget(widgetInfo, context, devCallback)" in the feedbacks.
* Added a config method to disable server config in the initialization "disableSDKBehaviorSettings()".

## 25.4.0
Expand Down
10 changes: 7 additions & 3 deletions sdk/src/main/java/ly/count/android/sdk/ModuleContent.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ void fetchContentsInternal(@NonNull String[] categories) {
intent.putExtra(TransparentActivity.CONFIGURATION_LANDSCAPE, placementCoordinates.get(Configuration.ORIENTATION_LANDSCAPE));
intent.putExtra(TransparentActivity.CONFIGURATION_PORTRAIT, placementCoordinates.get(Configuration.ORIENTATION_PORTRAIT));
intent.putExtra(TransparentActivity.ORIENTATION, _cly.context_.getResources().getConfiguration().orientation);

Long id = System.currentTimeMillis();
intent.putExtra(TransparentActivity.ID_CALLBACK, id);
if (globalContentCallback != null) {
TransparentActivity.contentCallbacks.put(id, globalContentCallback);
}

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
_cly.context_.startActivity(intent);

Expand Down Expand Up @@ -213,9 +220,6 @@ private TransparentActivityConfig extractOrientationPlacements(@NonNull JSONObje

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

Expand Down
183 changes: 125 additions & 58 deletions sdk/src/main/java/ly/count/android/sdk/ModuleFeedback.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,37 @@
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Looper;
import android.util.DisplayMetrics;
import android.webkit.WebSettings;
import android.webkit.WebView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
// import android.webkit.WebView;

public class ModuleFeedback extends ModuleBase {

public enum FeedbackWidgetType {survey, nps, rating}

public static class CountlyFeedbackWidget {
public static class CountlyFeedbackWidget implements Serializable {
public String widgetId;
public FeedbackWidgetType type;
public String name;
public String[] tags;
public String widgetVersion;
}

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

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

parsedRes.add(se);
} catch (Exception ex) {
Expand Down Expand Up @@ -247,90 +256,132 @@ void presentFeedbackWidgetInternal(@Nullable final CountlyFeedbackWidget widgetI
JSONObject customObjectToSendWithTheWidget = new JSONObject();
try {
customObjectToSendWithTheWidget.put("tc", 1);
// these are used only in case of a widget with a version
if (!Utils.isNullOrEmpty(widgetInfo.widgetVersion)) {
customObjectToSendWithTheWidget.put("rw", 1);
customObjectToSendWithTheWidget.put("xb", 1);
}
} catch (JSONException e) {
throw new RuntimeException(e);
}
widgetListUrl.append("&custom=");
widgetListUrl.append(customObjectToSendWithTheWidget.toString());

final String preparedWidgetUrl = widgetListUrl.toString();

L.d("[ModuleFeedback] Using following url for widget:[" + widgetListUrl + "]");

//enable for chrome debugging
//WebView.setWebContentsDebuggingEnabled(true);

final boolean useAlertDialog = true;
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
public void run() {
L.d("[ModuleFeedback] Calling on main thread");
widgetListUrl.append(customObjectToSendWithTheWidget);

try {
String preparedWidgetUrl = widgetListUrl.toString();

ModuleRatings.RatingDialogWebView webView = new ModuleRatings.RatingDialogWebView(context);
webView.getSettings().setJavaScriptEnabled(true);
webView.clearCache(true);
webView.clearHistory();
webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
webView.setWebViewClient(new ModuleRatings.FeedbackDialogWebViewClient());
webView.loadUrl(preparedWidgetUrl);
webView.requestFocus();

AlertDialog.Builder builder = prepareAlertDialog(context, webView, closeButtonText, widgetInfo, devCallback);

if (useAlertDialog) {
// use alert dialog to host the webView
L.d("[ModuleFeedback] Creating standalone Alert dialog");
builder.show();
} else {
// use dialog fragment to host the webView
L.d("[ModuleFeedback] Creating Alert dialog in dialogFragment");
L.d("[ModuleFeedback] Using following url for widget:[" + preparedWidgetUrl + "]");
if (!Utils.isNullOrEmpty(widgetInfo.widgetVersion)) {
L.d("[ModuleFeedback] Will use transparent activity for displaying the widget");
showFeedbackWidget_newActivity(context, preparedWidgetUrl, widgetInfo, devCallback);
} else {
L.d("[ModuleFeedback] Will use dialog for displaying the widget");
//enable for chrome debugging
// WebView.setWebContentsDebuggingEnabled(true);
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
public void run() {
L.d("[ModuleFeedback] Calling on main thread");

//CountlyDialogFragment newFragment = CountlyDialogFragment.newInstance(builder);
//newFragment.show(fragmentManager, "CountlyFragmentDialog");
}
try {
showFeedbackWidget(context, widgetInfo, closeButtonText, devCallback, preparedWidgetUrl);

if (devCallback != null) {
devCallback.onFinished(null);
}
} catch (Exception ex) {
L.e("[ModuleFeedback] Failed at displaying feedback widget dialog, [" + ex.toString() + "]");
if (devCallback != null) {
devCallback.onFinished("Failed at displaying feedback widget dialog, [" + ex.toString() + "]");
if (devCallback != null) {
devCallback.onFinished(null);
}
} catch (Exception ex) {
L.e("[ModuleFeedback] Failed at displaying feedback widget dialog, [" + ex.toString() + "]");
if (devCallback != null) {
devCallback.onFinished("Failed at displaying feedback widget dialog, [" + ex.toString() + "]");
}
}
}
}
});
});
}
}

AlertDialog.Builder prepareAlertDialog(@NonNull final Context context, @NonNull WebView webView, @Nullable String closeButtonText, @NonNull final CountlyFeedbackWidget widgetInfo, @Nullable final FeedbackCallback devCallback) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setView(webView);
builder.setCancelable(false);
private void showFeedbackWidget(Context context, CountlyFeedbackWidget widgetInfo, String closeButtonText, FeedbackCallback devCallback, String url) {
ModuleRatings.RatingDialogWebView webView = new ModuleRatings.RatingDialogWebView(context);
webView.getSettings().setJavaScriptEnabled(true);
webView.clearCache(true);
webView.clearHistory();
webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
ModuleRatings.FeedbackDialogWebViewClient webViewClient = new ModuleRatings.FeedbackDialogWebViewClient();
webView.setWebViewClient(webViewClient);
webView.loadUrl(url);
webView.requestFocus();

AlertDialog.Builder builder = new AlertDialog.Builder(context).setView(webView).setCancelable(false);
String usedCloseButtonText = closeButtonText;
if (closeButtonText == null || closeButtonText.isEmpty()) {
usedCloseButtonText = "Close";
}
builder.setNeutralButton(usedCloseButtonText, new DialogInterface.OnClickListener() {
@Override public void onClick(DialogInterface dialogInterface, int i) {
L.d("[ModuleFeedback] Cancel button clicked for the feedback widget");
reportFeedbackWidgetCancelButton(widgetInfo, deviceInfo.mp.getAppVersion(context));
reportFeedbackWidgetCancelButton(widgetInfo);

if (devCallback != null) {
devCallback.onClosed();
}
}
});
return builder;
builder.show();
}

void reportFeedbackWidgetCancelButton(@NonNull CountlyFeedbackWidget widgetInfo, @NonNull String appVersion) {
private void showFeedbackWidget_newActivity(@NonNull Context context, String url, CountlyFeedbackWidget widgetInfo, FeedbackCallback devCallback) {
DisplayMetrics displayMetrics = deviceInfo.mp.getDisplayMetrics(context);
Resources resources = context.getResources();
int currentOrientation = resources.getConfiguration().orientation;
boolean portrait = currentOrientation == Configuration.ORIENTATION_PORTRAIT;

int width = displayMetrics.widthPixels;
int height = displayMetrics.heightPixels;

// this calculation needs improvement for status bar and navigation bar
int portraitWidth = portrait ? width : height;
int portraitHeight = portrait ? height : width;
int landscapeWidth = portrait ? height : width;
int landscapeHeight = portrait ? width : height;

Map<Integer, TransparentActivityConfig> placementCoordinates = new ConcurrentHashMap<>();
TransparentActivityConfig pConfig = new TransparentActivityConfig(0, 0, portraitWidth, portraitHeight);
TransparentActivityConfig lConfig = new TransparentActivityConfig(0, 0, landscapeWidth, landscapeHeight);
pConfig.url = url;
lConfig.url = url;
placementCoordinates.put(Configuration.ORIENTATION_PORTRAIT, pConfig);
placementCoordinates.put(Configuration.ORIENTATION_LANDSCAPE, lConfig);

Intent intent = new Intent(context, TransparentActivity.class);
intent.putExtra(TransparentActivity.CONFIGURATION_LANDSCAPE, placementCoordinates.get(Configuration.ORIENTATION_LANDSCAPE));
intent.putExtra(TransparentActivity.CONFIGURATION_PORTRAIT, placementCoordinates.get(Configuration.ORIENTATION_PORTRAIT));
intent.putExtra(TransparentActivity.ORIENTATION, context.getResources().getConfiguration().orientation);
intent.putExtra(TransparentActivity.WIDGET_INFO, widgetInfo);

Long id = System.currentTimeMillis();
intent.putExtra(TransparentActivity.ID_CALLBACK, id);
if (devCallback != null) {
ContentCallback feedbackCallback = new ContentCallback() {
@Override public void onContentCallback(ContentStatus contentStatus, Map<String, Object> contentData) {
if (contentStatus.equals(ContentStatus.CLOSED)) {
devCallback.onClosed();
} else {
devCallback.onFinished(null);
}
}
};
TransparentActivity.contentCallbacks.put(id, feedbackCallback);
}

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
_cly.context_.startActivity(intent);
}

void reportFeedbackWidgetCancelButton(@NonNull CountlyFeedbackWidget widgetInfo) {
L.d("[reportFeedbackWidgetCancelButton] Cancel button event");
if (consentProvider.getConsent(Countly.CountlyFeatureNames.feedback)) {
final Map<String, Object> segm = new HashMap<>();
segm.put("platform", "android");
segm.put("app_version", appVersion);
segm.put("app_version", cachedAppVersion);
segm.put("widget_id", "" + widgetInfo.widgetId);
segm.put("closed", "1");
final String key;
Expand Down Expand Up @@ -675,21 +726,37 @@ public void getAvailableFeedbackWidgets(@Nullable RetrieveFeedbackWidgets callba
}

/**
* Present a chosen feedback widget in an alert dialog
* Present a chosen feedback widget
*
* @param widgetInfo
* @param context
* @param closeButtonText if this is null, no "close" button will be shown
* @param devCallback
* @deprecated use {@link #presentFeedbackWidget(CountlyFeedbackWidget, Context, FeedbackCallback)} instead
*/
public void presentFeedbackWidget(@Nullable CountlyFeedbackWidget widgetInfo, @Nullable Context context, @Nullable String closeButtonText, @Nullable FeedbackCallback devCallback) {
synchronized (_cly) {
L.i("[Feedback] Trying to present feedback widget in an alert dialog");
L.i("[Feedback] Trying to present feedback widget");

presentFeedbackWidgetInternal(widgetInfo, context, closeButtonText, devCallback);
}
}

/**
* Present a chosen feedback widget
*
* @param widgetInfo the widget to present
* @param context the context to use for displaying the feedback widget
* @param devCallback callback to be called when the feedback widget is closed
*/
public void presentFeedbackWidget(@Nullable CountlyFeedbackWidget widgetInfo, @Nullable Context context, @Nullable FeedbackCallback devCallback) {
synchronized (_cly) {
L.i("[Feedback] Trying to present feedback widget");

presentFeedbackWidgetInternal(widgetInfo, context, null, devCallback);
}
}

/**
* Download data for a specific widget so that it can be displayed with a custom UI
* When requesting this data, it will count as a shown widget (will increment that "shown" count in the dashboard)
Expand Down Expand Up @@ -820,4 +887,4 @@ public void presentRating(@NonNull Context context, @NonNull String nameIDorTag,
}
}
}
}
}
9 changes: 9 additions & 0 deletions sdk/src/main/java/ly/count/android/sdk/ModuleRatings.java
Original file line number Diff line number Diff line change
Expand Up @@ -566,10 +566,19 @@ public boolean onCheckIsTextEditor() {
}

static class FeedbackDialogWebViewClient extends WebViewClient {

WebViewUrlListener listener;

@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
String url = request.getUrl().toString();

if (listener != null) {
if (listener.onUrl(url, view)) {
return true;
}
}

// Filter out outgoing calls
if (url.endsWith("cly_x_int=1")) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
Expand Down
Loading
Loading