feat: duplicate calendar event (#4816)

* feat: duplicate calendar event

* test: add simple test
This commit is contained in:
Mathias Mogensen 2024-03-04 12:42:00 +01:00 committed by GitHub
parent ba965caa8f
commit 107e3cea4f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 103 additions and 24 deletions

View File

@ -1,5 +1,7 @@
import 'package:appflowy/plugins/database/calendar/presentation/calendar_event_editor.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
@ -147,6 +149,49 @@ void main() {
tester.assertNumberOfEventsOnSpecificDay(1, DateTime.now()); tester.assertNumberOfEventsOnSpecificDay(1, DateTime.now());
}); });
testWidgets('create and duplicate calendar event', (tester) async {
const customTitle = "EventTitleCustom";
await tester.initializeAppFlowy();
await tester.tapGoButton();
// Create the calendar view
await tester.createNewPageWithNameUnderParent(
layout: ViewLayoutPB.Calendar,
);
// Scroll until today's date cell is visible
await tester.scrollToToday();
// Hover over today's calendar cell
await tester.hoverOnTodayCalendarCell(
// Tap on create new event button
onHover: () async => tester.tapAddCalendarEventButton(),
);
// Make sure that the event editor popup is shown
tester.assertEventEditorOpen();
tester.assertNumberOfEventsInCalendar(1);
// Change the title of the event
await tester.editEventTitle(customTitle);
// Duplicate event
final duplicateBtnFinder = find
.descendant(
of: find.byType(CalendarEventEditor),
matching: find.byType(
FlowyIconButton,
),
)
.first;
await tester.tap(duplicateBtnFinder);
await tester.pumpAndSettle();
tester.assertNumberOfEventsInCalendar(2, title: customTitle);
});
testWidgets('rescheduling events', (tester) async { testWidgets('rescheduling events', (tester) async {
await tester.initializeAppFlowy(); await tester.initializeAppFlowy();
await tester.tapGoButton(); await tester.tapGoButton();

View File

@ -64,6 +64,20 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
createEvent: (DateTime date) async { createEvent: (DateTime date) async {
await _createEvent(date); await _createEvent(date);
}, },
duplicateEvent: (String viewId, String rowId) async {
final result = await RowBackendService.duplicateRow(viewId, rowId);
result.fold(
(_) => null,
(e) => Log.error('Failed to duplicate event: $e', e),
);
},
deleteEvent: (String viewId, String rowId) async {
final result = await RowBackendService.deleteRow(viewId, rowId);
result.fold(
(_) => null,
(e) => Log.error('Failed to delete event: $e', e),
);
},
newEventPopupDisplayed: () { newEventPopupDisplayed: () {
emit(state.copyWith(editingEvent: null)); emit(state.copyWith(editingEvent: null));
}, },
@ -407,6 +421,12 @@ class CalendarEvent with _$CalendarEvent {
const factory CalendarEvent.didReceiveDatabaseUpdate(DatabasePB database) = const factory CalendarEvent.didReceiveDatabaseUpdate(DatabasePB database) =
_ReceiveDatabaseUpdate; _ReceiveDatabaseUpdate;
const factory CalendarEvent.duplicateEvent(String viewId, String rowId) =
_DuplicateEvent;
const factory CalendarEvent.deleteEvent(String viewId, String rowId) =
_DeleteEvent;
} }
@freezed @freezed

View File

@ -1,3 +1,5 @@
import 'package:flutter/material.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart'; import 'package:appflowy/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart';
import 'package:appflowy/plugins/database/application/database_controller.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart';
import 'package:appflowy/plugins/database/application/row/row_cache.dart'; import 'package:appflowy/plugins/database/application/row/row_cache.dart';
@ -9,11 +11,11 @@ import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import '../application/calendar_bloc.dart'; import '../application/calendar_bloc.dart';
import 'calendar_event_editor.dart'; import 'calendar_event_editor.dart';
class EventCard extends StatefulWidget { class EventCard extends StatefulWidget {
@ -144,15 +146,18 @@ class _EventCardState extends State<EventCard> {
asBarrier: true, asBarrier: true,
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
offset: const Offset(10.0, 0), offset: const Offset(10.0, 0),
popupBuilder: (BuildContext popoverContext) { popupBuilder: (_) {
final settings = context.watch<CalendarBloc>().state.settings; final settings = context.watch<CalendarBloc>().state.settings;
if (settings == null) { if (settings == null) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
return CalendarEventEditor( return BlocProvider.value(
databaseController: widget.databaseController, value: context.read<CalendarBloc>(),
rowMeta: widget.event.event.rowMeta, child: CalendarEventEditor(
layoutSettings: settings, databaseController: widget.databaseController,
rowMeta: widget.event.event.rowMeta,
layoutSettings: settings,
),
); );
}, },
child: Container( child: Container(

View File

@ -1,25 +1,25 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/application/cell/bloc/text_cell_bloc.dart';
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
import 'package:appflowy/plugins/database/application/database_controller.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart';
import 'package:appflowy/plugins/database/application/field/field_controller.dart'; import 'package:appflowy/plugins/database/application/field/field_controller.dart';
import 'package:appflowy/plugins/database/application/row/row_controller.dart'; import 'package:appflowy/plugins/database/application/row/row_controller.dart';
import 'package:appflowy/plugins/database/application/row/row_service.dart'; import 'package:appflowy/plugins/database/calendar/application/calendar_bloc.dart';
import 'package:appflowy/plugins/database/calendar/application/calendar_event_editor_bloc.dart'; import 'package:appflowy/plugins/database/calendar/application/calendar_event_editor_bloc.dart';
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart';
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/text.dart'; import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/text.dart';
import 'package:appflowy/plugins/database/widgets/row/accessory/cell_accessory.dart'; import 'package:appflowy/plugins/database/widgets/row/accessory/cell_accessory.dart';
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart';
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
import 'package:appflowy/plugins/database/application/cell/bloc/text_cell_bloc.dart';
import 'package:appflowy/plugins/database/widgets/row/row_detail.dart'; import 'package:appflowy/plugins/database/widgets/row/row_detail.dart';
import 'package:appflowy/util/field_type_extension.dart'; import 'package:appflowy/util/field_type_extension.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
class CalendarEventEditor extends StatelessWidget { class CalendarEventEditor extends StatelessWidget {
@ -86,17 +86,28 @@ class EventEditorControls extends StatelessWidget {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
FlowyIconButton(
width: 20,
icon: const FlowySvg(FlowySvgs.m_duplicate_s),
iconColorOnHover: Theme.of(context).colorScheme.onSecondary,
onPressed: () => context.read<CalendarBloc>().add(
CalendarEvent.duplicateEvent(
rowController.viewId,
rowController.rowId,
),
),
),
const HSpace(8.0),
FlowyIconButton( FlowyIconButton(
width: 20, width: 20,
icon: const FlowySvg(FlowySvgs.delete_s), icon: const FlowySvg(FlowySvgs.delete_s),
iconColorOnHover: Theme.of(context).colorScheme.onSecondary, iconColorOnHover: Theme.of(context).colorScheme.onSecondary,
onPressed: () async { onPressed: () => context.read<CalendarBloc>().add(
final result = await RowBackendService.deleteRow( CalendarEvent.deleteEvent(
rowController.viewId, rowController.viewId,
rowController.rowId, rowController.rowId,
); ),
result.fold((l) => null, (err) => Log.error(err)); ),
},
), ),
const HSpace(8.0), const HSpace(8.0),
FlowyIconButton( FlowyIconButton(
@ -107,12 +118,10 @@ class EventEditorControls extends StatelessWidget {
PopoverContainer.of(context).close(); PopoverContainer.of(context).close();
FlowyOverlay.show( FlowyOverlay.show(
context: context, context: context,
builder: (BuildContext context) { builder: (_) => RowDetailPage(
return RowDetailPage( databaseController: databaseController,
databaseController: databaseController, rowController: rowController,
rowController: rowController, ),
);
},
); );
}, },
), ),