Flutter日程提醒插件remind_timetable的使用

Flutter日程提醒插件remind_timetable的使用

remind_timetable 是一个用于在 Flutter 应用程序中实现日程提醒功能的插件。它允许用户查看和管理时间表上的事件。本文将通过一个完整的示例来展示如何使用 remind_timetable 插件。

示例代码

import 'package:black_hole_flutter/black_hole_flutter.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:remind_timetable/timetable.dart';
import 'package:time/time.dart';

// 忽略未使用的导入
import 'positioning_demo.dart';
import 'utils.dart';

Future<void> main() async {
  initDebugOverlay();
  runApp(ExampleApp(child: TimetableExample()));
}

class TimetableExample extends StatefulWidget {
  @override
  State<TimetableExample> createState() => _TimetableExampleState();
}

// 定义一个全局键,用于获取 MultiDateContentGeometry 的状态
final GlobalKey<MultiDateContentGeometry> timetableKey = GlobalKey();

class _TimetableExampleState extends State<TimetableExample> with TickerProviderStateMixin {
  // 设置初始可见日期范围
  var _visibleDateRange = PredefinedVisibleDateRange.threeDays;

  // 更新可见日期范围的方法
  void _updateVisibleDateRange(PredefinedVisibleDateRange newValue) {
    setState(() {
      _visibleDateRange = newValue;
      _dateController.visibleRange = newValue.visibleDateRange;
    });
  }

  // 判断是否为固定布局
  bool get _isRecurringLayout => _visibleDateRange == PredefinedVisibleDateRange.fixed;

  // 初始化日期控制器
  late final _dateController = DateController(
    visibleRange: _visibleDateRange.visibleDateRange,
  );

  // 初始化时间控制器
  final _timeController = TimeController(
    maxRange: TimeRange(-3.hours, 26.hours),
  );

  // 存储拖拽事件
  final _draggedEvents = <BasicEvent>[];

  @override
  void dispose() {
    _timeController.dispose();
    _dateController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return TimetableConfig<BasicEvent>(
      // 必需参数
      dateController: _dateController,
      timeController: _timeController,
      eventBuilder: (context, event) => _buildPartDayEvent(event),
      child: Column(children: [
        Expanded(
          child: _isRecurringLayout
              ? RecurringMultiDateTimetable<BasicEvent>()
              : MultiDateTimetable<BasicEvent>(
                  headerBuilder: (context, leadingWidth) => ColoredBox(
                    color: Colors.amber.shade800.withOpacity(0.7),
                    child: MultiDateTimetableHeader<BasicEvent>(
                      leading: SizedBox(
                        width: leadingWidth,
                        child: Align(
                          heightFactor: 1,
                          alignment: Alignment.center,
                          child: WeekIndicator.forController(null),
                        ),
                      ),
                    ),
                  ),
                  contentBuilder: (context, onLeadingWidthChanged) => GestureDetector(
                    onTapDown: (details) {
                      final time = timetableKey.currentState!.resolveOffset(details.globalPosition);
                      print("Pointer at ${time} ${time.isUtc}");
                    },
                    child: MultiDateTimetableContent<BasicEvent>(
                      contentGeometryKey: timetableKey,
                    ),
                  ),
                ),
        ),
      ]),
      // 可选参数
      eventProvider: eventProviderFromFixedList(positioningDemoEvents),
      allDayEventBuilder: (context, event, info) => BasicAllDayEventWidget(
        event,
        info: info,
        onTap: () => _showSnackBar('All-day event $event tapped'),
      ),
      timeOverlayProvider: mergeTimeOverlayProviders([
        (context, date) => _draggedEvents
            .map(
              (it) => it.toTimeOverlay(context, date: date, widget: BasicEventWidget(it)),
            )
            .whereNotNull()
            .toList(),
      ]),
      callbacks: TimetableCallbacks(
        onDateTap: (date) {
          _showSnackBar('Tapped on date $date.');
          _dateController.animateTo(date, vsync: this);
        },
        onDateBackgroundTap: (date) => _showSnackBar('Tapped on date background at $date.'),
        onMultiDateHeaderOverflowTap: (date) => _showSnackBar('Tapped on the overflow of $date.'),
      ),
      theme: TimetableThemeData(
        context,
        multiDateTimetableStyle: MultiDateTimetableStyle(context, contentBehindHead: true),
      ),
    );
  }

  // 构建部分天数事件
  Widget _buildPartDayEvent(BasicEvent event) {
    final roundedTo = 15.minutes;

    return PartDayDraggableEvent(
      onDragStart: () => setState(() => _draggedEvents.add(event)),
      onDragUpdate: (dateTime) => setState(() {
        dateTime = dateTime.roundTimeToMultipleOf(roundedTo);
        final index = _draggedEvents.indexWhere((it) => it.id == event.id);
        final oldEvent = _draggedEvents[index];
        _draggedEvents[index] = oldEvent.copyWith(
          start: dateTime,
          end: dateTime + oldEvent.duration,
        );
      }),
      onDragEnd: (dateTime) {
        dateTime = (dateTime ?? event.start).roundTimeToMultipleOf(roundedTo);
        setState(() => _draggedEvents.removeWhere((it) => it.id == event.id));
        _showSnackBar('Dragged event to $dateTime.');
      },
      onDragCanceled: (isMoved) => _showSnackBar('Your finger moved: $isMoved'),
      child: BasicEventWidget(
        event,
        onTap: () => _showSnackBar('Part-day event $event tapped'),
      ),
    );
  }

  // 构建应用栏
  Widget _buildAppBar() {
    final colorScheme = context.theme.colorScheme;
    Widget child = AppBar(
      elevation: 0,
      titleTextStyle: TextStyle(color: colorScheme.onSurface),
      iconTheme: IconThemeData(color: colorScheme.onSurface),
      systemOverlayStyle: SystemUiOverlayStyle.light,
      backgroundColor: Colors.transparent,
      title: _isRecurringLayout ? null : MonthIndicator.forController(_dateController),
      actions: <Widget>[
        IconButton(
          icon: const Icon(Icons.today),
          onPressed: () {
            _dateController.animateToToday(vsync: this);
            _timeController.animateToShowFullDay(vsync: this);
          },
          tooltip: 'Go to today',
        ),
        const SizedBox(width: 8),
        DropdownButton<PredefinedVisibleDateRange>(
          onChanged: (visibleRange) => _updateVisibleDateRange(visibleRange!),
          value: _visibleDateRange,
          items: [
            for (final visibleRange in PredefinedVisibleDateRange.values)
              DropdownMenuItem(
                value: visibleRange,
                child: Text(visibleRange.title),
              ),
          ],
        ),
        const SizedBox(width: 16),
      ],
    );

    if (!_isRecurringLayout) {
      child = Column(children: [
        child,
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
          child: Builder(builder: (context) {
            return DefaultTimetableCallbacks(
              callbacks: DefaultTimetableCallbacks.of(context)!.copyWith(
                onDateTap: (date) {
                  _showSnackBar('Tapped on date $date.');
                  _updateVisibleDateRange(PredefinedVisibleDateRange.day);
                  _dateController.animateTo(date, vsync: this);
                },
              ),
              child: CompactMonthTimetable(),
            );
          }),
        ),
      ]);
    }

    return Material(color: colorScheme.surface, elevation: 4, child: child);
  }

  // 显示 SnackBar
  void _showSnackBar(String content) => context.scaffoldMessenger.showSnackBar(SnackBar(content: Text(content)));
}

// 预定义的可见日期范围枚举
enum PredefinedVisibleDateRange { day, threeDays, workWeek, week, fixed }

// 枚举扩展方法
extension on PredefinedVisibleDateRange {
  VisibleDateRange get visibleDateRange {
    switch (this) {
      case PredefinedVisibleDateRange.day:
        return VisibleDateRange.days(1);
      case PredefinedVisibleDateRange.threeDays:
        return VisibleDateRange.days(3);
      case PredefinedVisibleDateRange.workWeek:
        return VisibleDateRange.weekAligned(5);
      case PredefinedVisibleDateRange.week:
        return VisibleDateRange.week();
      case PredefinedVisibleDateRange.fixed:
        return VisibleDateRange.fixed(
          DateTimeTimetable.today(),
          DateTime.daysPerWeek,
        );
    }
  }

  String get title {
    switch (this) {
      case PredefinedVisibleDateRange.day:
        return 'Day';
      case PredefinedVisibleDateRange.threeDays:
        return '3 Days';
      case PredefinedVisibleDateRange.workWeek:
        return 'Work Week';
      case PredefinedVisibleDateRange.week:
        return 'Week';
      case PredefinedVisibleDateRange.fixed:
        return '7 Days (fixed)';
    }
  }
}

代码解释

主函数

Future<void> main() async {
  initDebugOverlay();
  runApp(ExampleApp(child: TimetableExample()));
}
  • 初始化调试覆盖层。
  • 运行应用程序,并传递 TimetableExample 组件作为子组件。

定义状态类

class TimetableExample extends StatefulWidget {
  @override
  State<TimetableExample> createState() => _TimetableExampleState();
}
  • 创建一个 TimetableExample 状态类,用于管理状态。

初始化控件

final GlobalKey<MultiDateContentGeometry> timetableKey = GlobalKey();

class _TimetableExampleState extends State<TimetableExample> with TickerProviderStateMixin {
  var _visibleDateRange = PredefinedVisibleDateRange.threeDays;
  void _updateVisibleDateRange(PredefinedVisibleDateRange newValue) {
    setState(() {
      _visibleDateRange = newValue;
      _dateController.visibleRange = newValue.visibleDateRange;
    });
  }

  bool get _isRecurringLayout => _visibleDateRange == PredefinedVisibleDateRange.fixed;

  late final _dateController = DateController(
    visibleRange: _visibleDateRange.visibleDateRange,
  );

  final _timeController = TimeController(
    maxRange: TimeRange(-3.hours, 26.hours),
  );

  final _draggedEvents = <BasicEvent>[];
  • 定义全局键 timetableKey 用于获取 MultiDateContentGeometry 的状态。
  • 初始化 _visibleDateRangePredefinedVisibleDateRange.threeDays
  • 定义 _updateVisibleDateRange 方法更新可见日期范围。
  • 判断是否为固定布局。
  • 初始化日期控制器 _dateController 和时间控制器 _timeController
  • 定义 _draggedEvents 存储拖拽事件。

处理生命周期

@override
void dispose() {
  _timeController.dispose();
  _dateController.dispose();
  super.dispose();
}
  • 在组件销毁时释放 _timeController_dateController

构建界面

@override
Widget build(BuildContext context) {
  return TimetableConfig<BasicEvent>(
    dateController: _dateController,
    timeController: _timeController,
    eventBuilder: (context, event) => _buildPartDayEvent(event),
    child: Column(children: [
      Expanded(
        child: _isRecurringLayout
            ? RecurringMultiDateTimetable<BasicEvent>()
            : MultiDateTimetable<BasicEvent>(
                headerBuilder: (context, leadingWidth) => ColoredBox(
                  color: Colors.amber.shade800.withOpacity(0.7),
                  child: MultiDateTimetableHeader<BasicEvent>(
                    leading: SizedBox(
                      width: leadingWidth,
                      child: Align(
                        heightFactor: 1,
                        alignment: Alignment.center,
                        child: WeekIndicator.forController(null),
                      ),
                    ),
                  ),
                ),
                contentBuilder: (context, onLeadingWidthChanged) => GestureDetector(
                  onTapDown: (details) {
                    final time = timetableKey.currentState!.resolveOffset(details.globalPosition);
                    print("Pointer at ${time} ${time.isUtc}");
                  },
                  child: MultiDateTimetableContent<BasicEvent>(
                    contentGeometryKey: timetableKey,
                  ),
                ),
              ),
      ),
    ]),
    eventProvider: eventProviderFromFixedList(positioningDemoEvents),
    allDayEventBuilder: (context, event, info) => BasicAllDayEventWidget(
      event,
      info: info,
      onTap: () => _showSnackBar('All-day event $event tapped'),
    ),
    timeOverlayProvider: mergeTimeOverlayProviders([
      (context, date) => _draggedEvents
          .map(
            (it) => it.toTimeOverlay(context, date: date, widget: BasicEventWidget(it)),
          )
          .whereNotNull()
          .toList(),
    ]),
    callbacks: TimetableCallbacks(
      onDateTap: (date) {
        _showSnackBar('Tapped on date $date.');
        _dateController.animateTo(date, vsync: this);
      },
      onDateBackgroundTap: (date) => _showSnackBar('Tapped on date background at $date.'),
      onMultiDateHeaderOverflowTap: (date) => _showSnackBar('Tapped on the overflow of $date.'),
    ),
    theme: TimetableThemeData(
      context,
      multiDateTimetableStyle: MultiDateTimetableStyle(context, contentBehindHead: true),
    ),
  );
}
  • 使用 TimetableConfig 组件构建时间表配置。
  • 设置必需的参数,如日期控制器、时间控制器和事件构建器。
  • 设置可选参数,如事件提供器、全天事件构建器、时间覆盖提供器和回调。
  • 设置主题样式。

构建部分天数事件

Widget _buildPartDayEvent(BasicEvent event) {
  final roundedTo = 15.minutes;

  return PartDayDraggableEvent(
    onDragStart: () => setState(() => _draggedEvents.add(event)),
    onDragUpdate: (dateTime) => setState(() {
      dateTime = dateTime.roundTimeToMultipleOf(roundedTo);
      final index = _draggedEvents.indexWhere((it) => it.id == event.id);
      final oldEvent = _draggedEvents[index];
      _draggedEvents[index] = oldEvent.copyWith(
        start: dateTime,
        end: dateTime + oldEvent.duration,
      );
    }),
    onDragEnd: (dateTime) {
      dateTime = (dateTime ?? event.start).roundTimeToMultipleOf(roundedTo);
      setState(() => _draggedEvents.removeWhere((it) => it.id == event.id));
      _showSnackBar('Dragged event to $dateTime.');
    },
    onDragCanceled: (isMoved) => _showSnackBar('Your finger moved: $isMoved'),
    child: BasicEventWidget(
      event,
      onTap: () => _showSnackBar('Part-day event $event tapped'),
    ),
  );
}
  • 构建部分天数事件。
  • 设置拖拽事件的开始、更新和结束逻辑。
  • 设置拖拽取消事件的逻辑。

构建应用栏

Widget _buildAppBar() {
  final colorScheme = context.theme.colorScheme;
  Widget child = AppBar(
    elevation: 0,
    titleTextStyle: TextStyle(color: colorScheme.onSurface),
    iconTheme: IconThemeData(color: colorScheme.onSurface),
    systemOverlayStyle: SystemUiOverlayStyle.light,
    backgroundColor: Colors.transparent,
    title: _isRecurringLayout ? null : MonthIndicator.forController(_dateController),
    actions: <Widget>[
      IconButton(
        icon: const Icon(Icons.today),
        onPressed: () {
          _dateController.animateToToday(vsync: this);
          _timeController.animateToShowFullDay(vsync: this);
        },
        tooltip: 'Go to today',
      ),
      const SizedBox(width: 8),
      DropdownButton<PredefinedVisibleDateRange>(
        onChanged: (visibleRange) => _updateVisibleDateRange(visibleRange!),
        value: _visibleDateRange,
        items: [
          for (final visibleRange in PredefinedVisibleDateRange.values)
            DropdownMenuItem(
              value: visibleRange,
              child: Text(visibleRange.title),
            ),
        ],
      ),
      const SizedBox(width: 16),
    ],
  );

  if (!_isRecurringLayout) {
    child = Column(children: [
      child,
      Padding(
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
        child: Builder(builder: (context) {
          return DefaultTimetableCallbacks(
            callbacks: DefaultTimetableCallbacks.of(context)!.copyWith(
              onDateTap: (date) {
                _showSnackBar('Tapped on date $date.');
                _updateVisibleDateRange(PredefinedVisibleDateRange.day);
                _dateController.animateTo(date, vsync: this);
              },
            ),
            child: CompactMonthTimetable(),
          );
        }),
      ),
    ]);
  }

  return Material(color: colorScheme.surface, elevation: 4, child: child);
}
  • 构建应用栏。
  • 设置应用栏的属性,如背景颜色、图标等。
  • 添加按钮和下拉菜单,用于切换日期范围。

显示 SnackBar

void _showSnackBar(String content) => context.scaffoldMessenger.showSnackBar(SnackBar(content: Text(content)));
  • 显示 SnackBar 以提示用户操作结果。

预定义的可见日期范围枚举

enum PredefinedVisibleDateRange { day, threeDays, workWeek, week, fixed }
  • 定义预定义的可见日期范围枚举。

枚举扩展方法

extension on PredefinedVisibleDateRange {
  VisibleDateRange get visibleDateRange {
    switch (this) {
      case PredefinedVisibleDateRange.day:
        return VisibleDateRange.days(1);
      case PredefinedVisibleDateRange.threeDays:
        return VisibleDateRange.days(3);
      case PredefinedVisibleDateRange.workWeek:
        return VisibleDateRange.weekAligned(5);
      case PredefinedVisibleDateRange.week:
        return VisibleDateRange.week();
      case PredefinedVisibleDateRange.fixed:
        return VisibleDateRange.fixed(
          DateTimeTimetable.today(),
          DateTime.daysPerWeek,
        );
    }
  }

  String get title {
    switch (this) {
      case PredefinedVisibleDateRange.day:
        return 'Day';
      case PredefinedVisibleDateRange.threeDays:
        return '3 Days';
      case PredefinedVisibleDateRange.workWeek:
        return 'Work Week';
      case PredefinedVisibleDateRange.week:
        return 'Week';
      case PredefinedVisibleDateRange.fixed:
        return '7 Days (fixed)';
    }
  }
}

更多关于Flutter日程提醒插件remind_timetable的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter日程提醒插件remind_timetable的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


remind_timetable 是一个 Flutter 插件,用于在应用程序中实现日程提醒功能。虽然这个插件的具体实现可能会有所不同,但通常情况下,它会提供一些基本的功能,如添加、删除、更新和查看日程提醒。

以下是一个基本的使用指南,假设你已经创建了一个 Flutter 项目并添加了 remind_timetable 插件到你的 pubspec.yaml 文件中。

1. 添加依赖

首先,在 pubspec.yaml 文件中添加 remind_timetable 插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  remind_timetable: ^latest_version

然后运行 flutter pub get 来获取依赖。

2. 导入插件

在你的 Dart 文件中导入 remind_timetable 插件:

import 'package:remind_timetable/remind_timetable.dart';

3. 初始化插件

在使用插件之前,通常需要初始化它。你可以通过调用 RemindTimetable.initialize() 来完成初始化:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await RemindTimetable.initialize();
  runApp(MyApp());
}

4. 添加日程提醒

你可以使用 RemindTimetable.addReminder() 方法来添加一个新的日程提醒。通常,你需要提供提醒的标题、描述、时间等信息。

void addReminder() async {
  Reminder reminder = Reminder(
    title: 'Meeting with Team',
    description: 'Discuss project updates',
    time: DateTime.now().add(Duration(hours: 1)),
  );

  await RemindTimetable.addReminder(reminder);
}

5. 获取日程提醒

你可以使用 RemindTimetable.getReminders() 方法来获取所有的日程提醒:

void getReminders() async {
  List<Reminder> reminders = await RemindTimetable.getReminders();
  for (var reminder in reminders) {
    print('Title: ${reminder.title}, Time: ${reminder.time}');
  }
}

6. 更新日程提醒

你可以使用 RemindTimetable.updateReminder() 方法来更新现有的日程提醒:

void updateReminder(Reminder reminder) async {
  reminder.title = 'Updated Meeting Title';
  await RemindTimetable.updateReminder(reminder);
}

7. 删除日程提醒

你可以使用 RemindTimetable.deleteReminder() 方法来删除一个日程提醒:

void deleteReminder(Reminder reminder) async {
  await RemindTimetable.deleteReminder(reminder);
}

8. 监听提醒事件

有些插件可能支持监听提醒事件,例如提醒触发时执行某些操作:

void listenToReminders() {
  RemindTimetable.onReminderTriggered.listen((Reminder reminder) {
    print('Reminder triggered: ${reminder.title}');
  });
}

9. 错误处理

在使用插件时,记得处理可能出现的异常:

void addReminder() async {
  try {
    Reminder reminder = Reminder(
      title: 'Meeting with Team',
      description: 'Discuss project updates',
      time: DateTime.now().add(Duration(hours: 1)),
    );

    await RemindTimetable.addReminder(reminder);
  } catch (e) {
    print('Failed to add reminder: $e');
  }
}

10. UI 集成

最后,将上述逻辑集成到你的 Flutter UI 中。例如,你可以在按钮的 onPressed 事件中调用 addReminder() 方法:

ElevatedButton(
  onPressed: addReminder,
  child: Text('Add Reminder'),
);
回到顶部