2025-12-18 14:20:41 +08:00
|
|
|
|
package com.example.livestreaming;
|
|
|
|
|
|
|
2025-12-22 16:31:46 +08:00
|
|
|
|
import android.graphics.Outline;
|
|
|
|
|
|
import android.net.Uri;
|
|
|
|
|
|
import android.text.TextUtils;
|
2025-12-18 14:20:41 +08:00
|
|
|
|
import android.view.LayoutInflater;
|
|
|
|
|
|
import android.view.View;
|
|
|
|
|
|
import android.view.ViewGroup;
|
2025-12-22 16:31:46 +08:00
|
|
|
|
import android.view.ViewOutlineProvider;
|
2025-12-18 14:20:41 +08:00
|
|
|
|
|
|
|
|
|
|
import androidx.annotation.NonNull;
|
2025-12-23 15:37:37 +08:00
|
|
|
|
import androidx.core.content.FileProvider;
|
2025-12-18 14:20:41 +08:00
|
|
|
|
import androidx.recyclerview.widget.DiffUtil;
|
|
|
|
|
|
import androidx.recyclerview.widget.ListAdapter;
|
|
|
|
|
|
import androidx.recyclerview.widget.RecyclerView;
|
|
|
|
|
|
|
2025-12-22 16:31:46 +08:00
|
|
|
|
import com.bumptech.glide.Glide;
|
2025-12-18 14:20:41 +08:00
|
|
|
|
import com.example.livestreaming.databinding.ItemConversationBinding;
|
|
|
|
|
|
|
|
|
|
|
|
public class ConversationsAdapter extends ListAdapter<ConversationItem, ConversationsAdapter.VH> {
|
|
|
|
|
|
|
|
|
|
|
|
public interface OnConversationClickListener {
|
|
|
|
|
|
void onConversationClick(ConversationItem item);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private final OnConversationClickListener onConversationClickListener;
|
|
|
|
|
|
|
|
|
|
|
|
public ConversationsAdapter(OnConversationClickListener onConversationClickListener) {
|
|
|
|
|
|
super(DIFF);
|
|
|
|
|
|
this.onConversationClickListener = onConversationClickListener;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@NonNull
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
|
|
|
|
|
ItemConversationBinding binding = ItemConversationBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
|
|
|
|
|
|
return new VH(binding, onConversationClickListener);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void onBindViewHolder(@NonNull VH holder, int position) {
|
|
|
|
|
|
holder.bind(getItem(position));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static class VH extends RecyclerView.ViewHolder {
|
|
|
|
|
|
|
|
|
|
|
|
private final ItemConversationBinding binding;
|
|
|
|
|
|
private final OnConversationClickListener onConversationClickListener;
|
|
|
|
|
|
|
|
|
|
|
|
VH(ItemConversationBinding binding, OnConversationClickListener onConversationClickListener) {
|
|
|
|
|
|
super(binding.getRoot());
|
|
|
|
|
|
this.binding = binding;
|
|
|
|
|
|
this.onConversationClickListener = onConversationClickListener;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void bind(ConversationItem item) {
|
|
|
|
|
|
binding.title.setText(item != null && item.getTitle() != null ? item.getTitle() : "");
|
|
|
|
|
|
binding.lastMessage.setText(item != null && item.getLastMessage() != null ? item.getLastMessage() : "");
|
|
|
|
|
|
binding.timeText.setText(item != null && item.getTimeText() != null ? item.getTimeText() : "");
|
|
|
|
|
|
|
|
|
|
|
|
int unread = item != null ? item.getUnreadCount() : 0;
|
|
|
|
|
|
if (unread > 0) {
|
|
|
|
|
|
binding.unreadBadge.setVisibility(View.VISIBLE);
|
|
|
|
|
|
binding.unreadBadge.setText(unread > 99 ? "99+" : String.valueOf(unread));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
binding.unreadBadge.setVisibility(View.GONE);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-22 16:31:46 +08:00
|
|
|
|
// 加载头像
|
|
|
|
|
|
loadAvatar(item);
|
|
|
|
|
|
|
2025-12-18 14:20:41 +08:00
|
|
|
|
binding.getRoot().setOnClickListener(v -> {
|
|
|
|
|
|
if (item == null) return;
|
|
|
|
|
|
if (onConversationClickListener != null) onConversationClickListener.onConversationClick(item);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-12-22 16:31:46 +08:00
|
|
|
|
|
|
|
|
|
|
private void loadAvatar(ConversationItem item) {
|
2025-12-23 15:37:37 +08:00
|
|
|
|
// TODO: 接入后端接口 - 加载会话对方头像
|
|
|
|
|
|
// 接口路径: GET /api/users/{userId}/avatar 或直接从ConversationItem的avatarUrl字段获取
|
|
|
|
|
|
// 如果ConversationItem对象包含avatarUrl字段,直接使用该URL加载头像
|
|
|
|
|
|
// 如果没有avatarUrl,需要根据会话类型(私信/群聊)调用相应接口获取头像
|
2025-12-22 16:31:46 +08:00
|
|
|
|
if (binding.avatar == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 设置圆形裁剪
|
|
|
|
|
|
setupAvatarOutline();
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2025-12-23 15:37:37 +08:00
|
|
|
|
// 优先使用ConversationItem中的avatarUrl(从后端获取)
|
|
|
|
|
|
// 尝试从 SharedPreferences 加载用户头像
|
|
|
|
|
|
String avatarUri = binding.avatar.getContext()
|
|
|
|
|
|
.getSharedPreferences("profile_prefs", android.content.Context.MODE_PRIVATE)
|
|
|
|
|
|
.getString("profile_avatar_uri", null);
|
|
|
|
|
|
|
|
|
|
|
|
if (!TextUtils.isEmpty(avatarUri)) {
|
|
|
|
|
|
// 尝试解析 URI
|
|
|
|
|
|
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(
|
|
|
|
|
|
binding.avatar.getContext(),
|
|
|
|
|
|
binding.avatar.getContext().getPackageName() + ".fileprovider",
|
|
|
|
|
|
file
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
// 如果转换失败,使用原始 URI
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Glide.with(binding.avatar)
|
|
|
|
|
|
.load(uri)
|
|
|
|
|
|
.circleCrop()
|
|
|
|
|
|
.error(R.drawable.ic_account_circle_24)
|
|
|
|
|
|
.placeholder(R.drawable.ic_account_circle_24)
|
|
|
|
|
|
.into(binding.avatar);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int avatarRes = binding.avatar.getContext()
|
|
|
|
|
|
.getSharedPreferences("profile_prefs", android.content.Context.MODE_PRIVATE)
|
|
|
|
|
|
.getInt("profile_avatar_res", 0);
|
|
|
|
|
|
|
|
|
|
|
|
if (avatarRes != 0) {
|
|
|
|
|
|
Glide.with(binding.avatar)
|
|
|
|
|
|
.load(avatarRes)
|
|
|
|
|
|
.circleCrop()
|
|
|
|
|
|
.error(R.drawable.ic_account_circle_24)
|
|
|
|
|
|
.placeholder(R.drawable.ic_account_circle_24)
|
|
|
|
|
|
.into(binding.avatar);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-12-22 16:31:46 +08:00
|
|
|
|
|
2025-12-23 15:37:37 +08:00
|
|
|
|
// 如果没有保存的头像,使用默认头像
|
2025-12-22 16:31:46 +08:00
|
|
|
|
Glide.with(binding.avatar)
|
|
|
|
|
|
.load(R.drawable.ic_account_circle_24)
|
|
|
|
|
|
.circleCrop()
|
|
|
|
|
|
.into(binding.avatar);
|
|
|
|
|
|
} catch (Exception e) {
|
2025-12-23 15:37:37 +08:00
|
|
|
|
// 如果加载失败,使用默认头像
|
2025-12-22 16:31:46 +08:00
|
|
|
|
Glide.with(binding.avatar)
|
|
|
|
|
|
.load(R.drawable.ic_account_circle_24)
|
|
|
|
|
|
.circleCrop()
|
|
|
|
|
|
.into(binding.avatar);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void setupAvatarOutline() {
|
|
|
|
|
|
if (binding.avatar == null) return;
|
|
|
|
|
|
// 设置圆形裁剪,确保在模拟器上也能正常工作
|
|
|
|
|
|
binding.avatar.post(() -> {
|
|
|
|
|
|
if (binding.avatar.getWidth() > 0 && binding.avatar.getHeight() > 0) {
|
|
|
|
|
|
binding.avatar.setOutlineProvider(new ViewOutlineProvider() {
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public void getOutline(View view, Outline outline) {
|
|
|
|
|
|
outline.setOval(0, 0, view.getWidth(), view.getHeight());
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
binding.avatar.setClipToOutline(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-12-18 14:20:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static final DiffUtil.ItemCallback<ConversationItem> DIFF = new DiffUtil.ItemCallback<ConversationItem>() {
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public boolean areItemsTheSame(@NonNull ConversationItem oldItem, @NonNull ConversationItem newItem) {
|
|
|
|
|
|
String o = oldItem.getId();
|
|
|
|
|
|
String n = newItem.getId();
|
|
|
|
|
|
return o != null && o.equals(n);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
public boolean areContentsTheSame(@NonNull ConversationItem oldItem, @NonNull ConversationItem newItem) {
|
|
|
|
|
|
return oldItem.equals(newItem);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|