You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

797 lines
27 KiB

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:huixiang/constant.dart';
import 'package:huixiang/generated/l10n.dart';
import 'package:huixiang/im/database/message.dart';
import 'package:huixiang/retrofit/data/msg_stats.dart';
import 'package:huixiang/main.dart';
import 'package:huixiang/retrofit/data/base_data.dart';
import 'package:huixiang/retrofit/retrofit_api.dart';
import 'package:huixiang/utils/font_weight.dart';
import 'package:huixiang/view_widget/classic_header.dart';
import 'package:huixiang/view_widget/my_footer.dart';
import 'package:huixiang/view_widget/round_button.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../retrofit/data/im_user.dart';
import '../../retrofit/data/page.dart';
import '../../utils/flutter_utils.dart';
import '../../view_widget/custom_image.dart';
import 'on_chat_message.dart';
import 'on_chat_msg_instance.dart';
class IMPage extends StatefulWidget {
IMPage(Key key) : super(key: key);
@override
State<StatefulWidget> createState() {
return _IMPage();
}
}
class _IMPage extends State<IMPage> implements OnChatMessage {
ApiService apiService;
int pageNum = 1;
List<Message> messages = [];
Map<String, int> msgNumber = {
"1": 0,
"2": 0,
"3": 0,
"4": 0,
"5": 0,
"6": 0,
};
int state = 0;
List<String> conversationIds = [];
Map<String, Message> lastMessageMap = {};
Map<String, int> unreadCountMap = {};
Map<String, ImUser> contactMap = {};
int insertIndex = 0;
String selfUserId;
final RefreshController _refreshController = RefreshController();
@override
void onMessage(txt) {
// SmartDialog.showToast("列表 $txt", alignment: Alignment.center);
}
@override
void dispose() {
super.dispose();
OnChatMsgInstance.instance.onChatMessage = null;
socketClient.removeCallback(socketClient.userId);
}
@override
void initState() {
super.initState();
OnChatMsgInstance.instance.onChatMessage = this;
initSocketClient();
SharedPreferences.getInstance().then((value) {
apiService = ApiService(Dio(), token: value.getString("token"), context: context);
queryMsgStats();
});
}
initSocketClient() async {
SharedPreferences shared = await SharedPreferences.getInstance();
selfUserId = shared.getString("userId");
socketClient.addCallback(selfUserId, (Message message) {
if (conversationIds.contains(message.conversationId)) {
conversationIds.remove(message.conversationId);
}
conversationIds.insert(insertIndex, message.conversationId);
lastMessageMap[message.conversationId] = message;
listenerRefresh(message);
});
loadMessageList();
}
_refresh() {
pageNum = 1;
loadMessageList();
queryMsgStats();
}
listenerRefresh(Message message) async {
await sortConversation(lastMessageMap);
await queryUnreadCount(conversationIds);
debugPrint("messages_records : ${message.toJson()}");
if (contactMap[message.fromId] == null) {
queryMemberInfo([message.fromId]);
return;
}
refreshState();
}
loadMessageList() async {
messages = await hxDatabase.queryList();
lastMessageMap = messages.lGroupBy((p0) => p0.conversationId).mGroupItem(key: (p1) => num.parse(p1.time));
await queryImUserInfo(messages.map((e) => e.toId != selfUserId ? e.toId : e.fromId).toList());
await sortConversation(lastMessageMap);
await queryUnreadCount(conversationIds);
refreshState();
}
/// update conversation by time sort
sortConversation(lastMessageMap) async {
List<Message> sortMessages = lastMessageMap.values.toList();
sortMessages.sort((a, b) => (num.parse(b.time)).compareTo(num.parse(a.time)));
conversationIds = sortMessages
.map((e) => e.conversationId)
.toSet()
.toList();
}
/// update conversation unreadcount
queryUnreadCount(conversationIds) async {
unreadCountMap = await hxDatabase.messageUnreadCount(conversationIds);
debugPrint("unreadCountMap: $unreadCountMap");
}
/// update imuser info by mids
queryImUserInfo(userIds) async {
List<ImUser> contacts = (await hxDatabase.queryImUser(userIds)) ?? [];
if (contacts?.isEmpty ?? true) {
await queryMemberInfo(userIds);
return;
} else {
List<String> queryUserIds = userIds.where((u) => contacts.where((c) => c.mid == u).isEmpty).toList();
if (queryUserIds.isNotEmpty) {
await queryMemberInfo(queryUserIds);
return;
}
}
contactMap = contacts.lGroupBy((p0) => conversationId(p0.mid, selfUserId)).mGroupItem();
List<String> topConversationIds = [],notTopUserIds = [];
contactMap.forEach((key, value) {
if(value.isTop == 1)
topConversationIds.add(key);
else
notTopUserIds.add(key);
});
insertIndex = topConversationIds.length;
this.conversationIds = topConversationIds..addAll(notTopUserIds);
}
/// update one conversation last message ,and update conversation sort
void updateLastMessage(String conversationId) async {
Message message = await hxDatabase.lastMessage(conversationId);
debugPrint("lastmessage: $conversationId ${message?.content} ${message?.toJson()}");
if (message != null) {
lastMessageMap[conversationId] = message;
await sortConversation(lastMessageMap);
refreshState();
}
}
void updateUnreadCount() async {
unreadCountMap = await hxDatabase.messageUnreadCount(conversationIds);
refreshState();
}
refreshState() {
if(_refreshController.isRefresh)_refreshController.refreshCompleted();
if (mounted) setState(() {});
}
///批量查询用户信息
queryMemberInfo(List<String> mids) async {
if (mids.isEmpty) {
return;
}
BaseData<List<ImUser>> baseData = await apiService.memberInfoByIds({
"mids": mids,
}).catchError((error) {
SmartDialog.showToast(
AppUtils.dioErrorTypeToString(error.type),
alignment: Alignment.center,
);
});
if (baseData != null && baseData.isSuccess) {
if (baseData.data.isNotEmpty) {
baseData.data.forEach((element) async {
await hxDatabase.insertOrUpdateImUser(element.toJson());
});
baseData.data.forEach((element) {
if (contactMap[conversationId(element.mid, selfUserId)] == null) {
contactMap[conversationId(element.mid, selfUserId)] = element;
}
});
refreshState();
}
} else {
SmartDialog.showToast(baseData.msg, alignment: Alignment.center);
}
}
///App消息 统计各类消息数量
queryMsgStats() async {
if (apiService == null) {
SharedPreferences value = await SharedPreferences.getInstance();
apiService = ApiService(
Dio(),
context: context,
token: value.getString("token"),
);
}
BaseData<List<MsgStats>> baseData =
await apiService.stats().catchError((onError) {});
if (baseData != null && baseData.isSuccess) {
setState(() {
msgNumber.forEach((key, value) {
msgNumber[key] = 0;
});
baseData.data.forEach((element) {
if (msgNumber.containsKey(element.name)) {
msgNumber[element.name] = element.number;
}
});
});
_refreshController.loadComplete();
_refreshController.refreshCompleted();
}
EasyLoading.dismiss();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFFFFFFFF),
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFFD9FFDE),
Color(0xFFD9FFDE),
Color(0xFFFFFFFF),
Color(0xFFFFFFFF),
],
stops: [0, 0.2, 0.4, 1],
),
),
child: SmartRefresher(
enablePullDown: true,
enablePullUp: false,
header: MyHeader(),
physics: BouncingScrollPhysics(),
footer: CustomFooter(
loadStyle: LoadStyle.ShowWhenLoading,
builder: (BuildContext context, LoadStatus mode) {
return (messages.length == 0) ? Container() : MyFooter(mode);
},
),
controller: _refreshController,
onRefresh: _refresh,
onLoading: () {
_refresh();
},
child: SingleChildScrollView(
physics: BouncingScrollPhysics(),
child: Column(
children: [
Container(
padding: EdgeInsets.only(
top: MediaQuery.of(context).padding.top + 12.h,
bottom: 15.h,
right: 16.w,
left: 16.w,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Text(
S.of(context).xiaoxi,
style: TextStyle(
color: Colors.black,
fontSize: 18.sp,
fontWeight: MyFontWeight.bold,
),
),
),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
Navigator.of(context).pushNamed('/router/chat_friend_group')
.then((value) {
_refresh();
});
},
child: Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Color(0xFFFFFFFF),
borderRadius: BorderRadius.circular(20.r),
),
child: Image.asset(
"assets/image/friend_grouping.webp",
fit: BoxFit.fill,
height: 14.h,
width: 14.h,
),
),
),
],
),
),
imPageSearch(),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: (){
Navigator.of(context).pushNamed('/router/system_notice').then((value) {
setState(() {
msgNumber["2"] = 0;
msgNumber["3"] = 0;
});
});
},
child:messageItem("assets/image/icon_system_message_new.webp", S.of(context).xitongxiaoxi, (msgNumber["2"]+msgNumber["3"]).toString()),
),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: (){
Navigator.of(context).pushNamed('/router/system_details',
arguments: {"msgType": 4}).then((value) {
setState(() {
msgNumber["4"] = 0;
});
});
},
child:messageItem("assets/image/icon_gz.webp", S.of(context).guanzhu,msgNumber["4"].toString()),
),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: (){
Navigator.of(context).pushNamed('/router/system_details',
arguments: {"msgType": 6}).then((value) {
setState(() {
msgNumber["6"] = 0;
});
});
},
child:messageItem("assets/image/icon_pl.webp", S.of(context).pinglun, msgNumber["6"].toString()),
),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: (){
Navigator.of(context).pushNamed('/router/system_details',
arguments: {"msgType": 5}).then((value) {
setState(() {
msgNumber["5"] = 0;
});
});
},
child: messageItem("assets/image/icon_z.webp", S.of(context).dianzan, msgNumber["5"].toString()),
),
chatList(),
SizedBox(height:100.h)
],
),
),
),
),
);
}
///搜索
Widget imPageSearch() {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap:(){
Navigator.of(context).pushNamed('/router/im_search').then((value) {
_refresh();
});
},
child: Container(
margin: EdgeInsets.fromLTRB(16.w, 0, 16.w, 16.h),
padding: EdgeInsets.symmetric(vertical: 13.h),
decoration: BoxDecoration(
color: Color(0xFFFDFCFC),
borderRadius: BorderRadius.circular(4),
),
child: Row(
children: [
Padding(
padding: EdgeInsets.only(left: 15.w, right: 5.w),
child: Image.asset(
"assets/image/icon_search.webp",
width: 14.h,
height: 14.h,
color: Color(0xFF353535),
),
),
Text(
"搜索",
style: TextStyle(
color: Color(0xFFA29E9E),
fontSize: 16.sp,
fontWeight: MyFontWeight.regular,
),
),
],
)
),
);
}
bool _isDelEnter = false;
Map<String, ScrollController> idsController = {};
String lastScrollId;
double lastScrollOffset = 0;
bool _isScrollOpen = false;
///聊天列表
Widget chatList() {
return ListView(
padding: EdgeInsets.only(top:8.h),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
children: conversationIds.map((e) {
ScrollController scrollController;
if (idsController.containsKey(e))
scrollController = idsController[e];
else {
scrollController = ScrollController();
idsController[e] = scrollController;
}
scrollController.addListener(() {
if (scrollController.offset > 0) {
if (lastScrollId != null && lastScrollId != e)
idsController[lastScrollId].jumpTo(0);
if(lastScrollOffset < scrollController.offset){
scrollController.jumpTo(scrollController.position.maxScrollExtent);
_isScrollOpen = true;
}else if(lastScrollOffset > scrollController.offset && _isScrollOpen){
scrollController.jumpTo(0);
}
lastScrollId = e;
// scrollController.animateTo(
// lastScrollOffset == scrollController.position.maxScrollExtent
// ? 0
// : scrollController.position.maxScrollExtent,
// duration: Duration(milliseconds: 100),
// curve: Curves.ease);
lastScrollOffset = scrollController.offset;
} else {
if (lastScrollId == e) {
setState(() {
_isDelEnter = false;
_isScrollOpen = false;
});
}
}
});
int position = conversationIds.indexOf(e);
return SingleChildScrollView(
physics: BouncingScrollPhysics(),
controller: scrollController,
scrollDirection: Axis.horizontal,
child: Row(
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
Navigator.of(context).pushNamed(
'/router/chat_details_page',
arguments: {
"toUser": contactMap[conversationIds[position]],
},
).then((value) {
unreadCountMap[conversationIds[position]] = 0;
updateLastMessage(conversationIds[position]);
_refresh();
});
},
child: chatItem(conversationIds[position]),
),
GestureDetector(
child: Container(
color: Colors.red,
alignment: Alignment.center,
padding:
EdgeInsets.symmetric(vertical: 25.h, horizontal: 14.w),
child: Text(
_isDelEnter && lastScrollId == e
? "删除并清空"
: S.of(context).shanchu,
style: TextStyle(
color: Colors.white,
fontSize: 14.sp,
fontWeight: MyFontWeight.regular,
),
),
),
onTap: () async{
// showDelDialog(conversationIds[position]);
if (_isDelEnter) {
await hxDatabase.deleteByUser(conversationIds[position]);
lastScrollId = null;
_refresh();
} else {
Future.delayed(Duration(milliseconds: 100), () {
idsController[lastScrollId].animateTo(
idsController[lastScrollId].position.maxScrollExtent,
duration: Duration(milliseconds: 100),
curve: Curves.ease);
});
}
setState(() {
_isDelEnter = !_isDelEnter;
});
},
),
],
),
);
}).toList(),
);
}
Widget chatItem(conversationId) {
return Container(
padding: EdgeInsets.only(
left: 16.w, right: 17.w, bottom: 18.h,
),
width: MediaQuery.of(context).size.width,
child: Row(
children: [
MImage(
!contactMap.containsKey(conversationId)
? null
: contactMap[conversationId]?.avatar ?? "",
isCircle: true,
height: 54.h,
width: 54.h,
fit: BoxFit.cover,
errorSrc: "assets/image/default_1.webp",
fadeSrc: "assets/image/default_1.webp",
),
SizedBox(
width: 12.w,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
!contactMap.containsKey(conversationId)
? ""
: contactMap[conversationId]?.nickname ?? "",
// overflow: TextOverflow.fade,
maxLines: 1,
style: TextStyle(
fontSize: 16.sp,
color: Color(0xFF060606),
fontWeight: MyFontWeight.semi_bold,
),
),
),
Text(
lastMessageMap[conversationId]?.time != null
? AppUtils.timeFormatter(
DateTime.fromMillisecondsSinceEpoch(num.parse(
lastMessageMap[conversationId]?.time ?? "")))
: "",
style: TextStyle(
fontSize: 12.sp,
color: Color(0xFFA29E9E),
fontWeight: MyFontWeight.regular,
),
),
],
),
SizedBox(
height: 7.h,
),
Row(
children: [
Expanded(
child: Text(
lastMessageMap[conversationId]?.content ?? "",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 12.sp,
color: Color(0xFF353535),
fontWeight: MyFontWeight.regular,
),
),
),
if (unreadCountMap[conversationId] != null && unreadCountMap[conversationId] > 0)
Container(
width: 16,
height: 16,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
color: Color(0xFFFF441A),
),
child: RoundButton(
text: "${unreadCountMap[conversationId]}",
textColor: Colors.white,
fontWeight: MyFontWeight.regular,
backgroup: Color(0xFFFF441A),
fontSize: 10.sp,
radius: 100,
),
),
],
),
],
),
),
],
),
);
}
Widget messageItem(img, title, messageNum) {
return Container(
padding: EdgeInsets.only(top:8.h,bottom:8.h, left:16.w,right:15.w),
child: Column(
children: [
Row(
children: [
Image.asset(
img,
fit: BoxFit.fill,
),
SizedBox(
width: 12.w,
),
Text(
title,
style: TextStyle(
fontSize: 14.sp,
color: Color(0xFF060606),
fontWeight: MyFontWeight.semi_bold,
),
),
Spacer(),
if(messageNum != "0")
((double.tryParse(messageNum) < 100)?
Container(
width: 16,
height: 16,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
color: Color(0xFFFF441A),
),
child: RoundButton(
text:messageNum,
textColor: Colors.white,
fontWeight: MyFontWeight.regular,
backgroup: Color(0xFFFF441A),
fontSize: 10.sp,
radius: 100,
)):
Container(
padding: EdgeInsets.symmetric(horizontal:4.w,vertical:2.h),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100),
color: Color(0xFFFF441A),
),
child: RoundButton(
text:"99+",
textColor: Colors.white,
fontWeight: MyFontWeight.regular,
backgroup: Color(0xFFFF441A),
fontSize: 10.sp,
radius: 100,
))),
],
),
],
),
);
}
///确认删除弹窗
showDelDialog(conversationId) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
contentPadding: EdgeInsets.zero, // 移除默认内边距
content: Container(
// width: MediaQuery.of(context).size.width - 84,
height: 130.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(child:Container(
alignment:Alignment.center,
child: Text(
"删除并清空聊天记录",
style: TextStyle(
color: Color(0xFF060606),
fontSize: 16.sp,
fontWeight: MyFontWeight.bold,
),
),
),
),
// Spacer(),
Container(
height:1.h,
width: double.infinity,
color:Color(0xFFEDEDED),
),
Row(
children: [
Expanded(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
Navigator.of(context).pop();
},
child: Container(
child: Text(
"取消",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16.sp,
color: Color(0xFF060606),
)
)
)
)
),
Container(
height:45,
width: 1.w,
color: Color(0xFFEDEDED),
),
Expanded(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () async{
await hxDatabase.deleteByUser(conversationId);
_refresh();
Navigator.of(context).pop();
},
child: Text(
"确认",
textAlign: TextAlign.center,
style: TextStyle(
color: Color(0xFFFF370A),
),
),
),
),
],
)
],
),
),
);
},
);
}
}