607 lines
17 KiB
Dart
607 lines
17 KiB
Dart
import 'dart:io';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:video_player/video_player.dart';
|
||
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
|
||
import '../pages/center/fullScreenImg.dart';
|
||
import '../pages/mine/drafts.dart';
|
||
import '../pages/mine/work/works.dart';
|
||
import 'gradientButton.dart';
|
||
import 'work_utils.dart';
|
||
|
||
class WorkEditor extends StatefulWidget {
|
||
final String? initialImage;
|
||
final String? initialTitle;
|
||
final String? initialContent;
|
||
final String? workId;
|
||
final AssetEntity? selectedAsset;
|
||
final bool isEditMode;
|
||
final bool isNetworkImage;
|
||
final bool isFromDrafts;
|
||
|
||
const WorkEditor({
|
||
super.key,
|
||
this.initialImage,
|
||
this.initialTitle,
|
||
this.initialContent,
|
||
this.workId,
|
||
this.selectedAsset,
|
||
this.isEditMode = false,
|
||
this.isNetworkImage = false,
|
||
this.isFromDrafts = false,
|
||
});
|
||
|
||
@override
|
||
State<WorkEditor> createState() => _WorkEditorState();
|
||
}
|
||
|
||
class _WorkEditorState extends State<WorkEditor> {
|
||
final TextEditingController titleController = TextEditingController();
|
||
final TextEditingController contentController = TextEditingController();
|
||
AssetEntity? currentAsset;
|
||
String? currentImage;
|
||
VideoPlayerController? videoController;
|
||
|
||
int _titleLength = 0;
|
||
int _contentLength = 0;
|
||
|
||
File? _cachedFile;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
titleController.text = widget.initialTitle ?? '';
|
||
contentController.text = widget.initialContent ?? '';
|
||
currentAsset = widget.selectedAsset;
|
||
currentImage = widget.initialImage;
|
||
|
||
_titleLength = titleController.text.length;
|
||
_contentLength = contentController.text.length;
|
||
|
||
titleController.addListener(() {
|
||
setState(() {
|
||
_titleLength = titleController.text.length;
|
||
});
|
||
});
|
||
|
||
contentController.addListener(() {
|
||
setState(() {
|
||
_contentLength = contentController.text.length;
|
||
});
|
||
});
|
||
|
||
if (currentAsset?.type == AssetType.video) {
|
||
_initializeVideoPlayer();
|
||
}
|
||
}
|
||
|
||
@override
|
||
void didUpdateWidget(WorkEditor oldWidget) {
|
||
super.didUpdateWidget(oldWidget);
|
||
|
||
// 如果selectedAsset改变了,更新currentAsset
|
||
if (widget.selectedAsset != oldWidget.selectedAsset && widget.selectedAsset != null) {
|
||
// 清除旧的缓存
|
||
_cachedFile = null;
|
||
setState(() {
|
||
currentAsset = widget.selectedAsset;
|
||
if (widget.selectedAsset!.type == AssetType.video) {
|
||
videoController?.dispose();
|
||
_initializeVideoPlayer();
|
||
}
|
||
});
|
||
// 预加载新的文件
|
||
_loadAssetFile();
|
||
}
|
||
}
|
||
|
||
// 添加加载资产文件的辅助方法
|
||
Future<void> _loadAssetFile() async {
|
||
if (currentAsset != null) {
|
||
_cachedFile = await currentAsset!.file;
|
||
if (mounted) {
|
||
setState(() {});
|
||
}
|
||
}
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
// 清理缓存
|
||
_cachedFile = null;
|
||
titleController.dispose();
|
||
contentController.dispose();
|
||
videoController?.dispose();
|
||
super.dispose();
|
||
}
|
||
Future<void> _initializeVideoPlayer() async {
|
||
if (currentAsset == null) return;
|
||
|
||
final file = await currentAsset!.file;
|
||
if (file != null) {
|
||
videoController = VideoPlayerController.file(file)
|
||
..initialize().then((_) {
|
||
setState(() {});
|
||
});
|
||
}
|
||
}
|
||
|
||
void _handleImageTap() async {
|
||
if (currentAsset != null) {
|
||
Navigator.push(
|
||
context,
|
||
MaterialPageRoute(
|
||
builder: (context) => FullscreenImagePage(
|
||
asset: currentAsset!,
|
||
onReplace: (newAsset) async {
|
||
// 清除旧的缓存
|
||
_cachedFile = null;
|
||
setState(() {
|
||
currentAsset = newAsset;
|
||
if (newAsset.type == AssetType.video) {
|
||
videoController?.dispose();
|
||
_initializeVideoPlayer();
|
||
}
|
||
});
|
||
// 预加载新的文件
|
||
_cachedFile = await newAsset.file;
|
||
},
|
||
),
|
||
),
|
||
);
|
||
} else {
|
||
_pickAsset();
|
||
}
|
||
}
|
||
|
||
Future<void> _pickAsset() async {
|
||
final List<AssetEntity>? result = await AssetPicker.pickAssets(
|
||
context,
|
||
pickerConfig: AssetPickerConfig(
|
||
maxAssets: 1,
|
||
themeColor: const Color(0xff7CDBC8),
|
||
textDelegate: const AssetPickerTextDelegate(),
|
||
requestType: RequestType.image,
|
||
specialItemPosition: SpecialItemPosition.none,
|
||
specialItemBuilder: null,
|
||
shouldRevertGrid: false,
|
||
selectedAssets: currentAsset != null ? [currentAsset!] : null,
|
||
gridCount: 4,
|
||
pathThumbnailSize: const ThumbnailSize.square(80),
|
||
selectPredicate: (BuildContext context, AssetEntity asset, bool isSelected) {
|
||
return true;
|
||
},
|
||
),
|
||
);
|
||
|
||
if (result != null && result.isNotEmpty) {
|
||
// 清除旧的缓存
|
||
_cachedFile = null;
|
||
setState(() {
|
||
currentAsset = result.first;
|
||
if (result.first.type == AssetType.video) {
|
||
videoController?.dispose();
|
||
_initializeVideoPlayer();
|
||
}
|
||
});
|
||
// 预加载新的文件
|
||
_cachedFile = await result.first.file;
|
||
}
|
||
}
|
||
|
||
void _showErrorMessage(String error) {
|
||
if (!mounted) return;
|
||
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text(
|
||
'保存失败: $error',
|
||
textAlign: TextAlign.center,
|
||
),
|
||
behavior: SnackBarBehavior.floating,
|
||
backgroundColor: const Color.fromRGBO(255, 0, 0, 0.9),
|
||
duration: const Duration(seconds: 2),
|
||
),
|
||
);
|
||
}
|
||
|
||
void _showSuccessMessage(String message) {
|
||
if (!mounted) return;
|
||
|
||
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
SnackBar(
|
||
content: Text(
|
||
message,
|
||
textAlign: TextAlign.center,
|
||
),
|
||
behavior: SnackBarBehavior.floating,
|
||
backgroundColor: const Color.fromRGBO(43, 46, 61, 0.9),
|
||
duration: const Duration(seconds: 2),
|
||
),
|
||
);
|
||
}
|
||
|
||
Future<void> _handleSaveAndNavigate(String status) async {
|
||
if (!mounted) return;
|
||
|
||
// print('开始保存操作');
|
||
// print('isEditMode: ${widget.isEditMode}');
|
||
// print('isFromDrafts: ${widget.isFromDrafts}');
|
||
// print('status: $status');
|
||
|
||
try {
|
||
final content = _getContent();
|
||
// print('准备保存的内容: $content');
|
||
|
||
if (widget.isEditMode) {
|
||
// print('执行编辑模式保存');
|
||
await WorkUtils.handleEditSave(context, content, status);
|
||
} else {
|
||
// print('执行创建模式保存');
|
||
await WorkUtils.handleNewSave(context, content, status);
|
||
}
|
||
|
||
if (!mounted) return;
|
||
// print('开始处理导航');
|
||
await _handleNavigation(status);
|
||
|
||
} catch (e) {
|
||
// print('保存过程出错: $e');
|
||
if (!mounted) return;
|
||
_showErrorMessage(e.toString());
|
||
}
|
||
}
|
||
|
||
Future<void> _handleNavigation(String status) async {
|
||
if (!mounted) return;
|
||
// print('进入_handleNavigation');
|
||
// print('isEditMode: ${widget.isEditMode}');
|
||
// print('isFromDrafts: ${widget.isFromDrafts}');
|
||
// print('status: $status');
|
||
|
||
if (!widget.isEditMode) {
|
||
// print('执行新建模式导航');
|
||
await _handleNewWorkNavigation(status);
|
||
} else if (widget.isFromDrafts) {
|
||
// print('执行草稿箱编辑模式导航');
|
||
await _handleDraftsEditNavigation(status);
|
||
} else {
|
||
// print('执行作品集编辑模式导航');
|
||
await _handleWorksEditNavigation(status);
|
||
}
|
||
}
|
||
|
||
Future<void> _handleNewWorkNavigation(String status) async {
|
||
if (!mounted) return;
|
||
|
||
if (status == "1") {
|
||
// 发布 - 跳转到首页 tab1
|
||
await Navigator.pushNamedAndRemoveUntil(
|
||
context,
|
||
'/tabs',
|
||
(route) => false,
|
||
arguments: {'initialIndex': 1},
|
||
);
|
||
_showSuccessMessage('作品已创建成功,快去发布吧');
|
||
} else {
|
||
// 存草稿 - 跳转到首页 tab0
|
||
await Navigator.pushNamedAndRemoveUntil(
|
||
context,
|
||
'/tabs',
|
||
(route) => false,
|
||
arguments: {'initialIndex': 0},
|
||
);
|
||
_showSuccessMessage('作品已存入草稿箱');
|
||
}
|
||
}
|
||
|
||
Future<void> _handleDraftsEditNavigation(String status) async {
|
||
if (!mounted) return;
|
||
|
||
if (status == "1") {
|
||
// 如果是发布,跳转到首页 tab1(发布界面)
|
||
await Navigator.pushNamedAndRemoveUntil(
|
||
context,
|
||
'/tabs',
|
||
(route) => false,
|
||
arguments: {'initialIndex': 1},
|
||
);
|
||
if (!mounted) return;
|
||
_showSuccessMessage('作品已发布成功');
|
||
} else {
|
||
// 如果是存草稿,直接返回上一级
|
||
Navigator.of(context).pop(true);
|
||
// if (!mounted) return;
|
||
// _showSuccessMessage('作品已更新到草稿箱');
|
||
}
|
||
}
|
||
|
||
Future<void> _handleWorksEditNavigation(String status) async {
|
||
if (!mounted) return;
|
||
|
||
if (status == "1") {
|
||
// 如果是发布,跳转到首页 tab1(发布界面)
|
||
await Navigator.pushNamedAndRemoveUntil(
|
||
context,
|
||
'/tabs',
|
||
(route) => false,
|
||
arguments: {'initialIndex': 1},
|
||
);
|
||
} else {
|
||
// 如果是存草稿,先跳转到草稿箱页面,然后在草稿箱页面返回时跳转到我的页面
|
||
await Navigator.pushNamed(
|
||
context,
|
||
'/drafts',
|
||
).then((_) {
|
||
Navigator.pushNamedAndRemoveUntil(
|
||
context,
|
||
'/tabs',
|
||
(route) => false,
|
||
arguments: {'initialIndex': 4},
|
||
);
|
||
});
|
||
}
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Column(
|
||
children: [
|
||
Expanded(
|
||
child: SingleChildScrollView(
|
||
child: Padding(
|
||
padding: const EdgeInsets.all(16.0),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
GestureDetector(
|
||
onTap: _handleImageTap,
|
||
child: _buildMediaPreview(),
|
||
),
|
||
const SizedBox(height: 16),
|
||
_buildTextField(
|
||
controller: titleController,
|
||
hintText: '填写标题',
|
||
isTitle: true,
|
||
),
|
||
const SizedBox(height: 16),
|
||
_buildTextField(
|
||
controller: contentController,
|
||
hintText: '添加正文',
|
||
isTitle: false,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
Container(
|
||
color: const Color(0xff0C0C16),
|
||
padding: EdgeInsets.only(
|
||
bottom: MediaQuery.of(context).padding.bottom + 20, // 适配底部安全区
|
||
left: 16,
|
||
right: 16,
|
||
top: 10,
|
||
),
|
||
child: _buildActionButtons(),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildMediaPreview() {
|
||
return ClipRRect(
|
||
borderRadius: BorderRadius.circular(8),
|
||
child: Container(
|
||
width: 80,
|
||
height: 80,
|
||
decoration: BoxDecoration(
|
||
color: Colors.grey[300],
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: AspectRatio( // 使用 AspectRatio 固定宽高比为 1:1
|
||
aspectRatio: 1,
|
||
child: _buildMediaContent(),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildMediaContent() {
|
||
if (widget.isNetworkImage && currentImage != null) {
|
||
return GestureDetector(
|
||
onTap: () => _handleNetworkImageTap(),
|
||
child: FittedBox( // 使用 FittedBox 控制子组件的缩放行为
|
||
fit: BoxFit.cover,
|
||
clipBehavior: Clip.hardEdge,
|
||
child: Image.network(
|
||
currentImage!,
|
||
fit: BoxFit.cover,
|
||
errorBuilder: (context, error, stackTrace) {
|
||
return const Center(child: Text('加载失败'));
|
||
},
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
if (currentAsset != null) {
|
||
// 使用缓存的文件
|
||
if (_cachedFile != null) {
|
||
if (currentAsset!.type == AssetType.video) {
|
||
return videoController != null && videoController!.value.isInitialized
|
||
? AspectRatio(
|
||
aspectRatio: videoController!.value.aspectRatio,
|
||
child: VideoPlayer(videoController!),
|
||
)
|
||
: const Center(child: CircularProgressIndicator());
|
||
}
|
||
return Image.file(_cachedFile!, fit: BoxFit.cover);
|
||
}
|
||
|
||
return FutureBuilder<File?>(
|
||
future: currentAsset!.file,
|
||
builder: (context, snapshot) {
|
||
if (snapshot.connectionState == ConnectionState.done &&
|
||
snapshot.hasData) {
|
||
// 缓存文件
|
||
_cachedFile = snapshot.data;
|
||
if (currentAsset!.type == AssetType.video) {
|
||
return videoController != null &&
|
||
videoController!.value.isInitialized
|
||
? AspectRatio(
|
||
aspectRatio: videoController!.value.aspectRatio,
|
||
child: VideoPlayer(videoController!),
|
||
)
|
||
: const Center(child: CircularProgressIndicator());
|
||
}
|
||
return Image.file(snapshot.data!, fit: BoxFit.cover);
|
||
}
|
||
return const Center(child: CircularProgressIndicator());
|
||
},
|
||
);
|
||
}
|
||
// 无图片时显示添加图片图标
|
||
return const Center(
|
||
child: Icon(
|
||
Icons.add_circle_outline,
|
||
color: Color(0xFFC1C1C1),
|
||
size: 32,
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildTextField({
|
||
required TextEditingController controller,
|
||
required String hintText,
|
||
bool isTitle = false,
|
||
}) {
|
||
return Container(
|
||
child: Stack(
|
||
children: [
|
||
TextField(
|
||
controller: controller,
|
||
maxLength: isTitle ? 15 : 500,
|
||
maxLines: isTitle ? 1 : 10,
|
||
textAlign: isTitle ? TextAlign.left : TextAlign.justify, // 添加两端对齐
|
||
style: TextStyle(
|
||
color: Colors.white,
|
||
fontSize: isTitle ? 16 : 14,
|
||
height: isTitle ? 1.0 : 1.5, // 设置行距,标题1.0,正文1.5
|
||
),
|
||
cursorColor: const Color(0xFF7CDBC8), // 设置光标颜色
|
||
cursorWidth: 2, // 设置光标宽度
|
||
decoration: InputDecoration(
|
||
hintText: hintText,
|
||
hintStyle: TextStyle(
|
||
color: const Color(0xFFC1C1C1),
|
||
fontSize: isTitle ? 16 : 14,
|
||
height: isTitle ? 1.0 : 1.5, // 提示文本也使用相同的行距
|
||
),
|
||
border: InputBorder.none,
|
||
enabledBorder: InputBorder.none,
|
||
focusedBorder: InputBorder.none,
|
||
counterText: '', // 隐藏默认计数器
|
||
contentPadding: EdgeInsets.only( // 添加底部padding,为计数器留出空间
|
||
bottom: isTitle ? 8 : 24,
|
||
),
|
||
),
|
||
),
|
||
Positioned(
|
||
right: 8,
|
||
bottom: 8,
|
||
child: Text(
|
||
'${isTitle ? _titleLength : _contentLength}/${isTitle ? 15 : 500}',
|
||
style: const TextStyle(
|
||
color: Color(0xFFC1C1C1),
|
||
fontSize: 12,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
Widget _buildActionButtons() {
|
||
return Row(
|
||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||
children: [
|
||
_buildDraftButton(),
|
||
_buildPublishButton(),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildDraftButton() {
|
||
return Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: [
|
||
IconButton(
|
||
icon: Image.asset(
|
||
'images/save_drafts.png',
|
||
width: 31,
|
||
height: 31,
|
||
),
|
||
onPressed: () => _handleSaveAndNavigate("0"),
|
||
),
|
||
Transform.translate(
|
||
offset: const Offset(0, -8),
|
||
child: const Text(
|
||
"存草稿",
|
||
style: TextStyle(
|
||
fontSize: 10,
|
||
color: Color(0xFFC1C1C1),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildPublishButton() {
|
||
return GradientButton(
|
||
width: 240,
|
||
height: 41,
|
||
text: '保存并发布',
|
||
onPressed: () => _handleSaveAndNavigate("1"),
|
||
);
|
||
}
|
||
|
||
Map<String, dynamic> _getContent() {
|
||
if (widget.isEditMode) {
|
||
// 编辑模式
|
||
return {
|
||
"workId": widget.workId,
|
||
"workName": titleController.text,
|
||
"detail": contentController.text,
|
||
"workAddress": currentImage,
|
||
};
|
||
} else {
|
||
// 创建模式
|
||
if (currentAsset == null) {
|
||
// 如果未选择图片,提示用户
|
||
throw "请先选择一张图片";
|
||
}
|
||
return {
|
||
"workname": titleController.text,
|
||
"detail": contentController.text,
|
||
"asset": currentAsset,
|
||
};
|
||
}
|
||
}
|
||
|
||
void _handleNetworkImageTap() async {
|
||
final List<AssetEntity>? result = await AssetPicker.pickAssets(
|
||
context,
|
||
pickerConfig: const AssetPickerConfig(maxAssets: 1 ,textDelegate: AssetPickerTextDelegate(), ),
|
||
|
||
);
|
||
|
||
if (result != null && result.isNotEmpty) {
|
||
setState(() {
|
||
currentAsset = result.first;
|
||
currentImage = null;
|
||
});
|
||
}
|
||
}
|
||
} |