diff --git a/.gitignore b/.gitignore
index d920ae6..4365314 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,5 @@ migrate_working_dir/
.dart_tool/
.packages
build/
+
+coverage/
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 41cc7d8..71d2704 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,3 @@
-## 0.0.1
+## [0.0.1] - 2 September 2022
-* TODO: Describe initial release.
+* Initial release.
diff --git a/README.md b/README.md
index aa5cf48..5f8f2ee 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,20 @@
[](https://github.com/Iconica-Development) [](URL TO GITHUB ACTIONS) [](https://github.com/tenhobi/effective_dart)
-Short description of what your package is, why you created it. What issues it fixes and how it works. Also mention the available platforms
+# Agenda
+A Flutter package for creating an agenda that displays events per day with an included calendar for picking the date. Multiple events at the same time are alongside each other. There is also the option to stack multiple items at the same time.
+The underlying datepicker widget supports marking dates and disabling dates.
-## Setup
-What setup steps are neccesarry and why>
-
-PLATFORM
-
-specific platform steps
+Supports all Flutter platforms.
-
+## Usage
-## How to use
+To use this package, add `agenda` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels).
-How can we use the package descibe the most common ways with examples in
-```dart
- codeblocks
-```
+### Example
+
+See [Example Code](example/lib/main.dart) for more info.
## Issues
diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml
index 55ece1e..19f44cd 100644
--- a/example/android/app/src/main/AndroidManifest.xml
+++ b/example/android/app/src/main/AndroidManifest.xml
@@ -1,7 +1,7 @@
=2.18.0 <3.0.0"
- flutter: ">=1.17.0"
+ flutter: ">=2.0.0"
diff --git a/lib/agenda.dart b/lib/agenda.dart
index e7e0021..4eba1ba 100644
--- a/lib/agenda.dart
+++ b/lib/agenda.dart
@@ -1 +1,7 @@
library agenda;
+
+export 'package:agenda/src/agenda.dart';
+export 'package:agenda/src/models/agenda_event.dart';
+export 'package:agenda/src/models/agenda_theme.dart';
+
+export 'package:timetable/timetable.dart';
diff --git a/lib/src/agenda.dart b/lib/src/agenda.dart
new file mode 100644
index 0000000..01b65e4
--- /dev/null
+++ b/lib/src/agenda.dart
@@ -0,0 +1,152 @@
+import 'package:agenda/src/models/agenda_event.dart';
+import 'package:agenda/src/models/agenda_theme.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_date_time_picker/flutter_date_time_picker.dart';
+import 'package:timetable/timetable.dart';
+
+class AgendaWidget extends StatefulWidget {
+ /// [AgendaWidget] is a widget that displays a timetable with events.
+ /// It is stateful and sorts the events based on the selected date.
+ /// All styling can be configured through the [AgendaTheme] class.
+ const AgendaWidget({
+ required this.blocks,
+ this.highlightedDates = const [],
+ this.disabledDates = const [],
+ this.initialDate,
+ this.header,
+ this.scrollController,
+ this.scrollPhysics,
+ this.onTapDay,
+ this.startHour = 0,
+ this.endHour = 24,
+ this.hourHeight = 80,
+ this.highlightToday = true,
+ this.blockWidth = 50,
+ this.blockColor = const Color(0x80FF0000),
+ this.theme = const AgendaTheme(),
+ super.key,
+ });
+
+ /// Header widget that is displayed above the datepicker.
+ final Widget? header;
+
+ /// The blocks that are displayed in the agenda
+ final List blocks;
+
+ /// The highlighted dates that are displayed in the agenda
+ final List highlightedDates;
+
+ /// The disabled dates that are displayed in the agenda
+ final List disabledDates;
+
+ /// The date that is initially selected.
+ final DateTime? initialDate;
+
+ /// Function called when the user taps on a day in the datepicker.
+ final Function(DateTime)? onTapDay;
+
+ /// Whether to highlight the current date in the agenda.
+ final bool highlightToday;
+
+ /// Hour at which the timetable starts.
+ final int startHour;
+
+ /// Hour at which the timetable ends.
+ final int endHour;
+
+ /// The heigh of one hour in the timetable.
+ final double hourHeight;
+
+ /// The width of the agendaItem if there is no child
+ final double blockWidth;
+
+ /// The color of the agendaItem if there is no child
+ final Color blockColor;
+
+ /// The theme used by the agenda.
+ /// The [TableTheme] used by the timetable is included.
+ final AgendaTheme theme;
+
+ /// The scroll controller to control the scrolling of the timetable.
+ final ScrollController? scrollController;
+
+ /// The scroll physics used for the SinglechildScrollView.
+ final ScrollPhysics? scrollPhysics;
+
+ @override
+ State createState() => _AgendaWidgetState();
+}
+
+class _AgendaWidgetState extends State {
+ late DateTime _selectedDate;
+
+ @override
+ void initState() {
+ super.initState();
+ _selectedDate = widget.initialDate ?? DateTime.now();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ var events = _filterEventsOnDay(widget.blocks, _selectedDate);
+ return DateTimePicker(
+ initialDate: _selectedDate,
+ pickTime: false,
+ highlightToday: widget.highlightToday,
+ header: widget.header,
+ onTapDay: (p0) {
+ setState(() {
+ _selectedDate = p0;
+ });
+ },
+ disabledDates: widget.disabledDates,
+ markedDates: widget.highlightedDates,
+ dateTimePickerTheme: widget.theme.timePickerTheme,
+ child: Timetable(
+ scrollPhysics: widget.scrollPhysics,
+ scrollController: widget.scrollController,
+ blockColor: widget.blockColor,
+ blockWidth: widget.blockWidth,
+ hourHeight: widget.hourHeight,
+ startHour: widget.startHour,
+ endHour: widget.endHour,
+ timeBlocks: events,
+ theme: widget.theme.tableTheme,
+ combineBlocks: true,
+ mergeBlocks: true,
+ ),
+ );
+ }
+
+ List _filterEventsOnDay(List events, DateTime day) {
+ return events
+ .where(
+ (e) =>
+ (e.start.day == day.day &&
+ e.start.month == day.month &&
+ e.start.year == day.year) ||
+ (e.end.day == day.day &&
+ e.end.month == day.month &&
+ e.end.year == day.year),
+ )
+ .map(
+ (e) => TimeBlock(
+ start: (e.start.day != day.day)
+ ? TimeOfDay(hour: widget.startHour, minute: 0)
+ : TimeOfDay(
+ hour: e.start.hour,
+ minute: e.start.minute,
+ ),
+ end: (e.end.day != day.day)
+ ? TimeOfDay(hour: widget.endHour, minute: 0)
+ : TimeOfDay(
+ hour: e.end.hour,
+ minute: e.end.minute,
+ ),
+ id: e.id ?? 0,
+ child: e.content,
+ ),
+ )
+ .toList();
+ }
+}
diff --git a/lib/src/models/agenda_event.dart b/lib/src/models/agenda_event.dart
new file mode 100644
index 0000000..6301c97
--- /dev/null
+++ b/lib/src/models/agenda_event.dart
@@ -0,0 +1,25 @@
+import 'package:flutter/material.dart';
+
+class AgendaEvent {
+ /// The model used for a single event in the [AgendaWidget].
+ /// AgendaEvent can be multiple days long.
+ AgendaEvent({
+ required this.start,
+ required this.end,
+ this.id,
+ this.content,
+ }) : assert(start.isBefore(end), 'start must be before end');
+
+ /// The start time of the event.
+ final DateTime start;
+
+ /// The end time of the event.
+ final DateTime end;
+
+ ///
+ final Widget? content;
+
+ /// The identifier of the event that is used to combine events
+ /// with the same id. Leave empty or 0 if you don't want to combine events.
+ final int? id;
+}
diff --git a/lib/src/models/agenda_theme.dart b/lib/src/models/agenda_theme.dart
new file mode 100644
index 0000000..d8ecd14
--- /dev/null
+++ b/lib/src/models/agenda_theme.dart
@@ -0,0 +1,19 @@
+import 'package:flutter_date_time_picker/flutter_date_time_picker.dart';
+import 'package:timetable/timetable.dart';
+
+class AgendaTheme {
+ /// [AgendaTheme] is a class that contains all styling options
+ /// for the [AgendaWidget].
+ /// The underlying [TableTheme] is used for the timetable.
+ /// The [DateTimePickerTheme] is used for the datepicker.
+ const AgendaTheme({
+ this.tableTheme = const TableTheme(),
+ this.timePickerTheme = const DateTimePickerTheme(),
+ });
+
+ /// The theme for the timetable.
+ final TableTheme tableTheme;
+
+ /// The theme for the datetime picker.
+ final DateTimePickerTheme timePickerTheme;
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index e22da7f..467d7c8 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,17 +3,27 @@ description: Agenda widget with timetable
version: 0.0.1
homepage: https://github.com/Iconica-Development/agenda
+publish_to: none
+
environment:
- sdk: '>=2.18.0 <3.0.0'
+ sdk: ">=2.18.0 <3.0.0"
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
+ flutter_date_time_picker:
+ git:
+ url: git@github.com:Iconica-Development/flutter_date_time_picker.git
+ ref: stable
+ timetable:
+ git:
+ url: git@github.com:Iconica-Development/timetable.git
+ ref: v0.0.2
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
-flutter:
\ No newline at end of file
+flutter:
diff --git a/test/agenda_test.dart b/test/agenda_test.dart
index fe61a56..7b1d6c2 100644
--- a/test/agenda_test.dart
+++ b/test/agenda_test.dart
@@ -1,7 +1,90 @@
+import 'package:agenda/agenda.dart';
+import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
- test('test', () {
- expect(true, true);
+ testWidgets('header is shown', (tester) async {
+ // Act
+ await tester.pumpWidget(
+ const MaterialApp(
+ home: Scaffold(
+ body: AgendaWidget(
+ header: Text('Agenda'),
+ blocks: [],
+ ),
+ ),
+ ),
+ );
+
+ // Assert
+ expect(find.text('Agenda'), findsOneWidget);
+ });
+
+ testWidgets('blocks are shown', (tester) async {
+ // Act
+ await tester.pumpWidget(
+ MaterialApp(
+ home: Scaffold(
+ body: AgendaWidget(
+ blocks: [
+ AgendaEvent(
+ start: DateTime.now().subtract(const Duration(days: 3)),
+ end: DateTime.now().subtract(const Duration(days: 2)),
+ content: const Text('not shown'),
+ ),
+ AgendaEvent(
+ start: DateTime.now().add(const Duration(hours: 3)),
+ end: DateTime.now().add(const Duration(hours: 4)),
+ id: 4,
+ content: const Text('event 4'),
+ ),
+ AgendaEvent(
+ start: DateTime.now().add(const Duration(hours: 3)),
+ end: DateTime.now().add(const Duration(hours: 4)),
+ id: 4,
+ content: const Text('event 5'),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+
+ // Assert
+ expect(find.text('event 4'), findsOneWidget);
+ expect(find.text('event 5'), findsOneWidget);
+ expect(find.text('not shown'), findsNothing);
+ });
+
+ testWidgets('event is removed on tap', (tester) async {
+ // Act
+ await tester.pumpWidget(
+ MaterialApp(
+ home: Scaffold(
+ body: AgendaWidget(
+ initialDate: DateTime.now(),
+ blocks: [
+ AgendaEvent(
+ start: DateTime.now(),
+ end: DateTime.now().add(const Duration(days: 1)),
+ content: const Text('single day event'),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ var firstDay = find.text('single day event');
+ expect(firstDay, findsOneWidget);
+
+ var nextDay = DateTime.now().add(const Duration(days: 2));
+ // if nextDay is monday or tuesday we need to go back 4 days
+ if (nextDay.weekday == DateTime.tuesday ||
+ nextDay.weekday == DateTime.monday) {
+ nextDay = nextDay.subtract(const Duration(days: 4));
+ }
+ await tester.tap(find.text(nextDay.day.toString()));
+ await tester.pump();
+ expect(find.text('single day event'), findsNothing);
});
}