diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index d89194b6ce..33ae65cbfa 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -71,7 +71,6 @@
-
persons;
- private HashMap chats;
- private Context context;
- private Intent serviceIntent;
- private Boolean shouldRefreshNotifications;
- private int ONE_TO_ONE_CHAT_TYPE = 1;
- private int PRIVATE_GROUP_CHAT_TYPE = 3;
-
- //NOTE: we use a dynamically created BroadcastReceiver here so that we can capture
- //intents from notifications and act on them. For instance when tapping/dismissing
- //a chat notification we want to clear the chat so that next messages don't show
- //the messages that we have seen again
- private final BroadcastReceiver notificationActionReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction() == ACTION_TAP_NOTIFICATION ||
- intent.getAction() == ACTION_DELETE_NOTIFICATION) {
- String chatId = intent.getExtras().getString("im.status.ethereum.chatId");
- int chatType = intent.getExtras().getInt("im.status.ethereum.chatType");
- if (intent.getAction() == ACTION_TAP_NOTIFICATION) {
- context.startActivity(getOpenAppIntent(chatId, chatType));
- }
- removeChat(chatId);
- // clean up the group notifications when there is no
- // more unread chats
- if (chats.size() == 0) {
- notificationManager.cancelAll();
- }}
- if (intent.getAction() == ACTION_TAP_STOP) {
- stop();
- System.exit(0);
- }
- Log.e(TAG, "intent received: " + intent.getAction());
- }
- };
-
- private void registerBroadcastReceiver() {
- IntentFilter filter = new IntentFilter();
- filter.addAction(ACTION_DELETE_NOTIFICATION);
- filter.addAction(ACTION_TAP_NOTIFICATION);
- filter.addAction(ACTION_TAP_STOP);
- context.registerReceiver(notificationActionReceiver, filter);
- Log.e(TAG, "Broadcast Receiver registered");
- }
-
- public Intent getOpenAppIntent() {
- Class intentClass;
- String packageName = context.getPackageName();
- Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
- String className = launchIntent.getComponent().getClassName();
- try {
- intentClass = Class.forName(className);
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- return null;
- }
- Intent intent = new Intent(context, intentClass);
- intent.addCategory(Intent.CATEGORY_BROWSABLE);
- intent.setAction(Intent.ACTION_VIEW);
- //NOTE: you might wonder, why the heck did he decide to set these flags in particular. Well,
- //the answer is a simple as it can get in the Android native development world. I noticed
- //that my initial setup was opening the app but wasn't triggering any events on the js side, like
- //the links do from the browser. So I compared both intents and noticed that the link from
- //the browser produces an intent with the flag 0x14000000. I found out that it was the following
- //flags in this link:
- //https://stackoverflow.com/questions/52390129/android-intent-setflags-issue
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- return intent;
- }
-
- //NOTE: this method takes a chatId and returns an intent that will open the app in that chat
- //Once we support other kind of notifications we will need to adapt it. The simplest method
- //is probably to pass the universal link as param instead of the chatId.
- public Intent getOpenAppIntent(String chatId, int chatType) {
- Intent intent = getOpenAppIntent();
- String path = "";
- if (chatType == ONE_TO_ONE_CHAT_TYPE) {
- path = "p/";
- } else if (chatType == PRIVATE_GROUP_CHAT_TYPE) {
- path = "g/args?a2=";
- }
- intent.setData(Uri.parse("status-im://" + path + chatId));
- return intent;
- }
-
- public NewMessageSignalHandler(Context context) {
- // NOTE: when instanciated the NewMessageSignalHandler class starts a foreground service
- // to keep the app running in the background in order to receive notifications
- // call the stop() method in order to stop the service
- this.context = context;
- this.persons = new HashMap();
- this.chats = new HashMap();
- this.notificationManager = context.getSystemService(NotificationManager.class);
- this.createNotificationChannel();
- this.shouldRefreshNotifications = false;
- Log.e(TAG, "Starting Foreground Service");
- Intent serviceIntent = new Intent(context, ForegroundService.class);
- context.startService(serviceIntent);
- this.registerBroadcastReceiver();
- }
-
- public void stop() {
- Log.e(TAG, "Stopping Foreground Service");
- //NOTE: we cancel all the current notifications, because the intents can't be used anymore
- //since the broadcast receiver will be killed as well and won't be able to handle any intent
- notificationManager.cancelAll();
- Intent serviceIntent = new Intent(context, ForegroundService.class);
- context.stopService(serviceIntent);
- context.unregisterReceiver(notificationActionReceiver);
- }
-
- private void createNotificationChannel() {
- // Create the NotificationChannel, but only on API 26+ because
- // the NotificationChannel class is new and not in the support library
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- Uri soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.getPackageName() + "/" + R.raw.notification_sound);
- NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
- channel.setDescription(context.getResources().getString(R.string.channel_description));
- AudioAttributes audioAttributes = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_NOTIFICATION)
- .build();
- channel.setSound(soundUri, audioAttributes);
- channel.setShowBadge(true);
- NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
- notificationManager.createNotificationChannel(channel);
- }
- }
-
- private void removeChat(String chatId) {
- this.chats.remove(chatId);
- }
-
- private PendingIntent createOnDismissedIntent(Context context, int notificationId, String chatId, int chatType) {
- Intent intent = new Intent(ACTION_DELETE_NOTIFICATION);
- intent.putExtra("im.status.ethereum.chatId", chatId);
- intent.putExtra("im.status.ethereum.chatType", chatType);
- return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, intent, PendingIntent.FLAG_CANCEL_CURRENT);
- }
-
- private PendingIntent createOnTapIntent(Context context, int notificationId, String chatId, int chatType) {
- Intent intent = new Intent(ACTION_TAP_NOTIFICATION);
- intent.putExtra("im.status.ethereum.chatId", chatId);
- intent.putExtra("im.status.ethereum.chatType", chatType);
- return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, intent, PendingIntent.FLAG_CANCEL_CURRENT);
- }
-
- public void notify(int notificationId, StatusChat chat) {
- NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle("Me");
- ArrayList messages = chat.getMessages();
- for (int i = 0; i < messages.size(); i++) {
- StatusMessage message = messages.get(i);
- messagingStyle.addMessage(message.getText(),
- message.getTimestamp(),
- message.getAuthor());
- }
- NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
- .setSmallIcon(R.drawable.ic_stat_notify_status)
- .setPriority(NotificationCompat.PRIORITY_HIGH)
- .setCategory(NotificationCompat.CATEGORY_MESSAGE)
- .setStyle(messagingStyle)
- .setGroup(GROUP_STATUS_MESSAGES)
- .setGroupSummary(true)
- .setContentIntent(createOnTapIntent(context, notificationId, chat.getId(), chat.getType()))
- .setDeleteIntent(createOnDismissedIntent(context, notificationId, chat.getId(), chat.getType()))
- .setNumber(messages.size())
- .setAutoCancel(true);
- if (Build.VERSION.SDK_INT >= 21) {
- builder.setVibrate(new long[0]);
- }
- notificationManager.notify(notificationId, builder.build());
- }
-
- public void refreshNotifications() {
- NotificationCompat.InboxStyle summaryStyle = new NotificationCompat.InboxStyle();
- int notificationId = 2; // we start at 2 because the service is using 1 and can't use 0
- Iterator chatIterator = chats.values().iterator();
- while(chatIterator.hasNext()) {
- StatusChat chat = (StatusChat)chatIterator.next();
- notify(notificationId, chat);
- notificationId++;
- }
- }
-
- void handleNewMessage (Bundle data) {
- upsertChat(data);
- upsertMessage(data);
-
- if(shouldRefreshNotifications) {
- refreshNotifications();
- shouldRefreshNotifications = false;
- }
- }
-
- private Person getPerson(String publicKey, String icon, String name) {
- // TODO: invalidate cache if icon and name are not the same as
- // the Person returned (in case the user set a different icon or username for instance)
- // not critical it's just for notifications at the moment
- // using a HashMap to cache Person because it's immutable
- Person person = persons.get(publicKey);
- if (person == null) {
- String base64Image = icon.split(",")[1];
- byte[] decodedString = Base64.decode(base64Image, Base64.DEFAULT);
- Bitmap decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
- person = new Person.Builder().setIcon(IconCompat.createWithBitmap(decodedByte)).setName(name).build();
- persons.put(publicKey, person);
- }
- return person;
- }
-
- private void upsertChat(Bundle data) {
- String id = data.getString("chatId");
- int type = Integer.parseInt(data.getString("chatType"));
- StatusChat chat = chats.get(id);
-
- // if the chat was not already there, we create one
- if (chat == null) {
- chat = new StatusChat(id, type);
- }
-
- chats.put(id, chat);
- }
-
- private void upsertMessage(Bundle data) {
- String chatId = data.getString("chatId");
- StatusChat chat = chats.get(chatId);
- if (chat == null) {
- return;
- }
-
- StatusMessage message = createMessage(data);
- if (message != null) {
- chat.appendMessage(message);
- chats.put(chatId, chat);
- shouldRefreshNotifications = true;
- }
- }
-
- private StatusMessage createMessage(Bundle data) {
- Person author = getPerson(data.getString("from"), data.getString("identicon"), data.getString("alias"));
- return new StatusMessage(author, data.getLong("whisperTimestamp"), data.getString("text"));
- }
-}
-
-class StatusChat {
- private ArrayList messages;
- private String id;
- private String name;
- private int type;
-
- StatusChat(String id, int type) {
- this.id = id;
- this.type = type;
- this.messages = new ArrayList();
- this.name = name;
- }
-
- public String getId() {
- return id;
- }
-
- public int getType() {
- return this.type;
- }
-
- public String getName() {
-
- //TODO this should be improved as it would rename the chat
- // after our own user if we were posting from another device
- // in 1-1 chats it should be the name of the user whose
- // key is different than ours
- StatusMessage message = getLastMessage();
- if (message == null) {
- return "no-name";
- }
- return message.getAuthor().getName().toString();
- }
-
- private StatusMessage getLastMessage() {
- if (messages.size() > 0) {
- return messages.get(messages.size()-1);
- }
- return null;
- }
-
- public long getTimestamp() {
- return getLastMessage().getTimestamp();
- }
-
- public ArrayList getMessages() {
- return messages;
- }
-
- public void appendMessage(StatusMessage message) {
- this.messages.add(message);
- }
-
- public String getSummary() {
- return "" + getLastMessage().getAuthor().getName() + ": " + getLastMessage().getText();
- }
-}
-
-
-class StatusMessage {
- public Person getAuthor() {
- return author;
- }
-
- public long getTimestamp() {
- return timestamp;
- }
-
- public String getText() {
- return text;
- }
-
- private Person author;
- private long timestamp;
- private String text;
-
- StatusMessage(Person author, long timestamp, String text) {
- this.author = author;
- this.timestamp = timestamp;
- this.text = text;
- }
-}
diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/pushnotifications/PushNotification.java b/modules/react-native-status/android/src/main/java/im/status/ethereum/pushnotifications/PushNotification.java
index 4ccd38f394..ce7a2e71b8 100644
--- a/modules/react-native-status/android/src/main/java/im/status/ethereum/pushnotifications/PushNotification.java
+++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/pushnotifications/PushNotification.java
@@ -37,7 +37,7 @@ public class PushNotification extends ReactContextBaseJavaModule implements Acti
private PushNotificationHelper pushNotificationHelper;
private PushNotificationJsDelivery delivery;
private ReactApplicationContext reactContext;
- private NewMessageSignalHandler newMessageSignalHandler;
+ private boolean started;
public PushNotification(ReactApplicationContext reactContext) {
super(reactContext);
@@ -104,33 +104,30 @@ public class PushNotification extends ReactContextBaseJavaModule implements Acti
@ReactMethod
public void presentLocalNotification(ReadableMap details) {
+ if (!this.started) {
+ return;
+ }
+
Bundle bundle = Arguments.toBundle(details);
// If notification ID is not provided by the user, generate one at random
if (bundle.getString("id") == null) {
bundle.putString("id", String.valueOf(mRandomNumberGenerator.nextInt()));
}
- String type = bundle.getString("type");
- if (type != null && type.equals("message")) {
- if (this.newMessageSignalHandler != null) {
- newMessageSignalHandler.handleNewMessage(bundle);
- }
- } else {
- pushNotificationHelper.sendToNotificationCentre(bundle);
- }
+ pushNotificationHelper.sendToNotificationCentre(bundle);
}
@ReactMethod
public void enableNotifications() {
- this.newMessageSignalHandler = new NewMessageSignalHandler(reactContext);
+ this.started = true;
+ this.pushNotificationHelper.start();
}
@ReactMethod
public void disableNotifications() {
- if (newMessageSignalHandler != null) {
- newMessageSignalHandler.stop();
- newMessageSignalHandler = null;
- }
+ if (this.started) {
+ this.started = false;
+ this.pushNotificationHelper.stop();
+ }
}
-
}
diff --git a/modules/react-native-status/android/src/main/java/im/status/ethereum/pushnotifications/PushNotificationHelper.java b/modules/react-native-status/android/src/main/java/im/status/ethereum/pushnotifications/PushNotificationHelper.java
index 9f7f94697f..91490b5f1c 100644
--- a/modules/react-native-status/android/src/main/java/im/status/ethereum/pushnotifications/PushNotificationHelper.java
+++ b/modules/react-native-status/android/src/main/java/im/status/ethereum/pushnotifications/PushNotificationHelper.java
@@ -11,6 +11,8 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.BroadcastReceiver;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
@@ -24,9 +26,12 @@ import android.os.Build;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
import android.util.Log;
+import android.util.Base64;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
+import androidx.core.app.Person;
+import androidx.core.graphics.drawable.IconCompat;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReadableArray;
@@ -43,8 +48,10 @@ import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
+import java.util.HashMap;
import java.util.Map;
+import im.status.ethereum.module.R;
import static im.status.ethereum.pushnotifications.PushNotification.LOG_TAG;
public class PushNotificationHelper {
@@ -53,11 +60,91 @@ public class PushNotificationHelper {
private static final long DEFAULT_VIBRATION = 300L;
private static final String CHANNEL_ID = "status-im-notifications";
+ public static final String ACTION_DELETE_NOTIFICATION = "im.status.ethereum.module.DELETE_NOTIFICATION";
+ public static final String ACTION_TAP_NOTIFICATION = "im.status.ethereum.module.TAP_NOTIFICATION";
+ public static final String ACTION_TAP_STOP = "im.status.ethereum.module.TAP_STOP";
+
+ private NotificationManager notificationManager;
+
+
+ private HashMap persons;
+ private HashMap messageGroups;
public PushNotificationHelper(Application context) {
this.context = context;
+ this.persons = new HashMap();
+ this.messageGroups = new HashMap();
+ this.notificationManager = context.getSystemService(NotificationManager.class);
+ this.registerBroadcastReceiver();
}
+ public Intent getOpenAppIntent(String deepLink) {
+ Class intentClass;
+ String packageName = context.getPackageName();
+ Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
+ String className = launchIntent.getComponent().getClassName();
+ try {
+ intentClass = Class.forName(className);
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ return null;
+ }
+ Intent intent = new Intent(context, intentClass);
+ intent.addCategory(Intent.CATEGORY_BROWSABLE);
+ intent.setAction(Intent.ACTION_VIEW);
+ //NOTE: you might wonder, why the heck did he decide to set these flags in particular. Well,
+ //the answer is a simple as it can get in the Android native development world. I noticed
+ //that my initial setup was opening the app but wasn't triggering any events on the js side, like
+ //the links do from the browser. So I compared both intents and noticed that the link from
+ //the browser produces an intent with the flag 0x14000000. I found out that it was the following
+ //flags in this link:
+ //https://stackoverflow.com/questions/52390129/android-intent-setflags-issue
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.setData(Uri.parse(deepLink));
+ return intent;
+ }
+
+ //NOTE: we use a dynamically created BroadcastReceiver here so that we can capture
+ //intents from notifications and act on them. For instance when tapping/dismissing
+ //a chat notification we want to clear the chat so that next messages don't show
+ //the messages that we have seen again
+ private final BroadcastReceiver notificationActionReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction() == ACTION_TAP_NOTIFICATION ||
+ intent.getAction() == ACTION_DELETE_NOTIFICATION) {
+ String deepLink = intent.getExtras().getString("im.status.ethereum.deepLink");
+ String groupId = intent.getExtras().getString("im.status.ethereum.groupId");
+ if (intent.getAction() == ACTION_TAP_NOTIFICATION) {
+ context.startActivity(getOpenAppIntent(deepLink));
+ }
+ if (groupId != null) {
+ removeGroup(groupId);
+ // clean up the group notifications when there is no
+ // more unread chats
+ if (messageGroups.size() == 0) {
+ notificationManager.cancelAll();
+ }}
+ }
+ if (intent.getAction() == ACTION_TAP_STOP) {
+ stop();
+ System.exit(0);
+ }
+ Log.e(LOG_TAG, "intent received: " + intent.getAction());
+ }
+ };
+
+ private void registerBroadcastReceiver() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_DELETE_NOTIFICATION);
+ filter.addAction(ACTION_TAP_NOTIFICATION);
+ filter.addAction(ACTION_TAP_STOP);
+ context.registerReceiver(notificationActionReceiver, filter);
+ Log.e(LOG_TAG, "Broadcast Receiver registered");
+ }
+
+
+
private NotificationManager notificationManager() {
return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
}
@@ -95,7 +182,12 @@ public class PushNotificationHelper {
aggregator.setBigPictureUrl(context, bundle.getString("bigPictureUrl"));
}
+ public void handleConversation(final Bundle bundle) {
+ this.addStatusMessage(bundle);
+ }
+
public void sendToNotificationCentreWithPicture(final Bundle bundle, Bitmap largeIconBitmap, Bitmap bigPictureBitmap) {
+
try {
Class intentClass = getMainActivityClass();
if (intentClass == null) {
@@ -103,6 +195,11 @@ public class PushNotificationHelper {
return;
}
+ if (bundle.getBoolean("isConversation")) {
+ this.handleConversation(bundle);
+ return;
+ }
+
if (bundle.getString("message") == null) {
// this happens when a 'data' notification is received - we do not synthesize a local notification in this case
Log.d(LOG_TAG, "Ignore this message if you sent data-only notification. Cannot send to notification centre because there is no 'message' field in: " + bundle);
@@ -325,10 +422,10 @@ public class PushNotificationHelper {
}
}
- int notificationID = Integer.parseInt(notificationIdString);
+ int notificationID = notificationIdString.hashCode();
- PendingIntent pendingIntent = PendingIntent.getActivity(context, notificationID, intent,
- PendingIntent.FLAG_UPDATE_CURRENT);
+ notification.setContentIntent(createOnTapIntent(context, notificationID, bundle.getString("deepLink")))
+ .setDeleteIntent(createOnDismissedIntent(context, notificationID, bundle.getString("deepLink")));
NotificationManager notificationManager = notificationManager();
@@ -367,7 +464,6 @@ public class PushNotificationHelper {
notification.setUsesChronometer(bundle.getBoolean("usesChronometer", false));
notification.setChannelId(channel_id);
- notification.setContentIntent(pendingIntent);
JSONArray actionsArray = null;
try {
@@ -546,4 +642,155 @@ public class PushNotificationHelper {
return false;
}
+ private Person getPerson(Bundle bundle) {
+ String base64Image = bundle.getString("icon").split(",")[1];
+ byte[] decodedString = Base64.decode(base64Image, Base64.DEFAULT);
+ Bitmap decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
+
+ String name = bundle.getString("name");
+
+ return new Person.Builder().setIcon(IconCompat.createWithBitmap(decodedByte)).setName(name).build();
+ }
+
+ private StatusMessage createMessage(Bundle data) {
+ Person author = getPerson(data.getBundle("notificationAuthor"));
+ return new StatusMessage(author, data.getLong("timestamp"), data.getString("message"));
+ }
+
+ private PendingIntent createGroupOnDismissedIntent(Context context, int notificationId, String groupId, String deepLink) {
+ Intent intent = new Intent(ACTION_DELETE_NOTIFICATION);
+ intent.putExtra("im.status.ethereum.deepLink", deepLink);
+ intent.putExtra("im.status.ethereum.groupId", groupId);
+ return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+ }
+
+ private PendingIntent createGroupOnTapIntent(Context context, int notificationId, String groupId, String deepLink) {
+ Intent intent = new Intent(ACTION_TAP_NOTIFICATION);
+ intent.putExtra("im.status.ethereum.deepLink", deepLink);
+ intent.putExtra("im.status.ethereum.groupId", groupId);
+ return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+ }
+
+ private PendingIntent createOnTapIntent(Context context, int notificationId, String deepLink) {
+ Intent intent = new Intent(ACTION_TAP_NOTIFICATION);
+ intent.putExtra("im.status.ethereum.deepLink", deepLink);
+ return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+ }
+
+ private PendingIntent createOnDismissedIntent(Context context, int notificationId, String deepLink) {
+ Intent intent = new Intent(ACTION_DELETE_NOTIFICATION);
+ intent.putExtra("im.status.ethereum.deepLink", deepLink);
+ return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+ }
+
+
+ public void addStatusMessage(Bundle bundle) {
+ String conversationId = bundle.getString("conversationId");
+ StatusMessageGroup group = this.messageGroups.get(conversationId);
+ NotificationManager notificationManager = notificationManager();
+
+ if (group == null) {
+ group = new StatusMessageGroup(conversationId);
+ }
+
+ this.messageGroups.put(conversationId, group);
+
+ group.addMessage(createMessage(bundle));
+
+ NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle("Me");
+ ArrayList messages = group.getMessages();
+ for (int i = 0; i < messages.size(); i++) {
+ StatusMessage message = messages.get(i);
+ messagingStyle.addMessage(message.getText(),
+ message.getTimestamp(),
+ message.getAuthor());
+ }
+
+ if (bundle.getString("title") != null) {
+ messagingStyle.setConversationTitle(bundle.getString("title"));
+ }
+
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_stat_notify_status)
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
+ .setCategory(NotificationCompat.CATEGORY_MESSAGE)
+ .setStyle(messagingStyle)
+ .setGroup(conversationId)
+ .setOnlyAlertOnce(true)
+ .setGroupSummary(true)
+ .setContentIntent(createGroupOnTapIntent(context, conversationId.hashCode(), conversationId, bundle.getString("deepLink")))
+ .setDeleteIntent(createGroupOnDismissedIntent(context, conversationId.hashCode(), conversationId, bundle.getString("deepLink")))
+ .setNumber(messages.size() + 1)
+ .setAutoCancel(true);
+ if (Build.VERSION.SDK_INT >= 21) {
+ builder.setVibrate(new long[0]);
+ }
+ notificationManager.notify(conversationId.hashCode(), builder.build());
+ }
+
+ class StatusMessageGroup {
+ private ArrayList messages;
+ private String id;
+
+ StatusMessageGroup(String id) {
+ this.id = id;
+ this.messages = new ArrayList();
+ }
+ public ArrayList getMessages() {
+ return messages;
+ }
+
+ public void addMessage(StatusMessage message) {
+ this.messages.add(message);
+ }
+
+ public String getId() {
+ return this.id;
+ }
+ }
+
+ class StatusMessage {
+ public Person getAuthor() {
+ return author;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ private Person author;
+ private long timestamp;
+ private String text;
+
+ StatusMessage(Person author, long timestamp, String text) {
+ this.author = author;
+ this.timestamp = timestamp;
+ this.text = text;
+ }
+ }
+
+ private void removeGroup(String groupId) {
+ this.messageGroups.remove(groupId);
+ }
+
+ public void start() {
+ Log.e(LOG_TAG, "Starting Foreground Service");
+ Intent serviceIntent = new Intent(context, ForegroundService.class);
+ context.startService(serviceIntent);
+ this.registerBroadcastReceiver();
+ }
+
+ public void stop() {
+ Log.e(LOG_TAG, "Stopping Foreground Service");
+ //NOTE: we cancel all the current notifications, because the intents can't be used anymore
+ //since the broadcast receiver will be killed as well and won't be able to handle any intent
+ notificationManager.cancelAll();
+ Intent serviceIntent = new Intent(context, ForegroundService.class);
+ context.stopService(serviceIntent);
+ context.unregisterReceiver(notificationActionReceiver);
+ }
}
diff --git a/src/status_im/core.cljs b/src/status_im/core.cljs
index a57a271db7..f3c6698317 100644
--- a/src/status_im/core.cljs
+++ b/src/status_im/core.cljs
@@ -30,8 +30,6 @@
(status/set-soft-input-mode status/adjust-resize))
(.registerComponent ^js (.-AppRegistry rn) "StatusIm" #(reagent/reactify-component views/root))
(notifications/listen-notifications)
- (when platform/android?
- (.registerHeadlessTask ^js (.-AppRegistry rn) "LocalNotifications" notifications/handle))
(snoopy/subscribe!)
(when (and js/goog.DEBUG platform/ios? DevSettings)
;;on Android this method doesn't work
diff --git a/src/status_im/notifications/local.cljs b/src/status_im/notifications/local.cljs
index 34efdaddce..cd6a80a22e 100644
--- a/src/status_im/notifications/local.cljs
+++ b/src/status_im/notifications/local.cljs
@@ -1,6 +1,5 @@
(ns status-im.notifications.local
- (:require [taoensso.timbre :as log]
- [status-im.utils.fx :as fx]
+ (:require [status-im.utils.fx :as fx]
[status-im.ethereum.decode :as decode]
["@react-native-community/push-notification-ios" :default pn-ios]
[status-im.notifications.android :as pn-android]
@@ -14,11 +13,8 @@
[re-frame.core :as re-frame]
[status-im.ui.components.react :as react]
[cljs-bean.core :as bean]
- [status-im.ui.screens.chat.components.reply :as reply]
[clojure.string :as clojure.string]
- [status-im.chat.models :as chat.models]
- [status-im.constants :as constants]
- [status-im.utils.identicon :as identicon]))
+ [status-im.chat.models :as chat.models]))
(def default-erc20-token
{:symbol :ERC20
@@ -39,21 +35,8 @@
{:notificationType "local-notification"}))})))
(defn local-push-android
- [{:keys [title message icon user-info channel-id type]
- :as notification
- :or {channel-id "status-im-notifications"}}]
- (when notification
- (pn-android/present-local-notification
- (merge {:channelId channel-id
- :title title
- :message message
- :showBadge false}
- (when user-info
- {:userInfo (bean/->js user-info)})
- (when icon
- {:largeIconUrl (:uri (react/resolve-asset-source icon))})
- (when (= type "message")
- notification)))))
+ [notification]
+ (pn-android/present-local-notification notification))
(defn handle-notification-press [{{deep-link :deepLink} :userInfo
interaction :userInteraction}]
@@ -106,65 +89,16 @@
nil)]
{:title title
:icon (get-in token [:icon :source])
+ :deepLink (:deepLink notification)
:user-info notification
:message description}))
-(defn chat-by-message
- [{:keys [chats]} {:keys [localChatId from]}]
- (if-let [chat (get chats localChatId)]
- (assoc chat :chat-id localChatId)
- (assoc (get chats from) :chat-id from)))
-
(defn show-message-pn?
[{{:keys [app-state]} :db :as cofx}
- {{:keys [chat-id]} :chat}]
- (or (= app-state "background")
- (not (chat.models/foreground-chat? cofx chat-id))))
-
-(defn create-message-notification
- ([{:keys [db] :as cofx} {{:keys [message]} :body :as notification}]
- (when-not (nil? cofx)
- (let [chat (chat-by-message db message)
- contact-id (get message :from)
- contact (get-in db [:contacts/contacts contact-id])
- notification (assoc notification
- :chat chat
- :contact-id contact-id
- :contact contact)]
- (when (show-message-pn? cofx notification)
- (create-message-notification notification)))))
- ([{{:keys [message]} :body
- {:keys [chat-type chat-id] :as chat} :chat
- {:keys [identicon]} :contact
- contact-id :contact-id}]
- (when (and chat-type chat-id)
- ;;TODO : DON'T USE SUBS IN EVENTS
- (let [contact-name @(re-frame/subscribe
- [:contacts/contact-name-by-identity contact-id])
- group-chat? (not= chat-type constants/one-to-one-chat-type)
- title (clojure.string/join
- " "
- (cond-> [contact-name]
- group-chat?
- (conj
- ;; TODO(rasom): to be translated
- "in")
-
- group-chat?
- (conj
- (str (when (contains? #{constants/public-chat-type
- constants/community-chat-type}
- chat-type)
- "#")
- (get chat :name)))))]
- {:type "message"
- :chatType (str chat-type)
- :from title
- :chatId chat-id
- :alias title
- :identicon (or identicon (identicon/identicon contact-id))
- :whisperTimestamp (get message :whisperTimestamp)
- :text (reply/get-quoted-text-with-mentions (:parsedText message))}))))
+ notification]
+ (let [chat-id (get-in notification [:body :chat :id])]
+ (or (= app-state "background")
+ (not (chat.models/foreground-chat? cofx chat-id)))))
(defn create-notification
([notification]
@@ -172,7 +106,7 @@
([cofx {:keys [bodyType] :as notification}]
(assoc
(case bodyType
- "message" (create-message-notification cofx notification)
+ "message" (when (show-message-pn? cofx notification) notification)
"transaction" (create-transfer-notification notification)
nil)
:body-type bodyType)))
@@ -194,16 +128,3 @@
(if platform/ios?
{::local-push-ios evt}
(local-notification-android cofx evt)))
-
-(defn handle []
- (fn [^js message]
- (let [evt (types/json->clj (.-event message))]
- (js/Promise.
- (fn [on-success on-error]
- (try
- (when (= "local-notifications" (:type evt))
- (re-frame/dispatch [::local-notification-android (:event evt)]))
- (on-success)
- (catch :default e
- (log/warn "failed to handle background notification" e)
- (on-error e))))))))
diff --git a/src/status_im/router/core.cljs b/src/status_im/router/core.cljs
index 3d257a4392..9e8aab1ae6 100644
--- a/src/status_im/router/core.cljs
+++ b/src/status_im/router/core.cljs
@@ -43,6 +43,7 @@
"b/" browser-extractor
"browser/" browser-extractor
["p/" :chat-id] :private-chat
+ ["cr/" :community-id] :community-requests
"g/" group-chat-extractor
["wallet/" :account] :wallet-account
["u/" :user-id] :user
@@ -201,6 +202,9 @@
(spec/valid? :global/public-key uri)
(match-contact-async chain {:user-id uri} cb)
+ (= handler :community-requests)
+ (cb {:type handler :community-id (:community-id route-params)})
+
(= handler :referrals)
(cb (match-referral route-params))
diff --git a/src/status_im/utils/universal_links/core.cljs b/src/status_im/utils/universal_links/core.cljs
index 9dde2a6285..c652209b1a 100644
--- a/src/status_im/utils/universal_links/core.cljs
+++ b/src/status_im/utils/universal_links/core.cljs
@@ -24,11 +24,12 @@
(def domains {:external "https://join.status.im"
:internal "status-im:/"})
-(def links {:public-chat "%s/%s"
- :private-chat "%s/p/%s"
- :group-chat "%s/g/%s"
- :user "%s/u/%s"
- :browse "%s/b/%s"})
+(def links {:public-chat "%s/%s"
+ :private-chat "%s/p/%s"
+ :community-requests "%s/cr/%s"
+ :group-chat "%s/g/%s"
+ :user "%s/u/%s"
+ :browse "%s/b/%s"})
(defn generate-link [link-type domain-type param]
(gstring/format (get links link-type)
@@ -59,6 +60,10 @@
{:utils/show-popup {:title (i18n/label :t/unable-to-read-this-code)
:content (i18n/label :t/can-not-add-yourself)}})))
+(fx/defn handle-community-requests [cofx {:keys [community-id]}]
+ (log/info "universal-links: handling community request " community-id)
+ (navigation/navigate-to-cofx cofx :community-requests-to-join {:community-id community-id}))
+
(fx/defn handle-public-chat [cofx {:keys [topic]}]
(log/info "universal-links: handling public chat" topic)
(when (seq topic)
@@ -113,20 +118,22 @@
{:events [::match-value]}
[cofx url {:keys [type] :as data}]
(case type
- :group-chat (handle-group-chat cofx data)
- :public-chat (handle-public-chat cofx data)
- :private-chat (handle-private-chat cofx data)
- :contact (handle-view-profile cofx data)
- :browser (handle-browse cofx data)
- :eip681 (handle-eip681 cofx data)
- :referrals (handle-referrer-url cofx data)
- :wallet-account (handle-wallet-account cofx data)
+ :group-chat (handle-group-chat cofx data)
+ :public-chat (handle-public-chat cofx data)
+ :private-chat (handle-private-chat cofx data)
+ :community-requests (handle-community-requests cofx data)
+ :contact (handle-view-profile cofx data)
+ :browser (handle-browse cofx data)
+ :eip681 (handle-eip681 cofx data)
+ :referrals (handle-referrer-url cofx data)
+ :wallet-account (handle-wallet-account cofx data)
(handle-not-found url)))
(fx/defn route-url
"Match a url against a list of routes and handle accordingly"
[{:keys [db]} url]
{::router/handle-uri {:chain (ethereum/chain-keyword db)
+ :chats (:chats db)
:uri url
:cb #(re-frame/dispatch [::match-value url %])}})
diff --git a/status-go-version.json b/status-go-version.json
index 18a8b9e41d..ce8b4da197 100644
--- a/status-go-version.json
+++ b/status-go-version.json
@@ -2,7 +2,7 @@
"_comment": "DO NOT EDIT THIS FILE BY HAND. USE 'scripts/update-status-go.sh ' instead",
"owner": "status-im",
"repo": "status-go",
- "version": "v0.74.1",
- "commit-sha1": "5a76e93063e3b9ef2ded83a5f545f7149401e8f0",
- "src-sha256": "0581048fvqg0fxv03pm9a0drfiq32slxliwamnch3052avpdkycj"
+ "version": "v0.74.2",
+ "commit-sha1": "1724ecffa178e9bcb04ae6d9d95d276c02878b6f",
+ "src-sha256": "1kn6996brpva1bdj8bv1mscwx17hynqk8xb667ai6qpdriscr22d"
}
diff --git a/test/appium/tests/atomic/account_management/test_profile.py b/test/appium/tests/atomic/account_management/test_profile.py
index b337af15aa..9518f402a4 100644
--- a/test/appium/tests/atomic/account_management/test_profile.py
+++ b/test/appium/tests/atomic/account_management/test_profile.py
@@ -1123,7 +1123,7 @@ class TestProfileMultipleDevice(MultipleDeviceTestCase):
home_1.just_fyi('check that PN is received and after tap you are redirected to public chat')
home_1.open_notification_bar()
- home_1.element_by_text_part('%s in #%s' % (username_2, chat_name)).click()
+ home_1.element_by_text_part(username_2).click()
chat_1.element_starts_with_text(user_1['ens'] +'.stateofus.eth','button').click()
if not profile_1.contacts_button.is_element_displayed():
self.errors.append('Was not redirected to own profile after tapping on mention of myself from another user!')
diff --git a/test/appium/tests/atomic/chats/test_group_chat.py b/test/appium/tests/atomic/chats/test_group_chat.py
index 5154671ed9..cb688d747c 100644
--- a/test/appium/tests/atomic/chats/test_group_chat.py
+++ b/test/appium/tests/atomic/chats/test_group_chat.py
@@ -57,6 +57,7 @@ class TestGroupChatMultipleDevice(MultipleDeviceTestCase):
for chat in (device_1_chat, device_2_chat):
if not chat.chat_element_by_text(join_system_message).is_element_displayed():
self.errors.append('System message after joining group chat is not shown')
+ device_2_chat.home_button.click(desired_view="home")
message_1 = "Message from device: %s" % device_1_chat.driver.number
device_1_chat.send_message(message_1)
if device_1_chat.chat_element_by_text(message_1).status != 'delivered':
diff --git a/test/appium/tests/atomic/chats/test_one_to_one.py b/test/appium/tests/atomic/chats/test_one_to_one.py
index 46665e6876..265a012ad2 100644
--- a/test/appium/tests/atomic/chats/test_one_to_one.py
+++ b/test/appium/tests/atomic/chats/test_one_to_one.py
@@ -360,7 +360,7 @@ class TestMessagesOneToOneChatMultiple(MultipleDeviceTestCase):
chat_1.send_message_button.click()
device_2.open_notification_bar()
- chat_2 = home_2.click_upon_push_notification_by_text("audio message")
+ chat_2 = home_2.click_upon_push_notification_by_text("Audio")
listen_time = 5