|
|
@ -1,29 +1,30 @@ |
|
|
|
import 'dart:async'; |
|
|
|
import 'dart:async'; |
|
|
|
import 'dart:convert'; |
|
|
|
import 'dart:convert'; |
|
|
|
import 'dart:io'; |
|
|
|
import 'dart:io'; |
|
|
|
import 'dart:ui'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import 'package:dio/dio.dart'; |
|
|
|
import 'package:dio/dio.dart'; |
|
|
|
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart'; |
|
|
|
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart'; |
|
|
|
import 'package:flutter/cupertino.dart'; |
|
|
|
import 'package:flutter/cupertino.dart'; |
|
|
|
import 'package:flutter/material.dart'; |
|
|
|
import 'package:flutter/material.dart'; |
|
|
|
import 'package:flutter/services.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_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/base_data.dart'; |
|
|
|
|
|
|
|
import 'package:huixiang/data/im_user.dart'; |
|
|
|
import 'package:huixiang/data/user_info.dart'; |
|
|
|
import 'package:huixiang/data/user_info.dart'; |
|
|
|
import 'package:huixiang/im/database/message.dart'; |
|
|
|
import 'package:huixiang/im/database/message.dart'; |
|
|
|
import 'package:huixiang/im/out/message.pb.dart'; |
|
|
|
import 'package:huixiang/im/out/message.pb.dart'; |
|
|
|
import 'package:huixiang/main.dart'; |
|
|
|
import 'package:huixiang/main.dart'; |
|
|
|
import 'package:huixiang/data/im_user.dart'; |
|
|
|
|
|
|
|
import 'package:huixiang/retrofit/retrofit_api.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/classic_header.dart'; |
|
|
|
import 'package:huixiang/view_widget/my_appbar.dart'; |
|
|
|
import 'package:huixiang/view_widget/my_appbar.dart'; |
|
|
|
import 'package:image_gallery_saver/image_gallery_saver.dart'; |
|
|
|
|
|
|
|
import 'package:image_pickers/image_pickers.dart'; |
|
|
|
import 'package:image_pickers/image_pickers.dart'; |
|
|
|
import 'package:permission_handler/permission_handler.dart'; |
|
|
|
import 'package:permission_handler/permission_handler.dart'; |
|
|
|
import 'package:pull_to_refresh/pull_to_refresh.dart'; |
|
|
|
import 'package:pull_to_refresh/pull_to_refresh.dart'; |
|
|
|
import 'package:shared_preferences/shared_preferences.dart'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import '../../community/release_dynamic.dart'; |
|
|
|
import '../../community/release_dynamic.dart'; |
|
|
|
import '../../generated/l10n.dart'; |
|
|
|
import '../../generated/l10n.dart'; |
|
|
@ -31,8 +32,7 @@ import '../../utils/font_weight.dart'; |
|
|
|
import '../utils/flutter_utils.dart'; |
|
|
|
import '../utils/flutter_utils.dart'; |
|
|
|
import '../view_widget/custom_image.dart'; |
|
|
|
import '../view_widget/custom_image.dart'; |
|
|
|
import '../view_widget/request_permission.dart'; |
|
|
|
import '../view_widget/request_permission.dart'; |
|
|
|
import 'im_view/on_chat_message.dart'; |
|
|
|
|
|
|
|
import 'im_view/on_chat_msg_instance.dart'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ChatDetailsPage extends StatefulWidget { |
|
|
|
class ChatDetailsPage extends StatefulWidget { |
|
|
|
final Map<String, dynamic>? arguments; |
|
|
|
final Map<String, dynamic>? arguments; |
|
|
@ -46,43 +46,39 @@ class ChatDetailsPage extends StatefulWidget { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
with WidgetsBindingObserver |
|
|
|
with WidgetsBindingObserver { |
|
|
|
implements OnChatMessage { |
|
|
|
|
|
|
|
ApiService? apiService; |
|
|
|
ApiService? apiService; |
|
|
|
List<Medias> mediaPaths = []; |
|
|
|
List<Medias> mediaPaths = []; |
|
|
|
int selectCount = 9; |
|
|
|
int selectCount = 9; |
|
|
|
int dynamicType = 0; |
|
|
|
int dynamicType = 0; |
|
|
|
final TextEditingController chatController = TextEditingController(); |
|
|
|
final TextEditingController chatController = TextEditingController(); |
|
|
|
bool emojiShowing = false; |
|
|
|
|
|
|
|
double keyboard = -1; |
|
|
|
|
|
|
|
bool needShowSmiley = false; |
|
|
|
|
|
|
|
bool needHideSmiley = false; |
|
|
|
|
|
|
|
bool isKeyBoardShow = false; |
|
|
|
bool isKeyBoardShow = false; |
|
|
|
|
|
|
|
bool emojiShowing = false; |
|
|
|
bool moreShow = false; |
|
|
|
bool moreShow = false; |
|
|
|
bool needShowMore = false; |
|
|
|
|
|
|
|
bool needHideMore = false; |
|
|
|
double keyboard = -1; |
|
|
|
var commentFocus = FocusNode(); |
|
|
|
var commentFocus = FocusNode(); |
|
|
|
String hintText = "输入消息内容..."; |
|
|
|
String hintText = "输入消息内容..."; |
|
|
|
UserInfo? userInfo; |
|
|
|
UserInfo? userInfo; |
|
|
|
final OnChatMessage? _tempOnChatMessage = OnChatMsgInstance.instance.onChatMessage; |
|
|
|
// final OnChatMessage? _tempOnChatMessage = OnChatMsgInstance.instance.onChatMessage; |
|
|
|
final ScrollController scrollController = ScrollController(); |
|
|
|
final ScrollController scrollController = ScrollController(); |
|
|
|
|
|
|
|
GlobalKey<AnimatedListState> animatedListKey = GlobalKey<AnimatedListState>(); |
|
|
|
String tex = ""; |
|
|
|
String tex = ""; |
|
|
|
String selfUserId = ""; |
|
|
|
String selfUserId = ""; |
|
|
|
ImUser? _toUser; |
|
|
|
ImUser? _toUser; |
|
|
|
late String conversation; |
|
|
|
late String conversation; |
|
|
|
List<String> imageUrl = []; |
|
|
|
List<String> imageUrl = []; |
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
double inputWidgetHeight = 95; |
|
|
|
void onMessage(txt) { |
|
|
|
double dynamicInputHeight = 0; |
|
|
|
// SmartDialog.showToast("聊天 $txt", alignment: Alignment.center); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
RefreshController refreshController = RefreshController(); |
|
|
|
RefreshController refreshController = RefreshController(); |
|
|
|
List<Message> messages = []; |
|
|
|
List<Message> messages = []; |
|
|
|
int page = 0; |
|
|
|
int page = 0; |
|
|
|
|
|
|
|
|
|
|
|
loadMessageList() async { |
|
|
|
loadMessageList() async { |
|
|
|
selfUserId = (await SharedPreferences.getInstance()).getString("userId") ?? ""; |
|
|
|
selfUserId = SharedInstance.instance.userId; |
|
|
|
|
|
|
|
debugPrint("selfUserId: $selfUserId _toUser?.mid: ${_toUser?.mid} "); |
|
|
|
conversation = conversationId(selfUserId, _toUser?.mid); |
|
|
|
conversation = conversationId(selfUserId, _toUser?.mid); |
|
|
|
|
|
|
|
|
|
|
|
ImUser? imUser = await hxDatabase.queryImUserById(conversation); |
|
|
|
ImUser? imUser = await hxDatabase.queryImUserById(conversation); |
|
|
@ -95,9 +91,10 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
await refresh(); |
|
|
|
await refresh(); |
|
|
|
|
|
|
|
|
|
|
|
socketClient.addCallback(_toUser?.mid ?? '', (Message message) { |
|
|
|
socketClient.addCallback(_toUser?.mid ?? '', (Message message) { |
|
|
|
messages.insert(0, message); |
|
|
|
messages.add(message); |
|
|
|
if (message.msgType == MsgType.VIDEO.value || message.msgType == MsgType.IMAGE.value) { |
|
|
|
animatedListKey.currentState?.insertItem(messages.length - 1); |
|
|
|
imageUrl.add(message.attach); |
|
|
|
if ((message.msgType == MsgType.VIDEO.value || message.msgType == MsgType.IMAGE.value) && (message.attach?.isNotEmpty ?? false)) { |
|
|
|
|
|
|
|
imageUrl.add(message.attach!); |
|
|
|
} |
|
|
|
} |
|
|
|
messageShowTime().then((value) { |
|
|
|
messageShowTime().then((value) { |
|
|
|
refreshState(); |
|
|
|
refreshState(); |
|
|
@ -107,66 +104,69 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
|
|
|
|
|
|
|
|
messageImageUrl(); |
|
|
|
messageImageUrl(); |
|
|
|
|
|
|
|
|
|
|
|
refreshState(); |
|
|
|
// refreshState(); |
|
|
|
jumpToBottom(); |
|
|
|
// jumpToBottom(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Future refresh() async { |
|
|
|
Future<double> refresh() async { |
|
|
|
List<Message>? messagePage = await hxDatabase.queryUList(conversation, page: page + 1, pageSize: 10); |
|
|
|
List<Message>? messagePage = await hxDatabase.queryUList(conversation, page: page + 1, pageSize: 10); |
|
|
|
debugPrint("refresh-message-list: ${messagePage?.length ?? ''} page: $page"); |
|
|
|
debugPrint("refresh-message-list: ${messagePage?.length ?? ''} page: $page"); |
|
|
|
if (messagePage?.isEmpty ?? true) { |
|
|
|
if (messagePage?.isEmpty ?? true) { |
|
|
|
refreshController.loadNoData(); |
|
|
|
refreshController.refreshCompleted(); |
|
|
|
return; |
|
|
|
return 0; |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
refreshController.loadComplete(); |
|
|
|
refreshController.refreshCompleted(); |
|
|
|
page += 1; |
|
|
|
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) { |
|
|
|
if (page == 1) { |
|
|
|
messages = messagePage!; |
|
|
|
messages.addAll(messagePage.reversed.toList()); |
|
|
|
|
|
|
|
animatedListKey.currentState?.insertAllItems(0, messagePage.length); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
messages.addAll(messagePage!); |
|
|
|
messages.insertAll(0, messagePage.reversed.toList()); |
|
|
|
|
|
|
|
animatedListKey.currentState?.insertAllItems(0, messagePage.length, duration: 300.milliseconds); |
|
|
|
} |
|
|
|
} |
|
|
|
await messageShowTime(); |
|
|
|
await messageShowTime(); |
|
|
|
return Future.value(); |
|
|
|
return height; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// 消息中的图片 |
|
|
|
/// 消息中的图片 |
|
|
|
messageImageUrl() { |
|
|
|
messageImageUrl() { |
|
|
|
imageUrl = messages.where((e) => e.msgType == MsgType.VIDEO.value || e.msgType == MsgType.IMAGE.value).map((e) => e.attach).toList(); |
|
|
|
imageUrl = messages.where((e) => (e.msgType == MsgType.VIDEO.value || e.msgType == MsgType.IMAGE.value) && (e.attach?.isNotEmpty ?? false)).map((e) => "${e.attach}").toList(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Future messageShowTime() async { |
|
|
|
Future messageShowTime() async { |
|
|
|
List<Message>? messagePages = await hxDatabase.queryTList(conversation); |
|
|
|
List<Message>? messagePages = await hxDatabase.queryTList(conversation); |
|
|
|
for (var value in messages) { |
|
|
|
for (var value in messages) { |
|
|
|
Message? message = messagePages?.firstWhere((element) => value.id == element.id); |
|
|
|
Message? message = messagePages?.firstWhere((element) => value.id == element.id, orElse: () => messages.first); |
|
|
|
value.showTime = message != null; |
|
|
|
value.showTime = message != null; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
SharedPreferences? sharedPre; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
///查询个人信息 |
|
|
|
///查询个人信息 |
|
|
|
queryUser() async { |
|
|
|
queryUser() async { |
|
|
|
sharedPre = await SharedPreferences.getInstance(); |
|
|
|
apiService = ApiService(Dio(), context: context, token: SharedInstance.instance.token); |
|
|
|
if (apiService == null) |
|
|
|
|
|
|
|
apiService = ApiService(Dio(), context: context, token: sharedPre?.getString("token")); |
|
|
|
userInfo = UserInfo.fromJson(jsonDecode(SharedInstance.instance.userJson)); |
|
|
|
if ((sharedPre?.containsKey('user') ?? false) && |
|
|
|
|
|
|
|
sharedPre?.getString('user') != null && |
|
|
|
|
|
|
|
sharedPre?.getString('user') != "") { |
|
|
|
|
|
|
|
userInfo = UserInfo.fromJson(jsonDecode(sharedPre!.getString('user')!)); |
|
|
|
|
|
|
|
if (userInfo?.phone?.isNotEmpty ?? false) { |
|
|
|
if (userInfo?.phone?.isNotEmpty ?? false) { |
|
|
|
setState(() {}); |
|
|
|
setState(() {}); |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
BaseData<UserInfo>? baseData = await apiService?.queryInfo().catchError((onError) { |
|
|
|
BaseData<UserInfo>? baseData = await apiService?.queryInfo().catchError((onError) {}); |
|
|
|
return BaseData<UserInfo>()..isSuccess = false; |
|
|
|
if (baseData?.isSuccess ?? false) { |
|
|
|
}); |
|
|
|
|
|
|
|
if ((baseData?.isSuccess ?? false) && baseData?.data != null) { |
|
|
|
setState(() { |
|
|
|
setState(() { |
|
|
|
userInfo = baseData!.data; |
|
|
|
userInfo = baseData!.data; |
|
|
|
}); |
|
|
|
}); |
|
|
|
SharedPreferences.getInstance().then((value) => { |
|
|
|
SharedInstance.instance.saveUser(baseData!.data!); |
|
|
|
value.setString('user', jsonEncode(baseData!.data)), |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -174,75 +174,93 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
if (mounted) setState(() {}); |
|
|
|
if (mounted) setState(() {}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
late StreamSubscription<bool> keyboardSubscription; |
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
@override |
|
|
|
void initState() { |
|
|
|
void initState() { |
|
|
|
super.initState(); |
|
|
|
super.initState(); |
|
|
|
|
|
|
|
|
|
|
|
_toUser = widget.arguments?["toUser"]; |
|
|
|
_toUser = widget.arguments?["toUser"]; |
|
|
|
OnChatMsgInstance.instance.onChatMessage = this; |
|
|
|
// OnChatMsgInstance.instance.onChatMessage = this; |
|
|
|
WidgetsBinding.instance.addObserver(this); |
|
|
|
WidgetsBinding.instance.addObserver(this); |
|
|
|
commentFocus.addListener(_focusNodeListener); |
|
|
|
commentFocus.addListener(_focusNodeListener); |
|
|
|
|
|
|
|
|
|
|
|
queryUser(); |
|
|
|
queryUser(); |
|
|
|
loadMessageList(); |
|
|
|
loadMessageList(); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void jumpToBottom() { |
|
|
|
scrollController.addListener(() { |
|
|
|
Future.delayed(const Duration(milliseconds: 100), () { |
|
|
|
debugPrint("scrollController-onChange: ${scrollController.position.pixels}"); |
|
|
|
scrollController.position.jumpTo(0); |
|
|
|
if (messages.length >= 10 && scrollController.position.pixels < -80) { |
|
|
|
}); |
|
|
|
if (!loading) { |
|
|
|
|
|
|
|
loading = true; |
|
|
|
|
|
|
|
refresh().then((value) => loading = false); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
void didChangeMetrics() { |
|
|
|
var keyboardVisibilityController = KeyboardVisibilityController(); |
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) { |
|
|
|
keyboardSubscription = keyboardVisibilityController.onChange.listen((bool visible) { |
|
|
|
if (!mounted) return; |
|
|
|
isKeyBoardShow = visible; |
|
|
|
isKeyBoardShow = MediaQuery.of(context).viewInsets.bottom > 0; |
|
|
|
debugPrint("keyboardVisibility-onChange: ${isKeyBoardShow}"); |
|
|
|
if (MediaQuery.of(context).viewInsets.bottom == 0) { |
|
|
|
|
|
|
|
if (isKeyBoardShow) { |
|
|
|
if (isKeyBoardShow) { |
|
|
|
FocusScope.of(context).requestFocus(FocusNode()); |
|
|
|
if (!emojiShowing && !moreShow) { |
|
|
|
if (mounted) if (!emojiShowing) if (!moreShow) |
|
|
|
chatController.clear(); |
|
|
|
setState(() { |
|
|
|
|
|
|
|
hintText = "请输入消息内容..."; |
|
|
|
|
|
|
|
isKeyBoardShow = false; |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
if (mounted) |
|
|
|
inputWidgetHeight = 95; |
|
|
|
setState(() { |
|
|
|
|
|
|
|
isKeyBoardShow = true; |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
jumpToBottom(); |
|
|
|
if (needShowSmiley && window.viewInsets.bottom <= 0.1) { |
|
|
|
// if (needShowSmiley && !visible) { |
|
|
|
needShowSmiley = false; |
|
|
|
// needShowSmiley = false; |
|
|
|
setState(() { |
|
|
|
// setState(() { |
|
|
|
emojiShowing = true; |
|
|
|
// 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; |
|
|
|
|
|
|
|
// }); |
|
|
|
|
|
|
|
// } |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
if (needHideSmiley && window.viewInsets.bottom > 0.1) { |
|
|
|
|
|
|
|
needHideSmiley = false; |
|
|
|
bool loading = false; |
|
|
|
setState(() { |
|
|
|
|
|
|
|
emojiShowing = 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); |
|
|
|
} |
|
|
|
} |
|
|
|
if (needShowMore && window.viewInsets.bottom <= 0.1) { |
|
|
|
|
|
|
|
needShowMore = false; |
|
|
|
|
|
|
|
setState(() { |
|
|
|
|
|
|
|
moreShow = true; |
|
|
|
|
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
if (needHideMore && window.viewInsets.bottom > 0.1) { |
|
|
|
|
|
|
|
needHideMore = false; |
|
|
|
@override |
|
|
|
setState(() { |
|
|
|
void didChangeMetrics() { |
|
|
|
moreShow = false; |
|
|
|
super.didChangeMetrics(); |
|
|
|
}); |
|
|
|
dynamicInputHeight = MediaQuery.of(context).viewInsets.bottom; |
|
|
|
|
|
|
|
if (dynamicInputHeight > 0 && dynamicInputHeight > keyboard) { |
|
|
|
|
|
|
|
keyboard = dynamicInputHeight; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
@override |
|
|
|
void dispose() { |
|
|
|
void dispose() { |
|
|
|
super.dispose(); |
|
|
|
super.dispose(); |
|
|
|
OnChatMsgInstance.instance.onChatMessage = _tempOnChatMessage; |
|
|
|
// OnChatMsgInstance.instance.onChatMessage = _tempOnChatMessage; |
|
|
|
|
|
|
|
keyboardSubscription.cancel(); |
|
|
|
WidgetsBinding.instance.removeObserver(this); |
|
|
|
WidgetsBinding.instance.removeObserver(this); |
|
|
|
commentFocus.removeListener(_focusNodeListener); |
|
|
|
commentFocus.removeListener(_focusNodeListener); |
|
|
|
scrollController.dispose(); |
|
|
|
scrollController.dispose(); |
|
|
@ -260,39 +278,51 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
///输入框点击事件 |
|
|
|
///输入框点击事件 |
|
|
|
_onTextFieldTap() { |
|
|
|
_onTextFieldTap() { |
|
|
|
if (emojiShowing) { |
|
|
|
if (emojiShowing) { |
|
|
|
needHideSmiley = true; |
|
|
|
emojiShowing = false; |
|
|
|
} |
|
|
|
} |
|
|
|
if (moreShow) { |
|
|
|
if (moreShow) { |
|
|
|
needHideMore = true; |
|
|
|
moreShow = false; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
///表情包点击事件 |
|
|
|
///表情包点击事件 |
|
|
|
_onSmileyTap() { |
|
|
|
_onSmileyTap() { |
|
|
|
if (!emojiShowing && commentFocus.hasFocus && isKeyBoardShow) { |
|
|
|
|
|
|
|
needShowSmiley = true; |
|
|
|
|
|
|
|
commentFocus.unfocus(); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
setState(() { |
|
|
|
|
|
|
|
emojiShowing = !emojiShowing; |
|
|
|
emojiShowing = !emojiShowing; |
|
|
|
isKeyBoardShow = emojiShowing; |
|
|
|
if (isKeyBoardShow && commentFocus.hasFocus) { |
|
|
|
|
|
|
|
commentFocus.unfocus(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (moreShow) { |
|
|
|
moreShow = false; |
|
|
|
moreShow = false; |
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (emojiShowing) { |
|
|
|
|
|
|
|
if (inputWidgetHeight == 95) { |
|
|
|
|
|
|
|
inputWidgetHeight += (keyboard == -1 ? 270 : keyboard); |
|
|
|
|
|
|
|
jumpToBottom(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
inputWidgetHeight = 95; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
setState(() {}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
///更多点击事件 |
|
|
|
///更多点击事件 |
|
|
|
_onMoreTap() { |
|
|
|
_onMoreTap() { |
|
|
|
if (!moreShow && commentFocus.hasFocus && isKeyBoardShow) { |
|
|
|
|
|
|
|
needShowMore = true; |
|
|
|
|
|
|
|
commentFocus.unfocus(); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
setState(() { |
|
|
|
|
|
|
|
moreShow = !moreShow; |
|
|
|
moreShow = !moreShow; |
|
|
|
isKeyBoardShow = moreShow; |
|
|
|
if (isKeyBoardShow && commentFocus.hasFocus) { |
|
|
|
|
|
|
|
commentFocus.unfocus(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (emojiShowing) { |
|
|
|
emojiShowing = false; |
|
|
|
emojiShowing = false; |
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (moreShow) { |
|
|
|
|
|
|
|
if (inputWidgetHeight == 95) { |
|
|
|
|
|
|
|
inputWidgetHeight += (keyboard == -1 ? 167 : keyboard); |
|
|
|
|
|
|
|
jumpToBottom(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
inputWidgetHeight = 95; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
setState(() {}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
///图片/视频选择 |
|
|
|
///图片/视频选择 |
|
|
@ -330,7 +360,8 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
String fileUrl = await qiniu.uploadFile(apiService!, path); |
|
|
|
String fileUrl = await qiniu.uploadFile(apiService!, path); |
|
|
|
socketClient.sendMessage(_toUser?.mid ?? '', fileUrl, attach: path, msgType: galleryMode == GalleryMode.image ? 2 : 4).then((value) { |
|
|
|
socketClient.sendMessage(_toUser?.mid ?? '', fileUrl, attach: path, msgType: galleryMode == GalleryMode.image ? 2 : 4).then((value) { |
|
|
|
Message message = value; |
|
|
|
Message message = value; |
|
|
|
messages.insert(0, message); |
|
|
|
messages.add(message); |
|
|
|
|
|
|
|
animatedListKey.currentState?.insertItem(messages.length - 1); |
|
|
|
chatController.clear(); |
|
|
|
chatController.clear(); |
|
|
|
messageShowTime().then((value) { |
|
|
|
messageShowTime().then((value) { |
|
|
|
refreshState(); |
|
|
|
refreshState(); |
|
|
@ -394,7 +425,8 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
String fileUrl = await qiniu.uploadFile(apiService!, path); |
|
|
|
String fileUrl = await qiniu.uploadFile(apiService!, path); |
|
|
|
socketClient.sendMessage(_toUser?.mid ?? '', fileUrl, attach: path, msgType: 2).then((value) { |
|
|
|
socketClient.sendMessage(_toUser?.mid ?? '', fileUrl, attach: path, msgType: 2).then((value) { |
|
|
|
Message message = value; |
|
|
|
Message message = value; |
|
|
|
messages.insert(0, message); |
|
|
|
messages.add(message); |
|
|
|
|
|
|
|
animatedListKey.currentState?.insertItem(messages.length - 1); |
|
|
|
chatController.clear(); |
|
|
|
chatController.clear(); |
|
|
|
messageShowTime().then((value) { |
|
|
|
messageShowTime().then((value) { |
|
|
|
refreshState(); |
|
|
|
refreshState(); |
|
|
@ -485,29 +517,21 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
@override |
|
|
|
Widget build(BuildContext context) { |
|
|
|
Widget build(BuildContext ctx) { |
|
|
|
double h = MediaQuery.of(context).viewInsets.bottom; |
|
|
|
|
|
|
|
if (h > 0) { |
|
|
|
|
|
|
|
jumpToBottom(); |
|
|
|
|
|
|
|
if (keyboard < h) { |
|
|
|
|
|
|
|
keyboard = h; |
|
|
|
|
|
|
|
// setState(() {}); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return GestureDetector( |
|
|
|
return GestureDetector( |
|
|
|
behavior: HitTestBehavior.translucent, |
|
|
|
behavior: HitTestBehavior.translucent, |
|
|
|
onTap: () { |
|
|
|
onTap: () { |
|
|
|
FocusScope.of(context).requestFocus(FocusNode()); |
|
|
|
|
|
|
|
setState(() { |
|
|
|
setState(() { |
|
|
|
needShowSmiley = false; |
|
|
|
|
|
|
|
emojiShowing = false; |
|
|
|
emojiShowing = false; |
|
|
|
isKeyBoardShow = emojiShowing; |
|
|
|
isKeyBoardShow = false; |
|
|
|
moreShow = false; |
|
|
|
moreShow = false; |
|
|
|
isKeyBoardShow = moreShow; |
|
|
|
|
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
inputWidgetHeight = 95; |
|
|
|
|
|
|
|
FocusScope.of(context).requestFocus(FocusNode()); |
|
|
|
|
|
|
|
// jumpToBottom(); |
|
|
|
}, |
|
|
|
}, |
|
|
|
child: Scaffold( |
|
|
|
child: Scaffold( |
|
|
|
// resizeToAvoidBottomInset: false, |
|
|
|
resizeToAvoidBottomInset: true, |
|
|
|
backgroundColor: Color(0xFFF6F6F6), |
|
|
|
backgroundColor: Color(0xFFF6F6F6), |
|
|
|
appBar: MyAppBar( |
|
|
|
appBar: MyAppBar( |
|
|
|
title: _toUser?.nickname, |
|
|
|
title: _toUser?.nickname, |
|
|
@ -515,11 +539,16 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
titleSize: 17.sp, |
|
|
|
titleSize: 17.sp, |
|
|
|
leading: true, |
|
|
|
leading: true, |
|
|
|
leadingColor: Colors.black, |
|
|
|
leadingColor: Colors.black, |
|
|
|
|
|
|
|
toolbarHeight: kToolbarHeight, |
|
|
|
action: GestureDetector( |
|
|
|
action: GestureDetector( |
|
|
|
behavior: HitTestBehavior.opaque, |
|
|
|
behavior: HitTestBehavior.opaque, |
|
|
|
onTap: () async { |
|
|
|
onTap: () async { |
|
|
|
await Navigator.of(context).pushNamed('/router/chat_setting', |
|
|
|
await Navigator.of(context).pushNamed( |
|
|
|
arguments: {"userId": _toUser?.mid}); |
|
|
|
'/router/chat_setting', |
|
|
|
|
|
|
|
arguments: { |
|
|
|
|
|
|
|
"userId": _toUser?.mid, |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
); |
|
|
|
page = 0; |
|
|
|
page = 0; |
|
|
|
refresh().then((value) { |
|
|
|
refresh().then((value) { |
|
|
|
refreshState(); |
|
|
|
refreshState(); |
|
|
@ -536,32 +565,11 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
body: Container( |
|
|
|
body: Container( |
|
|
|
|
|
|
|
height: MediaQuery.of(context).size.height - kToolbarHeight - MediaQuery.of(context).padding.top - MediaQuery.of(context).viewInsets.bottom, |
|
|
|
child: Column( |
|
|
|
child: Column( |
|
|
|
children: [ |
|
|
|
children: [ |
|
|
|
Expanded( |
|
|
|
Expanded( |
|
|
|
child: SmartRefresher( |
|
|
|
|
|
|
|
enablePullDown: false, |
|
|
|
|
|
|
|
enablePullUp: true, |
|
|
|
|
|
|
|
header: MyHeader(), |
|
|
|
|
|
|
|
footer: CustomFooter( |
|
|
|
|
|
|
|
loadStyle: LoadStyle.ShowWhenLoading, |
|
|
|
|
|
|
|
builder: (BuildContext context, LoadStatus? mode) { |
|
|
|
|
|
|
|
return SizedBox(); |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
reverse: true, |
|
|
|
|
|
|
|
scrollController: scrollController, |
|
|
|
|
|
|
|
controller: refreshController, |
|
|
|
|
|
|
|
onRefresh: () { |
|
|
|
|
|
|
|
debugPrint(""); |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
onLoading: () { |
|
|
|
|
|
|
|
refresh().then((value) { |
|
|
|
|
|
|
|
refreshState(); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
child: chatDetailsList(), |
|
|
|
child: chatDetailsList(), |
|
|
|
), |
|
|
|
|
|
|
|
flex: 1, |
|
|
|
flex: 1, |
|
|
|
), |
|
|
|
), |
|
|
|
input(), |
|
|
|
input(), |
|
|
@ -574,30 +582,68 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
|
|
|
|
|
|
|
|
///聊天列表 |
|
|
|
///聊天列表 |
|
|
|
Widget chatDetailsList() { |
|
|
|
Widget chatDetailsList() { |
|
|
|
return ListView.builder( |
|
|
|
return AnimatedList( |
|
|
|
itemCount: messages.length, |
|
|
|
key: animatedListKey, |
|
|
|
reverse: true, |
|
|
|
initialItemCount: messages.length, |
|
|
|
padding: EdgeInsets.zero, |
|
|
|
controller: scrollController, |
|
|
|
itemBuilder: (context, position) { |
|
|
|
physics: BouncingScrollPhysics()..frictionFactor(0.8), |
|
|
|
return GestureDetector( |
|
|
|
itemBuilder: (context, position, animation) { |
|
|
|
behavior: HitTestBehavior.opaque, |
|
|
|
return chatDetailsItem(position); |
|
|
|
onTap: () { |
|
|
|
|
|
|
|
setState(() { |
|
|
|
|
|
|
|
if (emojiShowing) { |
|
|
|
|
|
|
|
emojiShowing = !emojiShowing; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (moreShow) { |
|
|
|
|
|
|
|
moreShow = !moreShow; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
FocusScope.of(context).requestFocus(FocusNode()); |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
child: chatDetailsItem(position), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
}, |
|
|
|
}, |
|
|
|
); |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Future<double> 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(position) { |
|
|
|
Widget chatDetailsItem(position) { |
|
|
|
bool isSelf = messages[position].fromId == selfUserId; |
|
|
|
bool isSelf = messages[position].fromId == selfUserId; |
|
|
|
bool isText = messages[position].msgType == 1; |
|
|
|
bool isText = messages[position].msgType == 1; |
|
|
@ -626,6 +672,7 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
style: TextStyle( |
|
|
|
style: TextStyle( |
|
|
|
color: Color(0xFFA29E9E), |
|
|
|
color: Color(0xFFA29E9E), |
|
|
|
fontSize: 10.sp, |
|
|
|
fontSize: 10.sp, |
|
|
|
|
|
|
|
height: 1.0, |
|
|
|
fontWeight: MyFontWeight.regular, |
|
|
|
fontWeight: MyFontWeight.regular, |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
@ -662,6 +709,10 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
GestureDetector( |
|
|
|
GestureDetector( |
|
|
|
behavior: HitTestBehavior.opaque, |
|
|
|
behavior: HitTestBehavior.opaque, |
|
|
|
onTap: () { |
|
|
|
onTap: () { |
|
|
|
|
|
|
|
if (_toUser?.mid?.isEmpty ?? true) { |
|
|
|
|
|
|
|
"用户信息错误".toast; |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
Navigator.of(context).pushNamed( |
|
|
|
Navigator.of(context).pushNamed( |
|
|
|
'/router/personal_page', |
|
|
|
'/router/personal_page', |
|
|
|
arguments: { |
|
|
|
arguments: { |
|
|
@ -673,8 +724,8 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
child: MImage( |
|
|
|
child: MImage( |
|
|
|
_toUser?.avatar ?? '', |
|
|
|
_toUser?.avatar ?? '', |
|
|
|
isCircle: true, |
|
|
|
isCircle: true, |
|
|
|
height: 44.h, |
|
|
|
height: 44, |
|
|
|
width: 44.h, |
|
|
|
width: 44, |
|
|
|
fit: BoxFit.cover, |
|
|
|
fit: BoxFit.cover, |
|
|
|
errorSrc: "assets/image/default_1.webp", |
|
|
|
errorSrc: "assets/image/default_1.webp", |
|
|
|
fadeSrc: "assets/image/default_1.webp", |
|
|
|
fadeSrc: "assets/image/default_1.webp", |
|
|
@ -705,7 +756,7 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
), |
|
|
|
), |
|
|
|
key: _buttonKey, |
|
|
|
key: _buttonKey, |
|
|
|
padding: EdgeInsets.symmetric( |
|
|
|
padding: EdgeInsets.symmetric( |
|
|
|
vertical: 8.h, |
|
|
|
vertical: 6, |
|
|
|
horizontal: 12.w, |
|
|
|
horizontal: 12.w, |
|
|
|
), |
|
|
|
), |
|
|
|
child: Text( |
|
|
|
child: Text( |
|
|
@ -783,14 +834,14 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
), |
|
|
|
), |
|
|
|
key: _buttonKey, |
|
|
|
key: _buttonKey, |
|
|
|
padding: EdgeInsets.symmetric( |
|
|
|
padding: EdgeInsets.symmetric( |
|
|
|
vertical: 8.h, |
|
|
|
vertical: 6, |
|
|
|
horizontal: 12.w, |
|
|
|
horizontal: 12.w, |
|
|
|
), |
|
|
|
), |
|
|
|
child: Text( |
|
|
|
child: Text( |
|
|
|
tex = messages[position].content, |
|
|
|
tex = messages[position].content, |
|
|
|
textAlign: TextAlign.left, |
|
|
|
textAlign: TextAlign.left, |
|
|
|
style: TextStyle( |
|
|
|
style: TextStyle( |
|
|
|
height: 1.2.h, |
|
|
|
height: 1.2, |
|
|
|
color: Colors.white, |
|
|
|
color: Colors.white, |
|
|
|
fontSize: 17.sp, |
|
|
|
fontSize: 17.sp, |
|
|
|
fontWeight: MyFontWeight.medium, |
|
|
|
fontWeight: MyFontWeight.medium, |
|
|
@ -809,7 +860,7 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
Navigator.of(context).pushNamed( |
|
|
|
Navigator.of(context).pushNamed( |
|
|
|
'/router/personal_page', |
|
|
|
'/router/personal_page', |
|
|
|
arguments: { |
|
|
|
arguments: { |
|
|
|
"memberId": "0", |
|
|
|
"memberId": selfUserId, |
|
|
|
"inletType": 1, |
|
|
|
"inletType": 1, |
|
|
|
}, |
|
|
|
}, |
|
|
|
); |
|
|
|
); |
|
|
@ -817,8 +868,8 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
child: MImage( |
|
|
|
child: MImage( |
|
|
|
userInfo?.headimg ?? "", |
|
|
|
userInfo?.headimg ?? "", |
|
|
|
isCircle: true, |
|
|
|
isCircle: true, |
|
|
|
height: 44.h, |
|
|
|
height: 44, |
|
|
|
width: 44.h, |
|
|
|
width: 44, |
|
|
|
fit: BoxFit.cover, |
|
|
|
fit: BoxFit.cover, |
|
|
|
errorSrc: "assets/image/default_1.webp", |
|
|
|
errorSrc: "assets/image/default_1.webp", |
|
|
|
fadeSrc: "assets/image/default_1.webp", |
|
|
|
fadeSrc: "assets/image/default_1.webp", |
|
|
@ -831,7 +882,9 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
/// not self image 图片 |
|
|
|
/// not self image 图片 |
|
|
|
if (!isSelf && isImage) |
|
|
|
if (!isSelf && isImage) |
|
|
|
Padding( |
|
|
|
Padding( |
|
|
|
padding: EdgeInsets.only(left: 17.w, right: 39.w, top: 20.h), |
|
|
|
padding: EdgeInsets.only( |
|
|
|
|
|
|
|
left: 17.w, right: 39.w, top: 10.h, |
|
|
|
|
|
|
|
), |
|
|
|
child: Row( |
|
|
|
child: Row( |
|
|
|
children: [ |
|
|
|
children: [ |
|
|
|
MImage( |
|
|
|
MImage( |
|
|
@ -851,19 +904,23 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
showMessageMenu(context, _buttonKey, messages[position]); |
|
|
|
showMessageMenu(context, _buttonKey, messages[position]); |
|
|
|
}, |
|
|
|
}, |
|
|
|
onTap: () { |
|
|
|
onTap: () { |
|
|
|
if (imageUrl.contains(messages[position].attach)) { |
|
|
|
if (imageUrl.contains(messages[position].attach) && (messages[position].attach?.isNotEmpty ?? false)) { |
|
|
|
ImagePickers.previewImages(imageUrl, imageUrl.indexOf(messages[position].attach)); |
|
|
|
ImagePickers.previewImages(imageUrl, imageUrl.indexOf(messages[position].attach!)); |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
}, |
|
|
|
child: FutureBuilder( |
|
|
|
child: FutureBuilder( |
|
|
|
future: fetchImageSize(messages[position]), |
|
|
|
future: fetchImageSize(messages[position]), |
|
|
|
key: _buttonKey, |
|
|
|
key: _buttonKey, |
|
|
|
builder: (BuildContext context, AsyncSnapshot snapshot) { |
|
|
|
builder: (BuildContext context, AsyncSnapshot snapshot) { |
|
|
|
if (snapshot.connectionState == |
|
|
|
if (messages[position].attach?.isEmpty ?? true) { |
|
|
|
ConnectionState.waiting || |
|
|
|
return SizedBox( |
|
|
|
snapshot.hasError) { |
|
|
|
width: 180.w, |
|
|
|
|
|
|
|
height: 200.h, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (snapshot.connectionState == ConnectionState.waiting || snapshot.hasError) { |
|
|
|
return Image.file( |
|
|
|
return Image.file( |
|
|
|
File(messages[position].attach), |
|
|
|
File(messages[position].attach!), |
|
|
|
width: 180.w, |
|
|
|
width: 180.w, |
|
|
|
height: 200.h, |
|
|
|
height: 200.h, |
|
|
|
alignment: Alignment.centerLeft, |
|
|
|
alignment: Alignment.centerLeft, |
|
|
@ -871,7 +928,7 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
); |
|
|
|
); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
return Image.file( |
|
|
|
return Image.file( |
|
|
|
File(messages[position].attach), |
|
|
|
File(messages[position].attach!), |
|
|
|
width: snapshot.data.width, |
|
|
|
width: snapshot.data.width, |
|
|
|
height: snapshot.data.height, |
|
|
|
height: snapshot.data.height, |
|
|
|
alignment: Alignment.centerLeft, |
|
|
|
alignment: Alignment.centerLeft, |
|
|
@ -892,7 +949,7 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
padding: EdgeInsets.only( |
|
|
|
padding: EdgeInsets.only( |
|
|
|
left: 39.w, |
|
|
|
left: 39.w, |
|
|
|
right: 17.w, |
|
|
|
right: 17.w, |
|
|
|
top: 20.h, |
|
|
|
top: 10.h, |
|
|
|
), |
|
|
|
), |
|
|
|
child: Row( |
|
|
|
child: Row( |
|
|
|
children: [ |
|
|
|
children: [ |
|
|
@ -902,17 +959,23 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
showMessageMenu(context, _buttonKey, messages[position]); |
|
|
|
showMessageMenu(context, _buttonKey, messages[position]); |
|
|
|
}, |
|
|
|
}, |
|
|
|
onTap: () { |
|
|
|
onTap: () { |
|
|
|
if (imageUrl.contains(messages[position].attach)) { |
|
|
|
if (imageUrl.contains(messages[position].attach) && (messages[position].attach?.isNotEmpty ?? false)) { |
|
|
|
ImagePickers.previewImages(imageUrl, imageUrl.indexOf(messages[position].attach)); |
|
|
|
ImagePickers.previewImages(imageUrl, imageUrl.indexOf(messages[position].attach!)); |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
}, |
|
|
|
child: FutureBuilder ( |
|
|
|
child: FutureBuilder ( |
|
|
|
future: fetchImageSize(messages[position]), |
|
|
|
future: fetchImageSize(messages[position]), |
|
|
|
key: _buttonKey, |
|
|
|
key: _buttonKey, |
|
|
|
builder: (BuildContext context, AsyncSnapshot snapshot) { |
|
|
|
builder: (BuildContext context, AsyncSnapshot snapshot) { |
|
|
|
|
|
|
|
if (messages[position].attach?.isEmpty ?? true) { |
|
|
|
|
|
|
|
return SizedBox( |
|
|
|
|
|
|
|
width: 180.w, |
|
|
|
|
|
|
|
height: 200.h, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
} |
|
|
|
if (snapshot.connectionState == ConnectionState.waiting || snapshot.hasError) { |
|
|
|
if (snapshot.connectionState == ConnectionState.waiting || snapshot.hasError) { |
|
|
|
return Image.file( |
|
|
|
return Image.file( |
|
|
|
File(messages[position].attach), |
|
|
|
File(messages[position].attach!), |
|
|
|
width: 180.w, |
|
|
|
width: 180.w, |
|
|
|
height: 200.h, |
|
|
|
height: 200.h, |
|
|
|
alignment: Alignment.centerRight, |
|
|
|
alignment: Alignment.centerRight, |
|
|
@ -920,7 +983,7 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
); |
|
|
|
); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
return Image.file( |
|
|
|
return Image.file( |
|
|
|
File(messages[position].attach), |
|
|
|
File(messages[position].attach!), |
|
|
|
width: snapshot.data.width, |
|
|
|
width: snapshot.data.width, |
|
|
|
height: snapshot.data.height, |
|
|
|
height: snapshot.data.height, |
|
|
|
alignment: Alignment.centerRight, |
|
|
|
alignment: Alignment.centerRight, |
|
|
@ -1038,11 +1101,10 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
GestureDetector( |
|
|
|
GestureDetector( |
|
|
|
onTap: () async { |
|
|
|
onTap: () async { |
|
|
|
await hxDatabase.deleteByMsgId("${message.id}"); |
|
|
|
await hxDatabase.deleteByMsgId("${message.id}"); |
|
|
|
|
|
|
|
int index = messages.indexOf(message); |
|
|
|
messages.remove(message); |
|
|
|
messages.remove(message); |
|
|
|
|
|
|
|
animatedListKey.currentState?.removeItem(index, (context, animation) {return Container();}); |
|
|
|
Navigator.pop(context); |
|
|
|
Navigator.pop(context); |
|
|
|
if (mounted) { |
|
|
|
|
|
|
|
setState(() {}); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}, |
|
|
|
}, |
|
|
|
child: Column( |
|
|
|
child: Column( |
|
|
|
children: [ |
|
|
|
children: [ |
|
|
@ -1083,20 +1145,23 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Future<Size> fetchImageSize(Message message) async { |
|
|
|
Future<Size> fetchImageSize(Message message) async { |
|
|
|
String imageLocalPath = message.attach; |
|
|
|
String? imageLocalPath = message.attach; |
|
|
|
String imageUrl = message.content; |
|
|
|
String imageUrl = message.content; |
|
|
|
debugPrint("$imageLocalPath"); |
|
|
|
debugPrint("$imageLocalPath"); |
|
|
|
debugPrint("$imageUrl"); |
|
|
|
debugPrint("$imageUrl"); |
|
|
|
|
|
|
|
|
|
|
|
File file = File(imageLocalPath); |
|
|
|
if (imageLocalPath?.isEmpty ?? true) { |
|
|
|
|
|
|
|
return Future.value(Size.zero); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
File file = File(imageLocalPath!); |
|
|
|
if (!(await file.exists())) { |
|
|
|
if (!(await file.exists())) { |
|
|
|
await qiniu.downloadFile(imageUrl, savePath: imageLocalPath); |
|
|
|
await qiniu.downloadFile(imageUrl, savePath: imageLocalPath); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Size size = Size.zero; |
|
|
|
Size size = Size.zero; |
|
|
|
|
|
|
|
|
|
|
|
if (sharedPre != null) { |
|
|
|
String? sizeStr = SharedInstance.instance.getSize(imageLocalPath); |
|
|
|
String? sizeStr = sharedPre!.getString(imageLocalPath); |
|
|
|
|
|
|
|
if (sizeStr != null && sizeStr != "") { |
|
|
|
if (sizeStr != null && sizeStr != "") { |
|
|
|
Map<String, dynamic> sizeMap = jsonDecode(sizeStr); |
|
|
|
Map<String, dynamic> sizeMap = jsonDecode(sizeStr); |
|
|
|
size = Size(sizeMap["width"], sizeMap["height"]); |
|
|
|
size = Size(sizeMap["width"], sizeMap["height"]); |
|
|
@ -1104,7 +1169,6 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
return size; |
|
|
|
return size; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Completer<Size> completer = Completer(); |
|
|
|
Completer<Size> completer = Completer(); |
|
|
|
Image.file(file) |
|
|
|
Image.file(file) |
|
|
@ -1118,7 +1182,7 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
"width": size.width, |
|
|
|
"width": size.width, |
|
|
|
"height": size.height, |
|
|
|
"height": size.height, |
|
|
|
}; |
|
|
|
}; |
|
|
|
sharedPre?.setString(imageLocalPath, jsonEncode(sizeMap)); |
|
|
|
SharedInstance.instance.setSize(imageLocalPath, jsonEncode(sizeMap)); |
|
|
|
completer.complete(size); |
|
|
|
completer.complete(size); |
|
|
|
})); |
|
|
|
})); |
|
|
|
return completer.future; |
|
|
|
return completer.future; |
|
|
@ -1141,7 +1205,9 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
Widget input() { |
|
|
|
Widget input() { |
|
|
|
return Container( |
|
|
|
return Container( |
|
|
|
color: Color(0xFFFDFCFC), |
|
|
|
color: Color(0xFFFDFCFC), |
|
|
|
padding: EdgeInsets.only(top: 14.h, bottom: 15.h), |
|
|
|
// duration: 400.milliseconds, |
|
|
|
|
|
|
|
height: inputWidgetHeight, |
|
|
|
|
|
|
|
padding: EdgeInsets.only(top: 14, bottom: 15), |
|
|
|
child: Column( |
|
|
|
child: Column( |
|
|
|
children: [ |
|
|
|
children: [ |
|
|
|
///输入框 |
|
|
|
///输入框 |
|
|
@ -1171,11 +1237,11 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
if (commentText.trim() == "") { |
|
|
|
if (commentText.trim() == "") { |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
socketClient |
|
|
|
socketClient.sendMessage(_toUser?.mid ?? '', commentText) |
|
|
|
.sendMessage(_toUser?.mid ?? '', commentText) |
|
|
|
|
|
|
|
.then((value) { |
|
|
|
.then((value) { |
|
|
|
Message message = value; |
|
|
|
Message message = value; |
|
|
|
messages.insert(0, message); |
|
|
|
messages.add(message); |
|
|
|
|
|
|
|
animatedListKey.currentState?.insertItem(messages.length - 1); |
|
|
|
chatController.clear(); |
|
|
|
chatController.clear(); |
|
|
|
messageShowTime().then((value) { |
|
|
|
messageShowTime().then((value) { |
|
|
|
refreshState(); |
|
|
|
refreshState(); |
|
|
@ -1203,14 +1269,13 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
behavior: HitTestBehavior.opaque, |
|
|
|
behavior: HitTestBehavior.opaque, |
|
|
|
onTap: () { |
|
|
|
onTap: () { |
|
|
|
_onSmileyTap(); |
|
|
|
_onSmileyTap(); |
|
|
|
jumpToBottom(); |
|
|
|
|
|
|
|
}, |
|
|
|
}, |
|
|
|
child: Container( |
|
|
|
child: Container( |
|
|
|
padding: EdgeInsets.only(left: 15.w, right: 8.w), |
|
|
|
padding: EdgeInsets.only(left: 15.w, right: 8.w), |
|
|
|
child: Image.asset( |
|
|
|
child: Image.asset( |
|
|
|
"assets/image/icon_chat_emo.webp", |
|
|
|
"assets/image/icon_chat_emo.webp", |
|
|
|
height: 26.h, |
|
|
|
height: 26, |
|
|
|
width: 26.h, |
|
|
|
width: 26, |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
@ -1218,27 +1283,25 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
behavior: HitTestBehavior.opaque, |
|
|
|
behavior: HitTestBehavior.opaque, |
|
|
|
onTap: () { |
|
|
|
onTap: () { |
|
|
|
_onMoreTap(); |
|
|
|
_onMoreTap(); |
|
|
|
jumpToBottom(); |
|
|
|
|
|
|
|
}, |
|
|
|
}, |
|
|
|
child: Container( |
|
|
|
child: Container( |
|
|
|
padding: EdgeInsets.only(left: 8.w, right: 19.w), |
|
|
|
padding: EdgeInsets.only(left: 8.w, right: 19.w), |
|
|
|
child: Image.asset( |
|
|
|
child: Image.asset( |
|
|
|
"assets/image/chat_more.webp", |
|
|
|
"assets/image/chat_more.webp", |
|
|
|
height: 26.h, |
|
|
|
height: 26, |
|
|
|
width: 26.h, |
|
|
|
width: 26, |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
SizedBox( |
|
|
|
SizedBox( |
|
|
|
height: 16.h, |
|
|
|
height: 16, |
|
|
|
), |
|
|
|
), |
|
|
|
|
|
|
|
|
|
|
|
///表情 |
|
|
|
///表情 |
|
|
|
Offstage( |
|
|
|
if (emojiShowing) |
|
|
|
offstage: !emojiShowing, |
|
|
|
Container( |
|
|
|
child: SizedBox( |
|
|
|
|
|
|
|
height: keyboard == -1 ? 270 : keyboard, |
|
|
|
height: keyboard == -1 ? 270 : keyboard, |
|
|
|
width: MediaQuery.of(context).size.width, |
|
|
|
width: MediaQuery.of(context).size.width, |
|
|
|
child: EmojiPicker( |
|
|
|
child: EmojiPicker( |
|
|
@ -1246,12 +1309,12 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
config: Config( |
|
|
|
config: Config( |
|
|
|
emojiViewConfig: EmojiViewConfig( |
|
|
|
emojiViewConfig: EmojiViewConfig( |
|
|
|
columns: 7, |
|
|
|
columns: 7, |
|
|
|
emojiSizeMax: 32 * (Platform.isIOS ? 1.10 : 1.0), |
|
|
|
emojiSizeMax: 28 * (Platform.isIOS ? 1.10 : 1.0), |
|
|
|
verticalSpacing: 0, |
|
|
|
verticalSpacing: 0, |
|
|
|
horizontalSpacing: 0, |
|
|
|
horizontalSpacing: 0, |
|
|
|
backgroundColor: const Color(0xFFF2F2F2), |
|
|
|
backgroundColor: const Color(0xFFF2F2F2), |
|
|
|
gridPadding: EdgeInsets.zero, |
|
|
|
gridPadding: EdgeInsets.zero, |
|
|
|
recentsLimit: 28, |
|
|
|
recentsLimit: 35, |
|
|
|
replaceEmojiOnLimitExceed: false, |
|
|
|
replaceEmojiOnLimitExceed: false, |
|
|
|
noRecents: Text( |
|
|
|
noRecents: Text( |
|
|
|
"最近使用", |
|
|
|
"最近使用", |
|
|
@ -1274,17 +1337,22 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
categoryIcons: const CategoryIcons(), |
|
|
|
categoryIcons: const CategoryIcons(), |
|
|
|
tabIndicatorAnimDuration: Duration(milliseconds: 0), |
|
|
|
tabIndicatorAnimDuration: Duration(milliseconds: 0), |
|
|
|
), |
|
|
|
), |
|
|
|
checkPlatformCompatibility: true, |
|
|
|
bottomActionBarConfig: const BottomActionBarConfig( |
|
|
|
|
|
|
|
backgroundColor: Colors.transparent, |
|
|
|
|
|
|
|
buttonIconColor: Colors.blue, |
|
|
|
|
|
|
|
showBackspaceButton: true, |
|
|
|
|
|
|
|
showSearchViewButton: false, |
|
|
|
), |
|
|
|
), |
|
|
|
|
|
|
|
swapCategoryAndBottomBar: false, |
|
|
|
|
|
|
|
checkPlatformCompatibility: true, |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
|
|
|
|
|
|
|
|
///更多 |
|
|
|
///更多 |
|
|
|
Offstage( |
|
|
|
if (moreShow) |
|
|
|
offstage: !moreShow, |
|
|
|
Container( |
|
|
|
child: Container( |
|
|
|
height: keyboard == -1 ? 167 : keyboard, |
|
|
|
height: keyboard == -1 ? 167.h : keyboard, |
|
|
|
|
|
|
|
child: Column( |
|
|
|
child: Column( |
|
|
|
children: [ |
|
|
|
children: [ |
|
|
|
Container( |
|
|
|
Container( |
|
|
@ -1383,7 +1451,6 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
], |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
|
|
|
|
], |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
); |
|
|
|
); |
|
|
@ -1394,4 +1461,5 @@ class _ChatDetailsPage extends State<ChatDetailsPage> |
|
|
|
debugPrint(tex); |
|
|
|
debugPrint(tex); |
|
|
|
Clipboard.setData(ClipboardData(text: tex)); |
|
|
|
Clipboard.setData(ClipboardData(text: tex)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|