Bläddra i källkod

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 <[email protected]>
Richard Shiue 2 år sedan
förälder
incheckning
db8d030a85

+ 3 - 1
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"
     }
   }
 }

+ 13 - 25
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,

+ 30 - 19
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<CalendarPage> {
               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<CalendarPage> {
     // 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,
-    );
+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: _calendarBloc.rowCache.cellCache,
-          ),
-          dataController: dataController,
-        );
-      },
-    );
-  }
+  FlowyOverlay.show(
+    context: context,
+    builder: (BuildContext context) {
+      return RowDetailPage(
+        cellBuilder: GridCellBuilder(
+          cellCache: rowCache.cellCache,
+        ),
+        dataController: dataController,
+      );
+    },
+  );
 }

+ 7 - 4
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<DatabasePropertyBloc>(
@@ -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();

+ 103 - 7
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<StatefulWidget> 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<CalendarBloc>();
@@ -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<CalendarBloc, CalendarState>(
+      builder: (context, state) {
+        final unscheduledEvents = state.allEvents
+            .where((e) => e.date == DateTime.fromMillisecondsSinceEpoch(0))
+            .toList();
+        final viewId = context.read<CalendarBloc>().viewId;
+        final rowCache = context.read<CalendarBloc>().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<CalendarDayEvent> 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,
+      ),
+    );
+  }
+}