|
|
|
@ -1,6 +1,7 @@
@@ -1,6 +1,7 @@
|
|
|
|
|
import 'dart:convert'; |
|
|
|
|
import 'dart:ui'; |
|
|
|
|
import 'dart:io'; |
|
|
|
|
import 'package:collection/collection.dart'; |
|
|
|
|
import 'package:flutter/material.dart'; |
|
|
|
|
import 'package:provider/provider.dart'; |
|
|
|
|
import 'package:flutter/services.dart'; |
|
|
|
@ -13,21 +14,22 @@ class TextTab extends StatefulWidget {
@@ -13,21 +14,22 @@ class TextTab extends StatefulWidget {
|
|
|
|
|
const TextTab({super.key, required this.tabId}); |
|
|
|
|
|
|
|
|
|
@override |
|
|
|
|
State<TextTab> createState() => _TextTabState(); |
|
|
|
|
State<TextTab> createState() => TextTabState(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
class _TextTabState extends State<TextTab> { |
|
|
|
|
class TextTabState extends State<TextTab> { |
|
|
|
|
late TextEditingController _controller; |
|
|
|
|
late EditorProvider _provider; |
|
|
|
|
late FocusNode _focusNode; |
|
|
|
|
late ScrollController _scrollController; |
|
|
|
|
bool _isLoading = false; |
|
|
|
|
static const int maxFileSize = 10 * 1024 * 1024; // 10MB |
|
|
|
|
static const int maxFileSize = 1024 * 1024; // 1MB |
|
|
|
|
|
|
|
|
|
@override |
|
|
|
|
void initState() { |
|
|
|
|
super.initState(); |
|
|
|
|
_provider = Provider.of<EditorProvider>(context, listen: false); |
|
|
|
|
_provider.registerTextTabController(widget.tabId, this); |
|
|
|
|
_controller = TextEditingController(text: _getCurrentContent()); |
|
|
|
|
_focusNode = FocusNode(); |
|
|
|
|
_scrollController = ScrollController(); |
|
|
|
@ -58,7 +60,13 @@ class _TextTabState extends State<TextTab> {
@@ -58,7 +60,13 @@ class _TextTabState extends State<TextTab> {
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
|
Widget build(BuildContext context) { |
|
|
|
|
final tab = _provider.tabs.firstWhere((t) => t.id == widget.tabId); |
|
|
|
|
final tab = _provider.tabs.firstWhereOrNull((t) => t.id == widget.tabId); |
|
|
|
|
if (tab == null) { |
|
|
|
|
return const Center(child: Text('选项卡不存在')); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
String fileNameText = |
|
|
|
|
tab.fileName != null && tab.fileName!.isNotEmpty ? '${tab.fileName},' : ''; |
|
|
|
|
|
|
|
|
|
return Column( |
|
|
|
|
children: [ |
|
|
|
@ -73,7 +81,7 @@ class _TextTabState extends State<TextTab> {
@@ -73,7 +81,7 @@ class _TextTabState extends State<TextTab> {
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|
|
|
|
children: [ |
|
|
|
|
Text( |
|
|
|
|
'源文本${tab.content.isEmpty ? '' : ' (${tab.fileName!.isEmpty ? '' : '${tab.fileName},'}${tab.content.length}字符)'}', |
|
|
|
|
'源文本${tab.content.isEmpty ? '' : ' ($fileNameText${tab.content.length}字符)'}', |
|
|
|
|
style: const TextStyle(fontWeight: FontWeight.bold), |
|
|
|
|
), |
|
|
|
|
Row( |
|
|
|
@ -143,72 +151,9 @@ class _TextTabState extends State<TextTab> {
@@ -143,72 +151,9 @@ class _TextTabState extends State<TextTab> {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Future<void> _openFile(BuildContext context) async { |
|
|
|
|
try { |
|
|
|
|
setState(() => _isLoading = true); |
|
|
|
|
|
|
|
|
|
final result = await FilePicker.platform.pickFiles(type: FileType.any, allowMultiple: false); |
|
|
|
|
|
|
|
|
|
if (result != null && result.files.single.path != null) { |
|
|
|
|
final file = File(result.files.single.path!); |
|
|
|
|
final fileSize = await file.length(); |
|
|
|
|
|
|
|
|
|
// 检查文件大小 |
|
|
|
|
if (fileSize > maxFileSize) { |
|
|
|
|
if (context.mounted) { |
|
|
|
|
ScaffoldMessenger.of( |
|
|
|
|
context, |
|
|
|
|
).showSnackBar(const SnackBar(content: Text('文件过大(超过10MB),无法处理'))); |
|
|
|
|
} |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 清空当前内容 |
|
|
|
|
_provider.updateContent(widget.tabId, '', result.files.first.name); |
|
|
|
|
_controller.text = ''; |
|
|
|
|
|
|
|
|
|
// 逐行读取文件 |
|
|
|
|
final stream = file.openRead(); |
|
|
|
|
final lines = stream.transform(utf8.decoder).transform(const LineSplitter()); |
|
|
|
|
|
|
|
|
|
await for (final line in lines) { |
|
|
|
|
if (!mounted) break; // 如果组件已卸载,停止处理 |
|
|
|
|
|
|
|
|
|
setState(() { |
|
|
|
|
_controller.text += '$line\n'; |
|
|
|
|
_provider.updateContent(widget.tabId, _controller.text, result.files.first.name); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// 自动滚动到底部 |
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) { |
|
|
|
|
_scrollController.jumpTo(_scrollController.position.maxScrollExtent); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// 添加微小延迟,让用户能看到逐行加载效果 |
|
|
|
|
await Future.delayed(const Duration(milliseconds: 10)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (context.mounted) { |
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('已加载: ${file.path}'))); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} on FormatException { |
|
|
|
|
if (context.mounted) { |
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('这不是可读的文本文件'))); |
|
|
|
|
} |
|
|
|
|
} on FileSystemException catch (e) { |
|
|
|
|
if (context.mounted) { |
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('文件访问错误: ${e.message}'))); |
|
|
|
|
} |
|
|
|
|
} catch (e) { |
|
|
|
|
if (context.mounted) { |
|
|
|
|
ScaffoldMessenger.of( |
|
|
|
|
context, |
|
|
|
|
).showSnackBar(SnackBar(content: Text('读取失败: ${e.toString()}'))); |
|
|
|
|
} |
|
|
|
|
} finally { |
|
|
|
|
if (mounted) { |
|
|
|
|
setState(() => _isLoading = false); |
|
|
|
|
} |
|
|
|
|
final result = await FilePicker.platform.pickFiles(type: FileType.any, allowMultiple: false); |
|
|
|
|
if (result != null && result.files.single.path != null) { |
|
|
|
|
await loadFile(context, result.files.single.path!); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -272,4 +217,99 @@ class _TextTabState extends State<TextTab> {
@@ -272,4 +217,99 @@ class _TextTabState extends State<TextTab> {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 新增公共方法,处理文件加载逻辑 |
|
|
|
|
Future<void> loadFile(BuildContext context, String filePath) async { |
|
|
|
|
try { |
|
|
|
|
setState(() => _isLoading = true); |
|
|
|
|
final file = File(filePath); |
|
|
|
|
final fileSize = await file.length(); |
|
|
|
|
|
|
|
|
|
// 检查文件大小 |
|
|
|
|
if (fileSize > maxFileSize) { |
|
|
|
|
if (context.mounted) { |
|
|
|
|
ScaffoldMessenger.of( |
|
|
|
|
context, |
|
|
|
|
).showSnackBar(const SnackBar(content: Text('文件过大(超过1MB),无法处理'))); |
|
|
|
|
} |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 判断活动的文本编辑框中是否有内容 |
|
|
|
|
final activeTab = _provider.getTabById(widget.tabId); |
|
|
|
|
if (activeTab != null && activeTab.content.isNotEmpty) { |
|
|
|
|
final confirm = await showDialog<bool>( |
|
|
|
|
context: context, |
|
|
|
|
builder: |
|
|
|
|
(context) => AlertDialog( |
|
|
|
|
title: const Text('确认提示'), |
|
|
|
|
content: const Text('确认是否打开新的文件?打开后将覆盖当前内容'), |
|
|
|
|
actions: [ |
|
|
|
|
TextButton( |
|
|
|
|
onPressed: () => Navigator.pop(context, false), |
|
|
|
|
child: const Text('取消'), |
|
|
|
|
), |
|
|
|
|
TextButton( |
|
|
|
|
onPressed: () => Navigator.pop(context, true), |
|
|
|
|
child: const Text('确认'), |
|
|
|
|
), |
|
|
|
|
], |
|
|
|
|
), |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
if (confirm != true) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
final _fileName = file.path.split('\\').last; |
|
|
|
|
|
|
|
|
|
// 清空当前内容 |
|
|
|
|
_provider.updateContent(widget.tabId, '', _fileName); |
|
|
|
|
_controller.text = ''; |
|
|
|
|
|
|
|
|
|
// 逐行读取文件 |
|
|
|
|
final stream = file.openRead(); |
|
|
|
|
final lines = stream.transform(utf8.decoder).transform(const LineSplitter()); |
|
|
|
|
|
|
|
|
|
await for (final line in lines) { |
|
|
|
|
if (!mounted) break; // 如果组件已卸载,停止处理 |
|
|
|
|
|
|
|
|
|
setState(() { |
|
|
|
|
_controller.text += '$line\n'; |
|
|
|
|
_provider.updateContent(widget.tabId, _controller.text, _fileName); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// 自动滚动到底部 |
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) { |
|
|
|
|
_scrollController.jumpTo(_scrollController.position.maxScrollExtent); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// 添加微小延迟,让用户能看到逐行加载效果 |
|
|
|
|
await Future.delayed(const Duration(milliseconds: 10)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (context.mounted) { |
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('已加载: ${file.path}'))); |
|
|
|
|
} |
|
|
|
|
} on FormatException { |
|
|
|
|
if (context.mounted) { |
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('这不是可读的文本文件'))); |
|
|
|
|
} |
|
|
|
|
} on FileSystemException catch (e) { |
|
|
|
|
if (context.mounted) { |
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('文件访问错误: ${e.message}'))); |
|
|
|
|
} |
|
|
|
|
} catch (e) { |
|
|
|
|
if (context.mounted) { |
|
|
|
|
ScaffoldMessenger.of( |
|
|
|
|
context, |
|
|
|
|
).showSnackBar(SnackBar(content: Text('读取失败: ${e.toString()}'))); |
|
|
|
|
} |
|
|
|
|
} finally { |
|
|
|
|
if (mounted) { |
|
|
|
|
setState(() => _isLoading = false); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|