117 lines
3.5 KiB
Dart
117 lines
3.5 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:image_picker/image_picker.dart';
|
|
|
|
import '../core/network/api_exception.dart';
|
|
import '../services/upload_service.dart';
|
|
|
|
/// 上传发布流程步骤:选视频 → 填信息 → 发布中 → 完成
|
|
enum UploadStep { pick, edit, publishing, done }
|
|
|
|
/// 上传发布状态(《03-接口文档》5.5/5.6 三步链路)
|
|
class UploadState {
|
|
const UploadState({
|
|
this.step = UploadStep.pick,
|
|
this.file,
|
|
this.fileSize = 0,
|
|
this.progress = 0,
|
|
this.result,
|
|
this.error = '',
|
|
});
|
|
|
|
final UploadStep step;
|
|
final XFile? file; // 已选择的视频
|
|
final int fileSize; // 字节
|
|
final int progress; // 上传进度 0-100
|
|
final PublishResult? result;
|
|
final String error; // 发布失败文案(停留在 edit 步骤展示)
|
|
|
|
bool get publishing => step == UploadStep.publishing;
|
|
|
|
UploadState copyWith({
|
|
UploadStep? step,
|
|
XFile? file,
|
|
int? fileSize,
|
|
int? progress,
|
|
PublishResult? result,
|
|
String? error,
|
|
}) =>
|
|
UploadState(
|
|
step: step ?? this.step,
|
|
file: file ?? this.file,
|
|
fileSize: fileSize ?? this.fileSize,
|
|
progress: progress ?? this.progress,
|
|
result: result ?? this.result,
|
|
error: error ?? this.error,
|
|
);
|
|
}
|
|
|
|
class UploadNotifier extends StateNotifier<UploadState> {
|
|
UploadNotifier() : super(const UploadState());
|
|
|
|
final _picker = ImagePicker();
|
|
|
|
/// 从相册选择视频 → 进入填信息步骤
|
|
Future<void> pickVideo() async {
|
|
final file = await _picker.pickVideo(source: ImageSource.gallery);
|
|
if (file == null) return;
|
|
final size = await file.length();
|
|
state = UploadState(step: UploadStep.edit, file: file, fileSize: size);
|
|
}
|
|
|
|
/// 重新选择(回到选视频步骤)
|
|
void reset() => state = const UploadState();
|
|
|
|
/// 发布:获取凭证 → 上传文件(进度) → 创建视频
|
|
Future<void> submit({
|
|
required String title,
|
|
String description = '',
|
|
required int categoryId,
|
|
List<String> tags = const [],
|
|
}) async {
|
|
final file = state.file;
|
|
if (file == null || state.publishing) return;
|
|
state = state.copyWith(step: UploadStep.publishing, progress: 0, error: '');
|
|
try {
|
|
final token = await UploadService.fetchUploadToken(
|
|
fileName: file.name,
|
|
fileSize: state.fileSize,
|
|
);
|
|
await UploadService.uploadFile(
|
|
objectKey: token.objectKey,
|
|
filePath: file.path,
|
|
fileName: file.name,
|
|
onProgress: (sent, total) {
|
|
if (total > 0) {
|
|
state = state.copyWith(progress: (sent * 100) ~/ total);
|
|
}
|
|
},
|
|
);
|
|
final result = await UploadService.publish(
|
|
objectKey: token.objectKey,
|
|
title: title,
|
|
description: description,
|
|
categoryId: categoryId,
|
|
tags: tags,
|
|
);
|
|
state = state.copyWith(step: UploadStep.done, result: result);
|
|
} on ApiException catch (e) {
|
|
// 失败回到表单步骤,保留已填内容,展示服务端文案
|
|
state = state.copyWith(step: UploadStep.edit, error: e.message);
|
|
} catch (_) {
|
|
state = state.copyWith(step: UploadStep.edit, error: '网络异常,请稍后重试');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 上传发布(autoDispose:离开页面即重置流程)
|
|
final uploadProvider =
|
|
StateNotifierProvider.autoDispose<UploadNotifier, UploadState>(
|
|
(ref) => UploadNotifier(),
|
|
);
|
|
|
|
/// 分类列表(发布表单用)
|
|
final uploadCategoriesProvider =
|
|
FutureProvider.autoDispose<List<VideoCategory>>(
|
|
(ref) => UploadService.fetchCategories(),
|
|
);
|