import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:dio/dio.dart'; import 'package:emoji_picker_flutter/emoji_picker_flutter.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; 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'; import 'package:huixiang/data/im_user.dart'; import 'package:huixiang/data/user_info.dart'; import 'package:huixiang/im/database/message.dart'; import 'package:huixiang/im/out/message.pb.dart'; import 'package:huixiang/main.dart'; import 'package:huixiang/retrofit/retrofit_api.dart'; import 'package:huixiang/utils/constant.dart'; import 'package:huixiang/utils/shared_preference.dart'; import 'package:huixiang/view_widget/my_appbar.dart'; import 'package:image_pickers/image_pickers.dart'; import 'package:permission_handler/permission_handler.dart'; import '../../community/release_dynamic.dart'; import '../../generated/l10n.dart'; import '../../utils/font_weight.dart'; import '../utils/flutter_utils.dart'; import '../view_widget/custom_image.dart'; import '../view_widget/request_permission.dart'; class ChatDetailsPage extends StatefulWidget { final Map? arguments; ChatDetailsPage({this.arguments}); @override State createState() { return _ChatDetailsPage(); } } class _ChatDetailsPage extends State with WidgetsBindingObserver { ApiService? apiService; List mediaPaths = []; int selectCount = 9; int dynamicType = 0; final TextEditingController chatController = TextEditingController(); bool isKeyBoardShow = false; bool emojiShowing = false; bool moreShow = false; double keyboard = -1; var commentFocus = FocusNode(); String hintText = "输入消息内容..."; UserInfo? userInfo; // final OnChatMessage? _tempOnChatMessage = OnChatMsgInstance.instance.onChatMessage; final ScrollController scrollController = ScrollController(); GlobalKey newanimatedListKey = GlobalKey(); GlobalKey loadanimatedListKey = GlobalKey(); GlobalKey centerKey = GlobalKey(); String tex = ""; String selfUserId = ""; ImUser? _toUser; late String conversation; List imageUrl = []; double inputWidgetHeight = 95; double dynamicInputHeight = 0; late StreamSubscription keyboardSubscription; bool loading = false; List newmessages = []; List loadmessages = []; int page = 0; loadMessageList() async { selfUserId = SharedInstance.instance.userId; debugPrint("selfUserId: $selfUserId _toUser?.mid: ${_toUser?.mid} "); conversation = conversationId(selfUserId, _toUser?.mid); ImUser? imUser = await hxDatabase.queryImUserById(conversation); if (imUser == null) { await hxDatabase.insertOrUpdateImUser(_toUser!.toJson()); } // unread msg 2 read state await hxDatabase.readMessage(conversation); await refresh(); socketClient.addCallback(_toUser?.mid ?? '', (Message message) { 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!); } messageShowTime().then((value) { refreshState(); jumpToBottom(); }); }); messageImageUrl(newmessages); // refreshState(); // jumpToBottom(); } Future refresh() async { 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(); return 0; } else { // refreshController.refreshCompleted(); page += 1; } double height = 0; if (page == 1) { 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 { loadmessages.addAll(messagePage!.toList()); loadanimatedListKey.currentState?.insertAllItems(0, messagePage.length, duration: 300.milliseconds); } await messageShowTime(); return height; } /// 消息中的图片 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); 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; } } ///查询个人信息 queryUser() async { apiService = ApiService(Dio(), context: context, token: SharedInstance.instance.token); userInfo = UserInfo.fromJson(jsonDecode(SharedInstance.instance.userJson)); if (userInfo?.phone?.isNotEmpty ?? false) { setState(() {}); return; } BaseData? baseData = await apiService?.queryInfo().catchError((onError) { return BaseData()..isSuccess = false; }); if ((baseData?.isSuccess ?? false) && baseData?.data != null) { setState(() { userInfo = baseData!.data; }); SharedInstance.instance.saveUser(baseData!.data!); } } refreshState() { if (mounted) setState(() {}); } @override void initState() { super.initState(); _toUser = widget.arguments?["toUser"]; // OnChatMsgInstance.instance.onChatMessage = this; WidgetsBinding.instance.addObserver(this); commentFocus.addListener(_focusNodeListener); queryUser(); loadMessageList(); // 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) { isKeyBoardShow = visible; debugPrint("keyboardVisibility-onChange: ${isKeyBoardShow}"); if (isKeyBoardShow) { if (!emojiShowing && !moreShow) { chatController.clear(); } } else { inputWidgetHeight = 95; } jumpToBottom(); // if (needShowSmiley && !visible) { // needShowSmiley = false; // setState(() { // emojiShowing = true; // }); // } // if (needHideSmiley && visible) { // needHideSmiley = false; // setState(() { // emojiShowing = false; // }); // } // if (needShowMore && !visible) { // needShowMore = false; // setState(() { // moreShow = true; // }); // } // if (needHideMore && visible) { // needHideMore = false; // setState(() { // moreShow = false; // }); // } }); } void jumpToBottom() { Future.delayed(const Duration(milliseconds: 400), () { if (scrollController.hasClients && scrollController.position.pixels != scrollController.position.maxScrollExtent) { scrollController.position.animateTo(scrollController.position.maxScrollExtent, duration: 250.milliseconds, curve: Curves.easeInOut); } }); } @override void didChangeMetrics() { super.didChangeMetrics(); dynamicInputHeight = MediaQuery.of(context).viewInsets.bottom; if (dynamicInputHeight > 0 && dynamicInputHeight > keyboard) { keyboard = dynamicInputHeight; } } @override void dispose() { super.dispose(); // OnChatMsgInstance.instance.onChatMessage = _tempOnChatMessage; keyboardSubscription.cancel(); WidgetsBinding.instance.removeObserver(this); commentFocus.removeListener(_focusNodeListener); scrollController.dispose(); socketClient.removeCallback(_toUser?.mid ?? ''); } void _focusNodeListener() { /*if (_focusNode.hasFocus || _focusNode.consumeKeyboardToken()){ setState(() { smileyPadGone = true; }); }*/ } ///输入框点击事件 _onTextFieldTap() { if (emojiShowing) { emojiShowing = false; } if (moreShow) { moreShow = false; } } ///表情包点击事件 _onSmileyTap() { emojiShowing = !emojiShowing; if (isKeyBoardShow && commentFocus.hasFocus) { commentFocus.unfocus(); } if (moreShow) { moreShow = false; } if (emojiShowing) { if (inputWidgetHeight < 270) { inputWidgetHeight = 95 + (keyboard == -1 ? 270 : keyboard); jumpToBottom(); } } else { inputWidgetHeight = 95; } setState(() {}); } ///更多点击事件 _onMoreTap() { moreShow = !moreShow; if (isKeyBoardShow && commentFocus.hasFocus) { commentFocus.unfocus(); } if (emojiShowing) { emojiShowing = false; } if (moreShow) { if (inputWidgetHeight == 95) { inputWidgetHeight += (keyboard == -1 ? 167 : keyboard); jumpToBottom(); } } else { inputWidgetHeight = 95; } setState(() {}); } ///图片/视频选择 Future getImageOrVideo(GalleryMode galleryMode) async { if (selectCount == 0) return; mediaPaths.clear(); List medias = await ImagePickers.pickerPaths( galleryMode: galleryMode, selectCount: (galleryMode == GalleryMode.video) ? 1 : selectCount, showGif: true, showCamera: false, compressSize: 500, uiConfig: UIConfig( uiThemeColor: Color(0xFFFFFFFF), ), cropConfig: CropConfig( enableCrop: false, width: 200, height: 200, ), ); mediaPaths.addAll(medias.map((e) => Medias(e)).toList()); selectCount = 9 - mediaPaths.length; if (mediaPaths.length > 0) { if (galleryMode == GalleryMode.image) { dynamicType = 1; } else { dynamicType = 2; } List filePath = mediaPaths.map((e) => "${e.path}").toList(); Future.forEach(filePath.toSet(), (path) async { 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; newmessages.add(message); newanimatedListKey.currentState?.insertItem(newmessages.length - 1); chatController.clear(); messageShowTime().then((value) { refreshState(); jumpToBottom(); }); }); }); } setState(() {}); } ///拍照 openCamera() async { if (await Permission.camera.isPermanentlyDenied) { showCupertinoDialog( context: context, builder: (context) { return RequestPermission( "assets/image/icon_camera_permission_tips.webp", S.of(context).ninxiangjiquanxianweikaiqi, S.of(context).weilekaipaizhaoxuanzhetouxiang, S.of(context).kaiqiquanxian, (result) async { if (result) { await openAppSettings(); } }, heightRatioWithWidth: 0.82, ); }, ); } else if (await Permission.camera.isGranted) { if (await Permission.storage.isPermanentlyDenied) { showCupertinoDialog( context: context, builder: (context) { return RequestPermission( "assets/image/icon_storage_permission_tips.webp", "您未开启存储权限,请点击开启", "为了您可以在使用过程中访问您设备上的照片、媒体内容和文件,请您开启存储使用权限", S.of(context).kaiqiquanxian, (result) async { if (result) { await openAppSettings(); } }, heightRatioWithWidth: 0.82, ); }, ); } else if (await Permission.storage.isGranted) { Media? medias = await ImagePickers.openCamera( cameraMimeType: CameraMimeType.photo, cropConfig: CropConfig( enableCrop: true, width: 200, height: 200, ), compressSize: 500, ); if (medias == null) return; String path = medias.path ?? ''; String fileUrl = await qiniu.uploadFile(apiService!, path); socketClient.sendMessage(_toUser?.mid ?? '', fileUrl, attach: path, msgType: 2).then((value) { Message message = value; newmessages.add(message); newanimatedListKey.currentState?.insertItem(newmessages.length - 1); chatController.clear(); messageShowTime().then((value) { refreshState(); jumpToBottom(); }); }); } else { showCameraTipsAlertDialog(context); return false; } } else { showCameraTipsAlertDialog(context); return false; } } ///拍照权限说明 showCameraTipsAlertDialog(context) async { //显示对话框 showDialog( context: context, barrierDismissible: false, barrierColor: null, builder: (BuildContext context) { return Column( children: [ Container( width: double.infinity, padding: EdgeInsets.all(15), margin: EdgeInsets.all(15), decoration: new BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(10), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: EdgeInsets.only( right: 10.w, ), child: Icon( Icons.add_photo_alternate_outlined, color: Colors.black, size: 22, )), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( "相机权限使用说明", style: TextStyle( fontSize: 18.sp, fontWeight: MyFontWeight.regular, color: Colors.black, ), ), SizedBox( height: 3.h, ), Text( "实现您扫码、拍摄等功能。", style: TextStyle( fontSize: 13.sp, height: 1.2, fontWeight: MyFontWeight.regular, color: Colors.black, ), ), ], ), ), ], ), ) ], mainAxisSize: MainAxisSize.min, ); }, ); await Permission.camera.request(); Navigator.of(context).pop(); if (await Permission.camera.isGranted) { openCamera(); } } @override Widget build(BuildContext ctx) { return GestureDetector( behavior: HitTestBehavior.translucent, onTap: () { setState(() { emojiShowing = false; isKeyBoardShow = false; moreShow = false; }); inputWidgetHeight = 95; FocusScope.of(context).requestFocus(FocusNode()); // jumpToBottom(); }, child: Scaffold( resizeToAvoidBottomInset: true, backgroundColor: Color(0xFFF6F6F6), appBar: MyAppBar( title: _toUser?.nickname, titleColor: Color(0xFF0D0D0D), titleSize: 17.sp, leading: true, leadingColor: Colors.black, toolbarHeight: kToolbarHeight, action: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () async { await Navigator.of(context).pushNamed( '/router/chat_setting', arguments: { "userId": _toUser?.mid, }, ); page = 0; refresh().then((value) { refreshState(); }); }, child: Container( alignment: Alignment.center, child: Icon( Icons.more_horiz, color: Colors.black, size: 30, ), ), ), ), 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 RefreshIndicator( child: CustomScrollView( center: centerKey, controller: scrollController, 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 { bool isSelf = message.fromId == selfUserId; bool isText = message.msgType == 1; bool isImage = message.msgType == 2; double itemHeight = 32.0; if (message.showTime) { itemHeight += 10.sp; } if (!isSelf && isText) { itemHeight += 10.h; double itemWidth = MediaQuery.of(context).size.width - 92.w - 44; double textH = textHeight(message.content, 17.sp, 1.2, itemWidth) + 12; itemHeight += textH > 44 ? textH : 44; } if (isSelf && isText) { itemHeight += 10.h; double itemWidth = MediaQuery.of(context).size.width - 88.w - 44; if (message.state == 3) { itemWidth -= 20; itemWidth -= 12.w; } double textH = textHeight(message.content, 17.sp, 1.2, itemWidth) + 12; itemHeight += textH > 44 ? textH : 44; } if (!isSelf && isImage) { itemHeight += 10.h; Size size = await fetchImageSize(message); itemHeight += size.height; } if (isSelf && isImage) { itemHeight += 10.h; Size size = await fetchImageSize(message); itemHeight += size.height; } return itemHeight; } textHeight(text, fontSize, height, width) { final TextPainter textPainter = TextPainter( text: TextSpan( text: text, style: TextStyle( fontSize: fontSize, height: height, ), ), textDirection: TextDirection.ltr, )..layout(minWidth: 0, maxWidth: width); return textPainter.height; } 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( top: 16, bottom: 16, ), child: Column( children: [ if (message.showTime) Text( // position == messages.length-1 // ? AppUtils.milliTimeFormatter(DateTime.fromMillisecondsSinceEpoch( int.parse(message.time))) // : AppUtils.getTimeDisplay( // DateTime.fromMillisecondsSinceEpoch( // int.parse(messages[position].time)), // DateTime.fromMillisecondsSinceEpoch( // int.parse(messages[position+1].time))) , textAlign: TextAlign.center, style: TextStyle( color: Color(0xFFA29E9E), fontSize: 10.sp, height: 1.0, fontWeight: MyFontWeight.regular, ), ), // if (messages.indexOf(message) == (messages.length - 1)) // Padding( // padding: EdgeInsets.only(top: 10.h, bottom: 24.h), // child: Text( // "在对方未回复或关注你之前,你只能发送一条信息", // textAlign: TextAlign.center, // style: TextStyle( // color: Color(0xFFA29E9E), // fontSize: 10.sp, // fontWeight: MyFontWeight.regular, // ), // ), // ), // if (messages.indexOf(message) == (messages.length - 1)) // SizedBox( // height: 16.h, // ), /// not self if (!isSelf && isText) SizedBox( height: 10.h, ), if (!isSelf && isText) Padding( padding: EdgeInsets.only(left: 17.w, right: 39.w), child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { if (_toUser?.mid?.isEmpty ?? true) { "用户信息错误".toast; return; } Navigator.of(context).pushNamed( '/router/personal_page', arguments: { "memberId": _toUser?.mid ?? "", "inletType": 1 }, ); }, child: MImage( _toUser?.avatar ?? '', isCircle: true, height: 44, width: 44, fit: BoxFit.cover, errorSrc: "assets/image/default_1.webp", fadeSrc: "assets/image/default_1.webp", ), ), SizedBox( width: 12.w, ), Expanded( child: Container( alignment: Alignment.centerLeft, child: GestureDetector( onLongPress: () { showMessageMenu(context, _buttonKey, message, index, loadanimatedListKey); }, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(6), color: Color(0xFFFFFFFF), boxShadow: [ BoxShadow( color: Color(0xFFA8A3A3).withAlpha(12), offset: Offset(0, 4), blurRadius: 4, spreadRadius: 0, ), ], ), key: _buttonKey, padding: EdgeInsets.symmetric( vertical: 6.h, horizontal: 12.w, ), child: Text( tex = message.content, textAlign: TextAlign.left, style: TextStyle( height: 1.2.h, color: Color(0XFF0D0D0D), fontSize: 17.sp, fontWeight: MyFontWeight.medium, ), ), ), ), ), ), ], ), ), /// self if (isSelf && isText) SizedBox( height: 10.h, ), if (isSelf && isText) Padding( padding: EdgeInsets.only(left: 36.w, right: 16.w), child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ if (message.state == 3) Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(100), color: Color(0xFFFF441A), ), width: 20, height: 20, alignment: Alignment.center, child: Text( "!", textAlign: TextAlign.left, style: TextStyle( color: Colors.white, fontSize: 17.sp, fontWeight: MyFontWeight.regular, ), ), ), if (message.state == 3) SizedBox( width: 12.w, ), Expanded( child: Container( alignment: Alignment.centerRight, child: InkWell( onLongPress: () { showMessageMenu(context, _buttonKey, message, index, loadanimatedListKey); }, child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(6), color: Color(0xFF32A060), boxShadow: [ BoxShadow( color: Color(0xFFA8A3A3).withAlpha(12), offset: Offset(0, 4), blurRadius: 4, spreadRadius: 0, ), ], ), key: _buttonKey, padding: EdgeInsets.symmetric( vertical: 6.h, horizontal: 12.w, ), child: Text( tex = message.content, textAlign: TextAlign.left, style: TextStyle( height: 1.2, color: Colors.white, fontSize: 17.sp, fontWeight: MyFontWeight.medium, ), ), ), ), ), ), SizedBox( width: 12.w, ), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { Navigator.of(context).pushNamed( '/router/personal_page', arguments: { "memberId": selfUserId, "inletType": 1, }, ); }, child: MImage( userInfo?.headimg ?? "", isCircle: true, height: 44, width: 44, fit: BoxFit.cover, errorSrc: "assets/image/default_1.webp", fadeSrc: "assets/image/default_1.webp", ), ), ], ), ), /// not self image 图片 if (!isSelf && isImage) Padding( padding: EdgeInsets.only( left: 17.w, right: 39.w, top: 10.h, ), child: Row( children: [ MImage( userInfo?.headimg ?? "", isCircle: true, width: 44, height: 44, fit: BoxFit.cover, errorSrc: "assets/image/default_1.webp", fadeSrc: "assets/image/default_1.webp", ), SizedBox( width: 12.w, ), GestureDetector( onLongPress: () { showMessageMenu(context, _buttonKey, message, index, loadanimatedListKey); }, onTap: () { if (imageUrl.contains(message.attach) && (message.attach?.isNotEmpty ?? false)) { ImagePickers.previewImages(imageUrl, imageUrl.indexOf(message.attach!)); } }, child: FutureBuilder( future: fetchImageSize(message), key: _buttonKey, builder: (BuildContext context, AsyncSnapshot snapshot) { if (message.attach?.isEmpty ?? true) { return SizedBox( width: 180.w, height: 200.h, ); } if (snapshot.connectionState == ConnectionState.waiting || snapshot.hasError) { return Image.file( File(message.attach!), width: 180.w, height: 200.h, alignment: Alignment.centerLeft, fit: BoxFit.contain, ); } else { return Image.file( File(message.attach!), width: snapshot.data.width, height: snapshot.data.height, alignment: Alignment.centerLeft, fit: BoxFit.cover, ); } }, ), ), Spacer(), ], ), ), /// self image 图片 if (isSelf && isImage) Padding( padding: EdgeInsets.only( left: 39.w, right: 17.w, top: 10.h, ), child: Row( children: [ Spacer(), GestureDetector( onLongPress: () { showMessageMenu(context, _buttonKey, message, index, loadanimatedListKey); }, onTap: () { if (imageUrl.contains(message.attach) && (message.attach?.isNotEmpty ?? false)) { ImagePickers.previewImages(imageUrl, imageUrl.indexOf(message.attach!)); } }, child: FutureBuilder ( future: fetchImageSize(message), key: _buttonKey, builder: (BuildContext context, AsyncSnapshot snapshot) { if (message.attach?.isEmpty ?? true) { return SizedBox( width: 180.w, height: 200.h, ); } if (snapshot.connectionState == ConnectionState.waiting || snapshot.hasError) { return Image.file( File(message.attach!), width: 180.w, height: 200.h, alignment: Alignment.centerRight, fit: BoxFit.contain, ); } else { return Image.file( File(message.attach!), width: snapshot.data.width, height: snapshot.data.height, alignment: Alignment.centerRight, fit: BoxFit.contain, ); } }, ), ), SizedBox( width: 12.w, ), MImage( userInfo?.headimg ?? "", isCircle: true, width: 44, height: 44, fit: BoxFit.cover, errorSrc: "assets/image/default_1.webp", fadeSrc: "assets/image/default_1.webp", ), ], ), ), /// no reply long time // if (messages.indexOf(message) == 0) // Padding( // padding: EdgeInsets.only(left: 17.w, right: 17.w, top: 24.h), // child: Text( // "由于对方没有回复你,你只能发送一条消息,需要对方关注或回复后才能恢复正常聊天", // textAlign: TextAlign.center, // style: TextStyle( // color: Color(0xFFA29E9E), // fontSize: 10.sp, // fontWeight: MyFontWeight.regular, // ), // ), // ), ], ), ); } /// 显示消息菜单 showMessageMenu(context, _buttonKey, message, index, animatedListKey) { SmartDialog.showAttach( targetContext: _buttonKey.currentContext, alignment: Alignment.topCenter, maskColor: Colors.transparent, animationType: SmartAnimationType.fade, builder: (ctx) { return Stack( alignment: Alignment.bottomCenter, children: [ Container( padding: EdgeInsets.only(bottom: 16), child: Container( decoration: BoxDecoration( color: Color(0xFF2A2A2A), borderRadius: BorderRadius.circular(6), ), padding: EdgeInsets.symmetric( horizontal: 24.w, vertical: 7.5.h, ), child: IntrinsicWidth( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ GestureDetector( onTap: () { this.copy(message.content); SmartDialog.dismiss(status: SmartStatus.attach); }, 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, ), ), ], ), ), 24.w.vd, 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();}); } SmartDialog.dismiss(status: SmartStatus.attach); }, 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, fit: BoxFit.fill, width: 17, ), ], ); }, ); } Future fetchImageSize(Message message) async { String? imageLocalPath = message.attach; String imageUrl = message.content; debugPrint("$imageLocalPath"); debugPrint("$imageUrl"); if (imageLocalPath?.isEmpty ?? true) { return Future.value(Size.zero); } File file = File(imageLocalPath!); if (!(await file.exists())) { await qiniu.downloadFile(imageUrl, savePath: imageLocalPath); } Size size = Size.zero; String? sizeStr = SharedInstance.instance.getSize(imageLocalPath); if (sizeStr != null && sizeStr != "") { Map sizeMap = jsonDecode(sizeStr); size = Size(sizeMap["width"], sizeMap["height"]); if (size != Size.zero) { return size; } } Completer completer = Completer(); Image.file(file) .image .resolve(ImageConfiguration()) .addListener(ImageStreamListener((image, synchronousCall) { size = Size((image.image.width).toDouble(), (image.image.height).toDouble()); size = resizeImage(size); Map sizeMap = { "width": size.width, "height": size.height, }; SharedInstance.instance.setSize(imageLocalPath, jsonEncode(sizeMap)); completer.complete(size); })); return completer.future; } resizeImage(Size size) { if (size.width > 180 || size.height > 200) { if (size.width > size.height) { int height = size.height ~/ (size.width ~/ 180); size = Size(180, height.toDouble()); } else { int width = size.width ~/ (size.height ~/ 200); size = Size(width.toDouble(), 200); } } return size; } ///富文本输入框 Widget input() { return Container( color: Color(0xFFFDFCFC), // duration: 400.milliseconds, height: inputWidgetHeight, padding: EdgeInsets.only(top: 14, bottom: 15), child: Column( children: [ ///输入框 Row( children: [ Expanded( flex: 1, child: Container( margin: EdgeInsets.only( left: 20.w, ), decoration: BoxDecoration( color: Color(0xFFF6F6F6), borderRadius: BorderRadius.circular(6), ), child: Container( margin: EdgeInsets.only(left: 17.w), alignment: Alignment.topLeft, child: TextField( textInputAction: TextInputAction.send, onTap: () { moreShow = false; _onTextFieldTap(); }, onEditingComplete: () { var commentText = chatController.text; if (commentText.trim() == "") { return; } socketClient.sendMessage(_toUser?.mid ?? '', commentText) .then((value) { Message message = value; newmessages.add(message); newanimatedListKey.currentState?.insertItem(newmessages.length - 1); chatController.clear(); messageShowTime().then((value) { refreshState(); jumpToBottom(); }); }); }, maxLines: 8, minLines: 1, focusNode: commentFocus, controller: chatController, decoration: InputDecoration( border: InputBorder.none, hintText: hintText, hintStyle: TextStyle( fontSize: 14.sp, color: Color(0xFF868686), ), ), ), ), ), ), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { _onSmileyTap(); }, child: Container( padding: EdgeInsets.only(left: 15.w, right: 8.w), child: Image.asset( "assets/image/icon_chat_emo.webp", height: 26, width: 26, ), ), ), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { _onMoreTap(); }, child: Container( padding: EdgeInsets.only(left: 8.w, right: 19.w), child: Image.asset( "assets/image/chat_more.webp", height: 26, width: 26, ), ), ), ], ), SizedBox( height: 16, ), ///表情 if (emojiShowing) Container( height: keyboard == -1 ? 270 : keyboard, width: MediaQuery.of(context).size.width, child: EmojiPicker( textEditingController: chatController, config: Config( emojiViewConfig: EmojiViewConfig( columns: 7, emojiSizeMax: 28 * (Platform.isIOS ? 1.10 : 1.0), verticalSpacing: 0, horizontalSpacing: 0, backgroundColor: const Color(0xFFF2F2F2), gridPadding: EdgeInsets.zero, recentsLimit: 35, replaceEmojiOnLimitExceed: false, noRecents: Text( "最近使用", style: TextStyle(fontSize: 20, color: Colors.black26), textAlign: TextAlign.center, ), loadingIndicator: const SizedBox.shrink(), buttonMode: ButtonMode.MATERIAL, ), skinToneConfig: SkinToneConfig( enabled: true, dialogBackgroundColor: Colors.white, indicatorColor: Colors.grey, ), categoryViewConfig: CategoryViewConfig( initCategory: Category.RECENT, iconColor: Colors.grey, iconColorSelected: Colors.blue, backspaceColor: Colors.blue, categoryIcons: const CategoryIcons(), tabIndicatorAnimDuration: Duration(milliseconds: 0), ), bottomActionBarConfig: const BottomActionBarConfig( backgroundColor: Colors.transparent, buttonIconColor: Colors.blue, showBackspaceButton: true, showSearchViewButton: false, ), swapCategoryAndBottomBar: false, checkPlatformCompatibility: true, ), ), ), ///更多 if (moreShow) Container( height: keyboard == -1 ? 167 : keyboard, child: Column( children: [ Container( width: double.infinity, height: 1.h, color: Color(0xFFF7F7F7), margin: EdgeInsets.only(bottom: 10.h), ), Row( children: [ GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { openCamera(); }, child: Column( children: [ Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(6), color: Color(0xFFFFFFFF), boxShadow: [ BoxShadow( color: Color(0xFFD5D5D5).withAlpha(25), offset: Offset(4, 2), blurRadius: 4, spreadRadius: 0, ) ], ), margin: EdgeInsets.only(right: 15.w, left: 14.w), padding: EdgeInsets.all(12), child: Image.asset( "assets/image/icon_chat_camera.webp", height: 24, width: 24, ), ), Padding( padding: EdgeInsets.only(top: 8.h), child: Text( "拍照", style: TextStyle( fontSize: 12.sp, color: Color(0xFFA29E9E), fontWeight: MyFontWeight.regular, ), ), ), ], ), ), GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { getImageOrVideo(GalleryMode.image); }, child: Column( children: [ Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(6), color: Color(0xFFFFFFFF), boxShadow: [ BoxShadow( color: Color(0xFFD5D5D5).withAlpha(25), offset: Offset(4, 2), blurRadius: 4, spreadRadius: 0, ) ], ), padding: EdgeInsets.all(12), child: Image.asset( "assets/image/icon_chat_photo.webp", height: 24, width: 24, ), ), Padding( padding: EdgeInsets.only(top: 8.h), child: Text( "相册", style: TextStyle( fontSize: 12.sp, color: Color(0xFFA29E9E), fontWeight: MyFontWeight.regular, ), ), ), ], ), ), ], ), ], ), ), ], ), ); } /// 复制文本 copy(String tex) { debugPrint(tex); Clipboard.setData(ClipboardData(text: tex)); } }