Browse Source

剥离出抽象DataGrid

master
hejl 1 month ago
parent
commit
38721a8752
  1. 8
      win_text_editor/lib/modules/content_search/controllers/content_search_controller.dart
  2. 10
      win_text_editor/lib/modules/content_search/services/count_search_service.dart
  3. 32
      win_text_editor/lib/modules/content_search/widgets/directory_settings.dart
  4. 2
      win_text_editor/lib/modules/memory_table/controllers/index_data_source.dart
  5. 1
      win_text_editor/lib/modules/memory_table/models/memory_table.dart
  6. 2
      win_text_editor/lib/modules/uft_component/controllers/component_source.dart
  7. 1
      win_text_editor/lib/modules/uft_component/models/uft_component.dart
  8. 140
      win_text_editor/lib/shared/base/my_sf_data_grid.dart
  9. 2
      win_text_editor/lib/shared/base/selectable_data_source.dart
  10. 3
      win_text_editor/lib/shared/base/selectable_item.dart
  11. 2
      win_text_editor/lib/shared/components/my_grid_column.dart
  12. 43
      win_text_editor/lib/shared/components/my_sf_data_source.dart
  13. 5
      win_text_editor/lib/shared/models/std_filed.dart
  14. 6
      win_text_editor/lib/shared/uft_std_fields/field_data_source.dart
  15. 90
      win_text_editor/lib/shared/uft_std_fields/fields_data_grid.dart
  16. 30
      win_text_editor/test/widget_test.dart

8
win_text_editor/lib/modules/content_search/controllers/content_search_controller.dart

@ -16,6 +16,7 @@ class ContentSearchController extends BaseContentController { @@ -16,6 +16,7 @@ class ContentSearchController extends BaseContentController {
String _searchQuery = '';
String _searchDirectory = '';
String _fileType = '*.*';
String _jumpFiles = '';
bool _caseSensitive = false;
bool _wholeWord = false;
bool _useRegex = false;
@ -31,6 +32,7 @@ class ContentSearchController extends BaseContentController { @@ -31,6 +32,7 @@ class ContentSearchController extends BaseContentController {
String get searchQuery => _searchQuery;
String get searchDirectory => _searchDirectory;
String get fileType => _fileType;
String get jumpFiles => _jumpFiles;
bool get caseSensitive => _caseSensitive;
bool get wholeWord => _wholeWord;
bool get useRegex => _useRegex;
@ -91,6 +93,11 @@ class ContentSearchController extends BaseContentController { @@ -91,6 +93,11 @@ class ContentSearchController extends BaseContentController {
notifyListeners();
}
set jumpFiles(String value) {
_jumpFiles = value;
notifyListeners();
}
set caseSensitive(bool value) {
_caseSensitive = value;
notifyListeners();
@ -198,6 +205,7 @@ class ContentSearchController extends BaseContentController { @@ -198,6 +205,7 @@ class ContentSearchController extends BaseContentController {
directory: searchDirectory,
query: searchQuery,
fileType: fileType,
jumpFiles: jumpFiles,
caseSensitive: caseSensitive,
wholeWord: wholeWord,
useRegex: useRegex,

10
win_text_editor/lib/modules/content_search/services/count_search_service.dart

@ -2,6 +2,7 @@ import 'dart:async'; @@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io';
import 'dart:isolate';
import 'dart:math';
import 'package:path/path.dart' as path;
import 'package:win_text_editor/modules/content_search/services/base_search_service.dart';
import 'package:win_text_editor/shared/utils/file_utils.dart';
@ -23,6 +24,7 @@ class CountSearchService { @@ -23,6 +24,7 @@ class CountSearchService {
required String directory,
required String query,
required String fileType,
required String jumpFiles,
required bool caseSensitive,
required bool wholeWord,
required bool useRegex,
@ -38,7 +40,7 @@ class CountSearchService { @@ -38,7 +40,7 @@ class CountSearchService {
final queryBatches = _chunkList(allQueries, _queriesPerBatch);
//
final filePaths = await _collectFilePaths(directory, fileType, shouldStop);
final filePaths = await _collectFilePaths(directory, fileType, jumpFiles, shouldStop);
if (filePaths.isEmpty) return counts;
// Isolate池
@ -123,13 +125,17 @@ class CountSearchService { @@ -123,13 +125,17 @@ class CountSearchService {
static Future<List<String>> _collectFilePaths(
String directory,
String fileType,
String jumpFiles,
bool Function()? shouldStop,
) async {
final dir = Directory(directory);
final paths = <String>[];
final jumpFileList = BaseSearchService.splitQuery(jumpFiles.toLowerCase());
await for (final entity in dir.list(recursive: true)) {
if (shouldStop?.call() == true) break;
if (entity is File && FileUtils.matchesFileType(entity.path, fileType)) {
if (entity is File &&
!jumpFileList.contains(path.basename(entity.path).toLowerCase()) &&
FileUtils.matchesFileType(entity.path, fileType)) {
paths.add(entity.path);
}
}

32
win_text_editor/lib/modules/content_search/widgets/directory_settings.dart

@ -12,6 +12,7 @@ class DirectorySettings extends StatefulWidget { @@ -12,6 +12,7 @@ class DirectorySettings extends StatefulWidget {
class _DirectorySettingsState extends State<DirectorySettings> {
late TextEditingController _searchDirectoryController;
late TextEditingController _fileTypeController;
late TextEditingController _jumpFilesController;
@override
void initState() {
@ -19,12 +20,14 @@ class _DirectorySettingsState extends State<DirectorySettings> { @@ -19,12 +20,14 @@ class _DirectorySettingsState extends State<DirectorySettings> {
final controller = context.read<ContentSearchController>();
_searchDirectoryController = TextEditingController(text: controller.searchDirectory);
_fileTypeController = TextEditingController(text: controller.fileType);
_jumpFilesController = TextEditingController(text: controller.jumpFiles);
}
@override
void dispose() {
_searchDirectoryController.dispose();
_fileTypeController.dispose();
_jumpFilesController.dispose();
super.dispose();
}
@ -56,6 +59,17 @@ class _DirectorySettingsState extends State<DirectorySettings> { @@ -56,6 +59,17 @@ class _DirectorySettingsState extends State<DirectorySettings> {
onChanged: (value) => controller.searchDirectory = value,
),
),
const SizedBox(width: 8),
IconButton(
icon: const Icon(Icons.folder_open),
onPressed: () async {
await controller.pickDirectory();
// _searchDirectoryController.text
// Consumer
},
),
const SizedBox(width: 8),
SizedBox(
width: 100,
@ -68,14 +82,18 @@ class _DirectorySettingsState extends State<DirectorySettings> { @@ -68,14 +82,18 @@ class _DirectorySettingsState extends State<DirectorySettings> {
onChanged: (value) => controller.fileType = value,
),
),
const SizedBox(width: 8),
IconButton(
icon: const Icon(Icons.folder_open),
onPressed: () async {
await controller.pickDirectory();
// _searchDirectoryController.text
// Consumer
},
SizedBox(
width: 200,
child: TextField(
controller: _jumpFilesController,
decoration: const InputDecoration(
labelText: '跳过文件(列表)',
border: OutlineInputBorder(),
),
onChanged: (value) => controller.jumpFiles = value,
),
),
],
),

2
win_text_editor/lib/modules/memory_table/controllers/index_data_source.dart

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_datagrid/datagrid.dart';
import 'package:win_text_editor/shared/base/base_data_source.dart';
import 'package:win_text_editor/shared/base/selectable_data_source.dart';
import 'package:win_text_editor/modules/memory_table/models/memory_table.dart';
//

1
win_text_editor/lib/modules/memory_table/models/memory_table.dart

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
import 'package:win_text_editor/shared/base/selectable_item.dart';
import 'package:win_text_editor/shared/models/std_filed.dart';
class Index implements SelectableItem {

2
win_text_editor/lib/modules/uft_component/controllers/component_source.dart

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_datagrid/datagrid.dart';
import 'package:win_text_editor/modules/uft_component/models/uft_component.dart';
import 'package:win_text_editor/shared/base/base_data_source.dart';
import 'package:win_text_editor/shared/base/selectable_data_source.dart';
class ComponentSource extends SelectableDataSource<UftComponent> {
ComponentSource(

1
win_text_editor/lib/modules/uft_component/models/uft_component.dart

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
import 'package:win_text_editor/modules/memory_table/models/memory_table.dart';
import 'package:win_text_editor/shared/base/selectable_item.dart';
import 'package:win_text_editor/shared/models/std_filed.dart';
class UftComponent implements SelectableItem {

140
win_text_editor/lib/shared/base/my_sf_data_grid.dart

@ -0,0 +1,140 @@ @@ -0,0 +1,140 @@
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_datagrid/datagrid.dart';
import 'package:win_text_editor/shared/base/selectable_data_source.dart';
import 'package:win_text_editor/shared/base/selectable_item.dart';
import 'package:win_text_editor/shared/components/my_sf_data_source.dart';
class MySfDataGrid<T extends SelectableItem> extends StatelessWidget {
final MySfDataSource<T> dataSource;
final Function(int index, bool isSelected)? onSelectionChanged;
final List<GridColumn> columns;
const MySfDataGrid({
super.key,
required this.dataSource,
required this.columns,
this.onSelectionChanged,
});
Widget _buildCheckboxHeader(BuildContext context, SelectableDataSource<T> dataSource) {
final allSelected =
dataSource.items.isNotEmpty && dataSource.items.every((item) => item.isSelected);
final someSelected =
dataSource.items.isNotEmpty &&
dataSource.items.any((item) => item.isSelected) &&
!allSelected;
return Container(
alignment: Alignment.center,
color: Colors.grey[200],
child: Transform.scale(
scale: 0.75,
child: Checkbox(
value: allSelected,
tristate: someSelected,
onChanged: (value) => dataSource.toggleAllSelection(value ?? false),
),
),
);
}
@override
Widget build(BuildContext context) {
return Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
return SizedBox(
width: constraints.maxWidth,
child: SfDataGrid(
rowHeight: 32,
headerRowHeight: 32,
source: dataSource,
gridLinesVisibility: GridLinesVisibility.both,
headerGridLinesVisibility: GridLinesVisibility.both,
columnWidthMode: ColumnWidthMode.fitByCellValue,
selectionMode: SelectionMode.none,
allowEditing: true,
columns: [
GridColumn(
columnName: 'select',
label: ValueListenableBuilder<bool>(
valueListenable: dataSource.selectionNotifier,
builder: (context, _, __) => _buildCheckboxHeader(context, dataSource),
),
width: 60,
),
...columns,
],
onCellTap: (details) {
if (details.column.columnName == 'select') {
final rowIndex = details.rowColumnIndex.rowIndex - 1;
if (rowIndex >= 0 && rowIndex < dataSource.items.length) {
dataSource.toggleRowSelection(
rowIndex,
!dataSource.items[rowIndex].isSelected,
);
onSelectionChanged?.call(rowIndex, dataSource.items[rowIndex].isSelected);
}
}
},
onCellSecondaryTap: (details) {
final rowIndex = details.rowColumnIndex.rowIndex - 1;
final columnName = details.column.columnName;
final isHeader = rowIndex < 0;
final renderBox = context.findRenderObject() as RenderBox;
final position = details.globalPosition;
showMenu(
context: context,
position: RelativeRect.fromLTRB(
position.dx,
position.dy,
position.dx + renderBox.size.width - position.dx,
position.dy + renderBox.size.height - position.dy,
),
items: [
if (!isHeader)
PopupMenuItem(value: 'copyCell', child: Text('复制单元格 ($columnName)')),
if (!isHeader)
const PopupMenuItem(value: 'copyRow', child: Text('复制当前行')),
PopupMenuItem(value: 'copyColumn', child: Text('复制整列 ($columnName)')),
],
).then((value) async {
if (value != null) {
switch (value) {
case 'copyCell':
final row = dataSource.effectiveRows[rowIndex];
await dataSource.copyCellValue(row, columnName);
break;
case 'copyRow':
final row = dataSource.effectiveRows[rowIndex];
await dataSource.copyRowValues(row);
break;
case 'copyColumn':
await dataSource.copyColumnValues(
dataSource.effectiveRows,
columnName,
);
break;
}
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('已复制到剪贴板')));
}
});
},
),
);
},
),
),
],
),
);
}
}

2
win_text_editor/lib/shared/base/base_data_source.dart → win_text_editor/lib/shared/base/selectable_data_source.dart

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_datagrid/datagrid.dart';
import 'package:win_text_editor/shared/models/std_filed.dart';
import 'package:win_text_editor/shared/base/selectable_item.dart';
abstract class SelectableDataSource<T extends SelectableItem> extends DataGridSource {
SelectableDataSource(this.items, {this.onSelectionChanged}) {

3
win_text_editor/lib/shared/base/selectable_item.dart

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
abstract class SelectableItem {
bool isSelected = false;
}

2
win_text_editor/lib/shared/components/my_grid_column.dart

@ -7,7 +7,7 @@ class MyGridColumn extends GridColumn { @@ -7,7 +7,7 @@ class MyGridColumn extends GridColumn {
double minimumWidth = 100,
double maximumWidth = double.infinity,
required String label,
bool allowEditing = false,
bool allowEditing = true,
}) : super(
columnName: columnName,
minimumWidth: minimumWidth,

43
win_text_editor/lib/shared/components/my_sf_data_source.dart

@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
import 'package:flutter/services.dart';
import 'package:syncfusion_flutter_datagrid/datagrid.dart';
import 'package:win_text_editor/shared/base/selectable_data_source.dart';
import 'package:win_text_editor/shared/base/selectable_item.dart';
abstract class MySfDataSource<T extends SelectableItem> extends SelectableDataSource<T> {
MySfDataSource(
List<T> items, {
required Null Function(dynamic index, dynamic isSelected) onSelectionChanged,
}) : super(items, onSelectionChanged: onSelectionChanged);
@override
List<DataGridRow> get rows;
@override
DataGridRowAdapter buildRow(DataGridRow row);
Future<void> copyCellValue(DataGridRow row, String columnName) async {
final cell = row.getCells().firstWhere(
(cell) => cell.columnName == columnName,
orElse: () => throw Exception('Column not found'),
);
await Clipboard.setData(ClipboardData(text: cell.value.toString()));
}
Future<void> copyRowValues(DataGridRow row) async {
final values = row.getCells().map((cell) => cell.value.toString()).join('\t');
await Clipboard.setData(ClipboardData(text: values));
}
Future<void> copyColumnValues(List<DataGridRow> rows, String columnName) async {
final values = rows
.map((row) {
final cell = row.getCells().firstWhere(
(cell) => cell.columnName == columnName,
orElse: () => throw Exception('Column not found'),
);
return cell.value.toString();
})
.join('\n');
await Clipboard.setData(ClipboardData(text: values));
}
}

5
win_text_editor/lib/shared/models/std_filed.dart

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
import 'package:hive/hive.dart';
import 'package:win_text_editor/shared/base/selectable_item.dart';
@HiveType(typeId: 7) // typeId一致
class StdField {
@ -14,10 +15,6 @@ class StdField { @@ -14,10 +15,6 @@ class StdField {
StdField({required this.name, required this.chineseName, required this.dateType});
}
abstract class SelectableItem {
bool isSelected = false;
}
class Field implements SelectableItem {
Field(this.id, this.name, this.chineseName, this.type, [this.isSelected = false]);

6
win_text_editor/lib/shared/uft_std_fields/field_data_source.dart

@ -1,9 +1,9 @@ @@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_datagrid/datagrid.dart';
import 'package:win_text_editor/shared/base/base_data_source.dart';
import 'package:win_text_editor/shared/components/my_sf_data_source.dart';
import 'package:win_text_editor/shared/models/std_filed.dart';
class FieldsDataSource extends SelectableDataSource<Field> {
class FieldsDataSource extends MySfDataSource<Field> {
FieldsDataSource(
List<Field> fields, {
required Null Function(dynamic index, dynamic isSelected) onSelectionChanged,
@ -36,7 +36,7 @@ class FieldsDataSource extends SelectableDataSource<Field> { @@ -36,7 +36,7 @@ class FieldsDataSource extends SelectableDataSource<Field> {
if (cell.columnName == 'select') {
return Center(
child: Transform.scale(
scale: 0.75, //
scale: 0.75,
child: Checkbox(
value: items[rowIndex].isSelected,
onChanged: (value) => toggleRowSelection(rowIndex, value),

90
win_text_editor/lib/shared/uft_std_fields/fields_data_grid.dart

@ -1,6 +1,5 @@ @@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:syncfusion_flutter_datagrid/datagrid.dart';
import 'package:win_text_editor/shared/base/base_data_source.dart';
import 'package:win_text_editor/shared/base/my_sf_data_grid.dart';
import 'package:win_text_editor/shared/components/my_grid_column.dart';
import 'package:win_text_editor/shared/models/std_filed.dart';
import 'package:win_text_editor/shared/uft_std_fields/field_data_source.dart';
@ -11,86 +10,17 @@ class FieldsDataGrid extends StatelessWidget { @@ -11,86 +10,17 @@ class FieldsDataGrid extends StatelessWidget {
const FieldsDataGrid({super.key, required this.fieldsSource, this.onSelectionChanged});
Widget _buildCheckboxHeader<T extends SelectableItem>(
BuildContext context,
SelectableDataSource<T> dataSource,
) {
final allSelected =
dataSource.items.isNotEmpty && dataSource.items.every((item) => item.isSelected);
final someSelected =
dataSource.items.isNotEmpty &&
dataSource.items.any((item) => item.isSelected) &&
!allSelected;
return Container(
alignment: Alignment.center,
color: Colors.grey[200],
child: Transform.scale(
scale: 0.75, //
child: Checkbox(
value: allSelected,
tristate: someSelected,
onChanged: (value) => dataSource.toggleAllSelection(value ?? false),
),
),
);
}
@override
Widget build(BuildContext context) {
return Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
return SizedBox(
width: constraints.maxWidth,
child: SfDataGrid(
rowHeight: 32,
headerRowHeight: 32,
source: fieldsSource,
gridLinesVisibility: GridLinesVisibility.both,
headerGridLinesVisibility: GridLinesVisibility.both,
columnWidthMode: ColumnWidthMode.fitByCellValue,
selectionMode: SelectionMode.none,
columns: [
GridColumn(
columnName: 'select',
label: ValueListenableBuilder<bool>(
valueListenable: fieldsSource.selectionNotifier,
builder: (context, _, __) => _buildCheckboxHeader(context, fieldsSource),
),
width: 60,
),
MyGridColumn(columnName: 'id', label: '序号', minimumWidth: 80),
MyGridColumn(columnName: 'name', label: '名称', minimumWidth: 120),
MyGridColumn(columnName: 'chineseName', label: '中文名', minimumWidth: 120),
MyGridColumn(columnName: 'type', label: '类型', minimumWidth: 120),
],
onCellTap: (details) {
if (details.column.columnName == 'select') {
final rowIndex = details.rowColumnIndex.rowIndex - 1;
if (rowIndex >= 0 && rowIndex < fieldsSource.items.length) {
fieldsSource.toggleRowSelection(
rowIndex,
!fieldsSource.items[rowIndex].isSelected,
);
onSelectionChanged?.call(
rowIndex,
fieldsSource.items[rowIndex].isSelected,
);
}
}
},
),
);
},
),
),
],
),
return MySfDataGrid<Field>(
dataSource: fieldsSource,
onSelectionChanged: onSelectionChanged,
columns: [
MyGridColumn(columnName: 'id', label: '序号', minimumWidth: 80),
MyGridColumn(columnName: 'name', label: '名称', minimumWidth: 120),
MyGridColumn(columnName: 'chineseName', label: '中文名', minimumWidth: 120),
MyGridColumn(columnName: 'type', label: '类型', minimumWidth: 120),
],
);
}
}

30
win_text_editor/test/widget_test.dart

@ -1,30 +0,0 @@ @@ -1,30 +0,0 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:win_text_editor/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}
Loading…
Cancel
Save