Browse Source

chore: calendar UI polish (#3484)

* chore: update calendar theming

* feat: add event popup editor

* chore: new event button redesign and add card shadows

* chore: unscheduled events button

* chore: event title text field

* fix: focus node double dispose

* chore: show popover when create new event

* test: integrate some tests for integration testing purposes

* fix: some fixes and more integration tests
Richard Shiue 1 year ago
parent
commit
37ddce3a29
26 changed files with 1020 additions and 355 deletions
  1. 66 21
      frontend/appflowy_flutter/integration_test/database_calendar_test.dart
  2. 61 0
      frontend/appflowy_flutter/integration_test/util/database_test_op.dart
  3. 7 0
      frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/calendar_bloc.dart
  4. 82 0
      frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/calendar_event_editor_bloc.dart
  5. 4 1
      frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/unschedule_event_bloc.dart
  6. 102 56
      frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_day.dart
  7. 102 29
      frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_event_card.dart
  8. 280 0
      frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_event_editor.dart
  9. 196 48
      frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_page.dart
  10. 2 2
      frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/layout/sizes.dart
  11. 0 159
      frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_setting_bar.dart
  12. 11 0
      frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_editor.dart
  13. 11 0
      frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option.dart
  14. 3 1
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cell_builder.dart
  15. 12 0
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart
  16. 46 31
      frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/text_cell/text_cell.dart
  17. 1 0
      frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/auto_completion_node_widget.dart
  18. 1 0
      frontend/appflowy_flutter/lib/workspace/application/appearance.dart
  19. 2 0
      frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/colorscheme.dart
  20. 2 0
      frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/dandelion.dart
  21. 2 0
      frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/default_colorscheme.dart
  22. 2 0
      frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/lavender.dart
  23. 7 0
      frontend/appflowy_flutter/packages/flowy_infra/lib/theme_extension.dart
  24. 4 1
      frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/icon_button.dart
  25. 13 6
      frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text_field.dart
  26. 1 0
      frontend/resources/translations/en.json

+ 66 - 21
frontend/appflowy_flutter/integration_test/database_calendar_test.dart

@@ -79,33 +79,33 @@ void main() {
       // Tap on create new event button
       await tester.tapAddCalendarEventButton();
 
-      // Make sure that the row details page is opened
-      tester.assertRowDetailPageOpened();
-
-      // Dismiss the row details page
-      await tester.dismissRowDetailPage();
+      // Make sure that the event editor popup is shown
+      tester.assertEventEditorOpen();
 
       tester.assertNumberOfEventsInCalendar(1);
 
+      // Dismiss the event editor popup
+      await tester.dismissEventEditor();
+
       // Double click on today's calendar cell to create a new event
       await tester.doubleClickCalendarCell(DateTime.now());
 
-      // Make sure that the row details page is opened
-      tester.assertRowDetailPageOpened();
-
-      // Dismiss the row details page
-      await tester.dismissRowDetailPage();
+      // Make sure that the event editor popup is shown
+      tester.assertEventEditorOpen();
 
       // Make sure that the event is inserted in the cell
       tester.assertNumberOfEventsInCalendar(2);
 
+      // Dismiss the event editor popup
+      await tester.dismissEventEditor();
+
       // Click on the event
       await tester.openCalendarEvent(index: 0);
-      tester.assertRowDetailPageOpened();
+      tester.assertEventEditorOpen();
 
       // Change the title of the event
-      await tester.editTitleInRowDetailPage('hello world');
-      await tester.dismissRowDetailPage();
+      await tester.editEventTitle('hello world');
+      await tester.dismissEventEditor();
 
       // Make sure that the event is edited
       tester.assertNumberOfEventsInCalendar(1, title: 'hello world');
@@ -113,6 +113,10 @@ void main() {
 
       // Click on the event
       await tester.openCalendarEvent(index: 1);
+      tester.assertEventEditorOpen();
+
+      // Click on the open icon
+      await tester.openEventToRowDetailPage();
       tester.assertRowDetailPageOpened();
 
       // Duplicate the event
@@ -126,12 +130,23 @@ void main() {
 
       // Delete an event
       await tester.openCalendarEvent(index: 1);
-      await tester.tapRowDetailPageRowActionButton();
-      await tester.tapRowDetailPageDeleteRowButton();
+      await tester.deleteEventFromEventEditor();
 
       // Check that there is 1 event
       tester.assertNumberOfEventsInCalendar(1, title: 'hello world');
       tester.assertNumberOfEventsOnSpecificDay(2, DateTime.now());
+
+      // Delete event from row detail page
+      await tester.openCalendarEvent(index: 1);
+      await tester.openEventToRowDetailPage();
+      tester.assertRowDetailPageOpened();
+
+      await tester.tapRowDetailPageRowActionButton();
+      await tester.tapRowDetailPageDeleteRowButton();
+
+      // Check that there is 0 event
+      tester.assertNumberOfEventsInCalendar(0, title: 'hello world');
+      tester.assertNumberOfEventsOnSpecificDay(1, DateTime.now());
     });
 
     testWidgets('rescheduling events', (tester) async {
@@ -145,7 +160,7 @@ void main() {
       final today = DateTime.now();
       final firstOfThisMonth = DateTime(today.year, today.month, 1);
       await tester.doubleClickCalendarCell(firstOfThisMonth);
-      await tester.dismissRowDetailPage();
+      await tester.dismissEventEditor();
 
       // Drag and drop the event onto the next week, same day
       await tester.dragDropRescheduleCalendarEvent(firstOfThisMonth);
@@ -157,13 +172,12 @@ void main() {
 
       // Delete the event
       await tester.openCalendarEvent(index: 0, date: sameDayNextWeek);
-      await tester.tapRowDetailPageRowActionButton();
-      await tester.tapRowDetailPageDeleteRowButton();
+      await tester.deleteEventFromEventEditor();
 
       // Create a new event in today's calendar cell
       await tester.scrollToToday();
       await tester.doubleClickCalendarCell(today);
-      await tester.dismissRowDetailPage();
+      await tester.dismissEventEditor();
 
       // Make sure that the event is today
       tester.assertNumberOfEventsOnSpecificDay(1, today);
@@ -182,12 +196,43 @@ void main() {
       await tester.selectDay(content: newDate.day);
       await tester.dismissCellEditor();
 
-      // Dismiss the row details page
-      await tester.dismissRowDetailPage();
+      // Dismiss the event editor
+      await tester.dismissEventEditor();
 
       // Make sure that the event is edited
       tester.assertNumberOfEventsInCalendar(1);
       tester.assertNumberOfEventsOnSpecificDay(1, newDate);
+
+      // Click on the unscheduled events button
+      await tester.openUnscheduledEventsPopup();
+
+      // Assert that nothing shows up
+      tester.findUnscheduledPopup(findsNothing, 0);
+
+      // Click on the event in the calendar
+      await tester.openCalendarEvent(index: 0, date: newDate);
+
+      // Open the date editor of the event
+      await tester.tapDateCellInRowDetailPage();
+      await tester.findDateEditor(findsOneWidget);
+
+      // Clear the date of the event
+      await tester.clearDate();
+
+      // Dismiss the event editor
+      await tester.dismissEventEditor();
+      tester.assertNumberOfEventsInCalendar(0);
+
+      // Click on the unscheduled events button
+      await tester.openUnscheduledEventsPopup();
+
+      // Assert that a popup appears and 1 unscheduled event
+      tester.findUnscheduledPopup(findsOneWidget, 1);
+
+      // Click on the unscheduled event
+      await tester.clickUnscheduledEvent();
+
+      tester.assertRowDetailPageOpened();
     });
   });
 }

+ 61 - 0
frontend/appflowy_flutter/integration_test/util/database_test_op.dart

@@ -6,6 +6,7 @@ import 'package:appflowy/plugins/database_view/board/presentation/board_page.dar
 import 'package:appflowy/plugins/database_view/calendar/application/calendar_bloc.dart';
 import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_day.dart';
 import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_event_card.dart';
+import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_event_editor.dart';
 import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart';
 import 'package:appflowy/plugins/database_view/calendar/presentation/toolbar/calendar_layout_setting.dart';
 import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
@@ -1291,12 +1292,72 @@ extension AppFlowyDatabaseTest on WidgetTester {
     await tapButton(cards.at(index));
   }
 
+  void assertEventEditorOpen() {
+    expect(find.byType(CalendarEventEditor), findsOneWidget);
+  }
+
+  Future<void> dismissEventEditor() async {
+    await simulateKeyEvent(LogicalKeyboardKey.escape);
+  }
+
+  Future<void> editEventTitle(String title) async {
+    final textField = find.descendant(
+      of: find.byType(CalendarEventEditor),
+      matching: find.byType(FlowyTextField),
+    );
+
+    await enterText(textField, title);
+    await pumpAndSettle(const Duration(milliseconds: 300));
+  }
+
+  Future<void> openEventToRowDetailPage() async {
+    final button = find.descendant(
+      of: find.byType(CalendarEventEditor),
+      matching: find.byWidgetPredicate(
+        (widget) => widget is FlowySvg && widget.svg == FlowySvgs.full_view_s,
+      ),
+    );
+
+    await tapButton(button);
+  }
+
+  Future<void> deleteEventFromEventEditor() async {
+    final button = find.descendant(
+      of: find.byType(CalendarEventEditor),
+      matching: find.byWidgetPredicate(
+        (widget) => widget is FlowySvg && widget.svg == FlowySvgs.delete_s,
+      ),
+    );
+
+    await tapButton(button);
+  }
+
   Future<void> dragDropRescheduleCalendarEvent(DateTime startDate) async {
     final findEventCard = find.byType(EventCard);
     await drag(findEventCard.first, const Offset(0, 300));
     await pumpAndSettle();
   }
 
+  Future<void> openUnscheduledEventsPopup() async {
+    final button = find.byType(UnscheduledEventsButton);
+    await tapButton(button);
+  }
+
+  void findUnscheduledPopup(Matcher matcher, int numUnscheduledEvents) {
+    expect(find.byType(UnscheduleEventsList), matcher);
+    if (matcher != findsNothing) {
+      expect(
+        find.byType(UnscheduledEventCell),
+        findsNWidgets(numUnscheduledEvents),
+      );
+    }
+  }
+
+  Future<void> clickUnscheduledEvent() async {
+    final unscheduledEvent = find.byType(UnscheduledEventCell);
+    await tapButton(unscheduledEvent);
+  }
+
   Future<void> tapCreateLinkedDatabaseViewButton(AddButtonAction action) async {
     final findAddButton = find.byType(AddDatabaseViewButton);
     await tapButton(findAddButton);

+ 7 - 0
frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/calendar_bloc.dart

@@ -63,6 +63,9 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
           createEvent: (DateTime date) async {
             await _createEvent(date);
           },
+          newEventPopupDisplayed: () {
+            emit(state.copyWith(editingEvent: null));
+          },
           moveEvent: (CalendarDayEvent event, DateTime date) async {
             await _moveEvent(event, date);
           },
@@ -378,6 +381,10 @@ class CalendarEvent with _$CalendarEvent {
     CalendarEventData<CalendarDayEvent> event,
   ) = _DidReceiveNewEvent;
 
+  // Called after creating a new event
+  const factory CalendarEvent.newEventPopupDisplayed() =
+      _NewEventPopupDisplayed;
+
   // Called when receive a new event
   const factory CalendarEvent.didReceiveEvent(
     CalendarEventData<CalendarDayEvent> event,

+ 82 - 0
frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/calendar_event_editor_bloc.dart

@@ -0,0 +1,82 @@
+import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
+import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
+import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-database2/calendar_entities.pb.dart';
+import 'package:bloc/bloc.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'calendar_event_editor_bloc.freezed.dart';
+
+class CalendarEventEditorBloc
+    extends Bloc<CalendarEventEditorEvent, CalendarEventEditorState> {
+  final RowController rowController;
+  final CalendarLayoutSettingPB layoutSettings;
+  final RowBackendService _rowService;
+
+  CalendarEventEditorBloc({
+    required this.rowController,
+    required this.layoutSettings,
+  })  : _rowService = RowBackendService(viewId: rowController.viewId),
+        super(CalendarEventEditorState.initial()) {
+    on<CalendarEventEditorEvent>((event, emit) async {
+      await event.when(
+        initial: () {
+          _startListening();
+          final cells = rowController.loadData();
+          if (!isClosed) {
+            add(
+              CalendarEventEditorEvent.didReceiveCellDatas(
+                cells.values.toList(),
+              ),
+            );
+          }
+        },
+        didReceiveCellDatas: (cells) {
+          emit(state.copyWith(cells: cells));
+        },
+        delete: () async {
+          final result = await _rowService.deleteRow(rowController.rowId);
+          result.fold((l) => null, (err) => Log.error(err));
+        },
+      );
+    });
+  }
+
+  void _startListening() {
+    rowController.addListener(
+      onRowChanged: (cells, reason) {
+        if (!isClosed) {
+          add(
+            CalendarEventEditorEvent.didReceiveCellDatas(cells.values.toList()),
+          );
+        }
+      },
+    );
+  }
+
+  @override
+  Future<void> close() async {
+    rowController.dispose();
+    return super.close();
+  }
+}
+
+@freezed
+class CalendarEventEditorEvent with _$CalendarEventEditorEvent {
+  const factory CalendarEventEditorEvent.initial() = _Initial;
+  const factory CalendarEventEditorEvent.didReceiveCellDatas(
+    List<DatabaseCellContext> cells,
+  ) = _DidReceiveCellDatas;
+  const factory CalendarEventEditorEvent.delete() = _Delete;
+}
+
+@freezed
+class CalendarEventEditorState with _$CalendarEventEditorState {
+  const factory CalendarEventEditorState({
+    required List<DatabaseCellContext> cells,
+  }) = _CalendarEventEditorState;
+
+  factory CalendarEventEditorState.initial() =>
+      CalendarEventEditorState(cells: List.empty());
+}

+ 4 - 1
frontend/appflowy_flutter/lib/plugins/database_view/calendar/application/unschedule_event_bloc.dart

@@ -58,9 +58,12 @@ class UnscheduleEventsBloc
             );
           },
           didReceiveEvent: (CalendarEventPB event) {
+            final events = [...state.allEvents, event];
             emit(
               state.copyWith(
-                allEvents: [...state.allEvents, event],
+                allEvents: events,
+                unscheduleEvents:
+                    events.where((element) => !element.isScheduled).toList(),
               ),
             );
           },

+ 102 - 56
frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_day.dart

@@ -1,13 +1,14 @@
 import 'package:appflowy/generated/flowy_svgs.g.dart';
+import 'package:appflowy/generated/locale_keys.g.dart';
 import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
 import 'package:easy_localization/easy_localization.dart';
-
 import 'package:flowy_infra/size.dart';
+
 import 'package:flowy_infra/theme_extension.dart';
+import 'package:flowy_infra/time/duration.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
-import 'package:table_calendar/table_calendar.dart';
 
 import '../../grid/presentation/layout/sizes.dart';
 import '../application/calendar_bloc.dart';
@@ -18,7 +19,7 @@ class CalendarDayCard extends StatelessWidget {
   final bool isToday;
   final bool isInMonth;
   final DateTime date;
-  final RowCache _rowCache;
+  final RowCache rowCache;
   final List<CalendarDayEvent> events;
   final void Function(DateTime) onCreateEvent;
 
@@ -28,18 +29,21 @@ class CalendarDayCard extends StatelessWidget {
     required this.isInMonth,
     required this.date,
     required this.onCreateEvent,
-    required RowCache rowCache,
+    required this.rowCache,
     required this.events,
-    Key? key,
-  })  : _rowCache = rowCache,
-        super(key: key);
+    super.key,
+  });
 
   @override
   Widget build(BuildContext context) {
-    Color backgroundColor = Theme.of(context).colorScheme.surface;
-    if (!isInMonth) {
-      backgroundColor = AFThemeExtension.of(context).lightGreyHover;
+    Color backgroundColor = Colors.transparent;
+    if (date.isWeekend) {
+      backgroundColor = AFThemeExtension.of(context).calendarWeekendBGColor;
     }
+    final hoverBackgroundColor =
+        Theme.of(context).brightness == Brightness.light
+            ? Theme.of(context).colorScheme.secondaryContainer
+            : Colors.transparent;
 
     return LayoutBuilder(
       builder: (BuildContext context, BoxConstraints constraints) {
@@ -57,14 +61,14 @@ class CalendarDayCard extends StatelessWidget {
                 ),
 
                 // Add a separator between the header and the content.
-                VSpace(GridSize.typeOptionSeparatorHeight),
+                const VSpace(6.0),
 
                 // List of cards or empty space
                 if (events.isNotEmpty)
                   _EventList(
                     events: events,
                     viewId: viewId,
-                    rowCache: _rowCache,
+                    rowCache: rowCache,
                     constraints: constraints,
                   ),
               ],
@@ -79,34 +83,29 @@ class CalendarDayCard extends StatelessWidget {
                 DragTarget<CalendarDayEvent>(
                   builder: (context, candidate, __) {
                     return Stack(
-                      fit: StackFit.expand,
                       children: [
-                        if (candidate.isNotEmpty)
-                          Container(
-                            color: Theme.of(context)
-                                .colorScheme
-                                .secondaryContainer,
-                          ),
-                        Padding(
-                          padding: const EdgeInsets.only(top: 8.0),
+                        Container(
+                          width: double.infinity,
+                          height: double.infinity,
+                          color:
+                              candidate.isEmpty ? null : hoverBackgroundColor,
+                          padding: const EdgeInsets.only(top: 5.0),
                           child: child,
-                        )
+                        ),
+                        if (candidate.isEmpty)
+                          NewEventButton(onCreate: () => onCreateEvent(date)),
                       ],
                     );
                   },
-                  onWillAccept: (CalendarDayEvent? event) {
-                    if (event == null) {
-                      return false;
-                    }
-                    return !isSameDay(event.date, date);
-                  },
                   onAccept: (CalendarDayEvent event) {
+                    if (event.date == date) {
+                      return;
+                    }
                     context
                         .read<CalendarBloc>()
                         .add(CalendarEvent.moveEvent(event, date));
                   },
                 ),
-                NewEventButton(onCreate: () => onCreateEvent(date)),
                 MouseRegion(
                   onEnter: (p) => notifyEnter(context, true),
                   onExit: (p) => notifyEnter(context, false),
@@ -143,7 +142,7 @@ class _Header extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return Padding(
-      padding: const EdgeInsets.symmetric(horizontal: 8.0),
+      padding: const EdgeInsets.symmetric(horizontal: 6.0),
       child: _DayBadge(
         isToday: isToday,
         isInMonth: isInMonth,
@@ -166,7 +165,7 @@ class NewEventButton extends StatelessWidget {
           return const SizedBox.shrink();
         }
         return Padding(
-          padding: const EdgeInsets.all(8.0),
+          padding: const EdgeInsets.all(4.0),
           child: FlowyIconButton(
             onPressed: onCreate,
             iconPadding: EdgeInsets.zero,
@@ -174,6 +173,35 @@ class NewEventButton extends StatelessWidget {
             fillColor: Theme.of(context).colorScheme.background,
             hoverColor: AFThemeExtension.of(context).lightGreyHover,
             width: 22,
+            tooltipText: LocaleKeys.calendar_newEventButtonTooltip.tr(),
+            decoration: BoxDecoration(
+              border: Border.fromBorderSide(
+                BorderSide(
+                  color: Theme.of(context).brightness == Brightness.light
+                      ? const Color(0xffd0d3d6)
+                      : const Color(0xff59647a),
+                  width: 0.5,
+                ),
+              ),
+              borderRadius: Corners.s5Border,
+              boxShadow: [
+                BoxShadow(
+                  spreadRadius: -2,
+                  color: const Color(0xFF1F2329).withOpacity(0.02),
+                  blurRadius: 2,
+                ),
+                BoxShadow(
+                  spreadRadius: 0,
+                  color: const Color(0xFF1F2329).withOpacity(0.02),
+                  blurRadius: 4,
+                ),
+                BoxShadow(
+                  spreadRadius: 2,
+                  color: const Color(0xFF1F2329).withOpacity(0.02),
+                  blurRadius: 8,
+                ),
+              ],
+            ),
           ),
         );
       },
@@ -195,37 +223,49 @@ class _DayBadge extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     Color dayTextColor = Theme.of(context).colorScheme.onBackground;
+    Color monthTextColor = Theme.of(context).colorScheme.onBackground;
     final String monthString =
         DateFormat("MMM ", context.locale.toLanguageTag()).format(date);
     final String dayString = date.day.toString();
 
     if (!isInMonth) {
       dayTextColor = Theme.of(context).disabledColor;
+      monthTextColor = Theme.of(context).disabledColor;
     }
     if (isToday) {
       dayTextColor = Theme.of(context).colorScheme.onPrimary;
     }
 
-    return Row(
-      mainAxisAlignment: MainAxisAlignment.end,
-      children: [
-        if (date.day == 1) FlowyText.medium(monthString),
-        Container(
-          decoration: BoxDecoration(
-            color: isToday ? Theme.of(context).colorScheme.primary : null,
-            borderRadius: Corners.s6Border,
-          ),
-          width: isToday ? 26 : null,
-          height: isToday ? 26 : null,
-          padding: GridSize.typeOptionContentInsets,
-          child: Center(
-            child: FlowyText.medium(
-              dayString,
-              color: dayTextColor,
+    return SizedBox(
+      height: 18,
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.end,
+        crossAxisAlignment: CrossAxisAlignment.center,
+        children: [
+          if (date.day == 1)
+            FlowyText.medium(
+              monthString,
+              fontSize: 11,
+              color: monthTextColor,
+            ),
+          Container(
+            decoration: BoxDecoration(
+              color: isToday ? Theme.of(context).colorScheme.primary : null,
+              borderRadius: BorderRadius.circular(10),
+            ),
+            width: isToday ? 18 : null,
+            height: isToday ? 18 : null,
+            // padding: GridSize.typeOptionContentInsets,
+            child: Center(
+              child: FlowyText.medium(
+                dayString,
+                fontSize: 11,
+                color: dayTextColor,
+              ),
             ),
           ),
-        ),
-      ],
+        ],
+      ),
     );
   }
 }
@@ -246,20 +286,26 @@ class _EventList extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
+    final editingEvent = context.watch<CalendarBloc>().state.editingEvent;
     return Flexible(
       child: ScrollConfiguration(
         behavior: ScrollConfiguration.of(context).copyWith(
           scrollbars: true,
         ),
         child: ListView.separated(
-          itemBuilder: (BuildContext context, int index) => EventCard(
-            event: events[index],
-            viewId: viewId,
-            rowCache: rowCache,
-            constraints: constraints,
-          ),
+          itemBuilder: (BuildContext context, int index) {
+            final autoEdit =
+                editingEvent?.event?.eventId == events[index].eventId;
+            return EventCard(
+              event: events[index],
+              viewId: viewId,
+              rowCache: rowCache,
+              constraints: constraints,
+              autoEdit: autoEdit,
+            );
+          },
           itemCount: events.length,
-          padding: const EdgeInsets.fromLTRB(8.0, 0, 8.0, 8.0),
+          padding: const EdgeInsets.fromLTRB(4.0, 0, 4.0, 4.0),
           separatorBuilder: (BuildContext context, int index) =>
               VSpace(GridSize.typeOptionSeparatorHeight),
           shrinkWrap: true,

+ 102 - 29
frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_event_card.dart

@@ -8,40 +8,66 @@ import 'package:appflowy/plugins/database_view/widgets/card/cells/number_card_ce
 import 'package:appflowy/plugins/database_view/widgets/card/cells/url_card_cell.dart';
 import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart';
 import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flowy_infra/size.dart';
-import 'package:flowy_infra/theme_extension.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/style_widget/hover.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 import '../application/calendar_bloc.dart';
-import 'calendar_page.dart';
+import 'calendar_event_editor.dart';
 
-class EventCard extends StatelessWidget {
+class EventCard extends StatefulWidget {
   final CalendarDayEvent event;
   final String viewId;
   final RowCache rowCache;
   final BoxConstraints constraints;
+  final bool autoEdit;
 
   const EventCard({
     required this.event,
     required this.viewId,
     required this.rowCache,
     required this.constraints,
+    required this.autoEdit,
     super.key,
   });
 
+  @override
+  State<EventCard> createState() => _EventCardState();
+}
+
+class _EventCardState extends State<EventCard> {
+  late final PopoverController _popoverController;
+
+  @override
+  void initState() {
+    super.initState();
+    _popoverController = PopoverController();
+    if (widget.autoEdit) {
+      WidgetsBinding.instance.addPostFrameCallback((_) {
+        _popoverController.show();
+        context
+            .read<CalendarBloc>()
+            .add(const CalendarEvent.newEventPopupDisplayed());
+      });
+    }
+  }
+
   @override
   Widget build(BuildContext context) {
-    final rowInfo = rowCache.getRow(event.eventId);
+    final rowInfo = widget.rowCache.getRow(widget.event.eventId);
+    if (rowInfo == null) {
+      return const SizedBox.shrink();
+    }
     final styles = <FieldType, CardCellStyle>{
       FieldType.Number: NumberCardCellStyle(10),
       FieldType.URL: URLCardCellStyle(10),
     };
     final cellBuilder = CardCellBuilder<CalendarDayEvent>(
-      rowCache.cellCache,
+      widget.rowCache.cellCache,
       styles: styles,
     );
     final renderHook = _calendarEventCardRenderHook(context);
@@ -49,24 +75,22 @@ class EventCard extends StatelessWidget {
     final card = RowCard<CalendarDayEvent>(
       // Add the key here to make sure the card is rebuilt when the cells
       // in this row are updated.
-      key: ValueKey(event.eventId),
-      rowMeta: rowInfo!.rowMeta,
-      viewId: viewId,
-      rowCache: rowCache,
-      cardData: event,
+      key: ValueKey(widget.event.eventId),
+      rowMeta: rowInfo.rowMeta,
+      viewId: widget.viewId,
+      rowCache: widget.rowCache,
+      cardData: widget.event,
       isEditing: false,
       cellBuilder: cellBuilder,
-      openCard: (context) => showEventDetails(
-        context: context,
-        event: event.event,
-        viewId: viewId,
-        rowCache: rowCache,
-      ),
+      openCard: (_) => _popoverController.show(),
       styleConfiguration: RowCardStyleConfiguration(
         showAccessory: false,
         cellPadding: EdgeInsets.zero,
+        cardPadding: const EdgeInsets.all(6),
         hoverStyle: HoverStyle(
-          hoverColor: AFThemeExtension.of(context).lightGreyHover,
+          hoverColor: Theme.of(context).brightness == Brightness.light
+              ? const Color(0x0F1F2329)
+              : const Color(0x0FEFF4FB),
           foregroundColorOnHover: Theme.of(context).colorScheme.onBackground,
         ),
       ),
@@ -76,28 +100,76 @@ class EventCard extends StatelessWidget {
     );
 
     final decoration = BoxDecoration(
+      color: Theme.of(context).colorScheme.surface,
       border: Border.fromBorderSide(
-        BorderSide(color: Theme.of(context).dividerColor),
+        BorderSide(
+          color: Theme.of(context).brightness == Brightness.light
+              ? const Color(0xffd0d3d6)
+              : const Color(0xff59647a),
+          width: 0.5,
+        ),
       ),
       borderRadius: Corners.s6Border,
+      boxShadow: [
+        BoxShadow(
+          spreadRadius: -2,
+          color: const Color(0xFF1F2329).withOpacity(0.02),
+          blurRadius: 2,
+        ),
+        BoxShadow(
+          spreadRadius: 0,
+          color: const Color(0xFF1F2329).withOpacity(0.02),
+          blurRadius: 4,
+        ),
+        BoxShadow(
+          spreadRadius: 2,
+          color: const Color(0xFF1F2329).withOpacity(0.02),
+          blurRadius: 8,
+        ),
+      ],
     );
 
     return Draggable<CalendarDayEvent>(
-      data: event,
+      data: widget.event,
       feedback: ConstrainedBox(
         constraints: BoxConstraints(
-          maxWidth: constraints.maxWidth - 16.0,
+          maxWidth: widget.constraints.maxWidth - 8.0,
         ),
-        child: DecoratedBox(
-          decoration: decoration.copyWith(
-            color: AFThemeExtension.of(context).lightGreyHover,
+        child: Opacity(
+          opacity: 0.6,
+          child: DecoratedBox(
+            decoration: decoration,
+            child: card,
           ),
-          child: card,
         ),
       ),
-      child: DecoratedBox(
-        decoration: decoration,
-        child: card,
+      child: AppFlowyPopover(
+        triggerActions: PopoverTriggerFlags.none,
+        direction: PopoverDirection.rightWithCenterAligned,
+        controller: _popoverController,
+        constraints: const BoxConstraints(maxWidth: 360, maxHeight: 348),
+        asBarrier: true,
+        margin: EdgeInsets.zero,
+        offset: const Offset(10.0, 0),
+        popupBuilder: (BuildContext popoverContext) {
+          final settings = context.watch<CalendarBloc>().state.settings.fold(
+                () => null,
+                (layoutSettings) => layoutSettings,
+              );
+          if (settings == null) {
+            return const SizedBox.shrink();
+          }
+          return CalendarEventEditor(
+            rowCache: widget.rowCache,
+            rowMeta: widget.event.event.rowMeta,
+            viewId: widget.viewId,
+            layoutSettings: settings,
+          );
+        },
+        child: DecoratedBox(
+          decoration: decoration,
+          child: card,
+        ),
       ),
     );
   }
@@ -127,8 +199,9 @@ class EventCard extends StatelessWidget {
             child: FlowyText.medium(
               text,
               textAlign: TextAlign.left,
-              fontSize: 11,
-              maxLines: null, // Enable multiple lines
+              fontSize: isTitle ? 11 : 10,
+              maxLines: 1,
+              overflow: TextOverflow.ellipsis,
             ),
           );
         },

+ 280 - 0
frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_event_editor.dart

@@ -0,0 +1,280 @@
+import 'package:appflowy/generated/flowy_svgs.g.dart';
+import 'package:appflowy/generated/locale_keys.g.dart';
+import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
+import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
+import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
+import 'package:appflowy/plugins/database_view/calendar/application/calendar_event_editor_bloc.dart';
+import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
+import 'package:appflowy/plugins/database_view/widgets/row/accessory/cell_accessory.dart';
+import 'package:appflowy/plugins/database_view/widgets/row/cell_builder.dart';
+import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart';
+import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
+import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
+import 'package:appflowy_popover/appflowy_popover.dart';
+import 'package:collection/collection.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+class CalendarEventEditor extends StatelessWidget {
+  final RowController rowController;
+  final CalendarLayoutSettingPB layoutSettings;
+  final GridCellBuilder cellBuilder;
+
+  CalendarEventEditor({
+    super.key,
+    required RowCache rowCache,
+    required RowMetaPB rowMeta,
+    required String viewId,
+    required this.layoutSettings,
+  })  : rowController = RowController(
+          rowMeta: rowMeta,
+          viewId: viewId,
+          rowCache: rowCache,
+        ),
+        cellBuilder = GridCellBuilder(cellCache: rowCache.cellCache);
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocProvider<CalendarEventEditorBloc>(
+      create: (context) => CalendarEventEditorBloc(
+        rowController: rowController,
+        layoutSettings: layoutSettings,
+      )..add(const CalendarEventEditorEvent.initial()),
+      child: Column(
+        mainAxisSize: MainAxisSize.min,
+        children: [
+          EventEditorControls(rowController: rowController),
+          Flexible(
+            child: EventPropertyList(
+              dateFieldId: layoutSettings.fieldId,
+              cellBuilder: cellBuilder,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+}
+
+class EventEditorControls extends StatelessWidget {
+  final RowController rowController;
+  const EventEditorControls({
+    super.key,
+    required this.rowController,
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    return Padding(
+      padding: const EdgeInsets.fromLTRB(12.0, 8.0, 12.0, 0),
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.end,
+        children: [
+          FlowyIconButton(
+            width: 20,
+            icon: const FlowySvg(FlowySvgs.delete_s),
+            iconColorOnHover: Theme.of(context).colorScheme.onSecondary,
+            onPressed: () => context
+                .read<CalendarEventEditorBloc>()
+                .add(const CalendarEventEditorEvent.delete()),
+          ),
+          const HSpace(8.0),
+          FlowyIconButton(
+            width: 20,
+            icon: const FlowySvg(FlowySvgs.full_view_s),
+            iconColorOnHover: Theme.of(context).colorScheme.onSecondary,
+            onPressed: () {
+              PopoverContainer.of(context).close();
+              FlowyOverlay.show(
+                context: context,
+                builder: (BuildContext context) {
+                  return RowDetailPage(
+                    cellBuilder: GridCellBuilder(
+                      cellCache: rowController.cellCache,
+                    ),
+                    rowController: rowController,
+                  );
+                },
+              );
+            },
+          ),
+        ],
+      ),
+    );
+  }
+}
+
+class EventPropertyList extends StatelessWidget {
+  final String dateFieldId;
+  final GridCellBuilder cellBuilder;
+  const EventPropertyList({
+    super.key,
+    required this.dateFieldId,
+    required this.cellBuilder,
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<CalendarEventEditorBloc, CalendarEventEditorState>(
+      builder: (context, state) {
+        final reorderedList = List<DatabaseCellContext>.from(state.cells)
+          ..retainWhere((cell) => !cell.fieldInfo.isPrimary);
+
+        final primaryCellContext =
+            state.cells.firstWhereOrNull((cell) => cell.fieldInfo.isPrimary);
+        final dateFieldIndex =
+            reorderedList.indexWhere((cell) => cell.fieldId == dateFieldId);
+        if (primaryCellContext == null || dateFieldIndex == -1) {
+          return const SizedBox.shrink();
+        }
+        reorderedList.insert(0, reorderedList.removeAt(dateFieldIndex));
+
+        final children = <Widget>[
+          Padding(
+            padding: const EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
+            child: cellBuilder.build(
+              primaryCellContext,
+              style: GridTextCellStyle(
+                cellPadding: EdgeInsets.zero,
+                placeholder: LocaleKeys.calendar_defaultNewCalendarTitle.tr(),
+                textStyle: Theme.of(context)
+                    .textTheme
+                    .bodyMedium
+                    ?.copyWith(fontSize: 11),
+                autofocus: true,
+                useRoundedBorder: true,
+              ),
+            ),
+          ),
+          ...reorderedList.map(
+            (cell) => PropertyCell(cellContext: cell, cellBuilder: cellBuilder),
+          ),
+        ];
+
+        return ListView(
+          shrinkWrap: true,
+          padding: const EdgeInsets.only(bottom: 16.0),
+          children: children,
+        );
+      },
+    );
+  }
+}
+
+class PropertyCell extends StatefulWidget {
+  final DatabaseCellContext cellContext;
+  final GridCellBuilder cellBuilder;
+  const PropertyCell({
+    required this.cellContext,
+    required this.cellBuilder,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() => _PropertyCellState();
+}
+
+class _PropertyCellState extends State<PropertyCell> {
+  @override
+  Widget build(BuildContext context) {
+    final style = _customCellStyle(widget.cellContext.fieldType);
+    final cell = widget.cellBuilder.build(widget.cellContext, style: style);
+
+    final gesture = GestureDetector(
+      behavior: HitTestBehavior.opaque,
+      onTap: () => cell.requestFocus.notify(),
+      child: AccessoryHover(child: cell),
+    );
+
+    return Container(
+      margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
+      constraints: const BoxConstraints(minHeight: 28),
+      child: Row(
+        crossAxisAlignment: CrossAxisAlignment.start,
+        mainAxisAlignment: MainAxisAlignment.start,
+        children: [
+          SizedBox(
+            width: 88,
+            height: 28,
+            child: Padding(
+              padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 6),
+              child: Row(
+                children: [
+                  FlowySvg(
+                    widget.cellContext.fieldType.icon(),
+                    color: Theme.of(context).hintColor,
+                    size: const Size.square(14),
+                  ),
+                  const HSpace(4.0),
+                  FlowyText.regular(
+                    widget.cellContext.fieldInfo.name,
+                    color: Theme.of(context).hintColor,
+                    fontSize: 11,
+                  ),
+                ],
+              ),
+            ),
+          ),
+          const HSpace(8),
+          Expanded(child: gesture),
+        ],
+      ),
+    );
+  }
+
+  GridCellStyle? _customCellStyle(FieldType fieldType) {
+    switch (fieldType) {
+      case FieldType.Checkbox:
+        return GridCheckboxCellStyle(
+          cellPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5),
+        );
+      case FieldType.DateTime:
+        return DateCellStyle(
+          placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
+          alignment: Alignment.centerLeft,
+          cellPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
+        );
+      case FieldType.LastEditedTime:
+      case FieldType.CreatedTime:
+        return TimestampCellStyle(
+          placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
+          alignment: Alignment.centerLeft,
+          cellPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
+        );
+      case FieldType.MultiSelect:
+        return SelectOptionCellStyle(
+          placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
+          cellPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5),
+        );
+      case FieldType.Checklist:
+        return SelectOptionCellStyle(
+          placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
+        );
+      case FieldType.Number:
+        return GridNumberCellStyle(
+          placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
+        );
+      case FieldType.RichText:
+        return GridTextCellStyle(
+          placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
+        );
+      case FieldType.SingleSelect:
+        return SelectOptionCellStyle(
+          placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
+          cellPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5),
+        );
+
+      case FieldType.URL:
+        return GridURLCellStyle(
+          placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
+          accessoryTypes: [
+            GridURLCellAccessoryType.copyURL,
+            GridURLCellAccessoryType.visitURL,
+          ],
+        );
+    }
+    throw UnimplementedError;
+  }
+}

+ 196 - 48
frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_page.dart

@@ -2,11 +2,15 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
 import 'package:appflowy/generated/locale_keys.g.dart';
 import 'package:appflowy/plugins/database_view/application/database_controller.dart';
 import 'package:appflowy/plugins/database_view/calendar/application/calendar_bloc.dart';
+import 'package:appflowy/plugins/database_view/calendar/application/unschedule_event_bloc.dart';
+import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
 import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_view.dart';
 import 'package:appflowy_backend/protobuf/flowy-database2/calendar_entities.pb.dart';
 import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.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/size.dart';
 
 import 'package:flowy_infra/theme_extension.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@@ -119,19 +123,6 @@ class _CalendarPageState extends State<CalendarPage> {
                 );
               },
             ),
-            BlocListener<CalendarBloc, CalendarState>(
-              listenWhen: (p, c) => p.editingEvent != c.editingEvent,
-              listener: (context, state) {
-                if (state.editingEvent != null) {
-                  showEventDetails(
-                    context: context,
-                    event: state.editingEvent!.event!.event,
-                    viewId: widget.view.id,
-                    rowCache: _calendarBloc.rowCache,
-                  );
-                }
-              },
-            ),
             BlocListener<CalendarBloc, CalendarState>(
               // Event create by click the + button or double click on the
               // calendar
@@ -211,40 +202,49 @@ class _CalendarPageState extends State<CalendarPage> {
   }
 
   Widget _headerNavigatorBuilder(DateTime currentMonth) {
-    return Row(
-      children: [
-        FlowyText.medium(
-          DateFormat('MMMM y', context.locale.toLanguageTag())
-              .format(currentMonth),
-        ),
-        const Spacer(),
-        FlowyIconButton(
-          width: CalendarSize.navigatorButtonWidth,
-          height: CalendarSize.navigatorButtonHeight,
-          icon: const FlowySvg(FlowySvgs.arrow_left_s),
-          tooltipText: LocaleKeys.calendar_navigation_previousMonth.tr(),
-          hoverColor: AFThemeExtension.of(context).lightGreyHover,
-          onPressed: () => _calendarState?.currentState?.previousPage(),
-        ),
-        FlowyTextButton(
-          LocaleKeys.calendar_navigation_today.tr(),
-          fillColor: Colors.transparent,
-          fontWeight: FontWeight.w500,
-          tooltip: LocaleKeys.calendar_navigation_jumpToday.tr(),
-          padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
-          hoverColor: AFThemeExtension.of(context).lightGreyHover,
-          onPressed: () =>
-              _calendarState?.currentState?.animateToMonth(DateTime.now()),
-        ),
-        FlowyIconButton(
-          width: CalendarSize.navigatorButtonWidth,
-          height: CalendarSize.navigatorButtonHeight,
-          icon: const FlowySvg(FlowySvgs.arrow_right_s),
-          tooltipText: LocaleKeys.calendar_navigation_nextMonth.tr(),
-          hoverColor: AFThemeExtension.of(context).lightGreyHover,
-          onPressed: () => _calendarState?.currentState?.nextPage(),
-        ),
-      ],
+    return SizedBox(
+      height: 24,
+      child: Row(
+        crossAxisAlignment: CrossAxisAlignment.center,
+        children: [
+          FlowyText.medium(
+            DateFormat('MMMM y', context.locale.toLanguageTag())
+                .format(currentMonth),
+          ),
+          const Spacer(),
+          FlowyIconButton(
+            width: CalendarSize.navigatorButtonWidth,
+            height: CalendarSize.navigatorButtonHeight,
+            icon: const FlowySvg(FlowySvgs.arrow_left_s),
+            tooltipText: LocaleKeys.calendar_navigation_previousMonth.tr(),
+            hoverColor: AFThemeExtension.of(context).lightGreyHover,
+            onPressed: () => _calendarState?.currentState?.previousPage(),
+          ),
+          FlowyTextButton(
+            LocaleKeys.calendar_navigation_today.tr(),
+            fillColor: Colors.transparent,
+            fontWeight: FontWeight.w400,
+            fontSize: 10,
+            tooltip: LocaleKeys.calendar_navigation_jumpToday.tr(),
+            padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
+            hoverColor: AFThemeExtension.of(context).lightGreyHover,
+            onPressed: () =>
+                _calendarState?.currentState?.animateToMonth(DateTime.now()),
+          ),
+          FlowyIconButton(
+            width: CalendarSize.navigatorButtonWidth,
+            height: CalendarSize.navigatorButtonHeight,
+            icon: const FlowySvg(FlowySvgs.arrow_right_s),
+            tooltipText: LocaleKeys.calendar_navigation_nextMonth.tr(),
+            hoverColor: AFThemeExtension.of(context).lightGreyHover,
+            onPressed: () => _calendarState?.currentState?.nextPage(),
+          ),
+          const HSpace(6.0),
+          UnscheduledEventsButton(
+            databaseController: widget.databaseController,
+          ),
+        ],
+      ),
     );
   }
 
@@ -255,8 +255,9 @@ class _CalendarPageState extends State<CalendarPage> {
     return Center(
       child: Padding(
         padding: CalendarSize.daysOfWeekInsets,
-        child: FlowyText.medium(
+        child: FlowyText.regular(
           weekDayString,
+          fontSize: 9,
           color: Theme.of(context).hintColor,
         ),
       ),
@@ -321,3 +322,150 @@ void showEventDetails({
     },
   );
 }
+
+class UnscheduledEventsButton extends StatefulWidget {
+  final DatabaseController databaseController;
+
+  const UnscheduledEventsButton({super.key, required this.databaseController});
+
+  @override
+  State<UnscheduledEventsButton> createState() =>
+      _UnscheduledEventsButtonState();
+}
+
+class _UnscheduledEventsButtonState extends State<UnscheduledEventsButton> {
+  late final PopoverController _popoverController;
+
+  @override
+  void initState() {
+    super.initState();
+    _popoverController = PopoverController();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocProvider<UnscheduleEventsBloc>(
+      create: (_) =>
+          UnscheduleEventsBloc(databaseController: widget.databaseController)
+            ..add(const UnscheduleEventsEvent.initial()),
+      child: BlocBuilder<UnscheduleEventsBloc, UnscheduleEventsState>(
+        builder: (context, state) {
+          return AppFlowyPopover(
+            direction: PopoverDirection.bottomWithCenterAligned,
+            triggerActions: PopoverTriggerFlags.none,
+            controller: _popoverController,
+            offset: const Offset(0, 8),
+            constraints: const BoxConstraints(maxWidth: 282, maxHeight: 600),
+            child: OutlinedButton(
+              style: OutlinedButton.styleFrom(
+                shape: RoundedRectangleBorder(
+                  side: BorderSide(
+                    color: Theme.of(context).dividerColor,
+                    width: 1,
+                  ),
+                  borderRadius: Corners.s6Border,
+                ),
+                side:
+                    BorderSide(color: Theme.of(context).dividerColor, width: 1),
+                padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
+                visualDensity: VisualDensity.compact,
+              ),
+              onPressed: () {
+                if (state.unscheduleEvents.isNotEmpty) {
+                  _popoverController.show();
+                }
+              },
+              child: FlowyText.regular(
+                "${LocaleKeys.calendar_settings_noDateTitle.tr()} (${state.unscheduleEvents.length})",
+                fontSize: 10,
+              ),
+            ),
+            popupBuilder: (context) {
+              return UnscheduleEventsList(
+                viewId: widget.databaseController.viewId,
+                rowCache: widget.databaseController.rowCache,
+                unscheduleEvents: state.unscheduleEvents,
+              );
+            },
+          );
+        },
+      ),
+    );
+  }
+}
+
+class UnscheduleEventsList extends StatelessWidget {
+  final String viewId;
+  final RowCache rowCache;
+  final List<CalendarEventPB> unscheduleEvents;
+  const UnscheduleEventsList({
+    required this.viewId,
+    required this.unscheduleEvents,
+    required this.rowCache,
+    super.key,
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    final cells = <Widget>[
+      Padding(
+        padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
+        child: FlowyText.medium(
+          LocaleKeys.calendar_settings_clickToAdd.tr(),
+          fontSize: 10,
+          color: Theme.of(context).hintColor,
+          overflow: TextOverflow.ellipsis,
+        ),
+      ),
+      ...unscheduleEvents.map(
+        (e) => UnscheduledEventCell(
+          event: e,
+          onPressed: () {
+            showEventDetails(
+              context: context,
+              event: e,
+              viewId: viewId,
+              rowCache: rowCache,
+            );
+            PopoverContainer.of(context).close();
+          },
+        ),
+      )
+    ];
+
+    return ListView.separated(
+      itemBuilder: (context, index) => cells[index],
+      itemCount: cells.length,
+      separatorBuilder: (context, index) =>
+          VSpace(GridSize.typeOptionSeparatorHeight),
+      shrinkWrap: true,
+    );
+  }
+}
+
+class UnscheduledEventCell extends StatelessWidget {
+  final CalendarEventPB event;
+  final VoidCallback onPressed;
+  const UnscheduledEventCell({
+    required this.event,
+    required this.onPressed,
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return SizedBox(
+      height: 26,
+      child: FlowyButton(
+        margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
+        text: FlowyText.medium(
+          event.title.isEmpty
+              ? LocaleKeys.calendar_defaultNewCalendarTitle.tr()
+              : event.title,
+          fontSize: 11,
+        ),
+        onTap: onPressed,
+      ),
+    );
+  }
+}

+ 2 - 2
frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/layout/sizes.dart

@@ -15,7 +15,7 @@ class CalendarSize {
 
   static double get scrollBarSize => 8 * scale;
   static double get navigatorButtonWidth => 20 * scale;
-  static double get navigatorButtonHeight => 25 * scale;
+  static double get navigatorButtonHeight => 24 * scale;
   static EdgeInsets get daysOfWeekInsets =>
-      EdgeInsets.symmetric(vertical: 10.0 * scale);
+      EdgeInsets.only(top: 12.0 * scale, bottom: 5.0 * scale);
 }

+ 0 - 159
frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/toolbar/calendar_setting_bar.dart

@@ -1,18 +1,6 @@
-import 'package:appflowy/generated/locale_keys.g.dart';
 import 'package:appflowy/plugins/database_view/application/database_controller.dart';
-import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
-import 'package:appflowy/plugins/database_view/calendar/application/unschedule_event_bloc.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/plugins/database_view/widgets/setting/setting_button.dart';
-import 'package:appflowy_backend/protobuf/flowy-database2/calendar_entities.pb.dart';
-import 'package:appflowy_popover/appflowy_popover.dart';
-import 'package:easy_localization/easy_localization.dart';
-import 'package:flowy_infra/size.dart';
-import 'package:flowy_infra/theme_extension.dart';
-import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
 
 class CalendarSettingBar extends StatelessWidget {
   final DatabaseController databaseController;
@@ -28,8 +16,6 @@ class CalendarSettingBar extends StatelessWidget {
       child: Row(
         mainAxisAlignment: MainAxisAlignment.end,
         children: [
-          UnscheduleEventsButton(databaseController: databaseController),
-          const HSpace(2),
           SettingButton(
             databaseController: databaseController,
           ),
@@ -38,148 +24,3 @@ class CalendarSettingBar extends StatelessWidget {
     );
   }
 }
-
-class UnscheduleEventsButton extends StatefulWidget {
-  final DatabaseController databaseController;
-  const UnscheduleEventsButton({
-    required this.databaseController,
-    Key? key,
-  }) : super(key: key);
-
-  @override
-  State<UnscheduleEventsButton> createState() => _UnscheduleEventsButtonState();
-}
-
-class _UnscheduleEventsButtonState extends State<UnscheduleEventsButton> {
-  late final PopoverController _popoverController;
-  late final UnscheduleEventsBloc _bloc;
-
-  @override
-  void initState() {
-    super.initState();
-    _bloc = UnscheduleEventsBloc(databaseController: widget.databaseController)
-      ..add(const UnscheduleEventsEvent.initial());
-    _popoverController = PopoverController();
-  }
-
-  @override
-  dispose() {
-    _bloc.close();
-    super.dispose();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    return AppFlowyPopover(
-      direction: PopoverDirection.bottomWithCenterAligned,
-      controller: _popoverController,
-      offset: const Offset(0, 8),
-      constraints: const BoxConstraints(maxWidth: 300, maxHeight: 600),
-      child: BlocProvider.value(
-        value: _bloc,
-        child: BlocBuilder<UnscheduleEventsBloc, UnscheduleEventsState>(
-          buildWhen: (previous, current) =>
-              previous.unscheduleEvents.length !=
-              current.unscheduleEvents.length,
-          builder: (context, state) {
-            return FlowyTextButton(
-              "${LocaleKeys.calendar_settings_noDateTitle.tr()} (${state.unscheduleEvents.length})",
-              fontSize: FontSizes.s11,
-              fontColor: AFThemeExtension.of(context).textColor,
-              fontWeight: FontWeight.w400,
-              fillColor: Colors.transparent,
-              hoverColor: AFThemeExtension.of(context).lightGreyHover,
-              padding: GridSize.toolbarSettingButtonInsets,
-              radius: Corners.s4Border,
-              onPressed: () => _popoverController.show(),
-            );
-          },
-        ),
-      ),
-      popupBuilder: (context) {
-        return UnscheduleEventsList(
-          viewId: _bloc.viewId,
-          rowCache: _bloc.rowCache,
-          controller: _popoverController,
-          unscheduleEvents: _bloc.state.unscheduleEvents,
-        );
-      },
-    );
-  }
-}
-
-class UnscheduleEventsList extends StatelessWidget {
-  final String viewId;
-  final RowCache rowCache;
-  final PopoverController controller;
-  final List<CalendarEventPB> unscheduleEvents;
-  const UnscheduleEventsList({
-    required this.viewId,
-    required this.controller,
-    required this.unscheduleEvents,
-    required this.rowCache,
-    super.key,
-  });
-
-  @override
-  Widget build(BuildContext context) {
-    final cells = <Widget>[
-      Padding(
-        padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
-        child: FlowyText.medium(
-          LocaleKeys.calendar_settings_clickToAdd.tr(),
-          color: Theme.of(context).hintColor,
-          overflow: TextOverflow.ellipsis,
-        ),
-      ),
-      const VSpace(6),
-      ...unscheduleEvents.map(
-        (e) => UnscheduledEventCell(
-          event: e,
-          onPressed: () {
-            showEventDetails(
-              context: context,
-              event: e,
-              viewId: viewId,
-              rowCache: rowCache,
-            );
-            controller.close();
-          },
-        ),
-      )
-    ];
-
-    return ListView.separated(
-      itemBuilder: (context, index) => cells[index],
-      itemCount: cells.length,
-      separatorBuilder: (context, index) =>
-          VSpace(GridSize.typeOptionSeparatorHeight),
-      shrinkWrap: true,
-    );
-  }
-}
-
-class UnscheduledEventCell extends StatelessWidget {
-  final CalendarEventPB event;
-  final VoidCallback onPressed;
-  const UnscheduledEventCell({
-    required this.event,
-    required this.onPressed,
-    Key? key,
-  }) : super(key: key);
-
-  @override
-  Widget build(BuildContext context) {
-    return SizedBox(
-      height: GridSize.popoverItemHeight,
-      child: FlowyButton(
-        text: FlowyText.medium(
-          event.title.isEmpty
-              ? LocaleKeys.calendar_defaultNewCalendarTitle.tr()
-              : event.title,
-        ),
-        onTap: onPressed,
-      ),
-    );
-  }
-}

+ 11 - 0
frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_editor.dart

@@ -205,6 +205,17 @@ class _FieldNameTextFieldState extends State<FieldNameTextField> {
       },
     );
   }
+
+  @override
+  void dispose() {
+    focusNode.removeListener(() {
+      if (focusNode.hasFocus) {
+        widget.popoverMutex.close();
+      }
+    });
+    focusNode.dispose();
+    super.dispose();
+  }
 }
 
 class _DeleteFieldButton extends StatelessWidget {

+ 11 - 0
frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/type_option/select_option.dart

@@ -325,4 +325,15 @@ class _CreateOptionTextFieldState extends State<CreateOptionTextField> {
       },
     );
   }
+
+  @override
+  void dispose() {
+    _focusNode.removeListener(() {
+      if (_focusNode.hasFocus) {
+        widget.popoverMutex?.close();
+      }
+    });
+    _focusNode.dispose();
+    super.dispose();
+  }
 }

+ 3 - 1
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cell_builder.dart

@@ -247,7 +247,9 @@ class RequestFocusListener extends ChangeNotifier {
   }
 }
 
-abstract class GridCellStyle {}
+abstract class GridCellStyle {
+  const GridCellStyle();
+}
 
 class SingleListenerFocusNode extends FocusNode {
   VoidCallback? _listener;

+ 12 - 0
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart

@@ -438,6 +438,18 @@ class _TimeTextFieldState extends State<_TimeTextField> {
       },
     );
   }
+
+  @override
+  void dispose() {
+    _textController.dispose();
+    _focusNode.removeListener(() {
+      if (_focusNode.hasFocus) {
+        widget.popoverMutex.close();
+      }
+    });
+    _focusNode.dispose();
+    super.dispose();
+  }
 }
 
 @visibleForTesting

+ 46 - 31
frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/text_cell/text_cell.dart

@@ -8,22 +8,24 @@ import '../../../../grid/presentation/layout/sizes.dart';
 import '../../cell_builder.dart';
 
 class GridTextCellStyle extends GridCellStyle {
-  String? placeholder;
-  TextStyle? textStyle;
-  bool? autofocus;
-  double emojiFontSize;
-  double emojiHPadding;
-  bool showEmoji;
-  EdgeInsets? cellPadding;
+  final String? placeholder;
+  final TextStyle? textStyle;
+  final EdgeInsets? cellPadding;
+  final bool autofocus;
+  final double emojiFontSize;
+  final double emojiHPadding;
+  final bool showEmoji;
+  final bool useRoundedBorder;
 
-  GridTextCellStyle({
+  const GridTextCellStyle({
     this.placeholder,
     this.textStyle,
-    this.autofocus,
+    this.cellPadding,
+    this.autofocus = false,
     this.showEmoji = true,
     this.emojiFontSize = 16,
     this.emojiHPadding = 0,
-    this.cellPadding,
+    this.useRoundedBorder = false,
   });
 }
 
@@ -38,7 +40,7 @@ class GridTextCell extends GridCellWidget {
     if (style != null) {
       cellStyle = (style as GridTextCellStyle);
     } else {
-      cellStyle = GridTextCellStyle();
+      cellStyle = const GridTextCellStyle();
     }
   }
 
@@ -55,12 +57,12 @@ class _GridTextCellState extends GridEditableTextCell<GridTextCell> {
 
   @override
   void initState() {
+    super.initState();
     final cellController =
         widget.cellControllerBuilder.build() as TextCellController;
-    _cellBloc = TextCellBloc(cellController: cellController);
-    _cellBloc.add(const TextCellEvent.initial());
+    _cellBloc = TextCellBloc(cellController: cellController)
+      ..add(const TextCellEvent.initial());
     _controller = TextEditingController(text: _cellBloc.state.content);
-    super.initState();
   }
 
   @override
@@ -94,23 +96,36 @@ class _GridTextCellState extends GridEditableTextCell<GridTextCell> {
                 ),
               HSpace(widget.cellStyle.emojiHPadding),
               Expanded(
-                child: TextField(
-                  controller: _controller,
-                  focusNode: focusNode,
-                  maxLines: null,
-                  style: widget.cellStyle.textStyle ??
-                      Theme.of(context).textTheme.bodyMedium,
-                  autofocus: widget.cellStyle.autofocus ?? false,
-                  decoration: InputDecoration(
-                    contentPadding: EdgeInsets.only(
-                      top: GridSize.cellContentInsets.top,
-                      bottom: GridSize.cellContentInsets.bottom,
-                    ),
-                    border: InputBorder.none,
-                    hintText: widget.cellStyle.placeholder,
-                    isDense: true,
-                  ),
-                ),
+                child: widget.cellStyle.useRoundedBorder
+                    ? FlowyTextField(
+                        controller: _controller,
+                        textStyle: widget.cellStyle.textStyle ??
+                            Theme.of(context).textTheme.bodyMedium,
+                        focusNode: focusNode,
+                        autoFocus: widget.cellStyle.autofocus,
+                        hintText: widget.cellStyle.placeholder,
+                        onChanged: (text) => _cellBloc.add(
+                          TextCellEvent.updateText(text),
+                        ),
+                        debounceDuration: const Duration(milliseconds: 300),
+                      )
+                    : TextField(
+                        controller: _controller,
+                        focusNode: focusNode,
+                        maxLines: null,
+                        style: widget.cellStyle.textStyle ??
+                            Theme.of(context).textTheme.bodyMedium,
+                        autofocus: widget.cellStyle.autofocus,
+                        decoration: InputDecoration(
+                          contentPadding: EdgeInsets.only(
+                            top: GridSize.cellContentInsets.top,
+                            bottom: GridSize.cellContentInsets.bottom,
+                          ),
+                          border: InputBorder.none,
+                          hintText: widget.cellStyle.placeholder,
+                          isDense: true,
+                        ),
+                      ),
               )
             ],
           ),

+ 1 - 0
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/auto_completion_node_widget.dart

@@ -127,6 +127,7 @@ class _AutoCompletionBlockComponentState
     _onExit();
     _unsubscribeSelectionGesture();
     controller.dispose();
+    textFieldFocusNode.dispose();
 
     super.dispose();
   }

+ 1 - 0
frontend/appflowy_flutter/lib/workspace/application/appearance.dart

@@ -433,6 +433,7 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
           toggleOffFill: theme.shader5,
           progressBarBGColor: theme.progressBarBGColor,
           toggleButtonBGColor: theme.toggleButtonBGColor,
+          calendarWeekendBGColor: theme.calendarWeekendBGColor,
           code: _getFontStyle(
             fontFamily: monospaceFontFamily,
             fontColor: theme.shader3,

+ 2 - 0
frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/colorscheme.dart

@@ -85,6 +85,7 @@ class FlowyColorScheme {
   //editor toolbar BG color
   final Color toolbarColor;
   final Color toggleButtonBGColor;
+  final Color calendarWeekendBGColor;
 
   const FlowyColorScheme({
     required this.surface,
@@ -133,6 +134,7 @@ class FlowyColorScheme {
     required this.progressBarBGColor,
     required this.toolbarColor,
     required this.toggleButtonBGColor,
+    required this.calendarWeekendBGColor,
   });
 
   factory FlowyColorScheme.fromJson(Map<String, dynamic> json) =>

+ 2 - 0
frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/dandelion.dart

@@ -72,6 +72,7 @@ class DandelionColorScheme extends FlowyColorScheme {
           progressBarBGColor: _lightTint9,
           toolbarColor: _lightShader1,
           toggleButtonBGColor: _lightShader5,
+          calendarWeekendBGColor: const Color(0xFFFBFBFC),
         );
 
   const DandelionColorScheme.dark()
@@ -122,5 +123,6 @@ class DandelionColorScheme extends FlowyColorScheme {
           progressBarBGColor: _darkShader3,
           toolbarColor: _darkInput,
           toggleButtonBGColor: _darkShader1,
+          calendarWeekendBGColor: const Color(0xff121212),
         );
 }

+ 2 - 0
frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/default_colorscheme.dart

@@ -70,6 +70,7 @@ class DefaultColorScheme extends FlowyColorScheme {
           progressBarBGColor: _lightTint9,
           toolbarColor: _lightShader1,
           toggleButtonBGColor: _lightShader5,
+          calendarWeekendBGColor: const Color(0xFFFBFBFC),
         );
 
   const DefaultColorScheme.dark()
@@ -120,5 +121,6 @@ class DefaultColorScheme extends FlowyColorScheme {
           progressBarBGColor: _darkShader3,
           toolbarColor: _darkInput,
           toggleButtonBGColor: _darkShader1,
+          calendarWeekendBGColor: const Color(0xff121212),
         );
 }

+ 2 - 0
frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/lavender.dart

@@ -73,6 +73,7 @@ class LavenderColorScheme extends FlowyColorScheme {
           progressBarBGColor: _lightTint9,
           toolbarColor: _lightShader1,
           toggleButtonBGColor: _lightSelector,
+          calendarWeekendBGColor: const Color(0xFFFBFBFC),
         );
 
   const LavenderColorScheme.dark()
@@ -123,5 +124,6 @@ class LavenderColorScheme extends FlowyColorScheme {
           progressBarBGColor: _darkShader3,
           toolbarColor: _darkInput,
           toggleButtonBGColor: _darkShader1,
+          calendarWeekendBGColor: const Color(0xff121212),
         );
 }

+ 7 - 0
frontend/appflowy_flutter/packages/flowy_infra/lib/theme_extension.dart

@@ -24,6 +24,7 @@ class AFThemeExtension extends ThemeExtension<AFThemeExtension> {
   final Color toggleButtonBGColor;
   final Color calloutBGColor;
   final Color tableCellBGColor;
+  final Color calendarWeekendBGColor;
 
   final TextStyle code;
   final TextStyle callout;
@@ -48,6 +49,7 @@ class AFThemeExtension extends ThemeExtension<AFThemeExtension> {
     required this.textColor,
     required this.calloutBGColor,
     required this.tableCellBGColor,
+    required this.calendarWeekendBGColor,
     required this.code,
     required this.callout,
     required this.caption,
@@ -81,6 +83,7 @@ class AFThemeExtension extends ThemeExtension<AFThemeExtension> {
     Color? toggleOffFill,
     Color? progressBarBGColor,
     Color? toggleButtonBGColor,
+    Color? calendarWeekendBGColor,
     TextStyle? code,
     TextStyle? callout,
     TextStyle? caption,
@@ -106,6 +109,8 @@ class AFThemeExtension extends ThemeExtension<AFThemeExtension> {
       toggleOffFill: toggleOffFill ?? this.toggleOffFill,
       progressBarBGColor: progressBarBGColor ?? this.progressBarBGColor,
       toggleButtonBGColor: toggleButtonBGColor ?? this.toggleButtonBGColor,
+      calendarWeekendBGColor:
+          calendarWeekendBGColor ?? this.calendarWeekendBGColor,
       code: code ?? this.code,
       callout: callout ?? this.callout,
       caption: caption ?? this.caption,
@@ -142,6 +147,8 @@ class AFThemeExtension extends ThemeExtension<AFThemeExtension> {
           Color.lerp(progressBarBGColor, other.progressBarBGColor, t)!,
       toggleButtonBGColor:
           Color.lerp(toggleButtonBGColor, other.toggleButtonBGColor, t)!,
+      calendarWeekendBGColor:
+          Color.lerp(calendarWeekendBGColor, other.calendarWeekendBGColor, t)!,
       code: other.code,
       callout: other.callout,
       caption: other.caption,

+ 4 - 1
frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/icon_button.dart

@@ -16,6 +16,7 @@ class FlowyIconButton extends StatelessWidget {
   final String? tooltipText;
   final InlineSpan? richTooltipText;
   final bool preferBelow;
+  final BoxDecoration? decoration;
 
   const FlowyIconButton({
     Key? key,
@@ -27,6 +28,7 @@ class FlowyIconButton extends StatelessWidget {
     this.iconColorOnHover,
     this.iconPadding = EdgeInsets.zero,
     this.radius,
+    this.decoration,
     this.tooltipText,
     this.richTooltipText,
     this.preferBelow = true,
@@ -47,11 +49,12 @@ class FlowyIconButton extends StatelessWidget {
     assert(size.width > iconPadding.horizontal);
     assert(size.height > iconPadding.vertical);
 
-    return ConstrainedBox(
+    return Container(
       constraints: BoxConstraints.tightFor(
         width: size.width,
         height: size.height,
       ),
+      decoration: decoration,
       child: Tooltip(
         preferBelow: preferBelow,
         message: tooltipMessage,

+ 13 - 6
frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text_field.dart

@@ -5,8 +5,9 @@ import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 
 class FlowyTextField extends StatefulWidget {
-  final String hintText;
-  final String text;
+  final String? hintText;
+  final String? text;
+  final TextStyle? textStyle;
   final void Function(String)? onChanged;
   final void Function()? onEditingComplete;
   final void Function(String)? onSubmitted;
@@ -23,7 +24,8 @@ class FlowyTextField extends StatefulWidget {
 
   const FlowyTextField({
     this.hintText = "",
-    this.text = "",
+    this.text,
+    this.textStyle,
     this.onChanged,
     this.onEditingComplete,
     this.onSubmitted,
@@ -55,7 +57,10 @@ class FlowyTextFieldState extends State<FlowyTextField> {
     focusNode.addListener(notifyDidEndEditing);
 
     controller = widget.controller ?? TextEditingController();
-    controller.text = widget.text;
+    if (widget.text != null) {
+      controller.text = widget.text!;
+    }
+
     if (widget.autoFocus) {
       WidgetsBinding.instance.addPostFrameCallback((_) {
         focusNode.requestFocus();
@@ -105,7 +110,7 @@ class FlowyTextFieldState extends State<FlowyTextField> {
       maxLines: widget.maxLines,
       maxLength: widget.maxLength,
       maxLengthEnforcement: MaxLengthEnforcement.truncateAfterCompositionEnds,
-      style: Theme.of(context).textTheme.bodySmall,
+      style: widget.textStyle ?? Theme.of(context).textTheme.bodySmall,
       decoration: InputDecoration(
         constraints: BoxConstraints(
             maxHeight: widget.errorText?.isEmpty ?? true ? 32 : 58),
@@ -158,7 +163,9 @@ class FlowyTextFieldState extends State<FlowyTextField> {
   @override
   void dispose() {
     focusNode.removeListener(notifyDidEndEditing);
-    focusNode.dispose();
+    if (widget.focusNode == null) {
+      focusNode.dispose();
+    }
     super.dispose();
   }
 

+ 1 - 0
frontend/resources/translations/en.json

@@ -659,6 +659,7 @@
   "calendar": {
     "menuName": "Calendar",
     "defaultNewCalendarTitle": "Untitled",
+    "newEventButtonTooltip": "Add a new event",
     "navigation": {
       "today": "Today",
       "jumpToday": "Jump to Today",