import 'dart:io'; 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 { const UploadVideoPage({super.key}); @override State createState() => _UploadVideoPageState(); } class _UploadVideoPageState extends State { final selectedVideo = Rxn(); final selectedCover = Rxn(); //视频 final uploading = false.obs; final uploadProgress = 0.0.obs; final status = ''.obs; //图片 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 TextEditingController descriptionController = TextEditingController(); Future pickVideo() async { FocusScope.of(context).unfocus(); final result = await PhotoManager.requestPermissionExtend(); if (!result.isAuth) { if (!mounted) return; Get.snackbar('权限被拒绝', '请前往设置授权访问视频'); return; } if (!mounted) return; final pickedAssets = await AssetPicker.pickAssets( context, pickerConfig: AssetPickerConfig( textDelegate: const AssetPickerTextDelegate(), pathNameBuilder: (AssetPathEntity album) { return Utils.translateAlbumName(album); }, maxAssets: 1, requestType: RequestType.video, filterOptions: FilterOptionGroup( videoOption: const FilterOption( durationConstraint: DurationConstraint( max: Duration(seconds: 30), ), ), ), ), ); if (pickedAssets != null && pickedAssets.isNotEmpty) { selectedVideo.value = pickedAssets.first; status.value = '已选择视频'; uploadVideo(); } } // 选择封面图 Future pickCoverImage() async { FocusScope.of(context).unfocus(); final result = await PhotoManager.requestPermissionExtend(); if (!result.isAuth) { if (!mounted) return; Get.snackbar('权限被拒绝', '请前往设置授权访问照片'); return; } if (!mounted) return; final pickedAssets = await AssetPicker.pickAssets( context, pickerConfig: AssetPickerConfig( textDelegate: const AssetPickerTextDelegate(), pathNameBuilder: (AssetPathEntity album) { return Utils.translateAlbumName(album); }, maxAssets: 1, requestType: RequestType.image, ), ); if (pickedAssets != null && pickedAssets.isNotEmpty) { selectedCover.value = pickedAssets.first; // 执行上传图片逻辑 uploadImg(); } } // 上传图片 Future 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 uploadVideo() async { final file = await selectedVideo.value?.file; if (file == null) { status.value = '未选择视频'; 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 res = await Http.upload( CommonApi.uploadFile, filePath: file.path, fileKey: 'file', onSendProgress: (sent, total) { if (total > 0) { uploadProgress.value = sent / total; } }, ); uploadedVideoUrl.value = res['data']['url']; status.value = '上传成功'; } catch (e) { descFocusNode.unfocus(); if (e is SocketException) { status.value = '网络错误,请检查连接'; } else { status.value = '上传失败: ${e.toString()}'; } } finally { descFocusNode.unfocus(); uploading.value = false; } } // 发布 Future 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 { 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; 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() { descriptionController.dispose(); descFocusNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return GestureDetector( onTap: () { FocusScope.of(context).unfocus(); }, child: Scaffold( appBar: AppBar(title: const Text('上传视频')), body: SingleChildScrollView( padding: const EdgeInsets.all(20), child: Column( children: [ TextField( focusNode: descFocusNode, autofocus: false, controller: descriptionController, decoration: const InputDecoration( labelText: '视频描述 *', border: OutlineInputBorder(), ), maxLines: 2, ), const SizedBox(height: 20), // 视频 Obx(() { return Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ // 左侧封面预览 snapshot.value.isNotEmpty ? GestureDetector( onTap: () => openVd(), child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.file( File(snapshot.value), width: 80, fit: BoxFit.cover, ), ), ) : 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), LinearProgressIndicator(value: uploadProgress.value), const SizedBox(height: 5), Text('${(uploadProgress.value * 100).toStringAsFixed(1)}%'), ], ) : 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: submitForm, icon: const Icon(Icons.upload), label: const Text('发布'), ), ), ], ), ), )); } }