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>
This commit is contained in:
Richard Shiue 2023-05-04 11:05:17 +08:00 committed by GitHub
parent 7aac4bc90b
commit db8d030a85
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 158 additions and 58 deletions

View file

@ -418,7 +418,9 @@
"showWeekNumbers": "Show week numbers", "showWeekNumbers": "Show week numbers",
"showWeekends": "Show weekends", "showWeekends": "Show weekends",
"firstDayOfWeek": "Start week on", "firstDayOfWeek": "Start week on",
"layoutDateField": "Layout calendar by" "layoutDateField": "Layout calendar by",
"noDateTitle": "No Date",
"emptyNoDate": "No unscheduled events"
} }
} }
} }

View file

@ -1,12 +1,9 @@
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart'; 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.dart';
import 'package:appflowy/plugins/database_view/widgets/card/card_cell_builder.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/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/number_card_cell.dart';
import 'package:appflowy/plugins/database_view/widgets/card/cells/url_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:appflowy_backend/protobuf/flowy-database/field_entities.pbenum.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/image.dart';
@ -19,6 +16,7 @@ import 'package:provider/provider.dart';
import '../../grid/presentation/layout/sizes.dart'; import '../../grid/presentation/layout/sizes.dart';
import '../../widgets/row/cells/select_option_cell/extension.dart'; import '../../widgets/row/cells/select_option_cell/extension.dart';
import '../application/calendar_bloc.dart'; import '../application/calendar_bloc.dart';
import 'calendar_page.dart';
class CalendarDayCard extends StatelessWidget { class CalendarDayCard extends StatelessWidget {
final String viewId; final String viewId;
@ -193,7 +191,12 @@ class CalendarDayCard extends StatelessWidget {
cardData: event.dateFieldId, cardData: event.dateFieldId,
isEditing: false, isEditing: false,
cellBuilder: cellBuilder, cellBuilder: cellBuilder,
openCard: (context) => _showRowDetailPage(event, context), openCard: (context) => showEventDetails(
context: context,
event: event,
viewId: viewId,
rowCache: _rowCache,
),
styleConfiguration: const RowCardStyleConfiguration( styleConfiguration: const RowCardStyleConfiguration(
showAccessory: false, showAccessory: false,
cellPadding: EdgeInsets.zero, cellPadding: EdgeInsets.zero,
@ -204,7 +207,12 @@ class CalendarDayCard extends StatelessWidget {
); );
return GestureDetector( return GestureDetector(
onTap: () => _showRowDetailPage(event, context), onTap: () => showEventDetails(
context: context,
event: event,
viewId: viewId,
rowCache: _rowCache,
),
child: MouseRegion( child: MouseRegion(
cursor: SystemMouseCursors.click, cursor: SystemMouseCursors.click,
child: Container( 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) { notifyEnter(BuildContext context, bool isEnter) {
Provider.of<_CardEnterNotifier>( Provider.of<_CardEnterNotifier>(
context, context,

View file

@ -9,6 +9,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../../application/row/row_cache.dart';
import '../../application/row/row_data_controller.dart'; import '../../application/row/row_data_controller.dart';
import '../../widgets/row/cell_builder.dart'; import '../../widgets/row/cell_builder.dart';
import '../../widgets/row/row_detail.dart'; import '../../widgets/row/row_detail.dart';
@ -76,7 +77,12 @@ class _CalendarPageState extends State<CalendarPage> {
listenWhen: (p, c) => p.editEvent != c.editEvent, listenWhen: (p, c) => p.editEvent != c.editEvent,
listener: (context, state) { listener: (context, state) {
if (state.editEvent != null) { if (state.editEvent != null) {
_showEditEventPage(state.editEvent!.event!, context); showEventDetails(
context: context,
event: state.editEvent!.event!,
viewId: widget.view.id,
rowCache: _calendarBloc.rowCache,
);
} }
}, },
), ),
@ -213,12 +219,18 @@ class _CalendarPageState extends State<CalendarPage> {
// MonthView places the first day of week on the second column for some reason. // MonthView places the first day of week on the second column for some reason.
return WeekDays.values[(dayOfWeek + 1) % 7]; return WeekDays.values[(dayOfWeek + 1) % 7];
} }
}
void _showEditEventPage(CalendarDayEvent event, BuildContext context) { void showEventDetails({
required BuildContext context,
required CalendarDayEvent event,
required String viewId,
required RowCache rowCache,
}) {
final dataController = RowController( final dataController = RowController(
rowId: event.eventId, rowId: event.eventId,
viewId: widget.view.id, viewId: viewId,
rowCache: _calendarBloc.rowCache, rowCache: rowCache,
); );
FlowyOverlay.show( FlowyOverlay.show(
@ -226,11 +238,10 @@ class _CalendarPageState extends State<CalendarPage> {
builder: (BuildContext context) { builder: (BuildContext context) {
return RowDetailPage( return RowDetailPage(
cellBuilder: GridCellBuilder( cellBuilder: GridCellBuilder(
cellCache: _calendarBloc.rowCache.cellCache, cellCache: rowCache.cellCache,
), ),
dataController: dataController, dataController: dataController,
); );
}, },
); );
}
} }

View file

@ -217,6 +217,7 @@ class LayoutDateField extends StatelessWidget {
direction: PopoverDirection.leftWithTopAligned, direction: PopoverDirection.leftWithTopAligned,
constraints: BoxConstraints.loose(const Size(300, 400)), constraints: BoxConstraints.loose(const Size(300, 400)),
mutex: popoverMutex, mutex: popoverMutex,
offset: const Offset(-16, 0),
popupBuilder: (context) { popupBuilder: (context) {
return BlocProvider( return BlocProvider(
create: (context) => getIt<DatabasePropertyBloc>( create: (context) => getIt<DatabasePropertyBloc>(
@ -237,9 +238,9 @@ class LayoutDateField extends StatelessWidget {
onUpdated(fieldInfo.id); onUpdated(fieldInfo.id);
popoverMutex.close(); popoverMutex.close();
}, },
leftIcon: svgWidget('grid/field/date'), leftIcon: const FlowySvg(name: 'grid/field/date'),
rightIcon: fieldInfo.id == fieldId rightIcon: fieldInfo.id == fieldId
? svgWidget('grid/checkmark') ? const FlowySvg(name: 'grid/checkmark')
: null, : null,
), ),
); );
@ -333,6 +334,7 @@ class FirstDayOfWeek extends StatelessWidget {
direction: PopoverDirection.leftWithTopAligned, direction: PopoverDirection.leftWithTopAligned,
constraints: BoxConstraints.loose(const Size(300, 400)), constraints: BoxConstraints.loose(const Size(300, 400)),
mutex: popoverMutex, mutex: popoverMutex,
offset: const Offset(-16, 0),
popupBuilder: (context) { popupBuilder: (context) {
final symbols = final symbols =
DateFormat.EEEE(context.locale.toLanguageTag()).dateSymbols; DateFormat.EEEE(context.locale.toLanguageTag()).dateSymbols;
@ -348,8 +350,9 @@ class FirstDayOfWeek extends StatelessWidget {
onUpdated(index); onUpdated(index);
popoverMutex.close(); popoverMutex.close();
}, },
rightIcon: rightIcon: firstDayOfWeek == index
firstDayOfWeek == index ? svgWidget('grid/checkmark') : null, ? const FlowySvg(name: 'grid/checkmark')
: null,
), ),
); );
}).toList(); }).toList();

View file

@ -1,6 +1,8 @@
import 'package:appflowy/generated/locale_keys.g.dart'; 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/plugins/database_view/grid/presentation/layout/sizes.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:calendar_view/calendar_view.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -19,7 +21,8 @@ class CalendarToolbar extends StatelessWidget {
height: 40, height: 40,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: const [
_UnscheduleEventsButton(),
_SettingButton(), _SettingButton(),
], ],
), ),
@ -28,25 +31,22 @@ class CalendarToolbar extends StatelessWidget {
} }
class _SettingButton extends StatefulWidget { class _SettingButton extends StatefulWidget {
const _SettingButton({Key? key}) : super(key: key);
@override @override
State<StatefulWidget> createState() => _SettingButtonState(); State<StatefulWidget> createState() => _SettingButtonState();
} }
class _SettingButtonState extends State<_SettingButton> { class _SettingButtonState extends State<_SettingButton> {
late PopoverController popoverController;
@override @override
void initState() { void initState() {
popoverController = PopoverController();
super.initState(); super.initState();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AppFlowyPopover( return AppFlowyPopover(
controller: popoverController,
direction: PopoverDirection.bottomWithRightAligned, direction: PopoverDirection.bottomWithRightAligned,
triggerActions: PopoverTriggerFlags.none,
constraints: BoxConstraints.loose(const Size(300, 400)), constraints: BoxConstraints.loose(const Size(300, 400)),
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
child: FlowyTextButton( child: FlowyTextButton(
@ -54,7 +54,6 @@ class _SettingButtonState extends State<_SettingButton> {
fillColor: Colors.transparent, fillColor: Colors.transparent,
hoverColor: AFThemeExtension.of(context).lightGreyHover, hoverColor: AFThemeExtension.of(context).lightGreyHover,
padding: GridSize.typeOptionContentInsets, padding: GridSize.typeOptionContentInsets,
onPressed: () => popoverController.show(),
), ),
popupBuilder: (BuildContext popoverContext) { popupBuilder: (BuildContext popoverContext) {
final bloc = context.watch<CalendarBloc>(); 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,
),
);
}
}