elysia/lib/page/ChatRoomPage.dart
2025-11-04 09:53:47 +08:00

347 lines
11 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:elysia/plugin/Remind.dart';
import 'package:elysia/plugin/RouteAnimation.dart';
import 'package:flutter/material.dart';
import 'package:shimmer/shimmer.dart';
import '../bean/ChatCacheItem.dart';
import '../plugin/CacheAavatar.dart';
import '../plugin/HTTP.dart';
import '../plugin/C.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:oktoast/oktoast.dart';
import '../plugin/HiverCache.dart';
import '../plugin/LoadingOverlay.dart';
import '../plugin/StringTool.dart';
import 'child/ChatPage.dart';
class ChatRoomPage extends StatefulWidget {
const ChatRoomPage({Key? key}) : super(key: key);
@override
State<ChatRoomPage> createState() => _ChatRoomPageState();
static Function(dynamic?)? flushData;
}
class _ChatRoomPageState extends State<ChatRoomPage>
with AutomaticKeepAliveClientMixin {
List<ChatCacheItem> _chatList = [];
bool _loading = true;
@override
void initState() {
super.initState();
ChatRoomPage.flushData = (data) {
if (data == null) {
getCache();
} else {
int index = _chatList.indexWhere(
(item) => item.roomId == data["roomId"].toString(),
);
if (index == -1) return; // 找不到就直接返回
// 深拷贝并更新数据
ChatCacheItem chatItem = _chatList[index].copy();
chatItem.lastChatMessage = data["message"] ?? data["content"];
chatItem.updateTime(DateTime.now());
setState(() {
// 先移除旧 item
_chatList.removeAt(index);
// 插入到列表头
_chatList.insert(0, chatItem);
});
HiverCache.cache(C.HIVE_CACHE_CACHE_LIST, _chatList);
}
};
getCache();
}
Future<void> getCache() async {
List<ChatCacheItem>? result = await HiverCache.getCache<ChatCacheItem>(
C.HIVE_CACHE_CACHE_LIST,
ChatCacheItem.fromJson,
);
setState(() {
_loading = false;
});
await fetchChatList(result??[]);
startFlushData(result??[]);
}
Future<void> startFlushData(List<ChatCacheItem> cache) async {
if(cache?.length==0){
_loading=true;
}
dynamic result = await loadData();
try {
if (result['code'] == 200) {
List<Map<String, dynamic>> listOfMap = result["data"]
.cast<Map<String, dynamic>>();
List<ChatCacheItem> dbData = listOfMap
.map((e) => ChatCacheItem.fromJson(e))
.toList();
syncLists(cache ?? [], dbData);
fetchChatList(cache);
HiverCache.cache(C.HIVE_CACHE_CACHE_LIST, cache);
} else {
setState(() {
_loading = false;
});
}
} catch (e) {
setState(() {
_loading = false;
});
}
}
void syncLists(List<ChatCacheItem> cacheList, List<ChatCacheItem> dbList) {
// 提取 bList 的所有 id
final bIds = dbList.map((e) => e.roomId).toSet();
// 删除 A 中多余的项ID 不在 B 里)
cacheList.removeWhere((a) => !bIds.contains(a.roomId));
// 提取 A 的所有 id
final aIds = cacheList.map((e) => e.roomId).toSet();
// 添加 B 中缺少的项
for (var b in dbList) {
if (!aIds.contains(b.roomId)) {
cacheList.add(b);
}
}
}
Future<void> fetchChatList(List<ChatCacheItem> result) async {
print(result);
setState(() {
_chatList = result;
_loading = false;
});
}
Future<dynamic> loadData() async {
dynamic result = await HTTP
.create("${C.BASE_URL}/chat/list")
.setHeader(C.TOKEN)
.setRequestType(RequestType.GET)
.execute();
return result;
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: _loading
? ListView.builder(
itemCount: 10, // 加载前显示 10 个 Shimmer
itemBuilder: (context, index) {
return ShimmerChatTile();
},
)
: ListView.builder(
itemCount: _chatList.length,
itemBuilder: (context, index) {
final chat = _chatList[index];
return ChatTile(
chat: chat,
onDelete: () {
Remind.show(
context,
"提示",
"是否删除聊天,这是不可恢复的!!!",
onTap: (status) async {
if (status) {
LoadingOverlay.show(
context: context,
barrierColor: Colors.black54,
);
try {
dynamic result = await HTTP
.create("${C.BASE_URL}/chat/del/${chat.roomId}")
.setHeader(C.TOKEN)
.setRequestType(RequestType.DELETE)
.execute();
LoadingOverlay.hide();
if (result['code'] == 200) {
setState(() {
_chatList.removeAt(index);
});
HiverCache.cache(C.HIVE_CACHE_CACHE_LIST, _chatList);
} else {
showToast(
result["message"],
position: ToastPosition.bottom,
);
}
} catch (e) {
LoadingOverlay.hide();
}
}
},
);
},
onClear: () {
Remind.show(
context,
"提示",
"是否清空聊天记录",
onTap: (status) async {
if (status) {
LoadingOverlay.show(
context: context,
barrierColor: Colors.black54,
);
try {
dynamic result = await HTTP
.create(
"${C.BASE_URL}/chat/clear/${chat.roomId}",
)
.setHeader(C.TOKEN)
.setRequestType(RequestType.DELETE)
.execute();
LoadingOverlay.hide();
if (result['code'] == 200) {
setState(() {
_chatList[index].lastChatTime = "";
_chatList[index].lastChatMessage = "";
});
HiverCache.cache(C.HIVE_CACHE_CACHE_LIST, _chatList);
} else {
showToast(
result["message"],
position: ToastPosition.bottom,
);
}
} catch (e) {
LoadingOverlay.hide();
}
}
},
);
},
);
},
),
);
}
@override
bool get wantKeepAlive => true;
}
/// 单条聊天列表项
class ChatTile extends StatelessWidget {
final ChatCacheItem chat;
final VoidCallback? onDelete;
final VoidCallback? onClear;
const ChatTile({Key? key, required this.chat, this.onDelete, this.onClear})
: super(key: key);
@override
Widget build(BuildContext context) {
return Slidable(
key: ValueKey(chat.roomId),
endActionPane: ActionPane(
motion: const DrawerMotion(), // 滑动动画
extentRatio: 0.45, // 右滑按钮占宽度比例
children: [
Expanded(
child: SlidableAction(
onPressed: (context) {
if (onDelete != null) onDelete!();
},
backgroundColor: Colors.red,
foregroundColor: Colors.white,
icon: Icons.delete,
label: '删除',
),
flex: 4,
),
Expanded(
child: SlidableAction(
onPressed: (context) {
if (onClear != null) onClear!();
},
backgroundColor: Colors.amberAccent,
foregroundColor: Colors.white,
icon: Icons.cleaning_services_rounded,
label: '清空聊天记录',
),
flex: 6,
),
],
),
child: ListTile(
leading: CacheAvatar(url: chat.roomImage),
title: Text(
chat.roomName,
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: Text(
StringTool.stripMarkdown(chat.lastChatMessage),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
trailing: Text(
chat.lastChatTime,
style: const TextStyle(fontSize: 12, color: Colors.grey),
),
onTap: () {
C.PUSH_STATUS = false;
Navigator.push(
context,
RouteAnimation(
ChatPage(
roomId: chat.roomId,
robotAvatar: chat.roomImage,
robotName: chat.roomName,
),
Offset(1, 0),
),
);
},
),
);
}
}
/// Shimmer 占位列表项
class ShimmerChatTile extends StatelessWidget {
const ShimmerChatTile({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListTile(
leading: Shimmer.fromColors(
baseColor: Colors.grey.shade300,
highlightColor: Colors.white,
child: Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
),
),
title: Shimmer.fromColors(
baseColor: Colors.grey.shade300,
highlightColor: Colors.grey.shade100,
child: Container(
height: 16,
color: Colors.grey,
margin: const EdgeInsets.symmetric(vertical: 4),
),
),
subtitle: Shimmer.fromColors(
baseColor: Colors.grey.shade300,
highlightColor: Colors.grey.shade100,
child: Container(
height: 14,
color: Colors.grey,
margin: const EdgeInsets.symmetric(vertical: 4),
),
),
);
}
}