mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-04-25 07:07:32 -04:00
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:
parent
7aac4bc90b
commit
db8d030a85
5 changed files with 158 additions and 58 deletions
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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,24 +219,29 @@ 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) {
|
|
||||||
final dataController = RowController(
|
void showEventDetails({
|
||||||
rowId: event.eventId,
|
required BuildContext context,
|
||||||
viewId: widget.view.id,
|
required CalendarDayEvent event,
|
||||||
rowCache: _calendarBloc.rowCache,
|
required String viewId,
|
||||||
);
|
required RowCache rowCache,
|
||||||
|
}) {
|
||||||
FlowyOverlay.show(
|
final dataController = RowController(
|
||||||
context: context,
|
rowId: event.eventId,
|
||||||
builder: (BuildContext context) {
|
viewId: viewId,
|
||||||
return RowDetailPage(
|
rowCache: rowCache,
|
||||||
cellBuilder: GridCellBuilder(
|
);
|
||||||
cellCache: _calendarBloc.rowCache.cellCache,
|
|
||||||
),
|
FlowyOverlay.show(
|
||||||
dataController: dataController,
|
context: context,
|
||||||
);
|
builder: (BuildContext context) {
|
||||||
},
|
return RowDetailPage(
|
||||||
);
|
cellBuilder: GridCellBuilder(
|
||||||
}
|
cellCache: rowCache.cellCache,
|
||||||
|
),
|
||||||
|
dataController: dataController,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue