Skip to content

Commit 43bc56f

Browse files
Merge branch 'RocketChat:develop' into develop
2 parents aa059ec + f7aef7f commit 43bc56f

File tree

17 files changed

+747
-396
lines changed

17 files changed

+747
-396
lines changed

android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java

Lines changed: 46 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@
1818

1919
import androidx.annotation.Nullable;
2020

21-
import com.bumptech.glide.Glide;
22-
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
23-
import com.bumptech.glide.request.RequestOptions;
2421
import com.facebook.react.bridge.ReactApplicationContext;
2522
import com.google.gson.Gson;
2623

@@ -30,9 +27,6 @@
3027
import java.util.List;
3128
import java.util.Map;
3229
import java.util.concurrent.ConcurrentHashMap;
33-
import java.util.concurrent.ExecutionException;
34-
import java.util.concurrent.TimeUnit;
35-
import java.util.concurrent.TimeoutException;
3630

3731
import chat.rocket.reactnative.BuildConfig;
3832
import chat.rocket.reactnative.MainActivity;
@@ -61,7 +55,7 @@ public class CustomPushNotification {
6155

6256
// Instance fields
6357
private final Context mContext;
64-
private Bundle mBundle;
58+
private volatile Bundle mBundle;
6559
private final NotificationManager notificationManager;
6660

6761
public CustomPushNotification(Context context, Bundle bundle) {
@@ -300,9 +294,6 @@ private void showNotification(Bundle bundle, Ejson ejson, String notId) {
300294
bundle.putString("senderId", hasSender ? ejson.sender._id : "1");
301295

302296
String avatarUri = ejson != null ? ejson.getAvatarUri() : null;
303-
if (ENABLE_VERBOSE_LOGS) {
304-
Log.d(TAG, "[showNotification] avatarUri=" + (avatarUri != null ? "[present]" : "[null]"));
305-
}
306297
bundle.putString("avatarUri", avatarUri);
307298

308299
// Handle special notification types
@@ -379,10 +370,27 @@ private Notification.Builder buildNotification(int notificationId) {
379370
Boolean notificationLoaded = mBundle.getBoolean("notificationLoaded", false);
380371
Ejson ejson = safeFromJson(mBundle.getString("ejson", "{}"), Ejson.class);
381372

373+
// Determine the correct title based on notification type
374+
String notificationTitle = title;
375+
if (ejson != null && ejson.type != null) {
376+
if ("p".equals(ejson.type) || "c".equals(ejson.type)) {
377+
// For groups/channels, use room name if available, otherwise fall back to title
378+
notificationTitle = (ejson.name != null && !ejson.name.isEmpty()) ? ejson.name : title;
379+
} else if ("d".equals(ejson.type)) {
380+
// For direct messages, use title (sender name from server)
381+
notificationTitle = title;
382+
} else if ("l".equals(ejson.type)) {
383+
// For omnichannel, use sender name if available, otherwise fall back to title
384+
notificationTitle = (ejson.sender != null && ejson.sender.name != null && !ejson.sender.name.isEmpty())
385+
? ejson.sender.name : title;
386+
}
387+
}
388+
382389
if (ENABLE_VERBOSE_LOGS) {
383390
Log.d(TAG, "[buildNotification] notId=" + notId);
384391
Log.d(TAG, "[buildNotification] notificationLoaded=" + notificationLoaded);
385392
Log.d(TAG, "[buildNotification] title=" + (title != null ? "[present]" : "[null]"));
393+
Log.d(TAG, "[buildNotification] notificationTitle=" + (notificationTitle != null ? "[present]" : "[null]"));
386394
Log.d(TAG, "[buildNotification] message length=" + (message != null ? message.length() : 0));
387395
}
388396

@@ -406,7 +414,7 @@ private Notification.Builder buildNotification(int notificationId) {
406414
}
407415

408416
notification
409-
.setContentTitle(title)
417+
.setContentTitle(notificationTitle)
410418
.setContentText(message)
411419
.setContentIntent(pendingIntent)
412420
.setPriority(Notification.PRIORITY_HIGH)
@@ -455,37 +463,7 @@ private void cancelPreviousFallbackNotifications(Ejson ejson) {
455463
}
456464

457465
private Bitmap getAvatar(String uri) {
458-
if (uri == null || uri.isEmpty()) {
459-
if (ENABLE_VERBOSE_LOGS) {
460-
Log.w(TAG, "getAvatar called with null/empty URI");
461-
}
462-
return largeIcon();
463-
}
464-
465-
if (ENABLE_VERBOSE_LOGS) {
466-
String sanitizedUri = uri;
467-
int queryStart = uri.indexOf("?");
468-
if (queryStart != -1) {
469-
sanitizedUri = uri.substring(0, queryStart) + "?[auth_params]";
470-
}
471-
Log.d(TAG, "Fetching avatar from: " + sanitizedUri);
472-
}
473-
474-
try {
475-
// Use a 3-second timeout to avoid blocking the FCM service for too long
476-
// FCM has a 10-second limit, so we need to fail fast and use fallback icon
477-
Bitmap avatar = Glide.with(mContext)
478-
.asBitmap()
479-
.apply(RequestOptions.bitmapTransform(new RoundedCorners(10)))
480-
.load(uri)
481-
.submit(100, 100)
482-
.get(3, TimeUnit.SECONDS);
483-
484-
return avatar != null ? avatar : largeIcon();
485-
} catch (final ExecutionException | InterruptedException | TimeoutException e) {
486-
Log.e(TAG, "Failed to fetch avatar: " + e.getMessage(), e);
487-
return largeIcon();
488-
}
466+
return NotificationHelper.fetchAvatarBitmap(mContext, uri, largeIcon());
489467
}
490468

491469
private Bitmap largeIcon() {
@@ -506,7 +484,10 @@ private void notificationIcons(Notification.Builder notification, Bundle bundle)
506484
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
507485
String avatarUri = ejson != null ? ejson.getAvatarUri() : null;
508486
if (avatarUri != null) {
509-
notification.setLargeIcon(getAvatar(avatarUri));
487+
Bitmap avatar = getAvatar(avatarUri);
488+
if (avatar != null) {
489+
notification.setLargeIcon(avatar);
490+
}
510491
}
511492
}
512493
}
@@ -517,8 +498,11 @@ private String extractMessage(String message, Ejson ejson) {
517498
}
518499
if (ejson != null && ejson.type != null && !ejson.type.equals("d")) {
519500
int pos = message.indexOf(":");
520-
int start = pos == -1 ? 0 : pos + 2;
521-
return message.substring(start);
501+
if (pos == -1) {
502+
return message;
503+
}
504+
int start = pos + 2;
505+
return start <= message.length() ? message.substring(start) : "";
522506
}
523507
return message;
524508
}
@@ -559,7 +543,23 @@ private void notificationStyle(Notification.Builder notification, int notId, Bun
559543
}
560544

561545
String title = bundle.getString("title");
562-
messageStyle.setConversationTitle(title);
546+
// Determine the correct conversation title based on notification type
547+
Ejson bundleEjson = safeFromJson(bundle.getString("ejson", "{}"), Ejson.class);
548+
String conversationTitle = title;
549+
if (bundleEjson != null && bundleEjson.type != null) {
550+
if ("p".equals(bundleEjson.type) || "c".equals(bundleEjson.type)) {
551+
// For groups/channels, use room name if available, otherwise fall back to title
552+
conversationTitle = (bundleEjson.name != null && !bundleEjson.name.isEmpty()) ? bundleEjson.name : title;
553+
} else if ("d".equals(bundleEjson.type)) {
554+
// For direct messages, use title (sender name from server)
555+
conversationTitle = title;
556+
} else if ("l".equals(bundleEjson.type)) {
557+
// For omnichannel, use sender name if available, otherwise fall back to title
558+
conversationTitle = (bundleEjson.sender != null && bundleEjson.sender.name != null && !bundleEjson.sender.name.isEmpty())
559+
? bundleEjson.sender.name : title;
560+
}
561+
}
562+
messageStyle.setConversationTitle(conversationTitle);
563563

564564
if (bundles != null) {
565565
for (Bundle data : bundles) {

android/app/src/main/java/chat/rocket/reactnative/notification/Ejson.java

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import com.tencent.mmkv.MMKV;
77

88
import java.math.BigInteger;
9+
import java.net.URLEncoder;
10+
import java.io.UnsupportedEncodingException;
911

1012
import chat.rocket.reactnative.BuildConfig;
1113
import chat.rocket.reactnative.storage.MMKVKeyManager;
@@ -40,6 +42,7 @@ public class Ejson {
4042
String notificationType;
4143
String messageType;
4244
String senderName;
45+
String name; // Room name for groups/channels
4346
String msg;
4447
Integer status; // For video conf: 0=incoming, 4=cancelled
4548

@@ -57,33 +60,79 @@ private MMKV getMMKV() {
5760
return MMKV.mmkvWithID("default", MMKV.SINGLE_PROCESS_MODE);
5861
}
5962

60-
public String getAvatarUri() {
61-
if (sender == null || sender.username == null || sender.username.isEmpty()) {
62-
Log.w(TAG, "Cannot generate avatar URI: sender or username is null");
63-
return null;
64-
}
65-
63+
/**
64+
* Helper method to build avatar URI from avatar path.
65+
* Validates server URL and credentials, then constructs the full URI.
66+
*/
67+
private String buildAvatarUri(String avatarPath, String errorContext) {
6668
String server = serverURL();
6769
if (server == null || server.isEmpty()) {
68-
Log.w(TAG, "Cannot generate avatar URI: serverURL is null");
70+
Log.w(TAG, "Cannot generate " + errorContext + " avatar URI: serverURL is null");
6971
return null;
7072
}
7173

7274
String userToken = token();
7375
String uid = userId();
7476

7577
if (userToken.isEmpty() || uid.isEmpty()) {
76-
Log.w(TAG, "Cannot generate avatar URI: missing auth credentials (token=" + !userToken.isEmpty() + ", uid=" + !uid.isEmpty() + ")");
78+
Log.w(TAG, "Cannot generate " + errorContext + " avatar URI: missing auth credentials");
7779
return null;
7880
}
7981

80-
String uri = server + "/avatar/" + sender.username + "?format=png&size=100&rc_token=" + userToken + "&rc_uid=" + uid;
82+
return server + avatarPath + "?format=png&size=100&rc_token=" + userToken + "&rc_uid=" + uid;
83+
}
84+
85+
public String getAvatarUri() {
86+
String avatarPath;
87+
88+
// For DMs, show sender's avatar; for groups/channels, show room avatar
89+
if ("d".equals(type)) {
90+
// Direct message: use sender's avatar
91+
if (sender == null || sender.username == null || sender.username.isEmpty()) {
92+
Log.w(TAG, "Cannot generate avatar URI: sender or username is null");
93+
return null;
94+
}
95+
try {
96+
avatarPath = "/avatar/" + URLEncoder.encode(sender.username, "UTF-8");
97+
} catch (UnsupportedEncodingException e) {
98+
Log.e(TAG, "Failed to encode username", e);
99+
return null;
100+
}
101+
} else {
102+
// Group/Channel/Livechat: use room avatar
103+
if (rid == null || rid.isEmpty()) {
104+
Log.w(TAG, "Cannot generate avatar URI: rid is null for non-DM");
105+
return null;
106+
}
107+
try {
108+
avatarPath = "/avatar/room/" + URLEncoder.encode(rid, "UTF-8");
109+
} catch (UnsupportedEncodingException e) {
110+
Log.e(TAG, "Failed to encode rid", e);
111+
return null;
112+
}
113+
}
81114

82-
if (BuildConfig.DEBUG) {
83-
Log.d(TAG, "Generated avatar URI for user: " + sender.username);
115+
return buildAvatarUri(avatarPath, "");
116+
}
117+
118+
/**
119+
* Generates avatar URI for video conference caller.
120+
* Returns null if caller username is not available (username is required for avatar endpoint).
121+
*/
122+
public String getCallerAvatarUri() {
123+
// Check if caller exists and has username (required - /avatar/{userId} endpoint doesn't exist)
124+
if (caller == null || caller.username == null || caller.username.isEmpty()) {
125+
Log.w(TAG, "Cannot generate caller avatar URI: caller or username is null");
126+
return null;
84127
}
85128

86-
return uri;
129+
try {
130+
String avatarPath = "/avatar/" + URLEncoder.encode(caller.username, "UTF-8");
131+
return buildAvatarUri(avatarPath, "caller");
132+
} catch (UnsupportedEncodingException e) {
133+
Log.e(TAG, "Failed to encode caller username", e);
134+
return null;
135+
}
87136
}
88137

89138
public String token() {
@@ -194,6 +243,7 @@ static class Sender {
194243
static class Caller {
195244
String _id;
196245
String name;
246+
String username;
197247
}
198248

199249
static class Content {

0 commit comments

Comments
 (0)