Browse Source

1. 修改发送状态错位。

2. 添加聊天用户标签。
3. 优化好友页面
ios
Jolie 4 months ago
parent
commit
0c8638d423
18 changed files with 992 additions and 639 deletions
  1. 4
      android/app/build.gradle.kts
  2. BIN
      assets/images/chat_user_bg.png
  3. BIN
      assets/images/chat_user_bg_bottom.png
  4. BIN
      assets/images/gift_pic.png
  5. 123
      ios/Podfile.lock
  6. 10
      ios/Runner.xcworkspace/contents.xcworkspacedata
  7. 98
      lib/controller/message/chat_controller.dart
  8. 2
      lib/generated/assets.dart
  9. 320
      lib/pages/message/chat_page.dart
  10. 221
      lib/pages/message/conversation_tab.dart
  11. 12
      lib/pages/message/friend_tab.dart
  12. 2
      lib/widget/message/chat_input_bar.dart
  13. 153
      lib/widget/message/image_item.dart
  14. 20
      lib/widget/message/message_item.dart
  15. 20
      lib/widget/message/text_item.dart
  16. 134
      lib/widget/message/video_item.dart
  17. 68
      location_plugin/example/pubspec.lock
  18. 444
      pubspec.lock

4
android/app/build.gradle.kts

@ -77,8 +77,8 @@ android {
}
// 对应:shrinkResources true / minifyEnabled true
isShrinkResources = true
isMinifyEnabled = true
isShrinkResources = false
isMinifyEnabled = false
// 对应:proguardFiles "proguard-rules.pro"
// 最简单写法:

BIN
assets/images/chat_user_bg.png

Before After
Width: 1420  |  Height: 450  |  Size: 6.3 KiB

BIN
assets/images/chat_user_bg_bottom.png

Before After
Width: 1422  |  Height: 704  |  Size: 9.5 KiB

BIN
assets/images/gift_pic.png

Before After
Width: 174  |  Height: 190  |  Size: 28 KiB

123
ios/Podfile.lock

@ -1,123 +0,0 @@
PODS:
- AgoraInfra_iOS (1.2.13)
- audioplayers_darwin (0.0.1):
- Flutter
- FlutterMacOS
- camera_avfoundation (0.0.1):
- Flutter
- Flutter (1.0.0)
- flutter_native_splash (2.4.3):
- Flutter
- HyphenateChat (4.15.1):
- AgoraInfra_iOS (= 1.2.13)
- im_flutter_sdk_ios (4.15.2):
- Flutter
- HyphenateChat (= 4.15.1)
- image_picker_ios (0.0.1):
- Flutter
- package_info_plus (0.4.5):
- Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- permission_handler_apple (9.3.0):
- Flutter
- photo_manager (3.7.1):
- Flutter
- FlutterMacOS
- record_ios (1.1.0):
- Flutter
- sensors_plus (0.0.1):
- Flutter
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
- video_player_avfoundation (0.0.1):
- Flutter
- FlutterMacOS
- wakelock_plus (0.0.1):
- Flutter
DEPENDENCIES:
- audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/darwin`)
- camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
- Flutter (from `Flutter`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- im_flutter_sdk_ios (from `.symlinks/plugins/im_flutter_sdk_ios/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
- record_ios (from `.symlinks/plugins/record_ios/ios`)
- sensors_plus (from `.symlinks/plugins/sensors_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
SPEC REPOS:
trunk:
- AgoraInfra_iOS
- HyphenateChat
EXTERNAL SOURCES:
audioplayers_darwin:
:path: ".symlinks/plugins/audioplayers_darwin/darwin"
camera_avfoundation:
:path: ".symlinks/plugins/camera_avfoundation/ios"
Flutter:
:path: Flutter
flutter_native_splash:
:path: ".symlinks/plugins/flutter_native_splash/ios"
im_flutter_sdk_ios:
:path: ".symlinks/plugins/im_flutter_sdk_ios/ios"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
photo_manager:
:path: ".symlinks/plugins/photo_manager/ios"
record_ios:
:path: ".symlinks/plugins/record_ios/ios"
sensors_plus:
:path: ".symlinks/plugins/sensors_plus/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sqflite_darwin:
:path: ".symlinks/plugins/sqflite_darwin/darwin"
video_player_avfoundation:
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
wakelock_plus:
:path: ".symlinks/plugins/wakelock_plus/ios"
SPEC CHECKSUMS:
AgoraInfra_iOS: 65e11a2183ab7836258768868d06058c22701b13
audioplayers_darwin: 4027b33a8f471d996c13f71cb77f0b1583b5d923
camera_avfoundation: 281867ff09f1da66f031a184ecfbc6f2e625c9f5
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
HyphenateChat: ec813941100d602d24e06b04b867474d634cb39d
im_flutter_sdk_ios: de87814fcf3a3cb585a78b55fba5f9fec989096b
image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
photo_manager: 81954a1bf804b6e882d0453b3b6bc7fad7b47d3d
record_ios: 840d21cce013c5a3b2168b74a54ebdb4136359e2
sensors_plus: 7229095999f30740798f0eeef5cd120357a8f4f2
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
video_player_avfoundation: 7993f492ae0bd77edaea24d9dc051d8bb2cd7c86
wakelock_plus: 76957ab028e12bfa4e66813c99e46637f367fc7e
PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e
COCOAPODS: 1.16.2

10
ios/Runner.xcworkspace/contents.xcworkspacedata

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

98
lib/controller/message/chat_controller.dart

@ -46,6 +46,7 @@ class ChatController extends GetxController {
final EMUserInfo? info = await IMManager.instance.getUserInfo(userId);
if (info != null) {
userInfo.value = info;
update(); // GetBuilder UI
if (Get.isLogEnable) {
Get.log('获取用户信息成功: ${info.nickName}');
}
@ -70,8 +71,8 @@ class ChatController extends GetxController {
content: content,
);
//
messages.insert(0, tempMessage);
//
messages.add(tempMessage);
update();
//
@ -111,8 +112,8 @@ class ChatController extends GetxController {
sendOriginalImage: false,
);
//
messages.insert(0, tempMessage);
//
messages.add(tempMessage);
update();
//
@ -155,8 +156,8 @@ class ChatController extends GetxController {
duration: seconds,
);
//
messages.insert(0, tempMessage);
//
messages.add(tempMessage);
update();
//
@ -192,25 +193,23 @@ class ChatController extends GetxController {
///
Future<bool> sendVideoMessage(String filePath, int duration) async {
//
if (isSendingVideo.value) {
SmartDialog.showToast('视频正在发送中,请稍候...');
return false;
}
try {
//
isSendingVideo.value = true;
sendingStatus.value = '正在准备视频...';
update();
print('🎬 [ChatController] 准备发送视频消息');
print('视频路径: $filePath');
print('视频时长: $duration');
sendingStatus.value = '正在上传视频...';
// 使
final tempMessage = EMMessage.createVideoSendMessage(
targetId: userId,
filePath: filePath,
duration: duration,
);
//
messages.add(tempMessage);
update();
//
final message = await IMManager.instance.sendVideoMessage(
filePath,
userId,
@ -218,50 +217,28 @@ class ChatController extends GetxController {
);
if (message != null) {
print('✅ [ChatController] 视频消息创建成功');
print('消息类型: ${message.body.type}');
sendingStatus.value = '发送成功';
update();
//
messages.insert(0, message);
print('✅ [ChatController] 视频消息发送成功');
//
final index = messages.indexWhere((msg) => msg.msgId == tempMessage.msgId);
if (index != -1) {
messages[index] = message;
}
update();
//
_refreshConversationList();
//
SmartDialog.showToast('✅ 视频发送成功');
return true;
} else {
// FAIL
update();
SmartDialog.showToast('视频发送失败,请点击重发');
return false;
}
print('❌ [ChatController] 视频消息创建失败');
sendingStatus.value = '发送失败';
update();
SmartDialog.showToast('❌ 视频消息发送失败,请重试');
return false;
} catch (e) {
print('❌ [ChatController] 发送视频消息异常: $e');
sendingStatus.value = '发送失败: $e';
update();
if (Get.isLogEnable) {
Get.log('发送视频消息失败: $e');
}
SmartDialog.showToast('❌ 视频消息发送失败: ${e.toString()}');
SmartDialog.showToast('视频发送失败: $e');
return false;
} finally {
//
isSendingVideo.value = false;
sendingStatus.value = '';
update();
}
}
@ -289,17 +266,16 @@ class ChatController extends GetxController {
.toList();
if (loadMore) {
//
// ListView是reverse的
//
final existingMsgIds = messages.map((msg) => msg.msgId).toSet();
final newMessages = validMessages
.where((msg) => !existingMsgIds.contains(msg.msgId))
.toList();
if (newMessages.isNotEmpty) {
messages.addAll(newMessages);
// ID
_cursor = newMessages.last.msgId;
messages.insertAll(0, newMessages);
// ID
_cursor = newMessages.first.msgId;
if (Get.isLogEnable) {
Get.log('加载更多消息成功,新增: ${newMessages.length} 条,总数量: ${messages.length}');
}
@ -313,9 +289,9 @@ class ChatController extends GetxController {
} else {
//
messages.assignAll(validMessages);
// ID
// ID
if (validMessages.isNotEmpty) {
_cursor = validMessages.last.msgId;
_cursor = validMessages.first.msgId;
} else {
_cursor = null;
}
@ -349,8 +325,8 @@ class ChatController extends GetxController {
void addReceivedMessage(EMMessage message) {
//
if (!messages.any((msg) => msg.msgId == message.msgId)) {
//
messages.insert(0, message);
//
messages.add(message);
update();
//
_refreshConversationList();

2
lib/generated/assets.dart

@ -86,6 +86,8 @@ class Assets {
static const String imagesCertPhone = 'assets/images/cert_phone.png';
static const String imagesCertRealname = 'assets/images/cert_realname.png';
static const String imagesChatBtn = 'assets/images/chat_btn.png';
static const String imagesChatUserBg = 'assets/images/chat_user_bg.png';
static const String imagesChatUserBgBottom = 'assets/images/chat_user_bg_bottom.png';
static const String imagesCheck = 'assets/images/check.png';
static const String imagesCloseArrow = 'assets/images/close_arrow.png';
static const String imagesCustomer = 'assets/images/customer.png';

320
lib/pages/message/chat_page.dart

@ -34,14 +34,28 @@ class _ChatPageState extends State<ChatPage> {
//
_scrollController.addListener(() {
if (_scrollController.hasClients &&
_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 100 &&
_scrollController.position.pixels <=
_scrollController.position.minScrollExtent + 100 &&
!_isLoadingMore &&
_controller.messages.isNotEmpty &&
_controller.hasMoreMessages()) {
_loadMoreMessages();
}
});
//
_controller.messages.listen((_) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_scrollController.hasClients && _controller.messages.isNotEmpty) {
//
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: Duration(milliseconds: 300),
curve: Curves.easeOut,
);
}
});
});
}
@override
@ -84,53 +98,6 @@ class _ChatPageState extends State<ChatPage> {
),
body: Column(
children: [
//
Obx(() {
if (controller.isSendingVideo.value) {
return Container(
width: double.infinity,
padding: EdgeInsets.symmetric(
horizontal: 16.w,
vertical: 12.h,
),
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.1),
border: Border(
bottom: BorderSide(
color: Colors.blue.withOpacity(0.3),
width: 1,
),
),
),
child: Row(
children: [
SizedBox(
width: 20.w,
height: 20.w,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.blue,
),
),
),
SizedBox(width: 12.w),
Expanded(
child: Text(
controller.sendingStatus.value,
style: TextStyle(
fontSize: 14.sp,
color: Colors.blue[700],
fontWeight: FontWeight.w500,
),
),
),
],
),
);
}
return SizedBox.shrink();
}),
//
Expanded(
child: Container(
@ -143,18 +110,28 @@ class _ChatPageState extends State<ChatPage> {
behavior: HitTestBehavior.opaque,
child: ListView.builder(
controller: _scrollController,
reverse: true,
padding: EdgeInsets.all(16.w),
itemCount: controller.messages.length,
reverse: false,
padding: EdgeInsets.only(
top: 16.w,
left: 16.w,
right: 16.w,
bottom: 16.w,
),
itemCount: controller.messages.length + 1, //
// 🚀
cacheExtent: 500, // 500
itemBuilder: (context, index) {
final message = controller.messages[index];
//
if (index == 0) {
return _buildUserInfoCard(controller);
}
final message = controller.messages[index - 1];
final isSentByMe =
message.direction == MessageDirection.SEND;
final previousMessage = index > 0
? controller.messages[index - 1]
final previousMessage = index > 1
? controller.messages[index - 2]
: null;
// 🚀 key
@ -207,6 +184,239 @@ class _ChatPageState extends State<ChatPage> {
);
}
//
Widget _buildUserInfoCard(ChatController controller) {
final userInfo = controller.userInfo.value;
//
if (userInfo == null) {
return SizedBox.shrink();
}
return Container(
margin: EdgeInsets.only(bottom: 16.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.w),
),
child: LayoutBuilder(
builder: (context, constraints) {
// 710:351
// ListView padding16.w
final screenWidth = MediaQuery.of(context).size.width;
final availableWidth = screenWidth - 32.w; // padding
final width = availableWidth;
final height = width * (351 / 710);
return Container(
height: height,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.w),
image: DecorationImage(
image: AssetImage(Assets.imagesChatUserBg),
fit: BoxFit.cover,
),
),
child: Stack(
children: [
// chat_user_bg_bottom710x351比例覆盖整个卡片
Positioned.fill(
child: ClipRRect(
borderRadius: BorderRadius.circular(16.w),
child: Image.asset(
Assets.imagesChatUserBgBottom,
fit: BoxFit.cover,
),
),
),
//
Padding(
padding: EdgeInsets.all(16.w),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//
Wrap(
spacing: 8.w,
runSpacing: 4.w,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Text(
userInfo.nickName ?? '用户',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: Colors.black87,
),
),
//
Container(
padding: EdgeInsets.symmetric(
horizontal: 8.w,
vertical: 2.h,
),
decoration: BoxDecoration(
color: Color(0xFFF3E9FF),
borderRadius: BorderRadius.circular(12.w),
border: Border.all(
width: 1,
color: Color.fromRGBO(117, 98, 249, 0.32),
),
),
constraints: BoxConstraints(
minWidth: 40.w,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
Assets.imagesVerifiedIcon,
width: 14.w,
height: 12.w,
),
SizedBox(width: 4.w),
Text(
'实名',
style: TextStyle(
fontSize: 9.sp,
color: Color.fromRGBO(160, 92, 255, 1),
fontWeight: FontWeight.w500,
),
),
],
),
),
// 线
Container(
padding: EdgeInsets.symmetric(
horizontal: 8.w,
vertical: 2.h,
),
decoration: BoxDecoration(
color: Color.fromRGBO(234, 255, 219, 1),
borderRadius: BorderRadius.circular(12.w),
border: Border.all(
width: 1,
color: Color.fromRGBO(117, 98, 249, 0.32),
),
),
child: Text(
'在线',
style: TextStyle(
fontSize: 9.sp,
color: Color.fromRGBO(38, 199, 124, 1),
fontWeight: FontWeight.w500,
),
),
),
],
),
SizedBox(height: 8.h),
//
Row(
children: [
//
Image.asset(
Assets.imagesFemale,
width: 14.w,
height: 14.w,
),
SizedBox(width: 4.w),
Text(
'19',
style: TextStyle(
fontSize: 12.sp,
color: Colors.grey[700],
),
),
SizedBox(width: 12.w),
Text(
'北京',
style: TextStyle(
fontSize: 12.sp,
color: Colors.grey[700],
),
),
SizedBox(width: 12.w),
Text(
'160cm',
style: TextStyle(
fontSize: 12.sp,
color: Colors.grey[700],
),
),
],
),
],
),
),
//
Container(
width: 32.w,
height: 32.w,
decoration: BoxDecoration(
color: Colors.grey[200],
shape: BoxShape.circle,
),
child: Icon(
Icons.arrow_forward_ios,
size: 16.w,
color: Colors.grey[700],
),
),
],
),
SizedBox(height: 8.h), //
//
Text(
'喜欢在广州的早茶里抢最后一个虾饺,也能在深夜的猎德大桥...',
style: TextStyle(
fontSize: 13.sp,
color: Colors.grey[800],
height: 1.4,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 8.h), //
//
Row(
children: List.generate(4, (index) {
return Expanded(
child: Container(
margin: EdgeInsets.only(
right: index < 3 ? 8.w : 0,
),
height: 50.w, //
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.w),
image: DecorationImage(
image: AssetImage(Assets.imagesAvatarsExample),
fit: BoxFit.cover,
),
),
),
);
}),
),
],
),
),
],
),
);
},
),
);
}
//
Future<void> _loadMoreMessages() async {
if (_isLoadingMore) return;

221
lib/pages/message/conversation_tab.dart

@ -44,15 +44,16 @@ class _ConversationTabState extends State<ConversationTab>
);
}
if (controller.conversations.isEmpty) {
return const Center(child: Text('暂无会话'));
}
// 使
return ListView.builder(
padding: const EdgeInsets.only(top: 8),
itemCount: controller.conversations.length,
itemCount: controller.conversations.length + 1, //
itemBuilder: (context, index) {
final conversation = controller.conversations[index];
//
if (index == 0) {
return _buildTestConversationItem();
}
final conversation = controller.conversations[index - 1];
return _buildConversationItem(conversation);
},
);
@ -62,6 +63,139 @@ class _ConversationTabState extends State<ConversationTab>
);
}
//
Widget _buildTestConversationItem() {
final double screenWidth = MediaQuery.of(context).size.width;
final int testUnreadCount = 3;
final String testTime = controller.formatMessageTime(
DateTime.now().millisecondsSinceEpoch,
);
final Widget cellContent = Builder(
builder: (cellContext) => GestureDetector(
onTap: () {
TDSwipeCellInherited.of(cellContext)?.cellClick();
// 使ID
Get.to(ChatPage(userId: 'test_user_001'));
},
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(16)),
),
margin: const EdgeInsets.only(
bottom: 8,
left: 16,
right: 16,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(28),
image: const DecorationImage(
image: AssetImage(Assets.imagesAvatarsExample),
fit: BoxFit.cover,
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Expanded(
child: Text(
'测试用户',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.black,
),
),
),
Text(
testTime,
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
],
),
const SizedBox(height: 6),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Expanded(
child: Text(
'这是一条测试消息,用于调试',
style: TextStyle(
fontSize: 14,
color: Colors.grey,
),
overflow: TextOverflow.ellipsis,
),
),
Container(
margin: const EdgeInsets.only(left: 8),
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(10),
),
child: Text(
testUnreadCount.toString(),
style: const TextStyle(
fontSize: 12,
color: Colors.white,
),
),
),
],
),
],
),
),
],
),
),
),
);
return TDSwipeCell(
slidableKey: const ValueKey('test_conversation'),
groupTag: 'conversation_swipe_group',
right: TDSwipeCellPanel(
extentRatio: 72 / screenWidth,
children: [
TDSwipeCellAction(
backgroundColor: TDTheme.of(context).errorColor6,
label: '删除',
onPressed: (actionContext) {
Get.snackbar('调试', '删除测试会话');
},
),
],
),
cell: cellContent,
);
}
//
Widget _buildConversationItem(EMConversation conversation) {
return FutureBuilder<EMUserInfo>(
@ -125,13 +259,16 @@ class _ConversationTabState extends State<ConversationTab>
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
userInfo?.nickName ?? '联系人',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.black,
Expanded(
child: Text(
userInfo?.nickName ?? '联系人',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.black,
),
),
),
Text(
@ -145,36 +282,44 @@ class _ConversationTabState extends State<ConversationTab>
),
],
),
const SizedBox(height: 4),
Text(
controller.getLastMessageContent(message),
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
overflow: TextOverflow.ellipsis,
const SizedBox(height: 6),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Text(
controller.getLastMessageContent(message),
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
overflow: TextOverflow.ellipsis,
),
),
if (unreadCount > 0)
Container(
margin: const EdgeInsets.only(left: 8),
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(10),
),
child: Text(
unreadCount.toString(),
style: const TextStyle(
fontSize: 12,
color: Colors.white,
),
),
),
],
),
],
),
),
if (unreadCount > 0)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(10),
),
child: Text(
unreadCount.toString(),
style: const TextStyle(
fontSize: 12,
color: Colors.white,
),
),
),
],
),
),

12
lib/pages/message/friend_tab.dart

@ -173,8 +173,8 @@ class _FriendTabState extends State<FriendTab> with TickerProviderStateMixin {
Widget _buildTabButton(String title, int index) {
final bool isSelected = _tabController.index == index;
//
final double width = index == 0 ? 72 : 36;
final double height = index == 0 ? 42 : 21;
final double width = index == 0 ? 82 : 60;
final double height = index == 0 ? 30 : 30;
return GestureDetector(
onTap: () {
@ -188,14 +188,14 @@ class _FriendTabState extends State<FriendTab> with TickerProviderStateMixin {
child: Container(
decoration: BoxDecoration(
color: isSelected ? _primaryPurple : Colors.white,
borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(28),
),
alignment: Alignment.center,
child: Text(
title,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
fontSize: 12,
fontWeight: FontWeight.w700,
color: isSelected ? Colors.white : Colors.black87,
),
),
@ -222,7 +222,7 @@ class _FriendTabState extends State<FriendTab> with TickerProviderStateMixin {
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
color: Color(0x00FFFFFF),
borderRadius: BorderRadius.circular(8),
),
child: Row(

2
lib/widget/message/chat_input_bar.dart

@ -248,7 +248,7 @@ class _ChatInputBarState extends State<ChatInputBar> {
vertical: 8.h,
),
decoration: BoxDecoration(
color: Colors.blue,
color: Color.fromRGBO(117, 98, 249, 1),
borderRadius: BorderRadius.circular(5.h),
),
child: Text(

153
lib/widget/message/image_item.dart

@ -11,12 +11,16 @@ class ImageItem extends StatelessWidget {
final bool isSentByMe;
final bool showTime;
final String formattedTime;
final EMMessage message; //
final VoidCallback? onResend; //
const ImageItem({
required this.imageBody,
required this.isSentByMe,
required this.showTime,
required this.formattedTime,
required this.message,
this.onResend,
super.key,
});
@ -31,31 +35,48 @@ class ImageItem extends StatelessWidget {
horizontal: 16.w,
vertical: 8.h,
),
child: Row(
mainAxisAlignment: isSentByMe ? MainAxisAlignment.end : MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!isSentByMe) _buildAvatar(),
if (!isSentByMe) SizedBox(width: 8.w),
GestureDetector(
onTap: _onImageTap,
child: Container(
margin: EdgeInsets.only(top: 10.h),
decoration: BoxDecoration(
color: isSentByMe ? Color(0xff8E7BF6) : Colors.white,
borderRadius: BorderRadius.only(
topLeft: isSentByMe ? Radius.circular(12.w) : Radius.circular(0),
topRight: isSentByMe ? Radius.circular(0) : Radius.circular(12.w),
bottomLeft: Radius.circular(12.w),
bottomRight: Radius.circular(12.w),
child: Builder(
builder: (context) {
//
double maxWidth = 180.w;
double width = maxWidth;
double height = width * (304 / 289); // 289:304
final imageHeight = height + 10.h; // margin top (10.h)
return Row(
mainAxisAlignment: isSentByMe ? MainAxisAlignment.end : MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!isSentByMe) _buildAvatar(),
if (!isSentByMe) SizedBox(width: 8.w),
//
if (isSentByMe)
SizedBox(
height: imageHeight,
child: Center(
child: _buildMessageStatus(),
),
),
if (isSentByMe) SizedBox(width: 10.w),
GestureDetector(
onTap: _onImageTap,
child: Container(
margin: EdgeInsets.only(top: 10.h),
decoration: BoxDecoration(
color: isSentByMe ? Color(0xff8E7BF6) : Colors.white,
borderRadius: BorderRadius.circular(18.w),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(18.w),
child: _buildImage(),
),
),
),
child: _buildImage(),
),
),
if (isSentByMe) SizedBox(width: 8.w),
if (isSentByMe) _buildAvatar(),
],
if (isSentByMe) SizedBox(width: 8.w),
if (isSentByMe) _buildAvatar(),
],
);
},
),
),
],
@ -101,25 +122,11 @@ class ImageItem extends StatelessWidget {
//
Widget _buildImage() {
// 200
double maxWidth = 200.w;
double maxHeight = 200.w;
// 289x304比例计算图片尺寸
// 289
double maxWidth = 180.w;
double width = maxWidth;
double height = maxHeight;
//
if (imageBody.width != null && imageBody.width! > 0 &&
imageBody.height != null && imageBody.height! > 0) {
final aspectRatio = imageBody.width! / imageBody.height!;
if (aspectRatio > 1) {
//
width = maxWidth;
height = maxWidth / aspectRatio;
} else {
//
height = maxHeight;
width = maxHeight * aspectRatio;
}
}
double height = width * (304 / 289); // 289:304
//
final localPath = imageBody.localPath;
if (localPath.isNotEmpty) {
@ -156,24 +163,72 @@ class ImageItem extends StatelessWidget {
return _buildErrorContainer(width, height);
}
//
//
Widget _buildLoadingContainer(double width, double height) {
return Container(
width: width,
height: height,
padding: EdgeInsets.all(8.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.w),
borderRadius: BorderRadius.circular(18.w),
color: Colors.grey[200],
),
alignment: Alignment.center,
child: CircularProgressIndicator(
strokeWidth: 2.w,
color: Colors.grey[400],
child: Center(
child: CircularProgressIndicator(
strokeWidth: 2.w,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.grey[400]!,
),
),
),
);
}
//
Widget _buildMessageStatus() {
//
if (!isSentByMe) {
return SizedBox.shrink();
}
//
final status = message.status;
if (status == MessageStatus.FAIL) {
//
return GestureDetector(
onTap: onResend,
child: Container(
width: 20.w,
height: 20.w,
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
Icons.refresh,
size: 14.w,
color: Colors.red,
),
),
);
} else if (status == MessageStatus.PROGRESS) {
//
return Container(
width: 16.w,
height: 16.w,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.grey,
),
),
);
} else {
//
return SizedBox.shrink();
}
}
//
Widget _buildErrorContainer(double width, double height) {
return Container(
@ -181,7 +236,7 @@ class ImageItem extends StatelessWidget {
height: height,
padding: EdgeInsets.all(8.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.w),
borderRadius: BorderRadius.circular(18.w),
color: Colors.grey[200],
),
alignment: Alignment.center,

20
lib/widget/message/message_item.dart

@ -53,6 +53,16 @@ class MessageItem extends StatelessWidget {
isSentByMe: isSentByMe,
showTime: shouldShowTime(),
formattedTime: formatMessageTime(message.serverTime),
message: message,
onResend: () {
// Get找到ChatController并调用重发方法
try {
final controller = Get.find<ChatController>();
controller.resendMessage(message);
} catch (e) {
print('重发消息失败: $e');
}
},
);
}
//
@ -75,6 +85,16 @@ class MessageItem extends StatelessWidget {
isSentByMe: isSentByMe,
showTime: shouldShowTime(),
formattedTime: formatMessageTime(message.serverTime),
message: message,
onResend: () {
// Get找到ChatController并调用重发方法
try {
final controller = Get.find<ChatController>();
controller.resendMessage(message);
} catch (e) {
print('重发消息失败: $e');
}
},
);
}

20
lib/widget/message/text_item.dart

@ -36,10 +36,20 @@ class TextItem extends StatelessWidget {
),
child: Row(
mainAxisAlignment: isSentByMe ? MainAxisAlignment.end : MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
if (!isSentByMe) _buildAvatar(),
if (!isSentByMe) SizedBox(width: 8.w),
//
if (isSentByMe)
Align(
alignment: Alignment.center,
child: Container(
margin: EdgeInsets.only(top: 10.h),
child: _buildMessageStatus(),
),
),
if (isSentByMe) SizedBox(width: 10.w),
Container(
constraints: BoxConstraints(maxWidth: 240.w),
margin: EdgeInsets.only(top: 10.h),
@ -65,8 +75,6 @@ class TextItem extends StatelessWidget {
emojiSize: 24.w,
),
),
//
if (isSentByMe) _buildMessageStatus(),
if (isSentByMe) SizedBox(width: 8.w),
if (isSentByMe) _buildAvatar(),
],
@ -130,7 +138,6 @@ class TextItem extends StatelessWidget {
child: Container(
width: 20.w,
height: 20.w,
margin: EdgeInsets.only(right: 4.w),
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
shape: BoxShape.circle,
@ -147,17 +154,16 @@ class TextItem extends StatelessWidget {
return Container(
width: 16.w,
height: 16.w,
margin: EdgeInsets.only(right: 4.w),
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
isSentByMe ? Colors.white70 : Colors.grey,
Colors.grey,
),
),
);
} else {
//
return SizedBox(width: 4.w);
return SizedBox.shrink();
}
}
}

134
lib/widget/message/video_item.dart

@ -7,18 +7,23 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:video_player/video_player.dart';
import 'package:dating_touchme_app/pages/message/video_player_page.dart';
import 'package:dating_touchme_app/generated/assets.dart';
class VideoItem extends StatefulWidget {
final EMVideoMessageBody videoBody;
final bool isSentByMe;
final bool showTime;
final String formattedTime;
final EMMessage message; //
final VoidCallback? onResend; //
const VideoItem({
required this.videoBody,
required this.isSentByMe,
required this.showTime,
required this.formattedTime,
required this.message,
this.onResend,
super.key,
});
@ -202,24 +207,44 @@ class _VideoItemState extends State<VideoItem> {
children: [
if (widget.showTime) _buildTimeLabel(),
Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 4.h),
child: Row(
mainAxisAlignment: widget.isSentByMe
? MainAxisAlignment.end
: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 🚀
Stack(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
child: Builder(
builder: (context) {
//
double maxWidth = 180.w;
double width = maxWidth;
double height = width * (304 / 289); // 289:304
final videoHeight = height + 10.h; // margin top (10.h)
return Row(
mainAxisAlignment: widget.isSentByMe
? MainAxisAlignment.end
: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
onTap: _playVideo,
child: Container(
width: 200.w,
height: 150.h,
if (!widget.isSentByMe) _buildAvatar(),
if (!widget.isSentByMe) SizedBox(width: 8.w),
//
if (widget.isSentByMe)
SizedBox(
height: videoHeight,
child: Center(
child: _buildMessageStatus(),
),
),
if (widget.isSentByMe) SizedBox(width: 10.w),
// 🚀
Stack(
children: [
GestureDetector(
onTap: _playVideo,
child: Container(
margin: EdgeInsets.only(top: 10.h),
width: 180.w,
height: 180.w * (304 / 289), // 289:304
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(12.w),
borderRadius: BorderRadius.circular(18.w),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
@ -229,7 +254,7 @@ class _VideoItemState extends State<VideoItem> {
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(12.w),
borderRadius: BorderRadius.circular(18.w),
child: Stack(
fit: StackFit.expand,
children: [
@ -328,11 +353,9 @@ class _VideoItemState extends State<VideoItem> {
if (_isLoadingVideo)
Positioned.fill(
child: Container(
width: 200.w,
height: 150.h,
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.5),
borderRadius: BorderRadius.circular(12.w),
borderRadius: BorderRadius.circular(18.w),
),
child: Center(
child: CircularProgressIndicator(
@ -346,13 +369,78 @@ class _VideoItemState extends State<VideoItem> {
),
],
),
],
if (widget.isSentByMe) SizedBox(width: 8.w),
if (widget.isSentByMe) _buildAvatar(),
],
);
},
),
),
],
);
}
//
Widget _buildAvatar() {
return Container(
width: 40.w,
height: 40.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.w),
image: DecorationImage(
image: AssetImage(Assets.imagesAvatarsExample),
fit: BoxFit.cover,
),
),
);
}
//
Widget _buildMessageStatus() {
//
if (!widget.isSentByMe) {
return SizedBox.shrink();
}
//
final status = widget.message.status;
if (status == MessageStatus.FAIL) {
//
return GestureDetector(
onTap: widget.onResend,
child: Container(
width: 20.w,
height: 20.w,
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(
Icons.refresh,
size: 14.w,
color: Colors.red,
),
),
);
} else if (status == MessageStatus.PROGRESS) {
//
return Container(
width: 16.w,
height: 16.w,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.grey,
),
),
);
} else {
//
return SizedBox.shrink();
}
}
// 🚀
Widget _buildThumbnail() {
if (_thumbnailPath == null || _thumbnailPath!.isEmpty) {
@ -375,7 +463,7 @@ class _VideoItemState extends State<VideoItem> {
// 🚀 使 CachedNetworkImage
return CachedNetworkImage(
imageUrl: _thumbnailPath!,
fit: BoxFit.cover,
fit: BoxFit.contain,
width: double.infinity,
height: double.infinity,
placeholder: (context, url) {
@ -410,7 +498,7 @@ class _VideoItemState extends State<VideoItem> {
print('✅ [VideoItem] 本地缩略图文件存在,开始渲染');
return Image.file(
file,
fit: BoxFit.cover,
fit: BoxFit.contain,
width: double.infinity,
height: double.infinity,
// 🚀
@ -439,7 +527,7 @@ class _VideoItemState extends State<VideoItem> {
print('✅ [VideoItem] 视频预览加载成功');
// 🎯 使 FittedBox
return FittedBox(
fit: BoxFit.cover,
fit: BoxFit.contain,
child: SizedBox(
width: snapshot.data!.value.size.width,
height: snapshot.data!.value.size.height,

68
location_plugin/example/pubspec.lock

@ -6,7 +6,7 @@ packages:
description:
name: async
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "2.13.0"
boolean_selector:
@ -14,7 +14,7 @@ packages:
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
characters:
@ -22,7 +22,7 @@ packages:
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
clock:
@ -30,7 +30,7 @@ packages:
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.1.2"
collection:
@ -38,7 +38,7 @@ packages:
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
cupertino_icons:
@ -46,7 +46,7 @@ packages:
description:
name: cupertino_icons
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.0.8"
fake_async:
@ -54,7 +54,7 @@ packages:
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
file:
@ -62,7 +62,7 @@ packages:
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "7.0.1"
flutter:
@ -80,7 +80,7 @@ packages:
description:
name: flutter_lints
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "5.0.0"
flutter_test:
@ -98,7 +98,7 @@ packages:
description:
name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.6.0"
http_parser:
@ -106,7 +106,7 @@ packages:
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
integration_test:
@ -119,7 +119,7 @@ packages:
description:
name: leak_tracker
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "11.0.2"
leak_tracker_flutter_testing:
@ -127,7 +127,7 @@ packages:
description:
name: leak_tracker_flutter_testing
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "3.0.10"
leak_tracker_testing:
@ -135,7 +135,7 @@ packages:
description:
name: leak_tracker_testing
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
lints:
@ -143,7 +143,7 @@ packages:
description:
name: lints
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "5.1.1"
location_plugin:
@ -158,7 +158,7 @@ packages:
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "0.12.17"
material_color_utilities:
@ -166,7 +166,7 @@ packages:
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "0.11.1"
meta:
@ -174,7 +174,7 @@ packages:
description:
name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.16.0"
path:
@ -182,7 +182,7 @@ packages:
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
platform:
@ -190,7 +190,7 @@ packages:
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
plugin_platform_interface:
@ -198,7 +198,7 @@ packages:
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
process:
@ -206,7 +206,7 @@ packages:
description:
name: process
sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "5.0.5"
sky_engine:
@ -219,7 +219,7 @@ packages:
description:
name: source_span
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.10.1"
stack_trace:
@ -227,7 +227,7 @@ packages:
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
stream_channel:
@ -235,7 +235,7 @@ packages:
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
string_scanner:
@ -243,7 +243,7 @@ packages:
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
sync_http:
@ -251,7 +251,7 @@ packages:
description:
name: sync_http
sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
term_glyph:
@ -259,7 +259,7 @@ packages:
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test_api:
@ -267,7 +267,7 @@ packages:
description:
name: test_api
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "0.7.6"
typed_data:
@ -275,7 +275,7 @@ packages:
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
vector_math:
@ -283,7 +283,7 @@ packages:
description:
name: vector_math
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
vm_service:
@ -291,7 +291,7 @@ packages:
description:
name: vm_service
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "15.0.2"
web:
@ -299,7 +299,7 @@ packages:
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
webdriver:
@ -307,7 +307,7 @@ packages:
description:
name: webdriver
sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade"
url: "https://pub.flutter-io.cn"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
sdks:

444
pubspec.lock
File diff suppressed because it is too large
View File

Loading…
Cancel
Save