From e4dacde6149f19e9423e7a68ed4bbcaf80990398 Mon Sep 17 00:00:00 2001 From: Freek van de Ven Date: Fri, 26 Aug 2022 09:04:08 +0200 Subject: [PATCH] refactor: added more doc comments and removed magic numbers --- example/analysis_options.yaml | 2 +- example/lib/main.dart | 52 ++++++++++++++--------------- lib/src/block_service.dart | 58 ++++++++++++++++++++------------- lib/src/models/table_theme.dart | 12 ++++++- lib/src/models/time_block.dart | 3 +- lib/src/timetable.dart | 42 +++++++++++++++++------- lib/src/widgets/block.dart | 27 ++++++++------- lib/src/widgets/table.dart | 36 +++++++++++++------- lib/timetable.dart | 14 +++----- test/block_service_test.dart | 7 ++-- 10 files changed, 156 insertions(+), 97 deletions(-) diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml index 89acbc8..0530d9c 100644 --- a/example/analysis_options.yaml +++ b/example/analysis_options.yaml @@ -124,7 +124,7 @@ linter: prefer_asserts_with_message: true prefer_collection_literals: true prefer_conditional_assignment: true - prefer_const_constructors: false + prefer_const_constructors: true prefer_const_constructors_in_immutables: true prefer_const_declarations: false prefer_const_literals_to_create_immutables: false diff --git a/example/lib/main.dart b/example/lib/main.dart index 5e9f6e3..0e81089 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:timetable/timetable.dart'; void main() { - runApp(MaterialApp(home: TimetableDemo())); + runApp(const MaterialApp(home: TimetableDemo())); } class TimetableDemo extends StatefulWidget { @@ -17,54 +17,54 @@ class _TimetableDemoState extends State { final ScrollController _scrollController = ScrollController(); final List blocks = [ TimeBlock( - start: TimeOfDay(hour: 14, minute: 0), - end: TimeOfDay(hour: 15, minute: 0), + start: const TimeOfDay(hour: 14, minute: 0), + end: const TimeOfDay(hour: 15, minute: 0), id: 0, ), TimeBlock( - start: TimeOfDay(hour: 8, minute: 0), - end: TimeOfDay(hour: 9, minute: 0), + start: const TimeOfDay(hour: 8, minute: 0), + end: const TimeOfDay(hour: 9, minute: 0), id: 1, ), TimeBlock( - start: TimeOfDay(hour: 9, minute: 15), - end: TimeOfDay(hour: 10, minute: 0), + start: const TimeOfDay(hour: 9, minute: 15), + end: const TimeOfDay(hour: 10, minute: 0), id: 1, ), TimeBlock( - start: TimeOfDay(hour: 10, minute: 15), - end: TimeOfDay(hour: 11, minute: 0), + start: const TimeOfDay(hour: 10, minute: 15), + end: const TimeOfDay(hour: 11, minute: 0), child: Container(color: Colors.purple, height: 300, width: 50), id: 2, ), TimeBlock( - start: TimeOfDay(hour: 6, minute: 15), - end: TimeOfDay(hour: 7, minute: 0), + start: const TimeOfDay(hour: 6, minute: 15), + end: const TimeOfDay(hour: 7, minute: 0), child: Container(color: Colors.blue, height: 300, width: 200), id: 2, ), TimeBlock( - start: TimeOfDay(hour: 18, minute: 0), - end: TimeOfDay(hour: 18, minute: 15), - child: Text('High Tea'), + start: const TimeOfDay(hour: 18, minute: 0), + end: const TimeOfDay(hour: 18, minute: 15), + child: const Text('High Tea'), id: 10, ), TimeBlock( - start: TimeOfDay(hour: 18, minute: 0), - end: TimeOfDay(hour: 18, minute: 15), - child: Text('High Tea'), + start: const TimeOfDay(hour: 18, minute: 0), + end: const TimeOfDay(hour: 18, minute: 15), + child: const Text('High Tea'), id: 10, ), TimeBlock( - start: TimeOfDay(hour: 18, minute: 0), - end: TimeOfDay(hour: 18, minute: 15), - child: Text('High Tea'), + start: const TimeOfDay(hour: 18, minute: 0), + end: const TimeOfDay(hour: 18, minute: 15), + child: const Text('High Tea'), id: 10, ), TimeBlock( - start: TimeOfDay(hour: 18, minute: 0), - end: TimeOfDay(hour: 18, minute: 15), - child: Text('High Tea'), + start: const TimeOfDay(hour: 18, minute: 0), + end: const TimeOfDay(hour: 18, minute: 15), + child: const Text('High Tea'), id: 0, ), ]; @@ -88,7 +88,7 @@ class _TimetableDemoState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('Grouped'), + const Text('Grouped'), Switch( value: _grouped, onChanged: (value) { @@ -106,7 +106,7 @@ class _TimetableDemoState extends State { timeBlocks: blocks, scrollController: _scrollController, tablePaddingStart: 0, - collapseBlocks: true, + combineBlocks: true, mergeBlocks: false, ) ] else ...[ @@ -116,7 +116,7 @@ class _TimetableDemoState extends State { timeBlocks: blocks, scrollController: _scrollController, tablePaddingStart: 0, - collapseBlocks: true, + combineBlocks: true, mergeBlocks: true, ), ], diff --git a/lib/src/block_service.dart b/lib/src/block_service.dart index 37131f0..af8b64e 100644 --- a/lib/src/block_service.dart +++ b/lib/src/block_service.dart @@ -1,26 +1,19 @@ -part of timetable; +import 'package:flutter/material.dart'; +import 'package:timetable/src/models/time_block.dart'; /// Combine blocks that have the same id and the same time. -List collapseBlocks(List blocks) { +List combineBlocksWithId(List blocks) { var newBlocks = []; var groupedBlocks = >[]; - // 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; - } + found = true; + } else { + found = _checkIfBlockWithIdExists(groupedBlocks, block); } + if (!found) { if (blocks .where( @@ -38,16 +31,24 @@ List collapseBlocks(List blocks) { } } + _combineGroupedBlocks(groupedBlocks, newBlocks); + return newBlocks; +} + +void _combineGroupedBlocks( + List> groupedBlocks, + List newBlocks, +) { 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 startMinute = block.first.start.minute + + block.first.start.hour * Duration.minutesPerHour; + var endMinute = + block.first.end.minute + block.first.end.hour * Duration.minutesPerHour; var durationMinute = (endMinute - startMinute) * block.length; var endTime = TimeOfDay( - hour: (startMinute + durationMinute) ~/ 60, - minute: (startMinute + durationMinute) % 60, + hour: (startMinute + durationMinute) ~/ Duration.minutesPerHour, + minute: (startMinute + durationMinute) % Duration.minutesPerHour, ); var newBlock = TimeBlock( start: block.first.start, @@ -61,7 +62,21 @@ List collapseBlocks(List blocks) { ); newBlocks.add(newBlock); } - return newBlocks; +} + +bool _checkIfBlockWithIdExists( + List> groupedBlocks, + TimeBlock block, +) { + for (var groupedBlock in groupedBlocks) { + if (groupedBlock.first.id == block.id && + groupedBlock.first.start == block.start && + groupedBlock.first.end == block.end) { + groupedBlock.add(block); + return true; + } + } + return false; } /// Group blocks with the same id together. @@ -95,7 +110,6 @@ List> groupBlocksById(List blocks) { /// Nerge blocks that fit below eachother into one column. List> mergeBlocksInColumns(List blocks) { var mergedBlocks = >[]; - // try to put blocks in the same column if the time doesn´t collide for (var block in blocks) { var mergeIndex = 0; diff --git a/lib/src/models/table_theme.dart b/lib/src/models/table_theme.dart index a4d399d..0fbcb72 100644 --- a/lib/src/models/table_theme.dart +++ b/lib/src/models/table_theme.dart @@ -1,9 +1,13 @@ -part of timetable; +import 'package:flutter/material.dart'; class TableTheme { + /// The [TableTheme] to style the [Table] with. Configure the line, text + /// and offsets here. const TableTheme({ this.lineColor = const Color(0x809E9E9E), this.lineHeight = 2, + this.tableTextOffset = 5, + this.lineDashFrequency = 25, this.timeStyle = const TextStyle(), }); @@ -13,6 +17,12 @@ class TableTheme { /// The height of the lines. final double lineHeight; + /// The amount of dashes on the line. + final int lineDashFrequency; + + /// Distance between the time text and the line. + final double tableTextOffset; + /// The style of the time text. final TextStyle timeStyle; } diff --git a/lib/src/models/time_block.dart b/lib/src/models/time_block.dart index 5bea5dc..ab24aef 100644 --- a/lib/src/models/time_block.dart +++ b/lib/src/models/time_block.dart @@ -1,6 +1,7 @@ -part of timetable; +import 'package:flutter/material.dart'; class TimeBlock { + /// The model used for a [Block] in a [TimeTable] which can contain a Widget. TimeBlock({ required this.start, required this.end, diff --git a/lib/src/timetable.dart b/lib/src/timetable.dart index a161b30..cf5f170 100644 --- a/lib/src/timetable.dart +++ b/lib/src/timetable.dart @@ -1,6 +1,17 @@ -part of timetable; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:timetable/src/block_service.dart'; +import 'package:timetable/src/models/table_theme.dart'; +import 'package:timetable/src/models/time_block.dart'; +import 'package:timetable/src/widgets/block.dart'; +import 'package:timetable/src/widgets/table.dart' as table; class Timetable extends StatefulWidget { + /// [Timetable] widget that displays a timetable with [TimeBlock]s. + /// The timetable automatically scrolls to the first item. + /// A [TableTheme] can be provided to customize the look of the timetable. + /// [mergeBlocks] and [combineBlocks] can be used to combine blocks + /// and merge columns of blocks when possible. const Timetable({ this.timeBlocks = const [], this.scrollController, @@ -14,7 +25,7 @@ class Timetable extends StatefulWidget { this.tablePaddingEnd = 15, this.theme = const TableTheme(), this.mergeBlocks = false, - this.collapseBlocks = true, + this.combineBlocks = true, Key? key, }) : super(key: key); @@ -56,7 +67,7 @@ class Timetable extends StatefulWidget { /// 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; + final bool combineBlocks; @override State createState() => _TimetableState(); @@ -83,8 +94,8 @@ class _TimetableState extends State { @override Widget build(BuildContext context) { List blocks; - if (widget.collapseBlocks) { - blocks = collapseBlocks(widget.timeBlocks); + if (widget.combineBlocks) { + blocks = combineBlocksWithId(widget.timeBlocks); } else { blocks = widget.timeBlocks; } @@ -93,17 +104,16 @@ class _TimetableState extends State { controller: _scrollController, child: Stack( children: [ - Table( + table.Table( startHour: widget.startHour, endHour: widget.endHour, - columnHeight: widget.hourHeight, + hourHeight: widget.hourHeight, + tableOffset: _calculateTableStart(), theme: widget.theme, ), Container( margin: EdgeInsets.only( - left: _calculateTableTextSize().width + - widget.tablePaddingStart + - 5, + left: _calculateTableStart(), ), child: SingleChildScrollView( physics: widget.scrollPhysics ?? const BouncingScrollPhysics(), @@ -112,7 +122,7 @@ class _TimetableState extends State { child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (widget.mergeBlocks || widget.collapseBlocks) ...[ + if (widget.mergeBlocks || widget.combineBlocks) ...[ for (var orderedBlocks in (widget.mergeBlocks) ? mergeBlocksInColumns(blocks) : groupBlocksById(blocks)) ...[ @@ -132,7 +142,9 @@ class _TimetableState extends State { SizedBox( width: widget.tablePaddingEnd, height: widget.hourHeight * - (widget.endHour - widget.startHour + 0.5), + (widget.endHour - + widget.startHour + + 0.5), // empty halfhour at the end ), ], ), @@ -144,6 +156,12 @@ class _TimetableState extends State { ); } + double _calculateTableStart() { + return _calculateTableTextSize().width + + widget.tablePaddingStart + + widget.theme.tableTextOffset; + } + Widget _showBlock(TimeBlock block) { return Block( start: block.start, diff --git a/lib/src/widgets/block.dart b/lib/src/widgets/block.dart index f48d6a6..7b398aa 100644 --- a/lib/src/widgets/block.dart +++ b/lib/src/widgets/block.dart @@ -1,6 +1,7 @@ -part of timetable; +import 'package:flutter/material.dart'; class Block extends StatelessWidget { + /// The [Block] to create a Widget or container in a [TimeTable]. const Block({ required this.start, required this.end, @@ -41,24 +42,28 @@ class Block extends StatelessWidget { Widget build(BuildContext context) { return Container( margin: EdgeInsets.only( - top: - (((start.hour - startHour) * 60) + start.minute) * sizePerMinute() + - linePadding, + top: (((start.hour - startHour) * Duration.minutesPerHour) + + start.minute) * + _sizePerMinute() + + linePadding, ), - height: (((end.hour - start.hour) * 60) + end.minute - start.minute) * - sizePerMinute(), + height: (((end.hour - start.hour) * Duration.minutesPerHour) + + end.minute - + start.minute) * + _sizePerMinute(), child: child ?? Container( - height: - (((end.hour - start.hour) * 60) + end.minute - start.minute) * - sizePerMinute(), + height: (((end.hour - start.hour) * Duration.minutesPerHour) + + end.minute - + start.minute) * + _sizePerMinute(), width: blockWidth, color: blockColor, ), ); } - double sizePerMinute() { - return hourHeight / 60; + double _sizePerMinute() { + return hourHeight / Duration.minutesPerHour; } } diff --git a/lib/src/widgets/table.dart b/lib/src/widgets/table.dart index 0881e81..c6b196b 100644 --- a/lib/src/widgets/table.dart +++ b/lib/src/widgets/table.dart @@ -1,17 +1,30 @@ -part of timetable; +import 'package:flutter/material.dart'; +import 'package:timetable/src/models/table_theme.dart'; class Table extends StatelessWidget { + /// The [Table] to draw an overview of timerange with corresponding hour lines const Table({ required this.startHour, required this.endHour, - this.columnHeight = 80, + this.hourHeight = 80, + this.tableOffset = 40, this.theme = const TableTheme(), Key? key, }) : super(key: key); + /// The hour the table starts at. final int startHour; + + /// The hour the table ends at. final int endHour; - final double columnHeight; + + /// The height of a single hour in the table. + final double hourHeight; + + /// The offset of the table; + final double tableOffset; + + /// The theme used by the table. final TableTheme theme; @override @@ -20,7 +33,7 @@ class Table extends StatelessWidget { children: [ for (int i = startHour; i <= endHour; i++) ...[ SizedBox( - height: i == endHour ? columnHeight / 2 : columnHeight, + height: i == endHour ? hourHeight / 2 : hourHeight, child: Column( children: [ Row( @@ -29,8 +42,8 @@ class Table extends StatelessWidget { '${i.toString().padLeft(2, '0')}:00', style: theme.timeStyle, ), - const SizedBox( - width: 5, + SizedBox( + width: theme.tableTextOffset, ), Expanded( child: Container( @@ -43,16 +56,17 @@ class Table extends StatelessWidget { if (i != endHour) ...[ const Spacer(), Container( - margin: const EdgeInsets.only( - left: 40, + margin: EdgeInsets.only( + left: tableOffset, ), height: theme.lineHeight, child: Row( children: [ - for (int i = 0; i < 25; i++) ...[ + for (int i = 0; i < theme.lineDashFrequency; i++) ...[ Container( - width: - (MediaQuery.of(context).size.width - 40) / 25, + width: (MediaQuery.of(context).size.width - + tableOffset) / + theme.lineDashFrequency, height: theme.lineHeight, color: i.isEven ? theme.lineColor : Colors.transparent, diff --git a/lib/timetable.dart b/lib/timetable.dart index d7494b1..e90dada 100644 --- a/lib/timetable.dart +++ b/lib/timetable.dart @@ -1,11 +1,7 @@ library timetable; -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; - -part 'src/timetable.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/widgets/block.dart'; +export 'src/models/table_theme.dart'; +export 'src/models/time_block.dart'; +export 'src/timetable.dart'; +export 'src/widgets/block.dart'; +export 'src/widgets/table.dart'; diff --git a/test/block_service_test.dart b/test/block_service_test.dart index ac4e61e..f37a24f 100644 --- a/test/block_service_test.dart +++ b/test/block_service_test.dart @@ -1,9 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:timetable/src/block_service.dart'; import 'package:timetable/timetable.dart'; void main() { - group('test collapseBlocks', () { + group('test combineBlocksWithId', () { test('new block creation success', () { //Arrange var blocks = [ @@ -25,7 +26,7 @@ void main() { ]; //Act - var result = collapseBlocks(blocks); + var result = combineBlocksWithId(blocks); //Assert expect(result.length, 2); @@ -51,7 +52,7 @@ void main() { ]; //Act - var result = collapseBlocks(blocks); + var result = combineBlocksWithId(blocks); //Assert expect(result.length, 2);