merge
This commit is contained in:
parent
c4c59d3ec3
commit
d61b852883
BIN
assets/images/group.png
Normal file
BIN
assets/images/group.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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 过滤有管理员标识的信息
|
||||
|
@ -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):群里的任何人都可以邀请其他人进群。
|
||||
/// 会议群(Meeting)和公开群(Public):只有通过 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 管理员可以踢人。
|
||||
/// 公开群(Public)、会议群(Meeting):群主、管理员和 APP 管理员可以踢人。
|
||||
/// 直播群(AVChatRoom):只支持禁言(muteGroupMember),不支持踢人。
|
||||
/// ```
|
||||
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] 目标角色
|
||||
///
|
||||
/// 注意:
|
||||
/// ```
|
||||
/// 公开群(Public)和会议群(Meeting):只有群主才能对群成员进行普通成员和管理员之间的角色切换。
|
||||
/// 其他群不支持设置群成员角色。
|
||||
/// 转让群组请调用 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
|
||||
///
|
||||
/// 注意:
|
||||
/// ```
|
||||
/// 普通类型的群(Work、Public、Meeting):只有群主才有权限进行群转让操作。
|
||||
/// 直播群(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);
|
||||
}
|
||||
|
||||
/// 退群 ,在公开群(Public)、会议(Meeting)和直播群(AVChatRoom)中,群主是不可以退群的,群主只能调用 dismissGroup 解散群组。
|
||||
Future<ImResult<void>> quitGroup({
|
||||
required String groupID,
|
||||
}) async {
|
||||
final res = await TIMManager.instance.quitGroup(groupID: groupID);
|
||||
return ImResult.wrapNoData(res);
|
||||
}
|
||||
}
|
||||
|
@ -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!));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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: NetworkOrAssetImage(
|
||||
imageUrl: imgUrl,
|
||||
child: Container(
|
||||
width: 120,
|
||||
height: 240,
|
||||
color: Colors.black,
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: imgUrl,
|
||||
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: () {},
|
||||
// ),
|
||||
// ],
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
}
|
||||
|
||||
// 发群红包弹窗
|
||||
|
@ -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: NetworkOrAssetImage(
|
||||
imageUrl: imgUrl,
|
||||
child: Container(
|
||||
width: 120,
|
||||
height: 240,
|
||||
color: Colors.black,
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: imgUrl,
|
||||
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']}',
|
||||
'${arguments.showName}',
|
||||
style: const TextStyle(fontSize: 18.0, fontFamily: 'Arial'),
|
||||
);
|
||||
}),
|
||||
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: [
|
||||
// 气泡箭头
|
||||
|
@ -270,15 +270,21 @@ class ChatPageState extends State<ChatPage> {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/order.svg',
|
||||
height: 36.0,
|
||||
width: 36.0,
|
||||
),
|
||||
Text('群聊'),
|
||||
],
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
// 去群聊列表
|
||||
Get.toNamed('/groupList');
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/order.svg',
|
||||
height: 36.0,
|
||||
width: 36.0,
|
||||
),
|
||||
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),
|
||||
|
@ -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,
|
||||
|
202
lib/pages/groupChat/groupList.dart
Normal file
202
lib/pages/groupChat/groupList.dart
Normal 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,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,13 +130,62 @@ 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,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -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,
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -554,6 +554,7 @@ class MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
//去视频详情
|
||||
Get.toNamed('/videoDetail', arguments: {'videoId': item['id']});
|
||||
},
|
||||
onLongPress: () {
|
||||
showModalBottomSheet(
|
||||
|
@ -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,343 +575,346 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
// 视频区域
|
||||
Positioned.fill(
|
||||
child: GestureDetector(
|
||||
child: Stack(
|
||||
children: [
|
||||
Visibility(
|
||||
visible: position > Duration.zero,
|
||||
child: Video(
|
||||
controller: videoController,
|
||||
fit: isHorizontal ? BoxFit.contain : BoxFit.cover,
|
||||
controls: NoVideoControls,
|
||||
body: SafeArea(
|
||||
bottom: true,
|
||||
child: Stack(
|
||||
children: [
|
||||
// 视频区域
|
||||
Positioned.fill(
|
||||
child: GestureDetector(
|
||||
child: Stack(
|
||||
children: [
|
||||
Visibility(
|
||||
visible: position > Duration.zero,
|
||||
child: Video(
|
||||
controller: videoController,
|
||||
fit: isHorizontal ? BoxFit.contain : BoxFit.cover,
|
||||
controls: NoVideoControls,
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedOpacity(
|
||||
opacity: position > Duration(milliseconds: 100) ? 0.0 : 1.0,
|
||||
duration: Duration(milliseconds: 50),
|
||||
child: Image.network(
|
||||
videoData['firstFrameImg'] ?? 'https://wuzhongjie.com.cn/download/logo.png',
|
||||
fit: isHorizontal ? BoxFit.contain : BoxFit.cover,
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
AnimatedOpacity(
|
||||
opacity: position > Duration(milliseconds: 100) ? 0.0 : 1.0,
|
||||
duration: Duration(milliseconds: 50),
|
||||
child: Image.network(
|
||||
videoData['firstFrameImg'] ?? 'https://wuzhongjie.com.cn/download/logo.png',
|
||||
fit: isHorizontal ? BoxFit.contain : BoxFit.cover,
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
),
|
||||
),
|
||||
),
|
||||
StreamBuilder(
|
||||
stream: player.stream.playing,
|
||||
builder: (context, playing) {
|
||||
return Visibility(
|
||||
visible: playing.data == false,
|
||||
child: Center(
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
player.playOrPause();
|
||||
},
|
||||
icon: Icon(
|
||||
playing.data == true ? Icons.pause : Icons.play_arrow_rounded,
|
||||
color: Colors.white60,
|
||||
size: 80,
|
||||
StreamBuilder(
|
||||
stream: player.stream.playing,
|
||||
builder: (context, playing) {
|
||||
return Visibility(
|
||||
visible: playing.data == false,
|
||||
child: Center(
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
player.playOrPause();
|
||||
},
|
||||
icon: Icon(
|
||||
playing.data == true ? Icons.pause : Icons.play_arrow_rounded,
|
||||
color: Colors.white60,
|
||||
size: 80,
|
||||
),
|
||||
style: ButtonStyle(backgroundColor: WidgetStateProperty.all(Colors.black.withAlpha(15))),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
player.playOrPause();
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// 右侧操作栏
|
||||
Positioned(
|
||||
bottom: 100.0,
|
||||
right: 6.0,
|
||||
child: Column(
|
||||
spacing: 15.0,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 55.0,
|
||||
width: 48.0,
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
player.pause();
|
||||
// 跳转到 Vloger 页面并等待返回结果
|
||||
final result = await Get.toNamed('/vloger', arguments: videoData);
|
||||
if (result != null) {
|
||||
// 处理返回的参数
|
||||
player.play();
|
||||
setState(() {
|
||||
videoData['doIFollowVloger'] = result['followStatus'];
|
||||
});
|
||||
}
|
||||
},
|
||||
child: UnconstrainedBox(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Container(
|
||||
height: 48.0,
|
||||
width: 48.0,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.white, width: 2.0),
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
),
|
||||
child: ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: videoData['vlogerFace'] ?? videoData['commentUserFace'],
|
||||
),
|
||||
),
|
||||
),
|
||||
style: ButtonStyle(backgroundColor: WidgetStateProperty.all(Colors.black.withAlpha(15))),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 15.0,
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
height: 18.0,
|
||||
width: 18.0,
|
||||
decoration: BoxDecoration(
|
||||
color: videoData['doIFollowVloger'] == true ? Colors.white : Color(0xFFFF5000),
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
),
|
||||
child: Icon(
|
||||
videoData['doIFollowVloger'] == true ? Icons.check : Icons.add,
|
||||
color: videoData['doIFollowVloger'] == true ? Color(0xFFFF5000) : Colors.white,
|
||||
size: 14.0,
|
||||
),
|
||||
),
|
||||
onTap: () async {
|
||||
if (videoData['doIFollowVloger'] == true) {
|
||||
await unfollowUser();
|
||||
} else {
|
||||
await followUser();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/heart.svg',
|
||||
colorFilter: ColorFilter.mode(videoData['doILikeThisVlog'] == true ? Color(0xFFFF5000) : Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
Text(
|
||||
'${videoData['likeCounts'] ?? 0}',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
if (videoData['doILikeThisVlog'] == true) {
|
||||
doUnLikeVideo();
|
||||
} else {
|
||||
doLikeVideo();
|
||||
}
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/reply.svg',
|
||||
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
Text(
|
||||
'${videoData['commentsCounts'] ?? 0}',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
handleComment();
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/share.svg',
|
||||
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
handleShare();
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/report.svg',
|
||||
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () async {
|
||||
player.pause();
|
||||
// 跳转到举报页面并等待返回结果
|
||||
final result = await Get.toNamed('/report', arguments: videoData);
|
||||
if (result != null) {
|
||||
player.play();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// 底部信息区域
|
||||
Positioned(
|
||||
bottom: 15.0,
|
||||
left: 10.0,
|
||||
right: 80.0,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'@${videoData['vlogerNickname'] ?? videoData['commentUserNickname'] ?? '未知用户'}',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 16.0),
|
||||
),
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final text = videoData['title'] ?? '未知';
|
||||
final span = TextSpan(
|
||||
text: text,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14.0),
|
||||
);
|
||||
final tp = TextPainter(
|
||||
text: span,
|
||||
maxLines: 3,
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
tp.layout(maxWidth: constraints.maxWidth);
|
||||
final isOverflow = tp.didExceedMaxLines;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
text,
|
||||
maxLines: videoData['expanded'] ?? false ? null : 3,
|
||||
overflow: videoData['expanded'] ?? false ? TextOverflow.visible : TextOverflow.ellipsis,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14.0),
|
||||
),
|
||||
if (isOverflow)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 6.0),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
videoData['expanded'] = !(videoData['expanded'] ?? false);
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
videoData['expanded'] ?? false ? '收起' : '展开更多',
|
||||
textAlign: TextAlign.right,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
player.playOrPause();
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// 右侧操作栏
|
||||
Positioned(
|
||||
bottom: 100.0,
|
||||
right: 6.0,
|
||||
child: Column(
|
||||
spacing: 15.0,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 55.0,
|
||||
width: 48.0,
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
player.pause();
|
||||
// 跳转到 Vloger 页面并等待返回结果
|
||||
final result = await Get.toNamed('/vloger', arguments: videoData);
|
||||
if (result != null) {
|
||||
// 处理返回的参数
|
||||
player.play();
|
||||
setState(() {
|
||||
videoData['doIFollowVloger'] = result['followStatus'];
|
||||
});
|
||||
}
|
||||
},
|
||||
child: UnconstrainedBox(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Container(
|
||||
height: 48.0,
|
||||
width: 48.0,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.white, width: 2.0),
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
),
|
||||
child: ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: videoData['vlogerFace'] ?? videoData['commentUserFace'],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// 进度条
|
||||
Positioned(
|
||||
bottom: 0.0,
|
||||
left: 6.0,
|
||||
right: 6.0,
|
||||
child: Visibility(
|
||||
visible: position > Duration.zero,
|
||||
child: Listener(
|
||||
child: SliderTheme(
|
||||
data: SliderThemeData(
|
||||
trackHeight: sliderDraging ? 6.0 : 2.0,
|
||||
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 4.0),
|
||||
overlayShape: RoundSliderOverlayShape(overlayRadius: 0),
|
||||
inactiveTrackColor: Colors.white24,
|
||||
activeTrackColor: Colors.white,
|
||||
thumbColor: Colors.white,
|
||||
overlayColor: Colors.transparent,
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 15.0,
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
height: 18.0,
|
||||
width: 18.0,
|
||||
decoration: BoxDecoration(
|
||||
color: videoData['doIFollowVloger'] == true ? Colors.white : Color(0xFFFF5000),
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
),
|
||||
child: Icon(
|
||||
videoData['doIFollowVloger'] == true ? Icons.check : Icons.add,
|
||||
color: videoData['doIFollowVloger'] == true ? Color(0xFFFF5000) : Colors.white,
|
||||
size: 14.0,
|
||||
),
|
||||
),
|
||||
onTap: () async {
|
||||
if (videoData['doIFollowVloger'] == true) {
|
||||
await unfollowUser();
|
||||
} else {
|
||||
await followUser();
|
||||
}
|
||||
},
|
||||
),
|
||||
child: Slider(
|
||||
value: sliderValue,
|
||||
onChanged: (value) async {
|
||||
setState(() {
|
||||
sliderValue = value;
|
||||
});
|
||||
await player.seek(duration * value.clamp(0.0, 1.0));
|
||||
},
|
||||
onChangeEnd: (value) async {
|
||||
setState(() {
|
||||
sliderDraging = false;
|
||||
});
|
||||
if (!player.state.playing) {
|
||||
await player.play();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/heart.svg',
|
||||
colorFilter: ColorFilter.mode(videoData['doILikeThisVlog'] == true ? Color(0xFFFF5000) : Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
Text(
|
||||
'${videoData['likeCounts'] ?? 0}',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
if (videoData['doILikeThisVlog'] == true) {
|
||||
doUnLikeVideo();
|
||||
} else {
|
||||
doLikeVideo();
|
||||
}
|
||||
onPointerMove: (e) {
|
||||
setState(() {
|
||||
sliderDraging = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/reply.svg',
|
||||
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
Text(
|
||||
'${videoData['commentsCounts'] ?? 0}',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
handleComment();
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/share.svg',
|
||||
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
handleShare();
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/report.svg',
|
||||
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () async {
|
||||
player.pause();
|
||||
// 跳转到举报页面并等待返回结果
|
||||
final result = await Get.toNamed('/report', arguments: videoData);
|
||||
if (result != null) {
|
||||
player.play();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// 底部信息区域
|
||||
Positioned(
|
||||
bottom: 15.0,
|
||||
left: 10.0,
|
||||
right: 80.0,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'@${videoData['vlogerNickname'] ?? videoData['commentUserNickname'] ?? '未知用户'}',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 16.0),
|
||||
),
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final text = videoData['title'] ?? '未知';
|
||||
final span = TextSpan(
|
||||
text: text,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14.0),
|
||||
);
|
||||
final tp = TextPainter(
|
||||
text: span,
|
||||
maxLines: 3,
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
tp.layout(maxWidth: constraints.maxWidth);
|
||||
final isOverflow = tp.didExceedMaxLines;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
text,
|
||||
maxLines: videoData['expanded'] ?? false ? null : 3,
|
||||
overflow: videoData['expanded'] ?? false ? TextOverflow.visible : TextOverflow.ellipsis,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14.0),
|
||||
),
|
||||
if (isOverflow)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 6.0),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
videoData['expanded'] = !(videoData['expanded'] ?? false);
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
videoData['expanded'] ?? false ? '收起' : '展开更多',
|
||||
textAlign: TextAlign.right,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// 进度条
|
||||
Positioned(
|
||||
bottom: 0.0,
|
||||
left: 6.0,
|
||||
right: 6.0,
|
||||
child: Visibility(
|
||||
visible: position > Duration.zero,
|
||||
child: Listener(
|
||||
child: SliderTheme(
|
||||
data: SliderThemeData(
|
||||
trackHeight: sliderDraging ? 6.0 : 2.0,
|
||||
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 4.0),
|
||||
overlayShape: RoundSliderOverlayShape(overlayRadius: 0),
|
||||
inactiveTrackColor: Colors.white24,
|
||||
activeTrackColor: Colors.white,
|
||||
thumbColor: Colors.white,
|
||||
overlayColor: Colors.transparent,
|
||||
),
|
||||
child: Slider(
|
||||
value: sliderValue,
|
||||
onChanged: (value) async {
|
||||
setState(() {
|
||||
sliderValue = value;
|
||||
});
|
||||
await player.seek(duration * value.clamp(0.0, 1.0));
|
||||
},
|
||||
onChangeEnd: (value) async {
|
||||
setState(() {
|
||||
sliderDraging = false;
|
||||
});
|
||||
if (!player.state.playing) {
|
||||
await player.play();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
onPointerMove: (e) {
|
||||
setState(() {
|
||||
sliderDraging = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 时间显示
|
||||
Positioned(
|
||||
bottom: 100.0,
|
||||
left: 10.0,
|
||||
right: 10.0,
|
||||
child: Visibility(
|
||||
visible: sliderDraging,
|
||||
child: DefaultTextStyle(
|
||||
style: TextStyle(color: Colors.white54, fontSize: 18.0, fontFamily: 'Arial'),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Text(position.label(reference: duration), style: TextStyle(color: Colors.white)),
|
||||
Text('/', style: TextStyle(fontSize: 14.0)),
|
||||
Text(duration.label(reference: duration)),
|
||||
],
|
||||
),
|
||||
)),
|
||||
),
|
||||
],
|
||||
// 时间显示
|
||||
Positioned(
|
||||
bottom: 100.0,
|
||||
left: 10.0,
|
||||
right: 10.0,
|
||||
child: Visibility(
|
||||
visible: sliderDraging,
|
||||
child: DefaultTextStyle(
|
||||
style: TextStyle(color: Colors.white54, fontSize: 18.0, fontFamily: 'Arial'),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Text(position.label(reference: duration), style: TextStyle(color: Colors.white)),
|
||||
Text('/', style: TextStyle(fontSize: 14.0)),
|
||||
Text(duration.label(reference: duration)),
|
||||
],
|
||||
),
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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,
|
||||
@ -1019,399 +1025,397 @@ class _FriendModuleState extends State<FriendModule> {
|
||||
child: Column(
|
||||
children: [
|
||||
// 添加暂无数据提示
|
||||
if (videoList.isEmpty && !isLoadingMore)
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Text(
|
||||
'暂无数据',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16.0,
|
||||
if (videoList.isEmpty && !isLoadingMore)
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Text(
|
||||
'暂无数据',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
/// 垂直滚动模块
|
||||
PageView.builder(
|
||||
scrollBehavior: CustomScrollBehavior().copyWith(scrollbars: false),
|
||||
scrollDirection: Axis.vertical,
|
||||
controller: pageController,
|
||||
onPageChanged: (index) async {
|
||||
videoModuleController.updateVideoPlayIndex(index);
|
||||
setState(() {
|
||||
sliderValue = 0.0;
|
||||
sliderDraging = false;
|
||||
position = Duration.zero;
|
||||
duration = Duration.zero;
|
||||
});
|
||||
)
|
||||
else
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: [
|
||||
/// 垂直滚动模块
|
||||
PageView.builder(
|
||||
scrollBehavior: CustomScrollBehavior().copyWith(scrollbars: false),
|
||||
scrollDirection: Axis.vertical,
|
||||
controller: pageController,
|
||||
onPageChanged: (index) async {
|
||||
videoModuleController.updateVideoPlayIndex(index);
|
||||
setState(() {
|
||||
sliderValue = 0.0;
|
||||
sliderDraging = false;
|
||||
position = Duration.zero;
|
||||
duration = Duration.zero;
|
||||
});
|
||||
|
||||
player.stop();
|
||||
await player.open(Media(videoList[index]['url']));
|
||||
if (index == videoList.length - 2 && !isLoadingMore) {
|
||||
await fetchVideoList();
|
||||
}
|
||||
},
|
||||
itemCount: videoList.length,
|
||||
itemBuilder: (context, index) {
|
||||
final videoWidth = videoList[index]['width'] ?? 1;
|
||||
final videoHeight = videoList[index]['height'] ?? 1;
|
||||
final isHorizontal = videoWidth > videoHeight;
|
||||
final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList();
|
||||
return Stack(
|
||||
children: [
|
||||
// 视频区域
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: GestureDetector(
|
||||
child: Stack(
|
||||
children: [
|
||||
Visibility(
|
||||
visible: videoModuleController.videoPlayIndex.value == index && position > Duration.zero,
|
||||
child: Video(
|
||||
controller: videoController,
|
||||
fit: isHorizontal ? BoxFit.contain : BoxFit.cover,
|
||||
controls: NoVideoControls,
|
||||
),
|
||||
),
|
||||
AnimatedOpacity(
|
||||
opacity: videoModuleController.videoPlayIndex.value == index && position > Duration(milliseconds: 100) ? 0.0 : 1.0,
|
||||
duration: Duration(milliseconds: 50),
|
||||
child: Image.network(
|
||||
videoList[index]['firstFrameImg'] ?? 'https://wuzhongjie.com.cn/download/logo.png',
|
||||
fit: isHorizontal ? BoxFit.contain : BoxFit.cover,
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
),
|
||||
),
|
||||
StreamBuilder(
|
||||
stream: player.stream.playing,
|
||||
builder: (context, playing) {
|
||||
return Visibility(
|
||||
visible: playing.data == false,
|
||||
child: Center(
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
player.playOrPause();
|
||||
},
|
||||
icon: Icon(
|
||||
playing.data == true ? Icons.pause : Icons.play_arrow_rounded,
|
||||
color: Colors.white60,
|
||||
size: 80,
|
||||
),
|
||||
style: ButtonStyle(backgroundColor: WidgetStateProperty.all(Colors.black.withAlpha(15))),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
player.playOrPause();
|
||||
},
|
||||
),
|
||||
),
|
||||
// 右侧操作栏
|
||||
Positioned(
|
||||
bottom: 100.0,
|
||||
right: 6.0,
|
||||
child: Column(
|
||||
spacing: 15.0,
|
||||
children: [
|
||||
Stack(
|
||||
player.stop();
|
||||
await player.open(Media(videoList[index]['url']));
|
||||
if (index == videoList.length - 2 && !isLoadingMore) {
|
||||
await fetchVideoList();
|
||||
}
|
||||
},
|
||||
itemCount: videoList.length,
|
||||
itemBuilder: (context, index) {
|
||||
final videoWidth = videoList[index]['width'] ?? 1;
|
||||
final videoHeight = videoList[index]['height'] ?? 1;
|
||||
final isHorizontal = videoWidth > videoHeight;
|
||||
final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList();
|
||||
return Stack(
|
||||
children: [
|
||||
// 视频区域
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: GestureDetector(
|
||||
child: Stack(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 55.0,
|
||||
width: 48.0,
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
player.pause();
|
||||
// 跳转到 Vloger 页面并等待返回结果
|
||||
final result = await Get.toNamed('/vloger', arguments: videoList[videoModuleController.videoPlayIndex.value]);
|
||||
if (result != null) {
|
||||
// 处理返回的参数
|
||||
print('返回的数据: ${result['followStatus']}');
|
||||
player.play();
|
||||
videoList[index]['doIFollowVloger'] = result['followStatus'];
|
||||
}
|
||||
},
|
||||
child: UnconstrainedBox(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Container(
|
||||
height: 48.0,
|
||||
width: 48.0,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.white, width: 2.0),
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
),
|
||||
child: ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: videoList[index]['commentUserFace'],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
visible: videoModuleController.videoPlayIndex.value == index && position > Duration.zero,
|
||||
child: Video(
|
||||
controller: videoController,
|
||||
fit: isHorizontal ? BoxFit.contain : BoxFit.cover,
|
||||
controls: NoVideoControls,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 15.0,
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
height: 18.0,
|
||||
width: 18.0,
|
||||
decoration: BoxDecoration(
|
||||
color: videoList[index]['doIFollowVloger'] ? Colors.white : Color(0xFFFF5000),
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
),
|
||||
child: Icon(
|
||||
videoList[index]['doIFollowVloger'] ? Icons.check : Icons.add,
|
||||
color: videoList[index]['doIFollowVloger'] ? Color(0xFFFF5000) : Colors.white,
|
||||
size: 14.0,
|
||||
),
|
||||
),
|
||||
onTap: () async {
|
||||
final vlogerId = videoList[index]['memberId'];
|
||||
final doIFollowVloger = videoList[index]['doIFollowVloger'];
|
||||
// 未关注点击才去关注
|
||||
if (doIFollowVloger == false) {
|
||||
final res = await ImService.instance.followUser(userIDList: [vlogerId]);
|
||||
if (res.success) {
|
||||
setState(() {
|
||||
videoList[index]['doIFollowVloger'] = !videoList[index]['doIFollowVloger'];
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
AnimatedOpacity(
|
||||
opacity: videoModuleController.videoPlayIndex.value == index && position > Duration(milliseconds: 100) ? 0.0 : 1.0,
|
||||
duration: Duration(milliseconds: 50),
|
||||
child: Image.network(
|
||||
videoList[index]['firstFrameImg'] ?? 'https://wuzhongjie.com.cn/download/logo.png',
|
||||
fit: isHorizontal ? BoxFit.contain : BoxFit.cover,
|
||||
width: double.infinity,
|
||||
height: double.infinity,
|
||||
),
|
||||
),
|
||||
StreamBuilder(
|
||||
stream: player.stream.playing,
|
||||
builder: (context, playing) {
|
||||
return Visibility(
|
||||
visible: playing.data == false,
|
||||
child: Center(
|
||||
child: IconButton(
|
||||
padding: EdgeInsets.zero,
|
||||
onPressed: () {
|
||||
player.playOrPause();
|
||||
},
|
||||
icon: Icon(
|
||||
playing.data == true ? Icons.pause : Icons.play_arrow_rounded,
|
||||
color: Colors.white60,
|
||||
size: 80,
|
||||
),
|
||||
style: ButtonStyle(backgroundColor: WidgetStateProperty.all(Colors.black.withAlpha(15))),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/heart.svg',
|
||||
colorFilter: ColorFilter.mode(videoList[index]['doILikeThisVlog'] ? Color(0xFFFF5000) : Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
Text(
|
||||
'${videoList[index]['likeCounts'] + (videoList[index]['doILikeThisVlog'] ? 1 : 0)}',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
logger.d('点击了点赞按钮${videoList[index]['doILikeThisVlog']}');
|
||||
if (videoList[index]['doILikeThisVlog'] == true) {
|
||||
logger.d('点击了点赞按钮${videoList[index]['doILikeThisVlog']}');
|
||||
doUnLikeVideo(videoList[index]);
|
||||
} else {
|
||||
doLikeVideo(videoList[index]);
|
||||
}
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/reply.svg',
|
||||
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
Text(
|
||||
'${videoList[index]['commentsCounts']}',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
handleComment(index);
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/share.svg',
|
||||
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
handleShare(index);
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/report.svg',
|
||||
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: ()async {
|
||||
player.pause();
|
||||
// 跳转到举报页面并等待返回结果
|
||||
final result = await Get.toNamed(
|
||||
'/report',
|
||||
arguments: videoList[videoModuleController
|
||||
.videoPlayIndex.value]);
|
||||
if (result != null) {
|
||||
player.play();
|
||||
};
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 15.0,
|
||||
left: 10.0,
|
||||
right: 80.0,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'@${videoList[videoModuleController.videoPlayIndex.value]['commentUserNickname'] ?? '未知'}',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 16.0),
|
||||
),
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final text = videoList[videoModuleController.videoPlayIndex.value]['title'] ?? '未知';
|
||||
final span = TextSpan(
|
||||
text: text,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14.0),
|
||||
);
|
||||
final tp = TextPainter(
|
||||
text: span,
|
||||
maxLines: 3,
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
tp.layout(maxWidth: constraints.maxWidth);
|
||||
final isOverflow = tp.didExceedMaxLines;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
text,
|
||||
maxLines: videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? null : 3,
|
||||
overflow:
|
||||
videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? TextOverflow.visible : TextOverflow.ellipsis,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14.0),
|
||||
),
|
||||
if (isOverflow)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 6.0),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
videoList[videoModuleController.videoPlayIndex.value]['expanded'] =
|
||||
!videoList[videoModuleController.videoPlayIndex.value]['expanded'];
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? '收起' : '展开更多',
|
||||
textAlign: TextAlign.right,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
)),
|
||||
Positioned(
|
||||
bottom: 0.0,
|
||||
left: 6.0,
|
||||
right: 6.0,
|
||||
child: Visibility(
|
||||
visible: videoModuleController.videoPlayIndex.value == index && position > Duration.zero,
|
||||
child: Listener(
|
||||
child: SliderTheme(
|
||||
data: SliderThemeData(
|
||||
trackHeight: sliderDraging ? 6.0 : 2.0,
|
||||
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 4.0),
|
||||
overlayShape: RoundSliderOverlayShape(overlayRadius: 0),
|
||||
inactiveTrackColor: Colors.white24,
|
||||
activeTrackColor: Colors.white,
|
||||
thumbColor: Colors.white,
|
||||
overlayColor: Colors.transparent,
|
||||
),
|
||||
child: Slider(
|
||||
value: sliderValue,
|
||||
onChanged: (value) async {
|
||||
setState(() {
|
||||
sliderValue = value;
|
||||
});
|
||||
await player.seek(duration * value.clamp(0.0, 1.0));
|
||||
},
|
||||
onChangeEnd: (value) async {
|
||||
setState(() {
|
||||
sliderDraging = false;
|
||||
});
|
||||
if (!player.state.playing) {
|
||||
await player.play();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
onPointerMove: (e) {
|
||||
setState(() {
|
||||
sliderDraging = true;
|
||||
});
|
||||
onTap: () {
|
||||
player.playOrPause();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 100.0,
|
||||
left: 10.0,
|
||||
right: 10.0,
|
||||
child: Visibility(
|
||||
visible: sliderDraging,
|
||||
child: DefaultTextStyle(
|
||||
style: TextStyle(color: Colors.white54, fontSize: 18.0, fontFamily: 'Arial'),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 8.0,
|
||||
// 右侧操作栏
|
||||
Positioned(
|
||||
bottom: 100.0,
|
||||
right: 6.0,
|
||||
child: Column(
|
||||
spacing: 15.0,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
Text(position.label(reference: duration), style: TextStyle(color: Colors.white)),
|
||||
Text('/', style: TextStyle(fontSize: 14.0)),
|
||||
Text(duration.label(reference: duration)),
|
||||
SizedBox(
|
||||
height: 55.0,
|
||||
width: 48.0,
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
player.pause();
|
||||
// 跳转到 Vloger 页面并等待返回结果
|
||||
final result = await Get.toNamed('/vloger', arguments: videoList[videoModuleController.videoPlayIndex.value]);
|
||||
if (result != null) {
|
||||
// 处理返回的参数
|
||||
print('返回的数据: ${result['followStatus']}');
|
||||
player.play();
|
||||
videoList[index]['doIFollowVloger'] = result['followStatus'];
|
||||
}
|
||||
},
|
||||
child: UnconstrainedBox(
|
||||
alignment: Alignment.topCenter,
|
||||
child: Container(
|
||||
height: 48.0,
|
||||
width: 48.0,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.white, width: 2.0),
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
),
|
||||
child: ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: videoList[index]['commentUserFace'],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 15.0,
|
||||
child: InkWell(
|
||||
child: Container(
|
||||
height: 18.0,
|
||||
width: 18.0,
|
||||
decoration: BoxDecoration(
|
||||
color: videoList[index]['doIFollowVloger'] ? Colors.white : Color(0xFFFF5000),
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
),
|
||||
child: Icon(
|
||||
videoList[index]['doIFollowVloger'] ? Icons.check : Icons.add,
|
||||
color: videoList[index]['doIFollowVloger'] ? Color(0xFFFF5000) : Colors.white,
|
||||
size: 14.0,
|
||||
),
|
||||
),
|
||||
onTap: () async {
|
||||
final vlogerId = videoList[index]['memberId'];
|
||||
final doIFollowVloger = videoList[index]['doIFollowVloger'];
|
||||
// 未关注点击才去关注
|
||||
if (doIFollowVloger == false) {
|
||||
final res = await ImService.instance.followUser(userIDList: [vlogerId]);
|
||||
if (res.success) {
|
||||
setState(() {
|
||||
videoList[index]['doIFollowVloger'] = !videoList[index]['doIFollowVloger'];
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/heart.svg',
|
||||
colorFilter: ColorFilter.mode(videoList[index]['doILikeThisVlog'] ? Color(0xFFFF5000) : Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
Text(
|
||||
'${videoList[index]['likeCounts'] + (videoList[index]['doILikeThisVlog'] ? 1 : 0)}',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
logger.d('点击了点赞按钮${videoList[index]['doILikeThisVlog']}');
|
||||
if (videoList[index]['doILikeThisVlog'] == true) {
|
||||
logger.d('点击了点赞按钮${videoList[index]['doILikeThisVlog']}');
|
||||
doUnLikeVideo(videoList[index]);
|
||||
} else {
|
||||
doLikeVideo(videoList[index]);
|
||||
}
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/reply.svg',
|
||||
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
Text(
|
||||
'${videoList[index]['commentsCounts']}',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12.0),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
handleComment(index);
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/share.svg',
|
||||
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
handleShare(index);
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
child: Column(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'assets/images/svg/report.svg',
|
||||
colorFilter: ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
height: 40.0,
|
||||
width: 40.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () async {
|
||||
player.pause();
|
||||
// 跳转到举报页面并等待返回结果
|
||||
final result = await Get.toNamed('/report', arguments: videoList[videoModuleController.videoPlayIndex.value]);
|
||||
if (result != null) {
|
||||
player.play();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 15.0,
|
||||
left: 10.0,
|
||||
right: 80.0,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'@${videoList[videoModuleController.videoPlayIndex.value]['commentUserNickname'] ?? '未知'}',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 16.0),
|
||||
),
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final text = videoList[videoModuleController.videoPlayIndex.value]['title'] ?? '未知';
|
||||
final span = TextSpan(
|
||||
text: text,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14.0),
|
||||
);
|
||||
final tp = TextPainter(
|
||||
text: span,
|
||||
maxLines: 3,
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
tp.layout(maxWidth: constraints.maxWidth);
|
||||
final isOverflow = tp.didExceedMaxLines;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
text,
|
||||
maxLines: videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? null : 3,
|
||||
overflow: videoList[videoModuleController.videoPlayIndex.value]['expanded']
|
||||
? TextOverflow.visible
|
||||
: TextOverflow.ellipsis,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14.0),
|
||||
),
|
||||
if (isOverflow)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 6.0),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
videoList[videoModuleController.videoPlayIndex.value]['expanded'] =
|
||||
!videoList[videoModuleController.videoPlayIndex.value]['expanded'];
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
videoList[videoModuleController.videoPlayIndex.value]['expanded'] ? '收起' : '展开更多',
|
||||
textAlign: TextAlign.right,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
Positioned(
|
||||
bottom: 0.0,
|
||||
left: 6.0,
|
||||
right: 6.0,
|
||||
child: Visibility(
|
||||
visible: videoModuleController.videoPlayIndex.value == index && position > Duration.zero,
|
||||
child: Listener(
|
||||
child: SliderTheme(
|
||||
data: SliderThemeData(
|
||||
trackHeight: sliderDraging ? 6.0 : 2.0,
|
||||
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 4.0),
|
||||
overlayShape: RoundSliderOverlayShape(overlayRadius: 0),
|
||||
inactiveTrackColor: Colors.white24,
|
||||
activeTrackColor: Colors.white,
|
||||
thumbColor: Colors.white,
|
||||
overlayColor: Colors.transparent,
|
||||
),
|
||||
child: Slider(
|
||||
value: sliderValue,
|
||||
onChanged: (value) async {
|
||||
setState(() {
|
||||
sliderValue = value;
|
||||
});
|
||||
await player.seek(duration * value.clamp(0.0, 1.0));
|
||||
},
|
||||
onChangeEnd: (value) async {
|
||||
setState(() {
|
||||
sliderDraging = false;
|
||||
});
|
||||
if (!player.state.playing) {
|
||||
await player.play();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
onPointerMove: (e) {
|
||||
setState(() {
|
||||
sliderDraging = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 100.0,
|
||||
left: 10.0,
|
||||
right: 10.0,
|
||||
child: Visibility(
|
||||
visible: sliderDraging,
|
||||
child: DefaultTextStyle(
|
||||
style: TextStyle(color: Colors.white54, fontSize: 18.0, fontFamily: 'Arial'),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Text(position.label(reference: duration), style: TextStyle(color: Colors.white)),
|
||||
Text('/', style: TextStyle(fontSize: 14.0)),
|
||||
Text(duration.label(reference: duration)),
|
||||
],
|
||||
),
|
||||
)),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -51,10 +51,24 @@ String parseMessageSummary(V2TimMessage msg) {
|
||||
// groupNotifyLeaveUp, // 群通知->群升级为达人群通知
|
||||
String _parseCustomMessage(V2TimMessage? msg) {
|
||||
if (msg == null) return '[null]';
|
||||
final sum = msg.cloudCustomData;
|
||||
String sum;
|
||||
// 客户端本地用的是字符串,服务端返回的是JSON(服务端改cloudCustomData的类型为String,或者客户端代码修改发送消息时携带的参数由string变为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:
|
||||
|
46
pubspec.lock
46
pubspec.lock
@ -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"
|
||||
|
@ -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 # 使用最新版本
|
||||
|
Loading…
x
Reference in New Issue
Block a user