[#11117] Move PNs to status-go
This commit is contained in:
parent
9a51a46869
commit
3685f6a500
|
@ -70,7 +70,7 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>
|
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>
|
||||||
<service android:name="im.status.ethereum.module.ForegroundService"></service>
|
<service android:name="im.status.ethereum.pushnotifications.ForegroundService"></service>
|
||||||
<service android:name="im.status.ethereum.module.LocalNotificationsService" />
|
<service android:name="im.status.ethereum.module.LocalNotificationsService" />
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
|
|
@ -80,7 +80,6 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||||
private static StatusModule module;
|
private static StatusModule module;
|
||||||
private ReactApplicationContext reactContext;
|
private ReactApplicationContext reactContext;
|
||||||
private boolean rootedDevice;
|
private boolean rootedDevice;
|
||||||
private NewMessageSignalHandler newMessageSignalHandler;
|
|
||||||
private boolean background;
|
private boolean background;
|
||||||
|
|
||||||
StatusModule(ReactApplicationContext reactContext, boolean rootedDevice) {
|
StatusModule(ReactApplicationContext reactContext, boolean rootedDevice) {
|
||||||
|
@ -112,19 +111,6 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||||
Log.d(TAG, "******************* ON HOST DESTROY *************************");
|
Log.d(TAG, "******************* ON HOST DESTROY *************************");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void enableNotifications() {
|
|
||||||
this.newMessageSignalHandler = new NewMessageSignalHandler(reactContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactMethod
|
|
||||||
public void disableNotifications() {
|
|
||||||
if (newMessageSignalHandler != null) {
|
|
||||||
newMessageSignalHandler.stop();
|
|
||||||
newMessageSignalHandler = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean checkAvailability() {
|
private boolean checkAvailability() {
|
||||||
// We wait at least 10s for getCurrentActivity to return a value,
|
// We wait at least 10s for getCurrentActivity to return a value,
|
||||||
// otherwise we give up
|
// otherwise we give up
|
||||||
|
@ -153,35 +139,10 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleSignal(final String jsonEventString) {
|
public void handleSignal(final String jsonEventString) {
|
||||||
try {
|
Log.d(TAG, "Signal event");
|
||||||
final JSONObject jsonEvent = new JSONObject(jsonEventString);
|
WritableMap params = Arguments.createMap();
|
||||||
String eventType = jsonEvent.getString("type");
|
params.putString("jsonEvent", jsonEventString);
|
||||||
Log.d(TAG, "Signal event: " + jsonEventString);
|
this.getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("gethEvent", params);
|
||||||
// NOTE: the newMessageSignalHandler is only instanciated if the user
|
|
||||||
// enabled notifications in the app
|
|
||||||
if (this.background && newMessageSignalHandler != null) {
|
|
||||||
if (eventType.equals("messages.new")) {
|
|
||||||
newMessageSignalHandler.handleNewMessageSignal(jsonEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(eventType.equals("local-notifications")) {
|
|
||||||
Context ctx = this.getReactApplicationContext();
|
|
||||||
Intent intent = new Intent(ctx, LocalNotificationsService.class);
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
|
|
||||||
bundle.putString("event", jsonEventString);
|
|
||||||
intent.putExtras(bundle);
|
|
||||||
|
|
||||||
ctx.startService(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
WritableMap params = Arguments.createMap();
|
|
||||||
params.putString("jsonEvent", jsonEventString);
|
|
||||||
this.getReactApplicationContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("gethEvent", params);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
Log.e(TAG, "JSON conversion failed: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private File getLogsFile() {
|
private File getLogsFile() {
|
||||||
|
@ -442,7 +403,6 @@ class StatusModule extends ReactContextBaseJavaModule implements LifecycleEventL
|
||||||
@ReactMethod
|
@ReactMethod
|
||||||
public void logout() {
|
public void logout() {
|
||||||
Log.d(TAG, "logout");
|
Log.d(TAG, "logout");
|
||||||
disableNotifications();
|
|
||||||
String result = Statusgo.logout();
|
String result = Statusgo.logout();
|
||||||
if (result.startsWith("{\"error\":\"\"")) {
|
if (result.startsWith("{\"error\":\"\"")) {
|
||||||
Log.d(TAG, "Logout result: " + result);
|
Log.d(TAG, "Logout result: " + result);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package im.status.ethereum.module;
|
package im.status.ethereum.pushnotifications;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
@ -13,6 +13,7 @@ import androidx.core.app.NotificationCompat;
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import im.status.ethereum.module.R;
|
||||||
|
|
||||||
public class ForegroundService extends Service {
|
public class ForegroundService extends Service {
|
||||||
private static final String CHANNEL_ID = "status-service";
|
private static final String CHANNEL_ID = "status-service";
|
|
@ -1,4 +1,4 @@
|
||||||
package im.status.ethereum.module;
|
package im.status.ethereum.pushnotifications;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
|
@ -33,10 +33,12 @@ import androidx.core.app.NotificationManagerCompat;
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.media.AudioAttributes;
|
import android.media.AudioAttributes;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import im.status.ethereum.module.R;
|
||||||
|
|
||||||
public class NewMessageSignalHandler {
|
public class NewMessageSignalHandler {
|
||||||
//NOTE: currently we only show notifications for 1-1 chats, in the future we
|
//NOTE: currently we only show notifications for 1-1 chats, in the future we
|
||||||
|
@ -57,6 +59,8 @@ public class NewMessageSignalHandler {
|
||||||
private Context context;
|
private Context context;
|
||||||
private Intent serviceIntent;
|
private Intent serviceIntent;
|
||||||
private Boolean shouldRefreshNotifications;
|
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
|
//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
|
//intents from notifications and act on them. For instance when tapping/dismissing
|
||||||
|
@ -68,8 +72,9 @@ public class NewMessageSignalHandler {
|
||||||
if (intent.getAction() == ACTION_TAP_NOTIFICATION ||
|
if (intent.getAction() == ACTION_TAP_NOTIFICATION ||
|
||||||
intent.getAction() == ACTION_DELETE_NOTIFICATION) {
|
intent.getAction() == ACTION_DELETE_NOTIFICATION) {
|
||||||
String chatId = intent.getExtras().getString("im.status.ethereum.chatId");
|
String chatId = intent.getExtras().getString("im.status.ethereum.chatId");
|
||||||
|
int chatType = intent.getExtras().getInt("im.status.ethereum.chatType");
|
||||||
if (intent.getAction() == ACTION_TAP_NOTIFICATION) {
|
if (intent.getAction() == ACTION_TAP_NOTIFICATION) {
|
||||||
context.startActivity(getOpenAppIntent(chatId));
|
context.startActivity(getOpenAppIntent(chatId, chatType));
|
||||||
}
|
}
|
||||||
removeChat(chatId);
|
removeChat(chatId);
|
||||||
// clean up the group notifications when there is no
|
// clean up the group notifications when there is no
|
||||||
|
@ -122,9 +127,15 @@ public class NewMessageSignalHandler {
|
||||||
//NOTE: this method takes a chatId and returns an intent that will open the app in that chat
|
//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
|
//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.
|
//is probably to pass the universal link as param instead of the chatId.
|
||||||
public Intent getOpenAppIntent(String chatId) {
|
public Intent getOpenAppIntent(String chatId, int chatType) {
|
||||||
Intent intent = getOpenAppIntent();
|
Intent intent = getOpenAppIntent();
|
||||||
intent.setData(Uri.parse("status-im://p/" + chatId));
|
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;
|
return intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,15 +186,17 @@ public class NewMessageSignalHandler {
|
||||||
this.chats.remove(chatId);
|
this.chats.remove(chatId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PendingIntent createOnDismissedIntent(Context context, int notificationId, String chatId) {
|
private PendingIntent createOnDismissedIntent(Context context, int notificationId, String chatId, int chatType) {
|
||||||
Intent intent = new Intent(ACTION_DELETE_NOTIFICATION);
|
Intent intent = new Intent(ACTION_DELETE_NOTIFICATION);
|
||||||
intent.putExtra("im.status.ethereum.chatId", chatId);
|
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);
|
return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, intent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PendingIntent createOnTapIntent(Context context, int notificationId, String chatId) {
|
private PendingIntent createOnTapIntent(Context context, int notificationId, String chatId, int chatType) {
|
||||||
Intent intent = new Intent(ACTION_TAP_NOTIFICATION);
|
Intent intent = new Intent(ACTION_TAP_NOTIFICATION);
|
||||||
intent.putExtra("im.status.ethereum.chatId", chatId);
|
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);
|
return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, intent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,8 +216,8 @@ public class NewMessageSignalHandler {
|
||||||
.setStyle(messagingStyle)
|
.setStyle(messagingStyle)
|
||||||
.setGroup(GROUP_STATUS_MESSAGES)
|
.setGroup(GROUP_STATUS_MESSAGES)
|
||||||
.setGroupSummary(true)
|
.setGroupSummary(true)
|
||||||
.setContentIntent(createOnTapIntent(context, notificationId, chat.getId()))
|
.setContentIntent(createOnTapIntent(context, notificationId, chat.getId(), chat.getType()))
|
||||||
.setDeleteIntent(createOnDismissedIntent(context, notificationId, chat.getId()))
|
.setDeleteIntent(createOnDismissedIntent(context, notificationId, chat.getId(), chat.getType()))
|
||||||
.setNumber(messages.size())
|
.setNumber(messages.size())
|
||||||
.setAutoCancel(true);
|
.setAutoCancel(true);
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
if (Build.VERSION.SDK_INT >= 21) {
|
||||||
|
@ -224,31 +237,13 @@ public class NewMessageSignalHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleNewMessageSignal(JSONObject newMessageSignal) {
|
void handleNewMessage (Bundle data) {
|
||||||
try {
|
upsertChat(data);
|
||||||
JSONArray chatsNewMessagesData = newMessageSignal.getJSONObject("event").getJSONArray("chats");
|
upsertMessage(data);
|
||||||
for (int i = 0; i < chatsNewMessagesData.length(); i++) {
|
|
||||||
try {
|
|
||||||
upsertChat(chatsNewMessagesData.getJSONObject(i));
|
|
||||||
} catch (JSONException e) {
|
|
||||||
Log.e(TAG, "JSON conversion failed: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
JSONArray messagesNewMessagesData = newMessageSignal.getJSONObject("event").getJSONArray("messages");
|
|
||||||
for (int i = 0; i < messagesNewMessagesData.length(); i++) {
|
|
||||||
try {
|
|
||||||
upsertMessage(messagesNewMessagesData.getJSONObject(i));
|
|
||||||
} catch (JSONException e) {
|
|
||||||
Log.e(TAG, "JSON conversion failed: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(shouldRefreshNotifications) {
|
if(shouldRefreshNotifications) {
|
||||||
refreshNotifications();
|
refreshNotifications();
|
||||||
shouldRefreshNotifications = false;
|
shouldRefreshNotifications = false;
|
||||||
}
|
|
||||||
} catch (JSONException e) {
|
|
||||||
Log.e(TAG, "JSON conversion failed: " + e.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,69 +263,37 @@ public class NewMessageSignalHandler {
|
||||||
return person;
|
return person;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void upsertChat(JSONObject chatData) {
|
private void upsertChat(Bundle data) {
|
||||||
try {
|
String id = data.getString("chatId");
|
||||||
// NOTE: this is an exemple of chatData
|
int type = Integer.parseInt(data.getString("chatType"));
|
||||||
// {"chatId":"contact-discovery-3622","filterId":"c0239d63f830e8b25f4bf7183c8d207f355a925b89514a17068cae4898e7f193",
|
StatusChat chat = chats.get(id);
|
||||||
// "symKeyId":"","oneToOne":true,"identity":"046599511623d7385b926ce709ac57d518dac10d451a81f75cd32c7fb4b1c...",
|
|
||||||
// "topic":"0xc446561b","discovery":false,"negotiated":false,"listen":true}
|
|
||||||
int oneToOne = chatData.getInt("chatType");
|
|
||||||
// NOTE: for now we only notify one to one chats
|
|
||||||
// TODO: also notifiy on mentions, keywords and group chats
|
|
||||||
// TODO: one would have to pass the ens name and keywords to notify on when instanciating the class as well
|
|
||||||
// as have a method to add new ones after the handler is instanciated
|
|
||||||
if (oneToOne == 1) {
|
|
||||||
//JSONArray messagesData = chatNewMessagesData.getJSONArray("messages");
|
|
||||||
|
|
||||||
// there is no proper id for oneToOne chat in chatData so we peek into first message sig
|
// if the chat was not already there, we create one
|
||||||
// TODO: won't work for sync becaus it could be our own message
|
if (chat == null) {
|
||||||
String id = chatData.getString("id");
|
chat = new StatusChat(id, type);
|
||||||
StatusChat chat = chats.get(id);
|
}
|
||||||
|
|
||||||
|
chats.put(id, chat);
|
||||||
|
}
|
||||||
|
|
||||||
// if the chat was not already there, we create one
|
private void upsertMessage(Bundle data) {
|
||||||
if (chat == null) {
|
String chatId = data.getString("chatId");
|
||||||
chat = new StatusChat(id, true);
|
StatusChat chat = chats.get(chatId);
|
||||||
}
|
if (chat == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
chats.put(id, chat);
|
StatusMessage message = createMessage(data);
|
||||||
}
|
if (message != null) {
|
||||||
} catch (JSONException e) {
|
chat.appendMessage(message);
|
||||||
Log.e(TAG, "JSON conversion failed: " + e.getMessage());
|
chats.put(chatId, chat);
|
||||||
|
shouldRefreshNotifications = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private StatusMessage createMessage(Bundle data) {
|
||||||
|
Person author = getPerson(data.getString("from"), data.getString("identicon"), data.getString("alias"));
|
||||||
private void upsertMessage(JSONObject messageData) {
|
return new StatusMessage(author, data.getLong("whisperTimestamp"), data.getString("text"));
|
||||||
try {
|
|
||||||
String chatId = messageData.getString("localChatId");
|
|
||||||
StatusChat chat = chats.get(chatId);
|
|
||||||
if (chat == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
StatusMessage message = createMessage(messageData);
|
|
||||||
if (message != null) {
|
|
||||||
chat.appendMessage(message);
|
|
||||||
chats.put(chatId, chat);
|
|
||||||
shouldRefreshNotifications = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (JSONException e) {
|
|
||||||
Log.e(TAG, "JSON conversion failed: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private StatusMessage createMessage(JSONObject messageData) {
|
|
||||||
try {
|
|
||||||
Person author = getPerson(messageData.getString("from"), messageData.getString("identicon"), messageData.getString("alias"));
|
|
||||||
return new StatusMessage(author, messageData.getLong("whisperTimestamp"), messageData.getString("text"));
|
|
||||||
} catch (JSONException e) {
|
|
||||||
Log.e(TAG, "JSON conversion failed: " + e.getMessage());
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,11 +301,11 @@ class StatusChat {
|
||||||
private ArrayList<StatusMessage> messages;
|
private ArrayList<StatusMessage> messages;
|
||||||
private String id;
|
private String id;
|
||||||
private String name;
|
private String name;
|
||||||
private Boolean oneToOne;
|
private int type;
|
||||||
|
|
||||||
StatusChat(String id, Boolean oneToOne) {
|
StatusChat(String id, int type) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.oneToOne = oneToOne;
|
this.type = type;
|
||||||
this.messages = new ArrayList<StatusMessage>();
|
this.messages = new ArrayList<StatusMessage>();
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
@ -351,6 +314,10 @@ class StatusChat {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getType() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
|
|
||||||
//TODO this should be improved as it would rename the chat
|
//TODO this should be improved as it would rename the chat
|
|
@ -26,6 +26,7 @@ import com.facebook.react.bridge.ReadableArray;
|
||||||
import com.facebook.react.bridge.WritableArray;
|
import com.facebook.react.bridge.WritableArray;
|
||||||
import com.facebook.react.bridge.ReadableMap;
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
import com.facebook.react.bridge.WritableMap;
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import im.status.ethereum.pushnotifications.PushNotificationJsDelivery;
|
import im.status.ethereum.pushnotifications.PushNotificationJsDelivery;
|
||||||
|
|
||||||
|
@ -35,9 +36,12 @@ public class PushNotification extends ReactContextBaseJavaModule implements Acti
|
||||||
private final SecureRandom mRandomNumberGenerator = new SecureRandom();
|
private final SecureRandom mRandomNumberGenerator = new SecureRandom();
|
||||||
private PushNotificationHelper pushNotificationHelper;
|
private PushNotificationHelper pushNotificationHelper;
|
||||||
private PushNotificationJsDelivery delivery;
|
private PushNotificationJsDelivery delivery;
|
||||||
|
private ReactApplicationContext reactContext;
|
||||||
|
private NewMessageSignalHandler newMessageSignalHandler;
|
||||||
|
|
||||||
public PushNotification(ReactApplicationContext reactContext) {
|
public PushNotification(ReactApplicationContext reactContext) {
|
||||||
super(reactContext);
|
super(reactContext);
|
||||||
|
this.reactContext = reactContext;
|
||||||
reactContext.addActivityEventListener(this);
|
reactContext.addActivityEventListener(this);
|
||||||
Application applicationContext = (Application) reactContext.getApplicationContext();
|
Application applicationContext = (Application) reactContext.getApplicationContext();
|
||||||
|
|
||||||
|
@ -105,6 +109,28 @@ public class PushNotification extends ReactContextBaseJavaModule implements Acti
|
||||||
if (bundle.getString("id") == null) {
|
if (bundle.getString("id") == null) {
|
||||||
bundle.putString("id", String.valueOf(mRandomNumberGenerator.nextInt()));
|
bundle.putString("id", String.valueOf(mRandomNumberGenerator.nextInt()));
|
||||||
}
|
}
|
||||||
pushNotificationHelper.sendToNotificationCentre(bundle);
|
|
||||||
|
String type = bundle.getString("type");
|
||||||
|
if (type != null && type.equals("message")) {
|
||||||
|
if (this.newMessageSignalHandler != null) {
|
||||||
|
newMessageSignalHandler.handleNewMessage(bundle);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pushNotificationHelper.sendToNotificationCentre(bundle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void enableNotifications() {
|
||||||
|
this.newMessageSignalHandler = new NewMessageSignalHandler(reactContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void disableNotifications() {
|
||||||
|
if (newMessageSignalHandler != null) {
|
||||||
|
newMessageSignalHandler.stop();
|
||||||
|
newMessageSignalHandler = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,11 @@
|
||||||
([cofx chat-id]
|
([cofx chat-id]
|
||||||
(active-chat? (get-chat cofx chat-id))))
|
(active-chat? (get-chat cofx chat-id))))
|
||||||
|
|
||||||
|
(defn foreground-chat?
|
||||||
|
[{{:keys [current-chat-id view-id]} :db} chat-id]
|
||||||
|
(and (= current-chat-id chat-id)
|
||||||
|
(= view-id :chat)))
|
||||||
|
|
||||||
(defn group-chat?
|
(defn group-chat?
|
||||||
([chat]
|
([chat]
|
||||||
(and (multi-user-chat? chat)
|
(and (multi-user-chat? chat)
|
||||||
|
|
|
@ -35,14 +35,6 @@
|
||||||
config
|
config
|
||||||
#(callback (types/json->clj %))))
|
#(callback (types/json->clj %))))
|
||||||
|
|
||||||
(defn enable-notifications []
|
|
||||||
(log/debug "[native-module] enable-notifications")
|
|
||||||
(.enableNotifications ^js (status)))
|
|
||||||
|
|
||||||
(defn disable-notifications []
|
|
||||||
(log/debug "[native-module] disable-notifications")
|
|
||||||
(.disableNotifications ^js (status)))
|
|
||||||
|
|
||||||
(defn save-account-and-login
|
(defn save-account-and-login
|
||||||
"NOTE: beware, the password has to be sha3 hashed"
|
"NOTE: beware, the password has to be sha3 hashed"
|
||||||
[key-uid multiaccount-data hashed-password settings config accounts-data]
|
[key-uid multiaccount-data hashed-password settings config accounts-data]
|
||||||
|
|
|
@ -15,3 +15,9 @@
|
||||||
#js {:channelId channel-id
|
#js {:channelId channel-id
|
||||||
:channelName channel-name}
|
:channelName channel-name}
|
||||||
#(log/info "Notifications create channel:" %)))
|
#(log/info "Notifications create channel:" %)))
|
||||||
|
|
||||||
|
(defn enable-notifications []
|
||||||
|
(.enableNotifications ^js (pn-android)))
|
||||||
|
|
||||||
|
(defn disable-notifications []
|
||||||
|
(.disableNotifications ^js (pn-android)))
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
[status-im.multiaccounts.update.core :as multiaccounts.update]
|
[status-im.multiaccounts.update.core :as multiaccounts.update]
|
||||||
["@react-native-community/push-notification-ios" :default pn-ios]
|
["@react-native-community/push-notification-ios" :default pn-ios]
|
||||||
[status-im.notifications.android :as pn-android]
|
[status-im.notifications.android :as pn-android]
|
||||||
[status-im.native-module.core :as status]
|
|
||||||
[status-im.notifications.local :as local]
|
[status-im.notifications.local :as local]
|
||||||
[quo.platform :as platform]
|
[quo.platform :as platform]
|
||||||
[status-im.utils.config :as config]
|
[status-im.utils.config :as config]
|
||||||
|
@ -70,21 +69,21 @@
|
||||||
(do
|
(do
|
||||||
(pn-android/create-channel {:channel-id "status-im-notifications"
|
(pn-android/create-channel {:channel-id "status-im-notifications"
|
||||||
:channel-name "Status push notifications"})
|
:channel-name "Status push notifications"})
|
||||||
(status/enable-notifications))
|
(pn-android/enable-notifications))
|
||||||
(enable-ios-notifications))))
|
(enable-ios-notifications))))
|
||||||
|
|
||||||
(re-frame/reg-fx
|
(re-frame/reg-fx
|
||||||
::disable
|
::disable
|
||||||
(fn [_]
|
(fn [_]
|
||||||
(if platform/android?
|
(if platform/android?
|
||||||
(status/disable-notifications)
|
(pn-android/disable-notifications)
|
||||||
(disable-ios-notifications))))
|
(disable-ios-notifications))))
|
||||||
|
|
||||||
(re-frame/reg-fx
|
(re-frame/reg-fx
|
||||||
::logout-disable
|
::logout-disable
|
||||||
(fn [_]
|
(fn [_]
|
||||||
(if platform/android?
|
(if platform/android?
|
||||||
(status/disable-notifications)
|
(pn-android/disable-notifications)
|
||||||
(.abandonPermissions ^js pn-ios))))
|
(.abandonPermissions ^js pn-ios))))
|
||||||
|
|
||||||
(fx/defn handle-enable-notifications-event
|
(fx/defn handle-enable-notifications-event
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
(ns status-im.notifications.local
|
(ns status-im.notifications.local
|
||||||
(:require [taoensso.timbre :as log]
|
(:require [taoensso.timbre :as log]
|
||||||
[clojure.string :as cstr]
|
|
||||||
[status-im.utils.fx :as fx]
|
[status-im.utils.fx :as fx]
|
||||||
[status-im.ethereum.decode :as decode]
|
[status-im.ethereum.decode :as decode]
|
||||||
["@react-native-community/push-notification-ios" :default pn-ios]
|
["@react-native-community/push-notification-ios" :default pn-ios]
|
||||||
|
@ -14,7 +13,11 @@
|
||||||
[quo.platform :as platform]
|
[quo.platform :as platform]
|
||||||
[re-frame.core :as re-frame]
|
[re-frame.core :as re-frame]
|
||||||
[status-im.ui.components.react :as react]
|
[status-im.ui.components.react :as react]
|
||||||
[cljs-bean.core :as bean]))
|
[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]))
|
||||||
|
|
||||||
(def default-erc20-token
|
(def default-erc20-token
|
||||||
{:symbol :ERC20
|
{:symbol :ERC20
|
||||||
|
@ -24,23 +27,31 @@
|
||||||
(def notification-event-ios "localNotification")
|
(def notification-event-ios "localNotification")
|
||||||
(def notification-event-android "remoteNotificationReceived")
|
(def notification-event-android "remoteNotificationReceived")
|
||||||
|
|
||||||
(defn local-push-ios [{:keys [title message user-info]}]
|
(defn local-push-ios [{:keys [title message user-info body-type]}]
|
||||||
(.presentLocalNotification pn-ios #js {:alertBody message
|
(when (not= body-type "message")
|
||||||
:alertTitle title
|
(.presentLocalNotification
|
||||||
;; NOTE: Use a special type to hide in Obj-C code other notifications
|
pn-ios
|
||||||
:userInfo (bean/->js (merge user-info
|
#js {:alertBody message
|
||||||
{:notificationType "local-notification"}))}))
|
:alertTitle title
|
||||||
|
;; NOTE: Use a special type to hide in Obj-C code other notifications
|
||||||
|
:userInfo (bean/->js (merge user-info
|
||||||
|
{:notificationType "local-notification"}))})))
|
||||||
|
|
||||||
(defn local-push-android [{:keys [title message icon user-info channel-id]
|
(defn local-push-android
|
||||||
:or {channel-id "status-im-notifications"}}]
|
[{:keys [title message icon user-info channel-id type]
|
||||||
(pn-android/present-local-notification (merge {:channelId channel-id
|
:as notification
|
||||||
:title title
|
:or {channel-id "status-im-notifications"}}]
|
||||||
:message message
|
(pn-android/present-local-notification
|
||||||
:showBadge false}
|
(merge {:channelId channel-id
|
||||||
(when user-info
|
:title title
|
||||||
{:userInfo (bean/->js user-info)})
|
:message message
|
||||||
(when icon
|
:showBadge false}
|
||||||
{:largeIconUrl (:uri (react/resolve-asset-source icon))}))))
|
(when user-info
|
||||||
|
{:userInfo (bean/->js user-info)})
|
||||||
|
(when icon
|
||||||
|
{:largeIconUrl (:uri (react/resolve-asset-source icon))})
|
||||||
|
(when (= type "message")
|
||||||
|
notification))))
|
||||||
|
|
||||||
(defn handle-notification-press [{{deep-link :deepLink} :userInfo
|
(defn handle-notification-press [{{deep-link :deepLink} :userInfo
|
||||||
interaction :userInteraction}]
|
interaction :userInteraction}]
|
||||||
|
@ -61,13 +72,14 @@
|
||||||
(when (and data (.-dataJSON data))
|
(when (and data (.-dataJSON data))
|
||||||
(handle-notification-press (types/json->clj (.-dataJSON data))))))))
|
(handle-notification-press (types/json->clj (.-dataJSON data))))))))
|
||||||
|
|
||||||
(defn create-notification [{{:keys [state from to fromAccount toAccount value erc20 contract network]}
|
(defn create-transfer-notification
|
||||||
:body
|
[{{:keys [state from to fromAccount toAccount value erc20 contract network]}
|
||||||
:as notification}]
|
:body
|
||||||
|
:as notification}]
|
||||||
(let [chain (ethereum/chain-id->chain-keyword network)
|
(let [chain (ethereum/chain-id->chain-keyword network)
|
||||||
token (if erc20
|
token (if erc20
|
||||||
(get-in tokens/all-tokens-normalized [(keyword chain)
|
(get-in tokens/all-tokens-normalized
|
||||||
(cstr/lower-case contract)]
|
[(keyword chain) (clojure.string/lower-case contract)]
|
||||||
default-erc20-token)
|
default-erc20-token)
|
||||||
(tokens/native-currency (keyword chain)))
|
(tokens/native-currency (keyword chain)))
|
||||||
amount (money/wei->ether (decode/uint value))
|
amount (money/wei->ether (decode/uint value))
|
||||||
|
@ -95,15 +107,83 @@
|
||||||
:user-info notification
|
:user-info notification
|
||||||
:message description}))
|
:message description}))
|
||||||
|
|
||||||
|
(defn show-message-pn?
|
||||||
|
[{{:keys [app-state multiaccount]} :db :as cofx}
|
||||||
|
{{:keys [message chat]} :body}]
|
||||||
|
(let [chat-id (get chat :id)
|
||||||
|
chat-type (get chat :chatType)]
|
||||||
|
(and
|
||||||
|
(or (= app-state "background")
|
||||||
|
(not (chat.models/foreground-chat? cofx chat-id)))
|
||||||
|
(or (contains? #{constants/one-to-one-chat-type
|
||||||
|
constants/private-group-chat-type}
|
||||||
|
chat-type)
|
||||||
|
(contains? (set (get message :mentions))
|
||||||
|
(get multiaccount :public-key))))))
|
||||||
|
|
||||||
|
(defn create-message-notification
|
||||||
|
([cofx notification]
|
||||||
|
(when (or (nil? cofx)
|
||||||
|
(show-message-pn? cofx notification))
|
||||||
|
(create-message-notification notification)))
|
||||||
|
([{{:keys [message contact chat]} :body}]
|
||||||
|
(let [chat-type (get chat :chatType)
|
||||||
|
chat-id (get chat :id)
|
||||||
|
contact-name @(re-frame/subscribe
|
||||||
|
[:contacts/contact-name-by-identity (get 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 (get chat :chatType))
|
||||||
|
:from title
|
||||||
|
:chatId chat-id
|
||||||
|
:alias title
|
||||||
|
:identicon (get contact :identicon)
|
||||||
|
:whisperTimestamp (get message :whisperTimestamp)
|
||||||
|
:text (reply/get-quoted-text-with-mentions (:parsedText message))})))
|
||||||
|
|
||||||
|
(defn create-notification
|
||||||
|
([notification]
|
||||||
|
(create-notification nil notification))
|
||||||
|
([cofx {:keys [bodyType] :as notification}]
|
||||||
|
(assoc
|
||||||
|
(case bodyType
|
||||||
|
"message" (create-message-notification cofx notification)
|
||||||
|
"transaction" (create-transfer-notification notification)
|
||||||
|
nil)
|
||||||
|
:body-type bodyType)))
|
||||||
|
|
||||||
(re-frame/reg-fx
|
(re-frame/reg-fx
|
||||||
::local-push-ios
|
::local-push-ios
|
||||||
(fn [evt]
|
(fn [evt]
|
||||||
(-> evt create-notification local-push-ios)))
|
(-> evt create-notification local-push-ios)))
|
||||||
|
|
||||||
|
(fx/defn local-notification-android
|
||||||
|
{:events [::local-notification-android]}
|
||||||
|
[cofx event]
|
||||||
|
(some->> event
|
||||||
|
(create-notification cofx)
|
||||||
|
local-push-android))
|
||||||
|
|
||||||
(fx/defn process
|
(fx/defn process
|
||||||
[_ evt]
|
[cofx evt]
|
||||||
(when platform/ios?
|
(if platform/ios?
|
||||||
{::local-push-ios evt}))
|
{::local-push-ios evt}
|
||||||
|
(local-notification-android cofx evt)))
|
||||||
|
|
||||||
(defn handle []
|
(defn handle []
|
||||||
(fn [^js message]
|
(fn [^js message]
|
||||||
|
@ -112,7 +192,7 @@
|
||||||
(fn [on-success on-error]
|
(fn [on-success on-error]
|
||||||
(try
|
(try
|
||||||
(when (= "local-notifications" (:type evt))
|
(when (= "local-notifications" (:type evt))
|
||||||
(-> (:event evt) create-notification local-push-android))
|
(re-frame/dispatch [::local-notification-android (:event evt)]))
|
||||||
(on-success)
|
(on-success)
|
||||||
(catch :default e
|
(catch :default e
|
||||||
(log/warn "failed to handle background notification" e)
|
(log/warn "failed to handle background notification" e)
|
||||||
|
|
|
@ -95,5 +95,6 @@
|
||||||
{:events [::on-scan-success]}
|
{:events [::on-scan-success]}
|
||||||
[{:keys [db]} uri]
|
[{:keys [db]} uri]
|
||||||
{::router/handle-uri {:chain (ethereum/chain-keyword db)
|
{::router/handle-uri {:chain (ethereum/chain-keyword db)
|
||||||
|
:chats (get db :chats)
|
||||||
:uri uri
|
:uri uri
|
||||||
:cb #(re-frame/dispatch [::match-scanned-value %])}})
|
:cb #(re-frame/dispatch [::match-scanned-value %])}})
|
||||||
|
|
|
@ -11,7 +11,8 @@
|
||||||
[cljs.spec.alpha :as spec]
|
[cljs.spec.alpha :as spec]
|
||||||
[status-im.ethereum.core :as ethereum]
|
[status-im.ethereum.core :as ethereum]
|
||||||
[status-im.utils.db :as utils.db]
|
[status-im.utils.db :as utils.db]
|
||||||
[status-im.utils.http :as http]))
|
[status-im.utils.http :as http]
|
||||||
|
[status-im.chat.models :as chat.models]))
|
||||||
|
|
||||||
(def ethereum-scheme "ethereum:")
|
(def ethereum-scheme "ethereum:")
|
||||||
|
|
||||||
|
@ -91,20 +92,30 @@
|
||||||
{:type :public-chat
|
{:type :public-chat
|
||||||
:error :invalid-topic}))
|
:error :invalid-topic}))
|
||||||
|
|
||||||
(defn match-group-chat [{:strs [a a1 a2]}]
|
(defn match-group-chat [chats {:strs [a a1 a2]}]
|
||||||
(let [[admin-pk encoded-chat-name chat-id] [a a1 a2]
|
(let [[admin-pk encoded-chat-name chat-id] [a a1 a2]
|
||||||
chat-id-parts (when (not (string/blank? chat-id)) (string/split chat-id #"-"))
|
chat-id-parts (when (not (string/blank? chat-id)) (string/split chat-id #"-"))
|
||||||
chat-name (when (not (string/blank? encoded-chat-name)) (js/decodeURI encoded-chat-name))]
|
chat-name (when (not (string/blank? encoded-chat-name)) (js/decodeURI encoded-chat-name))]
|
||||||
(if (and (not (string/blank? chat-id)) (not (string/blank? admin-pk)) (not (string/blank? chat-name))
|
(cond (and (not (string/blank? chat-id)) (not (string/blank? admin-pk)) (not (string/blank? chat-name))
|
||||||
(> (count chat-id-parts) 1)
|
(> (count chat-id-parts) 1)
|
||||||
(not (string/blank? (first chat-id-parts)))
|
(not (string/blank? (first chat-id-parts)))
|
||||||
(utils.db/valid-public-key? admin-pk)
|
(utils.db/valid-public-key? admin-pk)
|
||||||
(utils.db/valid-public-key? (last chat-id-parts)))
|
(utils.db/valid-public-key? (last chat-id-parts)))
|
||||||
{:type :group-chat
|
{:type :group-chat
|
||||||
:chat-id chat-id
|
:chat-id chat-id
|
||||||
:invitation-admin admin-pk
|
:invitation-admin admin-pk
|
||||||
:chat-name chat-name}
|
:chat-name chat-name}
|
||||||
{:error :invalid-group-chat-data})))
|
|
||||||
|
(and (not (string/blank? chat-id))
|
||||||
|
(chat.models/group-chat? (get chats chat-id)))
|
||||||
|
(let [{:keys [chat-name invitation-admin]} (get chats chat-id)]
|
||||||
|
{:type :group-chat
|
||||||
|
:chat-id chat-id
|
||||||
|
:invitation-admin invitation-admin
|
||||||
|
:chat-name chat-name})
|
||||||
|
|
||||||
|
:else
|
||||||
|
{:error :invalid-group-chat-data})))
|
||||||
|
|
||||||
(defn match-private-chat-async [chain {:keys [chat-id]} cb]
|
(defn match-private-chat-async [chain {:keys [chat-id]} cb]
|
||||||
(match-contact-async chain
|
(match-contact-async chain
|
||||||
|
@ -165,7 +176,7 @@
|
||||||
{:type :wallet-account
|
{:type :wallet-account
|
||||||
:account (when account (string/lower-case account))})
|
:account (when account (string/lower-case account))})
|
||||||
|
|
||||||
(defn handle-uri [chain uri cb]
|
(defn handle-uri [chain chats uri cb]
|
||||||
(let [{:keys [handler route-params query-params]} (match-uri uri)]
|
(let [{:keys [handler route-params query-params]} (match-uri uri)]
|
||||||
(log/info "[router] uri " uri " matched " handler " with " route-params)
|
(log/info "[router] uri " uri " matched " handler " with " route-params)
|
||||||
(cond
|
(cond
|
||||||
|
@ -185,7 +196,7 @@
|
||||||
(match-private-chat-async chain route-params cb)
|
(match-private-chat-async chain route-params cb)
|
||||||
|
|
||||||
(= handler :group-chat)
|
(= handler :group-chat)
|
||||||
(cb (match-group-chat query-params))
|
(cb (match-group-chat chats query-params))
|
||||||
|
|
||||||
(spec/valid? :global/public-key uri)
|
(spec/valid? :global/public-key uri)
|
||||||
(match-contact-async chain {:user-id uri} cb)
|
(match-contact-async chain {:user-id uri} cb)
|
||||||
|
@ -205,5 +216,5 @@
|
||||||
|
|
||||||
(re-frame/reg-fx
|
(re-frame/reg-fx
|
||||||
::handle-uri
|
::handle-uri
|
||||||
(fn [{:keys [chain uri cb]}]
|
(fn [{:keys [chain chats uri cb]}]
|
||||||
(handle-uri chain uri cb)))
|
(handle-uri chain chats uri cb)))
|
||||||
|
|
|
@ -67,7 +67,7 @@
|
||||||
(def error {:error :invalid-group-chat-data})
|
(def error {:error :invalid-group-chat-data})
|
||||||
|
|
||||||
(deftest match-group-chat-query
|
(deftest match-group-chat-query
|
||||||
(are [query-params expected] (= (router/match-group-chat query-params)
|
(are [query-params expected] (= (router/match-group-chat {} query-params)
|
||||||
expected)
|
expected)
|
||||||
nil error
|
nil error
|
||||||
{} error
|
{} error
|
||||||
|
@ -78,4 +78,4 @@
|
||||||
{"a" public-key "a1" chat-name "a2" chat-id} {:type :group-chat
|
{"a" public-key "a1" chat-name "a2" chat-id} {:type :group-chat
|
||||||
:chat-id chat-id
|
:chat-id chat-id
|
||||||
:invitation-admin public-key
|
:invitation-admin public-key
|
||||||
:chat-name chat-name}))
|
:chat-name chat-name}))
|
||||||
|
|
|
@ -97,5 +97,6 @@
|
||||||
{:events [:contact/qr-code-scanned]}
|
{:events [:contact/qr-code-scanned]}
|
||||||
[{:keys [db]} data opts]
|
[{:keys [db]} data opts]
|
||||||
{::router/handle-uri {:chain (ethereum/chain-keyword db)
|
{::router/handle-uri {:chain (ethereum/chain-keyword db)
|
||||||
|
:chats (get db :chats)
|
||||||
:uri data
|
:uri data
|
||||||
:cb #(re-frame/dispatch [::qr-code-handled % opts])}})
|
:cb #(re-frame/dispatch [::qr-code-handled % opts])}})
|
||||||
|
|
|
@ -6,5 +6,5 @@
|
||||||
(handlers/register-handler-fx
|
(handlers/register-handler-fx
|
||||||
:handle-universal-link
|
:handle-universal-link
|
||||||
(fn [cofx [_ url]]
|
(fn [cofx [_ url]]
|
||||||
(log/debug "universal links: event received for " url)
|
(log/info "universal links: event received for " url)
|
||||||
(universal-links/handle-url cofx url)))
|
(universal-links/handle-url cofx url)))
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"_comment": "DO NOT EDIT THIS FILE BY HAND. USE 'scripts/update-status-go.sh <tag>' instead",
|
"_comment": "DO NOT EDIT THIS FILE BY HAND. USE 'scripts/update-status-go.sh <tag>' instead",
|
||||||
"owner": "status-im",
|
"owner": "status-im",
|
||||||
"repo": "status-go",
|
"repo": "status-go",
|
||||||
"version": "v0.69.3",
|
"version": "v0.70.0",
|
||||||
"commit-sha1": "e18050b87f75b09cd8ad8451a7d7e0fa2e498654",
|
"commit-sha1": "d862b042ae78c8cdf38a33a7a824698ae5aa8089",
|
||||||
"src-sha256": "01s1s91afy2bwlx928b62fw4fc6mvsphz2py1sfc0fl9r7dw1lkz"
|
"src-sha256": "1g1kyn2nz2vjzh6qvs57k7yhy6zkw4s6jnhnxpvapzxndyg8q5h4"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue