[NEW] Reply notification (#1448)
This commit is contained in:
parent
9084f22ab1
commit
5f0389c7de
|
@ -217,6 +217,10 @@ dependencies {
|
||||||
} else {
|
} else {
|
||||||
implementation jscFlavor
|
implementation jscFlavor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
implementation "com.google.code.gson:gson:2.8.5"
|
||||||
|
implementation "com.github.bumptech.glide:glide:4.9.0"
|
||||||
|
annotationProcessor "com.github.bumptech.glide:compiler:4.9.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run this once to be able to run the application with BUCK
|
// Run this once to be able to run the application with BUCK
|
||||||
|
|
|
@ -47,6 +47,15 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||||
|
<receiver
|
||||||
|
android:name=".ReplyBroadcast"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false" />
|
||||||
|
<receiver
|
||||||
|
android:name=".DismissNotification"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false" >
|
||||||
|
</receiver>
|
||||||
<activity
|
<activity
|
||||||
android:noHistory="true"
|
android:noHistory="true"
|
||||||
android:name=".share.ShareActivity"
|
android:name=".share.ShareActivity"
|
||||||
|
|
|
@ -4,56 +4,139 @@ import android.app.Notification;
|
||||||
import android.app.NotificationChannel;
|
import android.app.NotificationChannel;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
|
import android.app.RemoteInput;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.media.AudioManager;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.Settings.System;
|
|
||||||
import android.media.RingtoneManager;
|
|
||||||
|
|
||||||
|
import com.google.gson.*;
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
||||||
|
import com.bumptech.glide.request.RequestOptions;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.lang.InterruptedException;
|
||||||
|
|
||||||
|
import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
import com.wix.reactnativenotifications.core.AppLaunchHelper;
|
import com.wix.reactnativenotifications.core.AppLaunchHelper;
|
||||||
import com.wix.reactnativenotifications.core.AppLifecycleFacade;
|
import com.wix.reactnativenotifications.core.AppLifecycleFacade;
|
||||||
import com.wix.reactnativenotifications.core.JsIOHelper;
|
import com.wix.reactnativenotifications.core.JsIOHelper;
|
||||||
import com.wix.reactnativenotifications.core.notification.PushNotification;
|
import com.wix.reactnativenotifications.core.notification.PushNotification;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import static com.wix.reactnativenotifications.Defs.NOTIFICATION_RECEIVED_EVENT_NAME;
|
||||||
|
|
||||||
public class CustomPushNotification extends PushNotification {
|
public class CustomPushNotification extends PushNotification {
|
||||||
|
public static ReactApplicationContext reactApplicationContext;
|
||||||
|
|
||||||
public CustomPushNotification(Context context, Bundle bundle, AppLifecycleFacade appLifecycleFacade, AppLaunchHelper appLaunchHelper, JsIOHelper jsIoHelper) {
|
public CustomPushNotification(Context context, Bundle bundle, AppLifecycleFacade appLifecycleFacade, AppLaunchHelper appLaunchHelper, JsIOHelper jsIoHelper) {
|
||||||
super(context, bundle, appLifecycleFacade, appLaunchHelper, jsIoHelper);
|
super(context, bundle, appLifecycleFacade, appLaunchHelper, jsIoHelper);
|
||||||
|
reactApplicationContext = new ReactApplicationContext(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, List<String>> notificationMessages = new HashMap<String, List<String>>();
|
||||||
|
public static String KEY_REPLY = "KEY_REPLY";
|
||||||
|
public static String NOTIFICATION_ID = "NOTIFICATION_ID";
|
||||||
|
|
||||||
|
public static void clearMessages(int notId) {
|
||||||
|
notificationMessages.remove(Integer.toString(notId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceived() throws InvalidNotificationException {
|
||||||
|
final Bundle bundle = mNotificationProps.asBundle();
|
||||||
|
|
||||||
|
String notId = bundle.getString("notId");
|
||||||
|
String message = bundle.getString("message");
|
||||||
|
|
||||||
|
if (notificationMessages.get(notId) == null) {
|
||||||
|
notificationMessages.put(notId, new ArrayList<String>());
|
||||||
|
}
|
||||||
|
notificationMessages.get(notId).add(message);
|
||||||
|
|
||||||
|
super.postNotification(Integer.parseInt(notId));
|
||||||
|
|
||||||
|
notifyReceivedToJS();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOpened() {
|
||||||
|
Bundle bundle = mNotificationProps.asBundle();
|
||||||
|
final String notId = bundle.getString("notId");
|
||||||
|
notificationMessages.remove(notId);
|
||||||
|
digestNotification();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Notification.Builder getNotificationBuilder(PendingIntent intent) {
|
protected Notification.Builder getNotificationBuilder(PendingIntent intent) {
|
||||||
final Resources res = mContext.getResources();
|
final Notification.Builder notification = new Notification.Builder(mContext);
|
||||||
String packageName = mContext.getPackageName();
|
|
||||||
|
|
||||||
Bundle bundle = mNotificationProps.asBundle();
|
Bundle bundle = mNotificationProps.asBundle();
|
||||||
int smallIconResId = res.getIdentifier("ic_notification", "mipmap", packageName);
|
|
||||||
int largeIconResId = res.getIdentifier("ic_launcher", "mipmap", packageName);
|
|
||||||
String title = bundle.getString("title");
|
String title = bundle.getString("title");
|
||||||
String message = bundle.getString("message");
|
String message = bundle.getString("message");
|
||||||
|
String notId = bundle.getString("notId");
|
||||||
|
|
||||||
String CHANNEL_ID = "rocketchatrn_channel_01";
|
notification
|
||||||
String CHANNEL_NAME = "All";
|
|
||||||
|
|
||||||
final Notification.Builder notification = new Notification.Builder(mContext)
|
|
||||||
.setSmallIcon(smallIconResId)
|
|
||||||
.setContentIntent(intent)
|
.setContentIntent(intent)
|
||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
.setContentText(message)
|
.setContentText(message)
|
||||||
.setStyle(new Notification.BigTextStyle().bigText(message))
|
|
||||||
.setPriority(Notification.PRIORITY_HIGH)
|
.setPriority(Notification.PRIORITY_HIGH)
|
||||||
.setDefaults(Notification.DEFAULT_ALL)
|
.setDefaults(Notification.DEFAULT_ALL)
|
||||||
.setAutoCancel(true);
|
.setAutoCancel(true);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
Integer notificationId = Integer.parseInt(notId);
|
||||||
notification.setColor(mContext.getColor(R.color.notification_text));
|
notificationChannel(notification);
|
||||||
}
|
notificationIcons(notification, bundle);
|
||||||
|
notificationStyle(notification, notificationId, bundle);
|
||||||
|
notificationReply(notification, notificationId, bundle);
|
||||||
|
notificationDismiss(notification, notificationId);
|
||||||
|
|
||||||
|
return notification;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyReceivedToJS() {
|
||||||
|
mJsIOHelper.sendEventToJS(NOTIFICATION_RECEIVED_EVENT_NAME, mNotificationProps.asBundle(), mAppLifecycleFacade.getRunningReactContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bitmap getAvatar(String uri) {
|
||||||
|
try {
|
||||||
|
return Glide.with(mContext)
|
||||||
|
.asBitmap()
|
||||||
|
.apply(RequestOptions.bitmapTransform(new RoundedCorners(10)))
|
||||||
|
.load(uri)
|
||||||
|
.submit(100, 100)
|
||||||
|
.get();
|
||||||
|
} catch (final ExecutionException | InterruptedException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notificationIcons(Notification.Builder notification, Bundle bundle) {
|
||||||
|
final Resources res = mContext.getResources();
|
||||||
|
String packageName = mContext.getPackageName();
|
||||||
|
|
||||||
|
int smallIconResId = res.getIdentifier("ic_notification", "mipmap", packageName);
|
||||||
|
|
||||||
|
Gson gson = new Gson();
|
||||||
|
Ejson ejson = gson.fromJson(bundle.getString("ejson", "{}"), Ejson.class);
|
||||||
|
|
||||||
|
notification
|
||||||
|
.setSmallIcon(smallIconResId)
|
||||||
|
.setLargeIcon(getAvatar(ejson.getAvatarUri()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notificationChannel(Notification.Builder notification) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
String CHANNEL_ID = "rocketchatrn_channel_01";
|
||||||
|
String CHANNEL_NAME = "All";
|
||||||
|
|
||||||
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
|
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
|
||||||
CHANNEL_NAME,
|
CHANNEL_NAME,
|
||||||
NotificationManager.IMPORTANCE_DEFAULT);
|
NotificationManager.IMPORTANCE_DEFAULT);
|
||||||
|
@ -63,10 +146,65 @@ public class CustomPushNotification extends PushNotification {
|
||||||
|
|
||||||
notification.setChannelId(CHANNEL_ID);
|
notification.setChannelId(CHANNEL_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bitmap largeIconBitmap = BitmapFactory.decodeResource(res, largeIconResId);
|
|
||||||
notification.setLargeIcon(largeIconBitmap);
|
|
||||||
|
|
||||||
return notification;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void notificationStyle(Notification.Builder notification, int notId, Bundle bundle) {
|
||||||
|
Notification.InboxStyle messageStyle = new Notification.InboxStyle();
|
||||||
|
List<String> messages = notificationMessages.get(Integer.toString(notId));
|
||||||
|
if (messages != null) {
|
||||||
|
for (int i = 0; i < messages.size(); i++) {
|
||||||
|
messageStyle.addLine(messages.get(i));
|
||||||
|
}
|
||||||
|
String summary = bundle.getString("summaryText");
|
||||||
|
messageStyle.setSummaryText(summary.replace("%n%", Integer.toString(messages.size())));
|
||||||
|
notification.setNumber(messages.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
notification.setColor(mContext.getColor(R.color.notification_text));
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.setStyle(messageStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notificationReply(Notification.Builder notification, int notificationId, Bundle bundle) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String label = "Reply";
|
||||||
|
|
||||||
|
final Resources res = mContext.getResources();
|
||||||
|
String packageName = mContext.getPackageName();
|
||||||
|
int smallIconResId = res.getIdentifier("ic_notification", "mipmap", packageName);
|
||||||
|
|
||||||
|
Intent replyIntent = new Intent(mContext, ReplyBroadcast.class);
|
||||||
|
replyIntent.setAction(KEY_REPLY);
|
||||||
|
replyIntent.putExtra("pushNotification", bundle);
|
||||||
|
|
||||||
|
PendingIntent replyPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, replyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
|
||||||
|
RemoteInput remoteInput = new RemoteInput.Builder(KEY_REPLY)
|
||||||
|
.setLabel(label)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
CharSequence title = label;
|
||||||
|
Notification.Action replyAction = new Notification.Action.Builder(smallIconResId, title, replyPendingIntent)
|
||||||
|
.addRemoteInput(remoteInput)
|
||||||
|
.setAllowGeneratedReplies(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
notification
|
||||||
|
.setShowWhen(true)
|
||||||
|
.addAction(replyAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notificationDismiss(Notification.Builder notification, int notificationId) {
|
||||||
|
Intent intent = new Intent(mContext, DismissNotification.class);
|
||||||
|
intent.putExtra(NOTIFICATION_ID, notificationId);
|
||||||
|
|
||||||
|
PendingIntent dismissPendingIntent = PendingIntent.getBroadcast(mContext, notificationId, intent, 0);
|
||||||
|
|
||||||
|
notification.setDeleteIntent(dismissPendingIntent);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package chat.rocket.reactnative;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
public class DismissNotification extends BroadcastReceiver {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
int notId = intent.getExtras().getInt(CustomPushNotification.NOTIFICATION_ID);
|
||||||
|
CustomPushNotification.clearMessages(notId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package chat.rocket.reactnative;
|
||||||
|
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import chat.rocket.userdefaults.RNUserDefaultsModule;
|
||||||
|
|
||||||
|
public class Ejson {
|
||||||
|
String host;
|
||||||
|
String rid;
|
||||||
|
String type;
|
||||||
|
Sender sender;
|
||||||
|
|
||||||
|
private String TOKEN_KEY = "reactnativemeteor_usertoken-";
|
||||||
|
private SharedPreferences sharedPreferences = RNUserDefaultsModule.getPreferences(CustomPushNotification.reactApplicationContext);
|
||||||
|
|
||||||
|
public String getAvatarUri() {
|
||||||
|
if (!type.equals("d")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return serverURL() + "/avatar/" + this.sender.username + "?rc_token=" + token() + "&rc_uid=" + userId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String token() {
|
||||||
|
return sharedPreferences.getString(TOKEN_KEY.concat(userId()), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String userId() {
|
||||||
|
return sharedPreferences.getString(TOKEN_KEY.concat(serverURL()), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String serverURL() {
|
||||||
|
String url = this.host;
|
||||||
|
if (url.endsWith("/")) {
|
||||||
|
url = url.substring(0, url.length() - 1);
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Sender {
|
||||||
|
String username;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
package chat.rocket.reactnative;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.RemoteInput;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import okhttp3.Call;
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
import chat.rocket.userdefaults.RNUserDefaultsModule;
|
||||||
|
import com.wix.reactnativenotifications.core.NotificationIntentAdapter;
|
||||||
|
|
||||||
|
public class ReplyBroadcast extends BroadcastReceiver {
|
||||||
|
private Context mContext;
|
||||||
|
private Bundle bundle;
|
||||||
|
private NotificationManager notificationManager;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
|
||||||
|
final CharSequence message = getReplyMessage(intent);
|
||||||
|
if (message == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mContext = context;
|
||||||
|
bundle = NotificationIntentAdapter.extractPendingNotificationDataFromIntent(intent);
|
||||||
|
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
|
||||||
|
String notId = bundle.getString("notId");
|
||||||
|
|
||||||
|
Gson gson = new Gson();
|
||||||
|
Ejson ejson = gson.fromJson(bundle.getString("ejson", "{}"), Ejson.class);
|
||||||
|
|
||||||
|
replyToMessage(ejson, Integer.parseInt(notId), message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void replyToMessage(final Ejson ejson, final int notId, final CharSequence message) {
|
||||||
|
String serverURL = ejson.serverURL();
|
||||||
|
String rid = ejson.rid;
|
||||||
|
|
||||||
|
if (serverURL == null || rid == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final OkHttpClient client = new OkHttpClient();
|
||||||
|
final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||||
|
|
||||||
|
String json = buildMessage(rid, message.toString());
|
||||||
|
|
||||||
|
CustomPushNotification.clearMessages(notId);
|
||||||
|
|
||||||
|
RequestBody body = RequestBody.create(JSON, json);
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.header("x-auth-token", ejson.token())
|
||||||
|
.header("x-user-id", ejson.userId())
|
||||||
|
.url(String.format("%s/api/v1/chat.sendMessage", serverURL))
|
||||||
|
.post(body)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
client.newCall(request).enqueue(new okhttp3.Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call call, IOException e) {
|
||||||
|
Log.i("RCNotification", String.format("Reply FAILED exception %s", e.getMessage()));
|
||||||
|
onReplyFailed(notificationManager, notId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, final Response response) throws IOException {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
Log.d("RCNotification", "Reply SUCCESS");
|
||||||
|
onReplySuccess(notificationManager, notId);
|
||||||
|
} else {
|
||||||
|
Log.i("RCNotification", String.format("Reply FAILED status %s BODY %s", response.code(), response.body().string()));
|
||||||
|
onReplyFailed(notificationManager, notId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getMessageId() {
|
||||||
|
final String ALPHA_NUMERIC_STRING = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
int count = 17;
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
while (count-- != 0) {
|
||||||
|
int character = (int)(Math.random()*ALPHA_NUMERIC_STRING.length());
|
||||||
|
builder.append(ALPHA_NUMERIC_STRING.charAt(character));
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String buildMessage(String rid, String message) {
|
||||||
|
Gson gsonBuilder = new GsonBuilder().create();
|
||||||
|
|
||||||
|
Map msgMap = new HashMap();
|
||||||
|
msgMap.put("_id", getMessageId());
|
||||||
|
msgMap.put("rid", rid);
|
||||||
|
msgMap.put("msg", message);
|
||||||
|
msgMap.put("tmid", null);
|
||||||
|
|
||||||
|
Map msg = new HashMap();
|
||||||
|
msg.put("message", msgMap);
|
||||||
|
|
||||||
|
String json = gsonBuilder.toJson(msg);
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onReplyFailed(NotificationManager notificationManager, int notId) {
|
||||||
|
String CHANNEL_ID = "CHANNEL_ID_REPLY_FAILED";
|
||||||
|
|
||||||
|
final Resources res = mContext.getResources();
|
||||||
|
String packageName = mContext.getPackageName();
|
||||||
|
int smallIconResId = res.getIdentifier("ic_notification", "mipmap", packageName);
|
||||||
|
|
||||||
|
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_ID, NotificationManager.IMPORTANCE_LOW);
|
||||||
|
notificationManager.createNotificationChannel(channel);
|
||||||
|
Notification notification =
|
||||||
|
new Notification.Builder(mContext, CHANNEL_ID)
|
||||||
|
.setContentTitle("Failed to reply message.")
|
||||||
|
.setSmallIcon(smallIconResId)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
notificationManager.notify(notId, notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onReplySuccess(NotificationManager notificationManager, int notId) {
|
||||||
|
notificationManager.cancel(notId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CharSequence getReplyMessage(Intent intent) {
|
||||||
|
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
|
||||||
|
if (remoteInput != null) {
|
||||||
|
return remoteInput.getCharSequence(CustomPushNotification.KEY_REPLY);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,17 @@
|
||||||
import NotificationsIOS from 'react-native-notifications';
|
import NotificationsIOS, { NotificationAction, NotificationCategory } from 'react-native-notifications';
|
||||||
|
|
||||||
import reduxStore from '../../lib/createStore';
|
import reduxStore from '../../lib/createStore';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
|
||||||
|
const replyAction = new NotificationAction({
|
||||||
|
activationMode: 'background',
|
||||||
|
title: I18n.t('Reply'),
|
||||||
|
textInput: {
|
||||||
|
buttonTitle: I18n.t('Reply'),
|
||||||
|
placeholder: I18n.t('Type_message')
|
||||||
|
},
|
||||||
|
identifier: 'REPLY_ACTION'
|
||||||
|
});
|
||||||
|
|
||||||
class PushNotification {
|
class PushNotification {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -20,7 +31,12 @@ class PushNotification {
|
||||||
completion();
|
completion();
|
||||||
});
|
});
|
||||||
|
|
||||||
NotificationsIOS.requestPermissions();
|
const actions = [];
|
||||||
|
actions.push(new NotificationCategory({
|
||||||
|
identifier: 'MESSAGE',
|
||||||
|
actions: [replyAction]
|
||||||
|
}));
|
||||||
|
NotificationsIOS.requestPermissions(actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
getDeviceToken() {
|
getDeviceToken() {
|
||||||
|
|
|
@ -111,6 +111,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
||||||
});
|
});
|
||||||
|
|
||||||
yield RNUserDefaults.set(`${ RocketChat.TOKEN_KEY }-${ server }`, user.id);
|
yield RNUserDefaults.set(`${ RocketChat.TOKEN_KEY }-${ server }`, user.id);
|
||||||
|
yield RNUserDefaults.set(`${ RocketChat.TOKEN_KEY }-${ user.id }`, user.token);
|
||||||
yield put(setUser(user));
|
yield put(setUser(user));
|
||||||
EventEmitter.emit('connected');
|
EventEmitter.emit('connected');
|
||||||
|
|
||||||
|
|
|
@ -381,7 +381,7 @@ PODS:
|
||||||
- React
|
- React
|
||||||
- RNScreens (2.0.0-alpha.3):
|
- RNScreens (2.0.0-alpha.3):
|
||||||
- React
|
- React
|
||||||
- RNUserDefaults (1.3.5):
|
- RNUserDefaults (1.7.0):
|
||||||
- React
|
- React
|
||||||
- RNVectorIcons (6.6.0):
|
- RNVectorIcons (6.6.0):
|
||||||
- React
|
- React
|
||||||
|
@ -756,7 +756,7 @@ SPEC CHECKSUMS:
|
||||||
RNReanimated: b2ab0b693dddd2339bd2f300e770f6302d2e960c
|
RNReanimated: b2ab0b693dddd2339bd2f300e770f6302d2e960c
|
||||||
RNRootView: 895a4813dedeaca82db2fa868ca1c333d790e494
|
RNRootView: 895a4813dedeaca82db2fa868ca1c333d790e494
|
||||||
RNScreens: 402a99b0a27c0c32f079cec12d3ccbd35e20cd7f
|
RNScreens: 402a99b0a27c0c32f079cec12d3ccbd35e20cd7f
|
||||||
RNUserDefaults: 8a4928443510aa99e4ccb3b53f1bf186593d690b
|
RNUserDefaults: af71a1cdf1c12baf8210bc741c65f5faba9826d6
|
||||||
RNVectorIcons: 0bb4def82230be1333ddaeee9fcba45f0b288ed4
|
RNVectorIcons: 0bb4def82230be1333ddaeee9fcba45f0b288ed4
|
||||||
RSKImageCropper: a446db0e8444a036b34f3c43db01b2373baa4b2a
|
RSKImageCropper: a446db0e8444a036b34f3c43db01b2373baa4b2a
|
||||||
SDWebImage: 96d7f03415ccb28d299d765f93557ff8a617abd8
|
SDWebImage: 96d7f03415ccb28d299d765f93557ff8a617abd8
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
{
|
{
|
||||||
"name": "RNUserDefaults",
|
"name": "RNUserDefaults",
|
||||||
"version": "1.3.5",
|
"version": "1.7.0",
|
||||||
"summary": "Use `UserDefaults` (iOS) with React Native and `AsyncStorage` on AndroidOS.",
|
"summary": "Use `UserDefaults` (iOS) with React Native and `SharedPreferences` on AndroidOS.",
|
||||||
"description": "Use `UserDefaults` (iOS) with React Native and `AsyncStorage` on AndroidOS.",
|
"description": "Use `UserDefaults` (iOS) with React Native and `SharedPreferences` on AndroidOS.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"authors": "djorkaeffalexandre",
|
"authors": "djorkaeffalexandre",
|
||||||
"homepage": "https://github.com/djorkaeffalexandre/rn-user-defaults",
|
"homepage": "https://github.com/RocketChat/rn-user-defaults.git",
|
||||||
"source": {
|
"source": {
|
||||||
"git": "https://github.com/djorkaeffalexandre/rn-user-defaults.git"
|
"git": "https://github.com/RocketChat/rn-user-defaults.git"
|
||||||
},
|
},
|
||||||
"requires_arc": true,
|
"requires_arc": true,
|
||||||
"platforms": {
|
"platforms": {
|
||||||
|
|
|
@ -381,7 +381,7 @@ PODS:
|
||||||
- React
|
- React
|
||||||
- RNScreens (2.0.0-alpha.3):
|
- RNScreens (2.0.0-alpha.3):
|
||||||
- React
|
- React
|
||||||
- RNUserDefaults (1.3.5):
|
- RNUserDefaults (1.7.0):
|
||||||
- React
|
- React
|
||||||
- RNVectorIcons (6.6.0):
|
- RNVectorIcons (6.6.0):
|
||||||
- React
|
- React
|
||||||
|
@ -756,7 +756,7 @@ SPEC CHECKSUMS:
|
||||||
RNReanimated: b2ab0b693dddd2339bd2f300e770f6302d2e960c
|
RNReanimated: b2ab0b693dddd2339bd2f300e770f6302d2e960c
|
||||||
RNRootView: 895a4813dedeaca82db2fa868ca1c333d790e494
|
RNRootView: 895a4813dedeaca82db2fa868ca1c333d790e494
|
||||||
RNScreens: 402a99b0a27c0c32f079cec12d3ccbd35e20cd7f
|
RNScreens: 402a99b0a27c0c32f079cec12d3ccbd35e20cd7f
|
||||||
RNUserDefaults: 8a4928443510aa99e4ccb3b53f1bf186593d690b
|
RNUserDefaults: af71a1cdf1c12baf8210bc741c65f5faba9826d6
|
||||||
RNVectorIcons: 0bb4def82230be1333ddaeee9fcba45f0b288ed4
|
RNVectorIcons: 0bb4def82230be1333ddaeee9fcba45f0b288ed4
|
||||||
RSKImageCropper: a446db0e8444a036b34f3c43db01b2373baa4b2a
|
RSKImageCropper: a446db0e8444a036b34f3c43db01b2373baa4b2a
|
||||||
SDWebImage: 96d7f03415ccb28d299d765f93557ff8a617abd8
|
SDWebImage: 96d7f03415ccb28d299d765f93557ff8a617abd8
|
||||||
|
|
|
@ -85,6 +85,7 @@
|
||||||
</dict>
|
</dict>
|
||||||
<key>UIBackgroundModes</key>
|
<key>UIBackgroundModes</key>
|
||||||
<array>
|
<array>
|
||||||
|
<string>fetch</string>
|
||||||
<string>voip</string>
|
<string>voip</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
|
|
|
@ -95,7 +95,7 @@
|
||||||
"rn-extensions-share": "^2.3.10",
|
"rn-extensions-share": "^2.3.10",
|
||||||
"rn-fetch-blob": "0.11.2",
|
"rn-fetch-blob": "0.11.2",
|
||||||
"rn-root-view": "^1.0.3",
|
"rn-root-view": "^1.0.3",
|
||||||
"rn-user-defaults": "^1.3.5",
|
"rn-user-defaults": "^1.7.0",
|
||||||
"semver": "6.3.0",
|
"semver": "6.3.0",
|
||||||
"snyk": "1.210.0",
|
"snyk": "1.210.0",
|
||||||
"strip-ansi": "5.2.0",
|
"strip-ansi": "5.2.0",
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
diff --git a/node_modules/react-native-notifications/RNNotifications/RNNotificationEventHandler.m b/node_modules/react-native-notifications/RNNotifications/RNNotificationEventHandler.m
|
||||||
|
index edc4fd4..aeb5eaa 100644
|
||||||
|
--- a/node_modules/react-native-notifications/RNNotifications/RNNotificationEventHandler.m
|
||||||
|
+++ b/node_modules/react-native-notifications/RNNotifications/RNNotificationEventHandler.m
|
||||||
|
@@ -28,9 +28,92 @@ - (void)didReceiveForegroundNotification:(UNNotification *)notification withComp
|
||||||
|
[RNEventEmitter sendEvent:RNNotificationReceivedForeground body:[RNNotificationParser parseNotification:notification]];
|
||||||
|
}
|
||||||
|
|
||||||
|
+/*
|
||||||
|
+ * Generate a random alphanumeric string to message id
|
||||||
|
+*/
|
||||||
|
+-(NSString *)random:(int)len {
|
||||||
|
+ NSString *letters = @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
+ NSMutableString *randomString = [NSMutableString stringWithCapacity:len];
|
||||||
|
+
|
||||||
|
+ for (int i=0; i<len; i++) {
|
||||||
|
+ [randomString appendFormat: @"%C", [letters characterAtIndex: arc4random_uniform([letters length])]];
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return randomString;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+/*
|
||||||
|
+ * Remove trailing slash on server url from notification
|
||||||
|
+*/
|
||||||
|
+-(NSString *)serverURL:(NSString *)host {
|
||||||
|
+ if ([host length] > 0) {
|
||||||
|
+ unichar last = [host characterAtIndex:[host length] - 1];
|
||||||
|
+ if (last == '/') {
|
||||||
|
+ host = [host substringToIndex:[host length] - 1];
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+ return host;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
- (void)didReceiveNotificationResponse:(UNNotificationResponse *)response completionHandler:(void (^)(void))completionHandler {
|
||||||
|
- [_store setActionCompletionHandler:completionHandler withCompletionKey:response.notification.request.identifier];
|
||||||
|
- [RNEventEmitter sendEvent:RNNotificationOpened body:[RNNotificationParser parseNotificationResponse:response]];
|
||||||
|
+ // if notification response is a REPLY_ACTION
|
||||||
|
+ if ([response.actionIdentifier isEqualToString:@"REPLY_ACTION"]) {
|
||||||
|
+ // convert notification data to dictionary payload
|
||||||
|
+ NSDictionary *notification = [RCTConvert UNNotificationPayload:response.notification];
|
||||||
|
+
|
||||||
|
+ // parse ejson from notification
|
||||||
|
+ NSData *ejsonData = [[notification valueForKey:@"ejson"] dataUsingEncoding:NSUTF8StringEncoding];
|
||||||
|
+ NSError *error;
|
||||||
|
+ NSDictionary *ejson = [NSJSONSerialization JSONObjectWithData:ejsonData options:kNilOptions error:&error];
|
||||||
|
+
|
||||||
|
+ // data from notification
|
||||||
|
+ NSString *host = [ejson valueForKey:@"host"];
|
||||||
|
+ NSString *rid = [ejson valueForKey:@"rid"];
|
||||||
|
+
|
||||||
|
+ // msg on textinput of notification
|
||||||
|
+ NSString *msg = [(UNTextInputNotificationResponse *)response userText];
|
||||||
|
+
|
||||||
|
+ // get credentials
|
||||||
|
+ NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.ios.chat.rocket"];
|
||||||
|
+ NSString *TOKEN_KEY = @"reactnativemeteor_usertoken";
|
||||||
|
+ NSString *userId = [userDefaults stringForKey:[NSString stringWithFormat:@"%@-%@", TOKEN_KEY, [self serverURL:host]]];
|
||||||
|
+ NSString *token = [userDefaults stringForKey:[NSString stringWithFormat:@"%@-%@", TOKEN_KEY, userId]];
|
||||||
|
+
|
||||||
|
+ // background task - we need this because fetch doesn't work if app is closed/killed
|
||||||
|
+ UIApplication *app = [UIApplication sharedApplication];
|
||||||
|
+ __block UIBackgroundTaskIdentifier task = [app beginBackgroundTaskWithExpirationHandler:^{
|
||||||
|
+ [app endBackgroundTask:task];
|
||||||
|
+ task = UIBackgroundTaskInvalid;
|
||||||
|
+ }];
|
||||||
|
+ // we use global queue to make requests with app closed/killed
|
||||||
|
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||||
|
+ // we make a synchronous request to post new message
|
||||||
|
+ NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/api/v1/chat.sendMessage", [self serverURL:host]]]];
|
||||||
|
+
|
||||||
|
+ NSString *message = [NSString stringWithFormat:@"{ \"message\": { \"_id\": \"%@\", \"msg\": \"%@\", \"rid\": \"%@\" } }", [self random:17], msg, rid];
|
||||||
|
+
|
||||||
|
+ [request setHTTPMethod:@"POST"];
|
||||||
|
+ [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||||
|
+ [request addValue:userId forHTTPHeaderField:@"x-user-id"];
|
||||||
|
+ [request addValue:token forHTTPHeaderField:@"x-auth-token"];
|
||||||
|
+ [request setHTTPBody:[message dataUsingEncoding:NSUTF8StringEncoding]];
|
||||||
|
+
|
||||||
|
+ NSURLResponse *response = nil;
|
||||||
|
+ NSError *error = nil;
|
||||||
|
+ NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
|
||||||
|
+
|
||||||
|
+ // end background task
|
||||||
|
+ [app endBackgroundTask:task];
|
||||||
|
+ task = UIBackgroundTaskInvalid;
|
||||||
|
+
|
||||||
|
+ // complete notification response
|
||||||
|
+ completionHandler();
|
||||||
|
+ });
|
||||||
|
+ } else {
|
||||||
|
+ // We only set initial notification and emit event to JS when not is a reply action
|
||||||
|
+ [_store setActionCompletionHandler:completionHandler withCompletionKey:response.notification.request.identifier];
|
||||||
|
+ [RNEventEmitter sendEvent:RNNotificationOpened body:[RNNotificationParser parseNotificationResponse:response]];
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
|
@ -10219,10 +10219,10 @@ rn-root-view@^1.0.3:
|
||||||
resolved "https://registry.yarnpkg.com/rn-root-view/-/rn-root-view-1.0.3.tgz#a2cddc717278cb2175fb29b7c006e407b7f0d0e2"
|
resolved "https://registry.yarnpkg.com/rn-root-view/-/rn-root-view-1.0.3.tgz#a2cddc717278cb2175fb29b7c006e407b7f0d0e2"
|
||||||
integrity sha512-BIKm8hY5q8+pxK9B5ugYjqutoI9xn2JfxIZKWoaFmAl1bOIM4oXjwFQrRM1e6lFgzz99MN6Mf2dK3Alsywnvvw==
|
integrity sha512-BIKm8hY5q8+pxK9B5ugYjqutoI9xn2JfxIZKWoaFmAl1bOIM4oXjwFQrRM1e6lFgzz99MN6Mf2dK3Alsywnvvw==
|
||||||
|
|
||||||
rn-user-defaults@^1.3.5:
|
rn-user-defaults@^1.7.0:
|
||||||
version "1.3.5"
|
version "1.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/rn-user-defaults/-/rn-user-defaults-1.3.5.tgz#8a93325e3fbbc47b1abd4147dc39b25eec8a45ab"
|
resolved "https://registry.yarnpkg.com/rn-user-defaults/-/rn-user-defaults-1.7.0.tgz#8d1b79657dec3977e8f8983814b8591821f77236"
|
||||||
integrity sha512-mqB57aQBb88QK49revJeJGMQXkPl2qtLeF4mINa7XTVCruAruNkm5wgTKLyS5aNLWdd3XIjkkAUSgH6o6FvIVQ==
|
integrity sha512-Qo6sIH8wldmQ0oOMMvljec4WOa/a1Up1pdatoXZGaPG1gl8OKgKH5HPKyddcABYtxPeBUTPVzCxP/6S6wPCqGQ==
|
||||||
|
|
||||||
rsvp@^3.3.3:
|
rsvp@^3.3.3:
|
||||||
version "3.6.2"
|
version "3.6.2"
|
||||||
|
|
Loading…
Reference in New Issue