mirror of
https://github.com/Iconica-Development/flutter_timetable.git
synced 2025-05-18 19:43:43 +02:00
feat: added timetable merge column option
This commit is contained in:
parent
190630012b
commit
5a54309a5d
3 changed files with 83 additions and 11 deletions
|
@ -53,6 +53,8 @@ class _TimetableDemoState extends State<TimetableDemo> {
|
||||||
endHour: 22,
|
endHour: 22,
|
||||||
timeBlocks: blocks,
|
timeBlocks: blocks,
|
||||||
scrollController: _scrollController,
|
scrollController: _scrollController,
|
||||||
|
tablePaddingStart: 0,
|
||||||
|
mergeBlocks: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,6 +4,7 @@ class TimeBlock {
|
||||||
TimeBlock({
|
TimeBlock({
|
||||||
required this.start,
|
required this.start,
|
||||||
required this.end,
|
required this.end,
|
||||||
|
this.id = 0,
|
||||||
this.child,
|
this.child,
|
||||||
}) : assert(
|
}) : assert(
|
||||||
start.hour <= end.hour ||
|
start.hour <= end.hour ||
|
||||||
|
@ -22,4 +23,17 @@ class TimeBlock {
|
||||||
/// The child widget that will be displayed within the block.
|
/// The child widget that will be displayed within the block.
|
||||||
/// The size of the parent widget should dictate the size of the child.
|
/// The size of the parent widget should dictate the size of the child.
|
||||||
final Widget? child;
|
final Widget? child;
|
||||||
|
|
||||||
|
/// The identifier of the block that is used to collapse blocks in 1 column.
|
||||||
|
/// Leave empty or 0 if you do not want to collapse blocks.
|
||||||
|
final int id;
|
||||||
|
|
||||||
|
/// Check if two blocks overlap each other
|
||||||
|
bool collidesWith(TimeBlock block) {
|
||||||
|
var startMinute = start.hour * 60 + start.minute;
|
||||||
|
var endMinute = end.hour * 60 + end.minute;
|
||||||
|
var otherStartMinute = block.start.hour * 60 + block.start.minute;
|
||||||
|
var otherEndMinute = block.end.hour * 60 + block.end.minute;
|
||||||
|
return startMinute < otherEndMinute && endMinute > otherStartMinute;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ class Timetable extends StatefulWidget {
|
||||||
const Timetable({
|
const Timetable({
|
||||||
this.timeBlocks = const [],
|
this.timeBlocks = const [],
|
||||||
this.scrollController,
|
this.scrollController,
|
||||||
|
this.scrollPhysics,
|
||||||
this.startHour = 0,
|
this.startHour = 0,
|
||||||
this.endHour = 24,
|
this.endHour = 24,
|
||||||
this.blockWidth = 50,
|
this.blockWidth = 50,
|
||||||
|
@ -12,6 +13,8 @@ class Timetable extends StatefulWidget {
|
||||||
this.tablePaddingStart = 10,
|
this.tablePaddingStart = 10,
|
||||||
this.tablePaddingEnd = 15,
|
this.tablePaddingEnd = 15,
|
||||||
this.theme = const TableTheme(),
|
this.theme = const TableTheme(),
|
||||||
|
this.mergeBlocks = false,
|
||||||
|
this.collapseBlocks = false,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@ -45,6 +48,15 @@ class Timetable extends StatefulWidget {
|
||||||
/// The scroll controller to control the scrolling of the timetable.
|
/// The scroll controller to control the scrolling of the timetable.
|
||||||
final ScrollController? scrollController;
|
final ScrollController? scrollController;
|
||||||
|
|
||||||
|
/// The scroll physics used for the SinglechildScrollView.
|
||||||
|
final ScrollPhysics? scrollPhysics;
|
||||||
|
|
||||||
|
/// Whether or not to merge blocks in 1 column that fit below eachother.
|
||||||
|
final bool mergeBlocks;
|
||||||
|
|
||||||
|
/// Whether or not to collapse blocks in 1 column if they have the same id.
|
||||||
|
final bool collapseBlocks;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<Timetable> createState() => _TimetableState();
|
State<Timetable> createState() => _TimetableState();
|
||||||
}
|
}
|
||||||
|
@ -70,13 +82,14 @@ class _TimetableState extends State<Timetable> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
physics: const BouncingScrollPhysics(),
|
physics: widget.scrollPhysics ?? const BouncingScrollPhysics(),
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Table(
|
Table(
|
||||||
startHour: widget.startHour,
|
startHour: widget.startHour,
|
||||||
endHour: widget.endHour,
|
endHour: widget.endHour,
|
||||||
|
columnHeight: widget.hourHeight,
|
||||||
theme: widget.theme,
|
theme: widget.theme,
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
|
@ -86,20 +99,43 @@ class _TimetableState extends State<Timetable> {
|
||||||
5,
|
5,
|
||||||
),
|
),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
|
physics: widget.scrollPhysics ?? const BouncingScrollPhysics(),
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: IntrinsicHeight(
|
child: IntrinsicHeight(
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
for (var block in widget.timeBlocks) ...[
|
if (!widget.mergeBlocks && !widget.collapseBlocks) ...[
|
||||||
Block(
|
for (var block in widget.timeBlocks) ...[
|
||||||
start: block.start,
|
Block(
|
||||||
end: block.end,
|
start: block.start,
|
||||||
startHour: widget.startHour,
|
end: block.end,
|
||||||
hourHeight: widget.hourHeight,
|
startHour: widget.startHour,
|
||||||
blockWidth: widget.blockWidth,
|
hourHeight: widget.hourHeight,
|
||||||
child: block.child,
|
blockWidth: widget.blockWidth,
|
||||||
),
|
blockColor: widget.blockColor,
|
||||||
|
child: block.child,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
] else if (widget.mergeBlocks) ...[
|
||||||
|
for (var blocks
|
||||||
|
in _mergeBlocksInColumns(widget.timeBlocks)) ...[
|
||||||
|
Stack(
|
||||||
|
children: [
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
],
|
],
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: widget.tablePaddingEnd,
|
width: widget.tablePaddingEnd,
|
||||||
|
@ -116,6 +152,27 @@ class _TimetableState extends State<Timetable> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
void _scrollToFirstBlock() {
|
||||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||||
var earliestStart = widget.timeBlocks.map((block) => block.start).reduce(
|
var earliestStart = widget.timeBlocks.map((block) => block.start).reduce(
|
||||||
|
@ -134,7 +191,6 @@ class _TimetableState extends State<Timetable> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates the width of 22:22
|
|
||||||
Size _calculateTableTextSize() {
|
Size _calculateTableTextSize() {
|
||||||
return (TextPainter(
|
return (TextPainter(
|
||||||
text: TextSpan(text: '22:22', style: widget.theme.timeStyle),
|
text: TextSpan(text: '22:22', style: widget.theme.timeStyle),
|
||||||
|
|
Loading…
Reference in a new issue