feat: integrate new field editor components (#4070)

* chore: use the fancy new create field UI

* chore: adjust select option tag text weight

* chore: use new field editor

* chore: remove old field editor

* chore: code cleanup
This commit is contained in:
Richard Shiue
2023-12-02 22:23:19 +08:00
committed by GitHub
parent 147080ad27
commit ff3ff9f888
11 changed files with 174 additions and 515 deletions

View File

@ -1,203 +0,0 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/widgets/flowy_paginated_bottom_sheet.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/plugins/database_view/application/setting/property_bloc.dart';
import 'package:appflowy/plugins/database_view/widgets/setting/mobile_database_property_editor.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_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: _buildBody(),
);
}
Widget _buildBody() {
return switch (viewMode) {
MobileDBBottomSheetViewMode.general => MobileDBFieldBottomSheetBody(
onAction: (action) {
switch (action) {
case MobileDBBottomSheetGeneralAction.typeOption:
FlowyBottomSheetController.of(context)!.push(
SheetPage(
title: LocaleKeys.grid_field_editProperty.tr(),
body: MobileDatabasePropertyEditor(
padding: EdgeInsets.zero,
viewId: widget.viewId,
fieldInfo:
widget.fieldController.getField(widget.field.id)!,
fieldController: widget.fieldController,
bloc: DatabasePropertyBloc(
viewId: widget.viewId,
fieldController: widget.fieldController,
),
),
),
);
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()),
],
),
],
);
}
}

View File

@ -23,7 +23,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:go_router/go_router.dart';
import 'widgets/mobile_create_row_field_button.dart';
import 'widgets/mobile_create_field_button.dart';
import 'widgets/mobile_row_property_list.dart';
class MobileRowDetailPage extends StatefulWidget {
@ -95,12 +95,9 @@ class _MobileRowDetailPageState extends State<MobileRowDetailPage> {
if (state.rowInfos.isEmpty || state.currentRowId == null) {
return const SizedBox.shrink();
}
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 18),
child: MobileRowDetailPageContent(
databaseController: widget.databaseController,
rowMeta: state.rowInfos[index].rowMeta,
),
return MobileRowDetailPageContent(
databaseController: widget.databaseController,
rowMeta: state.rowInfos[index].rowMeta,
);
},
);
@ -371,9 +368,12 @@ class MobileRowDetailPageContentState
fieldInfo: FieldInfo.initial(state.primaryField!),
);
return cellBuilder.build(
cellContext,
style: cellStyle,
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 18),
child: cellBuilder.build(
cellContext,
style: cellStyle,
),
);
}
return const SizedBox.shrink();
@ -384,20 +384,23 @@ class MobileRowDetailPageContentState
child: ListView(
padding: const EdgeInsets.symmetric(vertical: 8),
children: [
MobileRowPropertyList(
cellBuilder: cellBuilder,
viewId: viewId,
fieldController: fieldController,
Padding(
padding: const EdgeInsets.symmetric(horizontal: 18),
child: MobileRowPropertyList(
cellBuilder: cellBuilder,
viewId: viewId,
fieldController: fieldController,
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (rowDetailState.numHiddenFields != 0)
const ToggleHiddenFieldsVisibilityButton(),
const VSpace(12),
MobileCreateRowFieldButton(
MobileRowDetailCreateFieldButton(
viewId: viewId,
fieldController: fieldController,
),

View File

@ -0,0 +1,49 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/database/field/bottom_sheet_create_field.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
class MobileRowDetailCreateFieldButton extends StatelessWidget {
const MobileRowDetailCreateFieldButton({
super.key,
required this.viewId,
required this.fieldController,
});
final String viewId;
final FieldController fieldController;
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: const BoxConstraints(minWidth: double.infinity),
child: TextButton.icon(
style: Theme.of(context).textButtonTheme.style?.copyWith(
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
side: BorderSide.none,
),
),
overlayColor: MaterialStateProperty.all<Color>(
Theme.of(context).hoverColor,
),
alignment: AlignmentDirectional.centerStart,
splashFactory: NoSplash.splashFactory,
padding: const MaterialStatePropertyAll(
EdgeInsets.symmetric(vertical: 14, horizontal: 6),
),
),
label: FlowyText.medium(
LocaleKeys.grid_field_newProperty.tr(),
fontSize: 15,
),
onPressed: () => showCreateFieldBottomSheet(context, viewId),
icon: const FlowySvg(FlowySvgs.add_m),
),
);
}
}

View File

@ -1,59 +0,0 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/mobile_create_row_field_screen.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_service.dart';
import 'package:appflowy_backend/log.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class MobileCreateRowFieldButton extends StatelessWidget {
const MobileCreateRowFieldButton({
super.key,
required this.viewId,
required this.fieldController,
});
final String viewId;
final FieldController fieldController;
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints:
const BoxConstraints(maxHeight: 44, minWidth: double.infinity),
child: TextButton.icon(
style: Theme.of(context).textButtonTheme.style?.copyWith(
alignment: AlignmentDirectional.centerStart,
splashFactory: NoSplash.splashFactory,
padding: const MaterialStatePropertyAll(EdgeInsets.zero),
),
label: Text(
LocaleKeys.grid_field_newProperty.tr(),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(fontSize: 15),
),
onPressed: () async {
final result = await TypeOptionBackendService.createFieldTypeOption(
viewId: viewId,
);
result.fold(
(typeOption) {
context.push(
MobileCreateRowFieldScreen.routeName,
extra: {
MobileCreateRowFieldScreen.argViewId: viewId,
MobileCreateRowFieldScreen.argTypeOption: typeOption,
MobileCreateRowFieldScreen.argFieldController:
fieldController,
},
);
},
(r) => Log.error("Failed to create field type option: $r"),
);
},
icon: const FlowySvg(FlowySvgs.add_m),
),
);
}
}

View File

@ -1,3 +1,3 @@
export 'mobile_field_name_text_field.dart';
export 'mobile_create_row_field_button.dart';
export 'mobile_create_field_button.dart';
export 'mobile_row_property_list.dart';

View File

@ -0,0 +1,82 @@
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/mobile_create_field_screen.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/mobile_edit_field_screen.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/_field_options.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/_field_options_eidtor.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:flutter/material.dart';
import 'package:go_router/go_router.dart';
void showCreateFieldBottomSheet(BuildContext context, String viewId) {
showMobileBottomSheet(
context,
padding: EdgeInsets.zero,
builder: (context) {
return DraggableScrollableSheet(
expand: false,
snap: true,
initialChildSize: 0.7,
minChildSize: 0.7,
builder: (context, controller) => FieldOptions(
scrollController: controller,
onAddField: (type) async {
final optionValues = await context.push<FieldOptionValues>(
Uri(
path: MobileNewPropertyScreen.routeName,
queryParameters: {
MobileNewPropertyScreen.argViewId: viewId,
MobileNewPropertyScreen.argFieldTypeId: type.value.toString(),
},
).toString(),
);
if (optionValues != null) {
await optionValues.create(viewId: viewId);
if (context.mounted) {
context.pop();
}
}
},
),
);
},
);
}
void showEditFieldScreen(
BuildContext context,
String viewId,
FieldInfo field,
) async {
final optionValues = await context.push<FieldOptionValues>(
MobileEditPropertyScreen.routeName,
extra: {
MobileEditPropertyScreen.argViewId: viewId,
MobileEditPropertyScreen.argField: field.field,
MobileEditPropertyScreen.argIsPrimary: field.isPrimary,
},
);
if (optionValues != null) {
final service = FieldBackendService(
viewId: viewId,
fieldId: field.id,
);
if (optionValues.name != field.name) {
await service.updateField(name: optionValues.name);
}
if (optionValues.type != field.fieldType) {
await service.updateFieldType(fieldType: optionValues.type);
}
final data = optionValues.toTypeOptionBuffer();
if (data != null) {
await FieldBackendService.updateFieldTypeOption(
viewId: viewId,
fieldId: field.id,
typeOptionData: data,
);
}
}
}