Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/two_dimensional_scrollables/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
## NEXT
## 0.3.5

* Updates minimum supported SDK version to Flutter 3.27/Dart 3.6.
* Updates the simple table sample to demonstrate different types of selection: single-cell
selection, and multi-cell selection.

## 0.3.4

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ class TableExample extends StatefulWidget {
State<TableExample> createState() => _TableExampleState();
}

enum _TableSelection { multiCell, singleCell, disabled }

class _TableExampleState extends State<TableExample> {
late final ScrollController _verticalController = ScrollController();
_TableSelection _selectionMode = _TableSelection.disabled;
int _rowCount = 20;

@override
Expand All @@ -34,57 +37,114 @@ class _TableExampleState extends State<TableExample> {
return Scaffold(
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 50.0),
child: TableView.builder(
verticalDetails: ScrollableDetails.vertical(
controller: _verticalController,
),
cellBuilder: _buildCell,
columnCount: 20,
columnBuilder: _buildColumnSpan,
rowCount: _rowCount,
rowBuilder: _buildRowSpan,
),
child: _selectionMode == _TableSelection.multiCell
? SelectionArea(
child: TableView.builder(
verticalDetails: ScrollableDetails.vertical(
controller: _verticalController,
),
cellBuilder: _buildCell,
columnCount: 20,
columnBuilder: _buildColumnSpan,
rowCount: _rowCount,
rowBuilder: _buildRowSpan,
),
)
: TableView.builder(
verticalDetails: ScrollableDetails.vertical(
controller: _verticalController,
),
cellBuilder: _buildCell,
columnCount: 20,
columnBuilder: _buildColumnSpan,
rowCount: _rowCount,
rowBuilder: _buildRowSpan,
),
),
persistentFooterButtons: <Widget>[
TextButton(
onPressed: () {
_verticalController.jumpTo(0);
},
child: const Text('Jump to Top'),
),
TextButton(
onPressed: () {
_verticalController.jumpTo(
_verticalController.position.maxScrollExtent,
);
},
child: const Text('Jump to Bottom'),
),
TextButton(
onPressed: () {
setState(() {
_rowCount += 10;
});
},
child: const Text('Add 10 Rows'),
OverflowBar(
alignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('Selection'),
SegmentedButton<_TableSelection>(
segments: const <ButtonSegment<_TableSelection>>[
ButtonSegment<_TableSelection>(
value: _TableSelection.multiCell,
label: Text('Multi-Cell'),
icon: Icon(Icons.layers),
),
ButtonSegment<_TableSelection>(
value: _TableSelection.singleCell,
label: Text('Single-Cell'),
icon: Icon(Icons.crop_square),
),
ButtonSegment<_TableSelection>(
value: _TableSelection.disabled,
label: Text('Disabled'),
icon: Icon(Icons.disabled_by_default),
),
],
selected: <_TableSelection>{_selectionMode},
onSelectionChanged: (Set<_TableSelection> newSelectionMode) {
setState(() {
// By default there is only a single segment that can be
// selected at one time, so its value is always the first
// item in the selected set.
_selectionMode = newSelectionMode.first;
});
},
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextButton(
onPressed: () {
_verticalController.jumpTo(0);
},
child: const Text('Jump to Top'),
),
TextButton(
onPressed: () {
_verticalController.jumpTo(
_verticalController.position.maxScrollExtent,
);
},
child: const Text('Jump to Bottom'),
),
TextButton(
onPressed: () {
setState(() {
_rowCount += 10;
});
},
child: const Text('Add 10 Rows'),
),
],
),
],
),
],
);
}

TableViewCell _buildCell(BuildContext context, TableVicinity vicinity) {
return TableViewCell(
child: Center(
child: Text('Tile c: ${vicinity.column}, r: ${vicinity.row}'),
),
Widget result = Center(
child: Text('Tile c: ${vicinity.column}, r: ${vicinity.row}'),
);
if (_selectionMode == _TableSelection.singleCell) {
result = SelectionArea(child: result);
}
return TableViewCell(child: result);
}

TableSpan _buildColumnSpan(int index) {
const TableSpanDecoration decoration = TableSpanDecoration(
border: TableSpanBorder(
trailing: BorderSide(),
),
border: TableSpanBorder(trailing: BorderSide()),
);

switch (index % 5) {
Expand Down Expand Up @@ -137,11 +197,7 @@ class _TableExampleState extends State<TableExample> {
TableSpan _buildRowSpan(int index) {
final TableSpanDecoration decoration = TableSpanDecoration(
color: index.isEven ? Colors.purple[100] : null,
border: const TableSpanBorder(
trailing: BorderSide(
width: 3,
),
),
border: const TableSpanBorder(trailing: BorderSide(width: 3)),
);

switch (index % 3) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:two_dimensional_examples/table_view/simple_table.dart';

Expand Down Expand Up @@ -54,4 +56,91 @@ void main() {
await tester.pump();
expect(position.pixels, 0.0);
});

testWidgets('Selection SegmentedButton control works',
(WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(home: TableExample()));
await tester.pump();

// Find two adjacent cells. Adjust these finders as needed for your specific layout.
final Finder cell1 = find.text('Tile c: 0, r: 0');
final Finder cell2 = find.text('Tile c: 1, r: 0');

final Offset cell1Center = tester.getCenter(cell1);
final Offset cell2Center = tester.getCenter(cell2);

// Enable multi-cell selection and verify.
await tester.tap(find.textContaining('Multi-Cell'));
await tester.pumpAndSettle();

RenderParagraph paragraph1 = tester.renderObject<RenderParagraph>(
find.descendant(of: cell1, matching: find.byType(RichText)),
);
RenderParagraph paragraph2 = tester.renderObject<RenderParagraph>(
find.descendant(of: cell2, matching: find.byType(RichText)),
);

// Selection starts empty.
expect(paragraph1.selections.isEmpty, isTrue);
expect(paragraph2.selections.isEmpty, isTrue);

// Long press and drag to select multiple cells.
final TestGesture gesture = await tester.startGesture(cell1Center);
await tester.pump(kLongPressTimeout);
await gesture.moveTo(cell2Center);
await gesture.up();
await tester.pumpAndSettle();

expect(paragraph1.selections.isEmpty, isFalse);
expect(paragraph2.selections.isEmpty, isFalse);

// Enable single-cell selection and verify.
await tester.tap(find.textContaining('Single-Cell'));
await tester.pumpAndSettle();

paragraph1 = tester.renderObject<RenderParagraph>(
find.descendant(of: cell1, matching: find.byType(RichText)),
);
paragraph2 = tester.renderObject<RenderParagraph>(
find.descendant(of: cell2, matching: find.byType(RichText)),
);

// Selection has been cleared.
expect(paragraph1.selections.isEmpty, isTrue);
expect(paragraph2.selections.isEmpty, isTrue);

// Selecting from cell1 to cell2 only selects cell1.
await gesture.down(cell1Center);
await tester.pump(kLongPressTimeout);
await gesture.moveTo(cell2Center);
await gesture.up();
await tester.pumpAndSettle();

expect(paragraph1.selections.isEmpty, isFalse);
expect(paragraph2.selections.isEmpty, isTrue);

// Disable selection and verify.
await tester.tap(find.text('Disabled'));
await tester.pumpAndSettle();

paragraph1 = tester.renderObject<RenderParagraph>(
find.descendant(of: cell1, matching: find.byType(RichText)),
);
paragraph2 = tester.renderObject<RenderParagraph>(
find.descendant(of: cell2, matching: find.byType(RichText)),
);

// Selection has been cleared.
expect(paragraph1.selections.isEmpty, isTrue);
expect(paragraph2.selections.isEmpty, isTrue);

// Long pressing should not select anything.
await gesture.down(cell1Center);
await tester.pump(kLongPressTimeout);
await gesture.up();
await tester.pumpAndSettle();

expect(paragraph1.selections.isEmpty, isTrue);
expect(paragraph2.selections.isEmpty, isTrue);
});
}
2 changes: 1 addition & 1 deletion packages/two_dimensional_scrollables/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: two_dimensional_scrollables
description: Widgets that scroll using the two dimensional scrolling foundation.
version: 0.3.4
version: 0.3.5
repository: https://github.com/flutter/packages/tree/main/packages/two_dimensional_scrollables
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+two_dimensional_scrollables%22+

Expand Down