|
|
|
@ -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<ChatDetailsPage>
|
|
|
|
|
UserInfo? userInfo; |
|
|
|
|
// final OnChatMessage? _tempOnChatMessage = OnChatMsgInstance.instance.onChatMessage; |
|
|
|
|
final ScrollController scrollController = ScrollController(); |
|
|
|
|
GlobalKey<AnimatedListState> animatedListKey = GlobalKey<AnimatedListState>(); |
|
|
|
|
GlobalKey<SliverAnimatedListState> newanimatedListKey = GlobalKey<SliverAnimatedListState>(); |
|
|
|
|
GlobalKey<SliverAnimatedListState> loadanimatedListKey = GlobalKey<SliverAnimatedListState>(); |
|
|
|
|
GlobalKey centerKey = GlobalKey(); |
|
|
|
|
String tex = ""; |
|
|
|
|
String selfUserId = ""; |
|
|
|
|
ImUser? _toUser; |
|
|
|
@ -72,8 +72,11 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
|
|
|
|
|
double inputWidgetHeight = 95; |
|
|
|
|
double dynamicInputHeight = 0; |
|
|
|
|
|
|
|
|
|
RefreshController refreshController = RefreshController(); |
|
|
|
|
List<Message> messages = []; |
|
|
|
|
late StreamSubscription<bool> keyboardSubscription; |
|
|
|
|
bool loading = false; |
|
|
|
|
|
|
|
|
|
List<Message> newmessages = []; |
|
|
|
|
List<Message> loadmessages = []; |
|
|
|
|
int page = 0; |
|
|
|
|
|
|
|
|
|
loadMessageList() async { |
|
|
|
@ -91,8 +94,8 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
|
|
|
|
|
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<ChatDetailsPage>
|
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
messageImageUrl(); |
|
|
|
|
messageImageUrl(newmessages); |
|
|
|
|
|
|
|
|
|
// refreshState(); |
|
|
|
|
// jumpToBottom(); |
|
|
|
@ -112,40 +115,45 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
|
|
|
|
|
List<Message>? 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<Message> messages) { |
|
|
|
|
List<String> 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<Message>? messagePages = await hxDatabase.queryTList(conversation); |
|
|
|
|
for (var value in messages) { |
|
|
|
|
Message? message = messagePages?.firstWhere((element) => value.id == element.id, orElse: () => messages.first); |
|
|
|
|
List<Message> 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<ChatDetailsPage>
|
|
|
|
|
if (mounted) setState(() {}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
late StreamSubscription<bool> keyboardSubscription; |
|
|
|
|
|
|
|
|
|
@override |
|
|
|
|
void initState() { |
|
|
|
|
super.initState(); |
|
|
|
@ -188,15 +194,18 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
|
|
|
|
|
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<ChatDetailsPage>
|
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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<ChatDetailsPage>
|
|
|
|
|
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<ChatDetailsPage>
|
|
|
|
|
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<ChatDetailsPage>
|
|
|
|
|
}, |
|
|
|
|
heightRatioWithWidth: 0.82, |
|
|
|
|
); |
|
|
|
|
}); |
|
|
|
|
}, |
|
|
|
|
); |
|
|
|
|
} else if (await Permission.camera.isGranted) { |
|
|
|
|
if (await Permission.storage.isPermanentlyDenied) { |
|
|
|
|
showCupertinoDialog( |
|
|
|
@ -409,7 +417,8 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
|
|
|
|
|
}, |
|
|
|
|
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<ChatDetailsPage>
|
|
|
|
|
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<ChatDetailsPage>
|
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
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<double> chatDetailsItemHeight(Message message) async { |
|
|
|
@ -644,10 +687,10 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
|
|
|
|
|
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<ChatDetailsPage>
|
|
|
|
|
), |
|
|
|
|
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<ChatDetailsPage>
|
|
|
|
|
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<ChatDetailsPage>
|
|
|
|
|
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<ChatDetailsPage>
|
|
|
|
|
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<ChatDetailsPage>
|
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
), |
|
|
|
|
if (messages[position].state == 3) |
|
|
|
|
if (message.state == 3) |
|
|
|
|
SizedBox( |
|
|
|
|
width: 12.w, |
|
|
|
|
), |
|
|
|
@ -817,7 +860,7 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
|
|
|
|
|
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<ChatDetailsPage>
|
|
|
|
|
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<ChatDetailsPage>
|
|
|
|
|
), |
|
|
|
|
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<ChatDetailsPage>
|
|
|
|
|
} |
|
|
|
|
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<ChatDetailsPage>
|
|
|
|
|
); |
|
|
|
|
} 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<ChatDetailsPage>
|
|
|
|
|
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<ChatDetailsPage>
|
|
|
|
|
} |
|
|
|
|
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<ChatDetailsPage>
|
|
|
|
|
); |
|
|
|
|
} 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<ChatDetailsPage>
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// 显示消息菜单 |
|
|
|
|
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<Size> fetchImageSize(Message message) async { |
|
|
|
@ -1240,8 +1389,8 @@ class _ChatDetailsPage extends State<ChatDetailsPage>
|
|
|
|
|
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(); |
|
|
|
|