Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
e90837a9b7
@ -1,6 +1,8 @@
|
||||
/// 单图/多图预览查看器
|
||||
library;
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
@ -31,6 +33,19 @@ class _ImageViewerState extends State<ImageViewer> {
|
||||
currentIndex = widget.index;
|
||||
}
|
||||
|
||||
ImageProvider getImageProvider(String path) {
|
||||
if (Utils.isUrl(path)) {
|
||||
// 网络图片
|
||||
return NetworkImage(path);
|
||||
} else if (File(path).existsSync()) {
|
||||
// 本地文件路径
|
||||
return FileImage(File(path));
|
||||
} else {
|
||||
// assets 资源
|
||||
return AssetImage(path);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var imgCount = widget.images?.length;
|
||||
@ -44,8 +59,10 @@ class _ImageViewerState extends State<ImageViewer> {
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
child: GestureDetector(
|
||||
child: imgCount == 1 ? PhotoView(
|
||||
imageProvider: Utils.isUrl(widget.images![0]) ? NetworkImage(widget.images![0]) : AssetImage(widget.images![0]),
|
||||
child: imgCount == 1
|
||||
? PhotoView(
|
||||
// imageProvider: Utils.isUrl(widget.images![0]) ? NetworkImage(widget.images![0]) : AssetImage(widget.images![0]),
|
||||
imageProvider: getImageProvider(widget.images![0]),
|
||||
backgroundDecoration: const BoxDecoration(
|
||||
color: Colors.black,
|
||||
),
|
||||
@ -54,8 +71,7 @@ class _ImageViewerState extends State<ImageViewer> {
|
||||
heroAttributes: PhotoViewHeroAttributes(tag: widget.images![0]),
|
||||
enableRotation: true,
|
||||
)
|
||||
:
|
||||
PhotoViewGallery.builder(
|
||||
: PhotoViewGallery.builder(
|
||||
itemCount: widget.images?.length,
|
||||
builder: (context, index) {
|
||||
return PhotoViewGalleryPageOptions(
|
||||
@ -89,9 +105,11 @@ class _ImageViewerState extends State<ImageViewer> {
|
||||
child: Center(
|
||||
child: Visibility(
|
||||
visible: imgCount! > 1 ? true : false,
|
||||
child: Text('${currentIndex+1} / ${widget.images?.length}', style: const TextStyle(color: Colors.white, fontSize: 16, fontFamily: 'arial'),),
|
||||
)
|
||||
child: Text(
|
||||
'${currentIndex + 1} / ${widget.images?.length}',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 16, fontFamily: 'arial'),
|
||||
),
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -52,7 +52,7 @@ conversationTypeFromString(String? type) {
|
||||
return ConversationType.order.name;
|
||||
} else if (type.contains('groupNotify')) {
|
||||
return ConversationType.groupNotify.name;
|
||||
}
|
||||
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -527,6 +527,7 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
else if (item.elemType == 2 && item.cloudCustomData == SummaryType.shareTuangou) {
|
||||
//price,title,url,sell
|
||||
final obj = jsonDecode(item.customElem!.data!);
|
||||
logger.e(obj);
|
||||
final url = obj['url'];
|
||||
final title = obj['title'];
|
||||
final price = obj['price'];
|
||||
@ -607,6 +608,7 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
|
||||
else if (item.elemType == 2 && item.cloudCustomData == SummaryType.shareVideo) {
|
||||
/// {imgUrl,videoUrl,width,height}
|
||||
final obj = jsonDecode(item.customElem!.data!);
|
||||
logger.e(obj);
|
||||
final videoUrl = obj['videoUrl'];
|
||||
final imgUrl = obj['imgUrl'];
|
||||
final width = obj['width'] as num;
|
||||
|
@ -13,6 +13,7 @@ import 'package:loopin/IM/im_service.dart';
|
||||
import 'package:loopin/api/shop_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';
|
||||
@ -48,7 +49,7 @@ class _GoodsState extends State<Goods> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final goodsId = Get.arguments['goodsId'];
|
||||
final goodsId = Get.arguments['goodsId'] ?? '';
|
||||
scrollController.addListener(() {
|
||||
setState(() {
|
||||
scrollOffset = scrollController.offset;
|
||||
@ -83,17 +84,14 @@ class _GoodsState extends State<Goods> {
|
||||
"type": 1, // 订单类型:1->团购;2->拼团;3->秒杀;
|
||||
"distribution": 1, // 配送方式 1->到店核销;2->自提;3->配送;
|
||||
"skuItemBOList": [
|
||||
{
|
||||
"skuId": goodsId,
|
||||
"quantity": 1
|
||||
}
|
||||
{"skuId": goodsId, "quantity": 1}
|
||||
]
|
||||
};
|
||||
print('下单请求参数---->${params}');
|
||||
print('下单请求参数---->$params');
|
||||
try {
|
||||
final res = await Http.post('${ShopApi.createGoodsOrder}', data: params);
|
||||
final res = await Http.post(ShopApi.createGoodsOrder, data: params);
|
||||
var resData = res['data'];
|
||||
print('1111111111111111111111111---->${res}');
|
||||
print('1111111111111111111111111---->$res');
|
||||
if (resData['id'].isNotEmpty) {
|
||||
return resData['id'];
|
||||
} else {
|
||||
@ -104,6 +102,7 @@ class _GoodsState extends State<Goods> {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void handleShareClick(int index) {
|
||||
final description = shopObj['describe']; // 商品描述
|
||||
if (index == 1) {
|
||||
@ -116,12 +115,13 @@ class _GoodsState extends State<Goods> {
|
||||
}
|
||||
|
||||
void handlCoverClick(V2TimConversation conv) async {
|
||||
// 发送VideoMsg,获取当前视频信息
|
||||
// 发送自定义消息 商品信息
|
||||
final userId = conv.userID;
|
||||
//price,title,url,sell
|
||||
logger.w(shopObj['name']);
|
||||
final makeJson = jsonEncode({
|
||||
"price": shopObj['price'],
|
||||
"title": shopObj['describe'],
|
||||
"title": shopObj['name'],
|
||||
"url": shopObj['pic'],
|
||||
"sell": Utils.graceNumber(int.parse(shopObj['sales'] ?? '0')),
|
||||
});
|
||||
@ -200,7 +200,8 @@ class _GoodsState extends State<Goods> {
|
||||
// 会话列表
|
||||
Obx(() {
|
||||
// 这里过滤掉有分组的会话
|
||||
final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList();
|
||||
final filteredList = chatController.chatList.where((item) => conversationTypeFromString(item.isCustomAdmin) == null).toList();
|
||||
|
||||
if (filteredList.isEmpty) return SizedBox.shrink();
|
||||
return SizedBox(
|
||||
height: 110,
|
||||
@ -219,11 +220,13 @@ class _GoodsState extends State<Goods> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Image.asset('${chatController.chatList[index].faceUrl}', width: 48.0),
|
||||
NetworkOrAssetImage(
|
||||
ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: filteredList[index].faceUrl,
|
||||
width: 48.0,
|
||||
height: 48.0,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
Text(
|
||||
'${filteredList[index].conversation.showName}',
|
||||
@ -338,7 +341,12 @@ class _GoodsState extends State<Goods> {
|
||||
activeColor: Colors.white,
|
||||
)),
|
||||
indicatorLayout: PageIndicatorLayout.SCALE,
|
||||
children: swiperList.map((sw) => NetworkOrAssetImage(imageUrl: sw)).toList(),
|
||||
children: swiperList
|
||||
.map((sw) => NetworkOrAssetImage(
|
||||
imageUrl: sw,
|
||||
placeholderAsset: 'assets/images/bk.jpg',
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -577,35 +585,46 @@ class _GoodsState extends State<Goods> {
|
||||
// )
|
||||
// ],
|
||||
// ),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
// 可以在这里打开聊天
|
||||
logger.i('联系客服');
|
||||
final res = await ImService.instance.getConversation(conversationID: 'c2c_${shopObj['tenantId']}');
|
||||
V2TimConversation conversation = res.data;
|
||||
logger.i(conversation.toLogString());
|
||||
if (res.success) {
|
||||
// 客服聊天不用检测关注关系
|
||||
conversation.showName = conversation.showName ?? shopObj['storeName'];
|
||||
Get.toNamed('/chat', arguments: conversation);
|
||||
} else {
|
||||
MyDialog.toast(res.desc, icon: const Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.child_care_outlined,
|
||||
size: 18.0,
|
||||
),
|
||||
Text(
|
||||
'联系商家',
|
||||
style: TextStyle(fontSize: 12.0),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
// 联系商家
|
||||
// GestureDetector(
|
||||
// onTap: () async {
|
||||
// // 可以在这里打开聊天
|
||||
// logger.i('联系客服:$shopObj');
|
||||
// final res = await ImService.instance.getConversation(conversationID: 'c2c_${shopObj['tenantId']}');
|
||||
// V2TimConversation conversation = res.data;
|
||||
// logger.i(conversation.toLogString());
|
||||
// if (res.success) {
|
||||
// // 客服聊天不用检测关注关系
|
||||
// V2TimUserFullInfo? sellerInfo;
|
||||
// final resIm = await ImService.instance.otherInfo(shopObj['tenantId']);
|
||||
// if (resIm.success && resIm.data != null) {
|
||||
// sellerInfo = resIm.data!;
|
||||
// logger.i(sellerInfo!.toLogString());
|
||||
// } else {
|
||||
// logger.e(resIm.desc);
|
||||
// }
|
||||
// conversation.showName = conversation.showName ?? sellerInfo!.nickName;
|
||||
// Get.toNamed('/chat', arguments: conversation);
|
||||
// } else {
|
||||
// MyDialog.toast(res.desc, icon: const Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
|
||||
// }
|
||||
// },
|
||||
// child: Column(
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// children: [
|
||||
// Icon(
|
||||
// Icons.child_care_outlined,
|
||||
// size: 18.0,
|
||||
// ),
|
||||
// Text(
|
||||
// '联系商家',
|
||||
// style: TextStyle(fontSize: 12.0),
|
||||
// )
|
||||
// ],
|
||||
// ),
|
||||
// )
|
||||
|
||||
// Column(
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// children: [
|
||||
|
@ -182,7 +182,7 @@ class FansState extends State<Fans> with SingleTickerProviderStateMixin {
|
||||
onTap: () {
|
||||
Get.toNamed(
|
||||
'/vloger',
|
||||
arguments: item.userInfo.userID,
|
||||
arguments: {'memberId': item.userInfo.userID},
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
|
@ -182,7 +182,7 @@ class FlowingState extends State<Flowing> with SingleTickerProviderStateMixin {
|
||||
onTap: () {
|
||||
Get.toNamed(
|
||||
'/vloger',
|
||||
arguments: item.userInfo.userID,
|
||||
arguments: {'memberId': item.userInfo.userID},
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
|
@ -182,7 +182,7 @@ class MutualFollowersState extends State<MutualFollowers> with SingleTickerProvi
|
||||
onTap: () {
|
||||
Get.toNamed(
|
||||
'/vloger',
|
||||
arguments: item.userInfo.userID,
|
||||
arguments: {'memberId': item.userInfo.userID},
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
|
@ -1,12 +1,15 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loopin/IM/im_service.dart';
|
||||
import 'package:loopin/api/common_api.dart';
|
||||
import 'package:loopin/api/video_api.dart';
|
||||
import 'package:loopin/components/image_viewer.dart';
|
||||
import 'package:loopin/components/preview_video.dart';
|
||||
import 'package:loopin/service/http.dart';
|
||||
import 'package:loopin/utils/index.dart';
|
||||
import 'package:loopin/utils/snapshot.dart';
|
||||
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
|
||||
|
||||
class UploadVideoPage extends StatefulWidget {
|
||||
@ -19,15 +22,28 @@ class UploadVideoPage extends StatefulWidget {
|
||||
class _UploadVideoPageState extends State<UploadVideoPage> {
|
||||
final selectedVideo = Rxn<AssetEntity>();
|
||||
final selectedCover = Rxn<AssetEntity>();
|
||||
//视频
|
||||
final uploading = false.obs;
|
||||
final uploadProgress = 0.0.obs;
|
||||
final status = ''.obs;
|
||||
final descController = TextEditingController();
|
||||
//图片
|
||||
final uploading2 = false.obs;
|
||||
final uploadProgress2 = 0.0.obs;
|
||||
final status2 = ''.obs;
|
||||
//地址
|
||||
final uploadedVideoUrl = ''.obs; // 网络视频地址
|
||||
final uploadedImgUrl = ''.obs; // 网络图片地址
|
||||
final videoPath = ''.obs; // 本地视频地址
|
||||
final imgPath = ''.obs; //本地图片地址
|
||||
final snapshot = ''.obs;
|
||||
|
||||
final FocusNode descFocusNode = FocusNode();
|
||||
final uploadedVideoUrl = ''.obs;
|
||||
|
||||
final TextEditingController descriptionController = TextEditingController();
|
||||
|
||||
Future<void> pickVideo() async {
|
||||
FocusScope.of(context).unfocus();
|
||||
|
||||
final result = await PhotoManager.requestPermissionExtend();
|
||||
|
||||
if (!result.isAuth) {
|
||||
@ -60,10 +76,14 @@ class _UploadVideoPageState extends State<UploadVideoPage> {
|
||||
if (pickedAssets != null && pickedAssets.isNotEmpty) {
|
||||
selectedVideo.value = pickedAssets.first;
|
||||
status.value = '已选择视频';
|
||||
uploadVideo();
|
||||
}
|
||||
}
|
||||
|
||||
// 选择封面图
|
||||
Future<void> pickCoverImage() async {
|
||||
FocusScope.of(context).unfocus();
|
||||
|
||||
final result = await PhotoManager.requestPermissionExtend();
|
||||
|
||||
if (!result.isAuth) {
|
||||
@ -88,30 +108,73 @@ class _UploadVideoPageState extends State<UploadVideoPage> {
|
||||
|
||||
if (pickedAssets != null && pickedAssets.isNotEmpty) {
|
||||
selectedCover.value = pickedAssets.first;
|
||||
// 执行上传图片逻辑
|
||||
uploadImg();
|
||||
}
|
||||
}
|
||||
|
||||
// 上传图片
|
||||
Future<void> uploadImg() async {
|
||||
final coverFile = await selectedCover.value?.file;
|
||||
|
||||
if (coverFile == null) {
|
||||
status.value = '未选择封面图';
|
||||
return;
|
||||
}
|
||||
|
||||
imgPath.value = coverFile.path;
|
||||
uploading2.value = true;
|
||||
uploadProgress2.value = 0;
|
||||
status2.value = '上传中...';
|
||||
|
||||
try {
|
||||
final res = await Http.upload(
|
||||
CommonApi.uploadFile,
|
||||
filePath: coverFile.path,
|
||||
fileKey: 'file',
|
||||
onSendProgress: (sent, total) {
|
||||
if (total > 0) {
|
||||
uploadProgress2.value = sent / total;
|
||||
if (sent == total) uploading2.value = false;
|
||||
}
|
||||
},
|
||||
);
|
||||
uploadedImgUrl.value = res['data']['url'];
|
||||
status2.value = '上传成功';
|
||||
} catch (e) {
|
||||
if (e is SocketException) {
|
||||
status2.value = '网络错误,请检查连接';
|
||||
} else {
|
||||
status2.value = '上传失败: ${e.toString()}';
|
||||
}
|
||||
descFocusNode.unfocus();
|
||||
uploading2.value = false;
|
||||
} finally {
|
||||
descFocusNode.unfocus();
|
||||
uploading2.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 上传视频
|
||||
Future<void> uploadVideo() async {
|
||||
final file = await selectedVideo.value?.file;
|
||||
final coverFile = await selectedCover.value?.file;
|
||||
final desc = descController.text.trim();
|
||||
|
||||
if (file == null) {
|
||||
status.value = '未选择视频';
|
||||
return;
|
||||
}
|
||||
|
||||
if (desc.isEmpty) {
|
||||
Get.snackbar('请填写描述', '视频描述不能为空');
|
||||
return;
|
||||
}
|
||||
|
||||
uploading.value = true;
|
||||
uploadProgress.value = 0;
|
||||
status.value = '上传中...';
|
||||
videoPath.value = file.path;
|
||||
logger.w(videoPath.value);
|
||||
|
||||
snapshot.value = (await generateVideoThumbnail(file.path))!;
|
||||
logger.w(snapshot.value);
|
||||
|
||||
try {
|
||||
final data = await Http.upload(
|
||||
final res = await Http.upload(
|
||||
CommonApi.uploadFile,
|
||||
filePath: file.path,
|
||||
fileKey: 'file',
|
||||
@ -121,60 +184,98 @@ class _UploadVideoPageState extends State<UploadVideoPage> {
|
||||
}
|
||||
},
|
||||
);
|
||||
uploadedVideoUrl.value = data;
|
||||
uploadedVideoUrl.value = res['data']['url'];
|
||||
status.value = '上传成功';
|
||||
// 清空表单
|
||||
descController.clear();
|
||||
selectedVideo.value = null;
|
||||
selectedCover.value = null;
|
||||
uploadedVideoUrl.value = '';
|
||||
} catch (e) {
|
||||
descFocusNode.unfocus();
|
||||
if (e is SocketException) {
|
||||
status.value = '网络错误,请检查连接';
|
||||
} else {
|
||||
status.value = '上传失败: ${e.toString()}';
|
||||
}
|
||||
} finally {
|
||||
descFocusNode.unfocus();
|
||||
uploading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 发布
|
||||
Future<void> submitForm() async {
|
||||
logger.w(descriptionController.text);
|
||||
|
||||
FocusScope.of(context).unfocus();
|
||||
if (uploadedVideoUrl.value.isEmpty) {
|
||||
Get.snackbar('请先上传视频', '未检测到上传的视频');
|
||||
return;
|
||||
}
|
||||
|
||||
if (descriptionController.text.trim().isEmpty) {
|
||||
Get.snackbar('请填写视频描述', '描述是必填项');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await Http.post(VideoApi.publish, data: {
|
||||
'video_url': uploadedVideoUrl.value,
|
||||
'desc': descriptionController.text.trim(),
|
||||
'cover': selectedCover.value,
|
||||
});
|
||||
Get.snackbar('发布成功', '视频已成功发布');
|
||||
final data = {
|
||||
'url': uploadedVideoUrl.value,
|
||||
'title': descriptionController.text.trim(),
|
||||
};
|
||||
if (uploadedImgUrl.value.isNotEmpty) {
|
||||
data['cover'] = uploadedImgUrl.value;
|
||||
}
|
||||
logger.w('发布内容:$data');
|
||||
await Http.post(VideoApi.publish, data: data);
|
||||
Get.snackbar('发布成功', '视频正在审核中');
|
||||
clearForm();
|
||||
} catch (e) {
|
||||
Get.snackbar('发布失败', '$e');
|
||||
}
|
||||
} finally {}
|
||||
}
|
||||
|
||||
void clearForm() {
|
||||
selectedVideo.value = null;
|
||||
selectedCover.value = null;
|
||||
descriptionController.clear();
|
||||
status.value = '';
|
||||
uploadProgress.value = 0.0;
|
||||
uploadedImgUrl.value = '';
|
||||
uploadedVideoUrl.value = '';
|
||||
descriptionController.clear();
|
||||
descFocusNode.unfocus();
|
||||
status.value = '';
|
||||
status2.value = '';
|
||||
uploadProgress.value = 0.0;
|
||||
uploadProgress2.value = 0.0;
|
||||
videoPath.value = '';
|
||||
}
|
||||
|
||||
// 预览视频
|
||||
void openVd() {
|
||||
FocusScope.of(context).unfocus();
|
||||
showGeneralDialog(
|
||||
context: context,
|
||||
// barrierDismissible: true,
|
||||
barrierColor: Colors.black.withAlpha((1.0 * 255).round()),
|
||||
pageBuilder: (_, __, ___) {
|
||||
return SafeArea(
|
||||
child: PreviewVideo(
|
||||
videoUrl: videoPath.value,
|
||||
),
|
||||
);
|
||||
},
|
||||
transitionBuilder: (_, anim, __, child) {
|
||||
return FadeTransition(opacity: anim, child: child);
|
||||
},
|
||||
transitionDuration: const Duration(milliseconds: 200),
|
||||
);
|
||||
}
|
||||
|
||||
void openImg() {
|
||||
// 图片预览
|
||||
Get.to(() => ImageViewer(
|
||||
images: [imgPath.value],
|
||||
index: 0,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
descController.dispose();
|
||||
descriptionController.dispose();
|
||||
descFocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
@ -193,7 +294,8 @@ class _UploadVideoPageState extends State<UploadVideoPage> {
|
||||
children: [
|
||||
TextField(
|
||||
focusNode: descFocusNode,
|
||||
controller: descController,
|
||||
autofocus: false,
|
||||
controller: descriptionController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: '视频描述 *',
|
||||
border: OutlineInputBorder(),
|
||||
@ -201,54 +303,40 @@ class _UploadVideoPageState extends State<UploadVideoPage> {
|
||||
maxLines: 2,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
// 视频
|
||||
Obx(() {
|
||||
final video = selectedVideo.value;
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
video != null ? '已选择视频:${video.title}' : '未选择视频',
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: pickVideo,
|
||||
child: const Text('选择视频'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: 20),
|
||||
Obx(() {
|
||||
final cover = selectedCover.value;
|
||||
return Row(
|
||||
children: [
|
||||
cover != null
|
||||
? FutureBuilder<Uint8List?>(
|
||||
future: cover.thumbnailDataWithSize(const ThumbnailSize(80, 80)),
|
||||
builder: (_, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) {
|
||||
return ClipRRect(
|
||||
// 左侧封面预览
|
||||
snapshot.value.isNotEmpty
|
||||
? GestureDetector(
|
||||
onTap: () => openVd(),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Image.memory(snapshot.data!, width: 80, height: 80),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox(width: 80, height: 80);
|
||||
}
|
||||
},
|
||||
)
|
||||
: const Text('未选择封面图'),
|
||||
const SizedBox(width: 12),
|
||||
ElevatedButton(
|
||||
onPressed: pickCoverImage,
|
||||
child: const Text('选择封面(可选)'),
|
||||
child: Image.file(
|
||||
File(snapshot.value),
|
||||
width: 80,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: 30),
|
||||
Obx(() => uploading.value
|
||||
),
|
||||
)
|
||||
: const SizedBox(
|
||||
height: 80,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'未选择视频',
|
||||
style: TextStyle(fontSize: 16),
|
||||
)),
|
||||
),
|
||||
|
||||
const SizedBox(width: 24),
|
||||
|
||||
// 右侧按钮/进度条
|
||||
Expanded(
|
||||
child: uploading.value
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: 10),
|
||||
@ -257,16 +345,83 @@ class _UploadVideoPageState extends State<UploadVideoPage> {
|
||||
Text('${(uploadProgress.value * 100).toStringAsFixed(1)}%'),
|
||||
],
|
||||
)
|
||||
: SizedBox(
|
||||
: ElevatedButton.icon(
|
||||
onPressed: pickVideo,
|
||||
icon: const Icon(Icons.upload),
|
||||
label: Text(uploadedVideoUrl.value.isNotEmpty ? '重新选择' : '选择视频'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size.fromHeight(50),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: 20),
|
||||
// 图片上传
|
||||
Obx(() {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
// 左侧图片预览
|
||||
imgPath.value.isNotEmpty
|
||||
? GestureDetector(
|
||||
onTap: () => openImg(),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Image.file(
|
||||
File(imgPath.value),
|
||||
width: 80,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox(
|
||||
height: 80,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'未选择封面',
|
||||
style: TextStyle(fontSize: 16),
|
||||
)),
|
||||
),
|
||||
|
||||
const SizedBox(width: 24),
|
||||
|
||||
// 右侧按钮/进度条
|
||||
Expanded(
|
||||
child: uploading2.value
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: 10),
|
||||
LinearProgressIndicator(value: uploadProgress2.value),
|
||||
const SizedBox(height: 5),
|
||||
Text('${(uploadProgress2.value * 100).toStringAsFixed(1)}%'),
|
||||
],
|
||||
)
|
||||
: ElevatedButton.icon(
|
||||
onPressed: pickCoverImage,
|
||||
icon: const Icon(Icons.upload),
|
||||
label: Text(uploadedImgUrl.value.isNotEmpty ? '重新选择' : '选择封面(可选)'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size.fromHeight(50),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
const SizedBox(height: 30),
|
||||
// 发布按钮
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: uploadVideo,
|
||||
onPressed: submitForm,
|
||||
icon: const Icon(Icons.upload),
|
||||
label: const Text('上传'),
|
||||
label: const Text('发布'),
|
||||
),
|
||||
),
|
||||
)),
|
||||
const SizedBox(height: 20),
|
||||
Obx(() => Text(status.value)),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -21,6 +21,7 @@ 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';
|
||||
import 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
|
||||
|
||||
import '../../../router/fade_route.dart';
|
||||
import './components/popup_reply.dart';
|
||||
|
||||
@ -100,7 +101,7 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
|
||||
});
|
||||
|
||||
try {
|
||||
final res = await Http.get('${VideoApi.videoDetailApi}${videoId}');
|
||||
final res = await Http.get('${VideoApi.videoDetailApi}$videoId');
|
||||
|
||||
logger.d('视频详情接口返回: ${json.encode(res)}');
|
||||
|
||||
@ -256,7 +257,6 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
|
||||
Future<void> unfollowUser() async {
|
||||
try {
|
||||
final vlogerId = videoData['vlogerId'] ?? videoData['memberId'];
|
||||
|
||||
} catch (e) {
|
||||
logger.e('取消关注用户异常: $e');
|
||||
MyToast().tip(
|
||||
@ -309,6 +309,8 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) {
|
||||
final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList();
|
||||
|
||||
return Material(
|
||||
color: Colors.white,
|
||||
child: Padding(
|
||||
@ -348,15 +350,14 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
|
||||
),
|
||||
|
||||
// 会话列表
|
||||
if (chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).isNotEmpty)
|
||||
if (filteredList.isNotEmpty)
|
||||
SizedBox(
|
||||
height: 110,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).length,
|
||||
itemCount: filteredList.length,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 20.0),
|
||||
itemBuilder: (context, index) {
|
||||
final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList();
|
||||
return GestureDetector(
|
||||
onTap: () => handlCoverClick(filteredList[index].conversation),
|
||||
child: Container(
|
||||
@ -365,11 +366,13 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
NetworkOrAssetImage(
|
||||
ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: filteredList[index].faceUrl,
|
||||
width: 48.0,
|
||||
height: 48.0,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
filteredList[index].conversation.showName ?? '',
|
||||
@ -908,6 +911,7 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CommentBottomSheet extends StatefulWidget {
|
||||
final String videoId;
|
||||
final Function(int) onCommentCountChanged; // 新增回调函数
|
||||
|
@ -798,6 +798,8 @@ class _AttentionModuleState extends State<AttentionModule> {
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) {
|
||||
final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList();
|
||||
|
||||
return Material(
|
||||
color: Colors.white,
|
||||
child: Padding(
|
||||
@ -837,15 +839,14 @@ class _AttentionModuleState extends State<AttentionModule> {
|
||||
),
|
||||
|
||||
// 会话列表
|
||||
if (chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).isNotEmpty)
|
||||
if (filteredList.isNotEmpty)
|
||||
SizedBox(
|
||||
height: 110,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).length,
|
||||
itemCount: filteredList.length,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 20.0),
|
||||
itemBuilder: (context, index) {
|
||||
final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList();
|
||||
return GestureDetector(
|
||||
onTap: () => handlCoverClick(filteredList[index].conversation),
|
||||
child: Container(
|
||||
@ -1268,13 +1269,10 @@ class _AttentionModuleState extends State<AttentionModule> {
|
||||
onTap: () async {
|
||||
player.pause();
|
||||
// 跳转到举报页面并等待返回结果
|
||||
final result = await Get.toNamed(
|
||||
'/report',
|
||||
arguments: videoList[videoModuleController
|
||||
.videoPlayIndex.value]);
|
||||
final result = await Get.toNamed('/report', arguments: videoList[videoModuleController.videoPlayIndex.value]);
|
||||
if (result != null) {
|
||||
player.play();
|
||||
};
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
|
@ -15,6 +15,7 @@ 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/conversation_type.dart';
|
||||
import 'package:loopin/models/summary_type.dart';
|
||||
import 'package:loopin/service/http.dart';
|
||||
import 'package:loopin/utils/download_video.dart';
|
||||
@ -794,6 +795,7 @@ class _RecommendModuleState extends State<RecommendModule> {
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) {
|
||||
final filteredList = chatController.chatList.where((item) => conversationTypeFromString(item.isCustomAdmin) == null).toList();
|
||||
return Material(
|
||||
color: Colors.white,
|
||||
child: Padding(
|
||||
@ -831,17 +833,15 @@ class _RecommendModuleState extends State<RecommendModule> {
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// 会话列表
|
||||
if (chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).isNotEmpty)
|
||||
if (filteredList.isNotEmpty)
|
||||
SizedBox(
|
||||
height: 110,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).length,
|
||||
itemCount: filteredList.length,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 20.0),
|
||||
itemBuilder: (context, index) {
|
||||
final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList();
|
||||
return GestureDetector(
|
||||
onTap: () => handlCoverClick(filteredList[index].conversation),
|
||||
child: Container(
|
||||
@ -850,11 +850,13 @@ class _RecommendModuleState extends State<RecommendModule> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
NetworkOrAssetImage(
|
||||
ClipOval(
|
||||
child: NetworkOrAssetImage(
|
||||
imageUrl: filteredList[index].faceUrl,
|
||||
width: 48.0,
|
||||
height: 48.0,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5),
|
||||
Text(
|
||||
filteredList[index].conversation.showName ?? '',
|
||||
|
@ -11,5 +11,5 @@ Future<String?> generateVideoThumbnail(String videoPath) async {
|
||||
maxWidth: 120,
|
||||
quality: 75,
|
||||
);
|
||||
return thumbnailPath;
|
||||
return thumbnailPath ?? '';
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user