diff --git a/win_text_editor/.metadata b/win_text_editor/.metadata index 722e716..0de37da 100644 --- a/win_text_editor/.metadata +++ b/win_text_editor/.metadata @@ -15,21 +15,6 @@ migration: - platform: root create_revision: 09de023485e95e6d1225c2baa44b8feb85e0d45f base_revision: 09de023485e95e6d1225c2baa44b8feb85e0d45f - - platform: android - create_revision: 09de023485e95e6d1225c2baa44b8feb85e0d45f - base_revision: 09de023485e95e6d1225c2baa44b8feb85e0d45f - - platform: ios - create_revision: 09de023485e95e6d1225c2baa44b8feb85e0d45f - base_revision: 09de023485e95e6d1225c2baa44b8feb85e0d45f - - platform: linux - create_revision: 09de023485e95e6d1225c2baa44b8feb85e0d45f - base_revision: 09de023485e95e6d1225c2baa44b8feb85e0d45f - - platform: macos - create_revision: 09de023485e95e6d1225c2baa44b8feb85e0d45f - base_revision: 09de023485e95e6d1225c2baa44b8feb85e0d45f - - platform: web - create_revision: 09de023485e95e6d1225c2baa44b8feb85e0d45f - base_revision: 09de023485e95e6d1225c2baa44b8feb85e0d45f - platform: windows create_revision: 09de023485e95e6d1225c2baa44b8feb85e0d45f base_revision: 09de023485e95e6d1225c2baa44b8feb85e0d45f diff --git a/win_text_editor/devtools_options.yaml b/win_text_editor/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/win_text_editor/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/win_text_editor/lib/app/app.dart b/win_text_editor/lib/app/app.dart new file mode 100644 index 0000000..8f40530 --- /dev/null +++ b/win_text_editor/lib/app/app.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:win_text_editor/app/menus/app_menu.dart'; +import 'package:win_text_editor/app/providers/editor_provider.dart'; +import 'package:win_text_editor/app/providers/file_provider.dart'; +import 'package:win_text_editor/app/widgets/editor_pane.dart'; +import 'package:win_text_editor/app/widgets/file_explorer.dart'; + +class AppScaffold extends StatelessWidget { + const AppScaffold({super.key}); + + @override + Widget build(BuildContext context) { + return MultiProvider( + providers: [ + ChangeNotifierProvider(create: (_) => FileProvider()), + ChangeNotifierProvider(create: (_) => EditorProvider()), + ], + child: const Scaffold( + body: Column( + children: [ + // 菜单栏 + AppMenu(), + // 主内容区域 + Expanded( + child: _ResizablePanel(), + ), + ], + ), + ), + ); + } +} + +class _ResizablePanel extends StatefulWidget { + const _ResizablePanel(); + + @override + State<_ResizablePanel> createState() => _ResizablePanelState(); +} + +class _ResizablePanelState extends State<_ResizablePanel> { + double _leftWidth = 0.2; // 初始宽度20% + final double _minWidth = 100; // 最小宽度 + final double _maxWidth = 400; // 最大宽度 + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + return LayoutBuilder( + builder: (context, constraints) { + // 计算实际宽度 + final leftPanelWidth = (_leftWidth * screenWidth).clamp(_minWidth, _maxWidth); + + return Row( + children: [ + // 左侧文件资源管理器 + SizedBox( + width: leftPanelWidth, + child: const FileExplorer(), + ), + // 拖拽手柄 + GestureDetector( + behavior: HitTestBehavior.translucent, + onPanUpdate: (details) { + setState(() { + _leftWidth = ((leftPanelWidth + details.delta.dx) / screenWidth) + .clamp(_minWidth / screenWidth, _maxWidth / screenWidth); + }); + }, + child: MouseRegion( + cursor: SystemMouseCursors.resizeLeftRight, + child: Container( + width: 4, + color: Colors.grey[300], + ), + ), + ), + // 右侧编辑器区域 + Expanded( + child: const EditorPane(), + ), + ], + ); + }, + ); + } +} diff --git a/win_text_editor/lib/app/menus/app_menu.dart b/win_text_editor/lib/app/menus/app_menu.dart new file mode 100644 index 0000000..7c146f7 --- /dev/null +++ b/win_text_editor/lib/app/menus/app_menu.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:win_text_editor/app/menus/menu_constants.dart'; + +import 'menu_actions.dart'; + +class AppMenu extends StatelessWidget { + const AppMenu({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + height: 30, + color: Colors.grey[200], + child: Row( + children: [ + _buildMenuButton(context, '文件', _buildFileMenuItems()), + _buildMenuButton(context, '编辑', _buildEditMenuItems()), + _buildMenuButton(context, '窗口', _buildWindowMenuItems()), + _buildMenuButton(context, '帮助', _buildHelpMenuItems()), + ], + ), + ); + } + + Widget _buildMenuButton(BuildContext context, String label, List> items) { + return PopupMenuButton( + offset: const Offset(0, 30), + child: Padding(padding: const EdgeInsets.symmetric(horizontal: 12.0), child: Text(label)), + itemBuilder: (context) => items, + onSelected: (value) => MenuActions.handleMenuAction(value, context), + ); + } + + List> _buildFileMenuItems() { + return [ + const PopupMenuItem(value: MenuConstants.openFolder, child: Text('打开文件夹...')), + const PopupMenuItem(value: MenuConstants.save, child: Text('保存')), + const PopupMenuItem(value: MenuConstants.saveAs, child: Text('另存为...')), + const PopupMenuItem(value: MenuConstants.exit, child: Text('退出')), + ]; + } + + List> _buildEditMenuItems() { + return [ + const PopupMenuItem(value: MenuConstants.undo, child: Text('撤销')), + const PopupMenuItem(value: MenuConstants.redo, child: Text('重做')), + const PopupMenuItem(value: MenuConstants.cut, child: Text('剪切')), + const PopupMenuItem(value: MenuConstants.copy, child: Text('复制')), + const PopupMenuItem(value: MenuConstants.paste, child: Text('粘贴')), + ]; + } + + List> _buildWindowMenuItems() { + return [ + const PopupMenuItem(value: MenuConstants.minimize, child: Text('最小化')), + const PopupMenuItem(value: MenuConstants.maximize, child: Text('最大化')), + const PopupMenuItem(value: MenuConstants.close, child: Text('关闭')), + ]; + } + + List> _buildHelpMenuItems() { + return [ + const PopupMenuItem(value: MenuConstants.about, child: Text('关于')), + const PopupMenuItem(value: MenuConstants.help, child: Text('帮助')), + ]; + } +} diff --git a/win_text_editor/lib/app/menus/menu_actions.dart b/win_text_editor/lib/app/menus/menu_actions.dart new file mode 100644 index 0000000..dbaae43 --- /dev/null +++ b/win_text_editor/lib/app/menus/menu_actions.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:provider/provider.dart'; +import 'package:win_text_editor/app/menus/menu_constants.dart'; +import 'package:win_text_editor/app/providers/file_provider.dart'; +import 'dart:io'; + +class MenuActions { + static Future handleMenuAction(String value, BuildContext context) async { + switch (value) { + case MenuConstants.openFolder: + await _openFolder(context); + break; + case MenuConstants.exit: + _exitApplication(); + break; + // 其他菜单项可以在这里添加处理逻辑 + } + } + + static Future _openFolder(BuildContext context) async { + final fileProvider = Provider.of(context, listen: false); + final String? selectedDirectory = await FilePicker.platform.getDirectoryPath(); + + if (selectedDirectory != null) { + await fileProvider.loadDirectory(selectedDirectory); + } + } + + static void _exitApplication() { + exit(0); + } +} diff --git a/win_text_editor/lib/app/menus/menu_constants.dart b/win_text_editor/lib/app/menus/menu_constants.dart new file mode 100644 index 0000000..ba2c1ad --- /dev/null +++ b/win_text_editor/lib/app/menus/menu_constants.dart @@ -0,0 +1,29 @@ +class MenuConstants { + // 菜单项类型 + static const String fileMenu = 'file'; + static const String editMenu = 'edit'; + static const String windowMenu = 'window'; + static const String helpMenu = 'help'; + + // 文件菜单项 + static const String openFolder = 'open_folder'; + static const String save = 'save'; + static const String saveAs = 'save_as'; + static const String exit = 'exit'; + + // 编辑菜单项 + static const String undo = 'undo'; + static const String redo = 'redo'; + static const String cut = 'cut'; + static const String copy = 'copy'; + static const String paste = 'paste'; + + // 窗口菜单项 + static const String minimize = 'minimize'; + static const String maximize = 'maximize'; + static const String close = 'close'; + + // 帮助菜单项 + static const String about = 'about'; + static const String help = 'help'; +} diff --git a/win_text_editor/lib/app/models/editor_tab.dart b/win_text_editor/lib/app/models/editor_tab.dart new file mode 100644 index 0000000..a0b2525 --- /dev/null +++ b/win_text_editor/lib/app/models/editor_tab.dart @@ -0,0 +1,15 @@ +class EditorTab { + final String id; + final String title; + final String path; + String content; + final String fileType; + + EditorTab({ + required this.id, + required this.title, + required this.path, + required this.content, + required this.fileType, + }); +} diff --git a/win_text_editor/lib/app/models/file_node.dart b/win_text_editor/lib/app/models/file_node.dart new file mode 100644 index 0000000..c38032e --- /dev/null +++ b/win_text_editor/lib/app/models/file_node.dart @@ -0,0 +1,127 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +class FileNode { + final String name; + final String path; + final bool isDirectory; + final bool isRoot; + final int depth; + List children; + bool isExpanded; + + FileNode({ + required this.name, + required this.path, + required this.isDirectory, + this.isRoot = false, + this.depth = 0, + this.isExpanded = false, + List? children, + }) : children = children ?? []; + + // 获取文件图标数据 + 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); + } + + FileNode copyWith({ + String? name, + String? path, + bool? isDirectory, + bool? isRoot, + int? depth, // 添加depth参数 + List? children, + bool? isExpanded, + }) { + return FileNode( + name: name ?? this.name, + path: path ?? this.path, + isDirectory: isDirectory ?? this.isDirectory, + isRoot: isRoot ?? this.isRoot, + depth: depth ?? this.depth, // 保留原有depth或使用新值 + children: children ?? this.children, + isExpanded: isExpanded ?? this.isExpanded, + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is FileNode && + 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; + } +} diff --git a/win_text_editor/lib/app/providers/editor_provider.dart b/win_text_editor/lib/app/providers/editor_provider.dart new file mode 100644 index 0000000..4f7f8d1 --- /dev/null +++ b/win_text_editor/lib/app/providers/editor_provider.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:win_text_editor/app/models/editor_tab.dart'; +import 'package:win_text_editor/app/services/file_service.dart'; +import 'package:win_text_editor/app/services/syntax_service.dart'; + +class EditorProvider with ChangeNotifier { + final List _openTabs = []; + int _activeTabIndex = 0; + int _currentLayout = 0; // 0=平铺, 1=层叠, 2=单页 + + List get openTabs => _openTabs; + int get activeTabIndex => _activeTabIndex; + int get currentLayout => _currentLayout; + + Future openFile(String filePath) async { + try { + // 检查是否已经打开 + final existingIndex = _openTabs.indexWhere((tab) => tab.path == filePath); + if (existingIndex != -1) { + _activeTabIndex = existingIndex; + notifyListeners(); + return; + } + + // 读取文件内容 + final content = await FileService.readFile(filePath); + final fileName = filePath.split('/').last; + final fileType = SyntaxService.detectFileType(fileName); + + // 创建新标签页 + final newTab = EditorTab( + id: DateTime.now().millisecondsSinceEpoch.toString(), + title: fileName, + path: filePath, + content: content, + fileType: fileType, + ); + + _openTabs.add(newTab); + _activeTabIndex = _openTabs.length - 1; + notifyListeners(); + } catch (e) { + debugPrint('Error opening file: $e'); + } + } + + void closeTab(String tabId) { + final index = _openTabs.indexWhere((tab) => tab.id == tabId); + if (index != -1) { + _openTabs.removeAt(index); + if (_activeTabIndex >= index && _activeTabIndex > 0) { + _activeTabIndex--; + } + notifyListeners(); + } + } + + void setActiveTab(int index) { + if (index >= 0 && index < _openTabs.length) { + _activeTabIndex = index; + notifyListeners(); + } + } + + Future saveFile(String tabId) async { + final index = _openTabs.indexWhere((tab) => tab.id == tabId); + if (index != -1) { + final tab = _openTabs[index]; + await FileService.writeFile(tab.path, tab.content); + // 可以添加保存成功的提示 + } + } + + Future copyToClipboard(String tabId) async { + final index = _openTabs.indexWhere((tab) => tab.id == tabId); + if (index != -1) { + final tab = _openTabs[index]; + // 这里需要实现复制到剪贴板的逻辑 + // 可以使用 clipboard package + } + } + + void changeLayout(int layout) { + if (layout >= 0 && layout <= 2) { + _currentLayout = layout; + notifyListeners(); + } + } + + void updateTabContent(String tabId, String newContent) { + final index = _openTabs.indexWhere((tab) => tab.id == tabId); + if (index != -1) { + _openTabs[index].content = newContent; + notifyListeners(); + } + } +} diff --git a/win_text_editor/lib/app/providers/file_provider.dart b/win_text_editor/lib/app/providers/file_provider.dart new file mode 100644 index 0000000..f4546f4 --- /dev/null +++ b/win_text_editor/lib/app/providers/file_provider.dart @@ -0,0 +1,227 @@ +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/app/models/file_node.dart'; +import 'package:win_text_editor/app/services/file_service.dart'; + +class FileProvider with ChangeNotifier { + List _fileNodes = []; + bool _isLoading = false; + String _searchQuery = ''; + String? _currentRootPath; // 跟踪当前根路径 + + bool get isLoading => _isLoading; + bool get hasRoot => _fileNodes.isNotEmpty && _fileNodes[0].isRoot; + + // 移除构造函数的_initFileTree调用 + FileProvider(); + + // 新增方法:手动设置根路径 + Future setRootPath(String path) async { + _currentRootPath = path; + await _loadRootDirectory(); + } + + List get fileNodes => + _searchQuery.isEmpty ? _fileNodes : _fileNodes.where((node) => _filterNode(node)).toList(); + + bool _filterNode(FileNode node) { + if (node.name.toLowerCase().contains(_searchQuery.toLowerCase())) { + return true; + } + return node.children.any(_filterNode); + } + + void searchFiles(String query) { + _searchQuery = query; + notifyListeners(); + } + + void toggleExpand(FileNode node) { + node.isExpanded = !node.isExpanded; + notifyListeners(); + } + + Future pickAndOpenFile() async { + final result = await FilePicker.platform.pickFiles(); + if (result != null && result.files.single.path != null) { + // 这里需要与编辑器提供者交互来打开文件 + debugPrint('File selected: ${result.files.single.path}'); + } + } + + Future loadDirectory(String path) async { + _isLoading = true; + notifyListeners(); + + try { + final directory = Directory(path); + final rootNode = FileNode( + name: directory.path.split(Platform.pathSeparator).last, + path: directory.path, + isDirectory: true, + isRoot: true, // 添加根节点标识 + children: await FileService.buildFileTree(directory.path), + ); + _fileNodes = [rootNode]; // 将根节点作为唯一顶层节点 + } catch (e) { + debugPrint('Error loading directory: $e'); + _fileNodes = []; + } + + _isLoading = false; + notifyListeners(); + } + + Future _loadRootDirectory() async { + if (_currentRootPath == null) return; + + _isLoading = true; + notifyListeners(); + + try { + _fileNodes = [ + FileNode( + name: _currentRootPath!.split(Platform.pathSeparator).last, + path: _currentRootPath!, + isDirectory: true, + isRoot: true, + depth: 0, // 根节点深度为0 + children: [], // 初始为空,不加载内容 + ), + ]; + } catch (e) { + debugPrint('Error loading root directory: $e'); + _fileNodes = []; + } + + _isLoading = false; + notifyListeners(); + } + + Future loadRootDirectory(String path) async { + _isLoading = true; + notifyListeners(); + + try { + _fileNodes = [ + FileNode( + name: path.split(Platform.pathSeparator).last, + path: path, + isDirectory: true, + isRoot: true, + children: [], // 初始为空 + ), + ]; + } catch (e) { + debugPrint('Error loading root: $e'); + _fileNodes = []; + } + + _isLoading = false; + notifyListeners(); + } + + Future toggleDirectory(FileNode dirNode) async { + if (dirNode.children.isEmpty) { + // 首次点击:加载内容 + _isLoading = true; + notifyListeners(); + + try { + dirNode.children = await FileService.listDirectory(dirNode.path); + dirNode.isExpanded = true; + } catch (e) { + debugPrint('Error loading dir: $e'); + dirNode.children = []; + } + + _isLoading = false; + notifyListeners(); + } else { + // 已加载过:只切换展开状态 + dirNode.isExpanded = !dirNode.isExpanded; + notifyListeners(); + } + } + + Future loadDirectoryContents(FileNode dirNode) async { + if (dirNode.children.isNotEmpty && dirNode.isExpanded) { + // 如果已经加载过且是展开状态,只切换展开状态 + dirNode.isExpanded = !dirNode.isExpanded; + notifyListeners(); + return; + } + + _isLoading = true; + notifyListeners(); + + try { + final contents = await FileService.listDirectory( + dirNode.path, + parentDepth: dirNode.depth, // 确保传递父节点深度 + ); + + final updatedNode = dirNode.copyWith( + children: contents, + isExpanded: true, + // 不需要设置 depth,因为 copyWith 会自动保留原值 + ); + + _replaceNodeInTree(dirNode, updatedNode); + } catch (e) { + debugPrint('Error loading directory contents: $e'); + final updatedNode = dirNode.copyWith(children: []); + _replaceNodeInTree(dirNode, updatedNode); + } + + _isLoading = false; + notifyListeners(); + } + + void _replaceNodeInTree(FileNode oldNode, FileNode newNode) { + for (int i = 0; i < _fileNodes.length; i++) { + if (_fileNodes[i] == oldNode) { + _fileNodes[i] = newNode; + return; + } + _replaceNodeInChildren(_fileNodes[i], oldNode, newNode); + } + } + + void _replaceNodeInChildren(FileNode parent, FileNode oldNode, FileNode 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 refreshFileTree({bool loadContent = false}) async { + _isLoading = true; + notifyListeners(); + + try { + final rootDir = await getApplicationDocumentsDirectory(); + _fileNodes = [ + FileNode( + name: rootDir.path.split(Platform.pathSeparator).last, + path: rootDir.path, + isDirectory: true, + isRoot: true, + // 初始不加载内容 + children: loadContent ? await FileService.listDirectory(rootDir.path) : [], + ), + ]; + } catch (e) { + debugPrint('Error refreshing file tree: $e'); + _fileNodes = []; + } + + _isLoading = false; + notifyListeners(); + } +} diff --git a/win_text_editor/lib/app/services/file_service.dart b/win_text_editor/lib/app/services/file_service.dart new file mode 100644 index 0000000..dbce149 --- /dev/null +++ b/win_text_editor/lib/app/services/file_service.dart @@ -0,0 +1,60 @@ +import 'dart:io'; +import 'package:win_text_editor/app/models/file_node.dart'; + +class FileService { + /// 延时加载目录内容(不递归) + static Future> listDirectory(String path, {int parentDepth = 0}) async { + final directory = Directory(path); + final List nodes = []; + + if (await directory.exists()) { + final entities = directory.listSync(); + for (final entity in entities) { + nodes.add( + FileNode( + name: entity.path.split(Platform.pathSeparator).last, + path: entity.path, + isDirectory: entity is Directory, + depth: parentDepth + 1, // 关键修复:基于父节点深度+1 + children: [], + ), + ); + } + } + return nodes; + } + + /// 递归构建完整文件树(原方法保留备用) + static Future> buildFileTree(String rootPath) async { + final rootDirectory = Directory(rootPath); + final List nodes = []; + + if (await rootDirectory.exists()) { + final entities = rootDirectory.listSync(); + + for (final entity in entities) { + final node = FileNode( + name: entity.path.split(Platform.pathSeparator).last, + path: entity.path, + isDirectory: entity is Directory, + ); + + if (entity is Directory) { + node.children.addAll(await buildFileTree(entity.path)); + } + + nodes.add(node); + } + } + + return nodes; + } + + static Future readFile(String filePath) async { + return await File(filePath).readAsString(); + } + + static Future writeFile(String filePath, String content) async { + await File(filePath).writeAsString(content); + } +} diff --git a/win_text_editor/lib/app/services/syntax_service.dart b/win_text_editor/lib/app/services/syntax_service.dart new file mode 100644 index 0000000..dc097eb --- /dev/null +++ b/win_text_editor/lib/app/services/syntax_service.dart @@ -0,0 +1,31 @@ +class SyntaxService { + static String detectFileType(String fileName) { + final extension = fileName.split('.').last.toLowerCase(); + + switch (extension) { + case 'dart': + return 'dart'; + case 'java': + return 'java'; + case 'js': + return 'javascript'; + case 'py': + return 'python'; + case 'html': + return 'html'; + case 'css': + return 'css'; + case 'json': + return 'json'; + case 'xml': + return 'xml'; + case 'md': + return 'markdown'; + case 'yaml': + case 'yml': + return 'yaml'; + default: + return 'text'; + } + } +} diff --git a/win_text_editor/lib/app/widgets/editor_pane.dart b/win_text_editor/lib/app/widgets/editor_pane.dart new file mode 100644 index 0000000..adf607d --- /dev/null +++ b/win_text_editor/lib/app/widgets/editor_pane.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:win_text_editor/app/providers/editor_provider.dart'; +import 'package:win_text_editor/app/widgets/tab_bar.dart'; + +import '../models/editor_tab.dart'; + +class EditorPane extends StatelessWidget { + const EditorPane({super.key}); + + @override + Widget build(BuildContext context) { + final editorProvider = Provider.of(context); + + return Column( + children: [ + const EditorTabBar(), + Expanded( + child: IndexedStack( + index: editorProvider.currentLayout, + children: [ + // 平铺布局 + GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2), + itemCount: editorProvider.openTabs.length, + itemBuilder: (context, index) { + return EditorTabContent(tab: editorProvider.openTabs[index]); + }, + ), + // 层叠布局 + Stack( + children: + editorProvider.openTabs.map((tab) { + return Positioned( + top: 20.0 * editorProvider.openTabs.indexOf(tab), + left: 20.0 * editorProvider.openTabs.indexOf(tab), + right: 20.0 * editorProvider.openTabs.indexOf(tab), + bottom: 20.0 * editorProvider.openTabs.indexOf(tab), + child: EditorTabContent(tab: tab), + ); + }).toList(), + ), + // 单页布局 + if (editorProvider.openTabs.isNotEmpty) + EditorTabContent(tab: editorProvider.openTabs[editorProvider.activeTabIndex]), + ], + ), + ), + ], + ); + } +} + +class EditorTabContent extends StatelessWidget { + final EditorTab tab; + + const EditorTabContent({super.key, required this.tab}); + + @override + Widget build(BuildContext context) { + final editorProvider = Provider.of(context, listen: false); + + return Card( + margin: const EdgeInsets.all(8.0), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Text(tab.title), + const Spacer(), + IconButton( + icon: const Icon(Icons.content_copy), + onPressed: () => editorProvider.copyToClipboard(tab.id), + ), + IconButton( + icon: const Icon(Icons.save), + onPressed: () => editorProvider.saveFile(tab.id), + ), + IconButton( + icon: const Icon(Icons.close), + onPressed: () => editorProvider.closeTab(tab.id), + ), + ], + ), + ), + Expanded( + child: SingleChildScrollView( + child: Container(padding: const EdgeInsets.all(8.0), child: Text(tab.content)), + ), + ), + ], + ), + ); + } +} diff --git a/win_text_editor/lib/app/widgets/file_explorer.dart b/win_text_editor/lib/app/widgets/file_explorer.dart new file mode 100644 index 0000000..d148f3d --- /dev/null +++ b/win_text_editor/lib/app/widgets/file_explorer.dart @@ -0,0 +1,207 @@ +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../models/file_node.dart'; +import '../providers/file_provider.dart'; +import '../providers/editor_provider.dart'; + +class FileExplorer extends StatefulWidget { + const FileExplorer({super.key}); + + @override + State createState() => _FileExplorerState(); +} + +class _FileExplorerState extends State { + final ScrollController _scrollController = ScrollController(); // 添加这行 + + @override + void dispose() { + _scrollController.dispose(); // 记得释放资源 + super.dispose(); + } + + @override + void initState() { + super.initState(); + } + + Future _promptForDirectory(BuildContext context) async { + final fileProvider = Provider.of(context, listen: false); + final String? selectedDirectory = await FilePicker.platform.getDirectoryPath(); + + if (selectedDirectory != null) { + await fileProvider.setRootPath(selectedDirectory); + } + } + + @override + Widget build(BuildContext context) { + final fileProvider = Provider.of(context); + + return Container( + color: Colors.grey[200], + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(4.0), + child: Row( + children: [ + Expanded( + child: TextField( + decoration: const InputDecoration( + hintText: 'Search files...', + prefixIcon: Icon(Icons.search), + isDense: true, + ), + onChanged: (value) => fileProvider.searchFiles(value), + ), + ), + IconButton( + icon: const Icon(Icons.folder_open), // 改为文件夹打开图标 + onPressed: () => _promptForDirectory(context), + ), + ], + ), + ), + Expanded( + child: Scrollbar( + controller: _scrollController, // 添加控制器绑定 + thumbVisibility: true, // 始终显示滚动条 + interactive: true, // 启用滚动条拖动交互 + child: + fileProvider.isLoading + ? const Center(child: CircularProgressIndicator()) + : fileProvider.fileNodes.isEmpty + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('No directory selected'), + TextButton( + onPressed: () => _promptForDirectory(context), + child: const Text('Open Folder'), + ), + ], + ), + ) + : ListView.builder( + controller: _scrollController, // 确保ListView使用相同的控制器 + itemCount: _countVisibleNodes(fileProvider.fileNodes), + itemBuilder: (context, index) { + final node = _getVisibleNode(fileProvider.fileNodes, index); + return _FileNodeWidget( + key: ValueKey(node.path), + node: node, + onTap: () async { + if (node.isDirectory) { + await fileProvider.loadDirectoryContents(node); + } + }, + ); + }, + ), + ), + ), + ], + ), + ); + } +} + +// 辅助方法:计算所有可见节点数量 +int _countVisibleNodes(List nodes) { + int count = 0; + for (final node in nodes) { + count++; + if (node.isDirectory && node.isExpanded) { + count += _countVisibleNodes(node.children); + } + } + return count; +} + +// 辅助方法:根据索引获取可见节点 +FileNode _getVisibleNode(List nodes, int index) { + int current = 0; + for (final node in nodes) { + if (current == index) return node; + current++; + if (node.isDirectory && node.isExpanded) { + final childCount = _countVisibleNodes(node.children); + if (index - current < childCount) { + return _getVisibleNode(node.children, index - current); + } + current += childCount; + } + } + throw Exception('Index out of bounds: $index (max: ${current - 1})'); +} + +class _FileNodeWidget extends StatelessWidget { + final FileNode node; + final VoidCallback onTap; + + const _FileNodeWidget({ + Key? key, // 声明 key 参数 + required this.node, + required this.onTap, + }) : super(key: key); // 传递给父类 + + @override + Widget build(BuildContext context) { + final editorProvider = Provider.of(context, listen: false); + + return InkWell( + onTap: () { + if (node.isDirectory) { + onTap(); + } else { + editorProvider.openFile(node.path); + } + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 0), // 缩小垂直间距 + child: ListTile( + dense: true, // 启用紧凑模式 + visualDensity: const VisualDensity( + vertical: -4, // 负值减少高度,正值增加高度 + ), + contentPadding: const EdgeInsets.symmetric(horizontal: 2), // 调整水平内边距 + minVerticalPadding: 0, // 最小垂直内边距设为0 + leading: _buildLeadingWidget(context), + title: Text( + node.name, + style: Theme.of(context).textTheme.bodyMedium, // 使用标准文本样式 + ), + ), + ), + ); + } + + Widget _buildLeadingWidget(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + // Indentation lines + ...List.generate(node.depth, (index) { + return Padding( + padding: const EdgeInsets.only(left: 6, right: 6.0), + child: Container(width: 1.0, height: 32.0, color: Colors.grey[500]), + ); + }), + node.isDirectory + ? Icon( + node.isExpanded ? Icons.expand_more : Icons.chevron_right, + color: Colors.cyan[200], + size: 20, // 缩小图标尺寸 + ) + : const Icon(null, size: 2), + node.isDirectory + ? Icon(Icons.folder, color: Colors.cyan[500], size: 16) + : Icon(Icons.file_open, color: Colors.amber[700], size: 16), + ], + ); + } +} diff --git a/win_text_editor/lib/app/widgets/tab_bar.dart b/win_text_editor/lib/app/widgets/tab_bar.dart new file mode 100644 index 0000000..79f5372 --- /dev/null +++ b/win_text_editor/lib/app/widgets/tab_bar.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:win_text_editor/app/providers/editor_provider.dart'; + +import '../providers/file_provider.dart'; + +class EditorTabBar extends StatelessWidget { + const EditorTabBar({super.key}); + + @override + Widget build(BuildContext context) { + final editorProvider = Provider.of(context); + + return Container( + height: 40, + color: Colors.grey[300], + child: Row( + children: [ + // 布局切换按钮 + PopupMenuButton( + icon: const Icon(Icons.grid_view), + itemBuilder: + (context) => [ + const PopupMenuItem(value: 0, child: Text('平铺布局')), + const PopupMenuItem(value: 1, child: Text('层叠布局')), + const PopupMenuItem(value: 2, child: Text('单页布局')), + ], + onSelected: (value) => editorProvider.changeLayout(value), + ), + // 标签页 + Expanded( + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: editorProvider.openTabs.length, + itemBuilder: (context, index) { + final tab = editorProvider.openTabs[index]; + return InkWell( + onTap: () => editorProvider.setActiveTab(index), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + decoration: BoxDecoration( + color: + editorProvider.activeTabIndex == index ? Colors.white : Colors.grey[200], + border: Border( + bottom: BorderSide( + color: + editorProvider.activeTabIndex == index + ? Colors.blue + : Colors.transparent, + width: 2.0, + ), + ), + ), + child: Center( + child: Row( + children: [ + Text(tab.title), + const SizedBox(width: 8), + IconButton( + icon: const Icon(Icons.close, size: 16), + onPressed: () => editorProvider.closeTab(tab.id), + ), + ], + ), + ), + ), + ); + }, + ), + ), + // 打开文件按钮 + IconButton( + icon: const Icon(Icons.add), + onPressed: () async { + final fileProvider = Provider.of(context, listen: false); + await fileProvider.pickAndOpenFile(); + }, + ), + ], + ), + ); + } +} diff --git a/win_text_editor/lib/main.dart b/win_text_editor/lib/main.dart index 7b7f5b6..b51b43c 100644 --- a/win_text_editor/lib/main.dart +++ b/win_text_editor/lib/main.dart @@ -1,122 +1,38 @@ import 'package:flutter/material.dart'; +import 'package:window_manager/window_manager.dart'; +import 'app/app.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + // 配置窗口 + await windowManager.ensureInitialized(); + WindowOptions windowOptions = const WindowOptions( + size: Size(1200, 800), + center: true, + title: 'Win Text Editor', + ); + windowManager.waitUntilReadyToShow(windowOptions, () async { + await windowManager.show(); + await windowManager.focus(); + }); -void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); - // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( - title: 'Flutter Demo', + title: '文本编辑器', + debugShowCheckedModeBanner: false, theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a purple toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + primarySwatch: Colors.blue, + visualDensity: VisualDensity.adaptivePlatformDensity, ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('You have pushed the button this many times:'), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. + home: const AppScaffold(), ); } } diff --git a/win_text_editor/pubspec.lock b/win_text_editor/pubspec.lock index c2c57f7..25e72e5 100644 --- a/win_text_editor/pubspec.lock +++ b/win_text_editor/pubspec.lock @@ -9,6 +9,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.12.0" + bitsdojo_window: + dependency: "direct main" + description: + name: bitsdojo_window + sha256: "88ef7765dafe52d97d7a3684960fb5d003e3151e662c18645c1641c22b873195" + url: "https://pub.dev" + source: hosted + version: "0.1.6" + bitsdojo_window_linux: + dependency: transitive + description: + name: bitsdojo_window_linux + sha256: "9519c0614f98be733e0b1b7cb15b827007886f6fe36a4fb62cf3d35b9dd578ab" + url: "https://pub.dev" + source: hosted + version: "0.1.4" + bitsdojo_window_macos: + dependency: transitive + description: + name: bitsdojo_window_macos + sha256: f7c5be82e74568c68c5b8449e2c5d8fd12ec195ecd70745a7b9c0f802bb0268f + url: "https://pub.dev" + source: hosted + version: "0.1.4" + bitsdojo_window_platform_interface: + dependency: transitive + description: + name: bitsdojo_window_platform_interface + sha256: "65daa015a0c6dba749bdd35a0f092e7a8ba8b0766aa0480eb3ef808086f6e27c" + url: "https://pub.dev" + source: hosted + version: "0.1.2" + bitsdojo_window_windows: + dependency: transitive + description: + name: bitsdojo_window_windows + sha256: fa982cf61ede53f483e50b257344a1c250af231a3cdc93a7064dd6dc0d720b68 + url: "https://pub.dev" + source: hosted + version: "0.1.6" boolean_selector: dependency: transitive description: @@ -41,14 +81,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" - cupertino_icons: + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" + expandable: dependency: "direct main" description: - name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + name: expandable + sha256: "9604d612d4d1146dafa96c6d8eec9c2ff0994658d6d09fed720ab788c7f5afc2" url: "https://pub.dev" source: hosted - version: "1.0.8" + version: "5.0.1" fake_async: dependency: transitive description: @@ -57,6 +105,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: "978be1f602e0695daef8e345a3c597abf72b0c0ca6102fa2665eb549f5406a17" + url: "https://pub.dev" + source: hosted + version: "10.1.5" flutter: dependency: "direct main" description: flutter @@ -66,15 +130,36 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e + url: "https://pub.dev" + source: hosted + version: "2.0.28" + flutter_syntax_view: + dependency: "direct main" + description: + name: flutter_syntax_view + sha256: c5017bbedfdcf538daba765e16541fcb26434071655ca00cea7cbc205a70246a url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "4.1.7" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" leak_tracker: dependency: transitive description: @@ -103,10 +188,10 @@ packages: dependency: transitive description: name: lints - sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" url: "https://pub.dev" source: hosted - version: "5.1.1" + version: "2.1.1" matcher: dependency: transitive description: @@ -131,6 +216,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.16.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" path: dependency: transitive description: @@ -139,6 +232,86 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 + url: "https://pub.dev" + source: hosted + version: "2.2.17" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + provider: + dependency: "direct main" + description: + name: provider + sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" + url: "https://pub.dev" + source: hosted + version: "6.1.5" + screen_retriever: + dependency: transitive + description: + name: screen_retriever + sha256: "6ee02c8a1158e6dae7ca430da79436e3b1c9563c8cf02f524af997c201ac2b90" + url: "https://pub.dev" + source: hosted + version: "0.1.9" sky_engine: dependency: transitive description: flutter @@ -208,6 +381,38 @@ packages: url: "https://pub.dev" source: hosted version: "14.3.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f + url: "https://pub.dev" + source: hosted + version: "5.12.0" + window_manager: + dependency: "direct main" + description: + name: window_manager + sha256: "8699323b30da4cdbe2aa2e7c9de567a6abd8a97d9a5c850a3c86dcd0b34bbfbf" + url: "https://pub.dev" + source: hosted + version: "0.3.9" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" sdks: dart: ">=3.7.0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + flutter: ">=3.27.0" diff --git a/win_text_editor/pubspec.yaml b/win_text_editor/pubspec.yaml index aea57f0..1065a37 100644 --- a/win_text_editor/pubspec.yaml +++ b/win_text_editor/pubspec.yaml @@ -1,89 +1,29 @@ name: win_text_editor -description: "A new Flutter project." -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +description: A Windows text editor application built with Flutter -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -# In Windows, build-name is used as the major, minor, and patch parts -# of the product and file versions while build-number is used as the build suffix. version: 1.0.0+1 environment: - sdk: ^3.7.0 + sdk: '>=3.7.0 <4.0.0' -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. dependencies: flutter: sdk: flutter + provider: ^6.1.5 + path_provider: ^2.1.5 + file_picker: ^10.1.5 + # flutter_highlight: ^0.7.0 + # highlight: ^0.7.0 + window_manager: ^0.3.1 + bitsdojo_window: ^0.1.1+2 + flutter_syntax_view: ^4.1.7 + expandable: ^5.0.1 - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.8 dev_dependencies: flutter_test: sdk: flutter + flutter_lints: ^2.0.0 - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. - flutter_lints: ^5.0.0 - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/to/resolution-aware-images - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/to/asset-from-package - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/to/font-from-package diff --git a/win_text_editor/windows/flutter/generated_plugin_registrant.cc b/win_text_editor/windows/flutter/generated_plugin_registrant.cc index 8b6d468..a38b023 100644 --- a/win_text_editor/windows/flutter/generated_plugin_registrant.cc +++ b/win_text_editor/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,15 @@ #include "generated_plugin_registrant.h" +#include +#include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + BitsdojoWindowPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("BitsdojoWindowPlugin")); + ScreenRetrieverPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); + WindowManagerPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("WindowManagerPlugin")); } diff --git a/win_text_editor/windows/flutter/generated_plugins.cmake b/win_text_editor/windows/flutter/generated_plugins.cmake index b93c4c3..b404ce5 100644 --- a/win_text_editor/windows/flutter/generated_plugins.cmake +++ b/win_text_editor/windows/flutter/generated_plugins.cmake @@ -3,6 +3,9 @@ # list(APPEND FLUTTER_PLUGIN_LIST + bitsdojo_window_windows + screen_retriever + window_manager ) list(APPEND FLUTTER_FFI_PLUGIN_LIST