mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: field editing bloc refactor and add mobile field editor (#3981)
This commit is contained in:
parent
8afbf28430
commit
66835a5409
@ -32,7 +32,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
// Makesure the text cells are filled with the right content when there are
|
||||
// Make sure the text cells are filled with the right content when there are
|
||||
// multiple text cell
|
||||
testWidgets('edit multiple text cells', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
|
@ -106,7 +106,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('create checklist field ', (tester) async {
|
||||
testWidgets('create checklist field', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
|
@ -123,7 +123,6 @@ void main() {
|
||||
await tester.tapTypeOptionButton();
|
||||
|
||||
await tester.selectFieldType(fieldType);
|
||||
await tester.dismissFieldEditor();
|
||||
|
||||
// After update the field type, the cells should be updated
|
||||
await tester.findCellByFieldType(fieldType);
|
||||
|
@ -21,7 +21,6 @@ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/filter_menu_item.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/footer/grid_footer.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell_action_sheet.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_list.dart';
|
||||
@ -688,7 +687,10 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
}
|
||||
|
||||
Future<void> tapDeletePropertyInFieldEditor() async {
|
||||
final deleteButton = find.byType(DeleteFieldButton);
|
||||
final deleteButton = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is FieldActionCell && widget.action == FieldAction.delete,
|
||||
);
|
||||
await tapButton(deleteButton);
|
||||
|
||||
final confirmButton = find.descendant(
|
||||
@ -765,13 +767,18 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
Future<void> tapHidePropertyButton() async {
|
||||
final field = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is FieldActionCell && widget.action == FieldAction.hide,
|
||||
widget is FieldActionCell &&
|
||||
widget.action == FieldAction.toggleVisibility,
|
||||
);
|
||||
await tapButton(field);
|
||||
}
|
||||
|
||||
Future<void> tapHidePropertyButtonInFieldEditor() async {
|
||||
final button = find.byType(FieldVisibilityToggleButton);
|
||||
final button = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is FieldActionCell &&
|
||||
widget.action == FieldAction.toggleVisibility,
|
||||
);
|
||||
await tapButton(button);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,211 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.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';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'bottom_sheet_action_widget.dart';
|
||||
import 'bottom_sheet_database_field_header.dart';
|
||||
import 'bottom_sheet_rename_widget.dart';
|
||||
|
||||
/// The mobile bottom bar field editor is a two-deep menu. The type option
|
||||
/// sub-menu may have its own sub-menus as well though.
|
||||
enum MobileDBBottomSheetViewMode {
|
||||
// operations shared between all fields
|
||||
general,
|
||||
// operations specific to the field type
|
||||
typeOption,
|
||||
}
|
||||
|
||||
class MobileDBBottomSheetFieldEditor extends StatefulWidget {
|
||||
final String viewId;
|
||||
final FieldController fieldController;
|
||||
final FieldPB field;
|
||||
final MobileDBBottomSheetViewMode initialPage;
|
||||
|
||||
const MobileDBBottomSheetFieldEditor({
|
||||
super.key,
|
||||
required this.viewId,
|
||||
required this.fieldController,
|
||||
required this.field,
|
||||
this.initialPage = MobileDBBottomSheetViewMode.general,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MobileDBBottomSheetFieldEditor> createState() =>
|
||||
_MobileDBBottomSheetFieldEditorState();
|
||||
}
|
||||
|
||||
class _MobileDBBottomSheetFieldEditorState
|
||||
extends State<MobileDBBottomSheetFieldEditor> {
|
||||
late MobileDBBottomSheetViewMode viewMode;
|
||||
late final FieldEditorBloc _fieldEditorBloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
viewMode = widget.initialPage;
|
||||
final loader = FieldTypeOptionLoader(
|
||||
viewId: widget.viewId,
|
||||
field: widget.field,
|
||||
);
|
||||
_fieldEditorBloc = FieldEditorBloc(
|
||||
viewId: widget.viewId,
|
||||
field: widget.field,
|
||||
loader: loader,
|
||||
fieldController: widget.fieldController,
|
||||
)..add(const FieldEditorEvent.initial());
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<FieldEditorBloc>.value(
|
||||
value: _fieldEditorBloc,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 32),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildHeader(),
|
||||
const VSpace(16),
|
||||
_buildBody(),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader() {
|
||||
return MobileDBFieldBottomSheetHeader(
|
||||
showBackButton: viewMode == MobileDBBottomSheetViewMode.typeOption,
|
||||
onBack: () {
|
||||
if (viewMode == MobileDBBottomSheetViewMode.typeOption) {
|
||||
setState(() {
|
||||
viewMode = MobileDBBottomSheetViewMode.general;
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
return switch (viewMode) {
|
||||
MobileDBBottomSheetViewMode.general => MobileDBFieldBottomSheetBody(
|
||||
onAction: (action) {
|
||||
switch (action) {
|
||||
case MobileDBBottomSheetGeneralAction.typeOption:
|
||||
break;
|
||||
case MobileDBBottomSheetGeneralAction.toggleVisibility:
|
||||
_fieldEditorBloc
|
||||
.add(const FieldEditorEvent.toggleFieldVisibility());
|
||||
context.pop();
|
||||
break;
|
||||
case MobileDBBottomSheetGeneralAction.delete:
|
||||
_fieldEditorBloc.add(const FieldEditorEvent.deleteField());
|
||||
context.pop();
|
||||
break;
|
||||
case MobileDBBottomSheetGeneralAction.duplicate:
|
||||
_fieldEditorBloc.add(const FieldEditorEvent.duplicateField());
|
||||
context.pop();
|
||||
}
|
||||
},
|
||||
onRename: (name) {
|
||||
_fieldEditorBloc.add(FieldEditorEvent.renameField(name));
|
||||
},
|
||||
),
|
||||
MobileDBBottomSheetViewMode.typeOption => const SizedBox.shrink(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
enum MobileDBBottomSheetGeneralAction {
|
||||
toggleVisibility,
|
||||
duplicate,
|
||||
delete,
|
||||
typeOption,
|
||||
}
|
||||
|
||||
class MobileDBFieldBottomSheetBody extends StatelessWidget {
|
||||
const MobileDBFieldBottomSheetBody({
|
||||
super.key,
|
||||
required this.onAction,
|
||||
required this.onRename,
|
||||
});
|
||||
|
||||
final void Function(MobileDBBottomSheetGeneralAction action) onAction;
|
||||
final void Function(String name) onRename;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// field name editor
|
||||
MobileBottomSheetRenameWidget(
|
||||
name: context.read<FieldEditorBloc>().state.field.name,
|
||||
onRename: (newName) => onRename(newName),
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
const VSpace(8),
|
||||
// type option button
|
||||
BottomSheetActionWidget(
|
||||
svg: FlowySvgs.date_s,
|
||||
text: LocaleKeys.grid_field_editProperty.tr(),
|
||||
onTap: () {
|
||||
onAction(MobileDBBottomSheetGeneralAction.typeOption);
|
||||
},
|
||||
),
|
||||
const VSpace(8),
|
||||
Row(
|
||||
children: [
|
||||
// hide/show field
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
svg: FlowySvgs.hide_m,
|
||||
text: LocaleKeys.grid_field_hide.tr(),
|
||||
onTap: () {
|
||||
onAction(MobileDBBottomSheetGeneralAction.toggleVisibility);
|
||||
},
|
||||
),
|
||||
),
|
||||
const HSpace(8),
|
||||
// duplicate field
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
svg: FlowySvgs.copy_s,
|
||||
text: LocaleKeys.grid_field_duplicate.tr(),
|
||||
onTap: () {
|
||||
onAction(MobileDBBottomSheetGeneralAction.duplicate);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const VSpace(8),
|
||||
Row(
|
||||
children: [
|
||||
// delete field
|
||||
Expanded(
|
||||
child: BottomSheetActionWidget(
|
||||
svg: FlowySvgs.delete_s,
|
||||
text: LocaleKeys.grid_field_delete.tr(),
|
||||
onTap: () {
|
||||
onAction(MobileDBBottomSheetGeneralAction.delete);
|
||||
},
|
||||
),
|
||||
),
|
||||
const HSpace(8),
|
||||
const Expanded(child: SizedBox.shrink()),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class MobileDBFieldBottomSheetHeader extends StatelessWidget {
|
||||
const MobileDBFieldBottomSheetHeader({
|
||||
super.key,
|
||||
required this.showBackButton,
|
||||
required this.onBack,
|
||||
});
|
||||
|
||||
final bool showBackButton;
|
||||
final VoidCallback onBack;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Row(
|
||||
children: [
|
||||
// back button
|
||||
if (showBackButton)
|
||||
InkWell(
|
||||
onTap: onBack,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Icon(
|
||||
Icons.arrow_back_ios_new_rounded,
|
||||
size: 24.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
// field name
|
||||
Expanded(
|
||||
child: Text(
|
||||
LocaleKeys.grid_field_editProperty.tr(),
|
||||
style: theme.textTheme.labelSmall,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.close,
|
||||
color: theme.hintColor,
|
||||
),
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -8,10 +8,12 @@ class MobileBottomSheetRenameWidget extends StatefulWidget {
|
||||
super.key,
|
||||
required this.name,
|
||||
required this.onRename,
|
||||
this.padding = const EdgeInsets.symmetric(horizontal: 12.0, vertical: 16.0),
|
||||
});
|
||||
|
||||
final String name;
|
||||
final void Function(String name) onRename;
|
||||
final EdgeInsets padding;
|
||||
|
||||
@override
|
||||
State<MobileBottomSheetRenameWidget> createState() =>
|
||||
@ -39,17 +41,13 @@ class _MobileBottomSheetRenameWidgetState
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4.0,
|
||||
vertical: 16.0,
|
||||
),
|
||||
padding: widget.padding,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const HSpace(8.0),
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
height: 44.0,
|
||||
height: 42.0,
|
||||
child: FlowyTextField(
|
||||
controller: controller,
|
||||
),
|
||||
@ -58,17 +56,16 @@ class _MobileBottomSheetRenameWidgetState
|
||||
const HSpace(12.0),
|
||||
FlowyTextButton(
|
||||
LocaleKeys.button_edit.tr(),
|
||||
constraints: const BoxConstraints.tightFor(height: 42),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12.0,
|
||||
horizontal: 16.0,
|
||||
),
|
||||
fontColor: Colors.white,
|
||||
fillColor: Colors.lightBlue.shade300,
|
||||
fillColor: Theme.of(context).primaryColor,
|
||||
onPressed: () {
|
||||
widget.onRename(controller.text);
|
||||
},
|
||||
),
|
||||
const HSpace(8.0),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -4,6 +4,7 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_action_widget.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_bottom_sheet.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_banner_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
|
||||
@ -24,12 +25,16 @@ class MobileCardDetailScreen extends StatefulWidget {
|
||||
const MobileCardDetailScreen({
|
||||
super.key,
|
||||
required this.rowController,
|
||||
required this.fieldController,
|
||||
});
|
||||
|
||||
static const routeName = '/MobileCardDetailScreen';
|
||||
static const argRowController = 'rowController';
|
||||
static const argCellBuilder = 'cellBuilder';
|
||||
static const argFieldController = 'fieldController';
|
||||
|
||||
final RowController rowController;
|
||||
final FieldController fieldController;
|
||||
|
||||
@override
|
||||
State<MobileCardDetailScreen> createState() => _MobileCardDetailScreenState();
|
||||
@ -169,6 +174,7 @@ class _MobileCardDetailScreenState extends State<MobileCardDetailScreen> {
|
||||
MobileRowPropertyList(
|
||||
cellBuilder: _cellBuilder,
|
||||
viewId: widget.rowController.viewId,
|
||||
fieldController: widget.fieldController,
|
||||
),
|
||||
const Divider(),
|
||||
const VSpace(16),
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/card/card_property_edit/mobile_field_editor.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -9,15 +10,18 @@ import 'package:go_router/go_router.dart';
|
||||
class MobileCreateRowFieldScreen extends StatefulWidget {
|
||||
static const routeName = '/MobileCreateRowFieldScreen';
|
||||
static const argViewId = 'viewId';
|
||||
static const argFieldController = 'fieldController';
|
||||
static const argTypeOption = 'typeOption';
|
||||
|
||||
const MobileCreateRowFieldScreen({
|
||||
super.key,
|
||||
required this.viewId,
|
||||
required this.typeOption,
|
||||
super.key,
|
||||
required this.fieldController,
|
||||
});
|
||||
|
||||
final String viewId;
|
||||
final FieldController fieldController;
|
||||
final TypeOptionPB typeOption;
|
||||
|
||||
@override
|
||||
@ -53,6 +57,8 @@ class _MobileCreateRowFieldScreenState
|
||||
viewId: widget.viewId,
|
||||
field: widget.typeOption.field_2,
|
||||
),
|
||||
fieldController: widget.fieldController,
|
||||
field: widget.typeOption.field_2,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/card/card_detail/mobile_create_row_field_screen.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_service.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -8,9 +9,14 @@ import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class MobileCreateRowFieldButton extends StatelessWidget {
|
||||
const MobileCreateRowFieldButton({super.key, required this.viewId});
|
||||
const MobileCreateRowFieldButton({
|
||||
super.key,
|
||||
required this.viewId,
|
||||
required this.fieldController,
|
||||
});
|
||||
|
||||
final String viewId;
|
||||
final FieldController fieldController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -32,6 +38,7 @@ class MobileCreateRowFieldButton extends StatelessWidget {
|
||||
extra: {
|
||||
MobileCreateRowFieldScreen.argViewId: viewId,
|
||||
MobileCreateRowFieldScreen.argTypeOption: typeOption,
|
||||
MobileCreateRowFieldScreen.argFieldController: fieldController,
|
||||
},
|
||||
);
|
||||
},
|
||||
|
@ -38,7 +38,7 @@ class _MobileFieldNameTextFieldState extends State<MobileFieldNameTextField> {
|
||||
onChanged: (newName) {
|
||||
context
|
||||
.read<FieldEditorBloc>()
|
||||
.add(FieldEditorEvent.updateName(newName));
|
||||
.add(FieldEditorEvent.renameField(newName));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/mobile_create_row_field_button.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/card/card_property_edit/card_property_edit_screen.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_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';
|
||||
@ -23,10 +24,12 @@ class MobileRowPropertyList extends StatelessWidget {
|
||||
const MobileRowPropertyList({
|
||||
super.key,
|
||||
required this.viewId,
|
||||
required this.fieldController,
|
||||
required this.cellBuilder,
|
||||
});
|
||||
|
||||
final String viewId;
|
||||
final FieldController fieldController;
|
||||
final GridCellBuilder cellBuilder;
|
||||
|
||||
@override
|
||||
@ -44,6 +47,7 @@ class MobileRowPropertyList extends StatelessWidget {
|
||||
itemBuilder: (context, index) => _PropertyCell(
|
||||
key: ValueKey('row_detail_${visibleCells[index].fieldId}'),
|
||||
cellContext: visibleCells[index],
|
||||
fieldController: fieldController,
|
||||
cellBuilder: cellBuilder,
|
||||
index: index,
|
||||
),
|
||||
@ -75,6 +79,7 @@ class MobileRowPropertyList extends StatelessWidget {
|
||||
// add new field
|
||||
MobileCreateRowFieldButton(
|
||||
viewId: viewId,
|
||||
fieldController: fieldController,
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -93,11 +98,13 @@ class _PropertyCell extends StatefulWidget {
|
||||
const _PropertyCell({
|
||||
super.key,
|
||||
required this.cellContext,
|
||||
required this.fieldController,
|
||||
required this.cellBuilder,
|
||||
required this.index,
|
||||
});
|
||||
|
||||
final DatabaseCellContext cellContext;
|
||||
final FieldController fieldController;
|
||||
final GridCellBuilder cellBuilder;
|
||||
final int index;
|
||||
|
||||
@ -142,6 +149,7 @@ class _PropertyCellState extends State<_PropertyCell> {
|
||||
CardPropertyEditScreen.routeName,
|
||||
extra: {
|
||||
CardPropertyEditScreen.argCellContext: widget.cellContext,
|
||||
CardPropertyEditScreen.argFieldController: widget.fieldController,
|
||||
CardPropertyEditScreen.argRowDetailBloc:
|
||||
context.read<RowDetailBloc>(),
|
||||
},
|
||||
|
@ -3,6 +3,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/card/card_property_edit/mobile_field_editor.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -14,13 +15,16 @@ class CardPropertyEditScreen extends StatelessWidget {
|
||||
const CardPropertyEditScreen({
|
||||
super.key,
|
||||
required this.cellContext,
|
||||
required this.fieldController,
|
||||
});
|
||||
|
||||
static const routeName = '/CardPropertyEditScreen';
|
||||
static const argCellContext = 'cellContext';
|
||||
static const argFieldController = 'fieldController';
|
||||
static const argRowDetailBloc = 'rowDetailBloc';
|
||||
|
||||
final DatabaseCellContext cellContext;
|
||||
final FieldController fieldController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -57,7 +61,8 @@ class CardPropertyEditScreen extends StatelessWidget {
|
||||
viewId: cellContext.viewId,
|
||||
field: cellContext.fieldInfo.field,
|
||||
),
|
||||
fieldInfo: cellContext.fieldInfo,
|
||||
fieldController: fieldController,
|
||||
field: cellContext.fieldInfo.field,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -2,12 +2,11 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/widgets.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/card/card_property_edit/mobile_field_type_option_editor.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/card/card_property_edit/widgets/property_title.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -19,65 +18,63 @@ class MobileFieldEditor extends StatelessWidget {
|
||||
super.key,
|
||||
required this.viewId,
|
||||
required this.typeOptionLoader,
|
||||
this.isGroupingField = false,
|
||||
this.fieldInfo,
|
||||
required this.field,
|
||||
required this.fieldController,
|
||||
});
|
||||
|
||||
final String viewId;
|
||||
final bool isGroupingField;
|
||||
final FieldController fieldController;
|
||||
final FieldTypeOptionLoader typeOptionLoader;
|
||||
final FieldInfo? fieldInfo;
|
||||
final FieldPB field;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) {
|
||||
return FieldEditorBloc(
|
||||
// group field is the field to be used to group cards in database view, it can not be deleted
|
||||
isGroupField: isGroupingField,
|
||||
viewId: viewId,
|
||||
loader: typeOptionLoader,
|
||||
field: typeOptionLoader.field,
|
||||
field: field,
|
||||
fieldController: fieldController,
|
||||
)..add(const FieldEditorEvent.initial());
|
||||
},
|
||||
child: BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
||||
builder: (context, state) {
|
||||
// for field type edit option
|
||||
final dataController = context.read<FieldEditorBloc>().dataController;
|
||||
final dataController =
|
||||
context.read<FieldEditorBloc>().typeOptionController;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// property name
|
||||
// field name
|
||||
// TODO(yijing): improve hint text
|
||||
PropertyTitle(LocaleKeys.settings_user_name.tr()),
|
||||
BlocSelector<FieldEditorBloc, FieldEditorState, String>(
|
||||
selector: (state) {
|
||||
return state.name;
|
||||
},
|
||||
builder: (context, propertyName) {
|
||||
selector: (state) => state.field.name,
|
||||
builder: (context, fieldName) {
|
||||
return MobileFieldNameTextField(
|
||||
text: propertyName,
|
||||
text: fieldName,
|
||||
);
|
||||
},
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
PropertyTitle(LocaleKeys.grid_field_visibility.tr()),
|
||||
const Spacer(),
|
||||
Expanded(
|
||||
child:
|
||||
PropertyTitle(LocaleKeys.grid_field_visibility.tr()),
|
||||
),
|
||||
VisibilitySwitch(
|
||||
isFieldHidden:
|
||||
fieldInfo?.visibility == FieldVisibility.AlwaysHidden,
|
||||
isFieldHidden: state.field.visibility ==
|
||||
FieldVisibility.AlwaysHidden,
|
||||
onChanged: () {
|
||||
state.field.fold(
|
||||
() => Log.error('Can not hidden the field'),
|
||||
(field) => context.read<RowDetailBloc>().add(
|
||||
RowDetailEvent.toggleFieldVisibility(
|
||||
field.id,
|
||||
),
|
||||
context.read<RowDetailBloc>().add(
|
||||
RowDetailEvent.toggleFieldVisibility(
|
||||
state.field.id,
|
||||
),
|
||||
);
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
|
@ -1,108 +0,0 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field_settings/field_settings_service.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'field_info.dart';
|
||||
import 'field_service.dart';
|
||||
|
||||
part 'field_action_sheet_bloc.freezed.dart';
|
||||
|
||||
class FieldActionSheetBloc
|
||||
extends Bloc<FieldActionSheetEvent, FieldActionSheetState> {
|
||||
final String fieldId;
|
||||
final FieldBackendService fieldService;
|
||||
final FieldSettingsBackendService fieldSettingsService;
|
||||
|
||||
FieldActionSheetBloc({
|
||||
required String viewId,
|
||||
required FieldInfo fieldInfo,
|
||||
}) : fieldId = fieldInfo.id,
|
||||
fieldService = FieldBackendService(
|
||||
viewId: viewId,
|
||||
fieldId: fieldInfo.id,
|
||||
),
|
||||
fieldSettingsService = FieldSettingsBackendService(viewId: viewId),
|
||||
super(
|
||||
FieldActionSheetState.initial(
|
||||
TypeOptionPB.create()..field_2 = fieldInfo.field,
|
||||
),
|
||||
) {
|
||||
on<FieldActionSheetEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
updateFieldName: (_UpdateFieldName value) async {
|
||||
final result = await fieldService.updateField(name: value.name);
|
||||
result.fold(
|
||||
(l) => null,
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
},
|
||||
hideField: (_HideField value) async {
|
||||
final result = await fieldSettingsService.updateFieldSettings(
|
||||
fieldId: fieldId,
|
||||
fieldVisibility: FieldVisibility.AlwaysHidden,
|
||||
);
|
||||
result.fold(
|
||||
(l) => null,
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
},
|
||||
showField: (_ShowField value) async {
|
||||
final result = await fieldSettingsService.updateFieldSettings(
|
||||
fieldId: fieldId,
|
||||
fieldVisibility: FieldVisibility.AlwaysShown,
|
||||
);
|
||||
result.fold(
|
||||
(l) => null,
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
},
|
||||
deleteField: (_DeleteField value) async {
|
||||
final result = await fieldService.deleteField();
|
||||
result.fold(
|
||||
(l) => null,
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
},
|
||||
duplicateField: (_DuplicateField value) async {
|
||||
final result = await fieldService.duplicateField();
|
||||
result.fold(
|
||||
(l) => null,
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
},
|
||||
saveField: (_SaveField value) {},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class FieldActionSheetEvent with _$FieldActionSheetEvent {
|
||||
const factory FieldActionSheetEvent.updateFieldName(String name) =
|
||||
_UpdateFieldName;
|
||||
const factory FieldActionSheetEvent.hideField() = _HideField;
|
||||
const factory FieldActionSheetEvent.showField() = _ShowField;
|
||||
const factory FieldActionSheetEvent.duplicateField() = _DuplicateField;
|
||||
const factory FieldActionSheetEvent.deleteField() = _DeleteField;
|
||||
const factory FieldActionSheetEvent.saveField() = _SaveField;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class FieldActionSheetState with _$FieldActionSheetState {
|
||||
const factory FieldActionSheetState({
|
||||
required TypeOptionPB fieldTypeOptionData,
|
||||
required String errorText,
|
||||
required String fieldName,
|
||||
}) = _FieldActionSheetState;
|
||||
|
||||
factory FieldActionSheetState.initial(TypeOptionPB data) =>
|
||||
FieldActionSheetState(
|
||||
fieldTypeOptionData: data,
|
||||
errorText: '',
|
||||
fieldName: data.field_2.name,
|
||||
);
|
||||
}
|
@ -1,112 +1,135 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field_settings/field_settings_service.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import 'field_controller.dart';
|
||||
import 'field_info.dart';
|
||||
import 'field_listener.dart';
|
||||
import 'field_service.dart';
|
||||
import 'type_option/type_option_context.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'type_option/type_option_data_controller.dart';
|
||||
|
||||
part 'field_editor_bloc.freezed.dart';
|
||||
|
||||
class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
||||
final TypeOptionController dataController;
|
||||
final FieldPB field;
|
||||
|
||||
final String viewId;
|
||||
final FieldController fieldController;
|
||||
final SingleFieldListener _singleFieldListener;
|
||||
final FieldBackendService fieldService;
|
||||
final FieldSettingsBackendService fieldSettingsService;
|
||||
final TypeOptionController typeOptionController;
|
||||
|
||||
FieldEditorBloc({
|
||||
required bool isGroupField,
|
||||
required FieldPB field,
|
||||
required this.viewId,
|
||||
required this.field,
|
||||
required this.fieldController,
|
||||
required FieldTypeOptionLoader loader,
|
||||
}) : dataController = TypeOptionController(
|
||||
}) : typeOptionController = TypeOptionController(
|
||||
field: field,
|
||||
loader: loader,
|
||||
),
|
||||
super(
|
||||
FieldEditorState.initial(
|
||||
loader.viewId,
|
||||
loader.field.name,
|
||||
isGroupField,
|
||||
),
|
||||
) {
|
||||
_singleFieldListener = SingleFieldListener(fieldId: field.id),
|
||||
fieldService = FieldBackendService(
|
||||
viewId: viewId,
|
||||
fieldId: field.id,
|
||||
),
|
||||
fieldSettingsService = FieldSettingsBackendService(viewId: viewId),
|
||||
super(FieldEditorState(field: FieldInfo.initial(field))) {
|
||||
on<FieldEditorEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
dataController.addFieldListener((field) {
|
||||
final fieldId = field.id;
|
||||
typeOptionController.addFieldListener((field) {
|
||||
if (!isClosed) {
|
||||
add(FieldEditorEvent.didReceiveFieldChanged(field));
|
||||
add(FieldEditorEvent.didReceiveFieldChanged(fieldId));
|
||||
}
|
||||
});
|
||||
await dataController.reloadTypeOption();
|
||||
add(FieldEditorEvent.didReceiveFieldChanged(dataController.field));
|
||||
},
|
||||
updateName: (name) {
|
||||
if (state.name != name) {
|
||||
dataController.fieldName = name;
|
||||
emit(state.copyWith(name: name));
|
||||
}
|
||||
},
|
||||
didReceiveFieldChanged: (FieldPB field) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
field: Some(field),
|
||||
name: field.name,
|
||||
canDelete: field.isPrimary,
|
||||
),
|
||||
);
|
||||
},
|
||||
deleteField: () {
|
||||
state.field.fold(
|
||||
() => null,
|
||||
(field) {
|
||||
final fieldService = FieldBackendService(
|
||||
viewId: loader.viewId,
|
||||
fieldId: field.id,
|
||||
);
|
||||
fieldService.deleteField();
|
||||
_singleFieldListener.start(
|
||||
onFieldChanged: (field) {
|
||||
if (!isClosed) {
|
||||
add(FieldEditorEvent.didReceiveFieldChanged(fieldId));
|
||||
}
|
||||
},
|
||||
);
|
||||
await typeOptionController.reloadTypeOption();
|
||||
add(FieldEditorEvent.didReceiveFieldChanged(fieldId));
|
||||
},
|
||||
switchToField: (FieldType fieldType) async {
|
||||
await dataController.switchToField(fieldType);
|
||||
didReceiveFieldChanged: (fieldId) async {
|
||||
await Future.delayed(const Duration(milliseconds: 50));
|
||||
emit(state.copyWith(field: fieldController.getField(fieldId)!));
|
||||
},
|
||||
switchFieldType: (fieldType) async {
|
||||
await typeOptionController.switchToField(fieldType);
|
||||
},
|
||||
renameField: (newName) async {
|
||||
final result = await fieldService.updateField(name: newName);
|
||||
_logIfError(result);
|
||||
},
|
||||
toggleFieldVisibility: () async {
|
||||
final currentVisibility =
|
||||
state.field.visibility ?? FieldVisibility.AlwaysShown;
|
||||
final newVisibility =
|
||||
currentVisibility == FieldVisibility.AlwaysHidden
|
||||
? FieldVisibility.AlwaysShown
|
||||
: FieldVisibility.AlwaysHidden;
|
||||
final result = await fieldSettingsService.updateFieldSettings(
|
||||
fieldId: state.field.id,
|
||||
fieldVisibility: newVisibility,
|
||||
);
|
||||
_logIfError(result);
|
||||
},
|
||||
deleteField: () async {
|
||||
final result = await fieldService.deleteField();
|
||||
_logIfError(result);
|
||||
},
|
||||
duplicateField: () async {
|
||||
final result = await fieldService.duplicateField();
|
||||
_logIfError(result);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _logIfError(Either<Unit, FlowyError> result) {
|
||||
result.fold(
|
||||
(l) => null,
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_singleFieldListener.stop();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class FieldEditorEvent with _$FieldEditorEvent {
|
||||
const factory FieldEditorEvent.initial() = _InitialField;
|
||||
const factory FieldEditorEvent.updateName(String name) = _UpdateName;
|
||||
const factory FieldEditorEvent.deleteField() = _DeleteField;
|
||||
const factory FieldEditorEvent.switchToField(FieldType fieldType) =
|
||||
_SwitchToField;
|
||||
const factory FieldEditorEvent.didReceiveFieldChanged(FieldPB field) =
|
||||
const factory FieldEditorEvent.didReceiveFieldChanged(String fieldId) =
|
||||
_DidReceiveFieldChanged;
|
||||
const factory FieldEditorEvent.switchFieldType(FieldType fieldType) =
|
||||
_SwitchFieldType;
|
||||
const factory FieldEditorEvent.renameField(String name) = _RenameField;
|
||||
const factory FieldEditorEvent.toggleFieldVisibility() =
|
||||
_ToggleFieldVisiblity;
|
||||
const factory FieldEditorEvent.deleteField() = _DeleteField;
|
||||
const factory FieldEditorEvent.duplicateField() = _DuplicateField;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class FieldEditorState with _$FieldEditorState {
|
||||
const factory FieldEditorState({
|
||||
required String viewId,
|
||||
required String errorText,
|
||||
required String name,
|
||||
required Option<FieldPB> field,
|
||||
required bool canDelete,
|
||||
required bool isGroupField,
|
||||
required FieldInfo field,
|
||||
}) = _FieldEditorState;
|
||||
|
||||
factory FieldEditorState.initial(
|
||||
String viewId,
|
||||
String fieldName,
|
||||
bool isGroupField,
|
||||
) =>
|
||||
FieldEditorState(
|
||||
viewId: viewId,
|
||||
errorText: '',
|
||||
field: none(),
|
||||
canDelete: false,
|
||||
name: fieldName,
|
||||
isGroupField: isGroupField,
|
||||
);
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ class FieldBackendService {
|
||||
Future<Either<Unit, FlowyError>> updateField({
|
||||
String? name,
|
||||
bool? frozen,
|
||||
double? width,
|
||||
}) {
|
||||
final payload = FieldChangesetPB.create()
|
||||
..viewId = viewId
|
||||
@ -41,10 +40,6 @@ class FieldBackendService {
|
||||
payload.frozen = frozen;
|
||||
}
|
||||
|
||||
if (width != null) {
|
||||
payload.width = width.toInt();
|
||||
}
|
||||
|
||||
return DatabaseEventUpdateField(payload).send();
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/database/card/card.dart';
|
||||
@ -16,7 +17,6 @@ import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
|
||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
import 'package:flutter/material.dart' hide Card;
|
||||
@ -326,13 +326,15 @@ class _BoardContentState extends State<BoardContent> {
|
||||
context.push(
|
||||
MobileCardDetailScreen.routeName,
|
||||
extra: {
|
||||
'rowController': dataController,
|
||||
MobileCardDetailScreen.argRowController: dataController,
|
||||
MobileCardDetailScreen.argFieldController: fieldController,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
FlowyOverlay.show(
|
||||
context: context,
|
||||
builder: (_) => RowDetailPage(
|
||||
fieldController: fieldController,
|
||||
cellBuilder: GridCellBuilder(cellCache: dataController.cellCache),
|
||||
rowController: dataController,
|
||||
),
|
||||
|
@ -421,6 +421,8 @@ class HiddenGroupPopupItemList extends StatelessWidget {
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return RowDetailPage(
|
||||
fieldController:
|
||||
context.read<BoardBloc>().fieldController,
|
||||
cellBuilder: GridCellBuilder(
|
||||
cellCache: rowController.cellCache,
|
||||
),
|
||||
|
@ -297,6 +297,7 @@ class _EventList extends StatelessWidget {
|
||||
final autoEdit =
|
||||
editingEvent?.event?.eventId == events[index].eventId;
|
||||
return EventCard(
|
||||
fieldController: context.read<CalendarBloc>().fieldController,
|
||||
event: events[index],
|
||||
viewId: viewId,
|
||||
rowCache: rowCache,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/card/card.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/card/card_cell_builder.dart';
|
||||
@ -20,6 +21,7 @@ import '../application/calendar_bloc.dart';
|
||||
import 'calendar_event_editor.dart';
|
||||
|
||||
class EventCard extends StatefulWidget {
|
||||
final FieldController fieldController;
|
||||
final CalendarDayEvent event;
|
||||
final String viewId;
|
||||
final RowCache rowCache;
|
||||
@ -27,12 +29,13 @@ class EventCard extends StatefulWidget {
|
||||
final bool autoEdit;
|
||||
|
||||
const EventCard({
|
||||
super.key,
|
||||
required this.event,
|
||||
required this.viewId,
|
||||
required this.rowCache,
|
||||
required this.constraints,
|
||||
required this.autoEdit,
|
||||
super.key,
|
||||
required this.fieldController,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -164,6 +167,7 @@ class _EventCardState extends State<EventCard> {
|
||||
rowMeta: widget.event.event.rowMeta,
|
||||
viewId: widget.viewId,
|
||||
layoutSettings: settings,
|
||||
fieldController: widget.fieldController,
|
||||
);
|
||||
},
|
||||
child: DecoratedBox(
|
||||
|
@ -1,6 +1,7 @@
|
||||
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/field/field_controller.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';
|
||||
@ -19,6 +20,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class CalendarEventEditor extends StatelessWidget {
|
||||
final RowController rowController;
|
||||
final FieldController fieldController;
|
||||
final CalendarLayoutSettingPB layoutSettings;
|
||||
final GridCellBuilder cellBuilder;
|
||||
|
||||
@ -28,6 +30,7 @@ class CalendarEventEditor extends StatelessWidget {
|
||||
required RowMetaPB rowMeta,
|
||||
required String viewId,
|
||||
required this.layoutSettings,
|
||||
required this.fieldController,
|
||||
}) : rowController = RowController(
|
||||
rowMeta: rowMeta,
|
||||
viewId: viewId,
|
||||
@ -45,7 +48,10 @@ class CalendarEventEditor extends StatelessWidget {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
EventEditorControls(rowController: rowController),
|
||||
EventEditorControls(
|
||||
rowController: rowController,
|
||||
fieldController: fieldController,
|
||||
),
|
||||
Flexible(
|
||||
child: EventPropertyList(
|
||||
dateFieldId: layoutSettings.fieldId,
|
||||
@ -60,9 +66,11 @@ class CalendarEventEditor extends StatelessWidget {
|
||||
|
||||
class EventEditorControls extends StatelessWidget {
|
||||
final RowController rowController;
|
||||
final FieldController fieldController;
|
||||
const EventEditorControls({
|
||||
super.key,
|
||||
required this.rowController,
|
||||
required this.fieldController,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -91,6 +99,7 @@ class EventEditorControls extends StatelessWidget {
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return RowDetailPage(
|
||||
fieldController: fieldController,
|
||||
cellBuilder: GridCellBuilder(
|
||||
cellCache: rowController.cellCache,
|
||||
),
|
||||
|
@ -1,6 +1,7 @@
|
||||
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/application/field/field_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';
|
||||
@ -304,6 +305,7 @@ void showEventDetails({
|
||||
required CalendarEventPB event,
|
||||
required String viewId,
|
||||
required RowCache rowCache,
|
||||
required FieldController fieldController,
|
||||
}) {
|
||||
final dataController = RowController(
|
||||
rowMeta: event.rowMeta,
|
||||
@ -313,12 +315,13 @@ void showEventDetails({
|
||||
|
||||
FlowyOverlay.show(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
builder: (BuildContext overlayContext) {
|
||||
return RowDetailPage(
|
||||
cellBuilder: GridCellBuilder(
|
||||
cellCache: rowCache.cellCache,
|
||||
),
|
||||
rowController: dataController,
|
||||
fieldController: fieldController,
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -387,8 +390,7 @@ class _UnscheduledEventsButtonState extends State<UnscheduledEventsButton> {
|
||||
),
|
||||
popupBuilder: (context) {
|
||||
return UnscheduleEventsList(
|
||||
viewId: widget.databaseController.viewId,
|
||||
rowCache: widget.databaseController.rowCache,
|
||||
databaseController: widget.databaseController,
|
||||
unscheduleEvents: state.unscheduleEvents,
|
||||
);
|
||||
},
|
||||
@ -400,14 +402,12 @@ class _UnscheduledEventsButtonState extends State<UnscheduledEventsButton> {
|
||||
}
|
||||
|
||||
class UnscheduleEventsList extends StatelessWidget {
|
||||
final String viewId;
|
||||
final RowCache rowCache;
|
||||
final DatabaseController databaseController;
|
||||
final List<CalendarEventPB> unscheduleEvents;
|
||||
const UnscheduleEventsList({
|
||||
required this.viewId,
|
||||
required this.unscheduleEvents,
|
||||
required this.rowCache,
|
||||
super.key,
|
||||
required this.unscheduleEvents,
|
||||
required this.databaseController,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -429,8 +429,9 @@ class UnscheduleEventsList extends StatelessWidget {
|
||||
showEventDetails(
|
||||
context: context,
|
||||
event: e,
|
||||
viewId: viewId,
|
||||
rowCache: rowCache,
|
||||
viewId: databaseController.viewId,
|
||||
rowCache: databaseController.rowCache,
|
||||
fieldController: databaseController.fieldController,
|
||||
);
|
||||
PopoverContainer.of(context).close();
|
||||
},
|
||||
|
@ -202,8 +202,6 @@ class _GridHeader extends StatelessWidget {
|
||||
builder: (context, state) {
|
||||
return GridHeaderSliverAdaptor(
|
||||
viewId: state.viewId,
|
||||
fieldController:
|
||||
context.read<GridBloc>().databaseController.fieldController,
|
||||
anchorScrollController: headerScrollController,
|
||||
);
|
||||
},
|
||||
@ -360,6 +358,7 @@ class _GridRows extends StatelessWidget {
|
||||
return RowDetailPage(
|
||||
cellBuilder: cellBuilder,
|
||||
rowController: dataController,
|
||||
fieldController: fieldController,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -11,7 +11,7 @@ class GridSize {
|
||||
static double get headerContainerPadding => 0 * scale;
|
||||
static double get cellHPadding => 10 * scale;
|
||||
static double get cellVPadding => 10 * scale;
|
||||
static double get popoverItemHeight => 32 * scale;
|
||||
static double get popoverItemHeight => 26 * scale;
|
||||
static double get typeOptionSeparatorHeight => 4 * scale;
|
||||
|
||||
static EdgeInsets get headerContentInsets => EdgeInsets.symmetric(
|
||||
|
@ -184,8 +184,6 @@ class _GridHeader extends StatelessWidget {
|
||||
builder: (context, state) {
|
||||
return GridHeaderSliverAdaptor(
|
||||
viewId: state.viewId,
|
||||
fieldController:
|
||||
context.read<GridBloc>().databaseController.fieldController,
|
||||
anchorScrollController: headerScrollController,
|
||||
);
|
||||
},
|
||||
@ -363,6 +361,7 @@ class _GridRows extends StatelessWidget {
|
||||
return RowDetailPage(
|
||||
cellBuilder: cellBuilder,
|
||||
rowController: dataController,
|
||||
fieldController: fieldController,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_cell_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
|
||||
@ -11,15 +12,17 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../layout/sizes.dart';
|
||||
import 'field_cell_action_sheet.dart';
|
||||
import 'field_editor.dart';
|
||||
import 'field_type_extension.dart';
|
||||
|
||||
class GridFieldCell extends StatefulWidget {
|
||||
final String viewId;
|
||||
final FieldController fieldController;
|
||||
final FieldInfo fieldInfo;
|
||||
const GridFieldCell({
|
||||
super.key,
|
||||
required this.viewId,
|
||||
required this.fieldController,
|
||||
required this.fieldInfo,
|
||||
});
|
||||
|
||||
@ -59,9 +62,11 @@ class _GridFieldCellState extends State<GridFieldCell> {
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
controller: popoverController,
|
||||
popupBuilder: (BuildContext context) {
|
||||
return GridFieldCellActionSheet(
|
||||
return FieldEditor(
|
||||
viewId: widget.viewId,
|
||||
fieldInfo: widget.fieldInfo,
|
||||
fieldController: widget.fieldController,
|
||||
field: widget.fieldInfo.field,
|
||||
initialPage: FieldEditorPage.general,
|
||||
);
|
||||
},
|
||||
child: FieldCellButton(
|
||||
|
@ -1,266 +0,0 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_action_sheet_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
import '../../layout/sizes.dart';
|
||||
import 'field_editor.dart';
|
||||
|
||||
class GridFieldCellActionSheet extends StatefulWidget {
|
||||
final String viewId;
|
||||
final FieldInfo fieldInfo;
|
||||
const GridFieldCellActionSheet({
|
||||
required this.viewId,
|
||||
required this.fieldInfo,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _GridFieldCellActionSheetState();
|
||||
}
|
||||
|
||||
class _GridFieldCellActionSheetState extends State<GridFieldCellActionSheet> {
|
||||
bool _showFieldEditor = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_showFieldEditor) {
|
||||
return SizedBox(
|
||||
width: 400,
|
||||
child: FieldEditor(
|
||||
viewId: widget.viewId,
|
||||
fieldInfo: widget.fieldInfo,
|
||||
typeOptionLoader: FieldTypeOptionLoader(
|
||||
viewId: widget.viewId,
|
||||
field: widget.fieldInfo.field,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return BlocProvider(
|
||||
create: (context) => FieldActionSheetBloc(
|
||||
viewId: widget.viewId,
|
||||
fieldInfo: widget.fieldInfo,
|
||||
),
|
||||
child: IntrinsicWidth(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_EditFieldButton(
|
||||
onTap: () {
|
||||
setState(() => _showFieldEditor = true);
|
||||
},
|
||||
),
|
||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||
_FieldOperationList(
|
||||
viewId: widget.viewId,
|
||||
fieldInfo: widget.fieldInfo,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
).padding(all: 6.0);
|
||||
}
|
||||
}
|
||||
|
||||
class _EditFieldButton extends StatelessWidget {
|
||||
final void Function()? onTap;
|
||||
const _EditFieldButton({Key? key, this.onTap}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<FieldActionSheetBloc, FieldActionSheetState>(
|
||||
builder: (context, state) {
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
text: FlowyText.medium(
|
||||
LocaleKeys.grid_field_editProperty.tr(),
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
),
|
||||
onTap: onTap,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FieldOperationList extends StatelessWidget {
|
||||
final String viewId;
|
||||
final FieldInfo fieldInfo;
|
||||
const _FieldOperationList({
|
||||
required this.viewId,
|
||||
required this.fieldInfo,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Flex(
|
||||
direction: Axis.horizontal,
|
||||
children: [
|
||||
_actionCell(FieldAction.hide),
|
||||
HSpace(GridSize.typeOptionSeparatorHeight),
|
||||
_actionCell(FieldAction.duplicate),
|
||||
],
|
||||
),
|
||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||
Flex(
|
||||
direction: Axis.horizontal,
|
||||
children: [
|
||||
_actionCell(FieldAction.delete),
|
||||
HSpace(GridSize.typeOptionSeparatorHeight),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _actionCell(FieldAction action) {
|
||||
bool enable = true;
|
||||
|
||||
// If the field is primary, delete and duplicate are disabled.
|
||||
if (fieldInfo.isPrimary) {
|
||||
switch (action) {
|
||||
case FieldAction.hide:
|
||||
break;
|
||||
case FieldAction.duplicate:
|
||||
enable = false;
|
||||
break;
|
||||
case FieldAction.delete:
|
||||
enable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Flexible(
|
||||
child: SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FieldActionCell(
|
||||
viewId: viewId,
|
||||
fieldInfo: fieldInfo,
|
||||
action: action,
|
||||
enable: enable,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FieldActionCell extends StatelessWidget {
|
||||
final String viewId;
|
||||
final FieldInfo fieldInfo;
|
||||
final FieldAction action;
|
||||
final bool enable;
|
||||
|
||||
const FieldActionCell({
|
||||
required this.viewId,
|
||||
required this.fieldInfo,
|
||||
required this.action,
|
||||
required this.enable,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyButton(
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
disable: !enable,
|
||||
text: FlowyText.medium(
|
||||
action.title(),
|
||||
color: enable
|
||||
? AFThemeExtension.of(context).textColor
|
||||
: Theme.of(context).disabledColor,
|
||||
),
|
||||
onTap: () => action.run(context, viewId, fieldInfo),
|
||||
leftIcon: FlowySvg(
|
||||
action.icon(),
|
||||
color: enable
|
||||
? AFThemeExtension.of(context).textColor
|
||||
: Theme.of(context).disabledColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum FieldAction {
|
||||
hide,
|
||||
duplicate,
|
||||
delete,
|
||||
}
|
||||
|
||||
extension _FieldActionExtension on FieldAction {
|
||||
FlowySvgData icon() {
|
||||
switch (this) {
|
||||
case FieldAction.hide:
|
||||
return FlowySvgs.hide_s;
|
||||
case FieldAction.duplicate:
|
||||
return FlowySvgs.copy_s;
|
||||
case FieldAction.delete:
|
||||
return FlowySvgs.delete_s;
|
||||
}
|
||||
}
|
||||
|
||||
String title() {
|
||||
switch (this) {
|
||||
case FieldAction.hide:
|
||||
return LocaleKeys.grid_field_hide.tr();
|
||||
case FieldAction.duplicate:
|
||||
return LocaleKeys.grid_field_duplicate.tr();
|
||||
case FieldAction.delete:
|
||||
return LocaleKeys.grid_field_delete.tr();
|
||||
}
|
||||
}
|
||||
|
||||
void run(BuildContext context, String viewId, FieldInfo fieldInfo) {
|
||||
switch (this) {
|
||||
case FieldAction.hide:
|
||||
context
|
||||
.read<FieldActionSheetBloc>()
|
||||
.add(const FieldActionSheetEvent.hideField());
|
||||
break;
|
||||
case FieldAction.duplicate:
|
||||
PopoverContainer.of(context).close();
|
||||
|
||||
FieldBackendService(
|
||||
viewId: viewId,
|
||||
fieldId: fieldInfo.id,
|
||||
).duplicateField();
|
||||
|
||||
break;
|
||||
case FieldAction.delete:
|
||||
PopoverContainer.of(context).close();
|
||||
|
||||
NavigatorAlertDialog(
|
||||
title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
|
||||
confirm: () {
|
||||
FieldBackendService(
|
||||
viewId: viewId,
|
||||
fieldId: fieldInfo.field.id,
|
||||
).deleteField();
|
||||
},
|
||||
).show(context);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,38 +1,41 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/common/type_option_separator.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pbenum.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text_field.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import '../../layout/sizes.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
import 'field_type_option_editor.dart';
|
||||
|
||||
enum FieldEditorPage {
|
||||
general,
|
||||
details,
|
||||
}
|
||||
|
||||
class FieldEditor extends StatefulWidget {
|
||||
final String viewId;
|
||||
final bool isGroupingField;
|
||||
final Function(String)? onDeleted;
|
||||
final Function(String)? onToggleVisibility;
|
||||
final FieldTypeOptionLoader typeOptionLoader;
|
||||
final FieldInfo? fieldInfo;
|
||||
final FieldController fieldController;
|
||||
final FieldPB field;
|
||||
final FieldEditorPage initialPage;
|
||||
|
||||
const FieldEditor({
|
||||
required this.viewId,
|
||||
required this.typeOptionLoader,
|
||||
this.fieldInfo,
|
||||
this.isGroupingField = false,
|
||||
this.onDeleted,
|
||||
this.onToggleVisibility,
|
||||
super.key,
|
||||
required this.viewId,
|
||||
required this.field,
|
||||
required this.fieldController,
|
||||
this.initialPage = FieldEditorPage.details,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -40,6 +43,227 @@ class FieldEditor extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _FieldEditorState extends State<FieldEditor> {
|
||||
late FieldEditorPage _currentPage;
|
||||
late final TextEditingController textController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_currentPage = widget.initialPage;
|
||||
textController = TextEditingController(text: widget.field.name);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) => FieldEditorBloc(
|
||||
viewId: widget.viewId,
|
||||
field: widget.field,
|
||||
fieldController: widget.fieldController,
|
||||
loader: FieldTypeOptionLoader(
|
||||
viewId: widget.viewId,
|
||||
field: widget.field,
|
||||
),
|
||||
)..add(const FieldEditorEvent.initial()),
|
||||
child: _currentPage == FieldEditorPage.details
|
||||
? _fieldDetails()
|
||||
: _fieldGeneral(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _fieldGeneral() {
|
||||
return SizedBox(
|
||||
width: 240,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FieldNameTextField(
|
||||
padding: const EdgeInsets.fromLTRB(4, 4, 4, 8),
|
||||
textEditingController: textController,
|
||||
),
|
||||
_EditFieldButton(
|
||||
onTap: () {
|
||||
setState(() => _currentPage = FieldEditorPage.details);
|
||||
},
|
||||
),
|
||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||
_actionCell(FieldAction.toggleVisibility),
|
||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||
_actionCell(FieldAction.duplicate),
|
||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||
_actionCell(FieldAction.delete),
|
||||
],
|
||||
).padding(all: 8.0),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _fieldDetails() {
|
||||
return SizedBox(
|
||||
width: 260,
|
||||
child: FieldDetailsEditor(
|
||||
viewId: widget.viewId,
|
||||
textEditingController: textController,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _actionCell(FieldAction action) {
|
||||
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
||||
builder: (context, state) => FieldActionCell(
|
||||
viewId: widget.viewId,
|
||||
fieldInfo: state.field,
|
||||
action: action,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _EditFieldButton extends StatelessWidget {
|
||||
final void Function()? onTap;
|
||||
const _EditFieldButton({this.onTap});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
leftIcon: const FlowySvg(FlowySvgs.edit_s),
|
||||
text: FlowyText.medium(
|
||||
LocaleKeys.grid_field_editProperty.tr(),
|
||||
),
|
||||
onTap: onTap,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FieldActionCell extends StatelessWidget {
|
||||
final String viewId;
|
||||
final FieldInfo fieldInfo;
|
||||
final FieldAction action;
|
||||
final PopoverMutex? popoverMutex;
|
||||
|
||||
const FieldActionCell({
|
||||
super.key,
|
||||
required this.viewId,
|
||||
required this.fieldInfo,
|
||||
required this.action,
|
||||
this.popoverMutex,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool enable = true;
|
||||
// If the field is primary, delete and duplicate are disabled.
|
||||
if (fieldInfo.isPrimary &&
|
||||
(action == FieldAction.duplicate || action == FieldAction.delete)) {
|
||||
enable = false;
|
||||
}
|
||||
|
||||
return FlowyButton(
|
||||
disable: !enable,
|
||||
text: FlowyText.medium(
|
||||
action.title(fieldInfo),
|
||||
color: enable ? null : Theme.of(context).disabledColor,
|
||||
),
|
||||
onHover: (_) => popoverMutex?.close(),
|
||||
onTap: () => action.run(context, viewId, fieldInfo),
|
||||
leftIcon: FlowySvg(
|
||||
action.icon(fieldInfo),
|
||||
size: const Size.square(16),
|
||||
color: enable ? null : Theme.of(context).disabledColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum FieldAction {
|
||||
toggleVisibility,
|
||||
duplicate,
|
||||
delete,
|
||||
}
|
||||
|
||||
extension _FieldActionExtension on FieldAction {
|
||||
FlowySvgData icon(FieldInfo fieldInfo) {
|
||||
switch (this) {
|
||||
case FieldAction.toggleVisibility:
|
||||
if (fieldInfo.visibility != null &&
|
||||
fieldInfo.visibility == FieldVisibility.AlwaysHidden) {
|
||||
return FlowySvgs.show_m;
|
||||
} else {
|
||||
return FlowySvgs.hide_s;
|
||||
}
|
||||
case FieldAction.duplicate:
|
||||
return FlowySvgs.copy_s;
|
||||
case FieldAction.delete:
|
||||
return FlowySvgs.delete_s;
|
||||
}
|
||||
}
|
||||
|
||||
String title(FieldInfo fieldInfo) {
|
||||
switch (this) {
|
||||
case FieldAction.toggleVisibility:
|
||||
if (fieldInfo.visibility != null &&
|
||||
fieldInfo.visibility == FieldVisibility.AlwaysHidden) {
|
||||
return LocaleKeys.grid_field_show.tr();
|
||||
} else {
|
||||
return LocaleKeys.grid_field_hide.tr();
|
||||
}
|
||||
case FieldAction.duplicate:
|
||||
return LocaleKeys.grid_field_duplicate.tr();
|
||||
case FieldAction.delete:
|
||||
return LocaleKeys.grid_field_delete.tr();
|
||||
}
|
||||
}
|
||||
|
||||
void run(BuildContext context, String viewId, FieldInfo fieldInfo) {
|
||||
switch (this) {
|
||||
case FieldAction.toggleVisibility:
|
||||
PopoverContainer.of(context).close();
|
||||
context
|
||||
.read<FieldEditorBloc>()
|
||||
.add(const FieldEditorEvent.toggleFieldVisibility());
|
||||
break;
|
||||
case FieldAction.duplicate:
|
||||
PopoverContainer.of(context).close();
|
||||
context
|
||||
.read<FieldEditorBloc>()
|
||||
.add(const FieldEditorEvent.duplicateField());
|
||||
break;
|
||||
case FieldAction.delete:
|
||||
NavigatorAlertDialog(
|
||||
title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
|
||||
confirm: () {
|
||||
FieldBackendService(
|
||||
viewId: viewId,
|
||||
fieldId: fieldInfo.id,
|
||||
).deleteField();
|
||||
},
|
||||
).show(context);
|
||||
PopoverContainer.of(context).close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FieldDetailsEditor extends StatefulWidget {
|
||||
final String viewId;
|
||||
final TextEditingController textEditingController;
|
||||
final Function()? onAction;
|
||||
|
||||
const FieldDetailsEditor({
|
||||
super.key,
|
||||
required this.viewId,
|
||||
required this.textEditingController,
|
||||
this.onAction,
|
||||
});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _FieldDetailsEditorState();
|
||||
}
|
||||
|
||||
class _FieldDetailsEditorState extends State<FieldDetailsEditor> {
|
||||
late PopoverMutex popoverMutex;
|
||||
|
||||
@override
|
||||
@ -56,71 +280,77 @@ class _FieldEditorState extends State<FieldEditor> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool requireSpace = widget.onDeleted != null ||
|
||||
widget.onToggleVisibility != null ||
|
||||
!widget.typeOptionLoader.field.isPrimary;
|
||||
|
||||
final List<Widget> children = [
|
||||
FieldNameTextField(popoverMutex: popoverMutex),
|
||||
if (requireSpace) const VSpace(4),
|
||||
if (widget.onDeleted != null) _addDeleteFieldButton(),
|
||||
if (widget.onToggleVisibility != null) _addHideFieldButton(),
|
||||
if (!widget.typeOptionLoader.field.isPrimary)
|
||||
FieldTypeOptionCell(popoverMutex: popoverMutex),
|
||||
FieldNameTextField(
|
||||
popoverMutex: popoverMutex,
|
||||
padding: const EdgeInsets.fromLTRB(12.0, 4.0, 12.0, 0.0),
|
||||
textEditingController: widget.textEditingController,
|
||||
),
|
||||
const VSpace(8),
|
||||
FieldTypeOptionCell(popoverMutex: popoverMutex),
|
||||
const TypeOptionSeparator(),
|
||||
_addFieldVisibilityToggleButton(),
|
||||
_addDuplicateFieldButton(),
|
||||
_addDeleteFieldButton(),
|
||||
];
|
||||
return BlocProvider(
|
||||
create: (context) {
|
||||
return FieldEditorBloc(
|
||||
isGroupField: widget.isGroupingField,
|
||||
loader: widget.typeOptionLoader,
|
||||
field: widget.typeOptionLoader.field,
|
||||
)..add(const FieldEditorEvent.initial());
|
||||
},
|
||||
child: ListView.separated(
|
||||
shrinkWrap: true,
|
||||
itemCount: children.length,
|
||||
itemBuilder: (context, index) => children[index],
|
||||
separatorBuilder: (context, index) =>
|
||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: children,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _addDeleteFieldButton() {
|
||||
Widget _addFieldVisibilityToggleButton() {
|
||||
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
||||
builder: (context, state) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: DeleteFieldButton(
|
||||
padding: const EdgeInsets.fromLTRB(8.0, 2.0, 8.0, 0),
|
||||
child: FieldActionCell(
|
||||
viewId: widget.viewId,
|
||||
fieldInfo: state.field,
|
||||
action: FieldAction.toggleVisibility,
|
||||
popoverMutex: popoverMutex,
|
||||
onDeleted: () {
|
||||
state.field.fold(
|
||||
() => Log.error('Can not delete the field'),
|
||||
(field) => widget.onDeleted?.call(field.id),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _addHideFieldButton() {
|
||||
Widget _addDeleteFieldButton() {
|
||||
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
||||
builder: (context, state) {
|
||||
if (state.field.isPrimary) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: FieldVisibilityToggleButton(
|
||||
isFieldHidden:
|
||||
widget.fieldInfo!.visibility == FieldVisibility.AlwaysHidden,
|
||||
padding: const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 0),
|
||||
child: FieldActionCell(
|
||||
viewId: widget.viewId,
|
||||
fieldInfo: state.field,
|
||||
action: FieldAction.delete,
|
||||
popoverMutex: popoverMutex,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _addDuplicateFieldButton() {
|
||||
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
||||
builder: (context, state) {
|
||||
if (state.field.isPrimary) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 0),
|
||||
child: FieldActionCell(
|
||||
viewId: widget.viewId,
|
||||
fieldInfo: state.field,
|
||||
action: FieldAction.duplicate,
|
||||
popoverMutex: popoverMutex,
|
||||
onTap: () {
|
||||
state.field.fold(
|
||||
() => Log.error('Can not hidden the field'),
|
||||
(field) => widget.onToggleVisibility?.call(field.id),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -132,25 +362,25 @@ class FieldTypeOptionCell extends StatelessWidget {
|
||||
final PopoverMutex popoverMutex;
|
||||
|
||||
const FieldTypeOptionCell({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.popoverMutex,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
||||
buildWhen: (p, c) => p.field != c.field,
|
||||
builder: (context, state) {
|
||||
return state.field.fold(
|
||||
() => const SizedBox.shrink(),
|
||||
(fieldInfo) {
|
||||
final dataController =
|
||||
context.read<FieldEditorBloc>().dataController;
|
||||
return FieldTypeOptionEditor(
|
||||
dataController: dataController,
|
||||
popoverMutex: popoverMutex,
|
||||
);
|
||||
},
|
||||
if (state.field.isPrimary) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final dataController =
|
||||
context.read<FieldEditorBloc>().typeOptionController;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2.0),
|
||||
child: FieldTypeOptionEditor(
|
||||
dataController: dataController,
|
||||
popoverMutex: popoverMutex,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -158,18 +388,21 @@ class FieldTypeOptionCell extends StatelessWidget {
|
||||
}
|
||||
|
||||
class FieldNameTextField extends StatefulWidget {
|
||||
final PopoverMutex popoverMutex;
|
||||
final TextEditingController textEditingController;
|
||||
final PopoverMutex? popoverMutex;
|
||||
final EdgeInsets padding;
|
||||
const FieldNameTextField({
|
||||
required this.popoverMutex,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
required this.textEditingController,
|
||||
this.popoverMutex,
|
||||
this.padding = EdgeInsets.zero,
|
||||
});
|
||||
|
||||
@override
|
||||
State<FieldNameTextField> createState() => _FieldNameTextFieldState();
|
||||
}
|
||||
|
||||
class _FieldNameTextFieldState extends State<FieldNameTextField> {
|
||||
final textController = TextEditingController();
|
||||
FocusNode focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
@ -178,11 +411,11 @@ class _FieldNameTextFieldState extends State<FieldNameTextField> {
|
||||
|
||||
focusNode.addListener(() {
|
||||
if (focusNode.hasFocus) {
|
||||
widget.popoverMutex.close();
|
||||
widget.popoverMutex?.close();
|
||||
}
|
||||
});
|
||||
|
||||
widget.popoverMutex.listenOnPopoverChanged(() {
|
||||
widget.popoverMutex?.listenOnPopoverChanged(() {
|
||||
if (focusNode.hasFocus) {
|
||||
focusNode.unfocus();
|
||||
}
|
||||
@ -191,24 +424,18 @@ class _FieldNameTextFieldState extends State<FieldNameTextField> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
||||
builder: (context, state) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: FlowyTextField(
|
||||
focusNode: focusNode,
|
||||
controller: textController,
|
||||
onSubmitted: (String _) => PopoverContainer.of(context).close(),
|
||||
text: state.name,
|
||||
errorText: state.errorText.isEmpty ? null : state.errorText,
|
||||
onChanged: (newName) {
|
||||
context
|
||||
.read<FieldEditorBloc>()
|
||||
.add(FieldEditorEvent.updateName(newName));
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
return Padding(
|
||||
padding: widget.padding,
|
||||
child: FlowyTextField(
|
||||
focusNode: focusNode,
|
||||
controller: widget.textEditingController,
|
||||
onSubmitted: (_) => PopoverContainer.of(context).close(),
|
||||
onChanged: (newName) {
|
||||
context
|
||||
.read<FieldEditorBloc>()
|
||||
.add(FieldEditorEvent.renameField(newName));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -216,80 +443,10 @@ class _FieldNameTextFieldState extends State<FieldNameTextField> {
|
||||
void dispose() {
|
||||
focusNode.removeListener(() {
|
||||
if (focusNode.hasFocus) {
|
||||
widget.popoverMutex.close();
|
||||
widget.popoverMutex?.close();
|
||||
}
|
||||
});
|
||||
focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
class DeleteFieldButton extends StatelessWidget {
|
||||
final PopoverMutex popoverMutex;
|
||||
final VoidCallback? onDeleted;
|
||||
|
||||
const DeleteFieldButton({
|
||||
required this.popoverMutex,
|
||||
required this.onDeleted,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
||||
buildWhen: (previous, current) => previous != current,
|
||||
builder: (context, state) {
|
||||
final enable = !state.canDelete && !state.isGroupField;
|
||||
final Widget button = FlowyButton(
|
||||
disable: !enable,
|
||||
text: FlowyText.medium(
|
||||
LocaleKeys.grid_field_delete.tr(),
|
||||
color: enable ? null : Theme.of(context).disabledColor,
|
||||
),
|
||||
leftIcon: const FlowySvg(FlowySvgs.delete_s),
|
||||
onTap: () {
|
||||
if (enable) onDeleted?.call();
|
||||
},
|
||||
onHover: (_) => popoverMutex.close(),
|
||||
);
|
||||
return SizedBox(height: GridSize.popoverItemHeight, child: button);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
class FieldVisibilityToggleButton extends StatelessWidget {
|
||||
final bool isFieldHidden;
|
||||
final PopoverMutex popoverMutex;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const FieldVisibilityToggleButton({
|
||||
required this.isFieldHidden,
|
||||
required this.popoverMutex,
|
||||
required this.onTap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
||||
buildWhen: (previous, current) => previous != current,
|
||||
builder: (context, state) {
|
||||
final Widget button = FlowyButton(
|
||||
text: FlowyText.medium(
|
||||
isFieldHidden
|
||||
? LocaleKeys.grid_field_show.tr()
|
||||
: LocaleKeys.grid_field_hide.tr(),
|
||||
),
|
||||
leftIcon:
|
||||
FlowySvg(isFieldHidden ? FlowySvgs.show_m : FlowySvgs.hide_m),
|
||||
onTap: onTap,
|
||||
onHover: (_) => popoverMutex.close(),
|
||||
);
|
||||
return SizedBox(height: GridSize.popoverItemHeight, child: button);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import 'dart:typed_data';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_type_option_edit_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_data_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:dartz/dartz.dart' show Either;
|
||||
|
||||
@ -10,7 +11,7 @@ import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import '../../layout/sizes.dart';
|
||||
|
||||
import 'field_type_extension.dart';
|
||||
import 'field_type_list.dart';
|
||||
import 'type_option/builder.dart';
|
||||
@ -27,19 +28,17 @@ class FieldTypeOptionEditor extends StatelessWidget {
|
||||
final PopoverMutex popoverMutex;
|
||||
|
||||
const FieldTypeOptionEditor({
|
||||
super.key,
|
||||
required TypeOptionController dataController,
|
||||
required this.popoverMutex,
|
||||
Key? key,
|
||||
}) : _dataController = dataController,
|
||||
super(key: key);
|
||||
}) : _dataController = dataController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) {
|
||||
final bloc = FieldTypeOptionEditBloc(_dataController);
|
||||
bloc.add(const FieldTypeOptionEditEvent.initial());
|
||||
return bloc;
|
||||
return FieldTypeOptionEditBloc(_dataController)
|
||||
..add(const FieldTypeOptionEditEvent.initial());
|
||||
},
|
||||
child: BlocBuilder<FieldTypeOptionEditBloc, FieldTypeOptionEditState>(
|
||||
builder: (context, state) {
|
||||
@ -53,8 +52,8 @@ class FieldTypeOptionEditor extends StatelessWidget {
|
||||
if (typeOptionWidget != null) typeOptionWidget,
|
||||
];
|
||||
|
||||
return ListView(
|
||||
shrinkWrap: true,
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: children,
|
||||
);
|
||||
},
|
||||
@ -74,22 +73,30 @@ class FieldTypeOptionEditor extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class SwitchFieldButton extends StatelessWidget {
|
||||
class SwitchFieldButton extends StatefulWidget {
|
||||
final PopoverMutex popoverMutex;
|
||||
const SwitchFieldButton({
|
||||
super.key,
|
||||
required this.popoverMutex,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
State<SwitchFieldButton> createState() => _SwitchFieldButtonState();
|
||||
}
|
||||
|
||||
class _SwitchFieldButtonState extends State<SwitchFieldButton> {
|
||||
final PopoverController _popoverController = PopoverController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final widget = AppFlowyPopover(
|
||||
final child = AppFlowyPopover(
|
||||
constraints: BoxConstraints.loose(const Size(460, 540)),
|
||||
asBarrier: true,
|
||||
triggerActions: PopoverTriggerFlags.click,
|
||||
mutex: popoverMutex,
|
||||
triggerActions: PopoverTriggerFlags.hover,
|
||||
mutex: widget.popoverMutex,
|
||||
controller: _popoverController,
|
||||
offset: const Offset(8, 0),
|
||||
popupBuilder: (popOverContext) {
|
||||
margin: const EdgeInsets.all(8),
|
||||
popupBuilder: (BuildContext popoverContext) {
|
||||
return FieldTypeList(
|
||||
onSelectField: (newFieldType) {
|
||||
context
|
||||
@ -99,20 +106,21 @@ class SwitchFieldButton extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: _buildMoreButton(context),
|
||||
),
|
||||
);
|
||||
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: widget,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMoreButton(BuildContext context) {
|
||||
final bloc = context.read<FieldTypeOptionEditBloc>();
|
||||
return FlowyButton(
|
||||
onTap: () => _popoverController.show(),
|
||||
text: FlowyText.medium(
|
||||
bloc.state.field.fieldType.title(),
|
||||
),
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/grid_header_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/mobile_field_cell.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
@ -22,15 +22,13 @@ import 'field_cell.dart';
|
||||
|
||||
class GridHeaderSliverAdaptor extends StatefulWidget {
|
||||
final String viewId;
|
||||
final FieldController fieldController;
|
||||
final ScrollController anchorScrollController;
|
||||
|
||||
const GridHeaderSliverAdaptor({
|
||||
required this.viewId,
|
||||
required this.fieldController,
|
||||
required this.anchorScrollController,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<GridHeaderSliverAdaptor> createState() =>
|
||||
@ -40,11 +38,13 @@ class GridHeaderSliverAdaptor extends StatefulWidget {
|
||||
class _GridHeaderSliverAdaptorState extends State<GridHeaderSliverAdaptor> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final fieldController =
|
||||
context.read<GridBloc>().databaseController.fieldController;
|
||||
return BlocProvider(
|
||||
create: (context) {
|
||||
return GridHeaderBloc(
|
||||
viewId: widget.viewId,
|
||||
fieldController: widget.fieldController,
|
||||
fieldController: fieldController,
|
||||
)..add(const GridHeaderEvent.initial());
|
||||
},
|
||||
child: BlocBuilder<GridHeaderBloc, GridHeaderState>(
|
||||
@ -54,7 +54,10 @@ class _GridHeaderSliverAdaptorState extends State<GridHeaderSliverAdaptor> {
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
controller: widget.anchorScrollController,
|
||||
child: _GridHeader(viewId: widget.viewId),
|
||||
child: _GridHeader(
|
||||
viewId: widget.viewId,
|
||||
fieldController: fieldController,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -64,7 +67,8 @@ class _GridHeaderSliverAdaptorState extends State<GridHeaderSliverAdaptor> {
|
||||
|
||||
class _GridHeader extends StatefulWidget {
|
||||
final String viewId;
|
||||
const _GridHeader({Key? key, required this.viewId}) : super(key: key);
|
||||
final FieldController fieldController;
|
||||
const _GridHeader({required this.viewId, required this.fieldController});
|
||||
|
||||
@override
|
||||
State<_GridHeader> createState() => _GridHeaderState();
|
||||
@ -98,6 +102,7 @@ class _GridHeaderState extends State<_GridHeader> {
|
||||
key: _getKeyById(fieldInfo.id),
|
||||
viewId: widget.viewId,
|
||||
fieldInfo: fieldInfo,
|
||||
fieldController: widget.fieldController,
|
||||
)
|
||||
: MobileFieldButton(
|
||||
key: _getKeyById(fieldInfo.id),
|
||||
@ -159,8 +164,9 @@ class _CellLeading extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _CellTrailing extends StatelessWidget {
|
||||
const _CellTrailing({required this.viewId});
|
||||
|
||||
final String viewId;
|
||||
const _CellTrailing({required this.viewId, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -179,7 +185,10 @@ class _CellTrailing extends StatelessWidget {
|
||||
|
||||
class CreateFieldButton extends StatefulWidget {
|
||||
final String viewId;
|
||||
const CreateFieldButton({required this.viewId, Key? key}) : super(key: key);
|
||||
const CreateFieldButton({
|
||||
super.key,
|
||||
required this.viewId,
|
||||
});
|
||||
|
||||
@override
|
||||
State<CreateFieldButton> createState() => _CreateFieldButtonState();
|
||||
@ -191,6 +200,8 @@ class _CreateFieldButtonState extends State<CreateFieldButton> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final fieldController =
|
||||
context.read<GridBloc>().databaseController.fieldController;
|
||||
return AppFlowyPopover(
|
||||
controller: popoverController,
|
||||
direction: PopoverDirection.bottomWithRightAligned,
|
||||
@ -222,49 +233,13 @@ class _CreateFieldButtonState extends State<CreateFieldButton> {
|
||||
},
|
||||
leftIcon: const FlowySvg(FlowySvgs.add_s),
|
||||
),
|
||||
popupBuilder: (BuildContext popover) {
|
||||
popupBuilder: (BuildContext popoverContext) {
|
||||
return FieldEditor(
|
||||
viewId: widget.viewId,
|
||||
typeOptionLoader: FieldTypeOptionLoader(
|
||||
viewId: widget.viewId,
|
||||
field: typeOption.field_2,
|
||||
),
|
||||
fieldController: fieldController,
|
||||
field: typeOption.field_2,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SliverHeaderDelegateImplementation
|
||||
extends SliverPersistentHeaderDelegate {
|
||||
final String gridId;
|
||||
final List<FieldPB> fields;
|
||||
|
||||
SliverHeaderDelegateImplementation({
|
||||
required this.gridId,
|
||||
required this.fields,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
double shrinkOffset,
|
||||
bool overlapsContent,
|
||||
) {
|
||||
return _GridHeader(viewId: gridId);
|
||||
}
|
||||
|
||||
@override
|
||||
double get maxExtent => GridSize.headerHeight;
|
||||
|
||||
@override
|
||||
double get minExtent => GridSize.headerHeight;
|
||||
|
||||
@override
|
||||
bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) {
|
||||
if (oldDelegate is SliverHeaderDelegateImplementation) {
|
||||
return fields.length != oldDelegate.fields.length;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,13 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_database_field_editor.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.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 'field_type_extension.dart';
|
||||
|
||||
@ -29,6 +33,8 @@ class MobileFieldButton extends StatelessWidget {
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1.0,
|
||||
);
|
||||
final fieldController =
|
||||
context.read<GridBloc>().databaseController.fieldController;
|
||||
return Container(
|
||||
width: field.fieldSettings!.width.toDouble(),
|
||||
decoration: BoxDecoration(
|
||||
@ -36,7 +42,14 @@ class MobileFieldButton extends StatelessWidget {
|
||||
),
|
||||
child: TextButton(
|
||||
onLongPress: () {
|
||||
debugPrint("gimme the bottom drawer");
|
||||
showMobileBottomSheet(
|
||||
context: context,
|
||||
builder: (context) => MobileDBBottomSheetFieldEditor(
|
||||
viewId: viewId,
|
||||
field: field.field,
|
||||
fieldController: fieldController,
|
||||
),
|
||||
);
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
|
||||
|
@ -42,11 +42,12 @@ class NumberTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
|
||||
class NumberTypeOptionWidget extends TypeOptionWidget {
|
||||
final NumberTypeOptionContext typeOptionContext;
|
||||
final PopoverMutex popoverMutex;
|
||||
|
||||
const NumberTypeOptionWidget({
|
||||
super.key,
|
||||
required this.typeOptionContext,
|
||||
required this.popoverMutex,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -60,7 +61,6 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
|
||||
final selectNumUnitButton = SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
margin: GridSize.typeOptionContentInsets,
|
||||
rightIcon: const FlowySvg(FlowySvgs.more_s),
|
||||
text: FlowyText.regular(
|
||||
state.typeOption.format.title(),
|
||||
@ -72,13 +72,15 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
|
||||
padding: const EdgeInsets.only(left: 6),
|
||||
height: GridSize.popoverItemHeight,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyText.medium(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
child: FlowyText.regular(
|
||||
LocaleKeys.grid_field_numberFormat.tr(),
|
||||
color: Theme.of(context).hintColor,
|
||||
fontSize: 11,
|
||||
),
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -87,7 +89,7 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
|
||||
mutex: popoverMutex,
|
||||
triggerActions:
|
||||
PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
|
||||
offset: const Offset(8, 0),
|
||||
offset: const Offset(16, 0),
|
||||
constraints: BoxConstraints.loose(const Size(460, 440)),
|
||||
margin: EdgeInsets.zero,
|
||||
child: selectNumUnitButton,
|
||||
|
@ -3,7 +3,6 @@ import 'package:appflowy/plugins/database_view/application/field/type_option/sel
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.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';
|
||||
@ -40,23 +39,21 @@ class SelectOptionTypeOptionWidget extends StatelessWidget {
|
||||
BlocBuilder<SelectOptionTypeOptionBloc, SelectOptionTypeOptionState>(
|
||||
builder: (context, state) {
|
||||
final List<Widget> children = [
|
||||
const TypeOptionSeparator(),
|
||||
const OptionTitle(),
|
||||
if (state.isEditingOption)
|
||||
const TypeOptionSeparator(spacing: 8),
|
||||
const _OptionTitle(),
|
||||
const VSpace(4),
|
||||
if (state.isEditingOption) ...[
|
||||
CreateOptionTextField(popoverMutex: popoverMutex),
|
||||
if (state.options.isNotEmpty && state.isEditingOption)
|
||||
const VSpace(10),
|
||||
if (state.options.isEmpty && !state.isEditingOption)
|
||||
const VSpace(4),
|
||||
] else
|
||||
const _AddOptionButton(),
|
||||
const VSpace(4),
|
||||
_OptionList(popoverMutex: popoverMutex),
|
||||
];
|
||||
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: children.length,
|
||||
itemBuilder: (context, index) {
|
||||
return children[index];
|
||||
},
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: children,
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -64,31 +61,22 @@ class SelectOptionTypeOptionWidget extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class OptionTitle extends StatelessWidget {
|
||||
const OptionTitle({Key? key}) : super(key: key);
|
||||
class _OptionTitle extends StatelessWidget {
|
||||
const _OptionTitle();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<SelectOptionTypeOptionBloc, SelectOptionTypeOptionState>(
|
||||
builder: (context, state) {
|
||||
final List<Widget> children = [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 9),
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.grid_field_optionTitle.tr(),
|
||||
),
|
||||
),
|
||||
];
|
||||
if (state.options.isNotEmpty && !state.isEditingOption) {
|
||||
children.add(const Spacer());
|
||||
children.add(const _OptionTitleButton());
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: Row(children: children),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: FlowyText.regular(
|
||||
LocaleKeys.grid_field_optionTitle.tr(),
|
||||
fontSize: 11,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -96,29 +84,6 @@ class OptionTitle extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _OptionTitleButton extends StatelessWidget {
|
||||
const _OptionTitleButton({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: 100,
|
||||
height: 26,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(
|
||||
LocaleKeys.grid_field_addOption.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
onTap: () {
|
||||
context
|
||||
.read<SelectOptionTypeOptionBloc>()
|
||||
.add(const SelectOptionTypeOptionEvent.addingOption());
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _OptionList extends StatelessWidget {
|
||||
final PopoverMutex? popoverMutex;
|
||||
const _OptionList({Key? key, this.popoverMutex}) : super(key: key);
|
||||
@ -140,7 +105,6 @@ class _OptionList extends StatelessWidget {
|
||||
|
||||
return ListView.separated(
|
||||
shrinkWrap: true,
|
||||
controller: ScrollController(),
|
||||
separatorBuilder: (context, index) {
|
||||
return VSpace(GridSize.typeOptionSeparatorHeight);
|
||||
},
|
||||
@ -187,7 +151,7 @@ class _OptionCellState extends State<_OptionCell> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final child = SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
height: 28,
|
||||
child: SelectOptionTagCell(
|
||||
option: widget.option,
|
||||
onSelected: (SelectOptionPB pb) {
|
||||
@ -212,7 +176,7 @@ class _OptionCellState extends State<_OptionCell> {
|
||||
asBarrier: true,
|
||||
constraints: BoxConstraints.loose(const Size(460, 470)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: child,
|
||||
),
|
||||
popupBuilder: (BuildContext popoverContext) {
|
||||
@ -243,24 +207,19 @@ class _AddOptionButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
text: FlowyText.medium(
|
||||
LocaleKeys.grid_field_addSelectOption.tr(),
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
),
|
||||
onTap: () {
|
||||
context
|
||||
.read<SelectOptionTypeOptionBloc>()
|
||||
.add(const SelectOptionTypeOptionEvent.addingOption());
|
||||
},
|
||||
leftIcon: FlowySvg(
|
||||
FlowySvgs.add_s,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
leftIcon: const FlowySvg(FlowySvgs.add_s),
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -303,7 +262,7 @@ class _CreateOptionTextFieldState extends State<CreateOptionTextField> {
|
||||
builder: (context, state) {
|
||||
final text = state.newOptionName.foldRight("", (a, previous) => a);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14.0),
|
||||
child: FlowyTextField(
|
||||
autoClearWhenDone: true,
|
||||
text: text,
|
||||
|
@ -62,20 +62,14 @@ class SelectOptionTypeOptionEditor extends StatelessWidget {
|
||||
),
|
||||
const VSpace(10),
|
||||
const _DeleteTag(),
|
||||
const TypeOptionSeparator(),
|
||||
SelectOptionColorList(
|
||||
selectedColor: state.option.color,
|
||||
onSelectedColor: (color) => context
|
||||
.read<EditSelectOptionBloc>()
|
||||
.add(EditSelectOptionEvent.updateColor(color)),
|
||||
),
|
||||
];
|
||||
|
||||
if (showOptions) {
|
||||
cells.add(const TypeOptionSeparator());
|
||||
cells.add(
|
||||
SelectOptionColorList(
|
||||
selectedColor: state.option.color,
|
||||
onSelectedColor: (color) => context
|
||||
.read<EditSelectOptionBloc>()
|
||||
.add(EditSelectOptionEvent.updateColor(color)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
width: 180,
|
||||
child: ListView.builder(
|
||||
@ -188,7 +182,6 @@ class SelectOptionColorList extends StatelessWidget {
|
||||
),
|
||||
ListView.separated(
|
||||
shrinkWrap: true,
|
||||
controller: ScrollController(),
|
||||
separatorBuilder: (context, index) {
|
||||
return VSpace(GridSize.typeOptionSeparatorHeight);
|
||||
},
|
||||
|
@ -44,7 +44,6 @@ class SingleSelectTypeOptionWidget extends TypeOptionWidget {
|
||||
},
|
||||
popoverMutex: popoverMutex,
|
||||
typeOptionAction: selectOptionAction,
|
||||
// key: ValueKey(state.typeOption.hashCode),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -91,7 +91,7 @@ class SelectOptionTag extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
EdgeInsets padding =
|
||||
const EdgeInsets.symmetric(vertical: 2, horizontal: 8.0);
|
||||
const EdgeInsets.symmetric(vertical: 1, horizontal: 8.0);
|
||||
if (onRemove != null) {
|
||||
padding = padding.copyWith(right: 2.0);
|
||||
}
|
||||
|
@ -109,7 +109,6 @@ class _OptionList extends StatelessWidget {
|
||||
|
||||
return ListView.separated(
|
||||
shrinkWrap: true,
|
||||
controller: ScrollController(),
|
||||
itemCount: cells.length,
|
||||
separatorBuilder: (_, __) =>
|
||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||
@ -302,7 +301,7 @@ class _SelectOptionCellState extends State<_SelectOptionCell> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final child = SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
height: 28,
|
||||
child: SelectOptionTagCell(
|
||||
option: widget.option,
|
||||
onSelected: (option) {
|
||||
@ -328,7 +327,6 @@ class _SelectOptionCellState extends State<_SelectOptionCell> {
|
||||
FlowyIconButton(
|
||||
onPressed: () => _popoverController.show(),
|
||||
iconPadding: const EdgeInsets.symmetric(horizontal: 6.0),
|
||||
// If hover color is none, it will use secondary color from the theme, we use [Colors.transparent] to remove the hover color
|
||||
hoverColor: Colors.transparent,
|
||||
icon: FlowySvg(
|
||||
FlowySvgs.details_s,
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/row/row_document.dart';
|
||||
@ -11,11 +12,13 @@ import 'row_banner.dart';
|
||||
import 'row_property.dart';
|
||||
|
||||
class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate {
|
||||
final FieldController fieldController;
|
||||
final RowController rowController;
|
||||
final GridCellBuilder cellBuilder;
|
||||
|
||||
const RowDetailPage({
|
||||
super.key,
|
||||
required this.fieldController,
|
||||
required this.rowController,
|
||||
required this.cellBuilder,
|
||||
});
|
||||
@ -56,6 +59,7 @@ class _RowDetailPageState extends State<RowDetailPage> {
|
||||
child: RowPropertyList(
|
||||
cellBuilder: widget.cellBuilder,
|
||||
viewId: widget.rowController.viewId,
|
||||
fieldController: widget.fieldController,
|
||||
),
|
||||
),
|
||||
const VSpace(20),
|
||||
|
@ -3,14 +3,13 @@ import 'dart:io';
|
||||
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/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
@ -28,12 +27,15 @@ import 'cell_builder.dart';
|
||||
/// [RowDetailPage].
|
||||
class RowPropertyList extends StatelessWidget {
|
||||
final String viewId;
|
||||
final FieldController fieldController;
|
||||
final GridCellBuilder cellBuilder;
|
||||
|
||||
const RowPropertyList({
|
||||
super.key,
|
||||
required this.viewId,
|
||||
required this.fieldController,
|
||||
required this.cellBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -46,6 +48,7 @@ class RowPropertyList extends StatelessWidget {
|
||||
key: ValueKey('row_detail_${cell.fieldId}'),
|
||||
cellContext: cell,
|
||||
cellBuilder: cellBuilder,
|
||||
fieldController: fieldController,
|
||||
index: index,
|
||||
),
|
||||
)
|
||||
@ -99,7 +102,10 @@ class RowPropertyList extends StatelessWidget {
|
||||
padding: EdgeInsets.only(bottom: 4.0),
|
||||
child: ToggleHiddenFieldsVisibilityButton(),
|
||||
),
|
||||
CreateRowFieldButton(viewId: viewId),
|
||||
CreateRowFieldButton(
|
||||
viewId: viewId,
|
||||
fieldController: fieldController,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -113,14 +119,16 @@ class RowPropertyList extends StatelessWidget {
|
||||
class _PropertyCell extends StatefulWidget {
|
||||
final DatabaseCellContext cellContext;
|
||||
final GridCellBuilder cellBuilder;
|
||||
|
||||
final FieldController fieldController;
|
||||
final int index;
|
||||
|
||||
const _PropertyCell({
|
||||
super.key,
|
||||
required this.cellContext,
|
||||
required this.cellBuilder,
|
||||
Key? key,
|
||||
required this.fieldController,
|
||||
required this.index,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _PropertyCellState();
|
||||
@ -219,30 +227,8 @@ class _PropertyCellState extends State<_PropertyCell> {
|
||||
Widget buildFieldEditor() {
|
||||
return FieldEditor(
|
||||
viewId: widget.cellContext.viewId,
|
||||
fieldInfo: widget.cellContext.fieldInfo,
|
||||
isGroupingField: widget.cellContext.fieldInfo.isGroupField,
|
||||
typeOptionLoader: FieldTypeOptionLoader(
|
||||
viewId: widget.cellContext.viewId,
|
||||
field: widget.cellContext.fieldInfo.field,
|
||||
),
|
||||
onToggleVisibility: (fieldId) {
|
||||
_popoverController.close();
|
||||
context
|
||||
.read<RowDetailBloc>()
|
||||
.add(RowDetailEvent.toggleFieldVisibility(fieldId));
|
||||
},
|
||||
onDeleted: (fieldId) {
|
||||
_popoverController.close();
|
||||
|
||||
NavigatorAlertDialog(
|
||||
title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
|
||||
confirm: () {
|
||||
context
|
||||
.read<RowDetailBloc>()
|
||||
.add(RowDetailEvent.deleteField(fieldId));
|
||||
},
|
||||
).show(context);
|
||||
},
|
||||
field: widget.cellContext.fieldInfo.field,
|
||||
fieldController: widget.fieldController,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -342,8 +328,13 @@ class ToggleHiddenFieldsVisibilityButton extends StatelessWidget {
|
||||
|
||||
class CreateRowFieldButton extends StatefulWidget {
|
||||
final String viewId;
|
||||
final FieldController fieldController;
|
||||
|
||||
const CreateRowFieldButton({required this.viewId, super.key});
|
||||
const CreateRowFieldButton({
|
||||
super.key,
|
||||
required this.viewId,
|
||||
required this.fieldController,
|
||||
});
|
||||
|
||||
@override
|
||||
State<CreateRowFieldButton> createState() => _CreateRowFieldButtonState();
|
||||
@ -394,24 +385,11 @@ class _CreateRowFieldButtonState extends State<CreateRowFieldButton> {
|
||||
),
|
||||
),
|
||||
),
|
||||
popupBuilder: (BuildContext popOverContext) {
|
||||
popupBuilder: (BuildContext popoverContext) {
|
||||
return FieldEditor(
|
||||
viewId: widget.viewId,
|
||||
typeOptionLoader: FieldTypeOptionLoader(
|
||||
viewId: widget.viewId,
|
||||
field: typeOption.field_2,
|
||||
),
|
||||
onDeleted: (fieldId) {
|
||||
popoverController.close();
|
||||
NavigatorAlertDialog(
|
||||
title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
|
||||
confirm: () {
|
||||
context
|
||||
.read<RowDetailBloc>()
|
||||
.add(RowDetailEvent.deleteField(fieldId));
|
||||
},
|
||||
).show(context);
|
||||
},
|
||||
field: typeOption.field_2,
|
||||
fieldController: widget.fieldController,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -3,22 +3,19 @@ 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';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/setting/property_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.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:styled_widget/styled_widget.dart';
|
||||
|
||||
import '../../grid/presentation/layout/sizes.dart';
|
||||
import '../../grid/presentation/widgets/header/field_editor.dart';
|
||||
|
||||
class DatabasePropertyList extends StatefulWidget {
|
||||
final String viewId;
|
||||
final FieldController fieldController;
|
||||
@ -49,6 +46,7 @@ class _DatabasePropertyListState extends State<DatabasePropertyList> {
|
||||
return DatabasePropertyCell(
|
||||
key: ValueKey(field.id),
|
||||
viewId: widget.viewId,
|
||||
fieldController: widget.fieldController,
|
||||
fieldInfo: field,
|
||||
popoverMutex: _popoverMutex,
|
||||
index: index,
|
||||
@ -93,6 +91,7 @@ class _DatabasePropertyListState extends State<DatabasePropertyList> {
|
||||
|
||||
@visibleForTesting
|
||||
class DatabasePropertyCell extends StatefulWidget {
|
||||
final FieldController fieldController;
|
||||
final FieldInfo fieldInfo;
|
||||
final String viewId;
|
||||
final PopoverMutex popoverMutex;
|
||||
@ -104,6 +103,7 @@ class DatabasePropertyCell extends StatefulWidget {
|
||||
required this.viewId,
|
||||
required this.popoverMutex,
|
||||
required this.index,
|
||||
required this.fieldController,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -120,6 +120,7 @@ class _DatabasePropertyCellState extends State<DatabasePropertyCell> {
|
||||
visiblity != null && visiblity != FieldVisibility.AlwaysHidden
|
||||
? FlowySvgs.show_m
|
||||
: FlowySvgs.hide_m,
|
||||
size: const Size.square(16),
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
);
|
||||
|
||||
@ -182,7 +183,7 @@ class _DatabasePropertyCellState extends State<DatabasePropertyCell> {
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: visibleIcon.padding(all: 4.0),
|
||||
icon: visibleIcon,
|
||||
),
|
||||
onTap: () => _popoverController.show(),
|
||||
).padding(horizontal: 6.0),
|
||||
@ -190,11 +191,8 @@ class _DatabasePropertyCellState extends State<DatabasePropertyCell> {
|
||||
popupBuilder: (BuildContext context) {
|
||||
return FieldEditor(
|
||||
viewId: widget.viewId,
|
||||
fieldInfo: widget.fieldInfo,
|
||||
typeOptionLoader: FieldTypeOptionLoader(
|
||||
viewId: widget.viewId,
|
||||
field: widget.fieldInfo.field,
|
||||
),
|
||||
field: widget.fieldInfo.field,
|
||||
fieldController: widget.fieldController,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -454,10 +454,12 @@ GoRoute _mobileCardDetailScreenRoute() {
|
||||
pageBuilder: (context, state) {
|
||||
final args = state.extra as Map<String, dynamic>;
|
||||
final rowController = args[MobileCardDetailScreen.argRowController];
|
||||
final fieldController = args[MobileCardDetailScreen.argFieldController];
|
||||
|
||||
return MaterialPage(
|
||||
child: MobileCardDetailScreen(
|
||||
rowController: rowController,
|
||||
fieldController: fieldController,
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -471,6 +473,7 @@ GoRoute _mobileCardPropertyEditScreenRoute() {
|
||||
pageBuilder: (context, state) {
|
||||
final args = state.extra as Map<String, dynamic>;
|
||||
final cellContext = args[CardPropertyEditScreen.argCellContext];
|
||||
final fieldController = args[CardPropertyEditScreen.argFieldController];
|
||||
final rowDetailBloc = args[CardPropertyEditScreen.argRowDetailBloc];
|
||||
|
||||
return MaterialPage(
|
||||
@ -478,6 +481,7 @@ GoRoute _mobileCardPropertyEditScreenRoute() {
|
||||
value: rowDetailBloc as RowDetailBloc,
|
||||
child: CardPropertyEditScreen(
|
||||
cellContext: cellContext,
|
||||
fieldController: fieldController,
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -508,11 +512,16 @@ GoRoute _mobileCreateRowFieldScreenRoute() {
|
||||
pageBuilder: (context, state) {
|
||||
final args = state.extra as Map<String, dynamic>;
|
||||
final viewId = args[MobileCreateRowFieldScreen.argViewId];
|
||||
final fieldController =
|
||||
args[MobileCreateRowFieldScreen.argFieldController];
|
||||
final typeOption = args[MobileCreateRowFieldScreen.argTypeOption];
|
||||
|
||||
return MaterialPage(
|
||||
child:
|
||||
MobileCreateRowFieldScreen(viewId: viewId, typeOption: typeOption),
|
||||
child: MobileCreateRowFieldScreen(
|
||||
viewId: viewId,
|
||||
typeOption: typeOption,
|
||||
fieldController: fieldController,
|
||||
),
|
||||
fullscreenDialog: true,
|
||||
);
|
||||
},
|
||||
|
@ -185,7 +185,7 @@ class FlowyTextButton extends StatelessWidget {
|
||||
List<Widget> children = [];
|
||||
if (heading != null) {
|
||||
children.add(heading!);
|
||||
children.add(const HSpace(6));
|
||||
children.add(const HSpace(8));
|
||||
}
|
||||
children.add(
|
||||
FlowyText(
|
||||
|
@ -40,13 +40,14 @@ void main() {
|
||||
);
|
||||
|
||||
final editorBloc = FieldEditorBloc(
|
||||
isGroupField: fieldInfo.isGroupField,
|
||||
viewId: context.gridView.id,
|
||||
loader: loader,
|
||||
field: fieldInfo.field,
|
||||
fieldController: context.fieldController,
|
||||
)..add(const FieldEditorEvent.initial());
|
||||
await boardResponseFuture();
|
||||
|
||||
editorBloc.add(const FieldEditorEvent.updateName('Hello world'));
|
||||
editorBloc.add(const FieldEditorEvent.renameField('Hello world'));
|
||||
await boardResponseFuture();
|
||||
|
||||
// assert the groups were not changed
|
||||
|
@ -29,13 +29,10 @@ void main() {
|
||||
build: () => editorBloc,
|
||||
wait: boardResponseDuration(),
|
||||
act: (bloc) async {
|
||||
bloc.add(const FieldEditorEvent.switchToField(FieldType.RichText));
|
||||
bloc.add(const FieldEditorEvent.switchFieldType(FieldType.RichText));
|
||||
},
|
||||
verify: (bloc) {
|
||||
bloc.state.field.fold(
|
||||
() => throw Exception(),
|
||||
(field) => field.fieldType == FieldType.RichText,
|
||||
);
|
||||
assert(bloc.state.field.fieldType == FieldType.RichText);
|
||||
},
|
||||
);
|
||||
blocTest<BoardBloc, BoardState>(
|
||||
|
@ -88,7 +88,8 @@ class BoardTestContext {
|
||||
);
|
||||
|
||||
final editorBloc = FieldEditorBloc(
|
||||
isGroupField: fieldInfo.isGroupField,
|
||||
viewId: databaseController.viewId,
|
||||
fieldController: fieldController,
|
||||
loader: loader,
|
||||
field: fieldInfo.field,
|
||||
);
|
||||
@ -126,10 +127,11 @@ class BoardTestContext {
|
||||
}
|
||||
|
||||
Future<FieldEditorBloc> createField(FieldType fieldType) async {
|
||||
final editorBloc = await createFieldEditor(viewId: gridView.id)
|
||||
..add(const FieldEditorEvent.initial());
|
||||
final editorBloc =
|
||||
await createFieldEditor(databaseController: _boardDataController)
|
||||
..add(const FieldEditorEvent.initial());
|
||||
await gridResponseFuture();
|
||||
editorBloc.add(FieldEditorEvent.switchToField(fieldType));
|
||||
editorBloc.add(FieldEditorEvent.switchFieldType(fieldType));
|
||||
await gridResponseFuture();
|
||||
return Future(() => editorBloc);
|
||||
}
|
||||
|
@ -13,7 +13,8 @@ Future<FieldEditorBloc> createEditorBloc(AppFlowyGridTest gridTest) async {
|
||||
);
|
||||
|
||||
return FieldEditorBloc(
|
||||
isGroupField: fieldInfo.isGroupField,
|
||||
viewId: context.gridView.id,
|
||||
fieldController: context.fieldController,
|
||||
loader: loader,
|
||||
field: fieldInfo.field,
|
||||
)..add(const FieldEditorEvent.initial());
|
||||
@ -27,67 +28,21 @@ void main() {
|
||||
});
|
||||
|
||||
test('rename field', () async {
|
||||
final editorBloc = await makeEditorBloc(gridTest);
|
||||
editorBloc.add(const FieldEditorEvent.updateName('Hello world'));
|
||||
await gridResponseFuture();
|
||||
final editorBloc = await createEditorBloc(gridTest);
|
||||
editorBloc.add(const FieldEditorEvent.renameField('Hello world'));
|
||||
|
||||
editorBloc.state.field.fold(
|
||||
() => throw Exception("The field should not be none"),
|
||||
(field) {
|
||||
assert(field.name == 'Hello world');
|
||||
},
|
||||
);
|
||||
await gridResponseFuture();
|
||||
expect(editorBloc.state.field.name, equals("Hello world"));
|
||||
});
|
||||
|
||||
test('switch to text field', () async {
|
||||
final editorBloc = await makeEditorBloc(gridTest);
|
||||
final editorBloc = await createEditorBloc(gridTest);
|
||||
|
||||
editorBloc.add(const FieldEditorEvent.switchToField(FieldType.RichText));
|
||||
editorBloc.add(const FieldEditorEvent.switchFieldType(FieldType.RichText));
|
||||
await gridResponseFuture();
|
||||
|
||||
editorBloc.state.field.fold(
|
||||
() => throw Exception("The field should not be none"),
|
||||
(field) {
|
||||
// The default length of the fields is 3. The length of the fields
|
||||
// should not change after switching to other field type
|
||||
// assert(gridTest.fieldContexts.length == 3);
|
||||
assert(field.fieldType == FieldType.RichText);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test('delete field', () async {
|
||||
final editorBloc = await makeEditorBloc(gridTest);
|
||||
editorBloc.add(const FieldEditorEvent.switchToField(FieldType.RichText));
|
||||
await gridResponseFuture();
|
||||
|
||||
editorBloc.state.field.fold(
|
||||
() => throw Exception("The field should not be none"),
|
||||
(field) {
|
||||
// The default length of the fields is 3. The length of the fields
|
||||
// should not change after switching to other field type
|
||||
// assert(gridTest.fieldContexts.length == 3);
|
||||
assert(field.fieldType == FieldType.RichText);
|
||||
},
|
||||
);
|
||||
// The default length of the fields is 3. The length of the fields
|
||||
// should not change after switching to other field type
|
||||
expect(editorBloc.state.field.fieldType, equals(FieldType.RichText));
|
||||
});
|
||||
}
|
||||
|
||||
Future<FieldEditorBloc> makeEditorBloc(AppFlowyGridTest gridTest) async {
|
||||
final context = await gridTest.createTestGrid();
|
||||
final fieldInfo = context.singleSelectFieldContext();
|
||||
final loader = FieldTypeOptionLoader(
|
||||
viewId: context.gridView.id,
|
||||
field: fieldInfo.field,
|
||||
);
|
||||
|
||||
final editorBloc = FieldEditorBloc(
|
||||
isGroupField: fieldInfo.isGroupField,
|
||||
loader: loader,
|
||||
field: fieldInfo.field,
|
||||
)..add(const FieldEditorEvent.initial());
|
||||
|
||||
await gridResponseFuture();
|
||||
|
||||
return editorBloc;
|
||||
}
|
||||
|
@ -1,128 +0,0 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_action_sheet_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/grid_header_bloc.dart';
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'util.dart';
|
||||
|
||||
void main() {
|
||||
late AppFlowyGridTest gridTest;
|
||||
|
||||
setUpAll(() async {
|
||||
gridTest = await AppFlowyGridTest.ensureInitialized();
|
||||
});
|
||||
|
||||
group('$GridHeaderBloc', () {
|
||||
late FieldActionSheetBloc actionSheetBloc;
|
||||
late GridTestContext context;
|
||||
setUp(() async {
|
||||
context = await gridTest.createTestGrid();
|
||||
actionSheetBloc = FieldActionSheetBloc(
|
||||
viewId: context.gridView.id,
|
||||
fieldInfo: context.singleSelectFieldContext(),
|
||||
);
|
||||
});
|
||||
|
||||
blocTest<GridHeaderBloc, GridHeaderState>(
|
||||
"hides property",
|
||||
build: () {
|
||||
final bloc = GridHeaderBloc(
|
||||
viewId: context.gridView.id,
|
||||
fieldController: context.fieldController,
|
||||
)..add(const GridHeaderEvent.initial());
|
||||
return bloc;
|
||||
},
|
||||
act: (bloc) async {
|
||||
actionSheetBloc.add(const FieldActionSheetEvent.hideField());
|
||||
await Future.delayed(gridResponseDuration());
|
||||
},
|
||||
wait: gridResponseDuration(),
|
||||
verify: (bloc) {
|
||||
assert(bloc.state.fields.length == 2);
|
||||
},
|
||||
);
|
||||
|
||||
blocTest<GridHeaderBloc, GridHeaderState>(
|
||||
"shows property",
|
||||
build: () {
|
||||
final bloc = GridHeaderBloc(
|
||||
viewId: context.gridView.id,
|
||||
fieldController: context.fieldController,
|
||||
)..add(const GridHeaderEvent.initial());
|
||||
return bloc;
|
||||
},
|
||||
act: (bloc) async {
|
||||
actionSheetBloc.add(const FieldActionSheetEvent.hideField());
|
||||
await Future.delayed(gridResponseDuration());
|
||||
actionSheetBloc.add(const FieldActionSheetEvent.showField());
|
||||
await Future.delayed(gridResponseDuration());
|
||||
},
|
||||
wait: gridResponseDuration(),
|
||||
verify: (bloc) {
|
||||
assert(bloc.state.fields.length == 3);
|
||||
},
|
||||
);
|
||||
|
||||
blocTest<GridHeaderBloc, GridHeaderState>(
|
||||
"duplicate property",
|
||||
build: () {
|
||||
final bloc = GridHeaderBloc(
|
||||
viewId: context.gridView.id,
|
||||
fieldController: context.fieldController,
|
||||
)..add(const GridHeaderEvent.initial());
|
||||
return bloc;
|
||||
},
|
||||
act: (bloc) async {
|
||||
actionSheetBloc.add(const FieldActionSheetEvent.duplicateField());
|
||||
await Future.delayed(gridResponseDuration());
|
||||
},
|
||||
wait: gridResponseDuration(),
|
||||
verify: (bloc) {
|
||||
expect(bloc.state.fields.length, 4);
|
||||
},
|
||||
);
|
||||
|
||||
blocTest<GridHeaderBloc, GridHeaderState>(
|
||||
"delete property",
|
||||
build: () {
|
||||
final bloc = GridHeaderBloc(
|
||||
viewId: context.gridView.id,
|
||||
fieldController: context.fieldController,
|
||||
)..add(const GridHeaderEvent.initial());
|
||||
return bloc;
|
||||
},
|
||||
act: (bloc) async {
|
||||
actionSheetBloc.add(const FieldActionSheetEvent.deleteField());
|
||||
await Future.delayed(gridResponseDuration());
|
||||
},
|
||||
wait: gridResponseDuration(),
|
||||
verify: (bloc) {
|
||||
expect(bloc.state.fields.length, 2);
|
||||
},
|
||||
);
|
||||
|
||||
blocTest<GridHeaderBloc, GridHeaderState>(
|
||||
"update name",
|
||||
build: () {
|
||||
final bloc = GridHeaderBloc(
|
||||
viewId: context.gridView.id,
|
||||
fieldController: context.fieldController,
|
||||
)..add(const GridHeaderEvent.initial());
|
||||
return bloc;
|
||||
},
|
||||
act: (bloc) async {
|
||||
actionSheetBloc
|
||||
.add(const FieldActionSheetEvent.updateFieldName("Hello world"));
|
||||
await Future.delayed(gridResponseDuration());
|
||||
},
|
||||
wait: gridResponseDuration(),
|
||||
verify: (bloc) {
|
||||
final field = bloc.state.fields.firstWhere(
|
||||
(element) => element.id == actionSheetBloc.fieldService.fieldId,
|
||||
);
|
||||
|
||||
expect(field.name, "Hello world");
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
@ -73,10 +73,11 @@ class GridTestContext {
|
||||
}
|
||||
|
||||
Future<FieldEditorBloc> createField(FieldType fieldType) async {
|
||||
final editorBloc = await createFieldEditor(viewId: gridView.id)
|
||||
..add(const FieldEditorEvent.initial());
|
||||
final editorBloc =
|
||||
await createFieldEditor(databaseController: gridController)
|
||||
..add(const FieldEditorEvent.initial());
|
||||
await gridResponseFuture();
|
||||
editorBloc.add(FieldEditorEvent.switchToField(fieldType));
|
||||
editorBloc.add(FieldEditorEvent.switchFieldType(fieldType));
|
||||
await gridResponseFuture();
|
||||
return Future(() => editorBloc);
|
||||
}
|
||||
@ -132,19 +133,21 @@ class GridTestContext {
|
||||
}
|
||||
|
||||
Future<FieldEditorBloc> createFieldEditor({
|
||||
required String viewId,
|
||||
required DatabaseController databaseController,
|
||||
}) async {
|
||||
final result = await TypeOptionBackendService.createFieldTypeOption(
|
||||
viewId: viewId,
|
||||
viewId: databaseController.viewId,
|
||||
);
|
||||
await gridResponseFuture();
|
||||
return result.fold(
|
||||
(data) {
|
||||
final loader = FieldTypeOptionLoader(
|
||||
viewId: viewId,
|
||||
viewId: databaseController.viewId,
|
||||
field: data.field_2,
|
||||
);
|
||||
return FieldEditorBloc(
|
||||
isGroupField: FieldInfo.initial(data.field_2).isGroupField,
|
||||
viewId: databaseController.viewId,
|
||||
fieldController: databaseController.fieldController,
|
||||
loader: loader,
|
||||
field: data.field_2,
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user