chore: improve setting tab bar dropdown (#3756)

* chore: improve setting tab bar dropdown

* test: fix integration tests
This commit is contained in:
Richard Shiue 2023-10-23 19:23:27 +08:00 committed by GitHub
parent d51c7f382f
commit b16a102f85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 178 additions and 172 deletions

View File

@ -38,7 +38,7 @@ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar
import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_header.dart';
import 'package:appflowy/plugins/database_view/tar_bar/tar_bar_add_button.dart';
import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart';
import 'package:appflowy/plugins/database_view/widgets/field/grid_property.dart';
import 'package:appflowy/plugins/database_view/widgets/setting/setting_property_list.dart';
import 'package:appflowy/plugins/database_view/widgets/row/accessory/cell_accessory.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor.dart';
@ -52,7 +52,6 @@ import 'package:appflowy/plugins/database_view/widgets/row/row_banner.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_document.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_property.dart';
import 'package:appflowy/plugins/database_view/widgets/setting/database_setting.dart';
import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_menu_item.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
@ -1132,7 +1131,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
/// Should call [tapDatabaseSettingButton] first.
Future<void> tapViewPropertiesButton() async {
final findSettingItem = find.byType(DatabaseSettingItem);
final findSettingItem = find.byType(DatabaseSettingListPopover);
final findLayoutButton = find.byWidgetPredicate(
(widget) =>
widget is FlowyText &&
@ -1149,7 +1148,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
/// Should call [tapDatabaseSettingButton] first.
Future<void> tapDatabaseLayoutButton() async {
final findSettingItem = find.byType(DatabaseSettingItem);
final findSettingItem = find.byType(DatabaseSettingListPopover);
final findLayoutButton = find.byWidgetPredicate(
(widget) =>
widget is FlowyText &&
@ -1165,7 +1164,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
}
Future<void> tapCalendarLayoutSettingButton() async {
final findSettingItem = find.byType(DatabaseSettingItem);
final findSettingItem = find.byType(DatabaseSettingListPopover);
final findLayoutButton = find.byWidgetPredicate(
(widget) =>
widget is FlowyText &&
@ -1505,7 +1504,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
) async {
final field = find.byWidgetPredicate(
(widget) =>
widget is GridPropertyCell && widget.fieldInfo.name == fieldName,
widget is DatabasePropertyCell && widget.fieldInfo.name == fieldName,
);
final toggleVisibilityButton =
find.descendant(of: field, matching: find.byType(FlowyIconButton));

View File

@ -25,34 +25,44 @@ class DatabasePropertyBloc
) {
on<DatabasePropertyEvent>(
(event, emit) async {
await event.map(
initial: (_Initial value) {
await event.when(
initial: () {
_startListening();
},
setFieldVisibility: (_SetFieldVisibility value) async {
setFieldVisibility: (fieldId, visibility) async {
final fieldSettingsSvc = FieldSettingsBackendService(
viewId: viewId,
);
final result = await fieldSettingsSvc.updateFieldSettings(
fieldId: value.fieldId,
fieldVisibility: value.visibility,
fieldId: fieldId,
fieldVisibility: visibility,
);
result.fold((l) => null, (err) => Log.error(err));
},
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
emit(state.copyWith(fieldContexts: value.fields));
didReceiveFieldUpdate: (fields) {
emit(state.copyWith(fieldContexts: fields));
},
moveField: (_MoveField value) async {
moveField: (fieldId, fromIndex, toIndex) async {
if (fromIndex < toIndex) {
toIndex--;
}
final fieldContexts = List<FieldInfo>.from(state.fieldContexts);
fieldContexts.insert(
toIndex,
fieldContexts.removeAt(fromIndex),
);
emit(state.copyWith(fieldContexts: fieldContexts));
final fieldBackendService = FieldBackendService(
viewId: viewId,
fieldId: value.fieldId,
fieldId: fieldId,
);
final result = await fieldBackendService.moveField(
value.fromIndex,
value.toIndex,
fromIndex,
toIndex,
);
result.fold((l) => null, (r) => Log.error(r));

View File

@ -228,6 +228,7 @@ class LayoutDateField extends StatelessWidget {
Widget build(BuildContext context) {
return AppFlowyPopover(
direction: PopoverDirection.leftWithTopAligned,
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
constraints: BoxConstraints.loose(const Size(300, 400)),
mutex: popoverMutex,
offset: const Offset(-16, 0),
@ -346,6 +347,7 @@ class FirstDayOfWeek extends StatelessWidget {
return AppFlowyPopover(
direction: PopoverDirection.leftWithTopAligned,
constraints: BoxConstraints.loose(const Size(300, 400)),
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
mutex: popoverMutex,
offset: const Offset(-16, 0),
popupBuilder: (context) {

View File

@ -1,75 +0,0 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
import '../../grid/presentation/layout/sizes.dart';
import 'setting_button.dart';
class DatabaseSettingList extends StatelessWidget {
final DatabaseController databaseContoller;
final Function(DatabaseSettingAction, DatabaseController) onAction;
const DatabaseSettingList({
required this.databaseContoller,
required this.onAction,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final cells = actionsForDatabaseLayout(databaseContoller.databaseLayout)
.map((action) {
return DatabaseSettingItem(
action: action,
onAction: (action) => onAction(action, databaseContoller),
);
}).toList();
return ListView.separated(
shrinkWrap: true,
controller: ScrollController(),
itemCount: cells.length,
separatorBuilder: (context, index) {
return VSpace(GridSize.typeOptionSeparatorHeight);
},
physics: StyledScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return cells[index];
},
);
}
}
class DatabaseSettingItem extends StatelessWidget {
final DatabaseSettingAction action;
final Function(DatabaseSettingAction) onAction;
const DatabaseSettingItem({
required this.action,
required this.onAction,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return SizedBox(
height: GridSize.popoverItemHeight,
child: FlowyButton(
hoverColor: AFThemeExtension.of(context).lightGreyHover,
text: FlowyText.medium(
action.title(),
color: AFThemeExtension.of(context).textColor,
),
onTap: () => onAction(action),
leftIcon: FlowySvg(
action.iconData(),
color: Theme.of(context).iconTheme.color,
),
),
);
}
}

View File

@ -11,12 +11,10 @@ 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:styled_widget/styled_widget.dart';
import '../../grid/presentation/layout/sizes.dart';
import '../../grid/presentation/widgets/toolbar/grid_layout.dart';
import '../field/grid_property.dart';
import 'database_setting.dart';
import 'setting_property_list.dart';
class SettingButton extends StatefulWidget {
final DatabaseController databaseController;
@ -39,7 +37,6 @@ class _SettingButtonState extends State<SettingButton> {
constraints: BoxConstraints.loose(const Size(200, 400)),
direction: PopoverDirection.bottomWithCenterAligned,
offset: const Offset(0, 8),
margin: EdgeInsets.zero,
triggerActions: PopoverTriggerFlags.none,
child: FlowyTextButton(
LocaleKeys.settings_title.tr(),
@ -53,7 +50,7 @@ class _SettingButtonState extends State<SettingButton> {
onPressed: () => _popoverController.show(),
),
popupBuilder: (BuildContext context) {
return _DatabaseSettingListPopover(
return DatabaseSettingListPopover(
databaseController: widget.databaseController,
);
},
@ -61,10 +58,10 @@ class _SettingButtonState extends State<SettingButton> {
}
}
class _DatabaseSettingListPopover extends StatefulWidget {
class DatabaseSettingListPopover extends StatefulWidget {
final DatabaseController databaseController;
const _DatabaseSettingListPopover({
const DatabaseSettingListPopover({
required this.databaseController,
Key? key,
}) : super(key: key);
@ -74,59 +71,40 @@ class _DatabaseSettingListPopover extends StatefulWidget {
}
class _DatabaseSettingListPopoverState
extends State<_DatabaseSettingListPopover> {
DatabaseSettingAction? _action;
extends State<DatabaseSettingListPopover> {
late final PopoverMutex popoverMutex;
@override
void initState() {
super.initState();
popoverMutex = PopoverMutex();
}
@override
Widget build(BuildContext context) {
if (_action == null) {
return DatabaseSettingList(
databaseContoller: widget.databaseController,
onAction: (action, settingContext) {
setState(() {
_action = action;
});
},
).padding(all: 6.0);
} else {
switch (_action!) {
case DatabaseSettingAction.showLayout:
return DatabaseLayoutList(
viewId: widget.databaseController.viewId,
currentLayout: widget.databaseController.databaseLayout,
);
case DatabaseSettingAction.showGroup:
return DatabaseGroupList(
viewId: widget.databaseController.viewId,
fieldController: widget.databaseController.fieldController,
onDismissed: () {
// widget.popoverController.close();
},
);
case DatabaseSettingAction.showProperties:
return Column(
mainAxisSize: MainAxisSize.min,
children: [
DatabasePropertyList(
viewId: widget.databaseController.viewId,
fieldController: widget.databaseController.fieldController,
final cells =
actionsForDatabaseLayout(widget.databaseController.databaseLayout)
.map(
(action) => action.build(
context,
widget.databaseController,
popoverMutex,
),
FlowyText.regular(
LocaleKeys.grid_settings_reorderPropertiesTooltip.tr(),
),
const VSpace(8),
],
);
case DatabaseSettingAction.showCalendarLayout:
return CalendarLayoutSetting(
viewId: widget.databaseController.viewId,
fieldController: widget.databaseController.fieldController,
calendarSettingController: ICalendarSettingImpl(
widget.databaseController,
),
);
}
}
)
.toList();
return ListView.separated(
shrinkWrap: true,
controller: ScrollController(),
itemCount: cells.length,
separatorBuilder: (context, index) {
return VSpace(GridSize.typeOptionSeparatorHeight);
},
physics: StyledScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return cells[index];
},
);
}
}
@ -179,6 +157,58 @@ extension DatabaseSettingActionExtension on DatabaseSettingAction {
return LocaleKeys.calendar_settings_name.tr();
}
}
Widget build(
BuildContext context,
DatabaseController databaseController,
PopoverMutex popoverMutex,
) {
final popover = switch (this) {
DatabaseSettingAction.showLayout => DatabaseLayoutList(
viewId: databaseController.viewId,
currentLayout: databaseController.databaseLayout,
),
DatabaseSettingAction.showGroup => DatabaseGroupList(
viewId: databaseController.viewId,
fieldController: databaseController.fieldController,
onDismissed: () {},
),
DatabaseSettingAction.showProperties => DatabasePropertyList(
viewId: databaseController.viewId,
fieldController: databaseController.fieldController,
),
DatabaseSettingAction.showCalendarLayout => CalendarLayoutSetting(
viewId: databaseController.viewId,
fieldController: databaseController.fieldController,
calendarSettingController: ICalendarSettingImpl(
databaseController,
),
),
};
return AppFlowyPopover(
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
direction: PopoverDirection.leftWithTopAligned,
mutex: popoverMutex,
margin: EdgeInsets.zero,
offset: const Offset(-16, 0),
child: SizedBox(
height: GridSize.popoverItemHeight,
child: FlowyButton(
hoverColor: AFThemeExtension.of(context).lightGreyHover,
text: FlowyText.medium(
title(),
color: AFThemeExtension.of(context).textColor,
),
leftIcon: FlowySvg(
iconData(),
color: Theme.of(context).iconTheme.color,
),
),
),
popupBuilder: (context) => popover,
);
}
}
/// Returns the list of actions that should be shown for the given database layout.

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
@ -7,12 +9,12 @@ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:collection/collection.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';
import 'package:reorderables/reorderables.dart';
import 'package:styled_widget/styled_widget.dart';
import '../../grid/presentation/layout/sizes.dart';
@ -44,29 +46,43 @@ class _DatabasePropertyListState extends State<DatabasePropertyList> {
)..add(const DatabasePropertyEvent.initial()),
child: BlocBuilder<DatabasePropertyBloc, DatabasePropertyState>(
builder: (context, state) {
final cells = state.fieldContexts.map((field) {
return GridPropertyCell(
final cells = state.fieldContexts.mapIndexed((index, field) {
return DatabasePropertyCell(
key: ValueKey(field.id),
viewId: widget.viewId,
fieldInfo: field,
popoverMutex: _popoverMutex,
index: index,
);
}).toList();
return ReorderableColumn(
needsLongPressDraggable: false,
buildDraggableFeedback: (context, constraints, child) =>
ConstrainedBox(
constraints: constraints,
child: Material(color: Colors.transparent, child: child),
),
onReorder: (from, to) => context.read<DatabasePropertyBloc>().add(
DatabasePropertyEvent.moveField(
fieldId: cells[from].fieldInfo.id,
fromIndex: from,
toIndex: to,
return ReorderableListView(
proxyDecorator: (child, index, _) => Material(
color: Colors.transparent,
child: Stack(
children: [
child,
MouseRegion(
cursor: Platform.isWindows
? SystemMouseCursors.click
: SystemMouseCursors.grabbing,
child: const SizedBox.expand(),
),
),
],
),
),
buildDefaultDragHandles: false,
shrinkWrap: true,
onReorder: (from, to) {
context.read<DatabasePropertyBloc>().add(
DatabasePropertyEvent.moveField(
fieldId: cells[from].fieldInfo.id,
fromIndex: from,
toIndex: to,
),
);
},
onReorderStart: (_) => _popoverMutex.close(),
padding: const EdgeInsets.symmetric(vertical: 6.0),
children: cells,
);
@ -77,23 +93,25 @@ class _DatabasePropertyListState extends State<DatabasePropertyList> {
}
@visibleForTesting
class GridPropertyCell extends StatefulWidget {
class DatabasePropertyCell extends StatefulWidget {
final FieldInfo fieldInfo;
final String viewId;
final PopoverMutex popoverMutex;
final int index;
const GridPropertyCell({
const DatabasePropertyCell({
super.key,
required this.fieldInfo,
required this.viewId,
required this.popoverMutex,
required this.index,
});
@override
State<GridPropertyCell> createState() => _GridPropertyCellState();
State<DatabasePropertyCell> createState() => _DatabasePropertyCellState();
}
class _GridPropertyCellState extends State<GridPropertyCell> {
class _DatabasePropertyCellState extends State<DatabasePropertyCell> {
final PopoverController _popoverController = PopoverController();
@override
@ -109,7 +127,7 @@ class _GridPropertyCellState extends State<GridPropertyCell> {
return AppFlowyPopover(
mutex: widget.popoverMutex,
controller: _popoverController,
offset: const Offset(8, 0),
offset: const Offset(-8, 0),
direction: PopoverDirection.leftWithTopAligned,
constraints: BoxConstraints.loose(const Size(240, 400)),
triggerActions: PopoverTriggerFlags.none,
@ -122,9 +140,31 @@ class _GridPropertyCellState extends State<GridPropertyCell> {
widget.fieldInfo.name,
color: AFThemeExtension.of(context).textColor,
),
leftIcon: FlowySvg(
widget.fieldInfo.fieldType.icon(),
color: Theme.of(context).iconTheme.color,
leftIconSize: const Size(36, 18),
leftIcon: Row(
children: [
ReorderableDragStartListener(
index: widget.index,
child: MouseRegion(
cursor: Platform.isWindows
? SystemMouseCursors.click
: SystemMouseCursors.grab,
child: SizedBox(
width: 14,
height: 14,
child: FlowySvg(
FlowySvgs.drag_element_s,
color: Theme.of(context).iconTheme.color,
),
),
),
),
const HSpace(6.0),
FlowySvg(
widget.fieldInfo.fieldType.icon(),
color: Theme.of(context).iconTheme.color,
),
],
),
rightIcon: FlowyIconButton(
hoverColor: Colors.transparent,