flutter_customizable_calendar 0.1.1 copy "flutter_customizable_calendar: ^0.1.1" to clipboard
flutter_customizable_calendar: ^0.1.1 copied to clipboard

a feature-rich Flutter package that offers highly customizable calendar views for displaying days, weeks, and months.

example/lib/main.dart

import 'package:example/bloc/list_cubit/list_cubit.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_customizable_calendar/flutter_customizable_calendar.dart';
import 'package:uuid/uuid.dart';

void main() => runApp(const App());

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

  @override
  Widget build(BuildContext context) {
    final today = DateUtils.dateOnly(DateTime.now());
    final breaks = List.generate(
      7,
      (index) {
        final dayDate =
            DateUtils.addDaysToDate(today, index - today.weekday + 1);
        final isSunday = dayDate.weekday == DateTime.sunday;

        return Break(
          id: 'Break $index',
          start: isSunday ? dayDate : dayDate.add(const Duration(hours: 13)),
          duration:
              isSunday ? const Duration(days: 1) : const Duration(hours: 1),
          color: Colors.grey.withOpacity(0.25),
        );
      },
    );
    final events = [
      TaskDue(
        id: 'TaskDue 1',
        start: today.add(const Duration(hours: 13)),
      ),
      SimpleEvent(
        id: 'Event 4',
        start: today.add(const Duration(hours: 70)),
        duration: const Duration(hours: 26),
        title: 'Event 4',
      ),
      SimpleEvent(
        id: 'Event 3',
        start: today.add(const Duration(hours: 12)),
        duration: const Duration(days: 1, minutes: 30),
        title: 'Event 3',
      ),
      SimpleEvent(
        id: 'Event 2',
        start: today.add(const Duration(hours: 12)),
        duration: const Duration(minutes: 30),
        title: 'Event 2',
      ),
      SimpleEvent(
        id: 'Event 1',
        start: today.add(const Duration(hours: 38)),
        duration: const Duration(hours: 70),
        title: 'Event 1',
      ),
    ];

    return BlocProvider<ListCubit>(
      create: (context) => ListCubit()
        ..saveAll(
          events: events,
          breaks: breaks,
        ),
      child: MaterialApp(
        title: 'Flutter customizable calendar',
        debugShowCheckedModeBanner: false,
        theme: ThemeData(
          primarySwatch: Colors.blue,
          scaffoldBackgroundColor: Colors.blue.shade50,
        ),
        home: CalendarPage<FloatingCalendarEvent>(),
      ),
    );
  }
}

class CalendarPage<T extends FloatingCalendarEvent> extends StatefulWidget {
  const CalendarPage({
    super.key,
  });

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

class _CalendarPageState<T extends FloatingCalendarEvent>
    extends State<CalendarPage<T>> with SingleTickerProviderStateMixin {
  final _daysViewController = DaysViewController(
    initialDate: _initialDate,
    endDate: _endDate,
  );
  final _weekViewController = WeekViewController(
    initialDate: _initialDate,
    endDate: _endDate,
  );
  final _monthViewController = MonthViewController(
    initialDate: _initialDate,
    endDate: _endDate,
  );
  late final TabController _tabController;
  late ThemeData _theme;

  // The initial date is 1970-01-01 in local time
  static DateTime get _initialDate => DateTime(1970);

  static DateTime? get _endDate => null;

  Map<int, CalendarController> get _controllers => {
        0: _daysViewController,
        1: _weekViewController,
        2: _monthViewController,
      };

  Map<int, String> get _segmentLabels => {
        0: CalendarView.days.name,
        1: CalendarView.week.name,
        2: CalendarView.month.name,
      };

  late final ListCubit listCubit;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(
      length: 3,
      vsync: this,
    );
    listCubit = context.read<ListCubit>();
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _theme = Theme.of(context);
  }

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<ListCubit, ListState>(builder: (context, state) {
      return Scaffold(
        appBar: AppBar(
          title: Text('Schedule'),
          backgroundColor: _theme.scaffoldBackgroundColor,
          actions: [
            CupertinoButton(
              onPressed: () => _controllers[_tabController.index]?.reset(),
              child: Text(
                'Now',
                style: const TextStyle(
                  fontSize: 14,
                  fontWeight: FontWeight.w500,
                  color: Colors.black,
                ),
              ),
            ),
          ],
        ),
        body: SafeArea(
          child: Column(
            children: [
              const SizedBox(height: 12),
              _calendarViewPicker(),
              Expanded(child: _calendarViews()),
            ],
          ),
        ),
      );
    });
  }

  @override
  void dispose() {
    _daysViewController.dispose();
    _weekViewController.dispose();
    _monthViewController.dispose();
    _tabController.dispose();
    super.dispose();
  }

  Widget _calendarViewPicker() => StatefulBuilder(
        builder: (context, setState) => CupertinoSegmentedControl<int>(
          children: Map.fromEntries(
            List.generate(
              _tabController.length,
              (index) => MapEntry(index, _segment(index)),
            ),
          ),
          onValueChanged: (index) {
            _tabController.animateTo(index);
            setState(() {});
          },
          groupValue: _tabController.index,
        ),
      );

  Widget _segment(int index) => Padding(
        padding: const EdgeInsets.symmetric(
          vertical: 8,
          horizontal: 20,
        ),
        child: Text(
          _segmentLabels[index]?.capitalized() ?? '???',
          style: const TextStyle(
            fontWeight: FontWeight.w500,
          ),
        ),
      );

  Widget _calendarViews() => TabBarView(
        controller: _tabController,
        physics: const NeverScrollableScrollPhysics(),
        children: [
          _daysView(),
          _weekView(),
          _monthView(),
        ],
      );

  Widget _daysView() => Stack(
        children: [
          DaysView<T>(
            saverConfig: _saverConfig(),
            controller: _daysViewController,
            monthPickerTheme: _periodPickerTheme,
            daysListTheme: DaysListTheme(
              itemTheme: DaysListItemTheme(
                foreground: _theme.primaryColor,
                shape: RoundedRectangleBorder(
                  side: BorderSide(color: _theme.primaryColor),
                  borderRadius: BorderRadius.circular(12),
                ),
              ),
            ),
            timelineTheme: TimelineTheme(
              padding: const EdgeInsets.fromLTRB(16, 10, 12, 10),
              timeScaleTheme: TimeScaleTheme(
                textStyle: _textStyle,
                currentTimeMarkTheme: _currentTimeMarkTheme,
              ),
              floatingEventsTheme: _floatingEventsTheme,
              draggableEventTheme: _draggableEventTheme,
            ),
            breaks: listCubit.state.breaks.values.toList(),
            events: listCubit.state.events.values.cast<T>().toList(),
            onDateLongPress: _onDateLongPress,
            onEventTap: print,
            onEventUpdated: (obj) {
              print(obj);
              context.read<ListCubit>().save(obj);
            },
            onDiscardChanges: (obj) {
              print(obj);
            },
          ),
        ],
      );

  Widget _weekView() {
    return WeekView<T>(
      saverConfig: _saverConfig(),
      controller: _weekViewController,
      weekPickerTheme: _periodPickerTheme,
      divider: Divider(
        height: 2,
        thickness: 2,
        color: Colors.grey.withOpacity(0.33),
      ),
      daysRowTheme: DaysRowTheme(
        weekdayStyle: _textStyle,
        numberStyle: _textStyle.copyWith(
          fontSize: 14,
          fontWeight: FontWeight.w500,
          color: _theme.primaryColor,
        ),
      ),
      timelineTheme: TimelineTheme(
        padding: const EdgeInsets.symmetric(vertical: 32),
        timeScaleTheme: TimeScaleTheme(
          width: 48,
          currentTimeMarkTheme: _currentTimeMarkTheme,
          drawHalfHourMarks: false,
          drawQuarterHourMarks: false,
          hourFormatter: (time) => time.hour.toString(),
          textStyle: _textStyle,
          marksAlign: MarksAlign.center,
        ),
        floatingEventsTheme: _floatingEventsTheme,
        draggableEventTheme: _draggableEventTheme,
      ),
      breaks: listCubit.state.breaks.values.toList(),
      events: listCubit.state.events.values.cast<T>().toList(),
      onDateLongPress: _onDateLongPress,
      onEventTap: print,
      onEventUpdated: (obj) {
        print(obj);
        context.read<ListCubit>().save(obj);
      },
      onDiscardChanges: (obj) {
        print(obj);
      },
    );
  }

  Widget _monthView() {
    return MonthView<T>(
      saverConfig: _saverConfig(),
      controller: _monthViewController,
      monthPickerTheme: _periodPickerTheme,
      divider: Divider(
        height: 2,
        thickness: 2,
        color: Colors.grey.withOpacity(0.33),
        // color: Colors.green,
      ),
      daysRowTheme: DaysRowTheme(
        weekdayStyle: _textStyle,
        numberStyle: _textStyle.copyWith(
          fontSize: 14,
          fontWeight: FontWeight.w500,
          color: _theme.primaryColor,
        ),
      ),
      timelineTheme: TimelineTheme(
        padding: const EdgeInsets.symmetric(vertical: 32),
        timeScaleTheme: TimeScaleTheme(
          width: 48,
          currentTimeMarkTheme: _currentTimeMarkTheme,
          drawHalfHourMarks: false,
          drawQuarterHourMarks: false,
          hourFormatter: (time) => time.hour.toString(),
          textStyle: _textStyle,
          marksAlign: MarksAlign.center,
        ),
        floatingEventsTheme: _floatingEventsTheme,
        draggableEventTheme: _draggableEventTheme,
      ),
      monthDayTheme: MonthDayTheme(
        currentDayNumberTextStyle: TextStyle(
          color: Colors.white,
          fontWeight: FontWeight.bold,
        ),
        // currentDayColor: Colors.grey,
        // dayColor: Colors.white,
        // spacingColor: Colors.orange,
        dayNumberHeight: 23,
        dayNumberMargin: EdgeInsets.all(3),
        dayNumberBackgroundColor: Colors.grey.withOpacity(0.3),
      ),
      breaks: listCubit.state.breaks.values.toList(),
      events: listCubit.state.events.values.cast<T>().toList(),
      onDateLongPress: _onDateLongPress,
      onEventTap: print,
      onEventUpdated: (obj) {
        print(obj);
        context.read<ListCubit>().save(obj);
      },
      onDiscardChanges: (obj) {
        print(obj);
      },
    );
  }

  Future<CalendarEvent?> _onDateLongPress(DateTime timestamp) async {
    print(timestamp);
    final _minute = timestamp.minute;
    return await showModalBottomSheet(
      context: context,
      builder: (context) => Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          const SizedBox(
            height: 32,
          ),
          ListTile(
            title: Text("Simple Event"),
            onTap: () {
              final T newItem = SimpleEvent(
                id: const Uuid().v1(),
                start: timestamp.subtract(Duration(minutes: _minute)),
                duration: Duration(hours: 1),
                title: "Simple event",
              ) as T;
              listCubit.save(newItem);
              Navigator.of(context).pop(newItem);
            },
          ),
          ListTile(
            title: Text("Task Due"),
            onTap: () {
              final T newItem = TaskDue(
                id: const Uuid().v1(),
                start: timestamp.subtract(Duration(minutes: _minute)),
              ) as T;
              listCubit.save(newItem);
              Navigator.of(context).pop(newItem);
            },
          ),
          ListTile(
            title: Text("Break"),
            onTap: () {
              final Break newItem = Break(
                id: const Uuid().v1(),
                start: timestamp.subtract(Duration(minutes: _minute)),
                duration: Duration(hours: 1),
              );
              listCubit.save(newItem);
              Navigator.of(context).pop(newItem);
            },
          ),
        ],
      ),
    );
  }

  SaverConfig _saverConfig() => SaverConfig(
        child: Container(
            color: Colors.transparent,
            padding: EdgeInsets.all(15),
            child: Icon(Icons.done)),
      );

  TextStyle get _textStyle => TextStyle(
        fontSize: 12,
        color: Colors.grey.shade700,
      );

  DisplayedPeriodPickerTheme get _periodPickerTheme =>
      DisplayedPeriodPickerTheme(
        height: 40,
        foregroundColor: _theme.primaryColor,
        shape: RoundedRectangleBorder(
          side: BorderSide(color: _theme.primaryColor),
          borderRadius: BorderRadius.circular(24),
        ),
        textStyle: TextStyle(
          color: _theme.primaryColor,
          fontWeight: FontWeight.w600,
          backgroundColor: Colors.transparent,
        ),
      );

  TimeMarkTheme get _currentTimeMarkTheme => TimeMarkTheme(
        length: 48,
        color: _theme.colorScheme.error,
      );

  FloatingEventsTheme get _floatingEventsTheme => FloatingEventsTheme(
        elevation: 1,
        shape: RoundedRectangleBorder(
          side: BorderSide(color: Colors.grey.withOpacity(0.1)),
          borderRadius: const BorderRadius.all(Radius.circular(4)),
        ),
        margin: const EdgeInsets.all(1),
        // monthTheme: ViewEventTheme(
        //   titleStyle: TextStyle(
        //     fontSize: 10,
        //   ),
        // )
      );

  DraggableEventTheme get _draggableEventTheme => DraggableEventTheme(
        elevation: 5,
        sizerTheme: SizerTheme(
          decoration: BoxDecoration(
            color: _theme.colorScheme.error,
            shape: BoxShape.circle,
          ),
        ),
      );
}
35
likes
0
points
129
downloads

Publisher

verified publisherradency.com

Weekly Downloads

a feature-rich Flutter package that offers highly customizable calendar views for displaying days, weeks, and months.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

animations, clock, collection, cupertino_icons, equatable, flutter, flutter_bloc, intl

More

Packages that depend on flutter_customizable_calendar