diff --git a/lib/im/chat_details_page.dart b/lib/im/chat_details_page.dart index a5cd9b7a..d1f95b3f 100644 --- a/lib/im/chat_details_page.dart +++ b/lib/im/chat_details_page.dart @@ -10,6 +10,7 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:huixiang/constant.dart'; import 'package:huixiang/data/base_data.dart'; @@ -20,12 +21,9 @@ import 'package:huixiang/im/out/message.pb.dart'; import 'package:huixiang/main.dart'; import 'package:huixiang/retrofit/retrofit_api.dart'; import 'package:huixiang/utils/shared_preference.dart'; -import 'package:huixiang/view_widget/classic_header.dart'; import 'package:huixiang/view_widget/my_appbar.dart'; import 'package:image_pickers/image_pickers.dart'; import 'package:permission_handler/permission_handler.dart'; -import 'package:pull_to_refresh/pull_to_refresh.dart'; - import '../../community/release_dynamic.dart'; import '../../generated/l10n.dart'; import '../../utils/font_weight.dart'; @@ -62,7 +60,9 @@ class _ChatDetailsPage extends State UserInfo? userInfo; // final OnChatMessage? _tempOnChatMessage = OnChatMsgInstance.instance.onChatMessage; final ScrollController scrollController = ScrollController(); - GlobalKey animatedListKey = GlobalKey(); + GlobalKey newanimatedListKey = GlobalKey(); + GlobalKey loadanimatedListKey = GlobalKey(); + GlobalKey centerKey = GlobalKey(); String tex = ""; String selfUserId = ""; ImUser? _toUser; @@ -72,8 +72,11 @@ class _ChatDetailsPage extends State double inputWidgetHeight = 95; double dynamicInputHeight = 0; - RefreshController refreshController = RefreshController(); - List messages = []; + late StreamSubscription keyboardSubscription; + bool loading = false; + + List newmessages = []; + List loadmessages = []; int page = 0; loadMessageList() async { @@ -91,8 +94,8 @@ class _ChatDetailsPage extends State await refresh(); socketClient.addCallback(_toUser?.mid ?? '', (Message message) { - messages.add(message); - animatedListKey.currentState?.insertItem(messages.length - 1); + newmessages.add(message); + newanimatedListKey.currentState?.insertItem(newmessages.length - 1); if ((message.msgType == MsgType.VIDEO.value || message.msgType == MsgType.IMAGE.value) && (message.attach?.isNotEmpty ?? false)) { imageUrl.add(message.attach!); } @@ -102,7 +105,7 @@ class _ChatDetailsPage extends State }); }); - messageImageUrl(); + messageImageUrl(newmessages); // refreshState(); // jumpToBottom(); @@ -112,40 +115,45 @@ class _ChatDetailsPage extends State List? messagePage = await hxDatabase.queryUList(conversation, page: page + 1, pageSize: 10); debugPrint("refresh-message-list: ${messagePage?.length ?? ''} page: $page"); if (messagePage?.isEmpty ?? true) { - refreshController.refreshCompleted(); + // refreshController.refreshCompleted(); return 0; } else { - refreshController.refreshCompleted(); + // refreshController.refreshCompleted(); page += 1; } double height = 0; - await Future.forEach(messagePage!, (element) async { - height += await chatDetailsItemHeight(element); - }); - if (scrollController.hasClients) { - debugPrint("refresh-message-height: ${height} page: $page"); - scrollController.position.restoreOffset(height, initialRestore: true); - } + if (page == 1) { - messages.addAll(messagePage.reversed.toList()); - animatedListKey.currentState?.insertAllItems(0, messagePage.length); + await Future.forEach(messagePage!, (element) async { + height += await chatDetailsItemHeight(element); + }); + if (scrollController.hasClients) { + debugPrint("refresh-message-height: ${height} page: $page"); + scrollController.position.restoreOffset(height, initialRestore: true); + } + newmessages.addAll(messagePage!.reversed.toList()); + newanimatedListKey.currentState?.insertAllItems(0, messagePage.length); } else { - messages.insertAll(0, messagePage.reversed.toList()); - animatedListKey.currentState?.insertAllItems(0, messagePage.length, duration: 300.milliseconds); + loadmessages.addAll(messagePage!.toList()); + loadanimatedListKey.currentState?.insertAllItems(0, messagePage.length, duration: 300.milliseconds); } await messageShowTime(); return height; } /// 消息中的图片 - messageImageUrl() { - imageUrl = messages.where((e) => (e.msgType == MsgType.VIDEO.value || e.msgType == MsgType.IMAGE.value) && (e.attach?.isNotEmpty ?? false)).map((e) => "${e.attach}").toList(); + messageImageUrl(List messages) { + List images = messages.where((e) => (e.msgType == MsgType.VIDEO.value || e.msgType == MsgType.IMAGE.value) && (e.attach?.isNotEmpty ?? false)).map((e) => "${e.attach}").toList(); + if (images.isNotEmpty) { + imageUrl.addAll(images); + } } Future messageShowTime() async { List? messagePages = await hxDatabase.queryTList(conversation); - for (var value in messages) { - Message? message = messagePages?.firstWhere((element) => value.id == element.id, orElse: () => messages.first); + List tempMessages = newmessages + loadmessages; + for (var value in tempMessages) { + Message? message = messagePages?.firstWhere((element) => value.id == element.id, orElse: () => tempMessages.first); value.showTime = message != null; } } @@ -174,8 +182,6 @@ class _ChatDetailsPage extends State if (mounted) setState(() {}); } - late StreamSubscription keyboardSubscription; - @override void initState() { super.initState(); @@ -188,15 +194,18 @@ class _ChatDetailsPage extends State queryUser(); loadMessageList(); - scrollController.addListener(() { - debugPrint("scrollController-onChange: ${scrollController.position.pixels}"); - if (messages.length >= 10 && scrollController.position.pixels < -80) { - if (!loading) { - loading = true; - refresh().then((value) => loading = false); - } - } - }); + // scrollController.addListener(() { + // debugPrint("scrollController-onChange: ${scrollController.position.pixels}"); + // if (scrollController.position.pixels < -80) { + // if (!loading) { + // loading = true; + // refresh().then((value) { + // loading = false; + // messageImageUrl(loadmessages); + // }); + // } + // } + // }); var keyboardVisibilityController = KeyboardVisibilityController(); keyboardSubscription = keyboardVisibilityController.onChange.listen((bool visible) { @@ -237,8 +246,6 @@ class _ChatDetailsPage extends State }); } - bool loading = false; - void jumpToBottom() { Future.delayed(const Duration(milliseconds: 400), () { if (scrollController.hasClients && scrollController.position.pixels != scrollController.position.maxScrollExtent) { @@ -295,8 +302,8 @@ class _ChatDetailsPage extends State moreShow = false; } if (emojiShowing) { - if (inputWidgetHeight == 95) { - inputWidgetHeight += (keyboard == -1 ? 270 : keyboard); + if (inputWidgetHeight < 270) { + inputWidgetHeight = 95 + (keyboard == -1 ? 270 : keyboard); jumpToBottom(); } } else { @@ -360,8 +367,8 @@ class _ChatDetailsPage extends State String fileUrl = await qiniu.uploadFile(apiService!, path); socketClient.sendMessage(_toUser?.mid ?? '', fileUrl, attach: path, msgType: galleryMode == GalleryMode.image ? 2 : 4).then((value) { Message message = value; - messages.add(message); - animatedListKey.currentState?.insertItem(messages.length - 1); + newmessages.add(message); + newanimatedListKey.currentState?.insertItem(newmessages.length - 1); chatController.clear(); messageShowTime().then((value) { refreshState(); @@ -391,7 +398,8 @@ class _ChatDetailsPage extends State }, heightRatioWithWidth: 0.82, ); - }); + }, + ); } else if (await Permission.camera.isGranted) { if (await Permission.storage.isPermanentlyDenied) { showCupertinoDialog( @@ -409,7 +417,8 @@ class _ChatDetailsPage extends State }, heightRatioWithWidth: 0.82, ); - }); + }, + ); } else if (await Permission.storage.isGranted) { Media? medias = await ImagePickers.openCamera( cameraMimeType: CameraMimeType.photo, @@ -425,8 +434,8 @@ class _ChatDetailsPage extends State String fileUrl = await qiniu.uploadFile(apiService!, path); socketClient.sendMessage(_toUser?.mid ?? '', fileUrl, attach: path, msgType: 2).then((value) { Message message = value; - messages.add(message); - animatedListKey.currentState?.insertItem(messages.length - 1); + newmessages.add(message); + newanimatedListKey.currentState?.insertItem(newmessages.length - 1); chatController.clear(); messageShowTime().then((value) { refreshState(); @@ -564,33 +573,67 @@ class _ChatDetailsPage extends State ), ), ), - body: Container( - height: MediaQuery.of(context).size.height - kToolbarHeight - MediaQuery.of(context).padding.top - MediaQuery.of(context).viewInsets.bottom, - child: Column( - children: [ - Expanded( - child: chatDetailsList(), - flex: 1, - ), - input(), - ], - ), + body: + // Container( + // height: MediaQuery.of(context).size.height - kToolbarHeight - MediaQuery.of(context).padding.top - MediaQuery.of(context).viewInsets.bottom, + // child: + Column( + children: [ + Expanded( + child: chatDetailsList(), + flex: 1, + ), + input(), + ], ), + // ), ), ); } ///聊天列表 Widget chatDetailsList() { - return AnimatedList( - key: animatedListKey, - initialItemCount: messages.length, + return RefreshIndicator( + child: CustomScrollView( + center: centerKey, controller: scrollController, - physics: BouncingScrollPhysics()..frictionFactor(0.8), - itemBuilder: (context, position, animation) { - return chatDetailsItem(position); + physics: ClampingScrollPhysics(), + slivers: [ + SliverAnimatedList( + itemBuilder: (context, index, animation) { + Message message = loadmessages[index]; + return chatDetailsItem(message, index, loadanimatedListKey); + }, + key: loadanimatedListKey, + initialItemCount: loadmessages.length, + ), + SliverPadding( + padding: EdgeInsets.zero, + key: centerKey, + ), + SliverAnimatedList( + itemBuilder: (context, index, animation) { + Message message = newmessages[index]; + return chatDetailsItem(message, index, loadanimatedListKey); + }, + key: newanimatedListKey, + initialItemCount: newmessages.length, + ), + ], + ), + onRefresh: () async { + refresh(); }, ); + // return AnimatedList( + // key: animatedListKey, + // initialItemCount: messages.length, + // controller: scrollController, + // physics: BouncingScrollPhysics()..frictionFactor(0.8), + // itemBuilder: (context, position, animation) { + // return chatDetailsItem(position); + // }, + // ); } Future chatDetailsItemHeight(Message message) async { @@ -644,10 +687,10 @@ class _ChatDetailsPage extends State return textPainter.height; } - Widget chatDetailsItem(position) { - bool isSelf = messages[position].fromId == selfUserId; - bool isText = messages[position].msgType == 1; - bool isImage = messages[position].msgType == 2; + Widget chatDetailsItem(Message message, index, animationKey) { + bool isSelf = message.fromId == selfUserId; + bool isText = message.msgType == 1; + bool isImage = message.msgType == 2; GlobalKey _buttonKey = GlobalKey(); return Container( padding: EdgeInsets.only( @@ -656,12 +699,12 @@ class _ChatDetailsPage extends State ), child: Column( children: [ - if (messages[position].showTime) + if (message.showTime) Text( // position == messages.length-1 // ? AppUtils.milliTimeFormatter(DateTime.fromMillisecondsSinceEpoch( - int.parse(messages[position].time))) + int.parse(message.time))) // : AppUtils.getTimeDisplay( // DateTime.fromMillisecondsSinceEpoch( // int.parse(messages[position].time)), @@ -739,7 +782,7 @@ class _ChatDetailsPage extends State alignment: Alignment.centerLeft, child: GestureDetector( onLongPress: () { - showMessageMenu(context, _buttonKey, messages[position]); + showMessageMenu(context, _buttonKey, message, index, loadanimatedListKey); }, child: Container( decoration: BoxDecoration( @@ -760,7 +803,7 @@ class _ChatDetailsPage extends State horizontal: 12.w, ), child: Text( - tex = messages[position].content, + tex = message.content, textAlign: TextAlign.left, style: TextStyle( height: 1.2.h, @@ -789,7 +832,7 @@ class _ChatDetailsPage extends State crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - if (messages[position].state == 3) + if (message.state == 3) Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(100), @@ -808,7 +851,7 @@ class _ChatDetailsPage extends State ), ), ), - if (messages[position].state == 3) + if (message.state == 3) SizedBox( width: 12.w, ), @@ -817,7 +860,7 @@ class _ChatDetailsPage extends State alignment: Alignment.centerRight, child: InkWell( onLongPress: () { - showMessageMenu(context, _buttonKey, messages[position]); + showMessageMenu(context, _buttonKey, message, index, loadanimatedListKey); }, child: Container( decoration: BoxDecoration( @@ -838,7 +881,7 @@ class _ChatDetailsPage extends State horizontal: 12.w, ), child: Text( - tex = messages[position].content, + tex = message.content, textAlign: TextAlign.left, style: TextStyle( height: 1.2, @@ -901,18 +944,18 @@ class _ChatDetailsPage extends State ), GestureDetector( onLongPress: () { - showMessageMenu(context, _buttonKey, messages[position]); + showMessageMenu(context, _buttonKey, message, index, loadanimatedListKey); }, onTap: () { - if (imageUrl.contains(messages[position].attach) && (messages[position].attach?.isNotEmpty ?? false)) { - ImagePickers.previewImages(imageUrl, imageUrl.indexOf(messages[position].attach!)); + if (imageUrl.contains(message.attach) && (message.attach?.isNotEmpty ?? false)) { + ImagePickers.previewImages(imageUrl, imageUrl.indexOf(message.attach!)); } }, child: FutureBuilder( - future: fetchImageSize(messages[position]), + future: fetchImageSize(message), key: _buttonKey, builder: (BuildContext context, AsyncSnapshot snapshot) { - if (messages[position].attach?.isEmpty ?? true) { + if (message.attach?.isEmpty ?? true) { return SizedBox( width: 180.w, height: 200.h, @@ -920,7 +963,7 @@ class _ChatDetailsPage extends State } if (snapshot.connectionState == ConnectionState.waiting || snapshot.hasError) { return Image.file( - File(messages[position].attach!), + File(message.attach!), width: 180.w, height: 200.h, alignment: Alignment.centerLeft, @@ -928,7 +971,7 @@ class _ChatDetailsPage extends State ); } else { return Image.file( - File(messages[position].attach!), + File(message.attach!), width: snapshot.data.width, height: snapshot.data.height, alignment: Alignment.centerLeft, @@ -956,18 +999,18 @@ class _ChatDetailsPage extends State Spacer(), GestureDetector( onLongPress: () { - showMessageMenu(context, _buttonKey, messages[position]); + showMessageMenu(context, _buttonKey, message, index, loadanimatedListKey); }, onTap: () { - if (imageUrl.contains(messages[position].attach) && (messages[position].attach?.isNotEmpty ?? false)) { - ImagePickers.previewImages(imageUrl, imageUrl.indexOf(messages[position].attach!)); + if (imageUrl.contains(message.attach) && (message.attach?.isNotEmpty ?? false)) { + ImagePickers.previewImages(imageUrl, imageUrl.indexOf(message.attach!)); } }, child: FutureBuilder ( - future: fetchImageSize(messages[position]), + future: fetchImageSize(message), key: _buttonKey, builder: (BuildContext context, AsyncSnapshot snapshot) { - if (messages[position].attach?.isEmpty ?? true) { + if (message.attach?.isEmpty ?? true) { return SizedBox( width: 180.w, height: 200.h, @@ -975,7 +1018,7 @@ class _ChatDetailsPage extends State } if (snapshot.connectionState == ConnectionState.waiting || snapshot.hasError) { return Image.file( - File(messages[position].attach!), + File(message.attach!), width: 180.w, height: 200.h, alignment: Alignment.centerRight, @@ -983,7 +1026,7 @@ class _ChatDetailsPage extends State ); } else { return Image.file( - File(messages[position].attach!), + File(message.attach!), width: snapshot.data.width, height: snapshot.data.height, alignment: Alignment.centerRight, @@ -1029,119 +1072,225 @@ class _ChatDetailsPage extends State } /// 显示消息菜单 - showMessageMenu(context, _buttonKey, message) { + showMessageMenu(context, _buttonKey, message, index, animatedListKey) { RenderBox renderBox = _buttonKey.currentContext.findRenderObject() as RenderBox; Offset buttonPosition = renderBox.localToGlobal(Offset.zero); Size buttonSize = renderBox.size; - showMenu( - context: context, ///去除阴影 - elevation: 0, - color: Colors.transparent, - position: RelativeRect.fromLTRB( - buttonPosition.dx + (buttonSize.width - 180.w) / 2, // 居中对齐 - buttonPosition.dy - 68, - buttonPosition.dx + buttonSize.width, - buttonPosition.dy, - ), - /// 设置弹出菜单的显示位置 - items: [ - PopupMenuItem( - value: 1, - padding: EdgeInsets.zero, - child: Stack( - alignment: Alignment.bottomCenter, - children: [ - Container( - padding: EdgeInsets.only(bottom: 13.h), - child: Container( - width: 180.w, - decoration: BoxDecoration( - color: Color(0xFF2A2A2A), - borderRadius: BorderRadius.circular(6), - ), - padding: EdgeInsets.symmetric( - horizontal: 32.w, - vertical: 7.5.h, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - GestureDetector( - onTap: () { - setState(() { - this.copy(message.content); - Navigator.pop(context); - }); - }, - child: Column( - children: [ - Image.asset( - "assets/image/icon_chat_copy.webp", - height: 16, - width: 16, - ), - SizedBox( - height: 2.h, - ), - Text( - "复制", - textAlign: - TextAlign.center, - style: TextStyle( - color: Colors.white, - fontSize: 12.sp, - fontWeight: MyFontWeight - .regular, - ), + // showMenu( + // context: context, ///去除阴影 + // elevation: 0, + // color: Colors.transparent, + // position: RelativeRect.fromLTRB( + // buttonPosition.dx + (buttonSize.width - 180.w) / 2, // 居中对齐 + // buttonPosition.dy - 68, + // buttonPosition.dx + buttonSize.width, + // buttonPosition.dy, + // ), + // /// 设置弹出菜单的显示位置 + // items: [ + // PopupMenuItem( + // value: 1, + // padding: EdgeInsets.zero, + // child: Stack( + // alignment: Alignment.bottomCenter, + // children: [ + // Container( + // padding: EdgeInsets.only(bottom: 13.h), + // child: Container( + // width: 180.w, + // decoration: BoxDecoration( + // color: Color(0xFF2A2A2A), + // borderRadius: BorderRadius.circular(6), + // ), + // padding: EdgeInsets.symmetric( + // horizontal: 32.w, + // vertical: 7.5.h, + // ), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // crossAxisAlignment: CrossAxisAlignment.center, + // children: [ + // GestureDetector( + // onTap: () { + // setState(() { + // this.copy(message.content); + // Navigator.pop(context); + // }); + // }, + // child: Column( + // children: [ + // Image.asset( + // "assets/image/icon_chat_copy.webp", + // height: 16, + // width: 16, + // ), + // SizedBox( + // height: 2.h, + // ), + // Text( + // "复制", + // textAlign: + // TextAlign.center, + // style: TextStyle( + // color: Colors.white, + // fontSize: 12.sp, + // fontWeight: MyFontWeight + // .regular, + // ), + // ), + // ], + // ), + // ), + // GestureDetector( + // onTap: () async { + // await hxDatabase.deleteByMsgId("${message.id}"); + // if (animatedListKey == loadanimatedListKey) { + // loadmessages.remove(message); + // animatedListKey.currentState?.removeItem(index, (context, animation) {return Container();}); + // } else { + // newmessages.remove(message); + // animatedListKey.currentState?.removeItem(index, (context, animation) {return Container();}); + // } + // Navigator.pop(context); + // }, + // child: Column( + // children: [ + // Image.asset( + // "assets/image/icon_chat_delete.webp", + // height: 16, + // width: 16, + // ), + // SizedBox( + // height: 2.h, + // ), + // Text( + // S.of(context).shanchu, + // textAlign: TextAlign.center, + // style: TextStyle( + // color: Colors.white, + // fontSize: 12.sp, + // fontWeight: MyFontWeight.regular, + // ), + // ), + // ], + // ), + // ), + // ], + // ), + // ), + // ), + // Image.asset( + // "assets/image/icon_copy_j.webp", + // height: 17, + // width: 17, + // ), + // ], + // ), + // ), + // ], + // ); + + SmartDialog.showAttach( + targetContext: _buttonKey.currentContext, + builder: (ctx) { + return Stack( + alignment: Alignment.bottomCenter, + children: [ + Container( + padding: EdgeInsets.only(bottom: 13.h), + child: Container( + width: 180.w, + decoration: BoxDecoration( + color: Color(0xFF2A2A2A), + borderRadius: BorderRadius.circular(6), + ), + padding: EdgeInsets.symmetric( + horizontal: 32.w, + vertical: 7.5.h, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + GestureDetector( + onTap: () { + setState(() { + this.copy(message.content); + Navigator.pop(context); + }); + }, + child: Column( + children: [ + Image.asset( + "assets/image/icon_chat_copy.webp", + height: 16, + width: 16, + ), + SizedBox( + height: 2.h, + ), + Text( + "复制", + textAlign: + TextAlign.center, + style: TextStyle( + color: Colors.white, + fontSize: 12.sp, + fontWeight: MyFontWeight + .regular, ), - ], - ), + ), + ], ), - GestureDetector( - onTap: () async { - await hxDatabase.deleteByMsgId("${message.id}"); - int index = messages.indexOf(message); - messages.remove(message); + ), + GestureDetector( + onTap: () async { + await hxDatabase.deleteByMsgId("${message.id}"); + if (animatedListKey == loadanimatedListKey) { + loadmessages.remove(message); animatedListKey.currentState?.removeItem(index, (context, animation) {return Container();}); - Navigator.pop(context); - }, - child: Column( - children: [ - Image.asset( - "assets/image/icon_chat_delete.webp", - height: 16, - width: 16, - ), - SizedBox( - height: 2.h, - ), - Text( - S.of(context).shanchu, - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - fontSize: 12.sp, - fontWeight: MyFontWeight.regular, - ), + } else { + newmessages.remove(message); + animatedListKey.currentState?.removeItem(index, (context, animation) {return Container();}); + } + Navigator.pop(context); + }, + child: Column( + children: [ + Image.asset( + "assets/image/icon_chat_delete.webp", + height: 16, + width: 16, + ), + SizedBox( + height: 2.h, + ), + Text( + S.of(context).shanchu, + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + fontSize: 12.sp, + fontWeight: MyFontWeight.regular, ), - ], - ), + ), + ], ), - ], - ), + ), + ], ), ), - Image.asset( - "assets/image/icon_copy_j.webp", - height: 17, - width: 17, - ), - ], - ), - ), - ], + ), + Image.asset( + "assets/image/icon_copy_j.webp", + height: 17, + width: 17, + ), + ], + ); + }, ); + } Future fetchImageSize(Message message) async { @@ -1240,8 +1389,8 @@ class _ChatDetailsPage extends State socketClient.sendMessage(_toUser?.mid ?? '', commentText) .then((value) { Message message = value; - messages.add(message); - animatedListKey.currentState?.insertItem(messages.length - 1); + newmessages.add(message); + newanimatedListKey.currentState?.insertItem(newmessages.length - 1); chatController.clear(); messageShowTime().then((value) { refreshState();