Merge pull request #2 from Iconica-Development/v-0.0.1

Version 0.0.1
This commit is contained in:
Freek van de Ven 2022-08-26 11:17:36 +02:00 committed by GitHub
commit f0ffd233c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 156 additions and 97 deletions

View file

@ -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

View file

@ -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<TimetableDemo> {
final ScrollController _scrollController = ScrollController();
final List<TimeBlock> 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<TimetableDemo> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Grouped'),
const Text('Grouped'),
Switch(
value: _grouped,
onChanged: (value) {
@ -106,7 +106,7 @@ class _TimetableDemoState extends State<TimetableDemo> {
timeBlocks: blocks,
scrollController: _scrollController,
tablePaddingStart: 0,
collapseBlocks: true,
combineBlocks: true,
mergeBlocks: false,
)
] else ...[
@ -116,7 +116,7 @@ class _TimetableDemoState extends State<TimetableDemo> {
timeBlocks: blocks,
scrollController: _scrollController,
tablePaddingStart: 0,
collapseBlocks: true,
combineBlocks: true,
mergeBlocks: true,
),
],

View file

@ -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<TimeBlock> collapseBlocks(List<TimeBlock> blocks) {
List<TimeBlock> combineBlocksWithId(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;
}
found = true;
} else {
found = _checkIfBlockWithIdExists(groupedBlocks, block);
}
if (!found) {
if (blocks
.where(
@ -38,16 +31,24 @@ List<TimeBlock> collapseBlocks(List<TimeBlock> blocks) {
}
}
_combineGroupedBlocks(groupedBlocks, newBlocks);
return newBlocks;
}
void _combineGroupedBlocks(
List<List<TimeBlock>> groupedBlocks,
List<TimeBlock> 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<TimeBlock> collapseBlocks(List<TimeBlock> blocks) {
);
newBlocks.add(newBlock);
}
return newBlocks;
}
bool _checkIfBlockWithIdExists(
List<List<TimeBlock>> 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<List<TimeBlock>> groupBlocksById(List<TimeBlock> blocks) {
/// 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;

View file

@ -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;
}

View file

@ -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,

View file

@ -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<Timetable> createState() => _TimetableState();
@ -83,8 +94,8 @@ class _TimetableState extends State<Timetable> {
@override
Widget build(BuildContext context) {
List<TimeBlock> 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<Timetable> {
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<Timetable> {
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<Timetable> {
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<Timetable> {
);
}
double _calculateTableStart() {
return _calculateTableTextSize().width +
widget.tablePaddingStart +
widget.theme.tableTextOffset;
}
Widget _showBlock(TimeBlock block) {
return Block(
start: block.start,

View file

@ -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;
}
}

View file

@ -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,

View file

@ -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';

View file

@ -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);