chore: grid property list

This commit is contained in:
appflowy 2022-04-03 17:05:28 +08:00
parent 5d007c5359
commit 6a94594a35
25 changed files with 600 additions and 382 deletions

View File

@ -165,8 +165,8 @@ void _resolveGridDeps(GetIt getIt) {
),
);
getIt.registerFactoryParam<GridFieldBloc, GridFieldData, void>(
(data, _) => GridFieldBloc(
getIt.registerFactoryParam<FieldActionSheetBloc, GridFieldCellContext, void>(
(data, _) => FieldActionSheetBloc(
field: data.field,
service: FieldService(gridId: data.gridId),
),
@ -214,8 +214,8 @@ void _resolveGridDeps(GetIt getIt) {
),
);
getIt.registerFactoryParam<FieldSwitchBloc, SwitchFieldContext, void>(
(context, _) => FieldSwitchBloc(context),
getIt.registerFactoryParam<FieldSwitcherBloc, SwitchFieldContext, void>(
(context, _) => FieldSwitcherBloc(context),
);
getIt.registerFactoryParam<SingleSelectTypeOptionBloc, SingleSelectTypeOption, String>(
@ -233,4 +233,8 @@ void _resolveGridDeps(GetIt getIt) {
getIt.registerFactoryParam<NumberTypeOptionBloc, NumberTypeOption, void>(
(typeOption, _) => NumberTypeOptionBloc(typeOption: typeOption),
);
getIt.registerFactoryParam<GridPropertyBloc, String, List<Field>>(
(gridId, fields) => GridPropertyBloc(gridId: gridId, fields: fields),
);
}

View File

@ -5,14 +5,14 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'field_service.dart';
part 'grid_field_bloc.freezed.dart';
part 'action_sheet_bloc.freezed.dart';
class GridFieldBloc extends Bloc<GridFieldEvent, GridFieldState> {
class FieldActionSheetBloc extends Bloc<ActionSheetEvent, ActionSheetState> {
final FieldService service;
GridFieldBloc({required Field field, required this.service})
: super(GridFieldState.initial(EditFieldContext.create()..gridField = field)) {
on<GridFieldEvent>(
FieldActionSheetBloc({required Field field, required this.service})
: super(ActionSheetState.initial(EditFieldContext.create()..gridField = field)) {
on<ActionSheetEvent>(
(event, emit) async {
await event.map(
updateFieldName: (_UpdateFieldName value) async {
@ -56,23 +56,23 @@ class GridFieldBloc extends Bloc<GridFieldEvent, GridFieldState> {
}
@freezed
class GridFieldEvent with _$GridFieldEvent {
const factory GridFieldEvent.updateFieldName(String name) = _UpdateFieldName;
const factory GridFieldEvent.hideField() = _HideField;
const factory GridFieldEvent.duplicateField() = _DuplicateField;
const factory GridFieldEvent.deleteField() = _DeleteField;
const factory GridFieldEvent.saveField() = _SaveField;
class ActionSheetEvent with _$ActionSheetEvent {
const factory ActionSheetEvent.updateFieldName(String name) = _UpdateFieldName;
const factory ActionSheetEvent.hideField() = _HideField;
const factory ActionSheetEvent.duplicateField() = _DuplicateField;
const factory ActionSheetEvent.deleteField() = _DeleteField;
const factory ActionSheetEvent.saveField() = _SaveField;
}
@freezed
class GridFieldState with _$GridFieldState {
const factory GridFieldState({
class ActionSheetState with _$ActionSheetState {
const factory ActionSheetState({
required EditFieldContext editContext,
required String errorText,
required String fieldName,
}) = _GridFieldState;
}) = _ActionSheetState;
factory GridFieldState.initial(EditFieldContext editContext) => GridFieldState(
factory ActionSheetState.initial(EditFieldContext editContext) => ActionSheetState(
editContext: editContext,
errorText: '',
fieldName: editContext.gridField.name,

View File

@ -7,7 +7,7 @@ import 'dart:async';
import 'field_service.dart';
import 'package:dartz/dartz.dart';
part 'edit_field_bloc.freezed.dart';
part 'field_editor_bloc.freezed.dart';
class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
final FieldService service;

View File

@ -98,11 +98,11 @@ class FieldService {
}
}
class GridFieldData extends Equatable {
class GridFieldCellContext extends Equatable {
final String gridId;
final Field field;
const GridFieldData({
const GridFieldCellContext({
required this.gridId,
required this.field,
});
@ -132,7 +132,7 @@ class NewFieldContextLoader extends FieldContextLoader {
}
class FieldContextLoaderAdaptor extends FieldContextLoader {
final GridFieldData data;
final GridFieldCellContext data;
FieldContextLoaderAdaptor(this.data);

View File

@ -7,10 +7,10 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'field_service.dart';
part 'switch_field_type_bloc.freezed.dart';
part 'field_switch_bloc.freezed.dart';
class FieldSwitchBloc extends Bloc<FieldSwitchEvent, FieldSwitchState> {
FieldSwitchBloc(SwitchFieldContext editContext) : super(FieldSwitchState.initial(editContext)) {
class FieldSwitcherBloc extends Bloc<FieldSwitchEvent, FieldSwitchState> {
FieldSwitcherBloc(SwitchFieldContext editContext) : super(FieldSwitchState.initial(editContext)) {
on<FieldSwitchEvent>(
(event, emit) async {
await event.map(

View File

@ -27,6 +27,7 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
createField: (_CreateField value) {},
insertField: (_InsertField value) {},
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
value.fields.retainWhere((field) => field.visibility);
emit(state.copyWith(fields: value.fields));
},
);
@ -64,5 +65,8 @@ class GridHeaderEvent with _$GridHeaderEvent {
class GridHeaderState with _$GridHeaderState {
const factory GridHeaderState({required List<Field> fields}) = _GridHeaderState;
factory GridHeaderState.initial(List<Field> fields) => GridHeaderState(fields: fields);
factory GridHeaderState.initial(List<Field> fields) {
fields.retainWhere((field) => field.visibility);
return GridHeaderState(fields: fields);
}
}

View File

@ -7,9 +7,9 @@ export 'data.dart';
// Field
export 'field/field_service.dart';
export 'field/grid_header_bloc.dart';
export 'field/grid_field_bloc.dart';
export 'field/edit_field_bloc.dart';
export 'field/switch_field_type_bloc.dart';
export 'field/action_sheet_bloc.dart';
export 'field/field_editor_bloc.dart';
export 'field/field_switch_bloc.dart';
// Field Type Option
export 'field/type_option/date_bloc.dart';
@ -26,3 +26,4 @@ export 'cell_bloc/cell_service.dart';
// Setting
export 'setting/setting_bloc.dart';
export 'setting/property_bloc.dart';

View File

@ -0,0 +1,51 @@
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'package:dartz/dartz.dart';
part 'property_bloc.freezed.dart';
class GridPropertyBloc extends Bloc<GridPropertyEvent, GridPropertyState> {
final FieldService _service;
GridPropertyBloc({required String gridId, required List<Field> fields})
: _service = FieldService(gridId: gridId),
super(GridPropertyState.initial(gridId, fields)) {
on<GridPropertyEvent>(
(event, emit) async {
await event.map(setFieldVisibility: (_SetFieldVisibility value) async {
final result = await _service.updateField(fieldId: value.fieldId, visibility: value.visibility);
result.fold(
(l) => null,
(err) => Log.error(err),
);
});
},
);
}
@override
Future<void> close() async {
return super.close();
}
}
@freezed
class GridPropertyEvent with _$GridPropertyEvent {
const factory GridPropertyEvent.setFieldVisibility(String fieldId, bool visibility) = _SetFieldVisibility;
}
@freezed
class GridPropertyState with _$GridPropertyState {
const factory GridPropertyState({
required String gridId,
required List<Field> fields,
}) = _GridPropertyState;
factory GridPropertyState.initial(String gridId, List<Field> fields) => GridPropertyState(
gridId: gridId,
fields: fields,
);
}

View File

@ -100,9 +100,9 @@ class _FlowyGridState extends State<FlowyGrid> {
physics: StyledScrollPhysics(),
controller: _scrollController.verticalController,
slivers: [
SliverToBoxAdapter(child: GridToolbar(gridId: gridId)),
_buildHeader(gridId),
_buildRows(context),
_renderToolbar(gridId),
_renderHeader(gridId),
_renderRows(context),
const GridFooter(),
],
),
@ -129,12 +129,27 @@ class _FlowyGridState extends State<FlowyGrid> {
);
}
Widget _buildHeader(String gridId) {
Widget _renderToolbar(String gridId) {
return BlocBuilder<GridBloc, GridState>(
builder: (context, state) {
final toolbarContext = GridToolbarContext(
gridId: gridId,
fields: state.fields,
);
return SliverToBoxAdapter(
child: GridToolbar(toolbarContext: toolbarContext),
);
},
);
}
Widget _renderHeader(String gridId) {
return BlocBuilder<GridBloc, GridState>(
buildWhen: (previous, current) => previous.fields.length != current.fields.length,
builder: (context, state) {
return SliverPersistentHeader(
delegate: GridHeaderDelegate(gridId: gridId, fields: state.fields),
delegate: GridHeaderDelegate(gridId: gridId, fields: List.from(state.fields)),
floating: true,
pinned: true,
);
@ -142,7 +157,7 @@ class _FlowyGridState extends State<FlowyGrid> {
);
}
Widget _buildRows(BuildContext context) {
Widget _renderRows(BuildContext context) {
return BlocBuilder<GridBloc, GridState>(
buildWhen: (previous, current) {
final rowChanged = previous.rows.length != current.rows.length;

View File

@ -124,6 +124,7 @@ class _RowCells extends StatelessWidget {
buildWhen: (previous, current) => previous.cellDataMap != current.cellDataMap,
builder: (context, state) {
final children = state.fields
.where((field) => field.visibility)
.map((field) => CellContainer(
width: field.width.toDouble(),
child: buildGridCell(

View File

@ -0,0 +1,55 @@
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'field_type_extension.dart';
import 'field_cell_action_sheet.dart';
import 'field_editor.dart';
class GridFieldCell extends StatelessWidget {
final GridFieldCellContext fieldCellContext;
const GridFieldCell(this.fieldCellContext, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
final field = fieldCellContext.field;
final button = FlowyButton(
hoverColor: theme.hover,
onTap: () => _showActionSheet(context),
rightIcon: svgWidget("editor/details", color: theme.iconColor),
leftIcon: svgWidget(field.fieldType.iconName(), color: theme.iconColor),
text: FlowyText.medium(field.name, fontSize: 12),
padding: GridSize.cellContentInsets,
);
final borderSide = BorderSide(color: theme.shader4, width: 0.4);
final decoration = BoxDecoration(border: Border(top: borderSide, right: borderSide, bottom: borderSide));
return Container(
width: field.width.toDouble(),
decoration: decoration,
child: button,
);
}
void _showActionSheet(BuildContext context) {
GridFieldCellActionSheet(
fieldCellContext: fieldCellContext,
onEdited: () => _showFieldEditor(context),
).show(context);
}
void _showFieldEditor(BuildContext context) {
FieldEditor(
gridId: fieldCellContext.gridId,
fieldContextLoader: FieldContextLoaderAdaptor(fieldCellContext),
).show(context);
}
}

View File

@ -0,0 +1,195 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/prelude.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.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/widget/spacing.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
class GridFieldCellActionSheet extends StatelessWidget with FlowyOverlayDelegate {
final GridFieldCellContext fieldCellContext;
final VoidCallback onEdited;
const GridFieldCellActionSheet({required this.fieldCellContext, required this.onEdited, Key? key}) : super(key: key);
void show(BuildContext overlayContext) {
FlowyOverlay.of(overlayContext).insertWithAnchor(
widget: OverlayContainer(
child: this,
constraints: BoxConstraints.loose(const Size(240, 200)),
),
identifier: identifier(),
anchorContext: overlayContext,
anchorDirection: AnchorDirection.bottomWithLeftAligned,
delegate: this,
);
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => getIt<FieldActionSheetBloc>(param1: fieldCellContext),
child: SingleChildScrollView(
child: Column(
children: [
_EditFieldButton(
onEdited: () {
FlowyOverlay.of(context).remove(identifier());
onEdited();
},
),
const VSpace(6),
_FieldOperationList(fieldCellContext, () => FlowyOverlay.of(context).remove(identifier())),
],
),
),
);
}
String identifier() {
return toString();
}
@override
bool asBarrier() {
return true;
}
}
class _EditFieldButton extends StatelessWidget {
final Function() onEdited;
const _EditFieldButton({required this.onEdited, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return BlocBuilder<FieldActionSheetBloc, ActionSheetState>(
builder: (context, state) {
return SizedBox(
height: GridSize.typeOptionItemHeight,
child: FlowyButton(
text: FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(), fontSize: 12),
hoverColor: theme.hover,
onTap: onEdited,
),
);
},
);
}
}
class _FieldOperationList extends StatelessWidget {
final GridFieldCellContext fieldData;
final VoidCallback onDismissed;
const _FieldOperationList(this.fieldData, this.onDismissed, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final actions = FieldAction.values
.map(
(action) => FieldActionCell(
fieldId: fieldData.field.id,
action: action,
onTap: onDismissed,
),
)
.toList();
return FieldOperationList(actions: actions);
}
}
class FieldOperationList extends StatelessWidget {
final List<FieldActionCell> actions;
const FieldOperationList({required this.actions, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GridView(
// https://api.flutter.dev/flutter/widgets/AnimatedList/shrinkWrap.html
shrinkWrap: true,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 4.0,
mainAxisSpacing: 8,
),
children: actions,
);
}
}
class FieldActionCell extends StatelessWidget {
final String fieldId;
final VoidCallback onTap;
final FieldAction action;
const FieldActionCell({
required this.fieldId,
required this.action,
required this.onTap,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return FlowyButton(
text: FlowyText.medium(action.title(), fontSize: 12),
hoverColor: theme.hover,
onTap: () {
action.run(context);
onTap();
},
leftIcon: svgWidget(action.iconName(), color: theme.iconColor),
);
}
}
enum FieldAction {
hide,
duplicate,
delete,
}
extension _FieldActionExtension on FieldAction {
String iconName() {
switch (this) {
case FieldAction.hide:
return 'grid/hide';
case FieldAction.duplicate:
return 'grid/duplicate';
case FieldAction.delete:
return 'grid/delete';
}
}
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) {
switch (this) {
case FieldAction.hide:
context.read<FieldActionSheetBloc>().add(const ActionSheetEvent.hideField());
break;
case FieldAction.duplicate:
context.read<FieldActionSheetBloc>().add(const ActionSheetEvent.duplicateField());
break;
case FieldAction.delete:
context.read<FieldActionSheetBloc>().add(const ActionSheetEvent.deleteField());
break;
}
}
}

View File

@ -1,7 +1,7 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/field/edit_field_bloc.dart';
import 'package:app_flowy/workspace/application/grid/field/field_editor_bloc.dart';
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
import 'package:app_flowy/workspace/application/grid/field/switch_field_type_bloc.dart';
import 'package:app_flowy/workspace/application/grid/field/field_switch_bloc.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
@ -22,7 +22,11 @@ class FieldEditor extends FlowyOverlayDelegate {
_fieldEditorBloc.add(const FieldEditorEvent.initial());
}
void show(BuildContext context) {
void show(
BuildContext context, {
AnchorDirection anchorDirection = AnchorDirection.bottomWithLeftAligned,
}) {
FlowyOverlay.of(context).remove(identifier());
FlowyOverlay.of(context).insertWithAnchor(
widget: OverlayContainer(
child: _FieldEditorWidget(_fieldEditorBloc),
@ -30,7 +34,7 @@ class FieldEditor extends FlowyOverlayDelegate {
),
identifier: identifier(),
anchorContext: context,
anchorDirection: AnchorDirection.bottomWithLeftAligned,
anchorDirection: anchorDirection,
style: FlowyOverlayStyle(blur: false),
delegate: this,
);

View File

@ -1,99 +0,0 @@
import 'package:app_flowy/workspace/application/grid/field/grid_field_bloc.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class FieldOperationList extends StatelessWidget {
final List<FieldActionCell> actions;
const FieldOperationList({required this.actions, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return GridView(
// https://api.flutter.dev/flutter/widgets/AnimatedList/shrinkWrap.html
shrinkWrap: true,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 4.0,
mainAxisSpacing: 8,
),
children: actions,
);
}
}
class FieldActionCell extends StatelessWidget {
final String fieldId;
final VoidCallback onTap;
final FieldAction action;
const FieldActionCell({
required this.fieldId,
required this.action,
required this.onTap,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return FlowyButton(
text: FlowyText.medium(action.title(), fontSize: 12),
hoverColor: theme.hover,
onTap: () {
action.run(context);
onTap();
},
leftIcon: svgWidget(action.iconName(), color: theme.iconColor),
);
}
}
enum FieldAction {
hide,
duplicate,
delete,
}
extension _FieldActionExtension on FieldAction {
String iconName() {
switch (this) {
case FieldAction.hide:
return 'grid/hide';
case FieldAction.duplicate:
return 'grid/duplicate';
case FieldAction.delete:
return 'grid/delete';
}
}
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) {
switch (this) {
case FieldAction.hide:
context.read<GridFieldBloc>().add(const GridFieldEvent.hideField());
break;
case FieldAction.duplicate:
context.read<GridFieldBloc>().add(const GridFieldEvent.duplicateField());
break;
case FieldAction.delete:
context.read<GridFieldBloc>().add(const GridFieldEvent.deleteField());
break;
}
}
}

View File

@ -15,7 +15,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/prelude.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_list.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_type_list.dart';
import 'field_type_extension.dart';
import 'type_option/multi_select.dart';
import 'type_option/number.dart';
@ -43,8 +44,8 @@ class _FieldSwitcherState extends State<FieldSwitcher> {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => getIt<FieldSwitchBloc>(param1: widget.switchContext),
child: BlocConsumer<FieldSwitchBloc, FieldSwitchState>(
create: (context) => getIt<FieldSwitcherBloc>(param1: widget.switchContext),
child: BlocConsumer<FieldSwitcherBloc, FieldSwitchState>(
listener: (context, state) {
widget.onUpdated(state.field, state.typeOptionData);
},
@ -79,7 +80,7 @@ class _FieldSwitcherState extends State<FieldSwitcher> {
hoverColor: theme.hover,
onTap: () {
final list = FieldTypeList(onSelectField: (fieldType) {
context.read<FieldSwitchBloc>().add(FieldSwitchEvent.toFieldType(fieldType));
context.read<FieldSwitcherBloc>().add(FieldSwitchEvent.toFieldType(fieldType));
});
_showOverlay(context, list);
},
@ -100,7 +101,7 @@ class _FieldSwitcherState extends State<FieldSwitcher> {
);
final dataDelegate = TypeOptionDataDelegate(didUpdateTypeOptionData: (data) {
context.read<FieldSwitchBloc>().add(FieldSwitchEvent.didUpdateTypeOptionData(data));
context.read<FieldSwitcherBloc>().add(FieldSwitchEvent.didUpdateTypeOptionData(data));
});
final builder = _makeTypeOptionBuild(

View File

@ -0,0 +1,44 @@
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/meta.pb.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
extension FieldTypeListExtension on FieldType {
String iconName() {
switch (this) {
case FieldType.Checkbox:
return "grid/field/checkbox";
case FieldType.DateTime:
return "grid/field/date";
case FieldType.MultiSelect:
return "grid/field/multi_select";
case FieldType.Number:
return "grid/field/number";
case FieldType.RichText:
return "grid/field/text";
case FieldType.SingleSelect:
return "grid/field/single_select";
default:
throw UnimplementedError;
}
}
String title() {
switch (this) {
case FieldType.Checkbox:
return LocaleKeys.grid_field_checkboxFieldName.tr();
case FieldType.DateTime:
return LocaleKeys.grid_field_dateFieldName.tr();
case FieldType.MultiSelect:
return LocaleKeys.grid_field_multiSelectFieldName.tr();
case FieldType.Number:
return LocaleKeys.grid_field_numberFieldName.tr();
case FieldType.RichText:
return LocaleKeys.grid_field_textFieldName.tr();
case FieldType.SingleSelect:
return LocaleKeys.grid_field_singleSelectFieldName.tr();
default:
throw UnimplementedError;
}
}
}

View File

@ -8,8 +8,7 @@ import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/meta.pb.dart';
import 'package:flutter/material.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
import 'field_type_extension.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
typedef SelectFieldCallback = void Function(FieldType);
@ -76,43 +75,3 @@ class FieldTypeCell extends StatelessWidget {
);
}
}
extension FieldTypeListExtension on FieldType {
String iconName() {
switch (this) {
case FieldType.Checkbox:
return "grid/field/checkbox";
case FieldType.DateTime:
return "grid/field/date";
case FieldType.MultiSelect:
return "grid/field/multi_select";
case FieldType.Number:
return "grid/field/number";
case FieldType.RichText:
return "grid/field/text";
case FieldType.SingleSelect:
return "grid/field/single_select";
default:
throw UnimplementedError;
}
}
String title() {
switch (this) {
case FieldType.Checkbox:
return LocaleKeys.grid_field_checkboxFieldName.tr();
case FieldType.DateTime:
return LocaleKeys.grid_field_dateFieldName.tr();
case FieldType.MultiSelect:
return LocaleKeys.grid_field_multiSelectFieldName.tr();
case FieldType.Number:
return LocaleKeys.grid_field_numberFieldName.tr();
case FieldType.RichText:
return LocaleKeys.grid_field_textFieldName.tr();
case FieldType.SingleSelect:
return LocaleKeys.grid_field_singleSelectFieldName.tr();
default:
throw UnimplementedError;
}
}
}

View File

@ -1,105 +0,0 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/prelude.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
import 'package:flowy_infra/theme.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/widget/spacing.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'field_operation_list.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
class GridFieldActionSheet extends StatelessWidget with FlowyOverlayDelegate {
final GridFieldData fieldData;
final VoidCallback onEdited;
const GridFieldActionSheet({required this.fieldData, required this.onEdited, Key? key}) : super(key: key);
void show(BuildContext overlayContext) {
FlowyOverlay.of(overlayContext).insertWithAnchor(
widget: OverlayContainer(
child: this,
constraints: BoxConstraints.loose(const Size(240, 200)),
),
identifier: identifier(),
anchorContext: overlayContext,
anchorDirection: AnchorDirection.bottomWithLeftAligned,
delegate: this,
);
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => getIt<GridFieldBloc>(param1: fieldData),
child: SingleChildScrollView(
child: Column(
children: [
_EditFieldButton(
onEdited: () {
FlowyOverlay.of(context).remove(identifier());
onEdited();
},
),
const VSpace(6),
_FieldOperationList(fieldData, () => FlowyOverlay.of(context).remove(identifier())),
],
),
),
);
}
String identifier() {
return toString();
}
@override
bool asBarrier() {
return true;
}
}
class _EditFieldButton extends StatelessWidget {
final Function() onEdited;
const _EditFieldButton({required this.onEdited, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return BlocBuilder<GridFieldBloc, GridFieldState>(
builder: (context, state) {
return SizedBox(
height: GridSize.typeOptionItemHeight,
child: FlowyButton(
text: FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(), fontSize: 12),
hoverColor: theme.hover,
onTap: onEdited,
),
);
},
);
}
}
class _FieldOperationList extends StatelessWidget {
final GridFieldData fieldData;
final VoidCallback onDismissed;
const _FieldOperationList(this.fieldData, this.onDismissed, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final actions = FieldAction.values
.map(
(action) => FieldActionCell(
fieldId: fieldData.field.id,
action: action,
onTap: onDismissed,
),
)
.toList();
return FieldOperationList(actions: actions);
}
}

View File

@ -9,8 +9,8 @@ import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' hide Row;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'grid_field_editor.dart';
import 'grid_header_cell.dart';
import 'field_editor.dart';
import 'field_cell.dart';
class GridHeaderDelegate extends SliverPersistentHeaderDelegate {
final String gridId;
@ -55,8 +55,8 @@ class GridHeader extends StatelessWidget {
child: BlocBuilder<GridHeaderBloc, GridHeaderState>(
builder: (context, state) {
final cells = state.fields.map(
(field) => GridHeaderCell(
GridFieldData(gridId: gridId, field: field),
(field) => GridFieldCell(
GridFieldCellContext(gridId: gridId, field: field),
),
);

View File

@ -1,45 +0,0 @@
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'grid_field_editor.dart';
import 'grid_field_action_sheet.dart';
class GridHeaderCell extends StatelessWidget {
final GridFieldData fieldData;
const GridHeaderCell(this.fieldData, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
final button = FlowyButton(
hoverColor: theme.hover,
onTap: () => GridFieldActionSheet(
fieldData: fieldData,
onEdited: () {
FieldEditor(
gridId: fieldData.gridId,
fieldContextLoader: FieldContextLoaderAdaptor(fieldData),
).show(context);
},
).show(context),
rightIcon: svgWidget("editor/details", color: theme.iconColor),
text: Padding(padding: GridSize.cellContentInsets, child: FlowyText.medium(fieldData.field.name, fontSize: 12)),
);
final borderSide = BorderSide(color: theme.shader4, width: 0.4);
final decoration = BoxDecoration(border: Border(top: borderSide, right: borderSide, bottom: borderSide));
return Container(
width: fieldData.field.width.toDouble(),
decoration: decoration,
padding: GridSize.headerContentInsets,
child: button,
);
}
}

View File

@ -109,8 +109,8 @@ class DateFormatList extends StatelessWidget {
@override
Widget build(BuildContext context) {
final formatItems = DateFormat.values.map((format) {
return DateFormatItem(
final cells = DateFormat.values.map((format) {
return DateFormatCell(
dateFormat: format,
onSelected: (format) {
onSelected(format);
@ -127,9 +127,9 @@ class DateFormatList extends StatelessWidget {
separatorBuilder: (context, index) {
return VSpace(GridSize.typeOptionSeparatorHeight);
},
itemCount: formatItems.length,
itemCount: cells.length,
itemBuilder: (BuildContext context, int index) {
return formatItems[index];
return cells[index];
},
),
);
@ -140,11 +140,11 @@ class DateFormatList extends StatelessWidget {
}
}
class DateFormatItem extends StatelessWidget {
class DateFormatCell extends StatelessWidget {
final bool isSelected;
final DateFormat dateFormat;
final Function(DateFormat format) onSelected;
const DateFormatItem({
const DateFormatCell({
required this.dateFormat,
required this.onSelected,
required this.isSelected,
@ -199,8 +199,8 @@ class TimeFormatList extends StatelessWidget {
@override
Widget build(BuildContext context) {
final formatItems = TimeFormat.values.map((format) {
return TimeFormatItem(
final cells = TimeFormat.values.map((format) {
return TimeFormatCell(
isSelected: format == selectedFormat,
timeFormat: format,
onSelected: (format) {
@ -217,9 +217,9 @@ class TimeFormatList extends StatelessWidget {
separatorBuilder: (context, index) {
return VSpace(GridSize.typeOptionSeparatorHeight);
},
itemCount: formatItems.length,
itemCount: cells.length,
itemBuilder: (BuildContext context, int index) {
return formatItems[index];
return cells[index];
},
),
);
@ -230,11 +230,11 @@ class TimeFormatList extends StatelessWidget {
}
}
class TimeFormatItem extends StatelessWidget {
class TimeFormatCell extends StatelessWidget {
final TimeFormat timeFormat;
final bool isSelected;
final Function(TimeFormat format) onSelected;
const TimeFormatItem({
const TimeFormatCell({
required this.timeFormat,
required this.onSelected,
required this.isSelected,

View File

@ -1,19 +1,123 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
import 'package:app_flowy/workspace/application/grid/setting/property_bloc.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_type_extension.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.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/icon_button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:styled_widget/styled_widget.dart';
class GridPropertyList extends StatelessWidget {
const GridPropertyList({Key? key}) : super(key: key);
class GridPropertyList extends StatelessWidget with FlowyOverlayDelegate {
final String gridId;
final List<Field> fields;
const GridPropertyList({
required this.gridId,
required this.fields,
Key? key,
}) : super(key: key);
void show(BuildContext context) {
FlowyOverlay.of(context).insertWithAnchor(
widget: OverlayContainer(
child: this,
constraints: BoxConstraints.loose(const Size(260, 400)),
),
identifier: identifier(),
anchorContext: context,
anchorDirection: AnchorDirection.bottomRight,
style: FlowyOverlayStyle(blur: false),
delegate: this,
);
}
@override
Widget build(BuildContext context) {
return Container();
return BlocProvider(
create: (context) => getIt<GridPropertyBloc>(param1: gridId, param2: fields),
child: BlocBuilder<GridPropertyBloc, GridPropertyState>(
builder: (context, state) {
final cells = state.fields.map((field) {
return _GridPropertyCell(gridId: gridId, field: field);
}).toList();
return ListView.separated(
shrinkWrap: true,
controller: ScrollController(),
separatorBuilder: (context, index) {
return VSpace(GridSize.typeOptionSeparatorHeight);
},
itemCount: cells.length,
itemBuilder: (BuildContext context, int index) {
return cells[index];
},
);
},
),
);
}
String identifier() {
return toString();
}
@override
bool asBarrier() => true;
}
class _GridPropertyCell extends StatelessWidget {
const _GridPropertyCell({Key? key}) : super(key: key);
final Field field;
final String gridId;
const _GridPropertyCell({required this.gridId, required this.field, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container();
final theme = context.watch<AppTheme>();
final checkmark = field.visibility
? svgWidget('home/show', color: theme.iconColor)
: svgWidget('home/hide', color: theme.iconColor);
return Row(
children: [
Expanded(
child: SizedBox(
height: GridSize.typeOptionItemHeight,
child: FlowyButton(
text: FlowyText.medium(field.name, fontSize: 12),
hoverColor: theme.hover,
leftIcon: svgWidget(field.fieldType.iconName(), color: theme.iconColor),
onTap: () {
final fieldCellContext = GridFieldCellContext(
gridId: gridId,
field: field,
);
FieldEditor(
gridId: gridId,
fieldContextLoader: FieldContextLoaderAdaptor(fieldCellContext),
).show(context, anchorDirection: AnchorDirection.bottomRight);
},
),
),
),
FlowyIconButton(
hoverColor: theme.hover,
width: GridSize.typeOptionItemHeight,
onPressed: () {
context.read<GridPropertyBloc>().add(GridPropertyEvent.setFieldVisibility(field.id, !field.visibility));
},
icon: checkmark.padding(all: 6),
)
],
);
}
}

View File

@ -7,34 +7,57 @@ import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
import 'grid_property.dart';
class GridSettingContext {
final String gridId;
final List<Field> fields;
GridSettingContext({
required this.gridId,
required this.fields,
});
}
class GridSettingList extends StatelessWidget with FlowyOverlayDelegate {
class GridSettingList extends StatelessWidget {
final GridSettingContext settingContext;
const GridSettingList({required this.settingContext, Key? key}) : super(key: key);
final Function(GridSettingAction, GridSettingContext) onAction;
const GridSettingList({required this.settingContext, required this.onAction, Key? key}) : super(key: key);
static void show(BuildContext context, GridSettingContext settingContext) {
final list = GridSettingList(
settingContext: settingContext,
onAction: (action, settingContext) {
switch (action) {
case GridSettingAction.filter:
// TODO: Handle this case.
break;
case GridSettingAction.sortBy:
// TODO: Handle this case.
break;
case GridSettingAction.properties:
GridPropertyList(gridId: settingContext.gridId, fields: settingContext.fields).show(context);
break;
}
},
);
void show(BuildContext context) {
FlowyOverlay.of(context).insertWithAnchor(
widget: OverlayContainer(
child: this,
child: list,
constraints: BoxConstraints.loose(const Size(140, 400)),
),
identifier: identifier(),
identifier: list.identifier(),
anchorContext: context,
anchorDirection: AnchorDirection.bottomRight,
style: FlowyOverlayStyle(blur: false),
delegate: this,
);
}
@ -46,17 +69,8 @@ class GridSettingList extends StatelessWidget with FlowyOverlayDelegate {
listenWhen: (previous, current) => previous.selectedAction != current.selectedAction,
listener: (context, state) {
state.selectedAction.foldLeft(null, (_, action) {
switch (action) {
case GridSettingAction.filter:
// TODO: Handle this case.
break;
case GridSettingAction.sortBy:
// TODO: Handle this case.
break;
case GridSettingAction.properties:
// TODO: Handle this case.
break;
}
FlowyOverlay.of(context).remove(identifier());
onAction(action, settingContext);
});
},
child: BlocBuilder<GridSettingBloc, GridSettingState>(

View File

@ -1,25 +1,40 @@
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
import 'package:flowy_infra_ui/style_widget/extension.dart';
import 'package:flutter/material.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/extension.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' hide Row;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
import 'grid_setting.dart';
class GridToolbar extends StatelessWidget {
class GridToolbarContext {
final String gridId;
const GridToolbar({required this.gridId, Key? key}) : super(key: key);
final List<Field> fields;
GridToolbarContext({
required this.gridId,
required this.fields,
});
}
class GridToolbar extends StatelessWidget {
final GridToolbarContext toolbarContext;
const GridToolbar({required this.toolbarContext, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final settingContext = GridSettingContext(
gridId: toolbarContext.gridId,
fields: toolbarContext.fields,
);
return SizedBox(
height: 40,
child: Row(
children: [
SizedBox(width: GridSize.leadingHeaderPadding),
_SettingButton(settingContext: GridSettingContext(gridId: gridId)),
_SettingButton(settingContext: settingContext),
const Spacer(),
],
),
@ -37,7 +52,7 @@ class _SettingButton extends StatelessWidget {
return FlowyIconButton(
hoverColor: theme.hover,
width: 22,
onPressed: () => GridSettingList(settingContext: settingContext).show(context),
onPressed: () => GridSettingList.show(context, settingContext),
icon: svgWidget("grid/setting/setting").padding(horizontal: 3, vertical: 3),
);
}

View File

@ -295,7 +295,7 @@ impl ClientGridEditor {
pub async fn get_field_metas(&self, field_orders: Option<RepeatedFieldOrder>) -> FlowyResult<Vec<FieldMeta>> {
let mut field_metas = self.pad.read().await.get_field_metas(field_orders)?;
field_metas.retain(|field_meta| field_meta.visibility);
// field_metas.retain(|field_meta| field_meta.visibility);
Ok(field_metas)
}