zhibo/android-app/app/src/main/java/com/example/livestreaming/ConversationActivity.java

258 lines
10 KiB
Java
Raw Normal View History

2025-12-19 15:11:49 +08:00
package com.example.livestreaming;
import android.content.ClipData;
import android.content.ClipboardManager;
2025-12-19 15:11:49 +08:00
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
2025-12-19 15:11:49 +08:00
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.MenuItem;
2025-12-19 15:11:49 +08:00
import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
2025-12-19 15:11:49 +08:00
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.PopupMenu;
2025-12-19 15:11:49 +08:00
import androidx.recyclerview.widget.LinearLayoutManager;
import com.example.livestreaming.databinding.ActivityConversationBinding;
import com.google.android.material.snackbar.Snackbar;
2025-12-19 15:11:49 +08:00
import java.util.ArrayList;
import java.util.List;
public class ConversationActivity extends AppCompatActivity {
private static final String EXTRA_CONVERSATION_ID = "extra_conversation_id";
private static final String EXTRA_CONVERSATION_TITLE = "extra_conversation_title";
private ActivityConversationBinding binding;
private ConversationMessagesAdapter adapter;
private final List<ChatMessage> messages = new ArrayList<>();
private Handler handler;
private Runnable statusUpdateRunnable;
2025-12-19 15:11:49 +08:00
public static void start(Context context, String conversationId, String title) {
Intent intent = new Intent(context, ConversationActivity.class);
intent.putExtra(EXTRA_CONVERSATION_ID, conversationId);
intent.putExtra(EXTRA_CONVERSATION_TITLE, title);
context.startActivity(intent);
}
private static final String EXTRA_UNREAD_COUNT = "extra_unread_count";
private int initialUnreadCount = 0;
2025-12-19 15:11:49 +08:00
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityConversationBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
handler = new Handler(Looper.getMainLooper());
2025-12-19 15:11:49 +08:00
String title = getIntent() != null ? getIntent().getStringExtra(EXTRA_CONVERSATION_TITLE) : null;
binding.titleText.setText(title != null ? title : "会话");
// 获取该会话的初始未读数量
initialUnreadCount = getIntent() != null ? getIntent().getIntExtra(EXTRA_UNREAD_COUNT, 0) : 0;
2025-12-19 15:11:49 +08:00
binding.backButton.setOnClickListener(v -> finish());
setupMessages();
setupInput();
// TODO: 接入后端接口 - 标记会话消息为已读
// 接口路径: POST /api/conversations/{conversationId}/read
// 请求参数:
// - conversationId: 会话ID路径参数
// - userId: 当前用户ID从token中获取
// 返回数据格式: ApiResponse<{success: boolean}>
// 用户进入会话时,标记该会话的所有消息为已读,减少未读数量
if (initialUnreadCount > 0) {
UnreadMessageManager.decrementUnreadCount(this, initialUnreadCount);
}
}
@Override
protected void onPause() {
super.onPause();
// 当用户离开会话时,更新总未读数量(如果会话未读数量已减少)
2025-12-19 15:11:49 +08:00
}
private void setupMessages() {
adapter = new ConversationMessagesAdapter();
// 设置长按监听
adapter.setOnMessageLongClickListener((message, position, view) -> {
showMessageMenu(message, position, view);
});
2025-12-19 15:11:49 +08:00
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setStackFromEnd(false);
binding.messagesRecyclerView.setLayoutManager(layoutManager);
binding.messagesRecyclerView.setAdapter(adapter);
// TODO: 接入后端接口 - 获取会话消息列表
// 接口路径: GET /api/conversations/{conversationId}/messages
// 请求参数:
// - conversationId: 会话ID路径参数
// - page (可选): 页码,用于分页加载历史消息
// - pageSize (可选): 每页数量默认20
// - beforeMessageId (可选): 获取指定消息ID之前的消息用于上拉加载更多
// 返回数据格式: ApiResponse<List<ChatMessage>>
// ChatMessage对象应包含: messageId, userId, username, avatarUrl, message, timestamp, status等字段
// 消息列表应按时间正序排列(最早的在前面)
2025-12-19 15:11:49 +08:00
messages.clear();
String title = binding.titleText.getText() != null ? binding.titleText.getText().toString() : "";
ChatMessage incomingMsg = new ChatMessage(title, "你好~");
incomingMsg.setStatus(ChatMessage.MessageStatus.SENT);
messages.add(incomingMsg);
ChatMessage outgoingMsg = new ChatMessage("", "在的,有什么需要帮忙?");
outgoingMsg.setStatus(ChatMessage.MessageStatus.READ);
messages.add(outgoingMsg);
2025-12-19 15:11:49 +08:00
adapter.submitList(new ArrayList<>(messages));
scrollToBottom();
}
private void showMessageMenu(ChatMessage message, int position, View anchorView) {
PopupMenu popupMenu = new PopupMenu(this, anchorView);
popupMenu.getMenu().add(0, 0, 0, "复制");
popupMenu.getMenu().add(0, 1, 0, "删除");
popupMenu.setOnMenuItemClickListener(item -> {
if (item.getItemId() == 0) {
// 复制消息
copyMessage(message);
return true;
} else if (item.getItemId() == 1) {
// 删除消息
deleteMessage(position);
return true;
}
return false;
});
popupMenu.show();
}
private void copyMessage(ChatMessage message) {
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("消息", message.getMessage());
clipboard.setPrimaryClip(clip);
Snackbar.make(binding.getRoot(), "已复制到剪贴板", Snackbar.LENGTH_SHORT).show();
}
private void deleteMessage(int position) {
// TODO: 接入后端接口 - 删除消息
// 接口路径: DELETE /api/messages/{messageId}
// 请求参数:
// - messageId: 消息ID路径参数
// - userId: 当前用户ID从token中获取验证是否为消息发送者
// 返回数据格式: ApiResponse<{success: boolean}>
// 删除成功后,从本地消息列表移除
if (position < 0 || position >= messages.size()) return;
new AlertDialog.Builder(this)
.setTitle("删除消息")
.setMessage("确定要删除这条消息吗?")
.setPositiveButton("删除", (dialog, which) -> {
messages.remove(position);
adapter.submitList(new ArrayList<>(messages));
Snackbar.make(binding.getRoot(), "消息已删除", Snackbar.LENGTH_SHORT).show();
})
.setNegativeButton("取消", null)
.show();
}
2025-12-19 15:11:49 +08:00
private void setupInput() {
binding.sendButton.setOnClickListener(v -> sendMessage());
binding.messageInput.setOnEditorActionListener((v, actionId, event) -> {
if (event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
sendMessage();
return true;
}
return false;
});
binding.messageInput.setOnFocusChangeListener((v, hasFocus) -> {
if (hasFocus) scrollToBottom();
});
}
private void sendMessage() {
// TODO: 接入后端接口 - 发送私信消息
// 接口路径: POST /api/conversations/{conversationId}/messages
// 请求参数:
// - conversationId: 会话ID路径参数
// - message: 消息内容
// - userId: 发送者用户ID从token中获取
// 返回数据格式: ApiResponse<ChatMessage>
// ChatMessage对象应包含: messageId, userId, username, avatarUrl, message, timestamp, status等字段
// 发送成功后更新消息状态为SENT并添加到消息列表
// TODO: 接入后端接口 - 接收对方已读状态WebSocket或轮询
// 方案1: WebSocket实时推送
// - 连接: ws://api.example.com/conversations/{conversationId}
// - 接收消息格式: {type: "read", messageId: "xxx"} 或 {type: "new_message", data: ChatMessage}
// 方案2: 轮询检查消息状态
// - 接口路径: GET /api/conversations/{conversationId}/messages/{messageId}/status
// - 返回数据格式: ApiResponse<{status: "sent"|"read"}>
2025-12-19 15:11:49 +08:00
String text = binding.messageInput.getText() != null ? binding.messageInput.getText().toString().trim() : "";
if (TextUtils.isEmpty(text)) return;
ChatMessage newMessage = new ChatMessage("", text);
newMessage.setStatus(ChatMessage.MessageStatus.SENDING);
messages.add(newMessage);
2025-12-19 15:11:49 +08:00
adapter.submitList(new ArrayList<>(messages));
binding.messageInput.setText("");
scrollToBottom();
// 模拟消息发送过程:发送中 -> 已发送 -> 已读
if (statusUpdateRunnable != null) {
handler.removeCallbacks(statusUpdateRunnable);
}
statusUpdateRunnable = () -> {
// 1秒后更新为已发送
newMessage.setStatus(ChatMessage.MessageStatus.SENT);
adapter.notifyItemChanged(messages.size() - 1);
// 再2秒后更新为已读
handler.postDelayed(() -> {
newMessage.setStatus(ChatMessage.MessageStatus.READ);
adapter.notifyItemChanged(messages.size() - 1);
}, 2000);
};
handler.postDelayed(statusUpdateRunnable, 1000);
2025-12-19 15:11:49 +08:00
}
private void scrollToBottom() {
if (messages.isEmpty()) return;
binding.messagesRecyclerView.post(() -> {
int lastPosition = messages.size() - 1;
if (lastPosition >= 0) {
binding.messagesRecyclerView.smoothScrollToPosition(lastPosition);
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
// 清理Handler中的延迟任务防止内存泄漏
if (handler != null && statusUpdateRunnable != null) {
handler.removeCallbacks(statusUpdateRunnable);
}
handler = null;
statusUpdateRunnable = null;
2025-12-19 15:11:49 +08:00
}
}