p_calendar 0.0.1-dev+1 copy "p_calendar: ^0.0.1-dev+1" to clipboard
p_calendar: ^0.0.1-dev+1 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? _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}) ',
                                  )
                            ],
                          ),
                        ),
                      );
                    },
                  ),
                  ListenableBuilder(
                    listenable: _calendarController,
                    builder: (context, child) {
                      final bool isDayView =
                          _calendarController.viewType == EventCalendarType.day;
                      return Row(
                        mainAxisAlignment: MainAxisAlignment.end,
                        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),
                          TextButton(
                            onPressed: _calendarController.today,
                            child: const Text('Today'),
                          ),
                          const SizedBox(width: 8.0),
                          IconButton(
                            tooltip:
                                isDayView ? 'Previous day' : 'Previous week',
                            onPressed: _calendarController.previousWeek,
                            icon: const Icon(Icons.keyboard_arrow_left),
                          ),
                          const SizedBox(width: 8.0),
                          IconButton(
                            tooltip: isDayView ? 'Next day' : 'Next week',
                            onPressed: _calendarController.nextWeek,
                            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: 15,
                  events: _events,
                  unavailableRanges: <DateRange>[
                    (
                      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(days: 2, hours: 3))
                    ),
                  ],
                  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