Merge remote-tracking branch 'origin/master'

This commit is contained in:
cuiyouliang 2025-09-05 10:56:15 +08:00
commit e90837a9b7
12 changed files with 410 additions and 212 deletions

View File

@ -1,6 +1,8 @@
/// / /// /
library; library;
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view.dart';
@ -31,6 +33,19 @@ class _ImageViewerState extends State<ImageViewer> {
currentIndex = widget.index; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var imgCount = widget.images?.length; var imgCount = widget.images?.length;
@ -44,8 +59,10 @@ class _ImageViewerState extends State<ImageViewer> {
bottom: 0, bottom: 0,
right: 0, right: 0,
child: GestureDetector( child: GestureDetector(
child: imgCount == 1 ? PhotoView( child: imgCount == 1
imageProvider: Utils.isUrl(widget.images![0]) ? NetworkImage(widget.images![0]) : AssetImage(widget.images![0]), ? PhotoView(
// imageProvider: Utils.isUrl(widget.images![0]) ? NetworkImage(widget.images![0]) : AssetImage(widget.images![0]),
imageProvider: getImageProvider(widget.images![0]),
backgroundDecoration: const BoxDecoration( backgroundDecoration: const BoxDecoration(
color: Colors.black, color: Colors.black,
), ),
@ -54,8 +71,7 @@ class _ImageViewerState extends State<ImageViewer> {
heroAttributes: PhotoViewHeroAttributes(tag: widget.images![0]), heroAttributes: PhotoViewHeroAttributes(tag: widget.images![0]),
enableRotation: true, enableRotation: true,
) )
: : PhotoViewGallery.builder(
PhotoViewGallery.builder(
itemCount: widget.images?.length, itemCount: widget.images?.length,
builder: (context, index) { builder: (context, index) {
return PhotoViewGalleryPageOptions( return PhotoViewGalleryPageOptions(
@ -89,9 +105,11 @@ class _ImageViewerState extends State<ImageViewer> {
child: Center( child: Center(
child: Visibility( child: Visibility(
visible: imgCount! > 1 ? true : false, 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'),
), ),
)),
), ),
], ],
), ),

View File

@ -52,7 +52,7 @@ conversationTypeFromString(String? type) {
return ConversationType.order.name; return ConversationType.order.name;
} else if (type.contains('groupNotify')) { } else if (type.contains('groupNotify')) {
return ConversationType.groupNotify.name; return ConversationType.groupNotify.name;
} } else {
return null; return null;
}
} }

View File

@ -527,6 +527,7 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
else if (item.elemType == 2 && item.cloudCustomData == SummaryType.shareTuangou) { else if (item.elemType == 2 && item.cloudCustomData == SummaryType.shareTuangou) {
//price,title,url,sell //price,title,url,sell
final obj = jsonDecode(item.customElem!.data!); final obj = jsonDecode(item.customElem!.data!);
logger.e(obj);
final url = obj['url']; final url = obj['url'];
final title = obj['title']; final title = obj['title'];
final price = obj['price']; final price = obj['price'];
@ -607,6 +608,7 @@ class _ChatState extends State<Chat> with SingleTickerProviderStateMixin {
else if (item.elemType == 2 && item.cloudCustomData == SummaryType.shareVideo) { else if (item.elemType == 2 && item.cloudCustomData == SummaryType.shareVideo) {
/// {imgUrl,videoUrl,width,height} /// {imgUrl,videoUrl,width,height}
final obj = jsonDecode(item.customElem!.data!); final obj = jsonDecode(item.customElem!.data!);
logger.e(obj);
final videoUrl = obj['videoUrl']; final videoUrl = obj['videoUrl'];
final imgUrl = obj['imgUrl']; final imgUrl = obj['imgUrl'];
final width = obj['width'] as num; final width = obj['width'] as num;

View File

@ -13,6 +13,7 @@ import 'package:loopin/IM/im_service.dart';
import 'package:loopin/api/shop_api.dart'; import 'package:loopin/api/shop_api.dart';
import 'package:loopin/components/my_toast.dart'; import 'package:loopin/components/my_toast.dart';
import 'package:loopin/components/network_or_asset_image.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/share_type.dart';
import 'package:loopin/models/summary_type.dart'; import 'package:loopin/models/summary_type.dart';
import 'package:loopin/service/http.dart'; import 'package:loopin/service/http.dart';
@ -48,7 +49,7 @@ class _GoodsState extends State<Goods> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
final goodsId = Get.arguments['goodsId']; final goodsId = Get.arguments['goodsId'] ?? '';
scrollController.addListener(() { scrollController.addListener(() {
setState(() { setState(() {
scrollOffset = scrollController.offset; scrollOffset = scrollController.offset;
@ -79,24 +80,21 @@ class _GoodsState extends State<Goods> {
/// ///
createOrder(String goodsId) async { createOrder(String goodsId) async {
var params ={ var params = {
"type": 1, // 1->2->;3-> "type": 1, // 1->2->;3->
"distribution": 1, // 1->2->;3->; "distribution": 1, // 1->2->;3->;
"skuItemBOList": [ "skuItemBOList": [
{ {"skuId": goodsId, "quantity": 1}
"skuId": goodsId,
"quantity": 1
}
] ]
}; };
print('下单请求参数---->${params}'); print('下单请求参数---->$params');
try { try {
final res = await Http.post('${ShopApi.createGoodsOrder}', data: params); final res = await Http.post(ShopApi.createGoodsOrder, data: params);
var resData = res['data']; var resData = res['data'];
print('1111111111111111111111111---->${res}'); print('1111111111111111111111111---->$res');
if(resData['id'].isNotEmpty){ if (resData['id'].isNotEmpty) {
return resData['id']; return resData['id'];
}else{ } else {
return null; return null;
} }
} catch (e) { } catch (e) {
@ -104,6 +102,7 @@ class _GoodsState extends State<Goods> {
return null; return null;
} }
} }
void handleShareClick(int index) { void handleShareClick(int index) {
final description = shopObj['describe']; // final description = shopObj['describe']; //
if (index == 1) { if (index == 1) {
@ -116,12 +115,13 @@ class _GoodsState extends State<Goods> {
} }
void handlCoverClick(V2TimConversation conv) async { void handlCoverClick(V2TimConversation conv) async {
// VideoMsg, //
final userId = conv.userID; final userId = conv.userID;
//price,title,url,sell //price,title,url,sell
logger.w(shopObj['name']);
final makeJson = jsonEncode({ final makeJson = jsonEncode({
"price": shopObj['price'], "price": shopObj['price'],
"title": shopObj['describe'], "title": shopObj['name'],
"url": shopObj['pic'], "url": shopObj['pic'],
"sell": Utils.graceNumber(int.parse(shopObj['sales'] ?? '0')), "sell": Utils.graceNumber(int.parse(shopObj['sales'] ?? '0')),
}); });
@ -200,7 +200,8 @@ class _GoodsState extends State<Goods> {
// //
Obx(() { 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(); if (filteredList.isEmpty) return SizedBox.shrink();
return SizedBox( return SizedBox(
height: 110, height: 110,
@ -219,11 +220,13 @@ class _GoodsState extends State<Goods> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
// Image.asset('${chatController.chatList[index].faceUrl}', width: 48.0), // Image.asset('${chatController.chatList[index].faceUrl}', width: 48.0),
NetworkOrAssetImage( ClipOval(
child: NetworkOrAssetImage(
imageUrl: filteredList[index].faceUrl, imageUrl: filteredList[index].faceUrl,
width: 48.0, width: 48.0,
height: 48.0, height: 48.0,
), ),
),
SizedBox(height: 5), SizedBox(height: 5),
Text( Text(
'${filteredList[index].conversation.showName}', '${filteredList[index].conversation.showName}',
@ -338,7 +341,12 @@ class _GoodsState extends State<Goods> {
activeColor: Colors.white, activeColor: Colors.white,
)), )),
indicatorLayout: PageIndicatorLayout.SCALE, 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 { //
// // GestureDetector(
logger.i('联系客服'); // onTap: () async {
final res = await ImService.instance.getConversation(conversationID: 'c2c_${shopObj['tenantId']}'); // //
V2TimConversation conversation = res.data; // logger.i('联系客服:$shopObj');
logger.i(conversation.toLogString()); // final res = await ImService.instance.getConversation(conversationID: 'c2c_${shopObj['tenantId']}');
if (res.success) { // V2TimConversation conversation = res.data;
// // logger.i(conversation.toLogString());
conversation.showName = conversation.showName ?? shopObj['storeName']; // if (res.success) {
Get.toNamed('/chat', arguments: conversation); // //
} else { // V2TimUserFullInfo? sellerInfo;
MyDialog.toast(res.desc, icon: const Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200))); // final resIm = await ImService.instance.otherInfo(shopObj['tenantId']);
} // if (resIm.success && resIm.data != null) {
}, // sellerInfo = resIm.data!;
child: Column( // logger.i(sellerInfo!.toLogString());
mainAxisAlignment: MainAxisAlignment.center, // } else {
children: [ // logger.e(resIm.desc);
Icon( // }
Icons.child_care_outlined, // conversation.showName = conversation.showName ?? sellerInfo!.nickName;
size: 18.0, // Get.toNamed('/chat', arguments: conversation);
), // } else {
Text( // MyDialog.toast(res.desc, icon: const Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
'联系商家', // }
style: TextStyle(fontSize: 12.0), // },
) // child: Column(
], // mainAxisAlignment: MainAxisAlignment.center,
), // children: [
) // Icon(
// Icons.child_care_outlined,
// size: 18.0,
// ),
// Text(
// '联系商家',
// style: TextStyle(fontSize: 12.0),
// )
// ],
// ),
// )
// Column( // Column(
// mainAxisAlignment: MainAxisAlignment.center, // mainAxisAlignment: MainAxisAlignment.center,
// children: [ // children: [
@ -652,9 +671,9 @@ class _GoodsState extends State<Goods> {
// orderId // orderId
// String orderId = '1958380183857659904'; // // String orderId = '1958380183857659904'; //
String orderId = await createOrder(shopObj['skuList'][0]['id']); String orderId = await createOrder(shopObj['skuList'][0]['id']);
if(orderId.isNotEmpty){ if (orderId.isNotEmpty) {
Get.toNamed('/order/detail', arguments: orderId); Get.toNamed('/order/detail', arguments: orderId);
}else{ } else {
MyDialog.toast('生成订单失败', icon: const Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200))); MyDialog.toast('生成订单失败', icon: const Icon(Icons.warning), style: ToastStyle(backgroundColor: Colors.red.withAlpha(200)));
} }
}, },

View File

@ -182,7 +182,7 @@ class FansState extends State<Fans> with SingleTickerProviderStateMixin {
onTap: () { onTap: () {
Get.toNamed( Get.toNamed(
'/vloger', '/vloger',
arguments: item.userInfo.userID, arguments: {'memberId': item.userInfo.userID},
); );
}, },
child: Row( child: Row(

View File

@ -182,7 +182,7 @@ class FlowingState extends State<Flowing> with SingleTickerProviderStateMixin {
onTap: () { onTap: () {
Get.toNamed( Get.toNamed(
'/vloger', '/vloger',
arguments: item.userInfo.userID, arguments: {'memberId': item.userInfo.userID},
); );
}, },
child: Row( child: Row(

View File

@ -182,7 +182,7 @@ class MutualFollowersState extends State<MutualFollowers> with SingleTickerProvi
onTap: () { onTap: () {
Get.toNamed( Get.toNamed(
'/vloger', '/vloger',
arguments: item.userInfo.userID, arguments: {'memberId': item.userInfo.userID},
); );
}, },
child: Row( child: Row(

View File

@ -1,12 +1,15 @@
import 'dart:io'; import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:loopin/IM/im_service.dart';
import 'package:loopin/api/common_api.dart'; import 'package:loopin/api/common_api.dart';
import 'package:loopin/api/video_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/service/http.dart';
import 'package:loopin/utils/index.dart'; import 'package:loopin/utils/index.dart';
import 'package:loopin/utils/snapshot.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart'; import 'package:wechat_assets_picker/wechat_assets_picker.dart';
class UploadVideoPage extends StatefulWidget { class UploadVideoPage extends StatefulWidget {
@ -19,15 +22,28 @@ class UploadVideoPage extends StatefulWidget {
class _UploadVideoPageState extends State<UploadVideoPage> { class _UploadVideoPageState extends State<UploadVideoPage> {
final selectedVideo = Rxn<AssetEntity>(); final selectedVideo = Rxn<AssetEntity>();
final selectedCover = Rxn<AssetEntity>(); final selectedCover = Rxn<AssetEntity>();
//
final uploading = false.obs; final uploading = false.obs;
final uploadProgress = 0.0.obs; final uploadProgress = 0.0.obs;
final status = ''.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 FocusNode descFocusNode = FocusNode();
final uploadedVideoUrl = ''.obs;
final TextEditingController descriptionController = TextEditingController(); final TextEditingController descriptionController = TextEditingController();
Future<void> pickVideo() async { Future<void> pickVideo() async {
FocusScope.of(context).unfocus();
final result = await PhotoManager.requestPermissionExtend(); final result = await PhotoManager.requestPermissionExtend();
if (!result.isAuth) { if (!result.isAuth) {
@ -60,10 +76,14 @@ class _UploadVideoPageState extends State<UploadVideoPage> {
if (pickedAssets != null && pickedAssets.isNotEmpty) { if (pickedAssets != null && pickedAssets.isNotEmpty) {
selectedVideo.value = pickedAssets.first; selectedVideo.value = pickedAssets.first;
status.value = '已选择视频'; status.value = '已选择视频';
uploadVideo();
} }
} }
//
Future<void> pickCoverImage() async { Future<void> pickCoverImage() async {
FocusScope.of(context).unfocus();
final result = await PhotoManager.requestPermissionExtend(); final result = await PhotoManager.requestPermissionExtend();
if (!result.isAuth) { if (!result.isAuth) {
@ -88,30 +108,73 @@ class _UploadVideoPageState extends State<UploadVideoPage> {
if (pickedAssets != null && pickedAssets.isNotEmpty) { if (pickedAssets != null && pickedAssets.isNotEmpty) {
selectedCover.value = pickedAssets.first; 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 { Future<void> uploadVideo() async {
final file = await selectedVideo.value?.file; final file = await selectedVideo.value?.file;
final coverFile = await selectedCover.value?.file;
final desc = descController.text.trim();
if (file == null) { if (file == null) {
status.value = '未选择视频'; status.value = '未选择视频';
return; return;
} }
if (desc.isEmpty) {
Get.snackbar('请填写描述', '视频描述不能为空');
return;
}
uploading.value = true; uploading.value = true;
uploadProgress.value = 0; uploadProgress.value = 0;
status.value = '上传中...'; status.value = '上传中...';
videoPath.value = file.path;
logger.w(videoPath.value);
snapshot.value = (await generateVideoThumbnail(file.path))!;
logger.w(snapshot.value);
try { try {
final data = await Http.upload( final res = await Http.upload(
CommonApi.uploadFile, CommonApi.uploadFile,
filePath: file.path, filePath: file.path,
fileKey: 'file', fileKey: 'file',
@ -121,60 +184,98 @@ class _UploadVideoPageState extends State<UploadVideoPage> {
} }
}, },
); );
uploadedVideoUrl.value = data; uploadedVideoUrl.value = res['data']['url'];
status.value = '上传成功'; status.value = '上传成功';
//
descController.clear();
selectedVideo.value = null;
selectedCover.value = null;
uploadedVideoUrl.value = '';
} catch (e) { } catch (e) {
descFocusNode.unfocus();
if (e is SocketException) { if (e is SocketException) {
status.value = '网络错误,请检查连接'; status.value = '网络错误,请检查连接';
} else { } else {
status.value = '上传失败: ${e.toString()}'; status.value = '上传失败: ${e.toString()}';
} }
} finally { } finally {
descFocusNode.unfocus();
uploading.value = false; uploading.value = false;
} }
} }
//
Future<void> submitForm() async { Future<void> submitForm() async {
logger.w(descriptionController.text);
FocusScope.of(context).unfocus();
if (uploadedVideoUrl.value.isEmpty) { if (uploadedVideoUrl.value.isEmpty) {
Get.snackbar('请先上传视频', '未检测到上传的视频'); Get.snackbar('请先上传视频', '未检测到上传的视频');
return; return;
} }
if (descriptionController.text.trim().isEmpty) { if (descriptionController.text.trim().isEmpty) {
Get.snackbar('请填写视频描述', '描述是必填项'); Get.snackbar('请填写视频描述', '描述是必填项');
return; return;
} }
try { try {
await Http.post(VideoApi.publish, data: { final data = {
'video_url': uploadedVideoUrl.value, 'url': uploadedVideoUrl.value,
'desc': descriptionController.text.trim(), 'title': descriptionController.text.trim(),
'cover': selectedCover.value, };
}); if (uploadedImgUrl.value.isNotEmpty) {
Get.snackbar('发布成功', '视频已成功发布'); data['cover'] = uploadedImgUrl.value;
}
logger.w('发布内容:$data');
await Http.post(VideoApi.publish, data: data);
Get.snackbar('发布成功', '视频正在审核中');
clearForm(); clearForm();
} catch (e) { } catch (e) {
Get.snackbar('发布失败', '$e'); Get.snackbar('发布失败', '$e');
} } finally {}
} }
void clearForm() { void clearForm() {
selectedVideo.value = null; selectedVideo.value = null;
selectedCover.value = null; selectedCover.value = null;
descriptionController.clear(); uploadedImgUrl.value = '';
status.value = '';
uploadProgress.value = 0.0;
uploadedVideoUrl.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 @override
void dispose() { void dispose() {
descController.dispose(); descriptionController.dispose();
descFocusNode.dispose(); descFocusNode.dispose();
super.dispose(); super.dispose();
} }
@ -193,7 +294,8 @@ class _UploadVideoPageState extends State<UploadVideoPage> {
children: [ children: [
TextField( TextField(
focusNode: descFocusNode, focusNode: descFocusNode,
controller: descController, autofocus: false,
controller: descriptionController,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: '视频描述 *', labelText: '视频描述 *',
border: OutlineInputBorder(), border: OutlineInputBorder(),
@ -201,54 +303,40 @@ class _UploadVideoPageState extends State<UploadVideoPage> {
maxLines: 2, maxLines: 2,
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
//
Obx(() { Obx(() {
final video = selectedVideo.value;
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Expanded( //
child: Text( snapshot.value.isNotEmpty
video != null ? '已选择视频:${video.title}' : '未选择视频', ? GestureDetector(
style: const TextStyle(fontSize: 16), onTap: () => openVd(),
), child: ClipRRect(
),
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(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
child: Image.memory(snapshot.data!, width: 80, height: 80), child: Image.file(
); File(snapshot.value),
} else { width: 80,
return const SizedBox(width: 80, height: 80); fit: BoxFit.cover,
}
},
)
: const Text('未选择封面图'),
const SizedBox(width: 12),
ElevatedButton(
onPressed: pickCoverImage,
child: const Text('选择封面(可选)'),
), ),
], ),
); )
}), : const SizedBox(
const SizedBox(height: 30), height: 80,
Obx(() => uploading.value child: Center(
child: Text(
'未选择视频',
style: TextStyle(fontSize: 16),
)),
),
const SizedBox(width: 24),
// /
Expanded(
child: uploading.value
? Column( ? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const CircularProgressIndicator(), const CircularProgressIndicator(),
const SizedBox(height: 10), const SizedBox(height: 10),
@ -257,16 +345,83 @@ class _UploadVideoPageState extends State<UploadVideoPage> {
Text('${(uploadProgress.value * 100).toStringAsFixed(1)}%'), 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, width: double.infinity,
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: uploadVideo, onPressed: submitForm,
icon: const Icon(Icons.upload), icon: const Icon(Icons.upload),
label: const Text('上传'), label: const Text('发布'),
),
), ),
)),
const SizedBox(height: 20),
Obx(() => Text(status.value)),
], ],
), ),
), ),

View File

@ -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.dart';
import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.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 'package:tencent_cloud_chat_sdk/models/v2_tim_conversation.dart';
import '../../../router/fade_route.dart'; import '../../../router/fade_route.dart';
import './components/popup_reply.dart'; import './components/popup_reply.dart';
@ -100,7 +101,7 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
}); });
try { try {
final res = await Http.get('${VideoApi.videoDetailApi}${videoId}'); final res = await Http.get('${VideoApi.videoDetailApi}$videoId');
logger.d('视频详情接口返回: ${json.encode(res)}'); logger.d('视频详情接口返回: ${json.encode(res)}');
@ -256,7 +257,6 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
Future<void> unfollowUser() async { Future<void> unfollowUser() async {
try { try {
final vlogerId = videoData['vlogerId'] ?? videoData['memberId']; final vlogerId = videoData['vlogerId'] ?? videoData['memberId'];
} catch (e) { } catch (e) {
logger.e('取消关注用户异常: $e'); logger.e('取消关注用户异常: $e');
MyToast().tip( MyToast().tip(
@ -309,6 +309,8 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: (context) { builder: (context) {
final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList();
return Material( return Material(
color: Colors.white, color: Colors.white,
child: Padding( 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( SizedBox(
height: 110, height: 110,
child: ListView.builder( child: ListView.builder(
scrollDirection: Axis.horizontal, 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), padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 20.0),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList();
return GestureDetector( return GestureDetector(
onTap: () => handlCoverClick(filteredList[index].conversation), onTap: () => handlCoverClick(filteredList[index].conversation),
child: Container( child: Container(
@ -365,11 +366,13 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
NetworkOrAssetImage( ClipOval(
child: NetworkOrAssetImage(
imageUrl: filteredList[index].faceUrl, imageUrl: filteredList[index].faceUrl,
width: 48.0, width: 48.0,
height: 48.0, height: 48.0,
), ),
),
const SizedBox(height: 5), const SizedBox(height: 5),
Text( Text(
filteredList[index].conversation.showName ?? '', filteredList[index].conversation.showName ?? '',
@ -908,6 +911,7 @@ class _VideoDetailPageState extends State<VideoDetailPage> {
); );
} }
} }
class CommentBottomSheet extends StatefulWidget { class CommentBottomSheet extends StatefulWidget {
final String videoId; final String videoId;
final Function(int) onCommentCountChanged; // final Function(int) onCommentCountChanged; //

View File

@ -798,6 +798,8 @@ class _AttentionModuleState extends State<AttentionModule> {
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: (context) { builder: (context) {
final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList();
return Material( return Material(
color: Colors.white, color: Colors.white,
child: Padding( 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( SizedBox(
height: 110, height: 110,
child: ListView.builder( child: ListView.builder(
scrollDirection: Axis.horizontal, 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), padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 20.0),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList();
return GestureDetector( return GestureDetector(
onTap: () => handlCoverClick(filteredList[index].conversation), onTap: () => handlCoverClick(filteredList[index].conversation),
child: Container( child: Container(
@ -1265,16 +1266,13 @@ class _AttentionModuleState extends State<AttentionModule> {
), ),
], ],
), ),
onTap: ()async { onTap: () async {
player.pause(); player.pause();
// //
final result = await Get.toNamed( final result = await Get.toNamed('/report', arguments: videoList[videoModuleController.videoPlayIndex.value]);
'/report',
arguments: videoList[videoModuleController
.videoPlayIndex.value]);
if (result != null) { if (result != null) {
player.play(); player.play();
}; }
}, },
), ),
], ],

View File

@ -15,6 +15,7 @@ import 'package:loopin/IM/im_service.dart' hide logger;
import 'package:loopin/api/video_api.dart'; import 'package:loopin/api/video_api.dart';
import 'package:loopin/components/my_toast.dart'; import 'package:loopin/components/my_toast.dart';
import 'package:loopin/components/network_or_asset_image.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/models/summary_type.dart';
import 'package:loopin/service/http.dart'; import 'package:loopin/service/http.dart';
import 'package:loopin/utils/download_video.dart'; import 'package:loopin/utils/download_video.dart';
@ -794,6 +795,7 @@ class _RecommendModuleState extends State<RecommendModule> {
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: (context) { builder: (context) {
final filteredList = chatController.chatList.where((item) => conversationTypeFromString(item.isCustomAdmin) == null).toList();
return Material( return Material(
color: Colors.white, color: Colors.white,
child: Padding( 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( SizedBox(
height: 110, height: 110,
child: ListView.builder( child: ListView.builder(
scrollDirection: Axis.horizontal, 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), padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 20.0),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final filteredList = chatController.chatList.where((item) => item.conversation.conversationGroupList?.isEmpty == true).toList();
return GestureDetector( return GestureDetector(
onTap: () => handlCoverClick(filteredList[index].conversation), onTap: () => handlCoverClick(filteredList[index].conversation),
child: Container( child: Container(
@ -850,11 +850,13 @@ class _RecommendModuleState extends State<RecommendModule> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
NetworkOrAssetImage( ClipOval(
child: NetworkOrAssetImage(
imageUrl: filteredList[index].faceUrl, imageUrl: filteredList[index].faceUrl,
width: 48.0, width: 48.0,
height: 48.0, height: 48.0,
), ),
),
const SizedBox(height: 5), const SizedBox(height: 5),
Text( Text(
filteredList[index].conversation.showName ?? '', filteredList[index].conversation.showName ?? '',

View File

@ -11,5 +11,5 @@ Future<String?> generateVideoThumbnail(String videoPath) async {
maxWidth: 120, maxWidth: 120,
quality: 75, quality: 75,
); );
return thumbnailPath; return thumbnailPath ?? '';
} }