|
|
|
@ -1,3 +1,4 @@ |
|
|
|
import 'dart:convert'; |
|
|
|
import 'dart:io'; |
|
|
|
|
|
|
|
import 'package:common_utils/common_utils.dart'; |
|
|
|
@ -13,6 +14,7 @@ import 'package:share_plus/share_plus.dart'; |
|
|
|
|
|
|
|
import 'package:webview_flutter/webview_flutter.dart'; |
|
|
|
|
|
|
|
import '../../../../core/utils/logger.dart'; |
|
|
|
import '../../../../data/services/network/dio_manager.dart'; |
|
|
|
import '../../controllers/meeting_details_controller.dart'; |
|
|
|
import '../../controllers/mind_map_controller.dart'; |
|
|
|
@ -37,6 +39,15 @@ import 'second_level_bottom_sheet.dart'; |
|
|
|
/// - 复制/导出/思维导图功能需要 tasktype >= 5 且 tasktype < 10001 |
|
|
|
/// ============================================================================ |
|
|
|
|
|
|
|
/// 思维导图操作类型常量 |
|
|
|
class _MindMapActionType { |
|
|
|
/// 保存到本地相册 |
|
|
|
static const int save = 1; |
|
|
|
|
|
|
|
/// 分享给他人 |
|
|
|
static const int share = 2; |
|
|
|
} |
|
|
|
|
|
|
|
/// 分享底部弹窗 - 有状态组件 |
|
|
|
/// 使用StatefulWidget以便在build方法中多次获取主题状态 |
|
|
|
class ShareBottomSheet extends StatefulWidget { |
|
|
|
@ -73,6 +84,44 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> { |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
/// 是否有转写权限(tasktype >= 3 且 tasktype < 10002) |
|
|
|
bool get _hasTranscriptPermission { |
|
|
|
final tasktype = _controller.meetingDetails.value.tasktype; |
|
|
|
return tasktype >= 3 && tasktype < 10002; |
|
|
|
} |
|
|
|
|
|
|
|
/// 是否有总结权限(tasktype >= 5 且 tasktype < 10001) |
|
|
|
bool get _hasSummaryPermission { |
|
|
|
final tasktype = _controller.meetingDetails.value.tasktype; |
|
|
|
return tasktype >= 5 && tasktype < 10001; |
|
|
|
} |
|
|
|
|
|
|
|
/// 清理文件名中的非法字符 |
|
|
|
/// 过滤 / \ : * ? " < > | 等文件系统不允许的字符 |
|
|
|
String _sanitizeFileName(String name) { |
|
|
|
return name.replaceAll(RegExp(r'[/\\:*?"<>|]'), '_'); |
|
|
|
} |
|
|
|
|
|
|
|
/// 处理思维导图操作(保存/分享) |
|
|
|
/// [actionType] 操作类型,使用 [_MindMapActionType] 常量 |
|
|
|
void _handleMindMapAction(int actionType) { |
|
|
|
Get.back(); |
|
|
|
final mindMapController = Get.find<MindMapController>(); |
|
|
|
mindMapController.imageType = actionType; |
|
|
|
if (mindMapController.isWebViewReady) { |
|
|
|
mindMapController.webViewController.runJavaScript('captureImage()'); |
|
|
|
} else { |
|
|
|
mindMapController.webViewController.setNavigationDelegate( |
|
|
|
NavigationDelegate( |
|
|
|
onPageFinished: (String url) { |
|
|
|
mindMapController.isWebViewReady = true; |
|
|
|
mindMapController.webViewController.runJavaScript('captureImage()'); |
|
|
|
}, |
|
|
|
), |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// ========================================================================== |
|
|
|
/// 构建UI界面 |
|
|
|
/// ========================================================================== |
|
|
|
@ -192,8 +241,9 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> { |
|
|
|
'shareSuccess'.tr); // 对应中文:分享完成 |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
EasyLoading.showToast('shareFailed'.tr.replaceAll( |
|
|
|
'{error}', e.toString())); // 对应中文:分享失败 |
|
|
|
Logger.error('分享链接失败', e); // 技术日志 |
|
|
|
EasyLoading.showToast( |
|
|
|
'operationFailedPleaseRetry'.tr); // 对应中文:操作失败,请重试 |
|
|
|
} |
|
|
|
}, |
|
|
|
), |
|
|
|
@ -210,9 +260,7 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> { |
|
|
|
// 4. 调用 _copy() 方法复制到剪贴板 |
|
|
|
// 数据格式:说话人 + 时间戳 + 内容 |
|
|
|
_shareItem(Icons.copy, 'copyTranscript'.tr, // 对应中文:复制转写 |
|
|
|
onTap: _controller.meetingDetails.value.tasktype >= 3 && |
|
|
|
_controller.meetingDetails.value.tasktype < |
|
|
|
10002 |
|
|
|
onTap: _hasTranscriptPermission |
|
|
|
? () { |
|
|
|
Get.back(); |
|
|
|
_copy(_getAllText()); |
|
|
|
@ -228,9 +276,7 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> { |
|
|
|
// 4. 调用 _copy() 方法复制到剪贴板 |
|
|
|
_shareItem(Icons.copy, 'copySummary'.tr, // 对应中文:复制总结 |
|
|
|
isBorder: false, |
|
|
|
onTap: _controller.meetingDetails.value.tasktype >= 5 && |
|
|
|
_controller.meetingDetails.value.tasktype < |
|
|
|
10001 |
|
|
|
onTap: _hasSummaryPermission |
|
|
|
? () { |
|
|
|
Get.back(); |
|
|
|
_copy(_markdownToPlainText( |
|
|
|
@ -267,16 +313,17 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> { |
|
|
|
// 6. 调用 _share() 方法分享文件 |
|
|
|
_shareItem( |
|
|
|
Icons.message, 'exportTranscript'.tr, // 对应中文:导出转写 |
|
|
|
onTap: _controller.meetingDetails.value.tasktype >= 3 && |
|
|
|
_controller.meetingDetails.value.tasktype < |
|
|
|
10002 |
|
|
|
onTap: _hasTranscriptPermission |
|
|
|
? () async { |
|
|
|
Get.back(); |
|
|
|
final tempDir = await getTemporaryDirectory(); |
|
|
|
final safeTitle = _sanitizeFileName( |
|
|
|
_controller.meetingData.value.title); |
|
|
|
final file = File( |
|
|
|
'${tempDir.path}/${_controller.meetingData.value.title}_speaker.txt'); |
|
|
|
'${tempDir.path}/${safeTitle}_speaker.txt'); |
|
|
|
String allText = _getAllText(); |
|
|
|
await file.writeAsString(allText); |
|
|
|
await file.writeAsString(allText, |
|
|
|
encoding: utf8); |
|
|
|
_share(file.path, |
|
|
|
'exportTranscript'.tr); // 对应中文:分享会议转写 |
|
|
|
} |
|
|
|
@ -292,9 +339,7 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> { |
|
|
|
// - TXT: 创建临时文件,写入纯文本内容,调用 _share() 分享 |
|
|
|
// - PDF: 调用 _exportMarkdownPdf() 方法生成PDF |
|
|
|
_shareItem(Icons.article, 'exportSummary'.tr, // 对应中文:导出总结 |
|
|
|
onTap: _controller.meetingDetails.value.tasktype >= 5 && |
|
|
|
_controller.meetingDetails.value.tasktype < |
|
|
|
10001 |
|
|
|
onTap: _hasSummaryPermission |
|
|
|
? () async { |
|
|
|
Get.back(); |
|
|
|
Get.bottomSheet( |
|
|
|
@ -308,11 +353,15 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> { |
|
|
|
// 导出TXT格式 |
|
|
|
final tempDir = |
|
|
|
await getTemporaryDirectory(); |
|
|
|
final safeTitle = _sanitizeFileName( |
|
|
|
_controller |
|
|
|
.meetingData.value.title); |
|
|
|
final file = File( |
|
|
|
'${tempDir.path}/${_controller.meetingData.value.title}_summary.txt'); |
|
|
|
'${tempDir.path}/${safeTitle}_summary.txt'); |
|
|
|
await file.writeAsString( |
|
|
|
_markdownToPlainText(_controller |
|
|
|
.meetingDetails.value.summary)); |
|
|
|
.meetingDetails.value.summary), |
|
|
|
encoding: utf8); |
|
|
|
_share(file.path, |
|
|
|
'exportSummary'.tr); // 对应中文:分享会议总结 |
|
|
|
} else { |
|
|
|
@ -360,29 +409,8 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> { |
|
|
|
_shareItem( |
|
|
|
Icons.save_alt, |
|
|
|
'saveMindMap'.tr, |
|
|
|
onTap: _controller.meetingDetails.value.tasktype >= 5 && |
|
|
|
_controller.meetingDetails.value.tasktype < 10001 |
|
|
|
? () { |
|
|
|
Get.back(); |
|
|
|
final mindMapController = |
|
|
|
Get.find<MindMapController>(); |
|
|
|
mindMapController.imageType = 1; // 1=保存到本地 |
|
|
|
if (mindMapController.isWebViewReady) { |
|
|
|
mindMapController.webViewController |
|
|
|
.runJavaScript('captureImage()'); |
|
|
|
} else { |
|
|
|
mindMapController.webViewController |
|
|
|
.setNavigationDelegate( |
|
|
|
NavigationDelegate( |
|
|
|
onPageFinished: (String url) { |
|
|
|
mindMapController.isWebViewReady = true; |
|
|
|
mindMapController.webViewController |
|
|
|
.runJavaScript('captureImage()'); |
|
|
|
}, |
|
|
|
), |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
onTap: _hasSummaryPermission |
|
|
|
? () => _handleMindMapAction(_MindMapActionType.save) |
|
|
|
: null, |
|
|
|
), |
|
|
|
// 【核心功能模块8:分享思维导图】 |
|
|
|
@ -401,29 +429,8 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> { |
|
|
|
Icons.lan, |
|
|
|
'shareMindMap'.tr, // 对应中文:分享思维导图 |
|
|
|
isBorder: false, |
|
|
|
onTap: _controller.meetingDetails.value.tasktype >= 5 && |
|
|
|
_controller.meetingDetails.value.tasktype < 10001 |
|
|
|
? () { |
|
|
|
Get.back(); |
|
|
|
final mindMapController = |
|
|
|
Get.find<MindMapController>(); |
|
|
|
mindMapController.imageType = 2; // 2=分享 |
|
|
|
if (mindMapController.isWebViewReady) { |
|
|
|
mindMapController.webViewController |
|
|
|
.runJavaScript('captureImage()'); |
|
|
|
} else { |
|
|
|
mindMapController.webViewController |
|
|
|
.setNavigationDelegate( |
|
|
|
NavigationDelegate( |
|
|
|
onPageFinished: (String url) { |
|
|
|
mindMapController.isWebViewReady = true; |
|
|
|
mindMapController.webViewController |
|
|
|
.runJavaScript('captureImage()'); |
|
|
|
}, |
|
|
|
), |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
onTap: _hasSummaryPermission |
|
|
|
? () => _handleMindMapAction(_MindMapActionType.share) |
|
|
|
: null, |
|
|
|
), |
|
|
|
]), |
|
|
|
@ -560,8 +567,8 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> { |
|
|
|
EasyLoading.showToast('shareSuccess'.tr); // 对应中文:分享完成! |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
EasyLoading.showToast( |
|
|
|
'shareFailed'.tr.replaceAll('{error}', e.toString())); // 对应中文:分享失败 |
|
|
|
Logger.error('分享文件失败: $filepath', e); // 技术日志 |
|
|
|
EasyLoading.showToast('operationFailedPleaseRetry'.tr); // 对应中文:操作失败,请重试 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -757,8 +764,8 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> { |
|
|
|
EasyLoading.show(status: 'exportingPDF'.tr); // 对应中文:正在导出PDF... |
|
|
|
try { |
|
|
|
final tempDir = await getTemporaryDirectory(); |
|
|
|
final file = File( |
|
|
|
'${tempDir.path}/${_controller.meetingData.value.title}_summary.pdf'); |
|
|
|
final safeTitle = _sanitizeFileName(_controller.meetingData.value.title); |
|
|
|
final file = File('${tempDir.path}/${safeTitle}_summary.pdf'); |
|
|
|
final List<pdf.Widget> markdownWidgets = |
|
|
|
await pdf.HTMLToPdf().convertMarkdown( |
|
|
|
_controller.meetingDetails.value.summary, |
|
|
|
@ -774,8 +781,8 @@ class _ShareBottomSheetState extends State<ShareBottomSheet> { |
|
|
|
await file.writeAsBytes(await markdownPdf.save()); |
|
|
|
await _share(file.path, 'exportSummary'.tr); // 对应中文:分享会议总结 |
|
|
|
} catch (e) { |
|
|
|
EasyLoading.showToast( |
|
|
|
'shareFailed'.tr.replaceAll('{error}', e.toString())); // 对应中文:分享失败 |
|
|
|
Logger.error('导出PDF失败', e); // 技术日志 |
|
|
|
EasyLoading.showToast('operationFailedPleaseRetry'.tr); // 对应中文:操作失败,请重试 |
|
|
|
} finally { |
|
|
|
EasyLoading.dismiss(); |
|
|
|
} |
|
|
|
|