diff --git a/.circleci/config.yml b/.circleci/config.yml
index 206b41d8d..7d612de47 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -197,7 +197,8 @@ jobs:
npx jetify
cd android
if [[ $KEYSTORE ]]; then
- ./gradlew bundleRelease
+ # TODO: enable app bundle again
+ ./gradlew assembleRelease
else
./gradlew assembleDebug
fi
diff --git a/README.md b/README.md
index 0fa994229..f399817d3 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Rocket.Chat React Native Mobile
+# Rocket.Chat Mobile
[](https://david-dm.org/RocketChat/Rocket.Chat.ReactNative)
[](https://www.codacy.com/app/RocketChat/Rocket.Chat.ReactNative?utm_source=github.com&utm_medium=referral&utm_content=RocketChat/Rocket.Chat.ReactNative&utm_campaign=badger)
@@ -8,6 +8,16 @@
**Supported Server Versions:** 0.70.0+
## Download
+
+### Official apps
+
+
+
+
+
+
+
+### Experimental apps
@@ -19,11 +29,17 @@
### TestFlight
-You can signup to our TestFlight builds by acessing this link: https://testflight.apple.com/join/7I3dLCNT.
+You can signup to our TestFlight builds by accessing these links:
+
+- Official: https://testflight.apple.com/join/3gcYeoMr
+- Experimental: https://testflight.apple.com/join/7I3dLCNT.
### Google Play beta
-You can subscribe to Google Play Beta program and download latest versions: https://play.google.com/store/apps/details?id=chat.rocket.reactnative
+You can subscribe to Google Play Beta program and download latest versions:
+
+- Official: https://play.google.com/store/apps/details?id=chat.rocket.android
+- Experimental: https://play.google.com/store/apps/details?id=chat.rocket.reactnative
## Reporting an Issue
@@ -57,13 +73,8 @@ If you don't need multiple servers, there is a branch `single-server` just for t
Readme will guide you on how to config.
## Current priorities
-1) Jitsi integration
-2) Notification Preferences
-3) Two-way authentication
-4) Bugsnag
-5) Optional Analytics
-6) Typescript
-7) Prettier
+1) Omnichannel support
+2) E2E encryption
## Features
| Feature | Status |
@@ -71,6 +82,7 @@ Readme will guide you on how to config.
| Jitsi Integration | ✅ |
| Federation (Directory) | ✅ |
| Discussions | ❌ |
+| Omnichannel | ❌ |
| Threads | ✅ |
| Record Audio | ✅ |
| Record Video | ✅ |
@@ -83,18 +95,18 @@ Readme will guide you on how to config.
| Grouped messages | ✅ |
| Mark room as read | ✅ |
| Mark room as unread | ✅ |
-| Tablet Support | ❌ |
+| Tablet Support | ✅ |
| Read receipt | ✅ |
| Broadbast Channel | ✅ |
| Authentication via SAML | ✅ |
| Authentication via CAS | ✅ |
| Custom Fields on Signup | ✅ |
| Report message | ✅ |
-| Theming | ❌ |
+| Theming | ✅ |
| Settings -> Review the App | ❌ |
| Settings -> Default Browser | ❌ |
| Admin panel | ✅ |
-| Reply message from notification | ❌ |
+| Reply message from notification | ✅ |
| Unread counter banner on message list | ✅ |
| E2E Encryption | ❌ |
| Join a Protected Room | ❌ |
@@ -106,7 +118,7 @@ Readme will guide you on how to config.
| Accessibility (Medium) | ❌ |
| Accessibility (Advanced) | ❌ |
| Authentication via Meteor | ❌ |
-| Authentication via Wordpress | ❌ |
+| Authentication via Wordpress | ✅ |
| Authentication via Custom OAuth | ✅ |
| Add user to the room | ✅ |
| Send message | ✅ |
diff --git a/__mocks__/react-native-splash-screen.js b/__mocks__/react-native-bootsplash.js
similarity index 100%
rename from __mocks__/react-native-splash-screen.js
rename to __mocks__/react-native-bootsplash.js
diff --git a/__tests__/__snapshots__/Storyshots.test.js.snap b/__tests__/__snapshots__/Storyshots.test.js.snap
index b6bc2022d..b7e07750c 100644
--- a/__tests__/__snapshots__/Storyshots.test.js.snap
+++ b/__tests__/__snapshots__/Storyshots.test.js.snap
@@ -213,7 +213,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -455,7 +455,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -697,7 +697,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -917,7 +917,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -1011,7 +1011,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -1105,7 +1105,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -1325,7 +1325,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -1441,7 +1441,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -1701,7 +1701,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -1939,7 +1939,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -2181,7 +2181,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -2452,7 +2452,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -2704,7 +2704,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -2745,7 +2745,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -3005,7 +3005,7 @@ exports[`Storyshots Message list 1`] = `
@@ -3025,7 +3025,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -3083,7 +3083,7 @@ exports[`Storyshots Message list 1`] = `
@@ -3103,7 +3103,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -3165,7 +3165,7 @@ exports[`Storyshots Message list 1`] = `
@@ -3185,7 +3185,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -3453,7 +3453,7 @@ exports[`Storyshots Message list 1`] = `
@@ -3473,7 +3473,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -3533,7 +3533,7 @@ exports[`Storyshots Message list 1`] = `
@@ -3553,7 +3553,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -3817,7 +3817,7 @@ exports[`Storyshots Message list 1`] = `
@@ -3837,7 +3837,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -3953,7 +3953,7 @@ exports[`Storyshots Message list 1`] = `
@@ -3973,7 +3973,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -4219,7 +4219,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -4461,7 +4461,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -4703,7 +4703,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -5108,7 +5108,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -5355,7 +5355,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -5602,7 +5602,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -5976,7 +5976,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -6224,7 +6224,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -6503,7 +6503,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -6803,7 +6803,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -7045,7 +7045,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -7627,7 +7627,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -8650,7 +8650,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -8870,7 +8870,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -9090,7 +9090,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -9310,7 +9310,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -9552,7 +9552,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -9834,7 +9834,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -9973,7 +9973,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -10193,7 +10193,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -10458,7 +10458,7 @@ exports[`Storyshots Message list 1`] = `
Array [
undefined,
Object {
- "color": "#0d0e12",
+ "color": "#2f343d",
},
]
}
@@ -10718,46 +10718,66 @@ exports[`Storyshots Message list 1`] = `
style={
Array [
Object {
- "overflow": "hidden",
+ "borderRadius": 4,
+ "borderWidth": 1,
+ "minHeight": 200,
+ "width": "100%",
+ },
+ Object {
+ "borderColor": "#e1e5e8",
},
- Array [
- Object {
- "borderRadius": 4,
- "borderWidth": 1,
- "minHeight": 200,
- "width": "100%",
- },
- Object {
- "borderColor": "#e1e5e8",
- },
- ],
]
}
>
-
+ >
+
+
-
+ >
+
+
- Thread with emoji :) 😂
+ Thread with emoji🙂 😂
+
+
+
+
+
+
+ android:windowSoftInputMode="adjustResize"
+ android:exported="true">
-
-
@@ -40,6 +47,15 @@
+
+
+
> notificationMessages = new HashMap>();
+ 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());
+ }
+ notificationMessages.get(notId).add(message);
+
+ super.postNotification(notId != null ? Integer.parseInt(notId) : 1);
+
+ notifyReceivedToJS();
+ }
+
+ @Override
+ public void onOpened() {
+ Bundle bundle = mNotificationProps.asBundle();
+ final String notId = bundle.getString("notId");
+ notificationMessages.remove(notId);
+ digestNotification();
}
@Override
protected Notification.Builder getNotificationBuilder(PendingIntent intent) {
- final Resources res = mContext.getResources();
- String packageName = mContext.getPackageName();
+ final Notification.Builder notification = new Notification.Builder(mContext);
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 message = bundle.getString("message");
+ String notId = bundle.getString("notId");
- String CHANNEL_ID = "rocketchatrn_channel_01";
- String CHANNEL_NAME = "All";
-
- final Notification.Builder notification = new Notification.Builder(mContext)
- .setSmallIcon(smallIconResId)
+ notification
.setContentIntent(intent)
.setContentTitle(title)
.setContentText(message)
- .setStyle(new Notification.BigTextStyle().bigText(message))
.setPriority(Notification.PRIORITY_HIGH)
.setDefaults(Notification.DEFAULT_ALL)
.setAutoCancel(true);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- notification.setColor(mContext.getColor(R.color.notification_text));
- }
+ Integer notificationId = notId != null ? Integer.parseInt(notId) : 1;
+ 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) {
+ String CHANNEL_ID = "rocketchatrn_channel_01";
+ String CHANNEL_NAME = "All";
+
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT);
@@ -63,10 +146,65 @@ public class CustomPushNotification extends PushNotification {
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 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);
+ }
+
}
diff --git a/android/app/src/main/java/chat/rocket/reactnative/DismissNotification.java b/android/app/src/main/java/chat/rocket/reactnative/DismissNotification.java
new file mode 100644
index 000000000..32524a35f
--- /dev/null
+++ b/android/app/src/main/java/chat/rocket/reactnative/DismissNotification.java
@@ -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);
+ }
+}
diff --git a/android/app/src/main/java/chat/rocket/reactnative/Ejson.java b/android/app/src/main/java/chat/rocket/reactnative/Ejson.java
new file mode 100644
index 000000000..591fda93a
--- /dev/null
+++ b/android/app/src/main/java/chat/rocket/reactnative/Ejson.java
@@ -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 == null || !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;
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/java/chat/rocket/reactnative/MainActivity.java b/android/app/src/main/java/chat/rocket/reactnative/MainActivity.java
index 078f14142..f408e11cd 100644
--- a/android/app/src/main/java/chat/rocket/reactnative/MainActivity.java
+++ b/android/app/src/main/java/chat/rocket/reactnative/MainActivity.java
@@ -5,16 +5,16 @@ import com.facebook.react.ReactRootView;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
import android.os.Bundle;
import com.facebook.react.ReactFragmentActivity;
-import org.devio.rn.splashscreen.SplashScreen;
import android.content.Intent;
import android.content.res.Configuration;
+import com.zoontek.rnbootsplash.RNBootSplash;
public class MainActivity extends ReactFragmentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
- SplashScreen.show(this);
- super.onCreate(null);
+ super.onCreate(savedInstanceState);
+ RNBootSplash.show(R.drawable.launch_screen, MainActivity.this);
}
/**
diff --git a/android/app/src/main/java/chat/rocket/reactnative/ReplyBroadcast.java b/android/app/src/main/java/chat/rocket/reactnative/ReplyBroadcast.java
new file mode 100644
index 000000000..81c62aeb0
--- /dev/null
+++ b/android/app/src/main/java/chat/rocket/reactnative/ReplyBroadcast.java
@@ -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;
+ }
+}
diff --git a/android/app/src/main/res/drawable-hdpi/icon_wordpress.png b/android/app/src/main/res/drawable-hdpi/icon_wordpress.png
new file mode 100644
index 000000000..4dbc11e67
Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/icon_wordpress.png differ
diff --git a/android/app/src/main/res/drawable-hdpi/launch_screen.png b/android/app/src/main/res/drawable-hdpi/launch_screen.png
deleted file mode 100644
index bc960ac72..000000000
Binary files a/android/app/src/main/res/drawable-hdpi/launch_screen.png and /dev/null differ
diff --git a/android/app/src/main/res/drawable-mdpi/icon_wordpress.png b/android/app/src/main/res/drawable-mdpi/icon_wordpress.png
new file mode 100644
index 000000000..cc24baf40
Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/icon_wordpress.png differ
diff --git a/android/app/src/main/res/drawable-mdpi/launch_screen.png b/android/app/src/main/res/drawable-mdpi/launch_screen.png
deleted file mode 100644
index 1c11678c8..000000000
Binary files a/android/app/src/main/res/drawable-mdpi/launch_screen.png and /dev/null differ
diff --git a/android/app/src/main/res/drawable-xhdpi/icon_wordpress.png b/android/app/src/main/res/drawable-xhdpi/icon_wordpress.png
new file mode 100644
index 000000000..e3457517b
Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/icon_wordpress.png differ
diff --git a/android/app/src/main/res/drawable-xhdpi/launch_screen.png b/android/app/src/main/res/drawable-xhdpi/launch_screen.png
deleted file mode 100644
index 38cbcb666..000000000
Binary files a/android/app/src/main/res/drawable-xhdpi/launch_screen.png and /dev/null differ
diff --git a/android/app/src/main/res/drawable-xxhdpi/icon_wordpress.png b/android/app/src/main/res/drawable-xxhdpi/icon_wordpress.png
new file mode 100644
index 000000000..82b896631
Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/icon_wordpress.png differ
diff --git a/android/app/src/main/res/drawable-xxhdpi/launch_screen.png b/android/app/src/main/res/drawable-xxhdpi/launch_screen.png
deleted file mode 100644
index 3b36d489c..000000000
Binary files a/android/app/src/main/res/drawable-xxhdpi/launch_screen.png and /dev/null differ
diff --git a/android/app/src/main/res/drawable-xxxhdpi/icon_wordpress.png b/android/app/src/main/res/drawable-xxxhdpi/icon_wordpress.png
new file mode 100644
index 000000000..6d7f42995
Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/icon_wordpress.png differ
diff --git a/android/app/src/main/res/drawable-xxxhdpi/launch_screen.png b/android/app/src/main/res/drawable-xxxhdpi/launch_screen.png
deleted file mode 100644
index 040a3ec38..000000000
Binary files a/android/app/src/main/res/drawable-xxxhdpi/launch_screen.png and /dev/null differ
diff --git a/android/app/src/main/res/drawable-xxxhdpi/splash.png b/android/app/src/main/res/drawable-xxxhdpi/splash.png
new file mode 100644
index 000000000..ad69ba207
Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable/launch_screen.xml b/android/app/src/main/res/drawable/launch_screen.xml
new file mode 100644
index 000000000..a85f4c9fe
--- /dev/null
+++ b/android/app/src/main/res/drawable/launch_screen.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+ -
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/drawable/launch_screen_bitmap.xml b/android/app/src/main/res/drawable/launch_screen_bitmap.xml
deleted file mode 100644
index de474fb1f..000000000
--- a/android/app/src/main/res/drawable/launch_screen_bitmap.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
- -
-
-
-
diff --git a/android/app/src/main/res/layout/launch_screen.xml b/android/app/src/main/res/layout/launch_screen.xml
deleted file mode 100644
index 97c4ac273..000000000
--- a/android/app/src/main/res/layout/launch_screen.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/android/app/src/main/res/values-night/colors.xml b/android/app/src/main/res/values-night/colors.xml
new file mode 100644
index 000000000..1e24e3159
--- /dev/null
+++ b/android/app/src/main/res/values-night/colors.xml
@@ -0,0 +1,4 @@
+
+
+ - #000000
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml
index effdcbed1..8ba17e337 100644
--- a/android/app/src/main/res/values/colors.xml
+++ b/android/app/src/main/res/values/colors.xml
@@ -1,6 +1,6 @@
#660B0B0B
- - #FFFFFF
+ - #eeeff1
- #CC3333
\ No newline at end of file
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
index fdfc9173c..50861d8e0 100644
--- a/android/app/src/main/res/values/styles.xml
+++ b/android/app/src/main/res/values/styles.xml
@@ -1,7 +1,8 @@
+
+
diff --git a/android/build.gradle b/android/build.gradle
index 62a22d81d..aaef31e10 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -55,16 +55,16 @@ allprojects {
}
}
-// subprojects { subproject ->
-// afterEvaluate {
-// if ((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) {
-// android {
-// compileSdkVersion 28
-// buildToolsVersion "28.0.3"
-// defaultConfig {
-// targetSdkVersion 28
-// }
-// }
-// }
-// }
-// }
+subprojects { subproject ->
+ afterEvaluate {
+ if ((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) {
+ android {
+ compileSdkVersion 28
+ buildToolsVersion "28.0.3"
+ defaultConfig {
+ targetSdkVersion 28
+ }
+ }
+ }
+ }
+}
diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js
index ced5b25ef..15718b171 100644
--- a/app/actions/actionsTypes.js
+++ b/app/actions/actionsTypes.js
@@ -54,3 +54,11 @@ export const TOGGLE_CRASH_REPORT = 'TOGGLE_CRASH_REPORT';
export const SET_CUSTOM_EMOJIS = 'SET_CUSTOM_EMOJIS';
export const SET_ACTIVE_USERS = 'SET_ACTIVE_USERS';
export const USERS_TYPING = createRequestTypes('USERS_TYPING', ['ADD', 'REMOVE', 'CLEAR']);
+export const INVITE_LINKS = createRequestTypes('INVITE_LINKS', [
+ 'SET_TOKEN',
+ 'SET_PARAMS',
+ 'SET_INVITE',
+ 'CREATE',
+ 'CLEAR',
+ ...defaultTypes
+]);
diff --git a/app/actions/inviteLinks.js b/app/actions/inviteLinks.js
new file mode 100644
index 000000000..b456603cf
--- /dev/null
+++ b/app/actions/inviteLinks.js
@@ -0,0 +1,55 @@
+import * as types from './actionsTypes';
+
+export function inviteLinksSetToken(token) {
+ return {
+ type: types.INVITE_LINKS.SET_TOKEN,
+ token
+ };
+}
+
+export function inviteLinksRequest(token) {
+ return {
+ type: types.INVITE_LINKS.REQUEST,
+ token
+ };
+}
+
+export function inviteLinksSuccess() {
+ return {
+ type: types.INVITE_LINKS.SUCCESS
+ };
+}
+
+export function inviteLinksFailure() {
+ return {
+ type: types.INVITE_LINKS.FAILURE
+ };
+}
+
+export function inviteLinksClear() {
+ return {
+ type: types.INVITE_LINKS.CLEAR
+ };
+}
+
+
+export function inviteLinksCreate(rid) {
+ return {
+ type: types.INVITE_LINKS.CREATE,
+ rid
+ };
+}
+
+export function inviteLinksSetParams(params) {
+ return {
+ type: types.INVITE_LINKS.SET_PARAMS,
+ params
+ };
+}
+
+export function inviteLinksSetInvite(invite) {
+ return {
+ type: types.INVITE_LINKS.SET_INVITE,
+ invite
+ };
+}
diff --git a/app/constants/settings.js b/app/constants/settings.js
index d4697a709..74c6234f6 100644
--- a/app/constants/settings.js
+++ b/app/constants/settings.js
@@ -59,6 +59,9 @@ export default {
Message_TimeFormat: {
type: 'valueAsString'
},
+ Message_TimeAndDateFormat: {
+ type: 'valueAsString'
+ },
Site_Name: {
type: 'valueAsString'
},
diff --git a/app/containers/EmojiPicker/EmojiCategory.js b/app/containers/EmojiPicker/EmojiCategory.js
index e7a71f513..22382f1e3 100644
--- a/app/containers/EmojiPicker/EmojiCategory.js
+++ b/app/containers/EmojiPicker/EmojiCategory.js
@@ -1,9 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Text, TouchableOpacity, FlatList } from 'react-native';
-import { shortnameToUnicode } from 'emoji-toolkit';
import { responsive } from 'react-native-responsive-ui';
+import shortnameToUnicode from '../../utils/shortnameToUnicode';
import styles from './styles';
import CustomEmoji from './CustomEmoji';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
diff --git a/app/containers/EmojiPicker/index.js b/app/containers/EmojiPicker/index.js
index 817b0f602..65a973715 100644
--- a/app/containers/EmojiPicker/index.js
+++ b/app/containers/EmojiPicker/index.js
@@ -2,7 +2,6 @@ import React, { Component } from 'react';
import { View } from 'react-native';
import PropTypes from 'prop-types';
import ScrollableTabView from 'react-native-scrollable-tab-view';
-import { shortnameToUnicode } from 'emoji-toolkit';
import equal from 'deep-equal';
import { connect } from 'react-redux';
import orderBy from 'lodash/orderBy';
@@ -15,6 +14,7 @@ import categories from './categories';
import database from '../../lib/database';
import { emojisByCategory } from '../../emojis';
import protectedFunction from '../../lib/methods/helpers/protectedFunction';
+import shortnameToUnicode from '../../utils/shortnameToUnicode';
import log from '../../utils/log';
import { themes } from '../../constants/colors';
import { withTheme } from '../../theme';
diff --git a/app/containers/FileModal.js b/app/containers/FileModal.js
deleted file mode 100644
index 2943afa4f..000000000
--- a/app/containers/FileModal.js
+++ /dev/null
@@ -1,132 +0,0 @@
-import React, { useState } from 'react';
-import {
- View, Text, TouchableWithoutFeedback, StyleSheet, SafeAreaView
-} from 'react-native';
-import FastImage from 'react-native-fast-image';
-import PropTypes from 'prop-types';
-import Modal from 'react-native-modal';
-import ImageViewer from 'react-native-image-zoom-viewer';
-import { Video } from 'expo-av';
-
-import sharedStyles from '../views/Styles';
-import { formatAttachmentUrl } from '../lib/utils';
-import ActivityIndicator from './ActivityIndicator';
-import { themes } from '../constants/colors';
-import { withTheme } from '../theme';
-
-const styles = StyleSheet.create({
- safeArea: {
- flex: 1
- },
- modal: {
- margin: 0
- },
- titleContainer: {
- width: '100%',
- alignItems: 'center',
- marginVertical: 10
- },
- title: {
- textAlign: 'center',
- fontSize: 16,
- ...sharedStyles.textSemibold
- },
- description: {
- textAlign: 'center',
- fontSize: 14,
- ...sharedStyles.textMedium
- },
- video: {
- flex: 1
- }
-});
-
-const ModalContent = React.memo(({
- attachment, onClose, user, baseUrl, theme
-}) => {
- if (attachment && attachment.image_url) {
- const url = formatAttachmentUrl(attachment.image_url, user.id, user.token, baseUrl);
- return (
-
-
-
- {attachment.title}
- {attachment.description ? {attachment.description} : null}
-
-
- null}
- renderImage={props => }
- loadingRender={() => }
- />
-
- );
- }
- if (attachment && attachment.video_url) {
- const [loading, setLoading] = useState(true);
- const uri = formatAttachmentUrl(attachment.video_url, user.id, user.token, baseUrl);
- return (
- <>
-