451 lines
14 KiB
Dart
451 lines
14 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:loopin/IM/controller/im_user_info_controller.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 {
|
|
final bool visible;
|
|
const UploadVideoPage({super.key, required this.visible});
|
|
|
|
@override
|
|
State<UploadVideoPage> createState() => _UploadVideoPageState();
|
|
}
|
|
|
|
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 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;
|
|
// 文件id
|
|
final fileId = ''.obs;
|
|
|
|
late final FocusNode descFocusNode;
|
|
|
|
late TextEditingController descriptionController;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
descFocusNode = FocusNode();
|
|
descriptionController = TextEditingController();
|
|
}
|
|
|
|
Future<void> pickVideo() async {
|
|
descFocusNode.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<void> pickCoverImage() async {
|
|
descFocusNode.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<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;
|
|
|
|
if (file == null) {
|
|
status.value = '未选择视频';
|
|
return;
|
|
}
|
|
|
|
uploading.value = true;
|
|
uploadProgress.value = 0;
|
|
status.value = '上传中...';
|
|
videoPath.value = file.path;
|
|
logger.w(videoPath.value);
|
|
// vwidth.value = file.width;
|
|
|
|
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) {
|
|
final pro = sent / total;
|
|
uploadProgress.value = pro.clamp(0, 0.999);
|
|
}
|
|
},
|
|
);
|
|
logger.w('上传结果$res');
|
|
uploadedVideoUrl.value = res['data']['url'];
|
|
fileId.value = res['data']['ossId'];
|
|
status.value = '上传成功';
|
|
descFocusNode.unfocus();
|
|
uploading.value = false;
|
|
} catch (e) {
|
|
if (e is SocketException) {
|
|
status.value = '网络错误,请检查连接';
|
|
} else {
|
|
status.value = '上传失败: ${e.toString()}';
|
|
}
|
|
logger.e(e);
|
|
descFocusNode.unfocus();
|
|
} finally {
|
|
descFocusNode.unfocus();
|
|
uploading.value = false;
|
|
}
|
|
}
|
|
|
|
// 发布
|
|
Future<void> submitForm() async {
|
|
logger.w(descriptionController.text);
|
|
|
|
descFocusNode.unfocus();
|
|
if (fileId.value.isEmpty) {
|
|
Get.snackbar('请先上传视频', '未检测到上传的视频');
|
|
return;
|
|
}
|
|
if (descriptionController.text.trim().isEmpty) {
|
|
Get.snackbar('请填写视频描述', '描述是必填项');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
final data = {
|
|
'url': uploadedVideoUrl.value,
|
|
'title': descriptionController.text.trim(),
|
|
'fileId': fileId.value,
|
|
'vlogerId': Get.find<ImUserInfoController>().userID.value,
|
|
'width': selectedVideo.value!.width,
|
|
'height': selectedVideo.value!.height,
|
|
};
|
|
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 = '';
|
|
// 本地回显
|
|
snapshot.value = '';
|
|
imgPath.value = '';
|
|
}
|
|
|
|
// 预览视频
|
|
void openVd() {
|
|
descFocusNode.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 FocusScope(
|
|
canRequestFocus: widget.visible,
|
|
// 只有 UploadVideoPage 可见时才允许 TextField 聚焦
|
|
child: GestureDetector(
|
|
onTap: () => descFocusNode.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('发布'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|