From b06ecf481166aadf41115796cf4db7e09fde9879 Mon Sep 17 00:00:00 2001 From: hejl Date: Mon, 12 May 2025 14:15:38 +0800 Subject: [PATCH] =?UTF-8?q?=E8=83=BD=E5=A4=9F=E5=8A=A0=E8=BD=BD=E5=B0=8F?= =?UTF-8?q?=E4=BA=8E10M=E7=9A=84=E6=96=87=E4=BB=B6=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/app/providers/editor_provider.dart | 2 +- win_text_editor/lib/app/widgets/text_tab.dart | 87 ++++++++++++++----- 2 files changed, 66 insertions(+), 23 deletions(-) diff --git a/win_text_editor/lib/app/providers/editor_provider.dart b/win_text_editor/lib/app/providers/editor_provider.dart index 7488489..c861572 100644 --- a/win_text_editor/lib/app/providers/editor_provider.dart +++ b/win_text_editor/lib/app/providers/editor_provider.dart @@ -51,7 +51,7 @@ class EditorProvider with ChangeNotifier { if (name != null) { tab.fileName = name; } - Logger().debug("内容更新成功,文件:${tab.fileName}, ${tab.content.length}"); + // Logger().debug("内容更新成功,文件:${tab.fileName}, ${tab.content.length}"); notifyListeners(); } catch (e) { Logger().error("更新内容失败: ${e.toString()}", source: 'EditorProvider'); diff --git a/win_text_editor/lib/app/widgets/text_tab.dart b/win_text_editor/lib/app/widgets/text_tab.dart index 7e5676d..40544cb 100644 --- a/win_text_editor/lib/app/widgets/text_tab.dart +++ b/win_text_editor/lib/app/widgets/text_tab.dart @@ -1,11 +1,11 @@ +import 'dart:convert'; import 'dart:ui'; - +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:flutter/services.dart'; // 复制功能需要 +import 'package:flutter/services.dart'; import 'package:win_text_editor/app/providers/editor_provider.dart'; import 'package:file_picker/file_picker.dart'; -import 'dart:io'; class TextTab extends StatefulWidget { final String tabId; @@ -21,8 +21,9 @@ class _TextTabState extends State { late EditorProvider _provider; late FocusNode _focusNode; late ScrollController _scrollController; + bool _isLoading = false; + static const int maxFileSize = 10 * 1024 * 1024; // 10MB - @override @override void initState() { super.initState(); @@ -51,7 +52,7 @@ class _TextTabState extends State { void dispose() { _controller.dispose(); _focusNode.dispose(); - _scrollController.dispose(); // 添加这行 + _scrollController.dispose(); super.dispose(); } @@ -80,7 +81,7 @@ class _TextTabState extends State { IconButton( icon: const Icon(Icons.folder_open, size: 20), tooltip: '打开文件', - onPressed: () => _openFile(context), + onPressed: _isLoading ? null : () => _openFile(context), ), IconButton( icon: const Icon(Icons.content_copy, size: 20), @@ -93,6 +94,15 @@ class _TextTabState extends State { tooltip: '保存到文件', onPressed: tab.content.isEmpty ? null : () => _saveFile(context, tab.content), ), + if (_isLoading) + const Padding( + padding: EdgeInsets.only(left: 8), + child: SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ), ], ), ], @@ -105,10 +115,9 @@ class _TextTabState extends State { dragDevices: {PointerDeviceKind.touch, PointerDeviceKind.mouse}, ), child: SingleChildScrollView( - controller: _scrollController, // 添加滚动控制器 + controller: _scrollController, child: Stack( children: [ - // 实际可编辑的TextField TextField( controller: _controller, focusNode: _focusNode, @@ -121,7 +130,7 @@ class _TextTabState extends State { style: TextStyle( fontFamily: 'monospace', fontSize: 14, - color: Theme.of(context).textTheme.bodyLarge?.color, // 使用主题文本颜色 + color: Theme.of(context).textTheme.bodyLarge?.color, ), ), ], @@ -135,34 +144,74 @@ class _TextTabState extends State { Future _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 content = await file.readAsString(); + 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()); - // 更新provider和控制器 - _provider.updateContent(widget.tabId, content, result.files.first.name); - _controller.text = content; // 确保更新控制器 + 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 { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('这不是可读的文本文件'))); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('这不是可读的文本文件'))); + } } on FileSystemException catch (e) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('文件访问错误: ${e.message}'))); + 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); + } } } - // 复制到剪贴板 Future _copyToClipboard(BuildContext context, String content) async { await Clipboard.setData(ClipboardData(text: content)); if (context.mounted) { @@ -170,11 +219,8 @@ class _TextTabState extends State { } } - // 保存文件模拟功能 - // 保存文件功能 Future _saveFile(BuildContext context, String content) async { try { - // 让用户选择保存位置 String? outputPath = await FilePicker.platform.saveFile( dialogTitle: '保存文件', fileName: 'untitled.txt', @@ -186,9 +232,7 @@ class _TextTabState extends State { final file = File(outputPath); - // 检查文件是否存在 if (await file.exists()) { - // 如果存在,显示确认对话框 final shouldOverwrite = await showDialog( context: context, builder: @@ -211,7 +255,6 @@ class _TextTabState extends State { if (shouldOverwrite != true) return; } - // 写入文件 await file.writeAsString(content); if (context.mounted) {