Compare commits
No commits in common. '8f34143f954a317e61e3d1c22db79e9092a00164' and '38721a875293f7124ca22d87fdc4bc35a4720ff8' have entirely different histories.
8f34143f95
...
38721a8752
28 changed files with 228 additions and 956 deletions
@ -1,10 +0,0 @@ |
|||||||
import 'package:win_text_editor/shared/base/selectable_item.dart'; |
|
||||||
|
|
||||||
class CountResult implements SelectableItem { |
|
||||||
final String keyword; |
|
||||||
final int matchCount; |
|
||||||
@override |
|
||||||
bool isSelected; |
|
||||||
|
|
||||||
CountResult({required this.keyword, required this.matchCount, this.isSelected = false}); |
|
||||||
} |
|
@ -1,13 +0,0 @@ |
|||||||
import 'package:win_text_editor/shared/base/base_content_controller.dart'; |
|
||||||
|
|
||||||
class OutlineController extends BaseContentController { |
|
||||||
@override |
|
||||||
void onOpenFile(String filePath) { |
|
||||||
// TODO: implement onOpenFile |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
void onOpenFolder(String folderPath) { |
|
||||||
// TODO: implement onOpenFolder |
|
||||||
} |
|
||||||
} |
|
@ -1,226 +0,0 @@ |
|||||||
import 'dart:io'; |
|
||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:file_picker/file_picker.dart'; |
|
||||||
import 'package:path_provider/path_provider.dart'; |
|
||||||
import 'package:win_text_editor/framework/controllers/logger.dart'; |
|
||||||
import 'package:win_text_editor/modules/outline/models/outline_node.dart'; |
|
||||||
import 'package:win_text_editor/modules/outline/services/outline_service.dart'; |
|
||||||
|
|
||||||
class OutlineProvider with ChangeNotifier { |
|
||||||
List<OutlineNode> _fileNodes = []; |
|
||||||
bool _isLoading = false; |
|
||||||
String _searchQuery = ''; |
|
||||||
String? _currentRootPath; // 跟踪当前根路径 |
|
||||||
|
|
||||||
bool get isLoading => _isLoading; |
|
||||||
bool get hasRoot => _fileNodes.isNotEmpty && _fileNodes[0].isRoot; |
|
||||||
|
|
||||||
// 移除构造函数的_initOutlineTree调用 |
|
||||||
OutlineProvider(); |
|
||||||
|
|
||||||
String? get rootPath => _currentRootPath; |
|
||||||
|
|
||||||
// 新增方法:手动设置根路径 |
|
||||||
Future<void> setRootPath(String path) async { |
|
||||||
_currentRootPath = path; |
|
||||||
await _loadRootDirectory(); |
|
||||||
} |
|
||||||
|
|
||||||
List<OutlineNode> get fileNodes => |
|
||||||
_searchQuery.isEmpty ? _fileNodes : _fileNodes.where((node) => _filterNode(node)).toList(); |
|
||||||
|
|
||||||
bool _filterNode(OutlineNode node) { |
|
||||||
if (node.name.toLowerCase().contains(_searchQuery.toLowerCase())) { |
|
||||||
return true; |
|
||||||
} |
|
||||||
return node.children.any(_filterNode); |
|
||||||
} |
|
||||||
|
|
||||||
void searchOutlines(String query) { |
|
||||||
_searchQuery = query; |
|
||||||
notifyListeners(); |
|
||||||
} |
|
||||||
|
|
||||||
void toggleExpand(OutlineNode node) { |
|
||||||
node.isExpanded = !node.isExpanded; |
|
||||||
notifyListeners(); |
|
||||||
} |
|
||||||
|
|
||||||
Future<void> pickAndOpenOutline() async { |
|
||||||
final result = await FilePicker.platform.pickFiles(); |
|
||||||
if (result != null && result.files.single.path != null) { |
|
||||||
// 这里需要与编辑器提供者交互来打开文件 |
|
||||||
Logger().info('Outline selected: ${result.files.single.path}'); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
Future<void> loadDirectory(String path) async { |
|
||||||
_isLoading = true; |
|
||||||
notifyListeners(); |
|
||||||
|
|
||||||
try { |
|
||||||
final directory = Directory(path); |
|
||||||
final displayName = await OutlineService.getModuleDisplayName(directory.path); |
|
||||||
final rootNode = OutlineNode( |
|
||||||
name: displayName ?? directory.path.split(Platform.pathSeparator).last, |
|
||||||
path: directory.path, |
|
||||||
isDirectory: true, |
|
||||||
isRoot: true, |
|
||||||
children: await OutlineService.buildOutlineTree(directory.path), |
|
||||||
); |
|
||||||
_fileNodes = [rootNode]; |
|
||||||
} catch (e) { |
|
||||||
Logger().error('Error loading directory: $e'); |
|
||||||
_fileNodes = []; |
|
||||||
} |
|
||||||
|
|
||||||
_isLoading = false; |
|
||||||
notifyListeners(); |
|
||||||
} |
|
||||||
|
|
||||||
Future<void> _loadRootDirectory() async { |
|
||||||
if (_currentRootPath == null) return; |
|
||||||
|
|
||||||
_isLoading = true; |
|
||||||
notifyListeners(); |
|
||||||
|
|
||||||
try { |
|
||||||
final displayName = await OutlineService.getModuleDisplayName(_currentRootPath!); |
|
||||||
_fileNodes = [ |
|
||||||
OutlineNode( |
|
||||||
name: displayName ?? _currentRootPath!.split(Platform.pathSeparator).last, |
|
||||||
path: _currentRootPath!, |
|
||||||
isDirectory: true, |
|
||||||
isRoot: true, |
|
||||||
depth: 0, // 根节点深度为0 |
|
||||||
children: [], // 初始为空,不加载内容 |
|
||||||
), |
|
||||||
]; |
|
||||||
} catch (e) { |
|
||||||
Logger().error('Error loading root directory: $e'); |
|
||||||
_fileNodes = []; |
|
||||||
} |
|
||||||
|
|
||||||
_isLoading = false; |
|
||||||
notifyListeners(); |
|
||||||
} |
|
||||||
|
|
||||||
Future<void> loadRootDirectory(String path) async { |
|
||||||
_isLoading = true; |
|
||||||
notifyListeners(); |
|
||||||
|
|
||||||
try { |
|
||||||
final displayName = await OutlineService.getModuleDisplayName(path); |
|
||||||
_fileNodes = [ |
|
||||||
OutlineNode( |
|
||||||
name: displayName ?? path.split(Platform.pathSeparator).last, |
|
||||||
path: path, |
|
||||||
isDirectory: true, |
|
||||||
isRoot: true, |
|
||||||
children: [], // 初始为空 |
|
||||||
), |
|
||||||
]; |
|
||||||
} catch (e) { |
|
||||||
Logger().error('Error loading root: $e'); |
|
||||||
_fileNodes = []; |
|
||||||
} |
|
||||||
|
|
||||||
_isLoading = false; |
|
||||||
notifyListeners(); |
|
||||||
} |
|
||||||
|
|
||||||
Future<void> toggleDirectory(OutlineNode dirNode) async { |
|
||||||
if (dirNode.children.isEmpty) { |
|
||||||
// 首次点击:加载内容 |
|
||||||
_isLoading = true; |
|
||||||
notifyListeners(); |
|
||||||
|
|
||||||
try { |
|
||||||
dirNode.children = await OutlineService.listDirectory(dirNode.path); |
|
||||||
dirNode.isExpanded = true; |
|
||||||
} catch (e) { |
|
||||||
Logger().error('Error loading directory: $e'); |
|
||||||
dirNode.children = []; |
|
||||||
} |
|
||||||
|
|
||||||
_isLoading = false; |
|
||||||
notifyListeners(); |
|
||||||
} else { |
|
||||||
// 已加载过:只切换展开状态 |
|
||||||
dirNode.isExpanded = !dirNode.isExpanded; |
|
||||||
notifyListeners(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
Future<void> loadDirectoryContents(OutlineNode dirNode) async { |
|
||||||
if (dirNode.children.isNotEmpty && dirNode.isExpanded) { |
|
||||||
// 如果已经加载过且是展开状态,只切换展开状态 |
|
||||||
dirNode.isExpanded = !dirNode.isExpanded; |
|
||||||
notifyListeners(); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
_isLoading = true; |
|
||||||
notifyListeners(); |
|
||||||
|
|
||||||
try { |
|
||||||
final contents = await OutlineService.listDirectory(dirNode.path, parentDepth: dirNode.depth); |
|
||||||
|
|
||||||
final updatedNode = dirNode.copyWith(children: contents, isExpanded: true); |
|
||||||
|
|
||||||
_replaceNodeInTree(dirNode, updatedNode); |
|
||||||
} catch (e) { |
|
||||||
Logger().error('Error loading directory contents: $e'); |
|
||||||
final updatedNode = dirNode.copyWith(children: []); |
|
||||||
_replaceNodeInTree(dirNode, updatedNode); |
|
||||||
} |
|
||||||
|
|
||||||
_isLoading = false; |
|
||||||
notifyListeners(); |
|
||||||
} |
|
||||||
|
|
||||||
void _replaceNodeInTree(OutlineNode oldNode, OutlineNode newNode) { |
|
||||||
for (int i = 0; i < _fileNodes.length; i++) { |
|
||||||
if (_fileNodes[i] == oldNode) { |
|
||||||
_fileNodes[i] = newNode; |
|
||||||
return; |
|
||||||
} |
|
||||||
_replaceNodeInChildren(_fileNodes[i], oldNode, newNode); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void _replaceNodeInChildren(OutlineNode parent, OutlineNode oldNode, OutlineNode newNode) { |
|
||||||
for (int i = 0; i < parent.children.length; i++) { |
|
||||||
if (parent.children[i] == oldNode) { |
|
||||||
parent.children[i] = newNode; |
|
||||||
return; |
|
||||||
} |
|
||||||
_replaceNodeInChildren(parent.children[i], oldNode, newNode); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
Future<void> refreshOutlineTree({bool loadContent = false}) async { |
|
||||||
_isLoading = true; |
|
||||||
notifyListeners(); |
|
||||||
|
|
||||||
try { |
|
||||||
final rootDir = await getApplicationDocumentsDirectory(); |
|
||||||
_fileNodes = [ |
|
||||||
OutlineNode( |
|
||||||
name: rootDir.path.split(Platform.pathSeparator).last, |
|
||||||
path: rootDir.path, |
|
||||||
isDirectory: true, |
|
||||||
isRoot: true, |
|
||||||
// 初始不加载内容 |
|
||||||
children: loadContent ? await OutlineService.listDirectory(rootDir.path) : [], |
|
||||||
), |
|
||||||
]; |
|
||||||
} catch (e) { |
|
||||||
Logger().error('Error refreshing file tree: $e'); |
|
||||||
_fileNodes = []; |
|
||||||
} |
|
||||||
|
|
||||||
_isLoading = false; |
|
||||||
notifyListeners(); |
|
||||||
} |
|
||||||
} |
|
@ -1,137 +0,0 @@ |
|||||||
import 'package:flutter/foundation.dart'; |
|
||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:win_text_editor/shared/components/tree_view.dart'; |
|
||||||
|
|
||||||
class OutlineNode implements TreeNode { |
|
||||||
@override |
|
||||||
final String name; |
|
||||||
final String path; |
|
||||||
@override |
|
||||||
final bool isDirectory; |
|
||||||
final bool isRoot; |
|
||||||
@override |
|
||||||
final int depth; |
|
||||||
@override |
|
||||||
List<OutlineNode> children; |
|
||||||
@override |
|
||||||
bool isExpanded; |
|
||||||
|
|
||||||
OutlineNode({ |
|
||||||
required this.name, |
|
||||||
required this.path, |
|
||||||
required this.isDirectory, |
|
||||||
this.isRoot = false, |
|
||||||
this.depth = 0, |
|
||||||
this.isExpanded = false, |
|
||||||
List<OutlineNode>? children, |
|
||||||
}) : children = children ?? []; |
|
||||||
|
|
||||||
@override |
|
||||||
String get id => path; |
|
||||||
|
|
||||||
// 获取文件图标数据 |
|
||||||
@override |
|
||||||
IconData get iconData { |
|
||||||
if (isDirectory) { |
|
||||||
return Icons.folder; |
|
||||||
} |
|
||||||
|
|
||||||
final ext = name.split('.').last.toLowerCase(); |
|
||||||
|
|
||||||
// 常见文件类型图标映射 |
|
||||||
switch (ext) { |
|
||||||
case 'pdf': |
|
||||||
return Icons.picture_as_pdf; |
|
||||||
case 'doc': |
|
||||||
case 'docx': |
|
||||||
return Icons.article; |
|
||||||
case 'xls': |
|
||||||
case 'xlsx': |
|
||||||
return Icons.table_chart; |
|
||||||
case 'ppt': |
|
||||||
case 'pptx': |
|
||||||
return Icons.slideshow; |
|
||||||
case 'txt': |
|
||||||
return Icons.text_snippet; |
|
||||||
case 'dart': |
|
||||||
return Icons.code; |
|
||||||
case 'js': |
|
||||||
return Icons.javascript; |
|
||||||
case 'java': |
|
||||||
return Icons.coffee; |
|
||||||
case 'py': |
|
||||||
return Icons.data_object; |
|
||||||
case 'html': |
|
||||||
return Icons.html; |
|
||||||
case 'css': |
|
||||||
return Icons.css; |
|
||||||
case 'json': |
|
||||||
return Icons.data_array; |
|
||||||
case 'png': |
|
||||||
case 'jpg': |
|
||||||
case 'jpeg': |
|
||||||
case 'gif': |
|
||||||
return Icons.image; |
|
||||||
case 'mp3': |
|
||||||
case 'wav': |
|
||||||
return Icons.audiotrack; |
|
||||||
case 'mp4': |
|
||||||
case 'avi': |
|
||||||
case 'mov': |
|
||||||
return Icons.videocam; |
|
||||||
case 'zip': |
|
||||||
case 'rar': |
|
||||||
case '7z': |
|
||||||
return Icons.archive; |
|
||||||
default: |
|
||||||
return Icons.insert_drive_file; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// 获取构建好的图标组件 |
|
||||||
Widget get icon { |
|
||||||
return Icon(iconData, color: isDirectory ? Colors.amber[700] : Colors.blue); |
|
||||||
} |
|
||||||
|
|
||||||
OutlineNode copyWith({ |
|
||||||
String? name, |
|
||||||
String? path, |
|
||||||
bool? isDirectory, |
|
||||||
bool? isExpanded, |
|
||||||
bool? isRoot, |
|
||||||
List<OutlineNode>? children, |
|
||||||
int? depth, |
|
||||||
}) { |
|
||||||
return OutlineNode( |
|
||||||
name: name ?? this.name, |
|
||||||
path: path ?? this.path, |
|
||||||
isDirectory: isDirectory ?? this.isDirectory, |
|
||||||
isExpanded: isExpanded ?? this.isExpanded, |
|
||||||
isRoot: isRoot ?? this.isRoot, |
|
||||||
children: children ?? this.children, |
|
||||||
depth: depth ?? this.depth, |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
bool operator ==(Object other) { |
|
||||||
if (identical(this, other)) return true; |
|
||||||
return other is OutlineNode && |
|
||||||
other.name == name && |
|
||||||
other.path == path && |
|
||||||
other.isDirectory == isDirectory && |
|
||||||
other.isRoot == isRoot && |
|
||||||
listEquals(other.children, children) && |
|
||||||
other.isExpanded == isExpanded; |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
int get hashCode { |
|
||||||
return name.hashCode ^ |
|
||||||
path.hashCode ^ |
|
||||||
isDirectory.hashCode ^ |
|
||||||
isRoot.hashCode ^ |
|
||||||
children.hashCode ^ |
|
||||||
isExpanded.hashCode; |
|
||||||
} |
|
||||||
} |
|
@ -1,128 +0,0 @@ |
|||||||
import 'dart:io'; |
|
||||||
import 'package:win_text_editor/framework/controllers/logger.dart'; |
|
||||||
import 'package:win_text_editor/framework/services/fast_xml_parser.dart'; |
|
||||||
import 'package:win_text_editor/modules/outline/models/outline_node.dart'; |
|
||||||
import 'package:xml/xml.dart'; |
|
||||||
|
|
||||||
class OutlineService { |
|
||||||
static const _specialExtensions = [ |
|
||||||
'.uftfunction', |
|
||||||
'.uftservice', |
|
||||||
'.uftatomfunction', |
|
||||||
'.uftatomservice', |
|
||||||
'.uftfactorfunction', |
|
||||||
'.uftfactorservice', |
|
||||||
]; |
|
||||||
static const Map<String, String> _uftFloders = { |
|
||||||
'.settings': '项目设置', |
|
||||||
'metadata': '元数据', |
|
||||||
'tools': '工具资源', |
|
||||||
'uftatom': 'UFT原子', |
|
||||||
'uftbusiness': 'UFT业务逻辑', |
|
||||||
'uftfactor': 'UFT因子', |
|
||||||
'uftstructure': 'UFT对象', |
|
||||||
}; |
|
||||||
static const _hiddenFiles = ['.classpath', '.project', '.respath', 'project.xml', 'module.xml']; |
|
||||||
|
|
||||||
static Future<String?> getSpecialFileName(String filePath) async { |
|
||||||
final extension = filePath.substring(filePath.lastIndexOf('.')); |
|
||||||
if (!_specialExtensions.contains(extension)) { |
|
||||||
return null; |
|
||||||
} |
|
||||||
|
|
||||||
try { |
|
||||||
final result = await FastXmlParser.parse(filePath); |
|
||||||
return ('[${result['objectId']}]${result['chineseName']}'); |
|
||||||
} catch (e) { |
|
||||||
Logger().debug('Error reading special file: $e'); |
|
||||||
} |
|
||||||
return null; |
|
||||||
} |
|
||||||
|
|
||||||
/// 延时加载目录内容(不递归) |
|
||||||
static Future<List<OutlineNode>> listDirectory(String path, {int parentDepth = 0}) async { |
|
||||||
final dir = Directory(path); |
|
||||||
final List<FileSystemEntity> entities = await dir.list().toList(); |
|
||||||
final List<OutlineNode> nodes = []; |
|
||||||
// final stopwatch = Stopwatch()..start(); |
|
||||||
|
|
||||||
for (final entity in entities) { |
|
||||||
final pathName = entity.path.split(Platform.pathSeparator).last; |
|
||||||
if (_hiddenFiles.contains(pathName)) continue; |
|
||||||
|
|
||||||
final isDirectory = await FileSystemEntity.isDirectory(entity.path); |
|
||||||
final displayName = |
|
||||||
isDirectory |
|
||||||
? await getModuleDisplayName(entity.path) |
|
||||||
: await getSpecialFileName(entity.path); |
|
||||||
|
|
||||||
nodes.add( |
|
||||||
OutlineNode( |
|
||||||
name: displayName ?? pathName, |
|
||||||
path: entity.path, |
|
||||||
isDirectory: isDirectory, |
|
||||||
depth: parentDepth + 1, |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
// stopwatch.stop(); |
|
||||||
// Logger().debug('执行耗时: ${stopwatch.elapsedMilliseconds} 毫秒 (ms)'); |
|
||||||
|
|
||||||
return nodes; |
|
||||||
} |
|
||||||
|
|
||||||
static Future<String?> getModuleDisplayName(String dirPath) async { |
|
||||||
try { |
|
||||||
final floderName = dirPath.split(Platform.pathSeparator).last; |
|
||||||
if (_uftFloders.containsKey(floderName)) return _uftFloders[floderName]; |
|
||||||
|
|
||||||
final moduleFile = File('$dirPath${Platform.pathSeparator}module.xml'); |
|
||||||
if (await moduleFile.exists()) { |
|
||||||
final content = await moduleFile.readAsString(); |
|
||||||
final xmlDoc = XmlDocument.parse(content); |
|
||||||
final infoNode = xmlDoc.findAllElements('info').firstOrNull; |
|
||||||
return infoNode?.getAttribute('cname'); |
|
||||||
} |
|
||||||
} catch (e) { |
|
||||||
Logger().debug('Error reading module.xml: $e'); |
|
||||||
} |
|
||||||
return null; |
|
||||||
} |
|
||||||
|
|
||||||
/// 递归构建完整文件树(原方法保留备用) |
|
||||||
static Future<List<OutlineNode>> buildOutlineTree(String rootPath) async { |
|
||||||
final rootDirectory = Directory(rootPath); |
|
||||||
final List<OutlineNode> nodes = []; |
|
||||||
|
|
||||||
if (await rootDirectory.exists()) { |
|
||||||
final entities = rootDirectory.listSync(); |
|
||||||
|
|
||||||
for (final entity in entities) { |
|
||||||
final pathName = entity.path.split(Platform.pathSeparator).last; |
|
||||||
if (_hiddenFiles.contains(pathName)) continue; |
|
||||||
final node = OutlineNode( |
|
||||||
name: pathName, |
|
||||||
path: entity.path, |
|
||||||
isDirectory: entity is Directory, |
|
||||||
); |
|
||||||
|
|
||||||
if (entity is Directory) { |
|
||||||
node.children.addAll(await buildOutlineTree(entity.path)); |
|
||||||
} |
|
||||||
|
|
||||||
nodes.add(node); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
return nodes; |
|
||||||
} |
|
||||||
|
|
||||||
static Future<String> readFile(String filePath) async { |
|
||||||
return await File(filePath).readAsString(); |
|
||||||
} |
|
||||||
|
|
||||||
static Future<void> writeFile(String filePath, String content) async { |
|
||||||
await File(filePath).writeAsString(content); |
|
||||||
} |
|
||||||
} |
|
@ -1,85 +0,0 @@ |
|||||||
import 'dart:math'; |
|
||||||
|
|
||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:provider/provider.dart'; |
|
||||||
import 'package:win_text_editor/modules/outline/controllers/outline_provider.dart'; |
|
||||||
import 'package:win_text_editor/modules/outline/models/outline_node.dart'; |
|
||||||
import 'package:win_text_editor/shared/components/tree_view.dart'; |
|
||||||
|
|
||||||
class OutlineExplorer extends StatefulWidget { |
|
||||||
final Function(String)? onFileDoubleTap; |
|
||||||
final Function(String)? onFolderDoubleTap; |
|
||||||
|
|
||||||
const OutlineExplorer({super.key, this.onFileDoubleTap, this.onFolderDoubleTap}); |
|
||||||
|
|
||||||
@override |
|
||||||
State<OutlineExplorer> createState() => _OutlineExplorerState(); |
|
||||||
} |
|
||||||
|
|
||||||
class _OutlineExplorerState extends State<OutlineExplorer> { |
|
||||||
final ScrollController _scrollController = ScrollController(); // 添加ScrollController |
|
||||||
|
|
||||||
@override |
|
||||||
void dispose() { |
|
||||||
_scrollController.dispose(); // 记得销毁controller |
|
||||||
super.dispose(); |
|
||||||
} |
|
||||||
|
|
||||||
// 动态计算总宽度(根据层级深度调整) |
|
||||||
double calculateTotalWidth(BuildContext context, OutlineProvider fileProvider) { |
|
||||||
final maxDepth = _getMaxDepth(fileProvider.fileNodes); |
|
||||||
return maxDepth * 60 + MediaQuery.of(context).size.width * 0.2; |
|
||||||
} |
|
||||||
|
|
||||||
int _getMaxDepth(List<OutlineNode> nodes) { |
|
||||||
int maxDepth = 0; |
|
||||||
for (final node in nodes) { |
|
||||||
if (node.isDirectory && node.isExpanded) { |
|
||||||
maxDepth = max(maxDepth, _getMaxDepth(node.children) + 1); |
|
||||||
} |
|
||||||
} |
|
||||||
return maxDepth; |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(BuildContext context) { |
|
||||||
final fileProvider = Provider.of<OutlineProvider>(context); |
|
||||||
|
|
||||||
return Scrollbar( |
|
||||||
controller: _scrollController, // 指定controller |
|
||||||
thumbVisibility: true, |
|
||||||
child: SingleChildScrollView( |
|
||||||
controller: _scrollController, // 使用同一个controller |
|
||||||
scrollDirection: Axis.horizontal, |
|
||||||
child: Container( |
|
||||||
color: Colors.white, |
|
||||||
child: SizedBox( |
|
||||||
width: calculateTotalWidth(context, fileProvider), |
|
||||||
child: TreeView( |
|
||||||
nodes: fileProvider.fileNodes, |
|
||||||
config: const TreeViewConfig(showIcons: true, lazyLoad: true), |
|
||||||
onNodeTap: (node) => _handleNodeTap(context, node as OutlineNode), |
|
||||||
onNodeDoubleTap: (node) => _handleNodeDoubleTap(node as OutlineNode), |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
Future<void> _handleNodeTap(BuildContext context, OutlineNode node) async { |
|
||||||
final fileProvider = Provider.of<OutlineProvider>(context, listen: false); |
|
||||||
if (node.isDirectory) { |
|
||||||
await fileProvider.loadDirectoryContents(node); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
void _handleNodeDoubleTap(TreeNode node) { |
|
||||||
final fileNode = node as OutlineNode; |
|
||||||
if (fileNode.isDirectory && widget.onFolderDoubleTap != null) { |
|
||||||
widget.onFolderDoubleTap!(fileNode.path); |
|
||||||
} else if (!fileNode.isDirectory && widget.onFileDoubleTap != null) { |
|
||||||
widget.onFileDoubleTap!(fileNode.path); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,73 +0,0 @@ |
|||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:provider/provider.dart'; |
|
||||||
import 'package:win_text_editor/framework/controllers/tab_items_controller.dart'; |
|
||||||
import 'package:win_text_editor/modules/outline/controllers/outline_controller.dart'; |
|
||||||
import 'package:win_text_editor/modules/outline/controllers/outline_provider.dart'; // 新增导入 |
|
||||||
import 'package:win_text_editor/modules/outline/widgets/outline_explorer.dart'; |
|
||||||
|
|
||||||
class OutlineView extends StatefulWidget { |
|
||||||
final String tabId; |
|
||||||
const OutlineView({super.key, required this.tabId}); |
|
||||||
|
|
||||||
@override |
|
||||||
State<OutlineView> createState() => _OutlineViewState(); |
|
||||||
} |
|
||||||
|
|
||||||
class _OutlineViewState extends State<OutlineView> { |
|
||||||
late final OutlineController _controller; |
|
||||||
late final OutlineProvider _outlineProvider; // 新增OutlineProvider实例 |
|
||||||
bool _isControllerFromTabManager = false; |
|
||||||
|
|
||||||
get tabManager => Provider.of<TabItemsController>(context, listen: false); |
|
||||||
|
|
||||||
@override |
|
||||||
void initState() { |
|
||||||
super.initState(); |
|
||||||
|
|
||||||
_outlineProvider = OutlineProvider(); // 初始化OutlineProvider |
|
||||||
|
|
||||||
final controllerFromManager = tabManager.getController(widget.tabId); |
|
||||||
if (controllerFromManager != null) { |
|
||||||
_controller = controllerFromManager; |
|
||||||
_isControllerFromTabManager = true; |
|
||||||
} else { |
|
||||||
_controller = OutlineController(); |
|
||||||
_isControllerFromTabManager = false; |
|
||||||
tabManager.registerController(widget.tabId, _controller); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
void dispose() { |
|
||||||
if (!_isControllerFromTabManager) { |
|
||||||
_controller.dispose(); |
|
||||||
} |
|
||||||
_outlineProvider.dispose(); // 确保销毁provider |
|
||||||
super.dispose(); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(BuildContext context) { |
|
||||||
return ChangeNotifierProvider<OutlineProvider>.value( |
|
||||||
value: _outlineProvider, // 提供OutlineProvider |
|
||||||
child: Row( |
|
||||||
children: [ |
|
||||||
const VerticalDivider(width: 1), |
|
||||||
SizedBox( |
|
||||||
width: 300, |
|
||||||
child: OutlineExplorer( |
|
||||||
onFileDoubleTap: (path) { |
|
||||||
// 处理文件双击 |
|
||||||
}, |
|
||||||
onFolderDoubleTap: (path) { |
|
||||||
// 处理文件夹双击 |
|
||||||
}, |
|
||||||
), |
|
||||||
), |
|
||||||
const VerticalDivider(width: 1), |
|
||||||
const Expanded(child: Center(child: Text('demo'))), |
|
||||||
], |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue