p_calendar 0.0.1-dev.6 copy "p_calendar: ^0.0.1-dev.6" to clipboard
p_calendar: ^0.0.1-dev.6 copied to clipboard

An event calendar widget

example/lib/main.dart

import 'dart:math';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:p_calendar/p_calendar.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await EventCalendar.disableContextMenu();
  runApp(const App());
}

final BorderSide borderSide = BorderSide(color: Colors.grey.shade300);

class App extends StatefulWidget {
  const App({super.key});

  static AppState of(BuildContext context) {
    return context.findRootAncestorStateOfType<AppState>()!;
  }

  @override
  State<App> createState() => AppState();
}

class AppState extends State<App> {
  ThemeMode _themeMode = ThemeMode.system;

  ThemeMode get themeMode => _themeMode;

  set themeMode(ThemeMode mode) {
    if (mode != _themeMode) {
      setState(() {
        _themeMode = mode;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Event Calendar',
      themeMode: _themeMode,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.blue,
          secondary: Colors.orange,
          secondaryContainer: Colors.purple,
        ),
        dividerColor: Colors.black12,
        useMaterial3: true,
      ),
      darkTheme: ThemeData(
        brightness: Brightness.dark,
        colorScheme: const ColorScheme.dark(
          primary: Colors.cyan,
          secondary: Colors.deepPurpleAccent,
        ),
        dividerColor: Colors.white10,
        useMaterial3: true,
      ),
      debugShowCheckedModeBanner: false,
      home: const CalendarPage(),
    );
  }
}

class CalendarPage extends StatefulWidget {
  const CalendarPage({super.key});

  @override
  State<CalendarPage> createState() => _CalendarPageState();
}

class _CalendarPageState extends State<CalendarPage> {
  final OverlayPortalController _overlayController = OverlayPortalController();
  final EventCalendarController _calendarController = EventCalendarController();
  final FocusNode _calendarFocusNode = FocusNode();

  final DateFormat _monthDateFormat = DateFormat.MMMM();

  List<CalendarEvent> _events = <CalendarEvent>[
    CalendarEvent(
      id: 'test',
      start: DateTime.now().startOfDay.add(const Duration(hours: 5)),
      end: DateTime.now().startOfDay.add(const Duration(hours: 5, minutes: 30)),
    )
  ];
  CalendarEvent? _event;
  Rect? _eventRect;

  @override
  void dispose() {
    _calendarController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return OverlayPortal(
      controller: _overlayController,
      overlayChildBuilder: (BuildContext context) {
        return Positioned.fill(
          child: Stack(
            children: <Widget>[
              Positioned.fill(
                child: GestureDetector(
                  behavior: HitTestBehavior.opaque,
                  onTap: () {
                    _overlayController.hide();
                    _eventRect = null;
                  },
                ),
              ),
              if (_eventRect case Rect rect)
                Positioned(
                  top: rect.topRight.dy + _calendarFocusNode.offset.dy,
                  left: rect.topRight.dx.clamp(
                      0.0, MediaQuery.sizeOf(context).width - rect.width),
                  child: Card(
                    color: Theme.of(context).colorScheme.primary,
                    child: Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: Column(
                        mainAxisSize: MainAxisSize.min,
                        crossAxisAlignment: CrossAxisAlignment.end,
                        children: <Widget>[
                          IconButton(
                            onPressed: () {
                              final CalendarEvent? event = _event;
                              setState(() {
                                _events = <CalendarEvent>[..._events]
                                  ..remove(event);
                              });
                              _overlayController.hide();
                              _event = null;
                              _eventRect = null;
                            },
                            icon: const Icon(Icons.delete),
                          ),
                          Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: <Widget>[
                              const SizedBox(height: 8.0),
                              Text('Start: ${_event!.start.toIso8601String()}'),
                              const SizedBox(height: 8.0),
                              Text('End: ${_event!.end.toIso8601String()}'),
                              const SizedBox(height: 8.0),
                              Text(
                                'Duration: ${_event!.end.difference(_event!.start)}',
                              ),
                            ],
                          )
                        ],
                      ),
                    ),
                  ),
                ),
            ],
          ),
        );
      },
      child: Scaffold(
        body: Column(
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: <Widget>[
                  ListenableBuilder(
                    listenable: _calendarController,
                    builder: (BuildContext context, Widget? child) {
                      return DecoratedBox(
                        decoration: BoxDecoration(
                          border: Border(
                              bottom: BorderSide(
                            color: Theme.of(context).colorScheme.secondary,
                          )),
                        ),
                        child: RichText(
                          text: TextSpan(
                            style: Theme.of(context).textTheme.titleSmall,
                            children: <InlineSpan>[
                              TextSpan(
                                text: _monthDateFormat
                                    .format(_calendarController.firstDayOfView),
                              ),
                              if (_calendarController.viewType !=
                                  EventCalendarType.day)
                                if (_calendarController.firstDayOfView.endOfWeek
                                    case DateTime endOfWeek
                                    when endOfWeek.month !=
                                        _calendarController.firstDayOfView
                                            .month) ...<InlineSpan>[
                                  if (_calendarController.firstDayOfView.year !=
                                      endOfWeek.year)
                                    TextSpan(
                                      text:
                                          ' (${_calendarController.firstDayOfView.year}) ',
                                    ),
                                  TextSpan(
                                    text:
                                        ' / ${_monthDateFormat.format(endOfWeek)}',
                                  ),
                                  TextSpan(
                                    text: ' (${endOfWeek.year}) ',
                                  ),
                                ] else
                                  TextSpan(
                                    text:
                                        ' (${_calendarController.firstDayOfView.year}) ',
                                  )
                            ],
                          ),
                        ),
                      );
                    },
                  ),
                  Flexible(
                    child: ListenableBuilder(
                      listenable: _calendarController,
                      builder: (BuildContext context, Widget? child) {
                        final bool isDayView = _calendarController.viewType ==
                            EventCalendarType.day;
                        return Wrap(
                          alignment: WrapAlignment.center,
                          crossAxisAlignment: WrapCrossAlignment.center,
                          runSpacing: 8.0,
                          children: <Widget>[
                            Builder(
                              builder: (BuildContext context) {
                                return SizedBox(
                                  width: 100.0,
                                  child: DropdownButtonFormField<ThemeMode>(
                                    decoration: const InputDecoration(
                                      label: Text('Theme'),
                                    ),
                                    onChanged: (ThemeMode? value) {
                                      if (value != null) {
                                        App.of(context).themeMode = value;
                                      }
                                    },
                                    value: App.of(context).themeMode,
                                    items: [
                                      for (final ThemeMode mode
                                          in ThemeMode.values)
                                        DropdownMenuItem(
                                          value: mode,
                                          child: Text(mode.name),
                                        )
                                    ],
                                  ),
                                );
                              },
                            ),
                            const SizedBox(width: 8.0),
                            SizedBox(
                              width: 150.0,
                              child: DropdownButtonFormField<EventCalendarType>(
                                decoration: const InputDecoration(
                                  label: Text('View type'),
                                ),
                                onChanged: (EventCalendarType? value) {
                                  if (value != null) {
                                    _calendarController.viewType = value;
                                  }
                                },
                                value: _calendarController.viewType,
                                items: [
                                  for (final EventCalendarType type
                                      in EventCalendarType.values)
                                    DropdownMenuItem(
                                      value: type,
                                      child: Text(type.name),
                                    )
                                ],
                              ),
                            ),
                            const SizedBox(width: 8.0),
                            Row(
                              mainAxisSize: MainAxisSize.min,
                              crossAxisAlignment: CrossAxisAlignment.center,
                              children: [
                                TextButton(
                                  onPressed: _calendarController.today,
                                  child: const Text('Today'),
                                ),
                                const SizedBox(width: 8.0),
                                IconButton(
                                  tooltip: isDayView
                                      ? 'Previous day'
                                      : 'Previous week',
                                  onPressed:
                                      _calendarController.jumpToPreviousPage,
                                  icon: const Icon(Icons.keyboard_arrow_left),
                                ),
                                const SizedBox(width: 8.0),
                                IconButton(
                                  tooltip: isDayView ? 'Next day' : 'Next week',
                                  onPressed: _calendarController.jumpToNextPage,
                                  icon: const Icon(Icons.keyboard_arrow_right),
                                ),
                              ],
                            ),
                          ],
                        );
                      },
                    ),
                  ),
                ],
              ),
            ),
            const Divider(thickness: 1.0, height: 1.0),
            Flexible(
              child: Focus(
                focusNode: _calendarFocusNode,
                child: EventCalendar(
                  calendarTheme:
                      EventCalendarTheme.fromThemeData(Theme.of(context))
                          .copyWith(
                    unavailableSlotsColor:
                        Theme.of(context).brightness == Brightness.dark
                            ? Colors.grey.shade800
                            : Colors.grey.shade300,
                    dividerColor:
                        Theme.of(context).brightness == Brightness.dark
                            ? Colors.grey.shade700
                            : Colors.grey.shade500,
                  ),
                  controller: _calendarController,
                  minutesPerSlot: 60,
                  events: _events,
                  availableRanges: <DateRange>[
                    for (var i = 0; i < 7; i += 1)
                      (
                        start: DateTime.now().addDays(i).startOfDay,
                        end: DateTime.now().addDays(1 + i).startOfDay
                      )
                    // (
                    //   start: DateTime.now().startOfDay,
                    //   end: DateTime.now()
                    //       .startOfDay
                    //       .add(const Duration(hours: 1))
                    // ),
                    // (
                    //   start: DateTime.now()
                    //       .startOfDay
                    //       .add(const Duration(hours: 5)),
                    //   end: DateTime.now()
                    //       .startOfDay
                    //       .add(const Duration(hours: 8))
                    // ),
                    // (
                    //   start: DateTime.now()
                    //       .startOfDay
                    //       .add(const Duration(hours: 20)),
                    //   end: DateTime.now()
                    //       .startOfDay
                    //       .add(const Duration(hours: 23))
                    // ),
                  ],
                  onEventCreated: (DateRange event) async {
                    setState(() {
                      _events = <CalendarEvent>[
                        ..._events,
                        CalendarEvent(
                          id: event.start.millisecondsSinceEpoch.toString(),
                          start: event.start,
                          end: event.end,
                          color: Color(0xff000000 |
                              (Random().nextDouble() * 0xffffff).toInt()),
                        )
                      ];
                    });
                  },
                  canAddEvent: (DateRange event) {
                    if (event.start.hour case 1 || 13) {
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(
                          content: const Text(
                            'Events at 1 AM/PM are prohibited',
                          ),
                          backgroundColor:
                              Theme.of(context).colorScheme.errorContainer,
                        ),
                      );
                      return SynchronousFuture<bool>(false);
                    }
                    return SynchronousFuture<bool>(true);
                  },
                  onEventTap: (CalendarEvent event, Rect rect) async {
                    _event = event;
                    _eventRect = rect;

                    if (MediaQuery.sizeOf(context).width <= 600) {
                      await showDialog(
                        context: context,
                        builder: (BuildContext context) {
                          return AlertDialog(
                            content: Column(
                              mainAxisSize: MainAxisSize.min,
                              crossAxisAlignment: CrossAxisAlignment.end,
                              children: <Widget>[
                                IconButton(
                                  onPressed: () {
                                    final CalendarEvent? event = _event;
                                    setState(() {
                                      _events = <CalendarEvent>[..._events]
                                        ..remove(event);
                                    });
                                    _event = null;
                                    _eventRect = null;
                                    Navigator.pop(context);
                                  },
                                  icon: const Icon(Icons.delete),
                                ),
                                Column(
                                  crossAxisAlignment: CrossAxisAlignment.start,
                                  children: <Widget>[
                                    const SizedBox(height: 8.0),
                                    Text(
                                        'Start: ${_event!.start.toIso8601String()}'),
                                    const SizedBox(height: 8.0),
                                    Text(
                                        'End: ${_event!.end.toIso8601String()}'),
                                    const SizedBox(height: 8.0),
                                    Text(
                                      'Duration: ${_event!.end.difference(_event!.start)}',
                                    ),
                                  ],
                                )
                              ],
                            ),
                          );
                        },
                      );
                      return;
                    }

                    _overlayController.show();
                  },
                  dayHeaderBuilder: (DateTime date) {
                    final dayWidget = Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: Text(
                        DateFormat('dd').format(date),
                        textAlign: TextAlign.center,
                      ),
                    );
                    return Center(
                      child: Padding(
                        padding: const EdgeInsets.all(8.0),
                        child: Column(
                          children: [
                            Text(
                              DateFormat('EEEE').format(date),
                              textAlign: TextAlign.center,
                            ),
                            if (date.isToday())
                              DecoratedBox(
                                decoration: const BoxDecoration(
                                  shape: BoxShape.circle,
                                  color: Colors.purple,
                                ),
                                child: dayWidget,
                              )
                            else
                              dayWidget
                          ],
                        ),
                      ),
                    );
                  },
                  timeHeaderBuilder: (DateTime date) {
                    return Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: Text(
                        DateFormat('HH:mm').format(date),
                        textAlign: TextAlign.center,
                      ),
                    );
                  },
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
2
likes
0
points
27
downloads

Publisher

verified publisherptsakoulis.com

Weekly Downloads

An event calendar widget

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter

More

Packages that depend on p_calendar