From db8d030a85df2fa25e5df39d59ed718786bdae84 Mon Sep 17 00:00:00 2001 From: Richard Shiue <71320345+richardshiue@users.noreply.github.com> Date: Thu, 4 May 2023 11:05:17 +0800 Subject: [PATCH] feat: show unscheduled events in calendar toolbar (#2411) * refactor: use same show row detail function * fix: adjust popover offset * feat: show unscheduled events in toolbar * chore: apply suggestions from Xazin * refactor: refactor list item into separate widget --------- Co-authored-by: Nathan.fooo <86001920+appflowy@users.noreply.github.com> --- .../assets/translations/en.json | 4 +- .../calendar/presentation/calendar_day.dart | 38 +++--- .../calendar/presentation/calendar_page.dart | 53 +++++---- .../toolbar/calendar_layout_setting.dart | 11 +- .../toolbar/calendar_toolbar.dart | 110 ++++++++++++++++-- 5 files changed, 158 insertions(+), 58 deletions(-) diff --git a/frontend/appflowy_flutter/assets/translations/en.json b/frontend/appflowy_flutter/assets/translations/en.json index 492478a114..c8b5f74215 100644 --- a/frontend/appflowy_flutter/assets/translations/en.json +++ b/frontend/appflowy_flutter/assets/translations/en.json @@ -418,7 +418,9 @@ "showWeekNumbers": "Show week numbers", "showWeekends": "Show weekends", "firstDayOfWeek": "Start week on", - "layoutDateField": "Layout calendar by" + "layoutDateField": "Layout calendar by", + "noDateTitle": "No Date", + "emptyNoDate": "No unscheduled events" } } } \ No newline at end of file diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_day.dart b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_day.dart index 3da4979752..ffd5004540 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_day.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_day.dart @@ -1,12 +1,9 @@ import 'package:appflowy/plugins/database_view/application/row/row_cache.dart'; -import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart'; import 'package:appflowy/plugins/database_view/widgets/card/card.dart'; import 'package:appflowy/plugins/database_view/widgets/card/card_cell_builder.dart'; import 'package:appflowy/plugins/database_view/widgets/card/cells/card_cell.dart'; import 'package:appflowy/plugins/database_view/widgets/card/cells/number_card_cell.dart'; import 'package:appflowy/plugins/database_view/widgets/card/cells/url_card_cell.dart'; -import 'package:appflowy/plugins/database_view/widgets/row/cell_builder.dart'; -import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart'; import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pbenum.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/image.dart'; @@ -19,6 +16,7 @@ import 'package:provider/provider.dart'; import '../../grid/presentation/layout/sizes.dart'; import '../../widgets/row/cells/select_option_cell/extension.dart'; import '../application/calendar_bloc.dart'; +import 'calendar_page.dart'; class CalendarDayCard extends StatelessWidget { final String viewId; @@ -193,7 +191,12 @@ class CalendarDayCard extends StatelessWidget { cardData: event.dateFieldId, isEditing: false, cellBuilder: cellBuilder, - openCard: (context) => _showRowDetailPage(event, context), + openCard: (context) => showEventDetails( + context: context, + event: event, + viewId: viewId, + rowCache: _rowCache, + ), styleConfiguration: const RowCardStyleConfiguration( showAccessory: false, cellPadding: EdgeInsets.zero, @@ -204,7 +207,12 @@ class CalendarDayCard extends StatelessWidget { ); return GestureDetector( - onTap: () => _showRowDetailPage(event, context), + onTap: () => showEventDetails( + context: context, + event: event, + viewId: viewId, + rowCache: _rowCache, + ), child: MouseRegion( cursor: SystemMouseCursors.click, child: Container( @@ -224,26 +232,6 @@ class CalendarDayCard extends StatelessWidget { ); } - void _showRowDetailPage(CalendarDayEvent event, BuildContext context) { - final dataController = RowController( - rowId: event.eventId, - viewId: viewId, - rowCache: _rowCache, - ); - - FlowyOverlay.show( - context: context, - builder: (BuildContext context) { - return RowDetailPage( - cellBuilder: GridCellBuilder( - cellCache: _rowCache.cellCache, - ), - dataController: dataController, - ); - }, - ); - } - notifyEnter(BuildContext context, bool isEnter) { Provider.of<_CardEnterNotifier>( context, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_page.dart b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_page.dart index 9f76e0252d..fe5e73097c 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_page.dart @@ -9,6 +9,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../application/row/row_cache.dart'; import '../../application/row/row_data_controller.dart'; import '../../widgets/row/cell_builder.dart'; import '../../widgets/row/row_detail.dart'; @@ -76,7 +77,12 @@ class _CalendarPageState extends State { listenWhen: (p, c) => p.editEvent != c.editEvent, listener: (context, state) { if (state.editEvent != null) { - _showEditEventPage(state.editEvent!.event!, context); + showEventDetails( + context: context, + event: state.editEvent!.event!, + viewId: widget.view.id, + rowCache: _calendarBloc.rowCache, + ); } }, ), @@ -213,24 +219,29 @@ class _CalendarPageState extends State { // MonthView places the first day of week on the second column for some reason. return WeekDays.values[(dayOfWeek + 1) % 7]; } - - void _showEditEventPage(CalendarDayEvent event, BuildContext context) { - final dataController = RowController( - rowId: event.eventId, - viewId: widget.view.id, - rowCache: _calendarBloc.rowCache, - ); - - FlowyOverlay.show( - context: context, - builder: (BuildContext context) { - return RowDetailPage( - cellBuilder: GridCellBuilder( - cellCache: _calendarBloc.rowCache.cellCache, - ), - dataController: dataController, - ); - }, - ); - } +} + +void showEventDetails({ + required BuildContext context, + required CalendarDayEvent event, + required String viewId, + required RowCache rowCache, +}) { + final dataController = RowController( + rowId: event.eventId, + viewId: viewId, + rowCache: rowCache, + ); + + FlowyOverlay.show( + context: context, + builder: (BuildContext context) { + return RowDetailPage( + cellBuilder: GridCellBuilder( + cellCache: rowCache.cellCache, + ), + dataController: dataController, + ); + }, + ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_layout_setting.dart b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_layout_setting.dart index 85cfe0825f..81f79e5061 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_layout_setting.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_layout_setting.dart @@ -217,6 +217,7 @@ class LayoutDateField extends StatelessWidget { direction: PopoverDirection.leftWithTopAligned, constraints: BoxConstraints.loose(const Size(300, 400)), mutex: popoverMutex, + offset: const Offset(-16, 0), popupBuilder: (context) { return BlocProvider( create: (context) => getIt( @@ -237,9 +238,9 @@ class LayoutDateField extends StatelessWidget { onUpdated(fieldInfo.id); popoverMutex.close(); }, - leftIcon: svgWidget('grid/field/date'), + leftIcon: const FlowySvg(name: 'grid/field/date'), rightIcon: fieldInfo.id == fieldId - ? svgWidget('grid/checkmark') + ? const FlowySvg(name: 'grid/checkmark') : null, ), ); @@ -333,6 +334,7 @@ class FirstDayOfWeek extends StatelessWidget { direction: PopoverDirection.leftWithTopAligned, constraints: BoxConstraints.loose(const Size(300, 400)), mutex: popoverMutex, + offset: const Offset(-16, 0), popupBuilder: (context) { final symbols = DateFormat.EEEE(context.locale.toLanguageTag()).dateSymbols; @@ -348,8 +350,9 @@ class FirstDayOfWeek extends StatelessWidget { onUpdated(index); popoverMutex.close(); }, - rightIcon: - firstDayOfWeek == index ? svgWidget('grid/checkmark') : null, + rightIcon: firstDayOfWeek == index + ? const FlowySvg(name: 'grid/checkmark') + : null, ), ); }).toList(); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_toolbar.dart b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_toolbar.dart index 1f704b5e83..c04e536d04 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_toolbar.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_toolbar.dart @@ -1,6 +1,8 @@ import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; +import 'package:calendar_view/calendar_view.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -19,7 +21,8 @@ class CalendarToolbar extends StatelessWidget { height: 40, child: Row( mainAxisAlignment: MainAxisAlignment.end, - children: [ + children: const [ + _UnscheduleEventsButton(), _SettingButton(), ], ), @@ -28,25 +31,22 @@ class CalendarToolbar extends StatelessWidget { } class _SettingButton extends StatefulWidget { + const _SettingButton({Key? key}) : super(key: key); + @override State createState() => _SettingButtonState(); } class _SettingButtonState extends State<_SettingButton> { - late PopoverController popoverController; - @override void initState() { - popoverController = PopoverController(); super.initState(); } @override Widget build(BuildContext context) { return AppFlowyPopover( - controller: popoverController, direction: PopoverDirection.bottomWithRightAligned, - triggerActions: PopoverTriggerFlags.none, constraints: BoxConstraints.loose(const Size(300, 400)), margin: EdgeInsets.zero, child: FlowyTextButton( @@ -54,7 +54,6 @@ class _SettingButtonState extends State<_SettingButton> { fillColor: Colors.transparent, hoverColor: AFThemeExtension.of(context).lightGreyHover, padding: GridSize.typeOptionContentInsets, - onPressed: () => popoverController.show(), ), popupBuilder: (BuildContext popoverContext) { final bloc = context.watch(); @@ -81,3 +80,100 @@ class _SettingButtonState extends State<_SettingButton> { ); } } + +class _UnscheduleEventsButton extends StatefulWidget { + const _UnscheduleEventsButton({Key? key}) : super(key: key); + + @override + State<_UnscheduleEventsButton> createState() => + _UnscheduleEventsButtonState(); +} + +class _UnscheduleEventsButtonState extends State<_UnscheduleEventsButton> { + late final PopoverController _controller; + + @override + void initState() { + super.initState(); + _controller = PopoverController(); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + final unscheduledEvents = state.allEvents + .where((e) => e.date == DateTime.fromMillisecondsSinceEpoch(0)) + .toList(); + final viewId = context.read().viewId; + final rowCache = context.read().rowCache; + return AppFlowyPopover( + direction: PopoverDirection.bottomWithCenterAligned, + controller: _controller, + offset: const Offset(0, 8), + child: FlowyTextButton( + "${LocaleKeys.calendar_settings_noDateTitle.tr()} (${unscheduledEvents.length})", + fillColor: Colors.transparent, + hoverColor: AFThemeExtension.of(context).lightGreyHover, + padding: GridSize.typeOptionContentInsets, + ), + popupBuilder: (context) { + if (unscheduledEvents.isEmpty) { + return SizedBox( + height: GridSize.popoverItemHeight, + child: Center( + child: FlowyText.medium( + LocaleKeys.calendar_settings_emptyNoDate.tr(), + color: Theme.of(context).hintColor, + ), + ), + ); + } + return ListView.separated( + itemBuilder: (context, index) => _UnscheduledEventItem( + event: unscheduledEvents[index], + onPressed: () { + showEventDetails( + context: context, + event: unscheduledEvents[index].event!, + viewId: viewId, + rowCache: rowCache, + ); + _controller.close(); + }, + ), + itemCount: unscheduledEvents.length, + separatorBuilder: (context, index) => + VSpace(GridSize.typeOptionSeparatorHeight), + shrinkWrap: true, + ); + }, + ); + }, + ); + } +} + +class _UnscheduledEventItem extends StatelessWidget { + final CalendarEventData event; + final VoidCallback onPressed; + const _UnscheduledEventItem({ + required this.event, + required this.onPressed, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: GridSize.popoverItemHeight, + child: FlowyTextButton( + event.title, + fillColor: Colors.transparent, + hoverColor: AFThemeExtension.of(context).lightGreyHover, + padding: GridSize.typeOptionContentInsets, + onPressed: onPressed, + ), + ); + } +}