mirror of
https://github.com/Iconica-Development/flutter_agenda.git
synced 2025-05-18 21:03:45 +02:00
commit
0ab096e0a9
12 changed files with 408 additions and 22 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -28,3 +28,5 @@ migrate_working_dir/
|
|||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
|
||||
coverage/
|
|
@ -1,3 +1,3 @@
|
|||
## 0.0.1
|
||||
## [0.0.1] - 2 September 2022
|
||||
|
||||
* TODO: Describe initial release.
|
||||
* Initial release.
|
||||
|
|
22
README.md
22
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>
|
||||
|
||||
<details>
|
||||
<summary>PLATFORM</summary>
|
||||
|
||||
specific platform steps
|
||||
Supports all Flutter platforms.
|
||||
|
||||
</details>
|
||||
## 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
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.iconica.agenda_example">
|
||||
<application
|
||||
android:label="agenda_example"
|
||||
android:label="Flutter Agenda Example"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:agenda/agenda.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() {
|
||||
|
@ -9,6 +10,73 @@ class AgendaDemo extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(body: Text('AgendaDemo'));
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: AgendaWidget(
|
||||
header: Text('Agenda', style: Theme.of(context).textTheme.headline6),
|
||||
blockWidth: 50,
|
||||
highlightToday: false,
|
||||
blocks: [
|
||||
AgendaEvent(
|
||||
start: DateTime.now().subtract(const Duration(hours: 3)),
|
||||
end: DateTime.now().add(const Duration(hours: 2)),
|
||||
),
|
||||
AgendaEvent(
|
||||
start: DateTime.now().subtract(const Duration(hours: 2)),
|
||||
end: DateTime.now().add(const Duration(hours: 1)),
|
||||
),
|
||||
AgendaEvent(
|
||||
start: DateTime.now().subtract(const Duration(hours: 1)),
|
||||
end: DateTime.now().add(const Duration(hours: 1)),
|
||||
),
|
||||
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'),
|
||||
),
|
||||
AgendaEvent(
|
||||
start: DateTime.now().add(const Duration(hours: 3)),
|
||||
end: DateTime.now().add(const Duration(hours: 4)),
|
||||
id: 4,
|
||||
content: const Text('event 6'),
|
||||
),
|
||||
AgendaEvent(
|
||||
start: DateTime.now().add(const Duration(days: 1)),
|
||||
end: DateTime.now()
|
||||
.add(const Duration(days: 1))
|
||||
.add(const Duration(hours: 2)),
|
||||
),
|
||||
AgendaEvent(
|
||||
start: DateTime.now().subtract(const Duration(hours: 2)),
|
||||
end: DateTime.now().add(const Duration(hours: 1)),
|
||||
),
|
||||
AgendaEvent(
|
||||
start: DateTime.now().add(const Duration(days: 2)),
|
||||
end: DateTime.now().add(const Duration(days: 3)),
|
||||
),
|
||||
],
|
||||
disabledDates: [
|
||||
// yesterday
|
||||
DateTime.now().subtract(const Duration(days: 1)),
|
||||
],
|
||||
highlightedDates: [
|
||||
// tomorrow
|
||||
DateTime.now().add(const Duration(days: 1)),
|
||||
],
|
||||
theme: const AgendaTheme(
|
||||
tableTheme: TableTheme(
|
||||
blockPaddingBetween: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,6 +62,15 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_date_time_picker:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: stable
|
||||
resolved-ref: "6b3f1f5d12c1762bc208a1f1a4b9384dcb3369d4"
|
||||
url: "git@github.com:Iconica-Development/flutter_date_time_picker.git"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
@ -74,6 +83,13 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: intl
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.17.0"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -156,6 +172,15 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.12"
|
||||
timetable:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: "v0.0.2"
|
||||
resolved-ref: "62fdaa443818bd646058b536a12304725d1619fd"
|
||||
url: "git@github.com:Iconica-Development/timetable.git"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -165,4 +190,4 @@ packages:
|
|||
version: "2.1.2"
|
||||
sdks:
|
||||
dart: ">=2.18.0 <3.0.0"
|
||||
flutter: ">=1.17.0"
|
||||
flutter: ">=2.0.0"
|
||||
|
|
|
@ -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';
|
||||
|
|
152
lib/src/agenda.dart
Normal file
152
lib/src/agenda.dart
Normal file
|
@ -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<AgendaEvent> blocks;
|
||||
|
||||
/// The highlighted dates that are displayed in the agenda
|
||||
final List<DateTime> highlightedDates;
|
||||
|
||||
/// The disabled dates that are displayed in the agenda
|
||||
final List<DateTime> 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<AgendaWidget> createState() => _AgendaWidgetState();
|
||||
}
|
||||
|
||||
class _AgendaWidgetState extends State<AgendaWidget> {
|
||||
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<TimeBlock> _filterEventsOnDay(List<AgendaEvent> 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();
|
||||
}
|
||||
}
|
25
lib/src/models/agenda_event.dart
Normal file
25
lib/src/models/agenda_event.dart
Normal file
|
@ -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;
|
||||
}
|
19
lib/src/models/agenda_theme.dart
Normal file
19
lib/src/models/agenda_theme.dart
Normal file
|
@ -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;
|
||||
}
|
14
pubspec.yaml
14
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:
|
||||
flutter:
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue