diff --git a/frontend/app_flowy/assets/images/grid/delete.svg b/frontend/app_flowy/assets/images/grid/delete.svg
new file mode 100644
index 0000000000..fcfbf2f6dd
--- /dev/null
+++ b/frontend/app_flowy/assets/images/grid/delete.svg
@@ -0,0 +1,6 @@
+
diff --git a/frontend/app_flowy/assets/images/grid/duplicate.svg b/frontend/app_flowy/assets/images/grid/duplicate.svg
new file mode 100644
index 0000000000..f11048fd2f
--- /dev/null
+++ b/frontend/app_flowy/assets/images/grid/duplicate.svg
@@ -0,0 +1,4 @@
+
diff --git a/frontend/app_flowy/assets/images/grid/hide.svg b/frontend/app_flowy/assets/images/grid/hide.svg
new file mode 100644
index 0000000000..dfb6dbb90c
--- /dev/null
+++ b/frontend/app_flowy/assets/images/grid/hide.svg
@@ -0,0 +1,4 @@
+
diff --git a/frontend/app_flowy/assets/images/grid/left.svg b/frontend/app_flowy/assets/images/grid/left.svg
new file mode 100644
index 0000000000..0f771a3858
--- /dev/null
+++ b/frontend/app_flowy/assets/images/grid/left.svg
@@ -0,0 +1,5 @@
+
diff --git a/frontend/app_flowy/assets/images/grid/right.svg b/frontend/app_flowy/assets/images/grid/right.svg
new file mode 100644
index 0000000000..7d738f4e69
--- /dev/null
+++ b/frontend/app_flowy/assets/images/grid/right.svg
@@ -0,0 +1,5 @@
+
diff --git a/frontend/app_flowy/assets/translations/en.json b/frontend/app_flowy/assets/translations/en.json
index 7717e71229..27574db94e 100644
--- a/frontend/app_flowy/assets/translations/en.json
+++ b/frontend/app_flowy/assets/translations/en.json
@@ -141,5 +141,14 @@
"lightLabel": "Light Mode",
"darkLabel": "Dark Mode"
}
+ },
+ "grid": {
+ "field": {
+ "hide": "Hide",
+ "insertLeft": "Insert Left",
+ "insertRight": "Insert Right",
+ "duplicate": "Duplicate",
+ "delete": "Delete"
+ }
}
}
diff --git a/frontend/app_flowy/lib/startup/home_deps_resolver.dart b/frontend/app_flowy/lib/startup/home_deps_resolver.dart
index 2dcf6ca077..688c53ad2a 100644
--- a/frontend/app_flowy/lib/startup/home_deps_resolver.dart
+++ b/frontend/app_flowy/lib/startup/home_deps_resolver.dart
@@ -3,7 +3,7 @@ import 'package:app_flowy/user/application/user_service.dart';
import 'package:app_flowy/workspace/application/app/prelude.dart';
import 'package:app_flowy/workspace/application/doc/prelude.dart';
import 'package:app_flowy/workspace/application/grid/prelude.dart';
-import 'package:app_flowy/workspace/application/grid/row_listener.dart';
+import 'package:app_flowy/workspace/application/grid/row/row_listener.dart';
import 'package:app_flowy/workspace/application/trash/prelude.dart';
import 'package:app_flowy/workspace/application/workspace/prelude.dart';
import 'package:app_flowy/workspace/application/view/prelude.dart';
@@ -101,10 +101,17 @@ class HomeDepsResolver {
),
);
- getIt.registerFactoryParam, void>(
- (data, _) => ColumnBloc(
+ getIt.registerFactoryParam, void>(
+ (data, _) => GridHeaderBloc(
data: GridColumnData(fields: data),
- service: ColumnService(),
+ service: FieldService(),
+ ),
+ );
+
+ getIt.registerFactoryParam(
+ (field, _) => FieldEditBloc(
+ field: field,
+ service: FieldService(),
),
);
diff --git a/frontend/app_flowy/lib/user/presentation/sign_in_screen.dart b/frontend/app_flowy/lib/user/presentation/sign_in_screen.dart
index f7afa0b4d3..8124241cf4 100644
--- a/frontend/app_flowy/lib/user/presentation/sign_in_screen.dart
+++ b/frontend/app_flowy/lib/user/presentation/sign_in_screen.dart
@@ -174,7 +174,7 @@ class PasswordTextField extends StatelessWidget {
obscureHideIcon: svg("home/show"),
hintText: LocaleKeys.signIn_passwordHint.tr(),
normalBorderColor: theme.shader4,
- highlightBorderColor: theme.red,
+ errorBorderColor: theme.red,
cursorColor: theme.main1,
errorText: context.read().state.passwordError.fold(() => "", (error) => error),
onChanged: (value) => context.read().add(SignInEvent.passwordChanged(value)),
@@ -199,7 +199,7 @@ class EmailTextField extends StatelessWidget {
hintText: LocaleKeys.signIn_emailHint.tr(),
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
normalBorderColor: theme.shader4,
- highlightBorderColor: theme.red,
+ errorBorderColor: theme.red,
cursorColor: theme.main1,
errorText: context.read().state.emailError.fold(() => "", (error) => error),
onChanged: (value) => context.read().add(SignInEvent.emailChanged(value)),
diff --git a/frontend/app_flowy/lib/user/presentation/sign_up_screen.dart b/frontend/app_flowy/lib/user/presentation/sign_up_screen.dart
index 931ccff516..06d4db6da2 100644
--- a/frontend/app_flowy/lib/user/presentation/sign_up_screen.dart
+++ b/frontend/app_flowy/lib/user/presentation/sign_up_screen.dart
@@ -139,7 +139,7 @@ class PasswordTextField extends StatelessWidget {
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
hintText: LocaleKeys.signUp_passwordHint.tr(),
normalBorderColor: theme.shader4,
- highlightBorderColor: theme.red,
+ errorBorderColor: theme.red,
cursorColor: theme.main1,
errorText: context.read().state.passwordError.fold(() => "", (error) => error),
onChanged: (value) => context.read().add(SignUpEvent.passwordChanged(value)),
@@ -167,7 +167,7 @@ class RepeatPasswordTextField extends StatelessWidget {
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
hintText: LocaleKeys.signUp_repeatPasswordHint.tr(),
normalBorderColor: theme.shader4,
- highlightBorderColor: theme.red,
+ errorBorderColor: theme.red,
cursorColor: theme.main1,
errorText: context.read().state.repeatPasswordError.fold(() => "", (error) => error),
onChanged: (value) => context.read().add(SignUpEvent.repeatPasswordChanged(value)),
@@ -192,7 +192,7 @@ class EmailTextField extends StatelessWidget {
hintText: LocaleKeys.signUp_emailHint.tr(),
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
normalBorderColor: theme.shader4,
- highlightBorderColor: theme.red,
+ errorBorderColor: theme.red,
cursorColor: theme.main1,
errorText: context.read().state.emailError.fold(() => "", (error) => error),
onChanged: (value) => context.read().add(SignUpEvent.emailChanged(value)),
diff --git a/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/cell_service.dart b/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/cell_service.dart
index 44cf86e5d2..79b4d2cf52 100644
--- a/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/cell_service.dart
+++ b/frontend/app_flowy/lib/workspace/application/grid/cell_bloc/cell_service.dart
@@ -1,4 +1,4 @@
-import 'package:app_flowy/workspace/application/grid/row_service.dart';
+import 'package:app_flowy/workspace/application/grid/prelude.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart';
diff --git a/frontend/app_flowy/lib/workspace/application/grid/column_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/column_bloc.dart
deleted file mode 100644
index 39badc922a..0000000000
--- a/frontend/app_flowy/lib/workspace/application/grid/column_bloc.dart
+++ /dev/null
@@ -1,42 +0,0 @@
-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 'column_service.dart';
-import 'data.dart';
-
-part 'column_bloc.freezed.dart';
-
-class ColumnBloc extends Bloc {
- final ColumnService service;
- final GridColumnData data;
-
- ColumnBloc({required this.data, required this.service}) : super(ColumnState.initial(data.fields)) {
- on(
- (event, emit) async {
- await event.map(
- initial: (_InitialColumn value) async {},
- createColumn: (_CreateColumn value) {},
- );
- },
- );
- }
-
- @override
- Future close() async {
- return super.close();
- }
-}
-
-@freezed
-abstract class ColumnEvent with _$ColumnEvent {
- const factory ColumnEvent.initial() = _InitialColumn;
- const factory ColumnEvent.createColumn() = _CreateColumn;
-}
-
-@freezed
-abstract class ColumnState with _$ColumnState {
- const factory ColumnState({required List fields}) = _ColumnState;
-
- factory ColumnState.initial(List fields) => ColumnState(fields: fields);
-}
diff --git a/frontend/app_flowy/lib/workspace/application/grid/column_service.dart b/frontend/app_flowy/lib/workspace/application/grid/column_service.dart
deleted file mode 100644
index c074dcf616..0000000000
--- a/frontend/app_flowy/lib/workspace/application/grid/column_service.dart
+++ /dev/null
@@ -1 +0,0 @@
-class ColumnService {}
diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/field_edit_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/field_edit_bloc.dart
new file mode 100644
index 0000000000..2d4aea16da
--- /dev/null
+++ b/frontend/app_flowy/lib/workspace/application/grid/field/field_edit_bloc.dart
@@ -0,0 +1,58 @@
+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 'field_service.dart';
+
+part 'field_edit_bloc.freezed.dart';
+
+class FieldEditBloc extends Bloc {
+ final FieldService service;
+
+ FieldEditBloc({required Field field, required this.service}) : super(FieldEditState.initial(field)) {
+ on(
+ (event, emit) async {
+ await event.map(
+ initial: (_InitialField value) {},
+ createField: (_CreateField value) {},
+ updateFieldName: (_UpdateFieldName value) {
+ //
+ },
+ hideField: (_HideField value) {},
+ deleteField: (_DeleteField value) {},
+ insertField: (_InsertField value) {},
+ duplicateField: (_DuplicateField value) {},
+ );
+ },
+ );
+ }
+
+ @override
+ Future close() async {
+ return super.close();
+ }
+}
+
+@freezed
+class FieldEditEvent with _$FieldEditEvent {
+ const factory FieldEditEvent.initial() = _InitialField;
+ const factory FieldEditEvent.createField() = _CreateField;
+ const factory FieldEditEvent.updateFieldName(String name) = _UpdateFieldName;
+ const factory FieldEditEvent.hideField() = _HideField;
+ const factory FieldEditEvent.duplicateField() = _DuplicateField;
+ const factory FieldEditEvent.insertField({required bool onLeft}) = _InsertField;
+ const factory FieldEditEvent.deleteField() = _DeleteField;
+}
+
+@freezed
+class FieldEditState with _$FieldEditState {
+ const factory FieldEditState({
+ required Field field,
+ required String errorText,
+ }) = _FieldEditState;
+
+ factory FieldEditState.initial(Field field) => FieldEditState(
+ field: field,
+ errorText: '',
+ );
+}
diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart b/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart
new file mode 100644
index 0000000000..48d4aa21bd
--- /dev/null
+++ b/frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart
@@ -0,0 +1 @@
+class FieldService {}
diff --git a/frontend/app_flowy/lib/workspace/application/grid/field/grid_header_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/field/grid_header_bloc.dart
new file mode 100644
index 0000000000..fd22d7a505
--- /dev/null
+++ b/frontend/app_flowy/lib/workspace/application/grid/field/grid_header_bloc.dart
@@ -0,0 +1,42 @@
+import 'package:app_flowy/workspace/application/grid/data.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 'field_service.dart';
+
+part 'grid_header_bloc.freezed.dart';
+
+class GridHeaderBloc extends Bloc {
+ final FieldService service;
+ final GridColumnData data;
+
+ GridHeaderBloc({required this.data, required this.service}) : super(GridHeaderState.initial(data.fields)) {
+ on(
+ (event, emit) async {
+ await event.map(
+ initial: (_InitialHeader value) async {},
+ createField: (_CreateField value) {},
+ );
+ },
+ );
+ }
+
+ @override
+ Future close() async {
+ return super.close();
+ }
+}
+
+@freezed
+class GridHeaderEvent with _$GridHeaderEvent {
+ const factory GridHeaderEvent.initial() = _InitialHeader;
+ const factory GridHeaderEvent.createField() = _CreateField;
+}
+
+@freezed
+class GridHeaderState with _$GridHeaderState {
+ const factory GridHeaderState({required List fields}) = _GridHeaderState;
+
+ factory GridHeaderState.initial(List fields) => GridHeaderState(fields: fields);
+}
diff --git a/frontend/app_flowy/lib/workspace/application/grid/prelude.dart b/frontend/app_flowy/lib/workspace/application/grid/prelude.dart
index d28df04c62..3190ced75d 100644
--- a/frontend/app_flowy/lib/workspace/application/grid/prelude.dart
+++ b/frontend/app_flowy/lib/workspace/application/grid/prelude.dart
@@ -1,10 +1,11 @@
export 'grid_bloc.dart';
-export 'row_bloc.dart';
-export 'row_service.dart';
+export 'row/row_bloc.dart';
+export 'row/row_service.dart';
export 'grid_service.dart';
export 'data.dart';
-export 'column_service.dart';
-export 'column_bloc.dart';
+export 'field/field_service.dart';
+export 'field/grid_header_bloc.dart';
+export 'field/field_edit_bloc.dart';
export 'cell_bloc/text_cell_bloc.dart';
export 'cell_bloc/number_cell_bloc.dart';
export 'cell_bloc/selection_cell_bloc.dart';
diff --git a/frontend/app_flowy/lib/workspace/application/grid/row_bloc.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart
similarity index 97%
rename from frontend/app_flowy/lib/workspace/application/grid/row_bloc.dart
rename to frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart
index 67fcc5cbc2..a30bea39ee 100644
--- a/frontend/app_flowy/lib/workspace/application/grid/row_bloc.dart
+++ b/frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart
@@ -1,9 +1,9 @@
+import 'package:app_flowy/workspace/application/grid/grid_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 'grid_service.dart';
import 'row_listener.dart';
import 'row_service.dart';
diff --git a/frontend/app_flowy/lib/workspace/application/grid/row_listener.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_listener.dart
similarity index 100%
rename from frontend/app_flowy/lib/workspace/application/grid/row_listener.dart
rename to frontend/app_flowy/lib/workspace/application/grid/row/row_listener.dart
diff --git a/frontend/app_flowy/lib/workspace/application/grid/row_service.dart b/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart
similarity index 93%
rename from frontend/app_flowy/lib/workspace/application/grid/row_service.dart
rename to frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart
index 013ae3fb1c..6819a9a40e 100644
--- a/frontend/app_flowy/lib/workspace/application/grid/row_service.dart
+++ b/frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart
@@ -3,7 +3,7 @@ import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
-import 'grid_service.dart';
+import 'package:app_flowy/workspace/application/grid/prelude.dart';
class RowService {
final GridRowData rowData;
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart
index 0daca272b6..e94507ab52 100755
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/layout/sizes.dart
@@ -21,6 +21,11 @@ class GridSize {
vertical: GridSize.cellContentPadding,
);
+ static EdgeInsets get fieldContentInsets => EdgeInsets.symmetric(
+ horizontal: GridSize.cellContentPadding,
+ vertical: GridSize.cellContentPadding,
+ );
+
static EdgeInsets get footerContentInsets => EdgeInsets.fromLTRB(
0,
GridSize.headerContainerPadding,
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/cell_builder.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/cell_builder.dart
index 187a81c1bc..7aa412589e 100755
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/cell_builder.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/cell_builder.dart
@@ -1,4 +1,4 @@
-import 'package:app_flowy/workspace/application/grid/row_service.dart';
+import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/meta.pb.dart';
import 'package:flutter/widgets.dart';
import 'checkbox_cell.dart';
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/checkbox_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/checkbox_cell.dart
index eec381914a..2f067ab1fa 100644
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/checkbox_cell.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/checkbox_cell.dart
@@ -1,6 +1,5 @@
import 'package:app_flowy/startup/startup.dart';
-import 'package:app_flowy/workspace/application/grid/cell_bloc/checkbox_cell_bloc.dart';
-import 'package:app_flowy/workspace/application/grid/row_service.dart';
+import 'package:app_flowy/workspace/application/grid/prelude.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/date_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/date_cell.dart
index 5d9f452c78..5d69783fc5 100644
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/date_cell.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/date_cell.dart
@@ -1,6 +1,5 @@
import 'package:app_flowy/startup/startup.dart';
-import 'package:app_flowy/workspace/application/grid/cell_bloc/date_cell_bloc.dart';
-import 'package:app_flowy/workspace/application/grid/row_service.dart';
+import 'package:app_flowy/workspace/application/grid/prelude.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/number_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/number_cell.dart
index ea3f70fed3..0402b85b12 100644
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/number_cell.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/number_cell.dart
@@ -1,6 +1,5 @@
import 'package:app_flowy/startup/startup.dart';
-import 'package:app_flowy/workspace/application/grid/cell_bloc/number_cell_bloc.dart';
-import 'package:app_flowy/workspace/application/grid/row_service.dart';
+import 'package:app_flowy/workspace/application/grid/prelude.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/text_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/text_cell.dart
index 937c5d84ab..d3a9e88c2e 100644
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/text_cell.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/text_cell.dart
@@ -1,8 +1,6 @@
import 'dart:async';
-
import 'package:app_flowy/startup/startup.dart';
-import 'package:app_flowy/workspace/application/grid/cell_bloc/text_cell_bloc.dart';
-import 'package:app_flowy/workspace/application/grid/row_service.dart';
+import 'package:app_flowy/workspace/application/grid/prelude.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart
index 8b13789179..531d83a1f3 100644
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart
@@ -1 +1,208 @@
+import 'package:app_flowy/startup/startup.dart';
+import 'package:app_flowy/workspace/application/grid/prelude.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/rounded_input_field.dart';
+import 'package:flowy_infra_ui/widget/spacing.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:easy_localization/easy_localization.dart';
+import 'package:app_flowy/generated/locale_keys.g.dart';
+class FieldEditor extends StatelessWidget {
+ final Field field;
+ const FieldEditor({required this.field, Key? key}) : super(key: key);
+
+ static void show(BuildContext context, Field field) {
+ final editor = FieldEditor(field: field);
+ FlowyOverlay.of(context).insertWithAnchor(
+ widget: OverlayContainer(child: editor),
+ identifier: editor.identifier(),
+ anchorContext: context,
+ anchorDirection: AnchorDirection.bottomWithLeftAligned,
+ style: FlowyOverlayStyle(blur: false),
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = context.watch();
+ return BlocProvider(
+ create: (context) => getIt(param1: field)..add(const FieldEditEvent.initial()),
+ child: Container(
+ color: theme.surface,
+ constraints: BoxConstraints.loose(const Size(300, 200)),
+ child: SingleChildScrollView(
+ child: Column(children: [
+ const FieldNameTextField(),
+ // FieldTypeSwitcher(),
+ const VSpace(10),
+ FieldOperationList(
+ onTap: () {
+ FlowyOverlay.of(context).remove(identifier());
+ },
+ ),
+ ]),
+ ),
+ ),
+ );
+ }
+
+ String identifier() {
+ return toString();
+ }
+}
+
+class FieldNameTextField extends StatelessWidget {
+ const FieldNameTextField({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = context.watch();
+ return BlocBuilder(
+ buildWhen: ((previous, current) => previous.field.name == current.field.name),
+ builder: (context, state) {
+ return RoundedInputField(
+ height: 36,
+ style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
+ initialValue: state.field.name,
+ normalBorderColor: theme.shader4,
+ errorBorderColor: theme.red,
+ focusBorderColor: theme.main1,
+ cursorColor: theme.main1,
+ errorText: state.errorText,
+ onChanged: (value) {
+ context.read().add(FieldEditEvent.updateFieldName(value));
+ },
+ );
+ },
+ );
+ }
+}
+
+class FieldTypeSwitcher extends StatelessWidget {
+ const FieldTypeSwitcher({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = context.watch();
+
+ return FlowyButton(
+ text: FlowyText.medium(context.read().state.field.name, fontSize: 12),
+ hoverColor: theme.hover,
+ onTap: () {},
+ leftIcon: svg("editor/details", color: theme.iconColor),
+ );
+ }
+}
+
+class FieldOperationList extends StatelessWidget {
+ final VoidCallback onTap;
+ const FieldOperationList({required this.onTap, Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ final children = FieldAction.values
+ .map((action) => FieldActionItem(
+ action: action,
+ onTap: onTap,
+ ))
+ .toList();
+ return GridView(
+ // https://api.flutter.dev/flutter/widgets/AnimatedList/shrinkWrap.html
+ shrinkWrap: true,
+ gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
+ crossAxisCount: 2,
+ childAspectRatio: 4.0,
+ mainAxisSpacing: 8,
+ ),
+ children: children,
+ );
+ }
+}
+
+class FieldActionItem extends StatelessWidget {
+ final VoidCallback onTap;
+ final FieldAction action;
+ const FieldActionItem({required this.action, required this.onTap, Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = context.watch();
+ return FlowyButton(
+ text: FlowyText.medium(action.title(), fontSize: 12),
+ hoverColor: theme.hover,
+ onTap: () {
+ action.run(context);
+ onTap();
+ },
+ leftIcon: svg(action.iconName(), color: theme.iconColor),
+ );
+ }
+}
+
+enum FieldAction {
+ hide,
+ insertLeft,
+ duplicate,
+ insertRight,
+ delete,
+}
+
+extension _FieldActionExtension on FieldAction {
+ String iconName() {
+ switch (this) {
+ case FieldAction.hide:
+ return 'grid/hide';
+ case FieldAction.insertLeft:
+ return 'grid/left';
+ case FieldAction.insertRight:
+ return 'grid/right';
+ 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.insertLeft:
+ return LocaleKeys.grid_field_insertLeft.tr();
+ case FieldAction.insertRight:
+ return LocaleKeys.grid_field_insertRight.tr();
+ case FieldAction.duplicate:
+ return LocaleKeys.grid_field_duplicate.tr();
+ case FieldAction.delete:
+ return LocaleKeys.grid_field_delete.tr();
+ }
+ }
+
+ void run(BuildContext context) {
+ final bloc = context.read();
+
+ switch (this) {
+ case FieldAction.hide:
+ bloc.add(const FieldEditEvent.hideField());
+ break;
+ case FieldAction.insertLeft:
+ bloc.add(const FieldEditEvent.insertField(onLeft: true));
+ break;
+ case FieldAction.insertRight:
+ bloc.add(const FieldEditEvent.insertField(onLeft: false));
+ break;
+ case FieldAction.duplicate:
+ bloc.add(const FieldEditEvent.duplicateField());
+ break;
+ case FieldAction.delete:
+ bloc.add(const FieldEditEvent.deleteField());
+ break;
+ }
+ }
+}
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header.dart
index a9fb91bb00..40f6a3e3b0 100644
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header.dart
@@ -1,5 +1,5 @@
import 'package:app_flowy/startup/startup.dart';
-import 'package:app_flowy/workspace/application/grid/column_bloc.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';
@@ -44,8 +44,8 @@ class GridHeader extends StatelessWidget {
Widget build(BuildContext context) {
final theme = context.watch();
return BlocProvider(
- create: (context) => getIt(param1: fields)..add(const ColumnEvent.initial()),
- child: BlocBuilder(
+ create: (context) => getIt(param1: fields)..add(const GridHeaderEvent.initial()),
+ child: BlocBuilder(
builder: (context, state) {
final headers = state.fields
.map(
@@ -111,7 +111,7 @@ class CreateColumnButton extends StatelessWidget {
return FlowyButton(
text: const FlowyText.medium('New column', fontSize: 12),
hoverColor: theme.hover,
- onTap: () => context.read().add(const ColumnEvent.createColumn()),
+ onTap: () => context.read().add(const GridHeaderEvent.createField()),
leftIcon: svg("home/add"),
);
}
diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header_cell.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header_cell.dart
index 4e870ae27d..fa8a3692c5 100755
--- a/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header_cell.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header_cell.dart
@@ -1,5 +1,5 @@
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
-import 'package:app_flowy/workspace/presentation/widgets/pop_up_window.dart';
+
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
@@ -8,6 +8,8 @@ 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 'field_editor.dart';
+
class HeaderCell extends StatelessWidget {
final Field field;
const HeaderCell(this.field, {Key? key}) : super(key: key);
@@ -16,44 +18,17 @@ class HeaderCell extends StatelessWidget {
Widget build(BuildContext context) {
final theme = context.watch();
return FlowyButton(
- text: Padding(padding: GridSize.cellContentInsets, child: FlowyText.medium(field.name, fontSize: 12)),
+ text: Padding(
+ padding: GridSize.cellContentInsets,
+ child: FlowyText.medium(field.name, fontSize: 12),
+ ),
hoverColor: theme.hover,
- onTap: () {
- FlowyPoppuWindow.show(
- context,
- size: Size(300, 100),
- child: CusTextField(),
- );
-
- // StyledDialog(
- // child: SingleChildScrollView(
- // child: Container(
- // color: Colors.red,
- // child: TextField(),
- // ),
- // ),
- // ).show(context);
- },
+ onTap: () => FieldEditor.show(context, field),
rightIcon: svg("editor/details", color: theme.iconColor),
);
}
}
-class CusTextField extends StatelessWidget {
- const CusTextField({Key? key}) : super(key: key);
-
- @override
- Widget build(BuildContext context) {
- final theme = context.watch();
- return Container(
- color: theme.bg3,
- child: TextField(
- decoration: InputDecoration(hintText: 'Please enter a text'),
- onSubmitted: print,
- ));
- }
-}
-
class HeaderCellContainer extends StatelessWidget {
final HeaderCell child;
final double width;
diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/pop_up_window.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/pop_up_window.dart
index bea342747b..1803257672 100644
--- a/frontend/app_flowy/lib/workspace/presentation/widgets/pop_up_window.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/widgets/pop_up_window.dart
@@ -22,10 +22,7 @@ class FlowyPoppuWindow extends StatelessWidget {
}) async {
final window = await getWindowInfo();
FlowyOverlay.of(context).insertWithRect(
- widget: SizedBox.fromSize(
- size: size,
- child: FlowyPoppuWindow(child: child),
- ),
+ widget: FlowyPoppuWindow(child: child),
identifier: 'FlowyPoppuWindow',
anchorPosition: Offset(-size.width / 2.0, -size.height / 2.0),
anchorSize: window.frame.size,
diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart
index 11ec524ed4..26760dc411 100644
--- a/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart
+++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_overlay.dart
@@ -5,6 +5,8 @@ import 'package:flowy_infra_ui/src/flowy_overlay/layout.dart';
import 'package:flutter/material.dart';
import 'dart:ui';
+export './overlay_container.dart';
+
/// Specifies how overlay are anchored to the SourceWidget
enum AnchorDirection {
// Corner aligned with a corner of the SourceWidget
diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/list_overlay.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/list_overlay.dart
index b83c454322..6c0a4019d1 100644
--- a/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/list_overlay.dart
+++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/list_overlay.dart
@@ -35,36 +35,29 @@ class ListOverlay extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final theme = context.watch();
const padding = EdgeInsets.symmetric(horizontal: 6, vertical: 6);
double totalHeight = height + padding.vertical;
if (footer != null) {
totalHeight = totalHeight + footer!.height + footer!.padding.vertical;
}
- return Material(
- type: MaterialType.transparency,
- child: Container(
- decoration: FlowyDecoration.decoration(theme.surface, theme.shadowColor.withOpacity(0.1)),
- constraints: BoxConstraints.tight(Size(width, totalHeight)),
- child: Padding(
- padding: padding,
- child: Column(
- children: [
- ListView.builder(
- shrinkWrap: true,
- itemBuilder: itemBuilder,
- itemCount: itemCount,
- controller: controller,
- ),
- if (footer != null)
- Padding(
- padding: footer!.padding,
- child: footer!.widget,
- ),
- ],
+ return OverlayContainer(
+ constraints: BoxConstraints.tight(Size(width, totalHeight)),
+ padding: padding,
+ child: Column(
+ children: [
+ ListView.builder(
+ shrinkWrap: true,
+ itemBuilder: itemBuilder,
+ itemCount: itemCount,
+ controller: controller,
),
- ),
+ if (footer != null)
+ Padding(
+ padding: footer!.padding,
+ child: footer!.widget,
+ ),
+ ],
),
);
}
diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/overlay_container.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/overlay_container.dart
new file mode 100644
index 0000000000..bee123d248
--- /dev/null
+++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/overlay_container.dart
@@ -0,0 +1,30 @@
+import 'package:flowy_infra/theme.dart';
+import 'package:flowy_infra_ui/style_widget/decoration.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+class OverlayContainer extends StatelessWidget {
+ final Widget child;
+ final BoxConstraints? constraints;
+ final EdgeInsets padding;
+ const OverlayContainer({
+ required this.child,
+ this.constraints,
+ this.padding = const EdgeInsets.all(12),
+ Key? key,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ final theme = context.watch();
+ return Material(
+ type: MaterialType.transparency,
+ child: Container(
+ padding: padding,
+ decoration: FlowyDecoration.decoration(theme.surface, theme.shadowColor.withOpacity(0.1)),
+ constraints: constraints,
+ child: child,
+ ),
+ );
+ }
+}
diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart
index a85867e456..b19578fe54 100644
--- a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart
+++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart
@@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
import 'package:flowy_infra/time/duration.dart';
typedef HoverBuilder = Widget Function(BuildContext context, bool onHover);
-
typedef IsOnSelected = bool Function();
class FlowyHover extends StatefulWidget {
@@ -29,23 +28,20 @@ class _FlowyHoverState extends State {
Widget build(BuildContext context) {
return MouseRegion(
cursor: SystemMouseCursors.click,
- onEnter: (p) => setOnHover(true),
- onExit: (p) => setOnHover(false),
+ onEnter: (p) => setState(() => _onHover = true),
+ onExit: (p) => setState(() => _onHover = false),
child: render(),
);
}
- void setOnHover(bool value) => setState(() => _onHover = value);
-
Widget render() {
var showHover = _onHover;
-
- if (showHover == false && widget.isOnSelected != null) {
+ if (!showHover && widget.isOnSelected != null) {
showHover = widget.isOnSelected!();
}
if (showHover) {
- return FlowyHoverBackground(
+ return FlowyHoverContainer(
config: widget.config,
child: widget.builder(context, _onHover),
);
@@ -68,12 +64,11 @@ class HoverDisplayConfig {
required this.hoverColor});
}
-class FlowyHoverBackground extends StatelessWidget {
+class FlowyHoverContainer extends StatelessWidget {
final HoverDisplayConfig config;
-
final Widget child;
- const FlowyHoverBackground({
+ const FlowyHoverContainer({
Key? key,
required this.child,
required this.config,
diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart
index e6e9684854..0cd2265cce 100644
--- a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart
+++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart
@@ -1,85 +1,105 @@
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/widget/rounded_button.dart';
-import 'package:flowy_infra_ui/widget/text_field_container.dart';
import 'package:flutter/material.dart';
import 'package:flowy_infra/time/duration.dart';
-// ignore: must_be_immutable
class RoundedInputField extends StatefulWidget {
final String? hintText;
- final IconData? icon;
final bool obscureText;
final Widget? obscureIcon;
final Widget? obscureHideIcon;
final Color normalBorderColor;
- final Color highlightBorderColor;
+ final Color errorBorderColor;
final Color cursorColor;
+ final Color? focusBorderColor;
final String errorText;
final TextStyle style;
final ValueChanged? onChanged;
final String? initialValue;
- late bool enableObscure;
- var _text = "";
+ final EdgeInsets margin;
+ final EdgeInsets padding;
+ final EdgeInsets contentPadding;
+ final double height;
- RoundedInputField({
+ const RoundedInputField({
Key? key,
this.hintText,
this.errorText = "",
this.initialValue,
- this.icon,
this.obscureText = false,
this.obscureIcon,
this.obscureHideIcon,
this.onChanged,
this.normalBorderColor = Colors.transparent,
- this.highlightBorderColor = Colors.transparent,
+ this.errorBorderColor = Colors.transparent,
+ this.focusBorderColor,
this.cursorColor = Colors.black,
this.style = const TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
- }) : super(key: key) {
- enableObscure = obscureText;
- }
+ this.margin = EdgeInsets.zero,
+ this.padding = EdgeInsets.zero,
+ this.contentPadding = const EdgeInsets.symmetric(horizontal: 10),
+ this.height = 48,
+ }) : super(key: key);
@override
State createState() => _RoundedInputFieldState();
}
class _RoundedInputFieldState extends State {
+ String inputText = "";
+ bool obscuteText = false;
+
+ @override
+ void initState() {
+ obscuteText = widget.obscureText;
+ super.initState();
+ }
+
@override
Widget build(BuildContext context) {
- final Icon? newIcon = widget.icon == null
- ? null
- : Icon(
- widget.icon!,
- color: const Color(0xFF6F35A5),
- );
-
var borderColor = widget.normalBorderColor;
+ var focusBorderColor = widget.focusBorderColor ?? borderColor;
+
if (widget.errorText.isNotEmpty) {
- borderColor = widget.highlightBorderColor;
+ borderColor = widget.errorBorderColor;
+ focusBorderColor = borderColor;
}
List children = [
- TextFieldContainer(
- height: 48,
- borderRadius: Corners.s10Border,
- borderColor: borderColor,
+ Container(
+ margin: widget.margin,
+ padding: widget.padding,
+ height: widget.height,
child: TextFormField(
initialValue: widget.initialValue,
onChanged: (value) {
- widget._text = value;
+ inputText = value;
if (widget.onChanged != null) {
widget.onChanged!(value);
}
setState(() {});
},
cursorColor: widget.cursorColor,
- obscureText: widget.enableObscure,
+ obscureText: obscuteText,
decoration: InputDecoration(
- icon: newIcon,
+ contentPadding: widget.contentPadding,
hintText: widget.hintText,
hintStyle: TextStyle(color: widget.normalBorderColor),
- border: InputBorder.none,
- suffixIcon: suffixIcon(),
+ border: OutlineInputBorder(
+ borderSide: BorderSide(
+ color: borderColor,
+ width: 1.0,
+ ),
+ borderRadius: Corners.s10Border,
+ ),
+ focusedBorder: OutlineInputBorder(
+ borderSide: BorderSide(
+ color: focusBorderColor,
+ width: 1.0,
+ ),
+ borderRadius: Corners.s10Border,
+ ),
+ suffixIcon: obscureIcon(),
),
),
),
@@ -100,39 +120,32 @@ class _RoundedInputFieldState extends State {
return AnimatedSize(
duration: .4.seconds,
curve: Curves.easeInOut,
- child: Column(
- children: children,
- ),
+ child: Column(children: children),
);
}
- Widget? suffixIcon() {
+ Widget? obscureIcon() {
if (widget.obscureText == false) {
return null;
}
- if (widget._text.isEmpty) {
- return SizedBox.fromSize(size: const Size.square(16));
+ const double iconWidth = 16;
+ if (inputText.isEmpty) {
+ return SizedBox.fromSize(size: const Size.square(iconWidth));
}
+ assert(widget.obscureIcon != null && widget.obscureHideIcon != null);
Widget? icon;
- if (widget.obscureText == true) {
- assert(widget.obscureIcon != null && widget.obscureHideIcon != null);
- if (widget.enableObscure) {
- icon = widget.obscureIcon!;
- } else {
- icon = widget.obscureHideIcon!;
- }
- }
-
- if (icon == null) {
- return null;
+ if (obscuteText) {
+ icon = widget.obscureIcon!;
+ } else {
+ icon = widget.obscureHideIcon!;
}
return RoundedImageButton(
- size: 16,
+ size: iconWidth,
press: () {
- widget.enableObscure = !widget.enableObscure;
+ obscuteText = !obscuteText;
setState(() {});
},
child: icon,
diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/text_field_container.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/text_field_container.dart
deleted file mode 100644
index 7c1bf131a1..0000000000
--- a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/text_field_container.dart
+++ /dev/null
@@ -1,40 +0,0 @@
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-
-class TextFieldContainer extends StatelessWidget {
- final Widget child;
- final BorderRadius borderRadius;
- final Color borderColor;
- final double? height;
- final double? width;
- const TextFieldContainer({
- Key? key,
- required this.child,
- this.borderRadius = BorderRadius.zero,
- this.borderColor = Colors.white,
- this.height,
- this.width,
- }) : super(key: key);
-
- @override
- Widget build(BuildContext context) {
- return Container(
- margin: const EdgeInsets.symmetric(vertical: 10),
- padding: const EdgeInsets.symmetric(horizontal: 15),
- height: height,
- width: width,
- decoration: BoxDecoration(
- border: Border.all(color: borderColor),
- color: Colors.white,
- borderRadius: borderRadius,
- ),
- child: Align(alignment: Alignment.center, child: child),
- );
- }
-
- @override
- void debugFillProperties(DiagnosticPropertiesBuilder properties) {
- super.debugFillProperties(properties);
- properties.add(DiagnosticsProperty('child', child));
- }
-}
diff --git a/frontend/app_flowy/pubspec.yaml b/frontend/app_flowy/pubspec.yaml
index 41ee902083..61b69b45f1 100644
--- a/frontend/app_flowy/pubspec.yaml
+++ b/frontend/app_flowy/pubspec.yaml
@@ -111,6 +111,7 @@ flutter:
- assets/images/
- assets/images/home/
- assets/images/editor/
+ - assets/images/grid/
- assets/translations/
# - images/a_dot_ham.jpeg