Merge branch 'AttachmentsToChat'
This commit is contained in:
@@ -16,7 +16,10 @@ import com.example.petstoremobile.R;
|
|||||||
import com.example.petstoremobile.databinding.ItemMessageReceivedBinding;
|
import com.example.petstoremobile.databinding.ItemMessageReceivedBinding;
|
||||||
import com.example.petstoremobile.databinding.ItemMessageSentBinding;
|
import com.example.petstoremobile.databinding.ItemMessageSentBinding;
|
||||||
import com.example.petstoremobile.models.Message;
|
import com.example.petstoremobile.models.Message;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||||
|
|
||||||
@@ -29,6 +32,7 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
|
|
||||||
private final List<Message> messages;
|
private final List<Message> messages;
|
||||||
private Long currentUserId;
|
private Long currentUserId;
|
||||||
|
private Long staffId;
|
||||||
private String token;
|
private String token;
|
||||||
private String baseUrl;
|
private String baseUrl;
|
||||||
private OnAttachmentClickListener attachmentClickListener;
|
private OnAttachmentClickListener attachmentClickListener;
|
||||||
@@ -50,6 +54,11 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setStaffId(Long id) {
|
||||||
|
this.staffId = id;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
public void setToken(String token) {
|
public void setToken(String token) {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
}
|
}
|
||||||
@@ -87,7 +96,7 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||||
Message m = messages.get(position);
|
Message m = messages.get(position);
|
||||||
if (holder instanceof SentHolder) ((SentHolder) holder).bind(m, token, baseUrl, attachmentClickListener);
|
if (holder instanceof SentHolder) ((SentHolder) holder).bind(m, token, baseUrl, attachmentClickListener);
|
||||||
if (holder instanceof ReceivedHolder) ((ReceivedHolder) holder).bind(m, token, baseUrl, attachmentClickListener);
|
if (holder instanceof ReceivedHolder) ((ReceivedHolder) holder).bind(m, token, baseUrl, attachmentClickListener, staffId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public int getItemCount() { return messages.size(); }
|
@Override public int getItemCount() { return messages.size(); }
|
||||||
@@ -99,7 +108,9 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
}
|
}
|
||||||
void bind(Message m, String token, String baseUrl, OnAttachmentClickListener listener) {
|
void bind(Message m, String token, String baseUrl, OnAttachmentClickListener listener) {
|
||||||
// Check for Text
|
binding.tvSenderName.setText("You");
|
||||||
|
binding.tvTimestamp.setText(formatTimestamp(m.getTimestamp()));
|
||||||
|
|
||||||
if (m.getContent() != null && !m.getContent().isEmpty()) {
|
if (m.getContent() != null && !m.getContent().isEmpty()) {
|
||||||
binding.tvMessageContent.setVisibility(View.VISIBLE);
|
binding.tvMessageContent.setVisibility(View.VISIBLE);
|
||||||
binding.tvMessageContent.setText(m.getContent());
|
binding.tvMessageContent.setText(m.getContent());
|
||||||
@@ -107,7 +118,6 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
binding.tvMessageContent.setVisibility(View.GONE);
|
binding.tvMessageContent.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for Attachment
|
|
||||||
displayAttachment(m, binding.ivAttachment, binding.tvAttachmentName, token, baseUrl);
|
displayAttachment(m, binding.ivAttachment, binding.tvAttachmentName, token, baseUrl);
|
||||||
|
|
||||||
View.OnClickListener click = v -> {
|
View.OnClickListener click = v -> {
|
||||||
@@ -124,8 +134,10 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
}
|
}
|
||||||
void bind(Message m, String token, String baseUrl, OnAttachmentClickListener listener) {
|
void bind(Message m, String token, String baseUrl, OnAttachmentClickListener listener, Long staffId) {
|
||||||
// Check for Text
|
binding.tvSenderName.setText(resolveSenderName(m, staffId));
|
||||||
|
binding.tvTimestamp.setText(formatTimestamp(m.getTimestamp()));
|
||||||
|
|
||||||
if (m.getContent() != null && !m.getContent().isEmpty()) {
|
if (m.getContent() != null && !m.getContent().isEmpty()) {
|
||||||
binding.tvMessageContent.setVisibility(View.VISIBLE);
|
binding.tvMessageContent.setVisibility(View.VISIBLE);
|
||||||
binding.tvMessageContent.setText(m.getContent());
|
binding.tvMessageContent.setText(m.getContent());
|
||||||
@@ -133,7 +145,6 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
binding.tvMessageContent.setVisibility(View.GONE);
|
binding.tvMessageContent.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for Attachment
|
|
||||||
displayAttachment(m, binding.ivAttachment, binding.tvAttachmentName, token, baseUrl);
|
displayAttachment(m, binding.ivAttachment, binding.tvAttachmentName, token, baseUrl);
|
||||||
|
|
||||||
View.OnClickListener click = v -> {
|
View.OnClickListener click = v -> {
|
||||||
@@ -144,7 +155,29 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper function to display the attachment to the chat bubble
|
private static String resolveSenderName(Message m, Long staffId) {
|
||||||
|
if ("BOT".equalsIgnoreCase(m.getSenderRole())) {
|
||||||
|
return (m.getSenderDisplayName() != null && !m.getSenderDisplayName().isEmpty())
|
||||||
|
? m.getSenderDisplayName() : "AI Bot";
|
||||||
|
}
|
||||||
|
if (staffId != null && staffId.equals(m.getSenderId())) {
|
||||||
|
return "Staff";
|
||||||
|
}
|
||||||
|
return "Customer";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatTimestamp(String timestamp) {
|
||||||
|
if (timestamp == null || timestamp.isEmpty()) return "";
|
||||||
|
try {
|
||||||
|
String normalized = timestamp.length() > 19 ? timestamp.substring(0, 19) : timestamp;
|
||||||
|
SimpleDateFormat input = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault());
|
||||||
|
Date date = input.parse(normalized);
|
||||||
|
return new SimpleDateFormat("MMM d, HH:mm", Locale.getDefault()).format(date);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void displayAttachment(Message m, ImageView iv, TextView tvName, String token, String baseUrl) {
|
private static void displayAttachment(Message m, ImageView iv, TextView tvName, String token, String baseUrl) {
|
||||||
// Check if there's an attachment by looking at name or mime type
|
// Check if there's an attachment by looking at name or mime type
|
||||||
if (m.getAttachmentName() != null || m.getAttachmentMimeType() != null) {
|
if (m.getAttachmentName() != null || m.getAttachmentMimeType() != null) {
|
||||||
|
|||||||
@@ -34,6 +34,12 @@ public class MessageDTO {
|
|||||||
@SerializedName("attachmentSizeBytes")
|
@SerializedName("attachmentSizeBytes")
|
||||||
private Long attachmentSizeBytes;
|
private Long attachmentSizeBytes;
|
||||||
|
|
||||||
|
@SerializedName("senderRole")
|
||||||
|
private String senderRole;
|
||||||
|
|
||||||
|
@SerializedName("senderDisplayName")
|
||||||
|
private String senderDisplayName;
|
||||||
|
|
||||||
public MessageDTO() {}
|
public MessageDTO() {}
|
||||||
|
|
||||||
public Long getId() { return id; }
|
public Long getId() { return id; }
|
||||||
@@ -65,4 +71,10 @@ public class MessageDTO {
|
|||||||
|
|
||||||
public Long getAttachmentSizeBytes() { return attachmentSizeBytes; }
|
public Long getAttachmentSizeBytes() { return attachmentSizeBytes; }
|
||||||
public void setAttachmentSizeBytes(Long attachmentSizeBytes) { this.attachmentSizeBytes = attachmentSizeBytes; }
|
public void setAttachmentSizeBytes(Long attachmentSizeBytes) { this.attachmentSizeBytes = attachmentSizeBytes; }
|
||||||
|
|
||||||
|
public String getSenderRole() { return senderRole; }
|
||||||
|
public void setSenderRole(String senderRole) { this.senderRole = senderRole; }
|
||||||
|
|
||||||
|
public String getSenderDisplayName() { return senderDisplayName; }
|
||||||
|
public void setSenderDisplayName(String senderDisplayName) { this.senderDisplayName = senderDisplayName; }
|
||||||
}
|
}
|
||||||
@@ -362,6 +362,8 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
binding.tvChatTitle.setText(chat.getCustomerName());
|
binding.tvChatTitle.setText(chat.getCustomerName());
|
||||||
binding.chatDrawerLayout.closeDrawer(GravityCompat.START);
|
binding.chatDrawerLayout.closeDrawer(GravityCompat.START);
|
||||||
|
|
||||||
|
messageAdapter.setStaffId(chat.getStaffId());
|
||||||
|
|
||||||
if (stompChatManager != null) stompChatManager.subscribeToConversation(activeConversationId);
|
if (stompChatManager != null) stompChatManager.subscribeToConversation(activeConversationId);
|
||||||
viewModel.loadMessageHistory(activeConversationId);
|
viewModel.loadMessageHistory(activeConversationId);
|
||||||
}
|
}
|
||||||
@@ -470,10 +472,8 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
public void onMessageReceived(MessageDTO dto) {
|
public void onMessageReceived(MessageDTO dto) {
|
||||||
requireActivity().runOnUiThread(() -> {
|
requireActivity().runOnUiThread(() -> {
|
||||||
if (activeConversationId != null && activeConversationId.equals(dto.getConversationId())) {
|
if (activeConversationId != null && activeConversationId.equals(dto.getConversationId())) {
|
||||||
if (!tokenManager.getUserId().equals(dto.getSenderId())) {
|
|
||||||
viewModel.addMessageLocally(dto);
|
viewModel.addMessageLocally(dto);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
viewModel.updateConversationLocally(new ConversationDTO(dto.getConversationId(), 0L, 0L, dto.getContent(), ""));
|
viewModel.updateConversationLocally(new ConversationDTO(dto.getConversationId(), 0L, 0L, dto.getContent(), ""));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ public class Message {
|
|||||||
private String attachmentName;
|
private String attachmentName;
|
||||||
private String attachmentMimeType;
|
private String attachmentMimeType;
|
||||||
private Long attachmentSizeBytes;
|
private Long attachmentSizeBytes;
|
||||||
|
private String senderRole;
|
||||||
|
private String senderDisplayName;
|
||||||
|
|
||||||
public Message() {}
|
public Message() {}
|
||||||
|
|
||||||
@@ -49,4 +51,10 @@ public class Message {
|
|||||||
|
|
||||||
public Long getAttachmentSizeBytes() { return attachmentSizeBytes; }
|
public Long getAttachmentSizeBytes() { return attachmentSizeBytes; }
|
||||||
public void setAttachmentSizeBytes(Long attachmentSizeBytes) { this.attachmentSizeBytes = attachmentSizeBytes; }
|
public void setAttachmentSizeBytes(Long attachmentSizeBytes) { this.attachmentSizeBytes = attachmentSizeBytes; }
|
||||||
|
|
||||||
|
public String getSenderRole() { return senderRole; }
|
||||||
|
public void setSenderRole(String senderRole) { this.senderRole = senderRole; }
|
||||||
|
|
||||||
|
public String getSenderDisplayName() { return senderDisplayName; }
|
||||||
|
public void setSenderDisplayName(String senderDisplayName) { this.senderDisplayName = senderDisplayName; }
|
||||||
}
|
}
|
||||||
@@ -133,6 +133,11 @@ public class ChatListViewModel extends ViewModel {
|
|||||||
|
|
||||||
public void addMessageLocally(MessageDTO dto) {
|
public void addMessageLocally(MessageDTO dto) {
|
||||||
List<Message> current = new ArrayList<>(messageList.getValue());
|
List<Message> current = new ArrayList<>(messageList.getValue());
|
||||||
|
if (dto.getId() != null) {
|
||||||
|
for (Message m : current) {
|
||||||
|
if (dto.getId().equals(m.getId())) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
current.add(dtoToModel(dto));
|
current.add(dtoToModel(dto));
|
||||||
messageList.setValue(current);
|
messageList.setValue(current);
|
||||||
}
|
}
|
||||||
@@ -168,6 +173,8 @@ public class ChatListViewModel extends ViewModel {
|
|||||||
m.setIsRead(dto.getIsRead());
|
m.setIsRead(dto.getIsRead());
|
||||||
m.setAttachmentUrl(dto.getAttachmentUrl());
|
m.setAttachmentUrl(dto.getAttachmentUrl());
|
||||||
m.setAttachmentName(dto.getAttachmentName());
|
m.setAttachmentName(dto.getAttachmentName());
|
||||||
|
m.setSenderRole(dto.getSenderRole());
|
||||||
|
m.setSenderDisplayName(dto.getSenderDisplayName());
|
||||||
m.setAttachmentMimeType(dto.getAttachmentMimeType());
|
m.setAttachmentMimeType(dto.getAttachmentMimeType());
|
||||||
m.setAttachmentSizeBytes(dto.getAttachmentSizeBytes());
|
m.setAttachmentSizeBytes(dto.getAttachmentSizeBytes());
|
||||||
return m;
|
return m;
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ public class StompChatManager {
|
|||||||
headers.put("Authorization", "Bearer " + authToken);
|
headers.put("Authorization", "Bearer " + authToken);
|
||||||
|
|
||||||
stompClient = Stomp.over(Stomp.ConnectionProvider.OKHTTP, webSocketUrl, headers);
|
stompClient = Stomp.over(Stomp.ConnectionProvider.OKHTTP, webSocketUrl, headers);
|
||||||
|
stompClient.withClientHeartbeat(0).withServerHeartbeat(0);
|
||||||
|
|
||||||
compositeDisposable.add(
|
compositeDisposable.add(
|
||||||
stompClient.lifecycle()
|
stompClient.lifecycle()
|
||||||
|
|||||||
@@ -14,6 +14,15 @@
|
|||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
android:maxWidth="300dp">
|
android:maxWidth="300dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSenderName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/ivAttachment"
|
android:id="@+id/ivAttachment"
|
||||||
android:layout_width="200dp"
|
android:layout_width="200dp"
|
||||||
@@ -38,6 +47,14 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Received message"
|
android:text="Received message"
|
||||||
android:textColor="@color/text_dark" />
|
android:textColor="@color/text_dark" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTimestamp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#94a3b8"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:layout_marginTop="4dp"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -14,6 +14,16 @@
|
|||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
android:maxWidth="300dp">
|
android:maxWidth="300dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSenderName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="You"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/ivAttachment"
|
android:id="@+id/ivAttachment"
|
||||||
android:layout_width="200dp"
|
android:layout_width="200dp"
|
||||||
@@ -38,6 +48,14 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Sent message"
|
android:text="Sent message"
|
||||||
android:textColor="@color/white" />
|
android:textColor="@color/white" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTimestamp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="#dbeafe"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:layout_marginTop="4dp"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -35,9 +35,11 @@ import java.io.File;
|
|||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class ChatController {
|
public class ChatController {
|
||||||
@@ -87,6 +89,7 @@ public class ChatController {
|
|||||||
private final ObservableList<ConversationResponse> activeConversations = FXCollections.observableArrayList();
|
private final ObservableList<ConversationResponse> activeConversations = FXCollections.observableArrayList();
|
||||||
private final ObservableList<ConversationResponse> closedConversations = FXCollections.observableArrayList();
|
private final ObservableList<ConversationResponse> closedConversations = FXCollections.observableArrayList();
|
||||||
private final Map<Long, String> customerLabels = new HashMap<>();
|
private final Map<Long, String> customerLabels = new HashMap<>();
|
||||||
|
private final Set<Long> renderedMessageIds = new HashSet<>();
|
||||||
private final ChatRealtimeClient realtimeClient = ChatRealtimeClient.getInstance();
|
private final ChatRealtimeClient realtimeClient = ChatRealtimeClient.getInstance();
|
||||||
private ConversationResponse selectedConversation;
|
private ConversationResponse selectedConversation;
|
||||||
private File selectedAttachmentFile;
|
private File selectedAttachmentFile;
|
||||||
@@ -131,12 +134,7 @@ public class ChatController {
|
|||||||
});
|
});
|
||||||
|
|
||||||
realtimeClient.setConversationListener(conversation -> Platform.runLater(() -> upsertConversation(conversation)));
|
realtimeClient.setConversationListener(conversation -> Platform.runLater(() -> upsertConversation(conversation)));
|
||||||
realtimeClient.setMessageListener(message -> Platform.runLater(() -> {
|
realtimeClient.setMessageListener(message -> Platform.runLater(() -> appendMessageIfSelected(message)));
|
||||||
Long myId = UserSession.getInstance().getUserId();
|
|
||||||
if (message.getSenderId() == null || !message.getSenderId().equals(myId)) {
|
|
||||||
appendMessageIfSelected(message);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
realtimeClient.setStatusListener(status -> Platform.runLater(() -> lblChatStatus.setText(status)));
|
realtimeClient.setStatusListener(status -> Platform.runLater(() -> lblChatStatus.setText(status)));
|
||||||
realtimeClient.subscribeToConversations();
|
realtimeClient.subscribeToConversations();
|
||||||
|
|
||||||
@@ -419,9 +417,11 @@ public class ChatController {
|
|||||||
|
|
||||||
private void renderMessages(List<MessageResponse> messages) {
|
private void renderMessages(List<MessageResponse> messages) {
|
||||||
vbMessages.getChildren().clear();
|
vbMessages.getChildren().clear();
|
||||||
|
renderedMessageIds.clear();
|
||||||
for (MessageResponse message : messages) {
|
for (MessageResponse message : messages) {
|
||||||
try {
|
try {
|
||||||
vbMessages.getChildren().add(createMessageBubble(message));
|
vbMessages.getChildren().add(createMessageBubble(message));
|
||||||
|
if (message.getId() != null) renderedMessageIds.add(message.getId());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ActivityLogger.getInstance().logException(
|
ActivityLogger.getInstance().logException(
|
||||||
"ChatController.renderMessages",
|
"ChatController.renderMessages",
|
||||||
@@ -436,7 +436,11 @@ public class ChatController {
|
|||||||
try {
|
try {
|
||||||
upsertConversationForMessage(message);
|
upsertConversationForMessage(message);
|
||||||
if (selectedConversation != null && selectedConversation.getId().equals(message.getConversationId())) {
|
if (selectedConversation != null && selectedConversation.getId().equals(message.getConversationId())) {
|
||||||
|
if (message.getId() != null && renderedMessageIds.contains(message.getId())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
vbMessages.getChildren().add(createMessageBubble(message));
|
vbMessages.getChildren().add(createMessageBubble(message));
|
||||||
|
if (message.getId() != null) renderedMessageIds.add(message.getId());
|
||||||
scrollMessagesToBottom();
|
scrollMessagesToBottom();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
Reference in New Issue
Block a user