fix: bottom sheet updates apply immediately (#4220)

* fix: mobile field editor

* fix: immediately update select option

* fix: insert left and right field flow

* fix: create row behavior
This commit is contained in:
Richard Shiue 2023-12-27 23:52:54 +08:00 committed by GitHub
parent ca7b186325
commit ce58737ec5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 203 additions and 188 deletions

View File

@ -2,7 +2,9 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart'; import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart';
import 'package:appflowy/mobile/presentation/database/field/mobile_field_type_option_editor.dart'; import 'package:appflowy/mobile/presentation/database/field/mobile_field_type_option_editor.dart';
import 'package:appflowy/plugins/database_view/application/field/field_backend_service.dart'; import 'package:appflowy/plugins/database_view/application/field/field_backend_service.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
import 'package:appflowy/plugins/database_view/widgets/setting/field_visibility_extension.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -20,7 +22,7 @@ class MobileEditPropertyScreen extends StatefulWidget {
}); });
final String viewId; final String viewId;
final FieldPB field; final FieldInfo field;
@override @override
State<MobileEditPropertyScreen> createState() => State<MobileEditPropertyScreen> createState() =>
@ -28,12 +30,17 @@ class MobileEditPropertyScreen extends StatefulWidget {
} }
class _MobileEditPropertyScreenState extends State<MobileEditPropertyScreen> { class _MobileEditPropertyScreenState extends State<MobileEditPropertyScreen> {
late FieldOptionValues optionValues; late final FieldBackendService fieldService;
late FieldOptionValues field;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
optionValues = FieldOptionValues.fromField(field: widget.field); field = FieldOptionValues.fromField(field: widget.field.field);
fieldService = FieldBackendService(
viewId: widget.viewId,
fieldId: widget.field.id,
);
} }
@override @override
@ -47,24 +54,40 @@ class _MobileEditPropertyScreenState extends State<MobileEditPropertyScreen> {
title: FlowyText.medium( title: FlowyText.medium(
LocaleKeys.grid_field_editProperty.tr(), LocaleKeys.grid_field_editProperty.tr(),
), ),
leading: AppBarCancelButton( leading: AppBarBackButton(
onTap: () => context.pop(), onTap: () => context.pop(),
), ),
leadingWidth: 120,
actions: [
_SaveButton(
onSave: () {
context.pop(optionValues);
},
),
],
), ),
body: FieldOptionEditor( body: FieldOptionEditor(
mode: FieldOptionMode.edit, mode: FieldOptionMode.edit,
isPrimary: widget.field.isPrimary, isPrimary: widget.field.isPrimary,
defaultValues: optionValues, defaultValues: field,
onOptionValuesChanged: (optionValues) { actions: [
this.optionValues = optionValues; if (widget.field.fieldSettings?.visibility.isVisibleState() ?? true)
FieldOptionAction.hide
else
FieldOptionAction.show,
FieldOptionAction.duplicate,
FieldOptionAction.delete,
],
onOptionValuesChanged: (newField) async {
if (newField.name != field.name) {
await fieldService.updateField(name: newField.name);
}
if (newField.type != field.type) {
await fieldService.updateFieldType(fieldType: newField.type);
}
final data = newField.getTypeOptionData();
if (data != null) {
await FieldBackendService.updateFieldTypeOption(
viewId: viewId,
fieldId: widget.field.id,
typeOptionData: data,
);
}
// setState(() => field = newField);
}, },
onAction: (action) { onAction: (action) {
final service = FieldServices( final service = FieldServices(
@ -81,6 +104,9 @@ class _MobileEditPropertyScreenState extends State<MobileEditPropertyScreen> {
case FieldOptionAction.hide: case FieldOptionAction.hide:
service.hide(); service.hide();
break; break;
case FieldOptionAction.show:
service.show();
break;
} }
context.pop(); context.pop();
}, },
@ -88,28 +114,3 @@ class _MobileEditPropertyScreenState extends State<MobileEditPropertyScreen> {
); );
} }
} }
class _SaveButton extends StatelessWidget {
const _SaveButton({
required this.onSave,
});
final VoidCallback onSave;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Align(
alignment: Alignment.center,
child: GestureDetector(
onTap: onSave,
child: FlowyText.medium(
LocaleKeys.button_save.tr(),
color: const Color(0xFF00ADDC),
),
),
),
);
}
}

View File

@ -1,7 +1,7 @@
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/field_info.dart'; import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
import 'package:appflowy/plugins/database_view/application/field/field_service.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@ -12,7 +12,11 @@ import 'mobile_field_type_grid.dart';
import 'mobile_field_type_option_editor.dart'; import 'mobile_field_type_option_editor.dart';
import 'mobile_quick_field_editor.dart'; import 'mobile_quick_field_editor.dart';
void showCreateFieldBottomSheet(BuildContext context, String viewId) { void showCreateFieldBottomSheet(
BuildContext context,
String viewId, {
OrderObjectPositionPB? position,
}) {
showMobileBottomSheet( showMobileBottomSheet(
context, context,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
@ -36,7 +40,7 @@ void showCreateFieldBottomSheet(BuildContext context, String viewId) {
).toString(), ).toString(),
); );
if (optionValues != null) { if (optionValues != null) {
await optionValues.create(viewId: viewId); await optionValues.create(viewId: viewId, position: position);
if (context.mounted) { if (context.mounted) {
context.pop(); context.pop();
} }
@ -57,32 +61,9 @@ Future<FieldOptionValues?> showEditFieldScreen(
MobileEditPropertyScreen.routeName, MobileEditPropertyScreen.routeName,
extra: { extra: {
MobileEditPropertyScreen.argViewId: viewId, MobileEditPropertyScreen.argViewId: viewId,
MobileEditPropertyScreen.argField: field.field, MobileEditPropertyScreen.argField: field,
}, },
); );
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,
);
}
}
return optionValues; return optionValues;
} }

View File

@ -101,7 +101,7 @@ class _Header extends StatelessWidget {
), ),
onPressed: () => context.pop(newFieldId), onPressed: () => context.pop(newFieldId),
child: FlowyText.medium( child: FlowyText.medium(
LocaleKeys.button_save.tr(), LocaleKeys.button_done.tr(),
fontSize: 16, fontSize: 16,
color: Theme.of(context).colorScheme.onPrimary, color: Theme.of(context).colorScheme.onPrimary,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,

View File

@ -55,16 +55,18 @@ class FieldOptionValues {
Future<void> create({ Future<void> create({
required String viewId, required String viewId,
OrderObjectPositionPB? position,
}) async { }) async {
await FieldBackendService.createField( await FieldBackendService.createField(
viewId: viewId, viewId: viewId,
fieldType: type, fieldType: type,
fieldName: name, fieldName: name,
typeOptionData: toTypeOptionBuffer(), typeOptionData: getTypeOptionData(),
position: position,
); );
} }
Uint8List? toTypeOptionBuffer() { Uint8List? getTypeOptionData() {
switch (type) { switch (type) {
case FieldType.RichText: case FieldType.RichText:
case FieldType.URL: case FieldType.URL:
@ -124,6 +126,7 @@ class FieldOptionValues {
enum FieldOptionAction { enum FieldOptionAction {
hide, hide,
show,
duplicate, duplicate,
delete, delete,
} }
@ -134,6 +137,7 @@ class FieldOptionEditor extends StatefulWidget {
required this.mode, required this.mode,
required this.defaultValues, required this.defaultValues,
required this.onOptionValuesChanged, required this.onOptionValuesChanged,
this.actions = const [],
this.onAction, this.onAction,
this.isPrimary = false, this.isPrimary = false,
}); });
@ -143,6 +147,7 @@ class FieldOptionEditor extends StatefulWidget {
final void Function(FieldOptionValues values) onOptionValuesChanged; final void Function(FieldOptionValues values) onOptionValuesChanged;
// only used in edit mode // only used in edit mode
final List<FieldOptionAction> actions;
final void Function(FieldOptionAction action)? onAction; final void Function(FieldOptionAction action)? onAction;
// the primary field can't be deleted, duplicated, and changed type // the primary field can't be deleted, duplicated, and changed type
@ -273,34 +278,44 @@ class _FieldOptionEditorState extends State<FieldOptionEditor> {
} }
List<Widget> _buildOptionActions() { List<Widget> _buildOptionActions() {
return switch (widget.mode) { if (widget.mode == FieldOptionMode.add || widget.actions.isEmpty) {
FieldOptionMode.add => [], return [];
FieldOptionMode.edit => [ }
FlowyOptionTile.text(
text: LocaleKeys.grid_field_hide.tr(), return [
leftIcon: const FlowySvg(FlowySvgs.hide_s), if (widget.actions.contains(FieldOptionAction.hide))
onTap: () => widget.onAction?.call(FieldOptionAction.hide), FlowyOptionTile.text(
text: LocaleKeys.grid_field_hide.tr(),
leftIcon: const FlowySvg(FlowySvgs.hide_s),
onTap: () => widget.onAction?.call(FieldOptionAction.hide),
),
if (widget.actions.contains(FieldOptionAction.show))
FlowyOptionTile.text(
text: LocaleKeys.grid_field_show.tr(),
leftIcon: const FlowySvg(FlowySvgs.show_m, size: Size.square(16)),
onTap: () => widget.onAction?.call(FieldOptionAction.show),
),
if (widget.actions.contains(FieldOptionAction.duplicate) &&
!widget.isPrimary)
FlowyOptionTile.text(
showTopBorder: false,
text: LocaleKeys.button_duplicate.tr(),
leftIcon: const FlowySvg(FlowySvgs.copy_s),
onTap: () => widget.onAction?.call(FieldOptionAction.duplicate),
),
if (widget.actions.contains(FieldOptionAction.delete) &&
!widget.isPrimary)
FlowyOptionTile.text(
showTopBorder: false,
text: LocaleKeys.button_delete.tr(),
textColor: Theme.of(context).colorScheme.error,
leftIcon: FlowySvg(
FlowySvgs.delete_s,
color: Theme.of(context).colorScheme.error,
), ),
if (!widget.isPrimary) ...[ onTap: () => widget.onAction?.call(FieldOptionAction.delete),
FlowyOptionTile.text( ),
showTopBorder: false, ];
text: LocaleKeys.button_duplicate.tr(),
leftIcon: const FlowySvg(FlowySvgs.copy_s),
onTap: () => widget.onAction?.call(FieldOptionAction.duplicate),
),
FlowyOptionTile.text(
showTopBorder: false,
text: LocaleKeys.button_delete.tr(),
textColor: Theme.of(context).colorScheme.error,
leftIcon: FlowySvg(
FlowySvgs.delete_s,
color: Theme.of(context).colorScheme.error,
),
onTap: () => widget.onAction?.call(FieldOptionAction.delete),
),
],
]
};
} }
void _updateOptionValues({ void _updateOptionValues({

View File

@ -6,11 +6,13 @@ import 'package:appflowy/mobile/presentation/database/field/mobile_field_bottom_
import 'package:appflowy/mobile/presentation/widgets/widgets.dart'; import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/plugins/database_view/application/field/field_backend_service.dart'; import 'package:appflowy/plugins/database_view/application/field/field_backend_service.dart';
import 'package:appflowy/plugins/database_view/application/field/field_info.dart'; import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
import 'package:appflowy/plugins/database_view/widgets/setting/field_visibility_extension.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:protobuf/protobuf.dart' hide FieldInfo;
class QuickEditField extends StatefulWidget { class QuickEditField extends StatefulWidget {
const QuickEditField({ const QuickEditField({
@ -35,12 +37,15 @@ class _QuickEditFieldState extends State<QuickEditField> {
); );
late FieldType fieldType; late FieldType fieldType;
late FieldVisibility fieldVisibility;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
fieldType = widget.fieldInfo.fieldType; fieldType = widget.fieldInfo.fieldType;
fieldVisibility = widget.fieldInfo.fieldSettings?.visibility ??
FieldVisibility.AlwaysShown;
controller.text = widget.fieldInfo.field.name; controller.text = widget.fieldInfo.field.name;
} }
@ -69,10 +74,14 @@ class _QuickEditFieldState extends State<QuickEditField> {
text: LocaleKeys.grid_field_editProperty.tr(), text: LocaleKeys.grid_field_editProperty.tr(),
leftIcon: const FlowySvg(FlowySvgs.edit_s), leftIcon: const FlowySvg(FlowySvgs.edit_s),
onTap: () async { onTap: () async {
widget.fieldInfo.field.freeze();
final field = widget.fieldInfo.field.rebuild((field) {
field.name = controller.text;
});
final optionValues = await showEditFieldScreen( final optionValues = await showEditFieldScreen(
context, context,
widget.viewId, widget.viewId,
widget.fieldInfo, widget.fieldInfo.copyWith(field: field),
); );
if (optionValues != null) { if (optionValues != null) {
setState(() { setState(() {
@ -85,11 +94,17 @@ class _QuickEditFieldState extends State<QuickEditField> {
if (!widget.fieldInfo.isPrimary) if (!widget.fieldInfo.isPrimary)
FlowyOptionTile.text( FlowyOptionTile.text(
showTopBorder: false, showTopBorder: false,
text: LocaleKeys.grid_field_hide.tr(), text: fieldVisibility.isVisibleState()
? LocaleKeys.grid_field_hide.tr()
: LocaleKeys.grid_field_show.tr(),
leftIcon: const FlowySvg(FlowySvgs.hide_s), leftIcon: const FlowySvg(FlowySvgs.hide_s),
onTap: () async { onTap: () async {
context.pop(); context.pop();
await service.hide(); if (fieldVisibility.isVisibleState()) {
await service.hide();
} else {
await service.hide();
}
}, },
), ),
if (!widget.fieldInfo.isPrimary) if (!widget.fieldInfo.isPrimary)
@ -99,7 +114,14 @@ class _QuickEditFieldState extends State<QuickEditField> {
leftIcon: const FlowySvg(FlowySvgs.insert_left_s), leftIcon: const FlowySvg(FlowySvgs.insert_left_s),
onTap: () async { onTap: () async {
context.pop(); context.pop();
await service.insertLeft(); showCreateFieldBottomSheet(
context,
widget.viewId,
position: OrderObjectPositionPB(
position: OrderObjectPositionTypePB.Before,
objectId: widget.fieldInfo.id,
),
);
}, },
), ),
FlowyOptionTile.text( FlowyOptionTile.text(
@ -108,7 +130,14 @@ class _QuickEditFieldState extends State<QuickEditField> {
leftIcon: const FlowySvg(FlowySvgs.insert_right_s), leftIcon: const FlowySvg(FlowySvgs.insert_right_s),
onTap: () async { onTap: () async {
context.pop(); context.pop();
await service.insertRight(); showCreateFieldBottomSheet(
context,
widget.viewId,
position: OrderObjectPositionPB(
position: OrderObjectPositionTypePB.After,
objectId: widget.fieldInfo.id,
),
);
}, },
), ),
if (!widget.fieldInfo.isPrimary) ...[ if (!widget.fieldInfo.isPrimary) ...[

View File

@ -110,7 +110,19 @@ class _MobileDatabaseFieldListBody extends StatelessWidget {
)..add(const DatabasePropertyEvent.initial()), )..add(const DatabasePropertyEvent.initial()),
child: BlocBuilder<DatabasePropertyBloc, DatabasePropertyState>( child: BlocBuilder<DatabasePropertyBloc, DatabasePropertyState>(
builder: (context, state) { builder: (context, state) {
final cells = state.fieldContexts if (state.fieldContexts.isEmpty) {
return const SizedBox.shrink();
}
final fields = [...state.fieldContexts];
final firstField = fields.removeAt(0);
final firstCell = DatabaseFieldListTile(
key: ValueKey(firstField.id),
viewId: view.id,
fieldController: databaseController.fieldController,
fieldInfo: firstField,
showTopBorder: true,
);
final cells = fields
.mapIndexed( .mapIndexed(
(index, field) => DatabaseFieldListTile( (index, field) => DatabaseFieldListTile(
key: ValueKey(field.id), key: ValueKey(field.id),
@ -118,14 +130,14 @@ class _MobileDatabaseFieldListBody extends StatelessWidget {
fieldController: databaseController.fieldController, fieldController: databaseController.fieldController,
fieldInfo: field, fieldInfo: field,
index: index, index: index,
showTopBorder: index == 0, showTopBorder: false,
), ),
) )
.toList(); .toList();
return ReorderableListView.builder( return ReorderableListView.builder(
proxyDecorator: (_, index, anim) { proxyDecorator: (_, index, anim) {
final field = state.fieldContexts[index]; final field = fields[index];
return AnimatedBuilder( return AnimatedBuilder(
animation: anim, animation: anim,
builder: (BuildContext context, Widget? child) { builder: (BuildContext context, Widget? child) {
@ -151,10 +163,13 @@ class _MobileDatabaseFieldListBody extends StatelessWidget {
buildDefaultDragHandles: true, buildDefaultDragHandles: true,
shrinkWrap: true, shrinkWrap: true,
onReorder: (from, to) { onReorder: (from, to) {
from++;
to++;
context context
.read<DatabasePropertyBloc>() .read<DatabasePropertyBloc>()
.add(DatabasePropertyEvent.moveField(from, to)); .add(DatabasePropertyEvent.moveField(from, to));
}, },
header: firstCell,
footer: Column( footer: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -176,14 +191,14 @@ class _MobileDatabaseFieldListBody extends StatelessWidget {
class DatabaseFieldListTile extends StatelessWidget { class DatabaseFieldListTile extends StatelessWidget {
const DatabaseFieldListTile({ const DatabaseFieldListTile({
super.key, super.key,
required this.index, this.index,
required this.fieldInfo, required this.fieldInfo,
required this.viewId, required this.viewId,
required this.fieldController, required this.fieldController,
required this.showTopBorder, required this.showTopBorder,
}); });
final int index; final int? index;
final FieldInfo fieldInfo; final FieldInfo fieldInfo;
final String viewId; final String viewId;
final FieldController fieldController; final FieldController fieldController;

View File

@ -36,6 +36,13 @@ class FieldServices {
); );
} }
Future<void> show() async {
await fieldSettingsService.updateFieldSettings(
fieldId: fieldId,
fieldVisibility: FieldVisibility.AlwaysShown,
);
}
Future<void> delete() async { Future<void> delete() async {
await fieldBackendService.delete(); await fieldBackendService.delete();
} }

View File

@ -27,15 +27,20 @@ class GridBloc extends Bloc<GridEvent, GridState> {
_startListening(); _startListening();
await _openGrid(emit); await _openGrid(emit);
}, },
createRow: () async { createRow: (openRowDetail) async {
final result = await RowBackendService.createRow(viewId: viewId); final result = await RowBackendService.createRow(viewId: viewId);
result.fold( result.fold(
(createdRow) => emit(state.copyWith(createdRow: createdRow)), (createdRow) => emit(
state.copyWith(
createdRow: createdRow,
openRowDetail: openRowDetail ?? false,
),
),
(err) => Log.error(err), (err) => Log.error(err),
); );
}, },
resetCreatedRow: () { resetCreatedRow: () {
emit(state.copyWith(createdRow: null)); emit(state.copyWith(createdRow: null, openRowDetail: false));
}, },
deleteRow: (rowInfo) async { deleteRow: (rowInfo) async {
await RowBackendService.deleteRow(rowInfo.viewId, rowInfo.rowId); await RowBackendService.deleteRow(rowInfo.viewId, rowInfo.rowId);
@ -151,7 +156,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
@freezed @freezed
class GridEvent with _$GridEvent { class GridEvent with _$GridEvent {
const factory GridEvent.initial() = InitialGrid; const factory GridEvent.initial() = InitialGrid;
const factory GridEvent.createRow() = _CreateRow; const factory GridEvent.createRow({bool? openRowDetail}) = _CreateRow;
const factory GridEvent.resetCreatedRow() = _ResetCreatedRow; const factory GridEvent.resetCreatedRow() = _ResetCreatedRow;
const factory GridEvent.deleteRow(RowInfo rowInfo) = _DeleteRow; const factory GridEvent.deleteRow(RowInfo rowInfo) = _DeleteRow;
const factory GridEvent.moveRow(int from, int to) = _MoveRow; const factory GridEvent.moveRow(int from, int to) = _MoveRow;
@ -187,6 +192,7 @@ class GridState with _$GridState {
required ChangedReason reason, required ChangedReason reason,
required List<SortInfo> sorts, required List<SortInfo> sorts,
required List<FilterInfo> filters, required List<FilterInfo> filters,
required bool openRowDetail,
}) = _GridState; }) = _GridState;
factory GridState.initial(String viewId) => GridState( factory GridState.initial(String viewId) => GridState(
@ -201,5 +207,6 @@ class GridState with _$GridState {
reason: const InitialListState(), reason: const InitialListState(),
filters: [], filters: [],
sorts: [], sorts: [],
openRowDetail: false,
); );
} }

View File

@ -139,7 +139,7 @@ class _GridPageContentState extends State<GridPageContent> {
listenWhen: (previous, current) => listenWhen: (previous, current) =>
previous.createdRow != current.createdRow, previous.createdRow != current.createdRow,
listener: (context, state) { listener: (context, state) {
if (state.createdRow == null) { if (state.createdRow == null || !state.openRowDetail) {
return; return;
} }
final bloc = context.read<GridBloc>(); final bloc = context.read<GridBloc>();

View File

@ -39,7 +39,9 @@ Widget getGridFabs(BuildContext context) {
backgroundColor: Theme.of(context).primaryColor, backgroundColor: Theme.of(context).primaryColor,
foregroundColor: Colors.white, foregroundColor: Colors.white,
onTap: () { onTap: () {
context.read<GridBloc>().add(const GridEvent.createRow()); context
.read<GridBloc>()
.add(const GridEvent.createRow(openRowDetail: true));
}, },
overlayColor: const MaterialStatePropertyAll<Color>(Color(0xFF009FD1)), overlayColor: const MaterialStatePropertyAll<Color>(Color(0xFF009FD1)),
boxShadow: const BoxShadow( boxShadow: const BoxShadow(

View File

@ -10,7 +10,6 @@ import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_c
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -64,14 +63,8 @@ class _MobileSelectOptionEditorState extends State<MobileSelectOptionEditor> {
const DragHandler(), const DragHandler(),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0), padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: _buildHeader( child: _buildHeader(context),
context,
showSaveButton: state.createOption
.fold(() => false, (a) => a.isNotEmpty) ||
showMoreOptions,
),
), ),
const Divider(),
Expanded( Expanded(
child: Padding( child: Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
@ -88,7 +81,7 @@ class _MobileSelectOptionEditorState extends State<MobileSelectOptionEditor> {
); );
} }
Widget _buildHeader(BuildContext context, {required bool showSaveButton}) { Widget _buildHeader(BuildContext context) {
const iconWidth = 36.0; const iconWidth = 36.0;
const height = 44.0; const height = 44.0;
return Stack( return Stack(
@ -96,9 +89,9 @@ class _MobileSelectOptionEditorState extends State<MobileSelectOptionEditor> {
Align( Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: FlowyIconButton( child: FlowyIconButton(
icon: const FlowySvg( icon: FlowySvg(
FlowySvgs.close_s, showMoreOptions ? FlowySvgs.arrow_left_s : FlowySvgs.close_s,
size: Size.square(iconWidth), size: const Size.square(iconWidth),
), ),
width: iconWidth, width: iconWidth,
iconPadding: EdgeInsets.zero, iconPadding: EdgeInsets.zero,
@ -115,54 +108,6 @@ class _MobileSelectOptionEditorState extends State<MobileSelectOptionEditor> {
), ),
), ),
), ),
Align(
alignment: Alignment.centerRight,
child: !showSaveButton
? const HSpace(iconWidth)
: Container(
padding: const EdgeInsets.symmetric(
vertical: 2.0,
horizontal: 8.0,
),
decoration: const BoxDecoration(
color: Color(0xFF00bcf0),
borderRadius: Corners.s10Border,
),
child: FlowyButton(
text: FlowyText(
LocaleKeys.button_save.tr(),
color: Colors.white,
),
useIntrinsicWidth: true,
onTap: () {
if (showMoreOptions) {
final option = this.option;
if (option == null) {
return;
}
option.freeze();
context.read<SelectOptionCellEditorBloc>().add(
SelectOptionEditorEvent.updateOption(
option.rebuild((p0) {
if (p0.name != renameController.text) {
p0.name = renameController.text;
}
}),
),
);
_popOrBack();
} else if (typingOption.isNotEmpty) {
context.read<SelectOptionCellEditorBloc>().add(
SelectOptionEditorEvent.trySelectOption(
typingOption,
),
);
searchController.clear();
}
},
),
),
),
].map((e) => SizedBox(height: height, child: e)).toList(), ].map((e) => SizedBox(height: height, child: e)).toList(),
); );
} }
@ -170,13 +115,13 @@ class _MobileSelectOptionEditorState extends State<MobileSelectOptionEditor> {
Widget _buildBody(BuildContext context) { Widget _buildBody(BuildContext context) {
if (showMoreOptions && option != null) { if (showMoreOptions && option != null) {
return _MoreOptions( return _MoreOptions(
option: option!, initialOption: option!,
controller: renameController..text = option!.name, controller: renameController,
onDelete: () { onDelete: () {
context context
.read<SelectOptionCellEditorBloc>() .read<SelectOptionCellEditorBloc>()
.add(SelectOptionEditorEvent.deleteOption(option!)); .add(SelectOptionEditorEvent.deleteOption(option!));
context.pop(); _popOrBack();
}, },
onUpdate: (name, color) { onUpdate: (name, color) {
final option = this.option; final option = this.option;
@ -196,7 +141,6 @@ class _MobileSelectOptionEditorState extends State<MobileSelectOptionEditor> {
}), }),
), ),
); );
_popOrBack();
}, },
); );
} }
@ -244,6 +188,7 @@ class _MobileSelectOptionEditorState extends State<MobileSelectOptionEditor> {
onMoreOptions: (option) { onMoreOptions: (option) {
setState(() { setState(() {
this.option = option; this.option = option;
renameController.text = option.name;
showMoreOptions = true; showMoreOptions = true;
}); });
}, },
@ -461,19 +406,26 @@ class _CreateOptionCell extends StatelessWidget {
} }
} }
class _MoreOptions extends StatelessWidget { class _MoreOptions extends StatefulWidget {
const _MoreOptions({ const _MoreOptions({
required this.option, required this.initialOption,
required this.onDelete, required this.onDelete,
required this.onUpdate, required this.onUpdate,
required this.controller, required this.controller,
}); });
final SelectOptionPB option; final SelectOptionPB initialOption;
final VoidCallback onDelete; final VoidCallback onDelete;
final void Function(String? name, SelectOptionColorPB? color) onUpdate; final void Function(String? name, SelectOptionColorPB? color) onUpdate;
final TextEditingController controller; final TextEditingController controller;
@override
State<_MoreOptions> createState() => _MoreOptionsState();
}
class _MoreOptionsState extends State<_MoreOptions> {
late SelectOptionPB option = widget.initialOption;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme.secondaryContainer; final color = Theme.of(context).colorScheme.secondaryContainer;
@ -481,7 +433,6 @@ class _MoreOptions extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const VSpace(8.0),
_buildRenameTextField(context), _buildRenameTextField(context),
const VSpace(16.0), const VSpace(16.0),
_buildDeleteButton(context), _buildDeleteButton(context),
@ -491,8 +442,9 @@ class _MoreOptions extends StatelessWidget {
child: ColoredBox( child: ColoredBox(
color: color, color: color,
child: FlowyText( child: FlowyText(
LocaleKeys.grid_field_optionTitle.tr(), LocaleKeys.grid_selectOption_colorPanelTitle.tr().toUpperCase(),
color: Theme.of(context).hintColor, color: Theme.of(context).hintColor,
fontSize: 13,
), ),
), ),
), ),
@ -508,7 +460,13 @@ class _MoreOptions extends StatelessWidget {
), ),
child: OptionColorList( child: OptionColorList(
selectedColor: option.color, selectedColor: option.color,
onSelectedColor: (color) => onUpdate(null, color), onSelectedColor: (color) {
widget.onUpdate(null, color);
setState(() {
option.freeze();
option = option.rebuild((option) => option.color = color);
});
},
), ),
), ),
), ),
@ -521,7 +479,8 @@ class _MoreOptions extends StatelessWidget {
return ConstrainedBox( return ConstrainedBox(
constraints: const BoxConstraints.tightFor(height: 52.0), constraints: const BoxConstraints.tightFor(height: 52.0),
child: FlowyOptionTile.textField( child: FlowyOptionTile.textField(
controller: controller, onTextChanged: (name) => widget.onUpdate(name, null),
controller: widget.controller,
), ),
); );
} }
@ -530,7 +489,7 @@ class _MoreOptions extends StatelessWidget {
return FlowyOptionTile.text( return FlowyOptionTile.text(
text: LocaleKeys.button_delete.tr(), text: LocaleKeys.button_delete.tr(),
leftIcon: const FlowySvg(FlowySvgs.delete_s), leftIcon: const FlowySvg(FlowySvgs.delete_s),
onTap: onDelete, onTap: widget.onDelete,
); );
} }
} }

View File

@ -620,7 +620,7 @@
"aquaColor": "Aqua", "aquaColor": "Aqua",
"blueColor": "Blue", "blueColor": "Blue",
"deleteTag": "Delete tag", "deleteTag": "Delete tag",
"colorPanelTitle": "Colors", "colorPanelTitle": "Color",
"panelTitle": "Select an option or create one", "panelTitle": "Select an option or create one",
"searchOption": "Search for an option", "searchOption": "Search for an option",
"searchOrCreateOption": "Search or create an option...", "searchOrCreateOption": "Search or create an option...",
@ -778,7 +778,6 @@
"textBlock": { "textBlock": {
"placeholder": "Type '/' for commands" "placeholder": "Type '/' for commands"
}, },
"title": { "title": {
"placeholder": "Untitled" "placeholder": "Untitled"
}, },