mirror of
https://github.com/Iconica-Development/flutter_timetable.git
synced 2025-05-18 19:43:43 +02:00
feat: added test for block reordering
This commit is contained in:
parent
e31f284c22
commit
19187e921c
7 changed files with 313 additions and 156 deletions
|
@ -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
115
lib/src/block_service.dart
Normal 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;
|
||||
}
|
|
@ -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,
|
||||
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,
|
||||
);
|
||||
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;
|
||||
}
|
||||
|
||||
void _scrollToFirstBlock() {
|
||||
|
|
|
@ -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';
|
||||
|
|
151
test/block_service_test.dart
Normal file
151
test/block_service_test.dart
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue