feat: added test for block reordering

This commit is contained in:
Freek van de Ven 2022-08-24 17:14:50 +02:00
parent e31f284c22
commit 19187e921c
7 changed files with 313 additions and 156 deletions

View file

@ -54,6 +54,12 @@ class _TimetableDemoState extends State<TimetableDemo> {
child: Text('High Tea'),
id: 10,
),
TimeBlock(
start: TimeOfDay(hour: 18, minute: 0),
end: TimeOfDay(hour: 18, minute: 15),
child: Text('High Tea'),
id: 0,
),
];
final List<TimeBlock> groupedBlocks = [
@ -102,6 +108,12 @@ class _TimetableDemoState extends State<TimetableDemo> {
child: Text('High Tea'),
id: 10,
),
TimeBlock(
start: TimeOfDay(hour: 18, minute: 0),
end: TimeOfDay(hour: 18, minute: 15),
child: Text('High Tea'),
id: 0,
),
];
@override

115
lib/src/block_service.dart Normal file
View file

@ -0,0 +1,115 @@
part of timetable;
/// Combine blocks that have the same id and the same time.
List<TimeBlock> collapseBlocks(List<TimeBlock> blocks) {
var newBlocks = <TimeBlock>[];
var groupedBlocks = <List<TimeBlock>>[];
// order blocks by id and collides with another block
for (var block in blocks) {
// check if the block is already in one of the grouped blocks
var found = false;
if (block.id == 0) {
newBlocks.add(block);
continue;
}
for (var groupedBlock in groupedBlocks) {
if (groupedBlock.first.id == block.id &&
groupedBlock.first.start == block.start &&
groupedBlock.first.end == block.end) {
groupedBlock.add(block);
found = true;
break;
}
}
if (!found) {
if (blocks
.where(
(b) =>
b != block &&
b.id == block.id &&
b.start == block.start &&
b.end == block.end,
)
.isNotEmpty) {
groupedBlocks.add([block]);
} else {
newBlocks.add(block);
}
}
}
for (var block in groupedBlocks) {
// combine the blocks into one block
// calculate the endtime of the combined block
var startMinute = block.first.start.minute + block.first.start.hour * 60;
var endMinute = block.first.end.minute + block.first.end.hour * 60;
var durationMinute = (endMinute - startMinute) * block.length;
var endTime = TimeOfDay(
hour: (startMinute + durationMinute) ~/ 60,
minute: (startMinute + durationMinute) % 60,
);
var newBlock = TimeBlock(
start: block.first.start,
end: endTime,
id: block.first.id,
child: Column(
children: [
for (var b in block) ...[b.child ?? Container()],
],
),
);
newBlocks.add(newBlock);
}
return newBlocks;
}
/// Group blocks with the same id together.
/// Items in the same group will be displayed in the same column.
List<List<TimeBlock>> groupBlocksById(List<TimeBlock> blocks) {
var groupedBlocks = <List<TimeBlock>>[];
var defaultGroup = <TimeBlock>[];
for (var block in blocks) {
var found = false;
if (block.id == 0) {
defaultGroup.add(block);
} else {
for (var groupedBlock in groupedBlocks) {
if (groupedBlock.first.id == block.id) {
groupedBlock.add(block);
found = true;
break;
}
}
if (!found) {
groupedBlocks.add([block]);
}
}
}
for (var block in defaultGroup) {
groupedBlocks.add([block]);
}
return groupedBlocks;
}
/// Nerge blocks that fit below eachother into one column.
List<List<TimeBlock>> mergeBlocksInColumns(List<TimeBlock> blocks) {
var mergedBlocks = <List<TimeBlock>>[];
// try to put blocks in the same column if the time doesn´t collide
for (var block in blocks) {
var mergeIndex = 0;
for (var mergedBlock in mergedBlocks) {
if (!mergedBlock.any((b) => b.collidesWith(block))) {
mergedBlock.add(block);
break;
} else {
mergeIndex++;
}
}
if (mergedBlocks.length == mergeIndex) {
mergedBlocks.add([block]);
}
}
return mergedBlocks;
}

View file

@ -14,7 +14,7 @@ class Timetable extends StatefulWidget {
this.tablePaddingEnd = 15,
this.theme = const TableTheme(),
this.mergeBlocks = false,
this.collapseBlocks = false,
this.collapseBlocks = true,
Key? key,
}) : super(key: key);
@ -55,6 +55,7 @@ class Timetable extends StatefulWidget {
final bool mergeBlocks;
/// Whether or not to collapse blocks in 1 column if they have the same id.
/// If blocks have the same id and time they will be combined into one block.
final bool collapseBlocks;
@override
@ -81,7 +82,12 @@ class _TimetableState extends State<Timetable> {
@override
Widget build(BuildContext context) {
var blocks = _collapseBlocks(widget.timeBlocks);
List<TimeBlock> blocks;
if (widget.collapseBlocks) {
blocks = collapseBlocks(widget.timeBlocks);
} else {
blocks = widget.timeBlocks;
}
return SingleChildScrollView(
physics: widget.scrollPhysics ?? const BouncingScrollPhysics(),
controller: _scrollController,
@ -106,54 +112,21 @@ class _TimetableState extends State<Timetable> {
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!widget.mergeBlocks && !widget.collapseBlocks) ...[
if (widget.mergeBlocks || widget.collapseBlocks) ...[
for (var orderedBlocks in (widget.mergeBlocks)
? mergeBlocksInColumns(blocks)
: groupBlocksById(blocks)) ...[
Stack(
children: [
for (var block in orderedBlocks) ...[
_showBlock(block),
],
],
),
],
] else ...[
for (var block in blocks) ...[
Block(
start: block.start,
end: block.end,
startHour: widget.startHour,
hourHeight: widget.hourHeight,
blockWidth: widget.blockWidth,
blockColor: widget.blockColor,
child: block.child,
),
],
] else if (widget.mergeBlocks) ...[
for (var mergedBlocks
in _mergeBlocksInColumns(blocks)) ...[
Stack(
children: [
for (var block in mergedBlocks) ...[
Block(
start: block.start,
end: block.end,
startHour: widget.startHour,
hourHeight: widget.hourHeight,
blockWidth: widget.blockWidth,
blockColor: widget.blockColor,
child: block.child,
),
],
],
),
],
] else if (widget.collapseBlocks) ...[
for (var groupedBlocks in _groupBlocksById(blocks)) ...[
Stack(
children: [
for (var block in groupedBlocks) ...[
Block(
start: block.start,
end: block.end,
startHour: widget.startHour,
hourHeight: widget.hourHeight,
blockWidth: widget.blockWidth,
blockColor: widget.blockColor,
child: block.child,
),
],
],
),
_showBlock(block),
],
],
SizedBox(
@ -171,111 +144,16 @@ class _TimetableState extends State<Timetable> {
);
}
/// Copmbine blocks that have the same id and the same time.
List<TimeBlock> _collapseBlocks(List<TimeBlock> blocks) {
var newBlocks = <TimeBlock>[];
var groupedBlocks = <List<TimeBlock>>[];
// order blocks by id and collides with another block
for (var block in blocks) {
// check if the block is already in one of the grouped blocks
var found = false;
for (var groupedBlock in groupedBlocks) {
if (groupedBlock.first.id == block.id &&
groupedBlock.first.start == block.start &&
groupedBlock.first.end == block.end) {
groupedBlock.add(block);
found = true;
break;
}
}
if (!found) {
if (blocks
.where(
(b) =>
b != block &&
b.id == block.id &&
b.start == block.start &&
b.end == block.end,
)
.isNotEmpty) {
groupedBlocks.add([block]);
} else {
newBlocks.add(block);
}
}
}
// 8.10 8.40 8.55
//
for (var block in groupedBlocks) {
// combine the blocks into one block
// calculate the endtime of the combined block
var startMinute = block.first.start.minute + block.first.start.hour * 60;
var endMinute = block.first.end.minute + block.first.end.hour * 60;
var durationMinute = (endMinute - startMinute) * block.length;
var endTime = TimeOfDay(
hour: (startMinute + durationMinute) ~/ 60,
minute: (startMinute + durationMinute) % 60,
);
var newBlock = TimeBlock(
start: block.first.start,
end: endTime,
child: Column(
children: [
for (var b in block) ...[b.child ?? Container()],
],
),
);
newBlocks.add(newBlock);
}
return newBlocks;
}
List<List<TimeBlock>> _groupBlocksById(List<TimeBlock> blocks) {
var groupedBlocks = <List<TimeBlock>>[];
var defaultGroup = <TimeBlock>[];
for (var block in blocks) {
var found = false;
if (block.id == 0) {
defaultGroup.add(block);
} else {
for (var groupedBlock in groupedBlocks) {
if (groupedBlock.first.id == block.id) {
groupedBlock.add(block);
found = true;
break;
}
}
if (!found) {
groupedBlocks.add([block]);
}
}
}
for (var block in defaultGroup) {
groupedBlocks.add([block]);
}
return groupedBlocks;
}
List<List<TimeBlock>> _mergeBlocksInColumns(List<TimeBlock> blocks) {
var mergedBlocks = <List<TimeBlock>>[];
// try to put blocks in the same column if the time doesn´t collide
for (var block in blocks) {
var mergeIndex = 0;
for (var mergedBlock in mergedBlocks) {
if (!mergedBlock.any((b) => b.collidesWith(block))) {
mergedBlock.add(block);
break;
} else {
mergeIndex++;
}
}
if (mergedBlocks.length == mergeIndex) {
mergedBlocks.add([block]);
}
}
return mergedBlocks;
Widget _showBlock(TimeBlock block) {
return Block(
start: block.start,
end: block.end,
startHour: widget.startHour,
hourHeight: widget.hourHeight,
blockWidth: widget.blockWidth,
blockColor: widget.blockColor,
child: block.child,
);
}
void _scrollToFirstBlock() {

View file

@ -4,7 +4,8 @@ import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
part 'src/timetable.dart';
part 'src/table.dart';
part 'src/block_service.dart';
part 'src/widgets/table.dart';
part 'src/models/time_block.dart';
part 'src/models/table_theme.dart';
part 'src/block.dart';
part 'src/widgets/block.dart';

View file

@ -0,0 +1,151 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:timetable/timetable.dart';
void main() {
group('test collapseBlocks', () {
test('new block creation success', () {
//Arrange
var blocks = [
TimeBlock(
start: const TimeOfDay(hour: 2, minute: 0),
end: const TimeOfDay(hour: 2, minute: 15),
id: 5,
),
TimeBlock(
start: const TimeOfDay(hour: 2, minute: 0),
end: const TimeOfDay(hour: 2, minute: 15),
id: 5,
),
TimeBlock(
start: const TimeOfDay(hour: 2, minute: 15),
end: const TimeOfDay(hour: 2, minute: 30),
id: 6,
),
];
//Act
var result = collapseBlocks(blocks);
//Assert
expect(result.length, 2);
expect(
result.firstWhere((element) => element.id == 5).end,
const TimeOfDay(hour: 2, minute: 30),
);
});
test('elements without id ignored', () {
//Arrange
var blocks = [
TimeBlock(
start: const TimeOfDay(hour: 2, minute: 0),
end: const TimeOfDay(hour: 2, minute: 15),
id: 0, // default id is 0
),
TimeBlock(
start: const TimeOfDay(hour: 2, minute: 0),
end: const TimeOfDay(hour: 2, minute: 15),
id: 0,
),
];
//Act
var result = collapseBlocks(blocks);
//Assert
expect(result.length, 2);
expect(result.first.end, const TimeOfDay(hour: 2, minute: 15));
});
});
group('test groupBlocksById', () {
test('groupBlocksById success', () {
//Arrange
var blocks = [
TimeBlock(
start: const TimeOfDay(hour: 2, minute: 0),
end: const TimeOfDay(hour: 2, minute: 15),
id: 5,
),
TimeBlock(
start: const TimeOfDay(hour: 2, minute: 0),
end: const TimeOfDay(hour: 2, minute: 25),
id: 5,
),
TimeBlock(
start: const TimeOfDay(hour: 2, minute: 20),
end: const TimeOfDay(hour: 2, minute: 30),
id: 6,
),
];
//Act
var result = groupBlocksById(blocks);
//Assert
expect(result.length, 2);
});
test('groupBlocksById id 0 ignored', () {
// Arrange
var blocks = [
TimeBlock(
start: const TimeOfDay(hour: 2, minute: 0),
end: const TimeOfDay(hour: 2, minute: 15),
id: 0,
),
TimeBlock(
start: const TimeOfDay(hour: 2, minute: 0),
end: const TimeOfDay(hour: 2, minute: 25),
id: 0,
),
TimeBlock(
start: const TimeOfDay(hour: 2, minute: 20),
end: const TimeOfDay(hour: 2, minute: 30),
id: 0,
),
];
//Act
var result = groupBlocksById(blocks);
//Assert
expect(result.length, 3);
});
});
group('test mergeBlocksInColumns', () {
test('mergeBlocksInColumns success', () {
//Arrange
var blocks = [
TimeBlock(
start: const TimeOfDay(hour: 2, minute: 0),
end: const TimeOfDay(hour: 6, minute: 15),
id: 1,
),
TimeBlock(
start: const TimeOfDay(hour: 8, minute: 0),
end: const TimeOfDay(hour: 10, minute: 25),
id: 2,
),
TimeBlock(
start: const TimeOfDay(hour: 12, minute: 20),
end: const TimeOfDay(hour: 14, minute: 30),
id: 3,
),
TimeBlock(
start: const TimeOfDay(hour: 13, minute: 20),
end: const TimeOfDay(hour: 14, minute: 15),
id: 4,
),
];
//Act
var result = mergeBlocksInColumns(blocks);
//Assert
expect(result.length, 2);
});
});
}