flutter/lib/pages/upload_video_page/upload_video_page.dart

431 lines
14 KiB
Dart
Raw Normal View History

2025-07-21 15:46:30 +08:00
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
2025-09-04 22:19:56 +08:00
import 'package:loopin/IM/im_service.dart';
2025-07-21 15:46:30 +08:00
import 'package:loopin/api/common_api.dart';
import 'package:loopin/api/video_api.dart';
2025-09-04 22:19:56 +08:00
import 'package:loopin/components/image_viewer.dart';
import 'package:loopin/components/preview_video.dart';
2025-07-21 15:46:30 +08:00
import 'package:loopin/service/http.dart';
import 'package:loopin/utils/index.dart';
2025-09-04 22:19:56 +08:00
import 'package:loopin/utils/snapshot.dart';
2025-07-21 15:46:30 +08:00
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
class UploadVideoPage extends StatefulWidget {
const UploadVideoPage({super.key});
@override
State<UploadVideoPage> createState() => _UploadVideoPageState();
}
class _UploadVideoPageState extends State<UploadVideoPage> {
final selectedVideo = Rxn<AssetEntity>();
final selectedCover = Rxn<AssetEntity>();
2025-09-04 22:19:56 +08:00
//视频
2025-07-21 15:46:30 +08:00
final uploading = false.obs;
final uploadProgress = 0.0.obs;
final status = ''.obs;
2025-09-04 22:19:56 +08:00
//图片
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;
2025-07-21 15:46:30 +08:00
final FocusNode descFocusNode = FocusNode();
2025-09-04 22:19:56 +08:00
2025-07-21 15:46:30 +08:00
final TextEditingController descriptionController = TextEditingController();
Future<void> pickVideo() async {
2025-09-04 22:19:56 +08:00
FocusScope.of(context).unfocus();
2025-07-21 15:46:30 +08:00
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 = '已选择视频';
2025-09-04 22:19:56 +08:00
uploadVideo();
2025-07-21 15:46:30 +08:00
}
}
2025-09-04 22:19:56 +08:00
// 选择封面图
2025-07-21 15:46:30 +08:00
Future<void> pickCoverImage() async {
2025-09-04 22:19:56 +08:00
FocusScope.of(context).unfocus();
2025-07-21 15:46:30 +08:00
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;
2025-09-04 22:19:56 +08:00
// 执行上传图片逻辑
uploadImg();
2025-07-21 15:46:30 +08:00
}
}
2025-09-04 22:19:56 +08:00
// 上传图片
Future<void> uploadImg() async {
2025-07-21 15:46:30 +08:00
final coverFile = await selectedCover.value?.file;
2025-09-04 22:19:56 +08:00
if (coverFile == null) {
status.value = '未选择封面图';
2025-07-21 15:46:30 +08:00
return;
}
2025-09-04 22:19:56 +08:00
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;
if (file == null) {
status.value = '未选择视频';
2025-07-21 15:46:30 +08:00
return;
}
uploading.value = true;
uploadProgress.value = 0;
status.value = '上传中...';
2025-09-04 22:19:56 +08:00
videoPath.value = file.path;
logger.w(videoPath.value);
snapshot.value = (await generateVideoThumbnail(file.path))!;
logger.w(snapshot.value);
2025-07-21 15:46:30 +08:00
try {
2025-09-04 22:19:56 +08:00
final res = await Http.upload(
2025-07-21 15:46:30 +08:00
CommonApi.uploadFile,
filePath: file.path,
fileKey: 'file',
onSendProgress: (sent, total) {
if (total > 0) {
uploadProgress.value = sent / total;
}
},
);
2025-09-04 22:19:56 +08:00
uploadedVideoUrl.value = res['data']['url'];
2025-07-21 15:46:30 +08:00
status.value = '上传成功';
} catch (e) {
2025-09-04 22:19:56 +08:00
descFocusNode.unfocus();
2025-07-21 15:46:30 +08:00
if (e is SocketException) {
status.value = '网络错误,请检查连接';
} else {
status.value = '上传失败: ${e.toString()}';
}
} finally {
2025-09-04 22:19:56 +08:00
descFocusNode.unfocus();
2025-07-21 15:46:30 +08:00
uploading.value = false;
}
}
2025-09-04 22:19:56 +08:00
// 发布
2025-07-21 15:46:30 +08:00
Future<void> submitForm() async {
2025-09-04 22:19:56 +08:00
logger.w(descriptionController.text);
FocusScope.of(context).unfocus();
2025-07-21 15:46:30 +08:00
if (uploadedVideoUrl.value.isEmpty) {
Get.snackbar('请先上传视频', '未检测到上传的视频');
return;
}
if (descriptionController.text.trim().isEmpty) {
Get.snackbar('请填写视频描述', '描述是必填项');
return;
}
try {
2025-09-04 22:19:56 +08:00
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('发布成功', '视频正在审核中');
2025-07-21 15:46:30 +08:00
clearForm();
} catch (e) {
Get.snackbar('发布失败', '$e');
2025-09-04 22:19:56 +08:00
} finally {}
2025-07-21 15:46:30 +08:00
}
void clearForm() {
selectedVideo.value = null;
selectedCover.value = null;
2025-09-04 22:19:56 +08:00
uploadedImgUrl.value = '';
uploadedVideoUrl.value = '';
2025-07-21 15:46:30 +08:00
descriptionController.clear();
2025-09-04 22:19:56 +08:00
descFocusNode.unfocus();
2025-07-21 15:46:30 +08:00
status.value = '';
2025-09-04 22:19:56 +08:00
status2.value = '';
2025-07-21 15:46:30 +08:00
uploadProgress.value = 0.0;
2025-09-04 22:19:56 +08:00
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,
));
2025-07-21 15:46:30 +08:00
}
@override
void dispose() {
2025-09-04 22:19:56 +08:00
descriptionController.dispose();
2025-07-21 15:46:30 +08:00
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,
2025-09-04 22:19:56 +08:00
autofocus: false,
controller: descriptionController,
2025-07-21 15:46:30 +08:00
decoration: const InputDecoration(
labelText: '视频描述 *',
border: OutlineInputBorder(),
),
maxLines: 2,
),
const SizedBox(height: 20),
2025-09-04 22:19:56 +08:00
// 视频
2025-07-21 15:46:30 +08:00
Obx(() {
return Row(
2025-09-04 22:19:56 +08:00
crossAxisAlignment: CrossAxisAlignment.center,
2025-07-21 15:46:30 +08:00
children: [
2025-09-04 22:19:56 +08:00
// 左侧封面预览
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),
// 右侧按钮/进度条
2025-07-21 15:46:30 +08:00
Expanded(
2025-09-04 22:19:56 +08:00
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),
),
),
2025-07-21 15:46:30 +08:00
),
],
);
}),
const SizedBox(height: 20),
2025-09-04 22:19:56 +08:00
// 图片上传
2025-07-21 15:46:30 +08:00
Obx(() {
return Row(
2025-09-04 22:19:56 +08:00
crossAxisAlignment: CrossAxisAlignment.center,
2025-07-21 15:46:30 +08:00
children: [
2025-09-04 22:19:56 +08:00
// 左侧图片预览
imgPath.value.isNotEmpty
? GestureDetector(
onTap: () => openImg(),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.file(
File(imgPath.value),
width: 80,
fit: BoxFit.cover,
),
),
2025-07-21 15:46:30 +08:00
)
2025-09-04 22:19:56 +08:00
: 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),
),
),
2025-07-21 15:46:30 +08:00
),
],
);
}),
const SizedBox(height: 30),
2025-09-04 22:19:56 +08:00
// 发布按钮
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: submitForm,
icon: const Icon(Icons.upload),
label: const Text('发布'),
),
),
2025-07-21 15:46:30 +08:00
],
),
),
));
}
}