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

355 lines
15 KiB
Java
Raw Normal View History

2025-12-19 15:11:49 +08:00
package com.example.livestreaming;
import android.graphics.Outline;
import android.net.Uri;
import android.text.TextUtils;
2025-12-19 15:11:49 +08:00
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.widget.ImageView;
2025-12-19 15:11:49 +08:00
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.FileProvider;
2025-12-19 15:11:49 +08:00
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
2025-12-19 15:11:49 +08:00
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class ConversationMessagesAdapter extends ListAdapter<ChatMessage, RecyclerView.ViewHolder> {
public interface OnMessageLongClickListener {
void onMessageLongClick(ChatMessage message, int position, View view);
}
private OnMessageLongClickListener longClickListener;
2025-12-19 15:11:49 +08:00
private static final int TYPE_INCOMING = 1;
private static final int TYPE_OUTGOING = 2;
private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm", Locale.getDefault());
public ConversationMessagesAdapter() {
super(DIFF);
}
public void setOnMessageLongClickListener(OnMessageLongClickListener listener) {
this.longClickListener = listener;
}
2025-12-19 15:11:49 +08:00
@Override
public int getItemViewType(int position) {
ChatMessage msg = getItem(position);
if (msg == null) return TYPE_INCOMING;
String u = msg.getUsername();
return "".equals(u) ? TYPE_OUTGOING : TYPE_INCOMING;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
if (viewType == TYPE_OUTGOING) {
View v = inflater.inflate(R.layout.item_conversation_message_outgoing, parent, false);
return new OutgoingVH(v);
}
View v = inflater.inflate(R.layout.item_conversation_message_incoming, parent, false);
return new IncomingVH(v);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
ChatMessage msg = getItem(position);
if (holder instanceof IncomingVH) {
((IncomingVH) holder).bind(msg, longClickListener);
2025-12-19 15:11:49 +08:00
} else if (holder instanceof OutgoingVH) {
((OutgoingVH) holder).bind(msg, longClickListener);
2025-12-19 15:11:49 +08:00
}
}
static class IncomingVH extends RecyclerView.ViewHolder {
private final ImageView avatarView;
2025-12-19 15:11:49 +08:00
private final TextView nameText;
private final TextView msgText;
private final TextView timeText;
IncomingVH(@NonNull View itemView) {
super(itemView);
avatarView = itemView.findViewById(R.id.avatarView);
2025-12-19 15:11:49 +08:00
nameText = itemView.findViewById(R.id.nameText);
msgText = itemView.findViewById(R.id.messageText);
timeText = itemView.findViewById(R.id.timeText);
setupAvatarOutline();
}
private void setupAvatarOutline() {
if (avatarView == null) return;
// 设置圆形裁剪,确保在模拟器上也能正常工作
// 使用post确保在View布局完成后再设置outline
avatarView.post(() -> {
if (avatarView.getWidth() > 0 && avatarView.getHeight() > 0) {
avatarView.setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setOval(0, 0, view.getWidth(), view.getHeight());
}
});
avatarView.setClipToOutline(true);
}
});
2025-12-19 15:11:49 +08:00
}
void bind(ChatMessage message, OnMessageLongClickListener listener) {
2025-12-19 15:11:49 +08:00
if (message == null) return;
nameText.setText(message.getUsername() != null ? message.getUsername() : "");
msgText.setText(message.getMessage() != null ? message.getMessage() : "");
timeText.setText(TIME_FORMAT.format(new Date(message.getTimestamp())));
// 清除之前的监听器,避免重复绑定
itemView.setOnClickListener(null);
itemView.setOnLongClickListener(null);
// 设置长按监听
itemView.setOnLongClickListener(v -> {
if (listener != null) {
int position = getBindingAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
listener.onMessageLongClick(message, position, v);
return true;
}
}
return false;
});
// 确保头像圆形裁剪设置正确
setupAvatarOutline();
// 加载对方头像(优先使用消息中的头像 URL如果没有则使用默认头像
loadAvatar(message);
}
private void loadAvatar(ChatMessage message) {
// TODO: 接入后端接口 - 加载消息发送者头像
// 接口路径: GET /api/users/{userId}/avatar 或直接从ChatMessage的avatarUrl字段获取
// ChatMessage对象应包含avatarUrl字段如果为空则根据userId调用接口获取
// 建议后端在返回消息列表时每条消息都包含发送者的avatarUrl避免额外请求
if (avatarView == null) return;
try {
// 优先使用消息中的头像 URL从后端获取
String avatarUrl = message != null ? message.getAvatarUrl() : null;
if (!TextUtils.isEmpty(avatarUrl)) {
Glide.with(avatarView)
.load(avatarUrl)
.circleCrop()
.error(R.drawable.ic_account_circle_24)
.placeholder(R.drawable.ic_account_circle_24)
.into(avatarView);
return;
}
// 如果没有头像 URL暂时根据用户名生成不同的默认头像
// 这样不同发送者至少能通过不同的默认头像区分开
String username = message != null ? message.getUsername() : null;
int defaultAvatarRes = getDefaultAvatarForUsername(username);
Glide.with(avatarView)
.load(defaultAvatarRes)
.circleCrop()
.into(avatarView);
} catch (Exception e) {
// 如果加载失败,使用默认头像
Glide.with(avatarView)
.load(R.drawable.ic_account_circle_24)
.circleCrop()
.into(avatarView);
}
2025-12-19 15:11:49 +08:00
}
/**
* 根据用户名生成一个稳定的默认头像资源
* 这样同一个用户名的消息会显示相同的默认头像
* TODO: 后续接入后端接口后这个方法可以改为从后端获取头像 URL
*/
private int getDefaultAvatarForUsername(String username) {
if (TextUtils.isEmpty(username)) {
return R.drawable.ic_account_circle_24;
}
// 使用用户名的哈希值来选择不同的默认头像
// 这样不同的发送者至少能通过不同的默认头像区分开
int hash = Math.abs(username.hashCode());
int[] defaultAvatars = {
R.drawable.ic_account_circle_24,
// 如果有其他默认头像资源,可以在这里添加
// R.drawable.default_avatar_1,
// R.drawable.default_avatar_2,
};
return defaultAvatars[hash % defaultAvatars.length];
}
2025-12-19 15:11:49 +08:00
}
static class OutgoingVH extends RecyclerView.ViewHolder {
private final ImageView avatarView;
2025-12-19 15:11:49 +08:00
private final TextView msgText;
private final TextView timeText;
private final ImageView statusIcon;
2025-12-19 15:11:49 +08:00
OutgoingVH(@NonNull View itemView) {
super(itemView);
avatarView = itemView.findViewById(R.id.avatarView);
2025-12-19 15:11:49 +08:00
msgText = itemView.findViewById(R.id.messageText);
timeText = itemView.findViewById(R.id.timeText);
statusIcon = itemView.findViewById(R.id.statusIcon);
setupAvatarOutline();
}
private void setupAvatarOutline() {
if (avatarView == null) return;
// 设置圆形裁剪,确保在模拟器上也能正常工作
// 使用post确保在View布局完成后再设置outline
avatarView.post(() -> {
if (avatarView.getWidth() > 0 && avatarView.getHeight() > 0) {
avatarView.setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setOval(0, 0, view.getWidth(), view.getHeight());
}
});
avatarView.setClipToOutline(true);
}
});
2025-12-19 15:11:49 +08:00
}
void bind(ChatMessage message, OnMessageLongClickListener listener) {
2025-12-19 15:11:49 +08:00
if (message == null) return;
msgText.setText(message.getMessage() != null ? message.getMessage() : "");
timeText.setText(TIME_FORMAT.format(new Date(message.getTimestamp())));
// 显示消息状态图标(仅对发送的消息显示)
if (statusIcon != null && message.getStatus() != null) {
switch (message.getStatus()) {
case SENDING:
statusIcon.setImageResource(R.drawable.ic_clock_24);
statusIcon.setColorFilter(itemView.getContext().getColor(android.R.color.darker_gray));
statusIcon.setVisibility(View.VISIBLE);
break;
case SENT:
statusIcon.setImageResource(R.drawable.ic_check_24);
statusIcon.setColorFilter(itemView.getContext().getColor(android.R.color.darker_gray));
statusIcon.setVisibility(View.VISIBLE);
break;
case READ:
statusIcon.setImageResource(R.drawable.ic_check_double_24);
statusIcon.setColorFilter(0xFF4CAF50);
statusIcon.setVisibility(View.VISIBLE);
break;
default:
statusIcon.setVisibility(View.GONE);
break;
}
}
// 清除之前的监听器,避免重复绑定
itemView.setOnClickListener(null);
itemView.setOnLongClickListener(null);
// 设置长按监听
itemView.setOnLongClickListener(v -> {
if (listener != null) {
int position = getBindingAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
listener.onMessageLongClick(message, position, v);
return true;
}
}
return false;
});
// 确保头像圆形裁剪设置正确
setupAvatarOutline();
// 加载用户头像
loadAvatar();
}
private void loadAvatar() {
if (avatarView == null) return;
try {
String avatarUri = avatarView.getContext()
.getSharedPreferences("profile_prefs", android.content.Context.MODE_PRIVATE)
.getString("profile_avatar_uri", null);
if (!TextUtils.isEmpty(avatarUri)) {
Uri uri = Uri.parse(avatarUri);
// 如果是 file:// 协议,尝试转换为 FileProvider URI
if ("file".equals(uri.getScheme())) {
try {
java.io.File file = new java.io.File(uri.getPath());
if (file.exists()) {
uri = FileProvider.getUriForFile(
avatarView.getContext(),
avatarView.getContext().getPackageName() + ".fileprovider",
file
);
}
} catch (Exception e) {
// 如果转换失败,使用原始 URI
}
}
Glide.with(avatarView)
.load(uri)
.circleCrop()
.error(R.drawable.ic_account_circle_24)
.placeholder(R.drawable.ic_account_circle_24)
.into(avatarView);
return;
}
int avatarRes = avatarView.getContext()
.getSharedPreferences("profile_prefs", android.content.Context.MODE_PRIVATE)
.getInt("profile_avatar_res", 0);
if (avatarRes != 0) {
Glide.with(avatarView)
.load(avatarRes)
.circleCrop()
.error(R.drawable.ic_account_circle_24)
.into(avatarView);
} else {
Glide.with(avatarView)
.load(R.drawable.ic_account_circle_24)
.circleCrop()
.into(avatarView);
}
} catch (Exception e) {
Glide.with(avatarView)
.load(R.drawable.ic_account_circle_24)
.circleCrop()
.into(avatarView);
}
2025-12-19 15:11:49 +08:00
}
}
private static final DiffUtil.ItemCallback<ChatMessage> DIFF = new DiffUtil.ItemCallback<ChatMessage>() {
@Override
public boolean areItemsTheSame(@NonNull ChatMessage oldItem, @NonNull ChatMessage newItem) {
// 使用messageId作为唯一标识
return oldItem.getMessageId() != null && oldItem.getMessageId().equals(newItem.getMessageId());
2025-12-19 15:11:49 +08:00
}
@Override
public boolean areContentsTheSame(@NonNull ChatMessage oldItem, @NonNull ChatMessage newItem) {
// 比较消息内容、状态等是否相同
return oldItem.getMessageId() != null && oldItem.getMessageId().equals(newItem.getMessageId()) &&
oldItem.getStatus() == newItem.getStatus() &&
oldItem.getMessage() != null && oldItem.getMessage().equals(newItem.getMessage());
2025-12-19 15:11:49 +08:00
}
};
}