import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:loopin/api/common_api.dart'; import 'package:loopin/api/video_api.dart'; import 'package:loopin/service/http.dart'; import 'package:loopin/utils/index.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 descController = TextEditingController(); final FocusNode descFocusNode = FocusNode(); final uploadedVideoUrl = ''.obs; final TextEditingController descriptionController = TextEditingController(); Future pickVideo() async { 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 = '已选择视频'; } } Future pickCoverImage() async { 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; } } Future 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 = '上传中...'; try { final data = await Http.upload( CommonApi.uploadFile, filePath: file.path, fileKey: 'file', onSendProgress: (sent, total) { if (total > 0) { uploadProgress.value = sent / total; } }, ); uploadedVideoUrl.value = data; status.value = '上传成功'; // 清空表单 descController.clear(); selectedVideo.value = null; selectedCover.value = null; uploadedVideoUrl.value = ''; } catch (e) { if (e is SocketException) { status.value = '网络错误,请检查连接'; } else { status.value = '上传失败: ${e.toString()}'; } } finally { uploading.value = false; } } Future submitForm() async { 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('发布成功', '视频已成功发布'); clearForm(); } catch (e) { Get.snackbar('发布失败', '$e'); } } void clearForm() { selectedVideo.value = null; selectedCover.value = null; descriptionController.clear(); status.value = ''; uploadProgress.value = 0.0; uploadedVideoUrl.value = ''; } @override void dispose() { descController.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, controller: descController, decoration: const InputDecoration( labelText: '视频描述 *', border: OutlineInputBorder(), ), maxLines: 2, ), const SizedBox(height: 20), Obx(() { final video = selectedVideo.value; return Row( 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( future: cover.thumbnailDataWithSize(const ThumbnailSize(80, 80)), builder: (_, snapshot) { if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) { return 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('选择封面(可选)'), ), ], ); }), const SizedBox(height: 30), Obx(() => uploading.value ? Column( children: [ const CircularProgressIndicator(), const SizedBox(height: 10), LinearProgressIndicator(value: uploadProgress.value), const SizedBox(height: 5), Text('${(uploadProgress.value * 100).toStringAsFixed(1)}%'), ], ) : SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: uploadVideo, icon: const Icon(Icons.upload), label: const Text('上传'), ), )), const SizedBox(height: 20), Obx(() => Text(status.value)), ], ), ), )); } }