Skip to content

Commit

Permalink
Grouped notifications into one and show the notifications as a list (#…
Browse files Browse the repository at this point in the history
…734)

* Group the notification into one showing all the notifications
Adds an action to clear all the notifications

* Show notification badges on android
  • Loading branch information
kunall17 authored and borisyankov committed Jun 26, 2017
1 parent 5f8a518 commit b88fc00
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 19 deletions.
2 changes: 2 additions & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ buildscript {
}
//apply plugin: 'io.fabric'
repositories {
mavenCentral()
maven { url 'https://maven.fabric.io/public' }
}

Expand All @@ -162,6 +163,7 @@ dependencies {
compile project(':react-native-vector-icons')
compile fileTree(dir: "libs", include: ["*.jar"])
compile "com.android.support:appcompat-v7:23.0.1"
compile "me.leolin:ShortcutBadger:1.1.16@aar"
compile "com.facebook.react:react-native:+" // From node_modules
compile('com.crashlytics.sdk.android:crashlytics:2.5.5@aar') {
transitive = true;
Expand Down
25 changes: 21 additions & 4 deletions android/app/src/main/java/com/zulipmobile/MainApplication.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.zulipmobile;

import android.app.Application;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.util.Pair;

import com.facebook.react.ReactApplication;
import com.wix.reactnativenotifications.RNNotificationsPackage;
Expand All @@ -16,23 +18,28 @@
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;
import com.learnium.RNDeviceInfo.RNDeviceInfo;
import com.smixx.fabric.FabricPackage;
import com.crashlytics.android.Crashlytics;
import io.fabric.sdk.android.Fabric;
import com.wix.reactnativenotifications.RNNotificationsPackage;
import com.wix.reactnativenotifications.core.AppLaunchHelper;
import com.wix.reactnativenotifications.core.AppLifecycleFacade;
import com.wix.reactnativenotifications.core.JsIOHelper;
import com.wix.reactnativenotifications.core.notification.INotificationsApplication;
import com.wix.reactnativenotifications.core.notification.IPushNotification;
import com.zmxv.RNSound.RNSoundPackage;
import com.zulipmobile.notifications.GCMPushNotifications;
import com.zulipmobile.notifications.PushNotificationsProp;

import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;

import com.learnium.RNDeviceInfo.RNDeviceInfo;
import static com.zulipmobile.notifications.GCMPushNotifications.ACTION_NOTIFICATIONS_DISMISS;
import static com.zulipmobile.notifications.NotificationHelper.addConversationToMap;
import static com.zulipmobile.notifications.NotificationHelper.clearConversations;

public class MainApplication extends Application implements ReactApplication, INotificationsApplication {
private LinkedHashMap<String, Pair<String, Integer>> conversations;

private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
Expand Down Expand Up @@ -66,10 +73,20 @@ public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
//Fabric.with(this, new Crashlytics());
conversations = new LinkedHashMap<>();
}

@Override
public IPushNotification getPushNotification(Context context, Bundle bundle, AppLifecycleFacade defaultFacade, AppLaunchHelper defaultAppLaunchHelper) {
return new GCMPushNotifications(context, bundle, defaultFacade, defaultAppLaunchHelper, new JsIOHelper());
if (ACTION_NOTIFICATIONS_DISMISS.equals(bundle.getString(ACTION_NOTIFICATIONS_DISMISS))) {
clearConversations(conversations);
NotificationManager nMgr = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nMgr.cancelAll();
return null;
} else {
PushNotificationsProp prop = new PushNotificationsProp(bundle);
addConversationToMap(prop, conversations);
return new GCMPushNotifications(context, bundle, defaultFacade, defaultAppLaunchHelper, new JsIOHelper(), conversations);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,47 @@
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;

import com.wix.reactnativenotifications.core.AppLaunchHelper;
import com.wix.reactnativenotifications.core.AppLifecycleFacade;
import com.wix.reactnativenotifications.core.JsIOHelper;
import com.wix.reactnativenotifications.core.ProxyService;
import com.wix.reactnativenotifications.core.notification.PushNotification;
import com.zulipmobile.R;

import java.io.IOException;
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.Locale;

import me.leolin.shortcutbadger.ShortcutBadger;

import static com.zulipmobile.notifications.NotificationHelper.buildNotificationContent;
import static com.zulipmobile.notifications.NotificationHelper.clearConversations;
import static com.zulipmobile.notifications.NotificationHelper.extractNames;
import static com.zulipmobile.notifications.NotificationHelper.extractTotalMessagesCount;

public class GCMPushNotifications extends PushNotification {

private static final int NOTIFICATION_ID = 1;
private static final int NOTIFICATION_ID = 435;
public static final String ACTION_NOTIFICATIONS_DISMISS = "ACTION_NOTIFICATIONS_DISMISS";
private LinkedHashMap<String, Pair<String, Integer>> conversations;

/**
* Same as {@link com.wix.reactnativenotifications.core.NotificationIntentAdapter#PUSH_NOTIFICATION_EXTRA_NAME}
*/
private static final String PUSH_NOTIFICATION_EXTRA_NAME = "pushNotification";

public GCMPushNotifications(Context context, Bundle bundle, AppLifecycleFacade appLifecycleFacade, AppLaunchHelper appLaunchHelper, JsIOHelper jsIoHelper) {
public GCMPushNotifications(Context context, Bundle bundle, AppLifecycleFacade appLifecycleFacade, AppLaunchHelper appLaunchHelper, JsIOHelper jsIoHelper, LinkedHashMap<String, Pair<String, Integer>> conversations) {
super(context, bundle, appLifecycleFacade, appLaunchHelper, jsIoHelper);
this.conversations = conversations;
}

@Override
Expand All @@ -33,6 +55,19 @@ protected PushNotificationsProp getProps() {
return (PushNotificationsProp) mNotificationProps;
}


@Override
public void onOpened() {
super.onOpened();
clearConversations(conversations);
try {
ShortcutBadger.removeCount(mContext);
} catch (Exception e) {
Log.e("BADGE ERROR", e.toString());
}
}


@Override
protected Notification.Builder getNotificationBuilder(PendingIntent intent) {
// First, get a builder initialized with defaults from the core class.
Expand All @@ -46,33 +81,65 @@ protected Notification.Builder getNotificationBuilder(PendingIntent intent) {
String stream = getProps().getStream();
String topic = getProps().getTopic();
String baseURL = getProps().getBaseURL();
int totalMessagesCount = extractTotalMessagesCount(conversations);

builder.setSmallIcon(R.drawable.zulip_notification);
builder.setContentTitle(title);
builder.setAutoCancel(true);
builder.setContentText(content);

if (type.equals("stream")) {
if (android.os.Build.VERSION.SDK_INT >= 16) {
String displayTopic = stream + " > "
+ topic;
builder.setSubText("Mention on " + displayTopic);
if (conversations.size() == 1) {
//Only one 1 notification therefore no using of big view styles
builder.setContentTitle(title);
if (type.equals("stream")) {
if (Build.VERSION.SDK_INT >= 16) {
String displayTopic = stream + " > "
+ topic;
builder.setSubText("Mention on " + displayTopic);
}
}
}
if (avatarURL != null && avatarURL.startsWith("http")) {
Bitmap avatar = fetchAvatar(NotificationHelper.sizedURL(mContext,
avatarURL, 64, baseURL));
if (avatar != null) {
builder.setLargeIcon(avatar);
if (avatarURL != null && avatarURL.startsWith("http")) {
Bitmap avatar = fetchAvatar(NotificationHelper.sizedURL(mContext,
avatarURL, 64, baseURL));
if (avatar != null) {
builder.setLargeIcon(avatar);
}
}
} else {
builder.setContentTitle(String.format(Locale.ENGLISH, "%d messages in %d conversations", totalMessagesCount, conversations.size()));
builder.setStyle(new Notification.BigTextStyle().bigText(buildNotificationContent(conversations)));
builder.setContentText("Messages from " + TextUtils.join(",", extractNames(conversations)));
}

try {
ShortcutBadger.applyCount(mContext, totalMessagesCount);
} catch (Exception e) {
Log.e("BADGE ERROR", e.toString());
}

if (time != null) {
long timStamp = Long.parseLong(getProps().getTime()) * 1000;
builder.setWhen(timStamp);
}

long[] vPattern = {0, 100, 200, 100};
builder.setVibrate(vPattern);


/**
* Ideally, actions are sent using dismissIntent.setAction(String),
* But here {@link com.wix.reactnativenotifications.core.NotificationIntentAdapter#extractPendingNotificationDataFromIntent(Intent)}
* it checks in the bundle hence, An empty bundle is sent and checked in
* {@link com.zulipmobile.MainApplication#getPushNotification} for this string and then dismissed
*
**/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Intent dismissIntent = new Intent(mContext, ProxyService.class);
Bundle bundle = new Bundle();
bundle.putString(ACTION_NOTIFICATIONS_DISMISS, ACTION_NOTIFICATIONS_DISMISS);
dismissIntent.putExtra(PUSH_NOTIFICATION_EXTRA_NAME, bundle);
PendingIntent piDismiss = PendingIntent.getService(mContext, 0, dismissIntent, 0);
Notification.Action action = new Notification.Action(android.R.drawable.ic_notification_clear_all, "Clear", piDismiss);
builder.addAction(action);
}
return builder;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import android.util.Pair;
import android.util.TypedValue;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.LinkedHashMap;
import java.util.Map;

public class NotificationHelper {

Expand Down Expand Up @@ -49,4 +52,61 @@ public static String addHost(String url, String baseURL) {
}
return url;
}


public static String extractName(String key) {
return key.split(":")[0];
}

public static String buildNotificationContent(LinkedHashMap<String, Pair<String, Integer>> conversations) {
StringBuilder stringBuilder = new StringBuilder();
for (Map.Entry<String, Pair<String, Integer>> map : conversations.entrySet()) {
stringBuilder.append(extractName(map.getKey())).append(" (").append(map.getValue().second).append("): ").append(map.getValue().first).append("\n");
}
return stringBuilder.toString();
}

public static int extractTotalMessagesCount(LinkedHashMap<String, Pair<String, Integer>> conversations) {
int totalNumber = 0;
for (Map.Entry<String, Pair<String, Integer>> map : conversations.entrySet()) {
totalNumber += map.getValue().second;
}
return totalNumber;
}

/**
* Formats -
* private message - fullName:Email
* stream message - fullName:Email:stream
*/
public static String buildKeyString(PushNotificationsProp prop) {
if (prop.getRecipientType() == "stream")
return prop.getSenderFullName() + ":" + prop.getEmail();
else
return String.format("%s:%s:stream", prop.getSenderFullName(), prop.getEmail());
}

public static String[] extractNames(LinkedHashMap<String, Pair<String, Integer>> conversations) {
String[] names = new String[conversations.size()];
int index = 0;
for (Map.Entry<String, Pair<String, Integer>> map : conversations.entrySet()) {
names[index++] = map.getKey().split(":")[0];
}
return names;
}

public static void addConversationToMap(PushNotificationsProp prop, LinkedHashMap<String, Pair<String, Integer>> conversations) {
String key = buildKeyString(prop);
Pair<String, Integer> messages = conversations.get(key);
if (messages != null) {
conversations.put(key, new Pair<>(prop.getContent(), messages.second + 1));
} else {
conversations.put(key, new Pair<>(prop.getContent(), 1));
}
}


public static void clearConversations(LinkedHashMap<String, Pair<String, Integer>> conversations) {
conversations.clear();
}
}

0 comments on commit b88fc00

Please sign in to comment.