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();
|
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
|
// multiple text cell
|
||||||
testWidgets('edit multiple text cells', (tester) async {
|
testWidgets('edit multiple text cells', (tester) async {
|
||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
|
@ -106,7 +106,7 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('create checklist field ', (tester) async {
|
testWidgets('create checklist field', (tester) async {
|
||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
|
@ -123,7 +123,6 @@ void main() {
|
|||||||
await tester.tapTypeOptionButton();
|
await tester.tapTypeOptionButton();
|
||||||
|
|
||||||
await tester.selectFieldType(fieldType);
|
await tester.selectFieldType(fieldType);
|
||||||
await tester.dismissFieldEditor();
|
|
||||||
|
|
||||||
// After update the field type, the cells should be updated
|
// After update the field type, the cells should be updated
|
||||||
await tester.findCellByFieldType(fieldType);
|
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/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/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.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_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_extension.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_list.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 {
|
Future<void> tapDeletePropertyInFieldEditor() async {
|
||||||
final deleteButton = find.byType(DeleteFieldButton);
|
final deleteButton = find.byWidgetPredicate(
|
||||||
|
(widget) =>
|
||||||
|
widget is FieldActionCell && widget.action == FieldAction.delete,
|
||||||
|
);
|
||||||
await tapButton(deleteButton);
|
await tapButton(deleteButton);
|
||||||
|
|
||||||
final confirmButton = find.descendant(
|
final confirmButton = find.descendant(
|
||||||
@ -765,13 +767,18 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
Future<void> tapHidePropertyButton() async {
|
Future<void> tapHidePropertyButton() async {
|
||||||
final field = find.byWidgetPredicate(
|
final field = find.byWidgetPredicate(
|
||||||
(widget) =>
|
(widget) =>
|
||||||
widget is FieldActionCell && widget.action == FieldAction.hide,
|
widget is FieldActionCell &&
|
||||||
|
widget.action == FieldAction.toggleVisibility,
|
||||||
);
|
);
|
||||||
await tapButton(field);
|
await tapButton(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> tapHidePropertyButtonInFieldEditor() async {
|
Future<void> tapHidePropertyButtonInFieldEditor() async {
|
||||||
final button = find.byType(FieldVisibilityToggleButton);
|
final button = find.byWidgetPredicate(
|
||||||
|
(widget) =>
|
||||||
|
widget is FieldActionCell &&
|
||||||
|
widget.action == FieldAction.toggleVisibility,
|
||||||
|
);
|
||||||
await tapButton(button);
|
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,
|
super.key,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.onRename,
|
required this.onRename,
|
||||||
|
this.padding = const EdgeInsets.symmetric(horizontal: 12.0, vertical: 16.0),
|
||||||
});
|
});
|
||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
final void Function(String name) onRename;
|
final void Function(String name) onRename;
|
||||||
|
final EdgeInsets padding;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MobileBottomSheetRenameWidget> createState() =>
|
State<MobileBottomSheetRenameWidget> createState() =>
|
||||||
@ -39,17 +41,13 @@ class _MobileBottomSheetRenameWidgetState
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: widget.padding,
|
||||||
horizontal: 4.0,
|
|
||||||
vertical: 16.0,
|
|
||||||
),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
const HSpace(8.0),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 44.0,
|
height: 42.0,
|
||||||
child: FlowyTextField(
|
child: FlowyTextField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
),
|
),
|
||||||
@ -58,17 +56,16 @@ class _MobileBottomSheetRenameWidgetState
|
|||||||
const HSpace(12.0),
|
const HSpace(12.0),
|
||||||
FlowyTextButton(
|
FlowyTextButton(
|
||||||
LocaleKeys.button_edit.tr(),
|
LocaleKeys.button_edit.tr(),
|
||||||
|
constraints: const BoxConstraints.tightFor(height: 42),
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 12.0,
|
|
||||||
horizontal: 16.0,
|
horizontal: 16.0,
|
||||||
),
|
),
|
||||||
fontColor: Colors.white,
|
fontColor: Colors.white,
|
||||||
fillColor: Colors.lightBlue.shade300,
|
fillColor: Theme.of(context).primaryColor,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
widget.onRename(controller.text);
|
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/bottom_sheet/bottom_sheet_action_widget.dart';
|
||||||
import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_bottom_sheet.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/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/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_banner_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
|
||||||
@ -24,12 +25,16 @@ class MobileCardDetailScreen extends StatefulWidget {
|
|||||||
const MobileCardDetailScreen({
|
const MobileCardDetailScreen({
|
||||||
super.key,
|
super.key,
|
||||||
required this.rowController,
|
required this.rowController,
|
||||||
|
required this.fieldController,
|
||||||
});
|
});
|
||||||
|
|
||||||
static const routeName = '/MobileCardDetailScreen';
|
static const routeName = '/MobileCardDetailScreen';
|
||||||
static const argRowController = 'rowController';
|
static const argRowController = 'rowController';
|
||||||
static const argCellBuilder = 'cellBuilder';
|
static const argCellBuilder = 'cellBuilder';
|
||||||
|
static const argFieldController = 'fieldController';
|
||||||
|
|
||||||
final RowController rowController;
|
final RowController rowController;
|
||||||
|
final FieldController fieldController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MobileCardDetailScreen> createState() => _MobileCardDetailScreenState();
|
State<MobileCardDetailScreen> createState() => _MobileCardDetailScreenState();
|
||||||
@ -169,6 +174,7 @@ class _MobileCardDetailScreenState extends State<MobileCardDetailScreen> {
|
|||||||
MobileRowPropertyList(
|
MobileRowPropertyList(
|
||||||
cellBuilder: _cellBuilder,
|
cellBuilder: _cellBuilder,
|
||||||
viewId: widget.rowController.viewId,
|
viewId: widget.rowController.viewId,
|
||||||
|
fieldController: widget.fieldController,
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
const VSpace(16),
|
const VSpace(16),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
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/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/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@ -9,15 +10,18 @@ import 'package:go_router/go_router.dart';
|
|||||||
class MobileCreateRowFieldScreen extends StatefulWidget {
|
class MobileCreateRowFieldScreen extends StatefulWidget {
|
||||||
static const routeName = '/MobileCreateRowFieldScreen';
|
static const routeName = '/MobileCreateRowFieldScreen';
|
||||||
static const argViewId = 'viewId';
|
static const argViewId = 'viewId';
|
||||||
|
static const argFieldController = 'fieldController';
|
||||||
static const argTypeOption = 'typeOption';
|
static const argTypeOption = 'typeOption';
|
||||||
|
|
||||||
const MobileCreateRowFieldScreen({
|
const MobileCreateRowFieldScreen({
|
||||||
|
super.key,
|
||||||
required this.viewId,
|
required this.viewId,
|
||||||
required this.typeOption,
|
required this.typeOption,
|
||||||
super.key,
|
required this.fieldController,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String viewId;
|
final String viewId;
|
||||||
|
final FieldController fieldController;
|
||||||
final TypeOptionPB typeOption;
|
final TypeOptionPB typeOption;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -53,6 +57,8 @@ class _MobileCreateRowFieldScreenState
|
|||||||
viewId: widget.viewId,
|
viewId: widget.viewId,
|
||||||
field: widget.typeOption.field_2,
|
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/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.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/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/plugins/database_view/application/field/type_option/type_option_service.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@ -8,9 +9,14 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
class MobileCreateRowFieldButton extends StatelessWidget {
|
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 String viewId;
|
||||||
|
final FieldController fieldController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -32,6 +38,7 @@ class MobileCreateRowFieldButton extends StatelessWidget {
|
|||||||
extra: {
|
extra: {
|
||||||
MobileCreateRowFieldScreen.argViewId: viewId,
|
MobileCreateRowFieldScreen.argViewId: viewId,
|
||||||
MobileCreateRowFieldScreen.argTypeOption: typeOption,
|
MobileCreateRowFieldScreen.argTypeOption: typeOption,
|
||||||
|
MobileCreateRowFieldScreen.argFieldController: fieldController,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -38,7 +38,7 @@ class _MobileFieldNameTextFieldState extends State<MobileFieldNameTextField> {
|
|||||||
onChanged: (newName) {
|
onChanged: (newName) {
|
||||||
context
|
context
|
||||||
.read<FieldEditorBloc>()
|
.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_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/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/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/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/grid/presentation/widgets/header/field_type_extension.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/accessory/cell_accessory.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/accessory/cell_accessory.dart';
|
||||||
@ -23,10 +24,12 @@ class MobileRowPropertyList extends StatelessWidget {
|
|||||||
const MobileRowPropertyList({
|
const MobileRowPropertyList({
|
||||||
super.key,
|
super.key,
|
||||||
required this.viewId,
|
required this.viewId,
|
||||||
|
required this.fieldController,
|
||||||
required this.cellBuilder,
|
required this.cellBuilder,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String viewId;
|
final String viewId;
|
||||||
|
final FieldController fieldController;
|
||||||
final GridCellBuilder cellBuilder;
|
final GridCellBuilder cellBuilder;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -44,6 +47,7 @@ class MobileRowPropertyList extends StatelessWidget {
|
|||||||
itemBuilder: (context, index) => _PropertyCell(
|
itemBuilder: (context, index) => _PropertyCell(
|
||||||
key: ValueKey('row_detail_${visibleCells[index].fieldId}'),
|
key: ValueKey('row_detail_${visibleCells[index].fieldId}'),
|
||||||
cellContext: visibleCells[index],
|
cellContext: visibleCells[index],
|
||||||
|
fieldController: fieldController,
|
||||||
cellBuilder: cellBuilder,
|
cellBuilder: cellBuilder,
|
||||||
index: index,
|
index: index,
|
||||||
),
|
),
|
||||||
@ -75,6 +79,7 @@ class MobileRowPropertyList extends StatelessWidget {
|
|||||||
// add new field
|
// add new field
|
||||||
MobileCreateRowFieldButton(
|
MobileCreateRowFieldButton(
|
||||||
viewId: viewId,
|
viewId: viewId,
|
||||||
|
fieldController: fieldController,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -93,11 +98,13 @@ class _PropertyCell extends StatefulWidget {
|
|||||||
const _PropertyCell({
|
const _PropertyCell({
|
||||||
super.key,
|
super.key,
|
||||||
required this.cellContext,
|
required this.cellContext,
|
||||||
|
required this.fieldController,
|
||||||
required this.cellBuilder,
|
required this.cellBuilder,
|
||||||
required this.index,
|
required this.index,
|
||||||
});
|
});
|
||||||
|
|
||||||
final DatabaseCellContext cellContext;
|
final DatabaseCellContext cellContext;
|
||||||
|
final FieldController fieldController;
|
||||||
final GridCellBuilder cellBuilder;
|
final GridCellBuilder cellBuilder;
|
||||||
final int index;
|
final int index;
|
||||||
|
|
||||||
@ -142,6 +149,7 @@ class _PropertyCellState extends State<_PropertyCell> {
|
|||||||
CardPropertyEditScreen.routeName,
|
CardPropertyEditScreen.routeName,
|
||||||
extra: {
|
extra: {
|
||||||
CardPropertyEditScreen.argCellContext: widget.cellContext,
|
CardPropertyEditScreen.argCellContext: widget.cellContext,
|
||||||
|
CardPropertyEditScreen.argFieldController: widget.fieldController,
|
||||||
CardPropertyEditScreen.argRowDetailBloc:
|
CardPropertyEditScreen.argRowDetailBloc:
|
||||||
context.read<RowDetailBloc>(),
|
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/database/card/card_property_edit/mobile_field_editor.dart';
|
||||||
import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.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/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/application/field/type_option/type_option_context.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
|
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@ -14,13 +15,16 @@ class CardPropertyEditScreen extends StatelessWidget {
|
|||||||
const CardPropertyEditScreen({
|
const CardPropertyEditScreen({
|
||||||
super.key,
|
super.key,
|
||||||
required this.cellContext,
|
required this.cellContext,
|
||||||
|
required this.fieldController,
|
||||||
});
|
});
|
||||||
|
|
||||||
static const routeName = '/CardPropertyEditScreen';
|
static const routeName = '/CardPropertyEditScreen';
|
||||||
static const argCellContext = 'cellContext';
|
static const argCellContext = 'cellContext';
|
||||||
|
static const argFieldController = 'fieldController';
|
||||||
static const argRowDetailBloc = 'rowDetailBloc';
|
static const argRowDetailBloc = 'rowDetailBloc';
|
||||||
|
|
||||||
final DatabaseCellContext cellContext;
|
final DatabaseCellContext cellContext;
|
||||||
|
final FieldController fieldController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -57,7 +61,8 @@ class CardPropertyEditScreen extends StatelessWidget {
|
|||||||
viewId: cellContext.viewId,
|
viewId: cellContext.viewId,
|
||||||
field: cellContext.fieldInfo.field,
|
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_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/mobile_field_type_option_editor.dart';
|
||||||
import 'package:appflowy/mobile/presentation/database/card/card_property_edit/widgets/property_title.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_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/application/field/type_option/type_option_context.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.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/protobuf.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pbenum.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -19,65 +18,63 @@ class MobileFieldEditor extends StatelessWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
required this.viewId,
|
required this.viewId,
|
||||||
required this.typeOptionLoader,
|
required this.typeOptionLoader,
|
||||||
this.isGroupingField = false,
|
required this.field,
|
||||||
this.fieldInfo,
|
required this.fieldController,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String viewId;
|
final String viewId;
|
||||||
final bool isGroupingField;
|
final FieldController fieldController;
|
||||||
final FieldTypeOptionLoader typeOptionLoader;
|
final FieldTypeOptionLoader typeOptionLoader;
|
||||||
final FieldInfo? fieldInfo;
|
final FieldPB field;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) {
|
create: (context) {
|
||||||
return FieldEditorBloc(
|
return FieldEditorBloc(
|
||||||
// group field is the field to be used to group cards in database view, it can not be deleted
|
viewId: viewId,
|
||||||
isGroupField: isGroupingField,
|
|
||||||
loader: typeOptionLoader,
|
loader: typeOptionLoader,
|
||||||
field: typeOptionLoader.field,
|
field: field,
|
||||||
|
fieldController: fieldController,
|
||||||
)..add(const FieldEditorEvent.initial());
|
)..add(const FieldEditorEvent.initial());
|
||||||
},
|
},
|
||||||
child: BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
child: BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
// for field type edit option
|
// for field type edit option
|
||||||
final dataController = context.read<FieldEditorBloc>().dataController;
|
final dataController =
|
||||||
|
context.read<FieldEditorBloc>().typeOptionController;
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// property name
|
// field name
|
||||||
// TODO(yijing): improve hint text
|
// TODO(yijing): improve hint text
|
||||||
PropertyTitle(LocaleKeys.settings_user_name.tr()),
|
PropertyTitle(LocaleKeys.settings_user_name.tr()),
|
||||||
BlocSelector<FieldEditorBloc, FieldEditorState, String>(
|
BlocSelector<FieldEditorBloc, FieldEditorState, String>(
|
||||||
selector: (state) {
|
selector: (state) => state.field.name,
|
||||||
return state.name;
|
builder: (context, fieldName) {
|
||||||
},
|
|
||||||
builder: (context, propertyName) {
|
|
||||||
return MobileFieldNameTextField(
|
return MobileFieldNameTextField(
|
||||||
text: propertyName,
|
text: fieldName,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
PropertyTitle(LocaleKeys.grid_field_visibility.tr()),
|
Expanded(
|
||||||
const Spacer(),
|
child:
|
||||||
|
PropertyTitle(LocaleKeys.grid_field_visibility.tr()),
|
||||||
|
),
|
||||||
VisibilitySwitch(
|
VisibilitySwitch(
|
||||||
isFieldHidden:
|
isFieldHidden: state.field.visibility ==
|
||||||
fieldInfo?.visibility == FieldVisibility.AlwaysHidden,
|
FieldVisibility.AlwaysHidden,
|
||||||
onChanged: () {
|
onChanged: () {
|
||||||
state.field.fold(
|
context.read<RowDetailBloc>().add(
|
||||||
() => Log.error('Can not hidden the field'),
|
RowDetailEvent.toggleFieldVisibility(
|
||||||
(field) => context.read<RowDetailBloc>().add(
|
state.field.id,
|
||||||
RowDetailEvent.toggleFieldVisibility(
|
|
||||||
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_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:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:dartz/dartz.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 'field_service.dart';
|
||||||
import 'type_option/type_option_context.dart';
|
import 'type_option/type_option_context.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
||||||
import 'type_option/type_option_data_controller.dart';
|
import 'type_option/type_option_data_controller.dart';
|
||||||
|
|
||||||
part 'field_editor_bloc.freezed.dart';
|
part 'field_editor_bloc.freezed.dart';
|
||||||
|
|
||||||
class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
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({
|
FieldEditorBloc({
|
||||||
required bool isGroupField,
|
required this.viewId,
|
||||||
required FieldPB field,
|
required this.field,
|
||||||
|
required this.fieldController,
|
||||||
required FieldTypeOptionLoader loader,
|
required FieldTypeOptionLoader loader,
|
||||||
}) : dataController = TypeOptionController(
|
}) : typeOptionController = TypeOptionController(
|
||||||
field: field,
|
field: field,
|
||||||
loader: loader,
|
loader: loader,
|
||||||
),
|
),
|
||||||
super(
|
_singleFieldListener = SingleFieldListener(fieldId: field.id),
|
||||||
FieldEditorState.initial(
|
fieldService = FieldBackendService(
|
||||||
loader.viewId,
|
viewId: viewId,
|
||||||
loader.field.name,
|
fieldId: field.id,
|
||||||
isGroupField,
|
),
|
||||||
),
|
fieldSettingsService = FieldSettingsBackendService(viewId: viewId),
|
||||||
) {
|
super(FieldEditorState(field: FieldInfo.initial(field))) {
|
||||||
on<FieldEditorEvent>(
|
on<FieldEditorEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.when(
|
await event.when(
|
||||||
initial: () async {
|
initial: () async {
|
||||||
dataController.addFieldListener((field) {
|
final fieldId = field.id;
|
||||||
|
typeOptionController.addFieldListener((field) {
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
add(FieldEditorEvent.didReceiveFieldChanged(field));
|
add(FieldEditorEvent.didReceiveFieldChanged(fieldId));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await dataController.reloadTypeOption();
|
_singleFieldListener.start(
|
||||||
add(FieldEditorEvent.didReceiveFieldChanged(dataController.field));
|
onFieldChanged: (field) {
|
||||||
},
|
if (!isClosed) {
|
||||||
updateName: (name) {
|
add(FieldEditorEvent.didReceiveFieldChanged(fieldId));
|
||||||
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();
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
await typeOptionController.reloadTypeOption();
|
||||||
|
add(FieldEditorEvent.didReceiveFieldChanged(fieldId));
|
||||||
},
|
},
|
||||||
switchToField: (FieldType fieldType) async {
|
didReceiveFieldChanged: (fieldId) async {
|
||||||
await dataController.switchToField(fieldType);
|
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
|
@freezed
|
||||||
class FieldEditorEvent with _$FieldEditorEvent {
|
class FieldEditorEvent with _$FieldEditorEvent {
|
||||||
const factory FieldEditorEvent.initial() = _InitialField;
|
const factory FieldEditorEvent.initial() = _InitialField;
|
||||||
const factory FieldEditorEvent.updateName(String name) = _UpdateName;
|
const factory FieldEditorEvent.didReceiveFieldChanged(String fieldId) =
|
||||||
const factory FieldEditorEvent.deleteField() = _DeleteField;
|
|
||||||
const factory FieldEditorEvent.switchToField(FieldType fieldType) =
|
|
||||||
_SwitchToField;
|
|
||||||
const factory FieldEditorEvent.didReceiveFieldChanged(FieldPB field) =
|
|
||||||
_DidReceiveFieldChanged;
|
_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
|
@freezed
|
||||||
class FieldEditorState with _$FieldEditorState {
|
class FieldEditorState with _$FieldEditorState {
|
||||||
const factory FieldEditorState({
|
const factory FieldEditorState({
|
||||||
required String viewId,
|
required FieldInfo field,
|
||||||
required String errorText,
|
|
||||||
required String name,
|
|
||||||
required Option<FieldPB> field,
|
|
||||||
required bool canDelete,
|
|
||||||
required bool isGroupField,
|
|
||||||
}) = _FieldEditorState;
|
}) = _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({
|
Future<Either<Unit, FlowyError>> updateField({
|
||||||
String? name,
|
String? name,
|
||||||
bool? frozen,
|
bool? frozen,
|
||||||
double? width,
|
|
||||||
}) {
|
}) {
|
||||||
final payload = FieldChangesetPB.create()
|
final payload = FieldChangesetPB.create()
|
||||||
..viewId = viewId
|
..viewId = viewId
|
||||||
@ -41,10 +40,6 @@ class FieldBackendService {
|
|||||||
payload.frozen = frozen;
|
payload.frozen = frozen;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (width != null) {
|
|
||||||
payload.width = width.toInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
return DatabaseEventUpdateField(payload).send();
|
return DatabaseEventUpdateField(payload).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/mobile/presentation/database/card/card.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:appflowy_board/appflowy_board.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
|
||||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||||
import 'package:flutter/material.dart' hide Card;
|
import 'package:flutter/material.dart' hide Card;
|
||||||
@ -326,13 +326,15 @@ class _BoardContentState extends State<BoardContent> {
|
|||||||
context.push(
|
context.push(
|
||||||
MobileCardDetailScreen.routeName,
|
MobileCardDetailScreen.routeName,
|
||||||
extra: {
|
extra: {
|
||||||
'rowController': dataController,
|
MobileCardDetailScreen.argRowController: dataController,
|
||||||
|
MobileCardDetailScreen.argFieldController: fieldController,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
FlowyOverlay.show(
|
FlowyOverlay.show(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => RowDetailPage(
|
builder: (_) => RowDetailPage(
|
||||||
|
fieldController: fieldController,
|
||||||
cellBuilder: GridCellBuilder(cellCache: dataController.cellCache),
|
cellBuilder: GridCellBuilder(cellCache: dataController.cellCache),
|
||||||
rowController: dataController,
|
rowController: dataController,
|
||||||
),
|
),
|
||||||
|
@ -421,6 +421,8 @@ class HiddenGroupPopupItemList extends StatelessWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return RowDetailPage(
|
return RowDetailPage(
|
||||||
|
fieldController:
|
||||||
|
context.read<BoardBloc>().fieldController,
|
||||||
cellBuilder: GridCellBuilder(
|
cellBuilder: GridCellBuilder(
|
||||||
cellCache: rowController.cellCache,
|
cellCache: rowController.cellCache,
|
||||||
),
|
),
|
||||||
|
@ -297,6 +297,7 @@ class _EventList extends StatelessWidget {
|
|||||||
final autoEdit =
|
final autoEdit =
|
||||||
editingEvent?.event?.eventId == events[index].eventId;
|
editingEvent?.event?.eventId == events[index].eventId;
|
||||||
return EventCard(
|
return EventCard(
|
||||||
|
fieldController: context.read<CalendarBloc>().fieldController,
|
||||||
event: events[index],
|
event: events[index],
|
||||||
viewId: viewId,
|
viewId: viewId,
|
||||||
rowCache: rowCache,
|
rowCache: rowCache,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.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/row/row_cache.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.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/card/card_cell_builder.dart';
|
import 'package:appflowy/plugins/database_view/widgets/card/card_cell_builder.dart';
|
||||||
@ -20,6 +21,7 @@ import '../application/calendar_bloc.dart';
|
|||||||
import 'calendar_event_editor.dart';
|
import 'calendar_event_editor.dart';
|
||||||
|
|
||||||
class EventCard extends StatefulWidget {
|
class EventCard extends StatefulWidget {
|
||||||
|
final FieldController fieldController;
|
||||||
final CalendarDayEvent event;
|
final CalendarDayEvent event;
|
||||||
final String viewId;
|
final String viewId;
|
||||||
final RowCache rowCache;
|
final RowCache rowCache;
|
||||||
@ -27,12 +29,13 @@ class EventCard extends StatefulWidget {
|
|||||||
final bool autoEdit;
|
final bool autoEdit;
|
||||||
|
|
||||||
const EventCard({
|
const EventCard({
|
||||||
|
super.key,
|
||||||
required this.event,
|
required this.event,
|
||||||
required this.viewId,
|
required this.viewId,
|
||||||
required this.rowCache,
|
required this.rowCache,
|
||||||
required this.constraints,
|
required this.constraints,
|
||||||
required this.autoEdit,
|
required this.autoEdit,
|
||||||
super.key,
|
required this.fieldController,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -164,6 +167,7 @@ class _EventCardState extends State<EventCard> {
|
|||||||
rowMeta: widget.event.event.rowMeta,
|
rowMeta: widget.event.event.rowMeta,
|
||||||
viewId: widget.viewId,
|
viewId: widget.viewId,
|
||||||
layoutSettings: settings,
|
layoutSettings: settings,
|
||||||
|
fieldController: widget.fieldController,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: DecoratedBox(
|
child: DecoratedBox(
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/cell/cell_service.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_cache.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/row/row_controller.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';
|
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 {
|
class CalendarEventEditor extends StatelessWidget {
|
||||||
final RowController rowController;
|
final RowController rowController;
|
||||||
|
final FieldController fieldController;
|
||||||
final CalendarLayoutSettingPB layoutSettings;
|
final CalendarLayoutSettingPB layoutSettings;
|
||||||
final GridCellBuilder cellBuilder;
|
final GridCellBuilder cellBuilder;
|
||||||
|
|
||||||
@ -28,6 +30,7 @@ class CalendarEventEditor extends StatelessWidget {
|
|||||||
required RowMetaPB rowMeta,
|
required RowMetaPB rowMeta,
|
||||||
required String viewId,
|
required String viewId,
|
||||||
required this.layoutSettings,
|
required this.layoutSettings,
|
||||||
|
required this.fieldController,
|
||||||
}) : rowController = RowController(
|
}) : rowController = RowController(
|
||||||
rowMeta: rowMeta,
|
rowMeta: rowMeta,
|
||||||
viewId: viewId,
|
viewId: viewId,
|
||||||
@ -45,7 +48,10 @@ class CalendarEventEditor extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
EventEditorControls(rowController: rowController),
|
EventEditorControls(
|
||||||
|
rowController: rowController,
|
||||||
|
fieldController: fieldController,
|
||||||
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: EventPropertyList(
|
child: EventPropertyList(
|
||||||
dateFieldId: layoutSettings.fieldId,
|
dateFieldId: layoutSettings.fieldId,
|
||||||
@ -60,9 +66,11 @@ class CalendarEventEditor extends StatelessWidget {
|
|||||||
|
|
||||||
class EventEditorControls extends StatelessWidget {
|
class EventEditorControls extends StatelessWidget {
|
||||||
final RowController rowController;
|
final RowController rowController;
|
||||||
|
final FieldController fieldController;
|
||||||
const EventEditorControls({
|
const EventEditorControls({
|
||||||
super.key,
|
super.key,
|
||||||
required this.rowController,
|
required this.rowController,
|
||||||
|
required this.fieldController,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -91,6 +99,7 @@ class EventEditorControls extends StatelessWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return RowDetailPage(
|
return RowDetailPage(
|
||||||
|
fieldController: fieldController,
|
||||||
cellBuilder: GridCellBuilder(
|
cellBuilder: GridCellBuilder(
|
||||||
cellCache: rowController.cellCache,
|
cellCache: rowController.cellCache,
|
||||||
),
|
),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/database_controller.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/calendar_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/calendar/application/unschedule_event_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';
|
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||||
@ -304,6 +305,7 @@ void showEventDetails({
|
|||||||
required CalendarEventPB event,
|
required CalendarEventPB event,
|
||||||
required String viewId,
|
required String viewId,
|
||||||
required RowCache rowCache,
|
required RowCache rowCache,
|
||||||
|
required FieldController fieldController,
|
||||||
}) {
|
}) {
|
||||||
final dataController = RowController(
|
final dataController = RowController(
|
||||||
rowMeta: event.rowMeta,
|
rowMeta: event.rowMeta,
|
||||||
@ -313,12 +315,13 @@ void showEventDetails({
|
|||||||
|
|
||||||
FlowyOverlay.show(
|
FlowyOverlay.show(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext overlayContext) {
|
||||||
return RowDetailPage(
|
return RowDetailPage(
|
||||||
cellBuilder: GridCellBuilder(
|
cellBuilder: GridCellBuilder(
|
||||||
cellCache: rowCache.cellCache,
|
cellCache: rowCache.cellCache,
|
||||||
),
|
),
|
||||||
rowController: dataController,
|
rowController: dataController,
|
||||||
|
fieldController: fieldController,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -387,8 +390,7 @@ class _UnscheduledEventsButtonState extends State<UnscheduledEventsButton> {
|
|||||||
),
|
),
|
||||||
popupBuilder: (context) {
|
popupBuilder: (context) {
|
||||||
return UnscheduleEventsList(
|
return UnscheduleEventsList(
|
||||||
viewId: widget.databaseController.viewId,
|
databaseController: widget.databaseController,
|
||||||
rowCache: widget.databaseController.rowCache,
|
|
||||||
unscheduleEvents: state.unscheduleEvents,
|
unscheduleEvents: state.unscheduleEvents,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -400,14 +402,12 @@ class _UnscheduledEventsButtonState extends State<UnscheduledEventsButton> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class UnscheduleEventsList extends StatelessWidget {
|
class UnscheduleEventsList extends StatelessWidget {
|
||||||
final String viewId;
|
final DatabaseController databaseController;
|
||||||
final RowCache rowCache;
|
|
||||||
final List<CalendarEventPB> unscheduleEvents;
|
final List<CalendarEventPB> unscheduleEvents;
|
||||||
const UnscheduleEventsList({
|
const UnscheduleEventsList({
|
||||||
required this.viewId,
|
|
||||||
required this.unscheduleEvents,
|
|
||||||
required this.rowCache,
|
|
||||||
super.key,
|
super.key,
|
||||||
|
required this.unscheduleEvents,
|
||||||
|
required this.databaseController,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -429,8 +429,9 @@ class UnscheduleEventsList extends StatelessWidget {
|
|||||||
showEventDetails(
|
showEventDetails(
|
||||||
context: context,
|
context: context,
|
||||||
event: e,
|
event: e,
|
||||||
viewId: viewId,
|
viewId: databaseController.viewId,
|
||||||
rowCache: rowCache,
|
rowCache: databaseController.rowCache,
|
||||||
|
fieldController: databaseController.fieldController,
|
||||||
);
|
);
|
||||||
PopoverContainer.of(context).close();
|
PopoverContainer.of(context).close();
|
||||||
},
|
},
|
||||||
|
@ -202,8 +202,6 @@ class _GridHeader extends StatelessWidget {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return GridHeaderSliverAdaptor(
|
return GridHeaderSliverAdaptor(
|
||||||
viewId: state.viewId,
|
viewId: state.viewId,
|
||||||
fieldController:
|
|
||||||
context.read<GridBloc>().databaseController.fieldController,
|
|
||||||
anchorScrollController: headerScrollController,
|
anchorScrollController: headerScrollController,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -360,6 +358,7 @@ class _GridRows extends StatelessWidget {
|
|||||||
return RowDetailPage(
|
return RowDetailPage(
|
||||||
cellBuilder: cellBuilder,
|
cellBuilder: cellBuilder,
|
||||||
rowController: dataController,
|
rowController: dataController,
|
||||||
|
fieldController: fieldController,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -11,7 +11,7 @@ class GridSize {
|
|||||||
static double get headerContainerPadding => 0 * scale;
|
static double get headerContainerPadding => 0 * scale;
|
||||||
static double get cellHPadding => 10 * scale;
|
static double get cellHPadding => 10 * scale;
|
||||||
static double get cellVPadding => 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 double get typeOptionSeparatorHeight => 4 * scale;
|
||||||
|
|
||||||
static EdgeInsets get headerContentInsets => EdgeInsets.symmetric(
|
static EdgeInsets get headerContentInsets => EdgeInsets.symmetric(
|
||||||
|
@ -184,8 +184,6 @@ class _GridHeader extends StatelessWidget {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return GridHeaderSliverAdaptor(
|
return GridHeaderSliverAdaptor(
|
||||||
viewId: state.viewId,
|
viewId: state.viewId,
|
||||||
fieldController:
|
|
||||||
context.read<GridBloc>().databaseController.fieldController,
|
|
||||||
anchorScrollController: headerScrollController,
|
anchorScrollController: headerScrollController,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -363,6 +361,7 @@ class _GridRows extends StatelessWidget {
|
|||||||
return RowDetailPage(
|
return RowDetailPage(
|
||||||
cellBuilder: cellBuilder,
|
cellBuilder: cellBuilder,
|
||||||
rowController: dataController,
|
rowController: dataController,
|
||||||
|
fieldController: fieldController,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
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_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/plugins/database_view/application/field/field_info.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.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 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import '../../layout/sizes.dart';
|
import '../../layout/sizes.dart';
|
||||||
import 'field_cell_action_sheet.dart';
|
import 'field_editor.dart';
|
||||||
import 'field_type_extension.dart';
|
import 'field_type_extension.dart';
|
||||||
|
|
||||||
class GridFieldCell extends StatefulWidget {
|
class GridFieldCell extends StatefulWidget {
|
||||||
final String viewId;
|
final String viewId;
|
||||||
|
final FieldController fieldController;
|
||||||
final FieldInfo fieldInfo;
|
final FieldInfo fieldInfo;
|
||||||
const GridFieldCell({
|
const GridFieldCell({
|
||||||
super.key,
|
super.key,
|
||||||
required this.viewId,
|
required this.viewId,
|
||||||
|
required this.fieldController,
|
||||||
required this.fieldInfo,
|
required this.fieldInfo,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -59,9 +62,11 @@ class _GridFieldCellState extends State<GridFieldCell> {
|
|||||||
direction: PopoverDirection.bottomWithLeftAligned,
|
direction: PopoverDirection.bottomWithLeftAligned,
|
||||||
controller: popoverController,
|
controller: popoverController,
|
||||||
popupBuilder: (BuildContext context) {
|
popupBuilder: (BuildContext context) {
|
||||||
return GridFieldCellActionSheet(
|
return FieldEditor(
|
||||||
viewId: widget.viewId,
|
viewId: widget.viewId,
|
||||||
fieldInfo: widget.fieldInfo,
|
fieldController: widget.fieldController,
|
||||||
|
field: widget.fieldInfo.field,
|
||||||
|
initialPage: FieldEditorPage.general,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: FieldCellButton(
|
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/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_editor_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_info.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/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:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.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:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import '../../layout/sizes.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
import 'field_type_option_editor.dart';
|
import 'field_type_option_editor.dart';
|
||||||
|
|
||||||
|
enum FieldEditorPage {
|
||||||
|
general,
|
||||||
|
details,
|
||||||
|
}
|
||||||
|
|
||||||
class FieldEditor extends StatefulWidget {
|
class FieldEditor extends StatefulWidget {
|
||||||
final String viewId;
|
final String viewId;
|
||||||
final bool isGroupingField;
|
final FieldController fieldController;
|
||||||
final Function(String)? onDeleted;
|
final FieldPB field;
|
||||||
final Function(String)? onToggleVisibility;
|
final FieldEditorPage initialPage;
|
||||||
final FieldTypeOptionLoader typeOptionLoader;
|
|
||||||
final FieldInfo? fieldInfo;
|
|
||||||
|
|
||||||
const FieldEditor({
|
const FieldEditor({
|
||||||
required this.viewId,
|
|
||||||
required this.typeOptionLoader,
|
|
||||||
this.fieldInfo,
|
|
||||||
this.isGroupingField = false,
|
|
||||||
this.onDeleted,
|
|
||||||
this.onToggleVisibility,
|
|
||||||
super.key,
|
super.key,
|
||||||
|
required this.viewId,
|
||||||
|
required this.field,
|
||||||
|
required this.fieldController,
|
||||||
|
this.initialPage = FieldEditorPage.details,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -40,6 +43,227 @@ class FieldEditor extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _FieldEditorState extends State<FieldEditor> {
|
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;
|
late PopoverMutex popoverMutex;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -56,71 +280,77 @@ class _FieldEditorState extends State<FieldEditor> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final bool requireSpace = widget.onDeleted != null ||
|
|
||||||
widget.onToggleVisibility != null ||
|
|
||||||
!widget.typeOptionLoader.field.isPrimary;
|
|
||||||
|
|
||||||
final List<Widget> children = [
|
final List<Widget> children = [
|
||||||
FieldNameTextField(popoverMutex: popoverMutex),
|
FieldNameTextField(
|
||||||
if (requireSpace) const VSpace(4),
|
popoverMutex: popoverMutex,
|
||||||
if (widget.onDeleted != null) _addDeleteFieldButton(),
|
padding: const EdgeInsets.fromLTRB(12.0, 4.0, 12.0, 0.0),
|
||||||
if (widget.onToggleVisibility != null) _addHideFieldButton(),
|
textEditingController: widget.textEditingController,
|
||||||
if (!widget.typeOptionLoader.field.isPrimary)
|
),
|
||||||
FieldTypeOptionCell(popoverMutex: popoverMutex),
|
const VSpace(8),
|
||||||
|
FieldTypeOptionCell(popoverMutex: popoverMutex),
|
||||||
|
const TypeOptionSeparator(),
|
||||||
|
_addFieldVisibilityToggleButton(),
|
||||||
|
_addDuplicateFieldButton(),
|
||||||
|
_addDeleteFieldButton(),
|
||||||
];
|
];
|
||||||
return BlocProvider(
|
|
||||||
create: (context) {
|
return Padding(
|
||||||
return FieldEditorBloc(
|
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
isGroupField: widget.isGroupingField,
|
child: Column(
|
||||||
loader: widget.typeOptionLoader,
|
mainAxisSize: MainAxisSize.min,
|
||||||
field: widget.typeOptionLoader.field,
|
children: children,
|
||||||
)..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),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _addDeleteFieldButton() {
|
Widget _addFieldVisibilityToggleButton() {
|
||||||
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
padding: const EdgeInsets.fromLTRB(8.0, 2.0, 8.0, 0),
|
||||||
child: DeleteFieldButton(
|
child: FieldActionCell(
|
||||||
|
viewId: widget.viewId,
|
||||||
|
fieldInfo: state.field,
|
||||||
|
action: FieldAction.toggleVisibility,
|
||||||
popoverMutex: popoverMutex,
|
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>(
|
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
if (state.field.isPrimary) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
padding: const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 0),
|
||||||
child: FieldVisibilityToggleButton(
|
child: FieldActionCell(
|
||||||
isFieldHidden:
|
viewId: widget.viewId,
|
||||||
widget.fieldInfo!.visibility == FieldVisibility.AlwaysHidden,
|
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,
|
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;
|
final PopoverMutex popoverMutex;
|
||||||
|
|
||||||
const FieldTypeOptionCell({
|
const FieldTypeOptionCell({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.popoverMutex,
|
required this.popoverMutex,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
||||||
buildWhen: (p, c) => p.field != c.field,
|
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return state.field.fold(
|
if (state.field.isPrimary) {
|
||||||
() => const SizedBox.shrink(),
|
return const SizedBox.shrink();
|
||||||
(fieldInfo) {
|
}
|
||||||
final dataController =
|
final dataController =
|
||||||
context.read<FieldEditorBloc>().dataController;
|
context.read<FieldEditorBloc>().typeOptionController;
|
||||||
return FieldTypeOptionEditor(
|
return Padding(
|
||||||
dataController: dataController,
|
padding: const EdgeInsets.only(bottom: 2.0),
|
||||||
popoverMutex: popoverMutex,
|
child: FieldTypeOptionEditor(
|
||||||
);
|
dataController: dataController,
|
||||||
},
|
popoverMutex: popoverMutex,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -158,18 +388,21 @@ class FieldTypeOptionCell extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class FieldNameTextField extends StatefulWidget {
|
class FieldNameTextField extends StatefulWidget {
|
||||||
final PopoverMutex popoverMutex;
|
final TextEditingController textEditingController;
|
||||||
|
final PopoverMutex? popoverMutex;
|
||||||
|
final EdgeInsets padding;
|
||||||
const FieldNameTextField({
|
const FieldNameTextField({
|
||||||
required this.popoverMutex,
|
super.key,
|
||||||
Key? key,
|
required this.textEditingController,
|
||||||
}) : super(key: key);
|
this.popoverMutex,
|
||||||
|
this.padding = EdgeInsets.zero,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FieldNameTextField> createState() => _FieldNameTextFieldState();
|
State<FieldNameTextField> createState() => _FieldNameTextFieldState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FieldNameTextFieldState extends State<FieldNameTextField> {
|
class _FieldNameTextFieldState extends State<FieldNameTextField> {
|
||||||
final textController = TextEditingController();
|
|
||||||
FocusNode focusNode = FocusNode();
|
FocusNode focusNode = FocusNode();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -178,11 +411,11 @@ class _FieldNameTextFieldState extends State<FieldNameTextField> {
|
|||||||
|
|
||||||
focusNode.addListener(() {
|
focusNode.addListener(() {
|
||||||
if (focusNode.hasFocus) {
|
if (focusNode.hasFocus) {
|
||||||
widget.popoverMutex.close();
|
widget.popoverMutex?.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
widget.popoverMutex.listenOnPopoverChanged(() {
|
widget.popoverMutex?.listenOnPopoverChanged(() {
|
||||||
if (focusNode.hasFocus) {
|
if (focusNode.hasFocus) {
|
||||||
focusNode.unfocus();
|
focusNode.unfocus();
|
||||||
}
|
}
|
||||||
@ -191,24 +424,18 @@ class _FieldNameTextFieldState extends State<FieldNameTextField> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
return Padding(
|
||||||
builder: (context, state) {
|
padding: widget.padding,
|
||||||
return Padding(
|
child: FlowyTextField(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
focusNode: focusNode,
|
||||||
child: FlowyTextField(
|
controller: widget.textEditingController,
|
||||||
focusNode: focusNode,
|
onSubmitted: (_) => PopoverContainer.of(context).close(),
|
||||||
controller: textController,
|
onChanged: (newName) {
|
||||||
onSubmitted: (String _) => PopoverContainer.of(context).close(),
|
context
|
||||||
text: state.name,
|
.read<FieldEditorBloc>()
|
||||||
errorText: state.errorText.isEmpty ? null : state.errorText,
|
.add(FieldEditorEvent.renameField(newName));
|
||||||
onChanged: (newName) {
|
},
|
||||||
context
|
),
|
||||||
.read<FieldEditorBloc>()
|
|
||||||
.add(FieldEditorEvent.updateName(newName));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,80 +443,10 @@ class _FieldNameTextFieldState extends State<FieldNameTextField> {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
focusNode.removeListener(() {
|
focusNode.removeListener(() {
|
||||||
if (focusNode.hasFocus) {
|
if (focusNode.hasFocus) {
|
||||||
widget.popoverMutex.close();
|
widget.popoverMutex?.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
focusNode.dispose();
|
focusNode.dispose();
|
||||||
super.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/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/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/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:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:dartz/dartz.dart' show Either;
|
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:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import '../../layout/sizes.dart';
|
|
||||||
import 'field_type_extension.dart';
|
import 'field_type_extension.dart';
|
||||||
import 'field_type_list.dart';
|
import 'field_type_list.dart';
|
||||||
import 'type_option/builder.dart';
|
import 'type_option/builder.dart';
|
||||||
@ -27,19 +28,17 @@ class FieldTypeOptionEditor extends StatelessWidget {
|
|||||||
final PopoverMutex popoverMutex;
|
final PopoverMutex popoverMutex;
|
||||||
|
|
||||||
const FieldTypeOptionEditor({
|
const FieldTypeOptionEditor({
|
||||||
|
super.key,
|
||||||
required TypeOptionController dataController,
|
required TypeOptionController dataController,
|
||||||
required this.popoverMutex,
|
required this.popoverMutex,
|
||||||
Key? key,
|
}) : _dataController = dataController;
|
||||||
}) : _dataController = dataController,
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) {
|
create: (context) {
|
||||||
final bloc = FieldTypeOptionEditBloc(_dataController);
|
return FieldTypeOptionEditBloc(_dataController)
|
||||||
bloc.add(const FieldTypeOptionEditEvent.initial());
|
..add(const FieldTypeOptionEditEvent.initial());
|
||||||
return bloc;
|
|
||||||
},
|
},
|
||||||
child: BlocBuilder<FieldTypeOptionEditBloc, FieldTypeOptionEditState>(
|
child: BlocBuilder<FieldTypeOptionEditBloc, FieldTypeOptionEditState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
@ -53,8 +52,8 @@ class FieldTypeOptionEditor extends StatelessWidget {
|
|||||||
if (typeOptionWidget != null) typeOptionWidget,
|
if (typeOptionWidget != null) typeOptionWidget,
|
||||||
];
|
];
|
||||||
|
|
||||||
return ListView(
|
return Column(
|
||||||
shrinkWrap: true,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: children,
|
children: children,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -74,22 +73,30 @@ class FieldTypeOptionEditor extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SwitchFieldButton extends StatelessWidget {
|
class SwitchFieldButton extends StatefulWidget {
|
||||||
final PopoverMutex popoverMutex;
|
final PopoverMutex popoverMutex;
|
||||||
const SwitchFieldButton({
|
const SwitchFieldButton({
|
||||||
|
super.key,
|
||||||
required this.popoverMutex,
|
required this.popoverMutex,
|
||||||
Key? key,
|
});
|
||||||
}) : super(key: key);
|
|
||||||
|
@override
|
||||||
|
State<SwitchFieldButton> createState() => _SwitchFieldButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SwitchFieldButtonState extends State<SwitchFieldButton> {
|
||||||
|
final PopoverController _popoverController = PopoverController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final widget = AppFlowyPopover(
|
final child = AppFlowyPopover(
|
||||||
constraints: BoxConstraints.loose(const Size(460, 540)),
|
constraints: BoxConstraints.loose(const Size(460, 540)),
|
||||||
asBarrier: true,
|
triggerActions: PopoverTriggerFlags.hover,
|
||||||
triggerActions: PopoverTriggerFlags.click,
|
mutex: widget.popoverMutex,
|
||||||
mutex: popoverMutex,
|
controller: _popoverController,
|
||||||
offset: const Offset(8, 0),
|
offset: const Offset(8, 0),
|
||||||
popupBuilder: (popOverContext) {
|
margin: const EdgeInsets.all(8),
|
||||||
|
popupBuilder: (BuildContext popoverContext) {
|
||||||
return FieldTypeList(
|
return FieldTypeList(
|
||||||
onSelectField: (newFieldType) {
|
onSelectField: (newFieldType) {
|
||||||
context
|
context
|
||||||
@ -99,20 +106,21 @@ class SwitchFieldButton extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
child: _buildMoreButton(context),
|
child: _buildMoreButton(context),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: GridSize.popoverItemHeight,
|
height: GridSize.popoverItemHeight,
|
||||||
child: widget,
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildMoreButton(BuildContext context) {
|
Widget _buildMoreButton(BuildContext context) {
|
||||||
final bloc = context.read<FieldTypeOptionEditBloc>();
|
final bloc = context.read<FieldTypeOptionEditBloc>();
|
||||||
return FlowyButton(
|
return FlowyButton(
|
||||||
|
onTap: () => _popoverController.show(),
|
||||||
text: FlowyText.medium(
|
text: FlowyText.medium(
|
||||||
bloc.state.field.fieldType.title(),
|
bloc.state.field.fieldType.title(),
|
||||||
),
|
),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.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/application/grid_header_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/mobile_field_cell.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/mobile_field_cell.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
@ -22,15 +22,13 @@ import 'field_cell.dart';
|
|||||||
|
|
||||||
class GridHeaderSliverAdaptor extends StatefulWidget {
|
class GridHeaderSliverAdaptor extends StatefulWidget {
|
||||||
final String viewId;
|
final String viewId;
|
||||||
final FieldController fieldController;
|
|
||||||
final ScrollController anchorScrollController;
|
final ScrollController anchorScrollController;
|
||||||
|
|
||||||
const GridHeaderSliverAdaptor({
|
const GridHeaderSliverAdaptor({
|
||||||
required this.viewId,
|
required this.viewId,
|
||||||
required this.fieldController,
|
|
||||||
required this.anchorScrollController,
|
required this.anchorScrollController,
|
||||||
Key? key,
|
super.key,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<GridHeaderSliverAdaptor> createState() =>
|
State<GridHeaderSliverAdaptor> createState() =>
|
||||||
@ -40,11 +38,13 @@ class GridHeaderSliverAdaptor extends StatefulWidget {
|
|||||||
class _GridHeaderSliverAdaptorState extends State<GridHeaderSliverAdaptor> {
|
class _GridHeaderSliverAdaptorState extends State<GridHeaderSliverAdaptor> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final fieldController =
|
||||||
|
context.read<GridBloc>().databaseController.fieldController;
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) {
|
create: (context) {
|
||||||
return GridHeaderBloc(
|
return GridHeaderBloc(
|
||||||
viewId: widget.viewId,
|
viewId: widget.viewId,
|
||||||
fieldController: widget.fieldController,
|
fieldController: fieldController,
|
||||||
)..add(const GridHeaderEvent.initial());
|
)..add(const GridHeaderEvent.initial());
|
||||||
},
|
},
|
||||||
child: BlocBuilder<GridHeaderBloc, GridHeaderState>(
|
child: BlocBuilder<GridHeaderBloc, GridHeaderState>(
|
||||||
@ -54,7 +54,10 @@ class _GridHeaderSliverAdaptorState extends State<GridHeaderSliverAdaptor> {
|
|||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
controller: widget.anchorScrollController,
|
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 {
|
class _GridHeader extends StatefulWidget {
|
||||||
final String viewId;
|
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
|
@override
|
||||||
State<_GridHeader> createState() => _GridHeaderState();
|
State<_GridHeader> createState() => _GridHeaderState();
|
||||||
@ -98,6 +102,7 @@ class _GridHeaderState extends State<_GridHeader> {
|
|||||||
key: _getKeyById(fieldInfo.id),
|
key: _getKeyById(fieldInfo.id),
|
||||||
viewId: widget.viewId,
|
viewId: widget.viewId,
|
||||||
fieldInfo: fieldInfo,
|
fieldInfo: fieldInfo,
|
||||||
|
fieldController: widget.fieldController,
|
||||||
)
|
)
|
||||||
: MobileFieldButton(
|
: MobileFieldButton(
|
||||||
key: _getKeyById(fieldInfo.id),
|
key: _getKeyById(fieldInfo.id),
|
||||||
@ -159,8 +164,9 @@ class _CellLeading extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _CellTrailing extends StatelessWidget {
|
class _CellTrailing extends StatelessWidget {
|
||||||
|
const _CellTrailing({required this.viewId});
|
||||||
|
|
||||||
final String viewId;
|
final String viewId;
|
||||||
const _CellTrailing({required this.viewId, Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -179,7 +185,10 @@ class _CellTrailing extends StatelessWidget {
|
|||||||
|
|
||||||
class CreateFieldButton extends StatefulWidget {
|
class CreateFieldButton extends StatefulWidget {
|
||||||
final String viewId;
|
final String viewId;
|
||||||
const CreateFieldButton({required this.viewId, Key? key}) : super(key: key);
|
const CreateFieldButton({
|
||||||
|
super.key,
|
||||||
|
required this.viewId,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CreateFieldButton> createState() => _CreateFieldButtonState();
|
State<CreateFieldButton> createState() => _CreateFieldButtonState();
|
||||||
@ -191,6 +200,8 @@ class _CreateFieldButtonState extends State<CreateFieldButton> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final fieldController =
|
||||||
|
context.read<GridBloc>().databaseController.fieldController;
|
||||||
return AppFlowyPopover(
|
return AppFlowyPopover(
|
||||||
controller: popoverController,
|
controller: popoverController,
|
||||||
direction: PopoverDirection.bottomWithRightAligned,
|
direction: PopoverDirection.bottomWithRightAligned,
|
||||||
@ -222,49 +233,13 @@ class _CreateFieldButtonState extends State<CreateFieldButton> {
|
|||||||
},
|
},
|
||||||
leftIcon: const FlowySvg(FlowySvgs.add_s),
|
leftIcon: const FlowySvg(FlowySvgs.add_s),
|
||||||
),
|
),
|
||||||
popupBuilder: (BuildContext popover) {
|
popupBuilder: (BuildContext popoverContext) {
|
||||||
return FieldEditor(
|
return FieldEditor(
|
||||||
viewId: widget.viewId,
|
viewId: widget.viewId,
|
||||||
typeOptionLoader: FieldTypeOptionLoader(
|
fieldController: fieldController,
|
||||||
viewId: widget.viewId,
|
field: typeOption.field_2,
|
||||||
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/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/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/theme_extension.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import 'field_type_extension.dart';
|
import 'field_type_extension.dart';
|
||||||
|
|
||||||
@ -29,6 +33,8 @@ class MobileFieldButton extends StatelessWidget {
|
|||||||
color: Theme.of(context).dividerColor,
|
color: Theme.of(context).dividerColor,
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
);
|
);
|
||||||
|
final fieldController =
|
||||||
|
context.read<GridBloc>().databaseController.fieldController;
|
||||||
return Container(
|
return Container(
|
||||||
width: field.fieldSettings!.width.toDouble(),
|
width: field.fieldSettings!.width.toDouble(),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@ -36,7 +42,14 @@ class MobileFieldButton extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
debugPrint("gimme the bottom drawer");
|
showMobileBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => MobileDBBottomSheetFieldEditor(
|
||||||
|
viewId: viewId,
|
||||||
|
field: field.field,
|
||||||
|
fieldController: fieldController,
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
|
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
|
||||||
|
@ -42,11 +42,12 @@ class NumberTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
|
|||||||
class NumberTypeOptionWidget extends TypeOptionWidget {
|
class NumberTypeOptionWidget extends TypeOptionWidget {
|
||||||
final NumberTypeOptionContext typeOptionContext;
|
final NumberTypeOptionContext typeOptionContext;
|
||||||
final PopoverMutex popoverMutex;
|
final PopoverMutex popoverMutex;
|
||||||
|
|
||||||
const NumberTypeOptionWidget({
|
const NumberTypeOptionWidget({
|
||||||
|
super.key,
|
||||||
required this.typeOptionContext,
|
required this.typeOptionContext,
|
||||||
required this.popoverMutex,
|
required this.popoverMutex,
|
||||||
Key? key,
|
});
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -60,7 +61,6 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
|
|||||||
final selectNumUnitButton = SizedBox(
|
final selectNumUnitButton = SizedBox(
|
||||||
height: GridSize.popoverItemHeight,
|
height: GridSize.popoverItemHeight,
|
||||||
child: FlowyButton(
|
child: FlowyButton(
|
||||||
margin: GridSize.typeOptionContentInsets,
|
|
||||||
rightIcon: const FlowySvg(FlowySvgs.more_s),
|
rightIcon: const FlowySvg(FlowySvgs.more_s),
|
||||||
text: FlowyText.regular(
|
text: FlowyText.regular(
|
||||||
state.typeOption.format.title(),
|
state.typeOption.format.title(),
|
||||||
@ -72,13 +72,15 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
|
|||||||
padding: const EdgeInsets.only(left: 6),
|
padding: const EdgeInsets.only(left: 6),
|
||||||
height: GridSize.popoverItemHeight,
|
height: GridSize.popoverItemHeight,
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: FlowyText.medium(
|
child: FlowyText.regular(
|
||||||
color: Theme.of(context).colorScheme.outline,
|
|
||||||
LocaleKeys.grid_field_numberFormat.tr(),
|
LocaleKeys.grid_field_numberFormat.tr(),
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
fontSize: 11,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -87,7 +89,7 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
|
|||||||
mutex: popoverMutex,
|
mutex: popoverMutex,
|
||||||
triggerActions:
|
triggerActions:
|
||||||
PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
|
PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
|
||||||
offset: const Offset(8, 0),
|
offset: const Offset(16, 0),
|
||||||
constraints: BoxConstraints.loose(const Size(460, 440)),
|
constraints: BoxConstraints.loose(const Size(460, 440)),
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
child: selectNumUnitButton,
|
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_backend/protobuf/flowy-database2/select_option.pb.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.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:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@ -40,23 +39,21 @@ class SelectOptionTypeOptionWidget extends StatelessWidget {
|
|||||||
BlocBuilder<SelectOptionTypeOptionBloc, SelectOptionTypeOptionState>(
|
BlocBuilder<SelectOptionTypeOptionBloc, SelectOptionTypeOptionState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final List<Widget> children = [
|
final List<Widget> children = [
|
||||||
const TypeOptionSeparator(),
|
const TypeOptionSeparator(spacing: 8),
|
||||||
const OptionTitle(),
|
const _OptionTitle(),
|
||||||
if (state.isEditingOption)
|
const VSpace(4),
|
||||||
|
if (state.isEditingOption) ...[
|
||||||
CreateOptionTextField(popoverMutex: popoverMutex),
|
CreateOptionTextField(popoverMutex: popoverMutex),
|
||||||
if (state.options.isNotEmpty && state.isEditingOption)
|
const VSpace(4),
|
||||||
const VSpace(10),
|
] else
|
||||||
if (state.options.isEmpty && !state.isEditingOption)
|
|
||||||
const _AddOptionButton(),
|
const _AddOptionButton(),
|
||||||
|
const VSpace(4),
|
||||||
_OptionList(popoverMutex: popoverMutex),
|
_OptionList(popoverMutex: popoverMutex),
|
||||||
];
|
];
|
||||||
|
|
||||||
return ListView.builder(
|
return Column(
|
||||||
shrinkWrap: true,
|
mainAxisSize: MainAxisSize.min,
|
||||||
itemCount: children.length,
|
children: children,
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return children[index];
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -64,31 +61,22 @@ class SelectOptionTypeOptionWidget extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class OptionTitle extends StatelessWidget {
|
class _OptionTitle extends StatelessWidget {
|
||||||
const OptionTitle({Key? key}) : super(key: key);
|
const _OptionTitle();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<SelectOptionTypeOptionBloc, SelectOptionTypeOptionState>(
|
return BlocBuilder<SelectOptionTypeOptionBloc, SelectOptionTypeOptionState>(
|
||||||
builder: (context, state) {
|
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(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: SizedBox(
|
child: Align(
|
||||||
height: GridSize.popoverItemHeight,
|
alignment: AlignmentDirectional.centerStart,
|
||||||
child: Row(children: children),
|
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 {
|
class _OptionList extends StatelessWidget {
|
||||||
final PopoverMutex? popoverMutex;
|
final PopoverMutex? popoverMutex;
|
||||||
const _OptionList({Key? key, this.popoverMutex}) : super(key: key);
|
const _OptionList({Key? key, this.popoverMutex}) : super(key: key);
|
||||||
@ -140,7 +105,6 @@ class _OptionList extends StatelessWidget {
|
|||||||
|
|
||||||
return ListView.separated(
|
return ListView.separated(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
controller: ScrollController(),
|
|
||||||
separatorBuilder: (context, index) {
|
separatorBuilder: (context, index) {
|
||||||
return VSpace(GridSize.typeOptionSeparatorHeight);
|
return VSpace(GridSize.typeOptionSeparatorHeight);
|
||||||
},
|
},
|
||||||
@ -187,7 +151,7 @@ class _OptionCellState extends State<_OptionCell> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final child = SizedBox(
|
final child = SizedBox(
|
||||||
height: GridSize.popoverItemHeight,
|
height: 28,
|
||||||
child: SelectOptionTagCell(
|
child: SelectOptionTagCell(
|
||||||
option: widget.option,
|
option: widget.option,
|
||||||
onSelected: (SelectOptionPB pb) {
|
onSelected: (SelectOptionPB pb) {
|
||||||
@ -212,7 +176,7 @@ class _OptionCellState extends State<_OptionCell> {
|
|||||||
asBarrier: true,
|
asBarrier: true,
|
||||||
constraints: BoxConstraints.loose(const Size(460, 470)),
|
constraints: BoxConstraints.loose(const Size(460, 470)),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
popupBuilder: (BuildContext popoverContext) {
|
popupBuilder: (BuildContext popoverContext) {
|
||||||
@ -243,24 +207,19 @@ class _AddOptionButton extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: GridSize.popoverItemHeight,
|
height: GridSize.popoverItemHeight,
|
||||||
child: FlowyButton(
|
child: FlowyButton(
|
||||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
|
||||||
text: FlowyText.medium(
|
text: FlowyText.medium(
|
||||||
LocaleKeys.grid_field_addSelectOption.tr(),
|
LocaleKeys.grid_field_addSelectOption.tr(),
|
||||||
color: AFThemeExtension.of(context).textColor,
|
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context
|
context
|
||||||
.read<SelectOptionTypeOptionBloc>()
|
.read<SelectOptionTypeOptionBloc>()
|
||||||
.add(const SelectOptionTypeOptionEvent.addingOption());
|
.add(const SelectOptionTypeOptionEvent.addingOption());
|
||||||
},
|
},
|
||||||
leftIcon: FlowySvg(
|
leftIcon: const FlowySvg(FlowySvgs.add_s),
|
||||||
FlowySvgs.add_s,
|
|
||||||
color: Theme.of(context).iconTheme.color,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -303,7 +262,7 @@ class _CreateOptionTextFieldState extends State<CreateOptionTextField> {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final text = state.newOptionName.foldRight("", (a, previous) => a);
|
final text = state.newOptionName.foldRight("", (a, previous) => a);
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
padding: const EdgeInsets.symmetric(horizontal: 14.0),
|
||||||
child: FlowyTextField(
|
child: FlowyTextField(
|
||||||
autoClearWhenDone: true,
|
autoClearWhenDone: true,
|
||||||
text: text,
|
text: text,
|
||||||
|
@ -62,20 +62,14 @@ class SelectOptionTypeOptionEditor extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const VSpace(10),
|
const VSpace(10),
|
||||||
const _DeleteTag(),
|
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(
|
return SizedBox(
|
||||||
width: 180,
|
width: 180,
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
@ -188,7 +182,6 @@ class SelectOptionColorList extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
ListView.separated(
|
ListView.separated(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
controller: ScrollController(),
|
|
||||||
separatorBuilder: (context, index) {
|
separatorBuilder: (context, index) {
|
||||||
return VSpace(GridSize.typeOptionSeparatorHeight);
|
return VSpace(GridSize.typeOptionSeparatorHeight);
|
||||||
},
|
},
|
||||||
|
@ -44,7 +44,6 @@ class SingleSelectTypeOptionWidget extends TypeOptionWidget {
|
|||||||
},
|
},
|
||||||
popoverMutex: popoverMutex,
|
popoverMutex: popoverMutex,
|
||||||
typeOptionAction: selectOptionAction,
|
typeOptionAction: selectOptionAction,
|
||||||
// key: ValueKey(state.typeOption.hashCode),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ class SelectOptionTag extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
EdgeInsets padding =
|
EdgeInsets padding =
|
||||||
const EdgeInsets.symmetric(vertical: 2, horizontal: 8.0);
|
const EdgeInsets.symmetric(vertical: 1, horizontal: 8.0);
|
||||||
if (onRemove != null) {
|
if (onRemove != null) {
|
||||||
padding = padding.copyWith(right: 2.0);
|
padding = padding.copyWith(right: 2.0);
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,6 @@ class _OptionList extends StatelessWidget {
|
|||||||
|
|
||||||
return ListView.separated(
|
return ListView.separated(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
controller: ScrollController(),
|
|
||||||
itemCount: cells.length,
|
itemCount: cells.length,
|
||||||
separatorBuilder: (_, __) =>
|
separatorBuilder: (_, __) =>
|
||||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||||
@ -302,7 +301,7 @@ class _SelectOptionCellState extends State<_SelectOptionCell> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final child = SizedBox(
|
final child = SizedBox(
|
||||||
height: GridSize.popoverItemHeight,
|
height: 28,
|
||||||
child: SelectOptionTagCell(
|
child: SelectOptionTagCell(
|
||||||
option: widget.option,
|
option: widget.option,
|
||||||
onSelected: (option) {
|
onSelected: (option) {
|
||||||
@ -328,7 +327,6 @@ class _SelectOptionCellState extends State<_SelectOptionCell> {
|
|||||||
FlowyIconButton(
|
FlowyIconButton(
|
||||||
onPressed: () => _popoverController.show(),
|
onPressed: () => _popoverController.show(),
|
||||||
iconPadding: const EdgeInsets.symmetric(horizontal: 6.0),
|
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,
|
hoverColor: Colors.transparent,
|
||||||
icon: FlowySvg(
|
icon: FlowySvg(
|
||||||
FlowySvgs.details_s,
|
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/application/row/row_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.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';
|
import 'package:appflowy/plugins/database_view/widgets/row/row_document.dart';
|
||||||
@ -11,11 +12,13 @@ import 'row_banner.dart';
|
|||||||
import 'row_property.dart';
|
import 'row_property.dart';
|
||||||
|
|
||||||
class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate {
|
class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate {
|
||||||
|
final FieldController fieldController;
|
||||||
final RowController rowController;
|
final RowController rowController;
|
||||||
final GridCellBuilder cellBuilder;
|
final GridCellBuilder cellBuilder;
|
||||||
|
|
||||||
const RowDetailPage({
|
const RowDetailPage({
|
||||||
super.key,
|
super.key,
|
||||||
|
required this.fieldController,
|
||||||
required this.rowController,
|
required this.rowController,
|
||||||
required this.cellBuilder,
|
required this.cellBuilder,
|
||||||
});
|
});
|
||||||
@ -56,6 +59,7 @@ class _RowDetailPageState extends State<RowDetailPage> {
|
|||||||
child: RowPropertyList(
|
child: RowPropertyList(
|
||||||
cellBuilder: widget.cellBuilder,
|
cellBuilder: widget.cellBuilder,
|
||||||
viewId: widget.rowController.viewId,
|
viewId: widget.rowController.viewId,
|
||||||
|
fieldController: widget.fieldController,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const VSpace(20),
|
const VSpace(20),
|
||||||
|
@ -3,14 +3,13 @@ import 'dart:io';
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/cell/cell_service.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/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/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_cell.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_editor.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.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/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/log.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
@ -28,12 +27,15 @@ import 'cell_builder.dart';
|
|||||||
/// [RowDetailPage].
|
/// [RowDetailPage].
|
||||||
class RowPropertyList extends StatelessWidget {
|
class RowPropertyList extends StatelessWidget {
|
||||||
final String viewId;
|
final String viewId;
|
||||||
|
final FieldController fieldController;
|
||||||
final GridCellBuilder cellBuilder;
|
final GridCellBuilder cellBuilder;
|
||||||
|
|
||||||
const RowPropertyList({
|
const RowPropertyList({
|
||||||
|
super.key,
|
||||||
required this.viewId,
|
required this.viewId,
|
||||||
|
required this.fieldController,
|
||||||
required this.cellBuilder,
|
required this.cellBuilder,
|
||||||
Key? key,
|
});
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -46,6 +48,7 @@ class RowPropertyList extends StatelessWidget {
|
|||||||
key: ValueKey('row_detail_${cell.fieldId}'),
|
key: ValueKey('row_detail_${cell.fieldId}'),
|
||||||
cellContext: cell,
|
cellContext: cell,
|
||||||
cellBuilder: cellBuilder,
|
cellBuilder: cellBuilder,
|
||||||
|
fieldController: fieldController,
|
||||||
index: index,
|
index: index,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -99,7 +102,10 @@ class RowPropertyList extends StatelessWidget {
|
|||||||
padding: EdgeInsets.only(bottom: 4.0),
|
padding: EdgeInsets.only(bottom: 4.0),
|
||||||
child: ToggleHiddenFieldsVisibilityButton(),
|
child: ToggleHiddenFieldsVisibilityButton(),
|
||||||
),
|
),
|
||||||
CreateRowFieldButton(viewId: viewId),
|
CreateRowFieldButton(
|
||||||
|
viewId: viewId,
|
||||||
|
fieldController: fieldController,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -113,14 +119,16 @@ class RowPropertyList extends StatelessWidget {
|
|||||||
class _PropertyCell extends StatefulWidget {
|
class _PropertyCell extends StatefulWidget {
|
||||||
final DatabaseCellContext cellContext;
|
final DatabaseCellContext cellContext;
|
||||||
final GridCellBuilder cellBuilder;
|
final GridCellBuilder cellBuilder;
|
||||||
|
final FieldController fieldController;
|
||||||
final int index;
|
final int index;
|
||||||
|
|
||||||
const _PropertyCell({
|
const _PropertyCell({
|
||||||
|
super.key,
|
||||||
required this.cellContext,
|
required this.cellContext,
|
||||||
required this.cellBuilder,
|
required this.cellBuilder,
|
||||||
Key? key,
|
required this.fieldController,
|
||||||
required this.index,
|
required this.index,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => _PropertyCellState();
|
State<StatefulWidget> createState() => _PropertyCellState();
|
||||||
@ -219,30 +227,8 @@ class _PropertyCellState extends State<_PropertyCell> {
|
|||||||
Widget buildFieldEditor() {
|
Widget buildFieldEditor() {
|
||||||
return FieldEditor(
|
return FieldEditor(
|
||||||
viewId: widget.cellContext.viewId,
|
viewId: widget.cellContext.viewId,
|
||||||
fieldInfo: widget.cellContext.fieldInfo,
|
field: widget.cellContext.fieldInfo.field,
|
||||||
isGroupingField: widget.cellContext.fieldInfo.isGroupField,
|
fieldController: widget.fieldController,
|
||||||
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);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -342,8 +328,13 @@ class ToggleHiddenFieldsVisibilityButton extends StatelessWidget {
|
|||||||
|
|
||||||
class CreateRowFieldButton extends StatefulWidget {
|
class CreateRowFieldButton extends StatefulWidget {
|
||||||
final String viewId;
|
final String viewId;
|
||||||
|
final FieldController fieldController;
|
||||||
|
|
||||||
const CreateRowFieldButton({required this.viewId, super.key});
|
const CreateRowFieldButton({
|
||||||
|
super.key,
|
||||||
|
required this.viewId,
|
||||||
|
required this.fieldController,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CreateRowFieldButton> createState() => _CreateRowFieldButtonState();
|
State<CreateRowFieldButton> createState() => _CreateRowFieldButtonState();
|
||||||
@ -394,24 +385,11 @@ class _CreateRowFieldButtonState extends State<CreateRowFieldButton> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
popupBuilder: (BuildContext popOverContext) {
|
popupBuilder: (BuildContext popoverContext) {
|
||||||
return FieldEditor(
|
return FieldEditor(
|
||||||
viewId: widget.viewId,
|
viewId: widget.viewId,
|
||||||
typeOptionLoader: FieldTypeOptionLoader(
|
field: typeOption.field_2,
|
||||||
viewId: widget.viewId,
|
fieldController: widget.fieldController,
|
||||||
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);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -3,22 +3,19 @@ import 'dart:io';
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
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_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_info.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/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/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:styled_widget/styled_widget.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 {
|
class DatabasePropertyList extends StatefulWidget {
|
||||||
final String viewId;
|
final String viewId;
|
||||||
final FieldController fieldController;
|
final FieldController fieldController;
|
||||||
@ -49,6 +46,7 @@ class _DatabasePropertyListState extends State<DatabasePropertyList> {
|
|||||||
return DatabasePropertyCell(
|
return DatabasePropertyCell(
|
||||||
key: ValueKey(field.id),
|
key: ValueKey(field.id),
|
||||||
viewId: widget.viewId,
|
viewId: widget.viewId,
|
||||||
|
fieldController: widget.fieldController,
|
||||||
fieldInfo: field,
|
fieldInfo: field,
|
||||||
popoverMutex: _popoverMutex,
|
popoverMutex: _popoverMutex,
|
||||||
index: index,
|
index: index,
|
||||||
@ -93,6 +91,7 @@ class _DatabasePropertyListState extends State<DatabasePropertyList> {
|
|||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
class DatabasePropertyCell extends StatefulWidget {
|
class DatabasePropertyCell extends StatefulWidget {
|
||||||
|
final FieldController fieldController;
|
||||||
final FieldInfo fieldInfo;
|
final FieldInfo fieldInfo;
|
||||||
final String viewId;
|
final String viewId;
|
||||||
final PopoverMutex popoverMutex;
|
final PopoverMutex popoverMutex;
|
||||||
@ -104,6 +103,7 @@ class DatabasePropertyCell extends StatefulWidget {
|
|||||||
required this.viewId,
|
required this.viewId,
|
||||||
required this.popoverMutex,
|
required this.popoverMutex,
|
||||||
required this.index,
|
required this.index,
|
||||||
|
required this.fieldController,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -120,6 +120,7 @@ class _DatabasePropertyCellState extends State<DatabasePropertyCell> {
|
|||||||
visiblity != null && visiblity != FieldVisibility.AlwaysHidden
|
visiblity != null && visiblity != FieldVisibility.AlwaysHidden
|
||||||
? FlowySvgs.show_m
|
? FlowySvgs.show_m
|
||||||
: FlowySvgs.hide_m,
|
: FlowySvgs.hide_m,
|
||||||
|
size: const Size.square(16),
|
||||||
color: Theme.of(context).iconTheme.color,
|
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(),
|
onTap: () => _popoverController.show(),
|
||||||
).padding(horizontal: 6.0),
|
).padding(horizontal: 6.0),
|
||||||
@ -190,11 +191,8 @@ class _DatabasePropertyCellState extends State<DatabasePropertyCell> {
|
|||||||
popupBuilder: (BuildContext context) {
|
popupBuilder: (BuildContext context) {
|
||||||
return FieldEditor(
|
return FieldEditor(
|
||||||
viewId: widget.viewId,
|
viewId: widget.viewId,
|
||||||
fieldInfo: widget.fieldInfo,
|
field: widget.fieldInfo.field,
|
||||||
typeOptionLoader: FieldTypeOptionLoader(
|
fieldController: widget.fieldController,
|
||||||
viewId: widget.viewId,
|
|
||||||
field: widget.fieldInfo.field,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -454,10 +454,12 @@ GoRoute _mobileCardDetailScreenRoute() {
|
|||||||
pageBuilder: (context, state) {
|
pageBuilder: (context, state) {
|
||||||
final args = state.extra as Map<String, dynamic>;
|
final args = state.extra as Map<String, dynamic>;
|
||||||
final rowController = args[MobileCardDetailScreen.argRowController];
|
final rowController = args[MobileCardDetailScreen.argRowController];
|
||||||
|
final fieldController = args[MobileCardDetailScreen.argFieldController];
|
||||||
|
|
||||||
return MaterialPage(
|
return MaterialPage(
|
||||||
child: MobileCardDetailScreen(
|
child: MobileCardDetailScreen(
|
||||||
rowController: rowController,
|
rowController: rowController,
|
||||||
|
fieldController: fieldController,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -471,6 +473,7 @@ GoRoute _mobileCardPropertyEditScreenRoute() {
|
|||||||
pageBuilder: (context, state) {
|
pageBuilder: (context, state) {
|
||||||
final args = state.extra as Map<String, dynamic>;
|
final args = state.extra as Map<String, dynamic>;
|
||||||
final cellContext = args[CardPropertyEditScreen.argCellContext];
|
final cellContext = args[CardPropertyEditScreen.argCellContext];
|
||||||
|
final fieldController = args[CardPropertyEditScreen.argFieldController];
|
||||||
final rowDetailBloc = args[CardPropertyEditScreen.argRowDetailBloc];
|
final rowDetailBloc = args[CardPropertyEditScreen.argRowDetailBloc];
|
||||||
|
|
||||||
return MaterialPage(
|
return MaterialPage(
|
||||||
@ -478,6 +481,7 @@ GoRoute _mobileCardPropertyEditScreenRoute() {
|
|||||||
value: rowDetailBloc as RowDetailBloc,
|
value: rowDetailBloc as RowDetailBloc,
|
||||||
child: CardPropertyEditScreen(
|
child: CardPropertyEditScreen(
|
||||||
cellContext: cellContext,
|
cellContext: cellContext,
|
||||||
|
fieldController: fieldController,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -508,11 +512,16 @@ GoRoute _mobileCreateRowFieldScreenRoute() {
|
|||||||
pageBuilder: (context, state) {
|
pageBuilder: (context, state) {
|
||||||
final args = state.extra as Map<String, dynamic>;
|
final args = state.extra as Map<String, dynamic>;
|
||||||
final viewId = args[MobileCreateRowFieldScreen.argViewId];
|
final viewId = args[MobileCreateRowFieldScreen.argViewId];
|
||||||
|
final fieldController =
|
||||||
|
args[MobileCreateRowFieldScreen.argFieldController];
|
||||||
final typeOption = args[MobileCreateRowFieldScreen.argTypeOption];
|
final typeOption = args[MobileCreateRowFieldScreen.argTypeOption];
|
||||||
|
|
||||||
return MaterialPage(
|
return MaterialPage(
|
||||||
child:
|
child: MobileCreateRowFieldScreen(
|
||||||
MobileCreateRowFieldScreen(viewId: viewId, typeOption: typeOption),
|
viewId: viewId,
|
||||||
|
typeOption: typeOption,
|
||||||
|
fieldController: fieldController,
|
||||||
|
),
|
||||||
fullscreenDialog: true,
|
fullscreenDialog: true,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -185,7 +185,7 @@ class FlowyTextButton extends StatelessWidget {
|
|||||||
List<Widget> children = [];
|
List<Widget> children = [];
|
||||||
if (heading != null) {
|
if (heading != null) {
|
||||||
children.add(heading!);
|
children.add(heading!);
|
||||||
children.add(const HSpace(6));
|
children.add(const HSpace(8));
|
||||||
}
|
}
|
||||||
children.add(
|
children.add(
|
||||||
FlowyText(
|
FlowyText(
|
||||||
|
@ -40,13 +40,14 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final editorBloc = FieldEditorBloc(
|
final editorBloc = FieldEditorBloc(
|
||||||
isGroupField: fieldInfo.isGroupField,
|
viewId: context.gridView.id,
|
||||||
loader: loader,
|
loader: loader,
|
||||||
field: fieldInfo.field,
|
field: fieldInfo.field,
|
||||||
|
fieldController: context.fieldController,
|
||||||
)..add(const FieldEditorEvent.initial());
|
)..add(const FieldEditorEvent.initial());
|
||||||
await boardResponseFuture();
|
await boardResponseFuture();
|
||||||
|
|
||||||
editorBloc.add(const FieldEditorEvent.updateName('Hello world'));
|
editorBloc.add(const FieldEditorEvent.renameField('Hello world'));
|
||||||
await boardResponseFuture();
|
await boardResponseFuture();
|
||||||
|
|
||||||
// assert the groups were not changed
|
// assert the groups were not changed
|
||||||
|
@ -29,13 +29,10 @@ void main() {
|
|||||||
build: () => editorBloc,
|
build: () => editorBloc,
|
||||||
wait: boardResponseDuration(),
|
wait: boardResponseDuration(),
|
||||||
act: (bloc) async {
|
act: (bloc) async {
|
||||||
bloc.add(const FieldEditorEvent.switchToField(FieldType.RichText));
|
bloc.add(const FieldEditorEvent.switchFieldType(FieldType.RichText));
|
||||||
},
|
},
|
||||||
verify: (bloc) {
|
verify: (bloc) {
|
||||||
bloc.state.field.fold(
|
assert(bloc.state.field.fieldType == FieldType.RichText);
|
||||||
() => throw Exception(),
|
|
||||||
(field) => field.fieldType == FieldType.RichText,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
blocTest<BoardBloc, BoardState>(
|
blocTest<BoardBloc, BoardState>(
|
||||||
|
@ -88,7 +88,8 @@ class BoardTestContext {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final editorBloc = FieldEditorBloc(
|
final editorBloc = FieldEditorBloc(
|
||||||
isGroupField: fieldInfo.isGroupField,
|
viewId: databaseController.viewId,
|
||||||
|
fieldController: fieldController,
|
||||||
loader: loader,
|
loader: loader,
|
||||||
field: fieldInfo.field,
|
field: fieldInfo.field,
|
||||||
);
|
);
|
||||||
@ -126,10 +127,11 @@ class BoardTestContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<FieldEditorBloc> createField(FieldType fieldType) async {
|
Future<FieldEditorBloc> createField(FieldType fieldType) async {
|
||||||
final editorBloc = await createFieldEditor(viewId: gridView.id)
|
final editorBloc =
|
||||||
..add(const FieldEditorEvent.initial());
|
await createFieldEditor(databaseController: _boardDataController)
|
||||||
|
..add(const FieldEditorEvent.initial());
|
||||||
await gridResponseFuture();
|
await gridResponseFuture();
|
||||||
editorBloc.add(FieldEditorEvent.switchToField(fieldType));
|
editorBloc.add(FieldEditorEvent.switchFieldType(fieldType));
|
||||||
await gridResponseFuture();
|
await gridResponseFuture();
|
||||||
return Future(() => editorBloc);
|
return Future(() => editorBloc);
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,8 @@ Future<FieldEditorBloc> createEditorBloc(AppFlowyGridTest gridTest) async {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return FieldEditorBloc(
|
return FieldEditorBloc(
|
||||||
isGroupField: fieldInfo.isGroupField,
|
viewId: context.gridView.id,
|
||||||
|
fieldController: context.fieldController,
|
||||||
loader: loader,
|
loader: loader,
|
||||||
field: fieldInfo.field,
|
field: fieldInfo.field,
|
||||||
)..add(const FieldEditorEvent.initial());
|
)..add(const FieldEditorEvent.initial());
|
||||||
@ -27,67 +28,21 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('rename field', () async {
|
test('rename field', () async {
|
||||||
final editorBloc = await makeEditorBloc(gridTest);
|
final editorBloc = await createEditorBloc(gridTest);
|
||||||
editorBloc.add(const FieldEditorEvent.updateName('Hello world'));
|
editorBloc.add(const FieldEditorEvent.renameField('Hello world'));
|
||||||
await gridResponseFuture();
|
|
||||||
|
|
||||||
editorBloc.state.field.fold(
|
await gridResponseFuture();
|
||||||
() => throw Exception("The field should not be none"),
|
expect(editorBloc.state.field.name, equals("Hello world"));
|
||||||
(field) {
|
|
||||||
assert(field.name == 'Hello world');
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('switch to text field', () async {
|
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();
|
await gridResponseFuture();
|
||||||
|
|
||||||
editorBloc.state.field.fold(
|
// The default length of the fields is 3. The length of the fields
|
||||||
() => throw Exception("The field should not be none"),
|
// should not change after switching to other field type
|
||||||
(field) {
|
expect(editorBloc.state.field.fieldType, equals(FieldType.RichText));
|
||||||
// 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);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
Future<FieldEditorBloc> createField(FieldType fieldType) async {
|
||||||
final editorBloc = await createFieldEditor(viewId: gridView.id)
|
final editorBloc =
|
||||||
..add(const FieldEditorEvent.initial());
|
await createFieldEditor(databaseController: gridController)
|
||||||
|
..add(const FieldEditorEvent.initial());
|
||||||
await gridResponseFuture();
|
await gridResponseFuture();
|
||||||
editorBloc.add(FieldEditorEvent.switchToField(fieldType));
|
editorBloc.add(FieldEditorEvent.switchFieldType(fieldType));
|
||||||
await gridResponseFuture();
|
await gridResponseFuture();
|
||||||
return Future(() => editorBloc);
|
return Future(() => editorBloc);
|
||||||
}
|
}
|
||||||
@ -132,19 +133,21 @@ class GridTestContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<FieldEditorBloc> createFieldEditor({
|
Future<FieldEditorBloc> createFieldEditor({
|
||||||
required String viewId,
|
required DatabaseController databaseController,
|
||||||
}) async {
|
}) async {
|
||||||
final result = await TypeOptionBackendService.createFieldTypeOption(
|
final result = await TypeOptionBackendService.createFieldTypeOption(
|
||||||
viewId: viewId,
|
viewId: databaseController.viewId,
|
||||||
);
|
);
|
||||||
|
await gridResponseFuture();
|
||||||
return result.fold(
|
return result.fold(
|
||||||
(data) {
|
(data) {
|
||||||
final loader = FieldTypeOptionLoader(
|
final loader = FieldTypeOptionLoader(
|
||||||
viewId: viewId,
|
viewId: databaseController.viewId,
|
||||||
field: data.field_2,
|
field: data.field_2,
|
||||||
);
|
);
|
||||||
return FieldEditorBloc(
|
return FieldEditorBloc(
|
||||||
isGroupField: FieldInfo.initial(data.field_2).isGroupField,
|
viewId: databaseController.viewId,
|
||||||
|
fieldController: databaseController.fieldController,
|
||||||
loader: loader,
|
loader: loader,
|
||||||
field: data.field_2,
|
field: data.field_2,
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user