Merge pull request #1 from Iconica-Development/v0.0.1

V0.0.1
This commit is contained in:
Gorter-dev 2022-09-02 16:31:58 +02:00 committed by GitHub
commit 0ab096e0a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 408 additions and 22 deletions

2
.gitignore vendored
View file

@ -28,3 +28,5 @@ migrate_working_dir/
.dart_tool/
.packages
build/
coverage/

View file

@ -1,3 +1,3 @@
## 0.0.1
## [0.0.1] - 2 September 2022
* TODO: Describe initial release.
* Initial release.

View file

@ -1,24 +1,20 @@
[![pub package](https://img.shields.io/pub/v/[PACKAGE NAME ON PUB].svg)](https://github.com/Iconica-Development) [![Build status](https://github.com/Iconica-Development/agenda)](URL TO GITHUB ACTIONS) [![style: effective dart](https://img.shields.io/badge/style-effective_dart-40c4ff.svg)](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

View file

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

View file

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

View file

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

View file

@ -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
View 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();
}
}

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

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

View file

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

View file

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