This commit is contained in:
abu 2025-09-09 10:57:52 +08:00
parent c4c59d3ec3
commit d61b852883
21 changed files with 1797 additions and 1008 deletions

BIN
assets/images/group.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -6,9 +6,9 @@ import 'package:loopin/utils/index.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
class ChatDetailController extends GetxController {
final String userID;
final String id;
ChatDetailController({required this.userID});
ChatDetailController({required this.id});
final ScrollController chatController = ScrollController();
final RxList<V2TimMessage> chatList = <V2TimMessage>[].obs;

View File

@ -24,6 +24,7 @@ class ImUserInfoController extends GetxController {
"area": "",
"areaCode": "",
"openId": "",
"tag": "",
}.obs;
final role = 0.obs;
final level = 0.obs;
@ -44,6 +45,7 @@ class ImUserInfoController extends GetxController {
"area": "",
"areaCode": "",
"openId": "",
"tag": "",
});
role.value = userInfo.role ?? 0;

View File

@ -71,24 +71,26 @@ class ImMessageListenerService extends GetxService {
///
void _handleNewMessage(V2TimMessage message) async {
final userID = message.sender ?? '';
if (userID.isEmpty) return;
final id = message.sender ?? message.groupID ?? '';
final isGroup = message.groupID != null && message.groupID!.isNotEmpty;
final conversationID = isGroup ? 'group_${message.groupID}' : 'c2c_${message.userID}';
if (id.isEmpty) return;
///
if ((Get.currentRoute == '/chat' || Get.currentRoute == '/chatNoFriend' || Get.currentRoute == '/chatGroup') && Get.isRegistered<ChatDetailController>()) {
final chatDetailController = Get.find<ChatDetailController>();
//
if (chatDetailController.userID == userID) {
// (chatDetailController.id是通过chat_binding传入的userID,groupID)
if (chatDetailController.id == id) {
//
insertTimeLabel(message);
//
await ImService.instance.clearConversationUnreadCount(conversationID: 'c2c_$userID');
await ImService.instance.clearConversationUnreadCount(conversationID: conversationID);
}
return;
}
// 线
if (!LifecycleHandler.isInForeground) {
logger.i("App 当前在后台,收到来自 $userID 的消息 ${message.msgID},暂不展示弹窗");
logger.i("App 当前在后台,收到来自 $id 的消息 ${message.msgID},暂不展示弹窗");
// return;
}
//TODO

View File

@ -17,6 +17,9 @@ import 'package:tencent_cloud_chat_sdk/enum/friend_application_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/friend_response_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/friend_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_add_opt_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_application_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_member_filter_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/group_member_role_enum.dart';
import 'package:tencent_cloud_chat_sdk/enum/history_msg_get_type_enum.dart';
import 'package:tencent_cloud_chat_sdk/manager/v2_tim_group_manager.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_callback.dart';
@ -30,7 +33,17 @@ import 'package:tencent_cloud_chat_sdk/models/v2_tim_follow_type_check_result.da
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_info_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_friend_operation_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_application.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_application_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_info_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_full_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_info_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_operation_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_search_param.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_member_search_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_search_param.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_change_info.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message_search_param.dart';
@ -40,6 +53,8 @@ import 'package:tencent_cloud_chat_sdk/models/v2_tim_user_info_result.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_value_callback.dart';
import 'package:tencent_cloud_chat_sdk/native_im/adapter/tim_conversation_manager.dart';
import 'package:tencent_cloud_chat_sdk/native_im/adapter/tim_friendship_manager.dart';
import 'package:tencent_cloud_chat_sdk/native_im/adapter/tim_group_manager.dart';
import 'package:tencent_cloud_chat_sdk/native_im/adapter/tim_manager.dart';
import 'package:tencent_cloud_chat_sdk/native_im/adapter/tim_message_manager.dart';
import 'package:tencent_cloud_chat_sdk/tencent_im_sdk_plugin.dart';
@ -734,6 +749,7 @@ class ImService {
return ImResult.wrap(res);
}
///------------------------------------
///
Future<ImResult<String>> createGroup({
String? groupID,
@ -768,4 +784,299 @@ class ImService {
return ImResult.wrap(res);
}
///
///
/// - (AVChatRoom) API
/// - SDK 1 10 7008
Future<ImResult<List<V2TimGroupInfo>>> getJoinedGroupList() async {
final res = await V2TIMGroupManager().getJoinedGroupList();
return ImResult.wrap(res);
}
/// work群work群只能邀请进群
Future<ImResult<void>> joinGroup({
required String groupID,
required String message,
}) async {
final res = await TIMManager.instance.joinGroup(
groupID: groupID,
message: message,
);
return ImResult.wrapNoData(res);
}
///
///
/// - [groupIDList] ID
Future<ImResult<List<V2TimGroupInfoResult>>> getGroupsInfo({
required List<String> groupIDList,
}) async {
final res = await V2TIMGroupManager().getGroupsInfo(groupIDList: groupIDList);
return ImResult.wrap(res);
}
///
///
///
/// - [info]
Future<ImResult<void>> setGroupInfo({
required V2TimGroupInfo info,
}) async {
final res = await V2TIMGroupManager().setGroupInfo(info: info);
return ImResult.wrapNoData(res);
}
///
///
///
/// [groupID] ID
/// [filter]
/// - V2TIM_GROUP_MEMBER_FILTER_ALL
/// - V2TIM_GROUP_MEMBER_FILTER_OWNER
/// - V2TIM_GROUP_MEMBER_FILTER_ADMIN
/// - V2TIM_GROUP_MEMBER_FILTER_COMMON
/// [nextSeq] 0
/// [count] 100
/// [offset] Web
Future<ImResult<V2TimGroupMemberInfoResult>> getGroupMemberList({
required String groupID,
required GroupMemberFilterTypeEnum filter,
required String nextSeq,
int count = 20,
int offset = 0,
}) async {
final res = await V2TIMGroupManager().getGroupMemberList(
groupID: groupID,
filter: filter,
nextSeq: nextSeq,
count: count,
offset: offset,
);
return ImResult.wrap(res);
}
///
///
/// [groupID] ID
/// [memberList] ID
Future<ImResult<List<V2TimGroupMemberFullInfo>>> getGroupMembersInfo({
required String groupID,
required List<String> memberList,
}) async {
final res = await V2TIMGroupManager().getGroupMembersInfo(
groupID: groupID,
memberList: memberList,
);
return ImResult.wrap(res);
}
///
///
/// [groupID] ID
/// [userID] ID
/// [nameCard]
/// [customInfo]
Future<ImResult<void>> setGroupMemberInfo({
required String groupID,
required String userID,
String? nameCard,
Map<String, String>? customInfo,
}) async {
final res = await V2TIMGroupManager().setGroupMemberInfo(
groupID: groupID,
userID: userID,
nameCard: nameCard,
customInfo: customInfo,
);
return ImResult.wrapNoData(res);
}
/// (work群的入群方式)
///
/// [groupID] ID
/// [userList] userID
///
///
///
/// ```
/// Work
/// MeetingPublic REST API 使 App
/// AVChatRoom
/// ```
Future<ImResult<List<V2TimGroupMemberOperationResult>>> inviteUserToGroup({
required String groupID,
required List<String> userList,
}) async {
final res = await TIMGroupManager.instance.inviteUserToGroup(
groupID: groupID,
userList: userList,
);
return ImResult.wrap(res);
}
///
///
/// [groupID] ID
/// [memberList] userID
/// [duration]
/// [reason]
///
///
/// ```
/// Work APP
/// PublicMeeting APP
/// AVChatRoommuteGroupMember
/// ```
Future<ImResult<void>> kickGroupMember({
required String groupID,
required List<String> memberList,
int? duration,
String? reason,
}) async {
final res = await TIMGroupManager.instance.kickGroupMember(
groupID: groupID,
memberList: memberList,
duration: duration,
reason: reason,
);
return ImResult.wrapNoData(res);
}
///
///
/// [groupID] ID
/// [userID] userID
/// [role]
///
///
/// ```
/// PublicMeeting
///
/// transferGroupOwner
/// ```
Future<ImResult<void>> setGroupMemberRole({
required String groupID,
required String userID,
required GroupMemberRoleTypeEnum role,
}) async {
final res = await TIMGroupManager.instance.setGroupMemberRole(
groupID: groupID,
userID: userID,
role: role,
);
return ImResult.wrapNoData(res);
}
///
///
/// [groupID] ID
/// [userID] userID
///
///
/// ```
/// WorkPublicMeeting
/// AVChatRoom
/// ```
Future<ImResult<void>> transferGroupOwner({
required String groupID,
required String userID,
}) async {
final res = await TIMGroupManager.instance.transferGroupOwner(
groupID: groupID,
userID: userID,
);
return ImResult.wrapNoData(res);
}
///
Future<ImResult<V2TimGroupApplicationResult>> getGroupApplicationList() async {
final res = await TIMGroupManager.instance.getGroupApplicationList();
return ImResult.wrap(res);
}
///
Future<ImResult<void>> acceptGroupApplication({
required String groupID,
String? reason,
required String fromUser,
required String toUser, //
int? addTime,
GroupApplicationTypeEnum? type,
V2TimGroupApplication? application,
String? webMessageInstance,
}) async {
final res = await TIMGroupManager.instance.acceptGroupApplication(
groupID: groupID,
reason: reason,
fromUser: fromUser,
toUser: toUser,
addTime: addTime,
type: type,
application: application,
);
return ImResult.wrapNoData(res);
}
///
Future<ImResult> refuseGroupApplication({
required String groupID,
String? reason,
required String fromUser,
required String toUser,
required int addTime,
required GroupApplicationTypeEnum type,
V2TimGroupApplication? application,
}) async {
final res = await TIMGroupManager.instance.refuseGroupApplication(
groupID: groupID,
fromUser: fromUser,
toUser: toUser,
addTime: addTime,
type: type,
application: application,
);
return ImResult.wrapNoData(res);
}
///
Future<ImResult> setGroupApplicationRead() async {
final res = await TIMGroupManager.instance.setGroupApplicationRead();
return ImResult.wrapNoData(res);
}
///
Future<ImResult<List<V2TimGroupInfo>>> searchGroups({
required V2TimGroupSearchParam searchParam,
}) async {
final res = await TIMGroupManager.instance.searchGroups(searchParam: searchParam);
return ImResult.wrap(res);
}
///
Future<ImResult<V2GroupMemberInfoSearchResult>> searchGroupMembers({
required V2TimGroupMemberSearchParam param,
}) async {
final res = await TIMGroupManager.instance.searchGroupMembers(param: param);
return ImResult.wrap(res);
}
/// (Work,)
Future<ImResult<void>> dismissGroup({
required String groupID,
}) async {
final res = await TIMManager.instance.dismissGroup(groupID: groupID);
return ImResult.wrapNoData(res);
}
/// 退 ,PublicMeetingAVChatRoom退 dismissGroup
Future<ImResult<void>> quitGroup({
required String groupID,
}) async {
final res = await TIMManager.instance.quitGroup(groupID: groupID);
return ImResult.wrapNoData(res);
}
}

View File

@ -6,6 +6,7 @@ class ChatBinding extends Bindings {
@override
void dependencies() {
V2TimConversation conversation = Get.arguments;
Get.put(ChatDetailController(userID: conversation.userID!));
final id = conversation.userID ?? conversation.groupID;
Get.put(ChatDetailController(id: id!));
}
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/im_friend_listeners.dart';
import 'package:loopin/api/shop_api.dart';
import 'package:loopin/service/http.dart';
@ -117,7 +118,7 @@ class ShopIndexController extends GetxController with GetSingleTickerProviderSta
});
final data = res['data']['records'];
print('商品返回数据------------------------->${data}');
logger.w('商品返回数据:$index------------------------->$data');
tab.dataList.addAll(data);
// logger.w(res);
@ -132,7 +133,7 @@ class ShopIndexController extends GetxController with GetSingleTickerProviderSta
'type': 1,
});
final data = res['data'];
// logger.w(res);
logger.w(res);
swiperData.assignAll(data);
}

View File

@ -2,23 +2,28 @@
library;
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart';
import 'package:loopin/IM/controller/chat_detail_controller.dart';
import 'package:loopin/IM/im_message.dart';
import 'package:loopin/IM/im_result.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/components/image_viewer.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/components/preview_video.dart';
import 'package:loopin/models/summary_type.dart';
import 'package:loopin/utils/audio_player_service.dart';
import 'package:loopin/utils/snapshot.dart';
import 'package:loopin/utils/voice_service.dart';
import 'package:mime/mime.dart';
import 'package:shirne_dialog/shirne_dialog.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
import 'package:video_player/video_player.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
import '../../styles/index.dart';
@ -36,6 +41,8 @@ class Chat extends StatefulWidget {
}
class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
final ImagePicker _picker = ImagePicker();
late final ChatDetailController controller;
//
late final Rx<V2TimConversation> arguments;
@ -346,6 +353,13 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
data: item,
child: Ink(
child: InkWell(
onTap: () {
//
Get.to(() => ImageViewer(
images: [imagePaths.first],
index: 0,
));
},
overlayColor: WidgetStateProperty.all(Colors.transparent),
child: ClipRRect(
borderRadius: BorderRadius.circular(10.0),
@ -525,9 +539,18 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
}
//
else if (item.elemType == 2 && item.cloudCustomData == SummaryType.shareTuangou) {
//price,title,url,sell
// final makeJson = jsonEncode({
// "price": shopObj['price'],
// "title": shopObj['name'],
// "url": shopObj['pic'],
// "sell": Utils.graceNumber(int.parse(shopObj['sales'] ?? '0')),
// "goodsId": shopObj['id'],
// "userID": Get.find<ImUserInfoController>().userID.value,
// });
final obj = jsonDecode(item.customElem!.data!);
logger.e(obj);
final goodsId = obj['goodsId'];
final userID = obj['userID'];
final url = obj['url'];
final title = obj['title'];
final price = obj['price'];
@ -535,6 +558,10 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
msgtpl.add(RenderChatItem(
data: item,
child: GestureDetector(
onTap: () {
// ID
Get.toNamed('/goods', arguments: {'goodsId': goodsId, 'userID': userID});
},
child: Container(
width: 160,
clipBehavior: Clip.antiAlias,
@ -596,11 +623,6 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
],
),
),
onTap: () {
// ID
// Get.toNamed('/goods');
Get.toNamed('/goods', arguments: {});
},
),
));
}
@ -609,10 +631,12 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
/// {imgUrl,videoUrl,width,height}
final obj = jsonDecode(item.customElem!.data!);
logger.e(obj);
final videoId = obj['videoId'];
final videoUrl = obj['videoUrl'];
final imgUrl = obj['imgUrl'];
final width = obj['width'] as num;
final height = obj['height'] as num;
final isHorizontal = width > height;
msgtpl.add(RenderChatItem(
data: item,
child: Ink(
@ -625,9 +649,15 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
children: [
ClipRRect(
borderRadius: BorderRadius.circular(10.0),
child: Container(
width: 120,
height: 240,
color: Colors.black,
child: NetworkOrAssetImage(
imageUrl: imgUrl,
width: 120,
fit: isHorizontal ? BoxFit.contain : BoxFit.cover,
placeholderAsset: 'assets/images/bk.jpg',
),
),
),
const Align(
@ -642,31 +672,32 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
),
),
onTap: () {
showGeneralDialog(
context: context,
barrierColor: Colors.black.withAlpha((1.0 * 255).round()),
pageBuilder: (_, __, ___) {
return SafeArea(
bottom: true,
child: Padding(
padding: const EdgeInsets.only(bottom: 4),
child: PreviewVideo(
videoUrl: videoUrl,
width: width.toDouble(),
height: height.toDouble(),
),
),
);
},
transitionBuilder: (_, anim, __, child) {
return FadeTransition(opacity: anim, child: child);
},
transitionDuration: const Duration(milliseconds: 200),
);
},
onLongPress: () {
contextMenuDialog();
Get.toNamed('/videoDetail', arguments: {'videoId': videoId});
// showGeneralDialog(
// context: context,
// barrierColor: Colors.black.withAlpha((1.0 * 255).round()),
// pageBuilder: (_, __, ___) {
// return SafeArea(
// bottom: true,
// child: Padding(
// padding: const EdgeInsets.only(bottom: 4),
// child: PreviewVideo(
// videoUrl: videoUrl,
// width: width.toDouble(),
// height: height.toDouble(),
// ),
// ),
// );
// },
// transitionBuilder: (_, anim, __, child) {
// return FadeTransition(opacity: anim, child: child);
// },
// transitionDuration: const Duration(milliseconds: 200),
// );
},
// onLongPress: () {
// contextMenuDialog();
// },
),
),
));
@ -1022,7 +1053,7 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
}
//
void sendMessage(message) async {
Future<void> sendMessage(message) async {
//
List<V2TimMessage> messagesToInsert = [];
V2TimMessage? lastRealMsg;
@ -1192,6 +1223,8 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
// =5
void sendVideo(videoFilePath, type, duration, snapshotPath) async {
final instance = MyDialog.loading('处理中');
final resImg = await IMMessage().createVideoMessage(
videoFilePath: videoFilePath,
type: type,
@ -1199,13 +1232,116 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
snapshotPath: snapshotPath,
);
if (resImg.success) {
sendMessage(resImg.data?.messageInfo);
await sendMessage(resImg.data?.messageInfo);
instance.close();
}
}
//
Future<void> showPicker(BuildContext context) async {
showModalBottomSheet(
context: context,
backgroundColor: Colors.white, //
builder: (context) {
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
onTap: () async {
Navigator.pop(context);
await _pickPhoto();
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(Icons.photo),
SizedBox(width: 8),
Text("拍摄照片"),
],
),
),
),
InkWell(
onTap: () async {
Navigator.pop(context);
await _pickVideo();
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(Icons.videocam),
SizedBox(width: 8),
Text("拍摄视频"),
],
),
),
),
const Divider(height: 1),
InkWell(
onTap: () => Navigator.pop(context),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
// Icon(Icons.close),
// SizedBox(width: 8),
Text("取消"),
],
),
),
),
],
),
);
},
);
}
///
Future<void> _pickPhoto() async {
final XFile? photo = await _picker.pickImage(
source: ImageSource.camera,
maxWidth: 1920,
maxHeight: 1080,
imageQuality: 90,
);
if (photo != null) {
logger.w("照片路径: ${photo.path}");
sendImage(photo.path);
}
}
///
Future<void> _pickVideo() async {
final XFile? video = await _picker.pickVideo(
source: ImageSource.camera,
maxDuration: const Duration(minutes: 1),
);
if (video == null) return;
logger.w("视频路径: ${video.path}");
//
final snapshot = await generateVideoThumbnail(video.path) ?? '';
//
final controller = VideoPlayerController.file(File(video.path));
await controller.initialize();
final duration = controller.value.duration.inSeconds;
await controller.dispose();
// mimeType
final mimeType = lookupMimeType(video.path) ?? 'video/mp4';
sendVideo(video.path, mimeType, duration, snapshot);
}
//
void handleChooseAction(key) {
MyDialog.toast('$key');
// MyDialog.toast('$key');
switch (key) {
case 'photo':
// ....
@ -1213,6 +1349,7 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
break;
case 'camera':
// ....
showPicker(context);
break;
case 'redpacket':
sendRedPacketDialog();
@ -1421,35 +1558,35 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
//
void contextMenuDialog() {
showDialog(
context: context,
builder: (context) {
return SimpleDialog(
backgroundColor: Colors.white,
surfaceTintColor: Colors.white,
contentPadding: const EdgeInsets.symmetric(vertical: 5.0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
children: [
SimpleDialogOption(
child: const Padding(padding: EdgeInsets.symmetric(vertical: 5.0), child: Text('复制')),
onPressed: () {},
),
SimpleDialogOption(
child: const Padding(padding: EdgeInsets.symmetric(vertical: 5.0), child: Text('发送给朋友')),
onPressed: () {},
),
SimpleDialogOption(
child: const Padding(padding: EdgeInsets.symmetric(vertical: 5.0), child: Text('收藏')),
onPressed: () {},
),
SimpleDialogOption(
child: const Padding(padding: EdgeInsets.symmetric(vertical: 5.0), child: Text('删除')),
onPressed: () {},
),
],
);
},
);
// showDialog(
// context: context,
// builder: (context) {
// return SimpleDialog(
// backgroundColor: Colors.white,
// surfaceTintColor: Colors.white,
// contentPadding: const EdgeInsets.symmetric(vertical: 5.0),
// shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
// children: [
// SimpleDialogOption(
// child: const Padding(padding: EdgeInsets.symmetric(vertical: 5.0), child: Text('复制')),
// onPressed: () {},
// ),
// SimpleDialogOption(
// child: const Padding(padding: EdgeInsets.symmetric(vertical: 5.0), child: Text('发送给朋友')),
// onPressed: () {},
// ),
// SimpleDialogOption(
// child: const Padding(padding: EdgeInsets.symmetric(vertical: 5.0), child: Text('收藏')),
// onPressed: () {},
// ),
// SimpleDialogOption(
// child: const Padding(padding: EdgeInsets.symmetric(vertical: 5.0), child: Text('删除')),
// onPressed: () {},
// ),
// ],
// );
// },
// );
}
//

View File

@ -1,31 +1,34 @@
///
///
library;
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart';
import 'package:loopin/IM/controller/chat_detail_controller.dart';
import 'package:loopin/IM/im_message.dart';
import 'package:loopin/IM/im_result.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/components/image_viewer.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/components/preview_video.dart';
import 'package:loopin/models/summary_type.dart';
import 'package:loopin/utils/audio_player_service.dart';
import 'package:loopin/utils/snapshot.dart';
import 'package:loopin/utils/voice_service.dart';
import 'package:mime/mime.dart';
import 'package:shirne_dialog/shirne_dialog.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_message.dart';
import 'package:video_player/video_player.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
import '../../styles/index.dart';
import '../../utils/index.dart';
import './components/redpacket.dart';
import './components/richtext.dart';
// import 'mock/chat_json.dart';
import 'mock/emoj_json.dart';
class ChatGroup extends StatefulWidget {
@ -36,9 +39,11 @@ class ChatGroup extends StatefulWidget {
}
class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMixin {
final ImagePicker _picker = ImagePicker();
late final ChatDetailController controller;
//
late V2TimConversation arguments;
final V2TimConversation arguments = Get.arguments;
late String selfUserId;
@ -91,7 +96,6 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
@override
void initState() {
super.initState();
arguments = Get.arguments;
controller = Get.find<ChatDetailController>();
chatController = controller.chatController;
@ -146,56 +150,6 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
super.dispose();
}
//
void setRemark() async {
String remark = '';
await MyDialog.confirm(
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 8),
TextField(
onChanged: (value) => remark = value,
maxLength: 16,
maxLengthEnforcement: MaxLengthEnforcement.enforced, //
decoration: InputDecoration(
hintText: '请输入备注',
filled: true,
fillColor: const Color(0xFFF5F5F5),
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: Color(0xFFBE4EFF), width: 1),
),
),
)
],
),
title: '设置备注',
buttonText: '确认',
cancelText: '取消',
onConfirm: () async {
// print('备注为:$remark');
final res = await ImService.instance.setFriendInfo(userID: arguments.userID!, friendRemark: remark);
if (res.success) {
setState(() {
arguments.showName = remark;
});
} else {
print(res.desc);
print(arguments.userID);
MyDialog.toast(res.desc, icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
}
return true;
},
);
}
void cleanUnRead() async {
if ((arguments.unreadCount ?? 0) > 0) {
final res = await ImService.instance.clearConversationUnreadCount(conversationID: arguments.conversationID);
@ -219,7 +173,6 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
//
V2TimMessage? lastRealMsg;
// for (var msg in controller.chatList.reversed) {
for (var msg in controller.chatList.reversed) {
if (msg.localCustomData != 'time_label') {
lastRealMsg = msg;
@ -227,12 +180,10 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
}
}
final lastMsg = lastRealMsg ?? arguments.lastMessage; //
print(lastMsg?.toLogString());
// final lastMsg = controller.chatList.isNotEmpty ? controller.chatList.last : arguments.lastMessage;
logger.w(lastMsg?.toLogString());
final res = await ImService.instance.getHistoryMessageList(
userID: arguments.userID,
groupID: arguments.groupID,
lastMsg: lastMsg,
);
@ -241,26 +192,21 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
if (newMessages.isEmpty) {
hasMore = false;
// MyDialog.toast('没有更多了~', icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
}
if (initFlag && lastMsg != null) {
newMessages.insert(0, lastMsg);
// controller.scrollToBottom();
}
controller.updateChatListWithTimeLabels(newMessages);
if (initFlag) {
//
WidgetsBinding.instance.addPostFrameCallback((_) {
if (chatController.hasClients) {
// controller.scrollToBottom();
// final bottomPadding = MediaQuery.of(context).padding.bottom; //
// chatController.jumpTo(chatController.position.maxScrollExtent); // 60
chatController.jumpTo(0);
}
});
}
print('聊天数据加载成功');
logger.w('群聊消息加载成功');
} else {
MyDialog.toast("获取聊天记录失败:${res.desc}", icon: Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
}
@ -343,6 +289,13 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
data: item,
child: Ink(
child: InkWell(
onTap: () {
//
Get.to(() => ImageViewer(
images: [imagePaths.first],
index: 0,
));
},
overlayColor: WidgetStateProperty.all(Colors.transparent),
child: ClipRRect(
borderRadius: BorderRadius.circular(10.0),
@ -525,6 +478,8 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
//price,title,url,sell
final obj = jsonDecode(item.customElem!.data!);
logger.e(obj);
final goodsId = obj['goodsId'];
final userID = obj['userID'];
final url = obj['url'];
final title = obj['title'];
final price = obj['price'];
@ -532,6 +487,10 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
msgtpl.add(RenderChatItem(
data: item,
child: GestureDetector(
onTap: () {
// ID
Get.toNamed('/goods', arguments: {'goodsId': goodsId, 'userID': userID});
},
child: Container(
width: 160,
clipBehavior: Clip.antiAlias,
@ -593,11 +552,6 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
],
),
),
onTap: () {
// ID
// Get.toNamed('/goods');
Get.toNamed('/goods', arguments: {});
},
),
));
}
@ -606,10 +560,12 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
/// {imgUrl,videoUrl,width,height}
final obj = jsonDecode(item.customElem!.data!);
logger.e(obj);
final videoId = obj['videoId'];
final videoUrl = obj['videoUrl'];
final imgUrl = obj['imgUrl'];
final width = obj['width'] as num;
final height = obj['height'] as num;
final isHorizontal = width > height;
msgtpl.add(RenderChatItem(
data: item,
child: Ink(
@ -622,9 +578,15 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
children: [
ClipRRect(
borderRadius: BorderRadius.circular(10.0),
child: Container(
width: 120,
height: 240,
color: Colors.black,
child: NetworkOrAssetImage(
imageUrl: imgUrl,
width: 120,
fit: isHorizontal ? BoxFit.contain : BoxFit.cover,
placeholderAsset: 'assets/images/bk.jpg',
),
),
),
const Align(
@ -639,31 +601,32 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
),
),
onTap: () {
showGeneralDialog(
context: context,
barrierColor: Colors.black.withAlpha((1.0 * 255).round()),
pageBuilder: (_, __, ___) {
return SafeArea(
bottom: true,
child: Padding(
padding: const EdgeInsets.only(bottom: 4),
child: PreviewVideo(
videoUrl: videoUrl,
width: width.toDouble(),
height: height.toDouble(),
),
),
);
},
transitionBuilder: (_, anim, __, child) {
return FadeTransition(opacity: anim, child: child);
},
transitionDuration: const Duration(milliseconds: 200),
);
},
onLongPress: () {
contextMenuDialog();
Get.toNamed('/videoDetail', arguments: {'videoId': videoId});
// showGeneralDialog(
// context: context,
// barrierColor: Colors.black.withAlpha((1.0 * 255).round()),
// pageBuilder: (_, __, ___) {
// return SafeArea(
// bottom: true,
// child: Padding(
// padding: const EdgeInsets.only(bottom: 4),
// child: PreviewVideo(
// videoUrl: videoUrl,
// width: width.toDouble(),
// height: height.toDouble(),
// ),
// ),
// );
// },
// transitionBuilder: (_, anim, __, child) {
// return FadeTransition(opacity: anim, child: child);
// },
// transitionDuration: const Duration(milliseconds: 200),
// );
},
// onLongPress: () {
// contextMenuDialog();
// },
),
),
));
@ -1019,7 +982,7 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
}
//
void sendMessage(message) async {
Future<void> sendMessage(message) async {
//
List<V2TimMessage> messagesToInsert = [];
V2TimMessage? lastRealMsg;
@ -1054,7 +1017,7 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
late final ImResult res;
res = await IMMessage().sendMessage(
msg: message,
toUserID: arguments.userID,
toUserID: arguments.groupID,
);
if (res.success && res.data != null) {
@ -1189,6 +1152,8 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
// =5
void sendVideo(videoFilePath, type, duration, snapshotPath) async {
final instance = MyDialog.loading('处理中');
final resImg = await IMMessage().createVideoMessage(
videoFilePath: videoFilePath,
type: type,
@ -1196,13 +1161,116 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
snapshotPath: snapshotPath,
);
if (resImg.success) {
sendMessage(resImg.data?.messageInfo);
await sendMessage(resImg.data?.messageInfo);
instance.close();
}
}
//
Future<void> showPicker(BuildContext context) async {
showModalBottomSheet(
context: context,
backgroundColor: Colors.white, //
builder: (context) {
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
onTap: () async {
Navigator.pop(context);
await _pickPhoto();
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(Icons.photo),
SizedBox(width: 8),
Text("拍摄照片"),
],
),
),
),
InkWell(
onTap: () async {
Navigator.pop(context);
await _pickVideo();
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(Icons.videocam),
SizedBox(width: 8),
Text("拍摄视频"),
],
),
),
),
const Divider(height: 1),
InkWell(
onTap: () => Navigator.pop(context),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
// Icon(Icons.close),
// SizedBox(width: 8),
Text("取消"),
],
),
),
),
],
),
);
},
);
}
///
Future<void> _pickPhoto() async {
final XFile? photo = await _picker.pickImage(
source: ImageSource.camera,
maxWidth: 1920,
maxHeight: 1080,
imageQuality: 90,
);
if (photo != null) {
logger.w("照片路径: ${photo.path}");
sendImage(photo.path);
}
}
///
Future<void> _pickVideo() async {
final XFile? video = await _picker.pickVideo(
source: ImageSource.camera,
maxDuration: const Duration(minutes: 1),
);
if (video == null) return;
logger.w("视频路径: ${video.path}");
//
final snapshot = await generateVideoThumbnail(video.path) ?? '';
//
final controller = VideoPlayerController.file(File(video.path));
await controller.initialize();
final duration = controller.value.duration.inSeconds;
await controller.dispose();
// mimeType
final mimeType = lookupMimeType(video.path) ?? 'video/mp4';
sendVideo(video.path, mimeType, duration, snapshot);
}
//
void handleChooseAction(key) {
MyDialog.toast('$key');
// MyDialog.toast('$key');
switch (key) {
case 'photo':
// ....
@ -1210,6 +1278,7 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
break;
case 'camera':
// ....
showPicker(context);
break;
case 'redpacket':
sendRedPacketDialog();
@ -1418,35 +1487,35 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
//
void contextMenuDialog() {
showDialog(
context: context,
builder: (context) {
return SimpleDialog(
backgroundColor: Colors.white,
surfaceTintColor: Colors.white,
contentPadding: const EdgeInsets.symmetric(vertical: 5.0),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
children: [
SimpleDialogOption(
child: const Padding(padding: EdgeInsets.symmetric(vertical: 5.0), child: Text('复制')),
onPressed: () {},
),
SimpleDialogOption(
child: const Padding(padding: EdgeInsets.symmetric(vertical: 5.0), child: Text('发送给朋友')),
onPressed: () {},
),
SimpleDialogOption(
child: const Padding(padding: EdgeInsets.symmetric(vertical: 5.0), child: Text('收藏')),
onPressed: () {},
),
SimpleDialogOption(
child: const Padding(padding: EdgeInsets.symmetric(vertical: 5.0), child: Text('删除')),
onPressed: () {},
),
],
);
},
);
// showDialog(
// context: context,
// builder: (context) {
// return SimpleDialog(
// backgroundColor: Colors.white,
// surfaceTintColor: Colors.white,
// contentPadding: const EdgeInsets.symmetric(vertical: 5.0),
// shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15.0)),
// children: [
// SimpleDialogOption(
// child: const Padding(padding: EdgeInsets.symmetric(vertical: 5.0), child: Text('复制')),
// onPressed: () {},
// ),
// SimpleDialogOption(
// child: const Padding(padding: EdgeInsets.symmetric(vertical: 5.0), child: Text('发送给朋友')),
// onPressed: () {},
// ),
// SimpleDialogOption(
// child: const Padding(padding: EdgeInsets.symmetric(vertical: 5.0), child: Text('收藏')),
// onPressed: () {},
// ),
// SimpleDialogOption(
// child: const Padding(padding: EdgeInsets.symmetric(vertical: 5.0), child: Text('删除')),
// onPressed: () {},
// ),
// ],
// );
// },
// );
}
//
@ -1496,13 +1565,13 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
},
),
titleSpacing: 1.0,
title: Obx(() {
return Text(
// '${arguments['title']}',
title: Text(
'${arguments.showName}',
style: const TextStyle(fontSize: 18.0, fontFamily: 'Arial'),
);
}),
maxLines: 1,
overflow: TextOverflow.ellipsis,
softWrap: false,
),
flexibleSpace: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
@ -1603,7 +1672,6 @@ class _ChatGroupState extends State<ChatGroup> with SingleTickerProviderStateMix
switch (selected) {
case 'remark':
print('点击了备注');
setRemark();
break;
case 'not':
print('点击了免打扰');
@ -2189,13 +2257,13 @@ class RenderChatItem extends StatelessWidget {
child: Column(
crossAxisAlignment: !(data.isSelf ?? false) ? CrossAxisAlignment.start : CrossAxisAlignment.end,
children: [
// Text(
// displayName ?? '未知昵称',
// style: const TextStyle(color: Colors.grey, fontSize: 12.0),
// ),
// const SizedBox(
// height: 3.0,
// ),
Text(
displayName ?? '未知昵称',
style: const TextStyle(color: Colors.grey, fontSize: 12.0),
),
const SizedBox(
height: 3.0,
),
Stack(
children: [
//

View File

@ -270,7 +270,12 @@ class ChatPageState extends State<ChatPage> {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(
GestureDetector(
onTap: () {
//
Get.toNamed('/groupList');
},
child: Column(
children: [
SvgPicture.asset(
'assets/images/svg/order.svg',
@ -280,6 +285,7 @@ class ChatPageState extends State<ChatPage> {
Text('群聊'),
],
),
),
GestureDetector(
onTap: () {
//
@ -351,6 +357,7 @@ class ChatPageState extends State<ChatPage> {
return Ink(
// color: chatList[index]['topMost'] == null ? Colors.white : Colors.grey[100], //
child: InkWell(
key: ValueKey(chatList[index].conversation.conversationID),
splashColor: Colors.grey[200],
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),

View File

@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/controller/chat_controller.dart';
import 'package:loopin/IM/controller/im_user_info_controller.dart';
import 'package:loopin/IM/im_message.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/api/shop_api.dart';
@ -33,7 +34,7 @@ class Goods extends StatefulWidget {
}
class _GoodsState extends State<Goods> {
// late int shopId; //id
final shareUserId = Get.arguments['userID'] ?? ''; //id,
dynamic shopObj;
late ScrollController scrollController = ScrollController();
final ChatController chatController = Get.find<ChatController>();
@ -124,6 +125,8 @@ class _GoodsState extends State<Goods> {
"title": shopObj['name'],
"url": shopObj['pic'],
"sell": Utils.graceNumber(int.parse(shopObj['sales'] ?? '0')),
"goodsId": shopObj['id'],
"userID": Get.find<ImUserInfoController>().userID.value,
});
final res = await IMMessage().createCustomMessage(
data: makeJson,

View File

@ -0,0 +1,202 @@
///
library;
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/behavior/custom_scroll_behavior.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
import 'package:tencent_cloud_chat_sdk/models/v2_tim_group_info.dart';
class Grouplist extends StatefulWidget {
const Grouplist({super.key});
@override
State<Grouplist> createState() => GrouplistState();
}
class GrouplistState extends State<Grouplist> with SingleTickerProviderStateMixin {
bool isLoading = false; //
bool hasMore = true; //
String page = '';
List<V2TimGroupInfo> dataList = <V2TimGroupInfo>[];
///-------------------
@override
void initState() {
super.initState();
getData();
}
//
Future<void> getData() async {
final res = await ImService.instance.getJoinedGroupList();
if (res.success && res.data != null) {
for (var item in res.data!) {
logger.i('获取成功:${item.toLogString()}');
}
final isFinished = true;
if (isFinished) {
setState(() {
hasMore = false;
});
}
setState(() {
dataList.addAll(res.data!);
});
} else {
logger.e('获取数据失败:${res.desc}');
}
}
//
Future<void> handleRefresh() async {
dataList.clear();
page = '';
getData();
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[50],
appBar: AppBar(
centerTitle: true,
forceMaterialTransparency: true,
bottom: PreferredSize(
preferredSize: const Size.fromHeight(1.0),
child: Container(
color: Colors.grey[300],
height: 1.0,
),
),
title: const Text(
'群聊',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
actions: [],
),
body: ScrollConfiguration(
behavior: CustomScrollBehavior().copyWith(scrollbars: false),
child: Column(
children: [
Expanded(
child: EasyRefresh.builder(
callLoadOverOffset: 20, //
callRefreshOverOffset: 20, //
header: ClassicHeader(
dragText: '下拉刷新',
armedText: '释放刷新',
readyText: '加载中...',
processingText: '加载中...',
processedText: '加载完成',
failedText: '加载失败,请重试',
messageText: '最后更新于 %T',
),
footer: ClassicFooter(
dragText: '加载更多',
armedText: '释放加载',
readyText: '加载中...',
processingText: '加载中...',
processedText: hasMore ? '加载完成' : '没有更多了~',
failedText: '加载失败,请重试',
messageText: '最后更新于 %T',
),
onRefresh: () async {
await handleRefresh();
},
onLoad: () async {
if (hasMore) {
await getData();
}
},
childBuilder: (context, physics) {
return ListView.builder(
physics: physics,
itemCount: dataList.length,
itemBuilder: (context, index) {
final item = dataList[index];
return Ink(
key: ValueKey(item.groupID),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
// + +
Expanded(
child: InkWell(
onTap: () async {
// ,
final res = await ImService.instance.getConversation(conversationID: 'group_${item.groupID}');
if (res.success) {
V2TimConversation conversation = res.data;
logger.w(conversation.toLogString());
conversation.showName = conversation.showName ?? item.groupName;
Get.toNamed(
'/chatGroup',
arguments: conversation,
);
}
},
child: Row(
children: [
ClipOval(
child: NetworkOrAssetImage(
imageUrl: item.faceUrl,
width: 50,
height: 50,
placeholderAsset: 'assets/images/group.png',
),
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
item.groupName?.isNotEmpty == true ? item.groupName! : '群聊',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
),
),
if (item.introduction?.isNotEmpty ?? false) ...[
const SizedBox(height: 2.0),
Text(
item.introduction!,
style: const TextStyle(
color: Colors.grey,
fontSize: 13.0,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
],
),
),
],
),
),
),
],
),
),
);
},
);
},
),
),
],
),
),
);
}
}

View File

@ -2,14 +2,17 @@
library;
import 'package:card_swiper/card_swiper.dart';
import 'package:dynamic_tabbar/dynamic_tabbar.dart';
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/im_friend_listeners.dart';
import 'package:loopin/components/backtop.dart';
import 'package:loopin/components/loading.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/controller/shop_index_controller.dart';
import 'package:loopin/styles/index.dart';
import 'package:loopin/utils/index.dart';
class IndexPage extends StatefulWidget {
@ -127,15 +130,64 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
//
Expanded(
child: controller.tabController == null
? const Center(child: CircularProgressIndicator())
: TabBarView(
controller: controller.tabController,
children: List.generate(
controller.tabList.length,
(index) => _buildTabContent(index),
? Center(child: CircularProgressIndicator())
: Obx(
() {
final tabs = controller.tabList.asMap().entries.map((entry) {
final idx = entry.key;
final item = entry.value;
return TabData(
index: idx,
title: Tab(
child: Center(
child: Text(
item['name'] ?? '',
style: const TextStyle(fontWeight: FontWeight.bold),
overflow: TextOverflow.ellipsis,
softWrap: false,
),
),
),
content: _buildTabContent(idx),
);
}).toList();
return DynamicTabBarWidget(
onTabControllerUpdated: (tabController) {
controller.tabController = tabController;
//
if (tabController.index != 0) {
tabController.animateTo(0);
}
},
onTabChanged: (index) {
logger.w("当前选中tab: $index");
},
dynamicTabs: tabs,
tabAlignment: TabAlignment.start,
isScrollable: true,
showNextIcon: false, //
showBackIcon: false, // 退
labelColor: FStyle.primaryColor,
indicatorColor: FStyle.primaryColor,
overlayColor: WidgetStateProperty.all(Colors.transparent),
unselectedLabelColor: Colors.black87,
indicator: const UnderlineTabIndicator(
borderSide: BorderSide(color: Color.fromARGB(255, 236, 108, 49), width: 2.0),
),
unselectedLabelStyle: const TextStyle(
fontSize: 14.0,
fontFamily: 'Microsoft YaHei',
),
labelStyle: const TextStyle(
fontSize: 14.0,
fontFamily: 'Microsoft YaHei',
fontWeight: FontWeight.bold,
),
dividerHeight: 0,
);
},
),
),
],
),
floatingActionButton: Obx(() {
@ -156,7 +208,6 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
if (controller.tabController == null) {
return SizedBox();
}
return Column(
children: [
//
@ -186,48 +237,6 @@ class _IndexPageState extends State<IndexPage> with SingleTickerProviderStateMix
),
);
}),
// TabBar
Obx(() {
int tabCount = controller.tabList.length;
double screenWidth = MediaQuery.of(context).size.width;
double minTabWidth = 60;
bool isScrollable = tabCount * minTabWidth > screenWidth;
return Container(
color: Colors.white,
child: TabBar(
controller: controller.tabController,
tabs: controller.tabList.map((item) {
return Tab(
child: Text(
item['name'] ?? '',
style: const TextStyle(fontWeight: FontWeight.bold),
overflow: TextOverflow.ellipsis,
softWrap: false,
),
);
}).toList(),
isScrollable: isScrollable,
overlayColor: WidgetStateProperty.all(Colors.transparent),
unselectedLabelColor: Colors.black87,
labelColor: const Color.fromARGB(255, 236, 108, 49),
indicator: const UnderlineTabIndicator(
borderSide: BorderSide(color: Color.fromARGB(255, 236, 108, 49), width: 2.0),
),
unselectedLabelStyle: const TextStyle(
fontSize: 14.0,
fontFamily: 'Microsoft YaHei',
),
labelStyle: const TextStyle(
fontSize: 16.0,
fontFamily: 'Microsoft YaHei',
fontWeight: FontWeight.bold,
),
dividerHeight: 0,
),
);
}),
],
);
}

View File

@ -554,6 +554,7 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
return InkWell(
onTap: () {
//
Get.toNamed('/videoDetail', arguments: {'videoId': item['id']});
},
onLongPress: () {
showModalBottomSheet(

View File

@ -6,18 +6,19 @@ import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/controller/chat_controller.dart';
import 'package:loopin/IM/controller/im_user_info_controller.dart';
import 'package:loopin/IM/im_core.dart';
import 'package:loopin/IM/im_message.dart';
import 'package:loopin/IM/im_service.dart' hide logger;
import 'package:loopin/api/video_api.dart';
import 'package:loopin/components/my_toast.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/models/share_type.dart';
import 'package:loopin/models/summary_type.dart';
import 'package:loopin/service/http.dart';
import 'package:loopin/utils/download_video.dart';
import 'package:loopin/utils/permissions.dart';
import 'package:loopin/utils/wxsdk.dart';
import 'package:loopin/models/share_type.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart';
@ -497,15 +498,19 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
void handlCoverClick(V2TimConversation conv) async {
// VideoMsg,
final userId = conv.userID;
logger.w(videoData);
final String url = videoData['url'];
final img = videoData['firstFrameImg'];
final img = (videoData['cover'] != null && videoData['cover'].toString().isNotEmpty) ? videoData['cover'] : videoData['firstFrameImg'];
final width = videoData['width'];
final height = videoData['height'];
final videoId = videoData['id'];
final makeJson = jsonEncode({
"width": width,
"height": height,
"imgUrl": img,
"videoUrl": url,
"videoId": videoId,
"userID": Get.find<ImUserInfoController>().userID.value,
});
final res = await IMMessage().createCustomMessage(
data: makeJson,
@ -570,7 +575,9 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
onPressed: () => Navigator.pop(context),
),
),
body: Stack(
body: SafeArea(
bottom: true,
child: Stack(
children: [
//
Positioned.fill(
@ -908,6 +915,7 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
),
],
),
),
);
}
}

View File

@ -9,18 +9,19 @@ import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/controller/chat_controller.dart';
import 'package:loopin/IM/controller/im_user_info_controller.dart';
import 'package:loopin/IM/im_core.dart';
import 'package:loopin/IM/im_message.dart';
import 'package:loopin/IM/im_service.dart' hide logger;
import 'package:loopin/api/video_api.dart';
import 'package:loopin/components/my_toast.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/models/share_type.dart';
import 'package:loopin/models/summary_type.dart';
import 'package:loopin/service/http.dart';
import 'package:loopin/utils/download_video.dart';
import 'package:loopin/utils/permissions.dart';
import 'package:loopin/utils/wxsdk.dart';
import 'package:loopin/models/share_type.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart';
@ -982,15 +983,20 @@ class _FriendModuleState extends State<FriendModule> {
void handlCoverClick(V2TimConversation conv) async {
// VideoMsg,
final userId = conv.userID;
final String url = videoList[videoModuleController.videoPlayIndex.value]['url'];
final img = videoList[videoModuleController.videoPlayIndex.value]['firstFrameImg'];
final width = videoList[videoModuleController.videoPlayIndex.value]['width'];
final height = videoList[videoModuleController.videoPlayIndex.value]['height'];
final currentVideo = videoList[videoModuleController.videoPlayIndex.value];
logger.w(currentVideo);
final img = (currentVideo['cover'] != null && currentVideo['cover'].toString().isNotEmpty) ? currentVideo['cover'] : currentVideo['firstFrameImg'];
final url = currentVideo['url'];
final width = currentVideo['width'];
final height = currentVideo['height'];
final videoId = currentVideo['id'];
final makeJson = jsonEncode({
"width": width,
"height": height,
"imgUrl": img,
"videoUrl": url,
"videoId": videoId,
"userID": Get.find<ImUserInfoController>().userID.value,
});
final res = await IMMessage().createCustomMessage(
data: makeJson,
@ -1265,16 +1271,13 @@ class _FriendModuleState extends State<FriendModule> {
),
],
),
onTap: ()async {
onTap: () async {
player.pause();
//
final result = await Get.toNamed(
'/report',
arguments: videoList[videoModuleController
.videoPlayIndex.value]);
final result = await Get.toNamed('/report', arguments: videoList[videoModuleController.videoPlayIndex.value]);
if (result != null) {
player.play();
};
}
},
),
],
@ -1312,8 +1315,9 @@ class _FriendModuleState extends State<FriendModule> {
Text(
text,
maxLines: videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? null : 3,
overflow:
videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? TextOverflow.visible : TextOverflow.ellipsis,
overflow: videoList[videoModuleController.videoPlayIndex.value]['expanded']
? TextOverflow.visible
: TextOverflow.ellipsis,
style: const TextStyle(color: Colors.white, fontSize: 14.0),
),
if (isOverflow)

View File

@ -9,6 +9,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:loopin/IM/controller/chat_controller.dart';
import 'package:loopin/IM/controller/im_user_info_controller.dart';
import 'package:loopin/IM/im_core.dart';
import 'package:loopin/IM/im_message.dart';
import 'package:loopin/IM/im_service.dart' hide logger;
@ -16,12 +17,12 @@ import 'package:loopin/api/video_api.dart';
import 'package:loopin/components/my_toast.dart';
import 'package:loopin/components/network_or_asset_image.dart';
import 'package:loopin/models/conversation_type.dart';
import 'package:loopin/models/share_type.dart';
import 'package:loopin/models/summary_type.dart';
import 'package:loopin/service/http.dart';
import 'package:loopin/utils/download_video.dart';
import 'package:loopin/utils/permissions.dart';
import 'package:loopin/utils/wxsdk.dart';
import 'package:loopin/models/share_type.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart';
@ -980,15 +981,20 @@ class _RecommendModuleState extends State<RecommendModule> {
void handlCoverClick(V2TimConversation conv) async {
// VideoMsg,
final userId = conv.userID;
final String url = videoList[videoModuleController.videoPlayIndex.value]['url'];
final img = videoList[videoModuleController.videoPlayIndex.value]['firstFrameImg'];
final width = videoList[videoModuleController.videoPlayIndex.value]['width'];
final height = videoList[videoModuleController.videoPlayIndex.value]['height'];
final currentVideo = videoList[videoModuleController.videoPlayIndex.value];
logger.w(currentVideo);
final img = (currentVideo['cover'] != null && currentVideo['cover'].toString().isNotEmpty) ? currentVideo['cover'] : currentVideo['firstFrameImg'];
final url = currentVideo['url'];
final width = currentVideo['width'];
final height = currentVideo['height'];
final videoId = currentVideo['id'];
final makeJson = jsonEncode({
"width": width,
"height": height,
"imgUrl": img,
"videoUrl": url,
"videoId": videoId,
"userID": Get.find<ImUserInfoController>().userID.value,
});
final res = await IMMessage().createCustomMessage(
data: makeJson,

View File

@ -10,6 +10,7 @@ import 'package:loopin/pages/chat/chat_no_friend.dart';
import 'package:loopin/pages/chat/notify/interaction.dart';
import 'package:loopin/pages/chat/notify/noFriend.dart';
import 'package:loopin/pages/chat/notify/system.dart';
import 'package:loopin/pages/groupChat/groupList.dart';
import 'package:loopin/pages/groupChat/index.dart';
import 'package:loopin/pages/my/des.dart';
import 'package:loopin/pages/my/fans.dart';
@ -67,8 +68,8 @@ final Map<String, Widget> routes = {
'/flow': const Flowing(),
'/eachFlow': const MutualFollowers(),
//
'/group': const StartGroupChatPage(),
//
'/group': const StartGroupChatPage(), //
'/groupList': const Grouplist(), //
};
final List<GetPage> routeList = routes.entries

View File

@ -51,10 +51,24 @@ String parseMessageSummary(V2TimMessage msg) {
// groupNotifyLeaveUp, // ->
String _parseCustomMessage(V2TimMessage? msg) {
if (msg == null) return '[null]';
final sum = msg.cloudCustomData;
String sum;
// JSONcloudCustomData的类型为Stringstring变为json
final raw = msg.cloudCustomData;
if (raw == null) {
sum = '';
} else {
try {
final decoded = jsonDecode(raw);
sum = decoded is String ? decoded : raw;
} catch (_) {
sum = raw;
}
}
// final sum = jsonDecode(msg.cloudCustomData!);
final elment = msg.customElem; //
logger.w('解析自定义消息:$sum,自定义属性:${msg.cloudCustomData}');
// logger.w('解析element${elment?.desc ?? 'summary_error'}');
// logger.w('解析自定义消息:$sum,自定义属性:${msg.cloudCustomData}');
logger.w(sum);
logger.w('解析element${elment?.desc ?? 'summary_error'}');
try {
switch (sum) {
case SummaryType.hongbao:

View File

@ -249,6 +249,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.1"
dynamic_tabbar:
dependency: "direct main"
description:
name: dynamic_tabbar
sha256: "017be3705f70e353e579b7a7d56bb19b5c112072b4532bc8bd68767c4a6fc3fe"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.9"
easy_refresh:
dependency: "direct main"
description:
@ -598,69 +606,69 @@ packages:
source: hosted
version: "4.5.4"
image_picker:
dependency: transitive
dependency: "direct main"
description:
name: image_picker
sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a"
sha256: "736eb56a911cf24d1859315ad09ddec0b66104bc41a7f8c5b96b4e2620cf5041"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.2"
version: "1.2.0"
image_picker_android:
dependency: transitive
description:
name: image_picker_android
sha256: "6fae381e6af2bbe0365a5e4ce1db3959462fa0c4d234facf070746024bb80c8d"
sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.8.12+24"
version: "0.8.13+1"
image_picker_for_web:
dependency: transitive
description:
name: image_picker_for_web
sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83"
sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.6"
version: "3.1.0"
image_picker_ios:
dependency: transitive
description:
name: image_picker_ios
sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100"
sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.8.12+2"
version: "0.8.13"
image_picker_linux:
dependency: transitive
description:
name: image_picker_linux
sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9"
sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.1+2"
version: "0.2.2"
image_picker_macos:
dependency: transitive
description:
name: image_picker_macos
sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1"
sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.1+2"
version: "0.2.2"
image_picker_platform_interface:
dependency: transitive
description:
name: image_picker_platform_interface
sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0"
sha256: "9f143b0dba3e459553209e20cc425c9801af48e6dfa4f01a0fcf927be3f41665"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.10.1"
version: "2.11.0"
image_picker_windows:
dependency: transitive
description:
name: image_picker_windows
sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb"
sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.1+1"
version: "0.2.2"
install_plugin:
dependency: "direct main"
description:
@ -846,7 +854,7 @@ packages:
source: hosted
version: "1.16.0"
mime:
dependency: transitive
dependency: "direct main"
description:
name: mime
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
@ -1419,7 +1427,7 @@ packages:
source: hosted
version: "2.1.4"
video_player:
dependency: transitive
dependency: "direct main"
description:
name: video_player
sha256: "0d55b1f1a31e5ad4c4967bfaa8ade0240b07d20ee4af1dfef5f531056512961a"

View File

@ -61,6 +61,7 @@ dependencies:
photo_view: ^0.15.0
shirne_dialog: ^4.8.3
package_info_plus: ^8.3.0
dynamic_tabbar: ^1.0.9 #tab
flutter_upgrader: ^1.1.20 #更新
path_provider: ^2.1.2
@ -88,6 +89,9 @@ dependencies:
audioplayers: ^6.5.0 #音频播放
flutter_html: ^3.0.0
timer_count_down: ^2.2.2 #倒计时
image_picker: ^1.2.0 #相机
video_player: ^2.10.0 #视频处理
mime: ^2.0.0 #文件类型推断
dev_dependencies:
flutter_launcher_icons: ^0.13.1 # 使用最新版本