diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart index b91a29b7c4..07275c442f 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart @@ -402,7 +402,6 @@ class MobileRowDetailPageContentState children: [ if (rowDetailState.numHiddenFields != 0) ...[ const ToggleHiddenFieldsVisibilityButton(), - const VSpace(12), ], MobileRowDetailCreateFieldButton( viewId: viewId, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/option_text_field.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/option_text_field.dart index ac9e290aa5..e495ec463c 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/option_text_field.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/option_text_field.dart @@ -20,6 +20,7 @@ class OptionTextField extends StatelessWidget { Widget build(BuildContext context) { return FlowyOptionTile.textField( controller: controller, + autofocus: true, textFieldPadding: const EdgeInsets.symmetric(horizontal: 12.0), onTextChanged: onTextChanged, leftIcon: FlowySvg( diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/date_picker/mobile_date_picker_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/date_picker/mobile_date_picker_screen.dart index 41e6be0034..5b050faf15 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/date_picker/mobile_date_picker_screen.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/date_picker/mobile_date_picker_screen.dart @@ -72,10 +72,8 @@ class _MobileDateCellEditScreenState extends State { child: const Center(child: DragHandler()), ), _buildHeader(), - Expanded( - child: _DateCellEditBody( - dateCellController: widget.controller, - ), + _DateCellEditBody( + dateCellController: widget.controller, ), ], ), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_bottom_sheets.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_bottom_sheets.dart index d6fb701706..29e9b1e961 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_bottom_sheets.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_bottom_sheets.dart @@ -24,7 +24,8 @@ void showCreateFieldBottomSheet(BuildContext context, String viewId) { minChildSize: 0.7, builder: (context, controller) => FieldOptions( scrollController: controller, - onAddField: (type) async { + mode: FieldOptionMode.add, + onSelectFieldType: (type) async { final optionValues = await context.push( Uri( path: MobileNewPropertyScreen.routeName, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_type_grid.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_type_grid.dart index 075c473266..a95030cdd1 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_type_grid.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_type_grid.dart @@ -8,6 +8,8 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'mobile_field_type_option_editor.dart'; + const _supportedFieldTypes = [ FieldType.RichText, FieldType.Number, @@ -22,18 +24,20 @@ const _supportedFieldTypes = [ class FieldOptions extends StatelessWidget { const FieldOptions({ super.key, - required this.onAddField, + required this.mode, + required this.onSelectFieldType, this.scrollController, }); - final void Function(FieldType) onAddField; + final FieldOptionMode mode; + final void Function(FieldType) onSelectFieldType; final ScrollController? scrollController; @override Widget build(BuildContext context) { return Column( children: [ - const _FieldHeader(), + _FieldHeader(mode: mode), const VSpace(12.0), Expanded( child: SingleChildScrollView( @@ -46,7 +50,7 @@ class FieldOptions extends StatelessWidget { .map( (e) => _Field( type: e, - onTap: () => onAddField(e), + onTap: () => onSelectFieldType(e), ), ) .toList(), @@ -59,7 +63,9 @@ class FieldOptions extends StatelessWidget { } class _FieldHeader extends StatelessWidget { - const _FieldHeader(); + const _FieldHeader({required this.mode}); + + final FieldOptionMode mode; @override Widget build(BuildContext context) { @@ -76,7 +82,10 @@ class _FieldHeader extends StatelessWidget { ), ), FlowyText.medium( - LocaleKeys.titleBar_addField.tr(), + switch (mode) { + FieldOptionMode.add => LocaleKeys.grid_field_newProperty.tr(), + FieldOptionMode.edit => LocaleKeys.grid_field_editProperty.tr(), + }, fontSize: 17.0, ), const HSpace(120), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_type_option_editor.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_type_option_editor.dart index 4139bad650..d8eb66afa7 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_type_option_editor.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_field_type_option_editor.dart @@ -378,7 +378,8 @@ class _PropertyType extends StatelessWidget { minChildSize: 0.7, builder: (context, controller) => FieldOptions( scrollController: controller, - onAddField: (type) { + mode: FieldOptionMode.edit, + onSelectFieldType: (type) { onSelected(type); context.pop(); }, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_quick_field_editor.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_quick_field_editor.dart index 562853b06f..a668c2954c 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_quick_field_editor.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_quick_field_editor.dart @@ -92,15 +92,16 @@ class _QuickEditFieldState extends State { await service.hide(); }, ), - FlowyOptionTile.text( - showTopBorder: false, - text: LocaleKeys.grid_field_insertLeft.tr(), - leftIcon: const FlowySvg(FlowySvgs.insert_left_s), - onTap: () async { - context.pop(); - await service.insertLeft(); - }, - ), + if (!widget.fieldInfo.isPrimary) + FlowyOptionTile.text( + showTopBorder: false, + text: LocaleKeys.grid_field_insertLeft.tr(), + leftIcon: const FlowySvg(FlowySvgs.insert_left_s), + onTap: () async { + context.pop(); + await service.insertLeft(); + }, + ), FlowyOptionTile.text( showTopBorder: false, text: LocaleKeys.grid_field_insertRight.tr(), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_field_list.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_field_list.dart index f24f2fe4bb..2bb53a10bd 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_field_list.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_field_list.dart @@ -209,6 +209,9 @@ class DatabaseFieldListTile extends StatelessWidget { size: const Size.square(20), ), showTopBorder: showTopBorder, + onTap: () { + showEditFieldScreen(context, viewId, fieldInfo); + }, onValueChanged: (value) { final newVisibility = fieldInfo.visibility!.toggle(); context.read().add( diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_view_list.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_view_list.dart index 67951bb542..693ac83183 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_view_list.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_view_list.dart @@ -22,9 +22,7 @@ import 'database_view_quick_actions.dart'; /// [MobileDatabaseViewList] shows a list of all the views in the database and /// adds a button to create a new database view. class MobileDatabaseViewList extends StatelessWidget { - const MobileDatabaseViewList({super.key, required this.databaseController}); - - final DatabaseController databaseController; + const MobileDatabaseViewList({super.key}); @override Widget build(BuildContext context) { @@ -37,7 +35,6 @@ class MobileDatabaseViewList extends StatelessWidget { ...views.mapIndexed( (index, view) => MobileDatabaseViewListButton( view: view, - databaseController: databaseController, showTopBorder: index == 0, ), ), @@ -93,12 +90,10 @@ class MobileDatabaseViewListButton extends StatelessWidget { const MobileDatabaseViewListButton({ super.key, required this.view, - required this.databaseController, required this.showTopBorder, }); final ViewPB view; - final DatabaseController databaseController; final bool showTopBorder; @override @@ -116,7 +111,11 @@ class MobileDatabaseViewListButton extends StatelessWidget { .add(DatabaseTabBarEvent.selectView(view.id)); }, leftIcon: _buildViewIconButton(context, view), - trailing: _trailing(context, isSelected), + trailing: _trailing( + context, + state.tabBarControllerByViewId[view.id]!.controller, + isSelected, + ), showTopBorder: showTopBorder, ); }, @@ -130,7 +129,11 @@ class MobileDatabaseViewListButton extends StatelessWidget { ); } - Widget _trailing(BuildContext context, bool isSelected) { + Widget _trailing( + BuildContext context, + DatabaseController databaseController, + bool isSelected, + ) { final more = FlowyIconButton( icon: FlowySvg( FlowySvgs.three_dots_s, @@ -240,6 +243,7 @@ class _MobileCreateDatabaseViewState extends State { selectedLayout: layoutType, ), FlowyOptionTile.textField( + autofocus: true, controller: controller, ), const VSpace(20), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_view_quick_actions.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_view_quick_actions.dart index 1282ea743d..102a0e58fd 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_view_quick_actions.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_view_quick_actions.dart @@ -2,9 +2,12 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart'; import 'package:appflowy/plugins/database_view/application/database_controller.dart'; +import 'package:appflowy/workspace/application/view/view_bloc.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; import 'edit_database_view_screen.dart'; @@ -35,34 +38,49 @@ class _MobileDatabaseViewQuickActionsState ? MobileEditDatabaseViewScreen( databaseController: widget.databaseController, ) - : Padding( - padding: const EdgeInsets.only(top: 8, bottom: 38), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - _actionButton(context, _Action.edit), - _divider(), - _actionButton(context, _Action.duplicate), - _divider(), - _actionButton(context, _Action.delete), - _divider(), - ], - ), - ); + : _quickActions(context, widget.view); } - Widget _actionButton(BuildContext context, _Action action) { + Widget _quickActions(BuildContext context, ViewPB view) { + final isInline = view.childViews.isNotEmpty; + return Padding( + padding: const EdgeInsets.only(top: 8, bottom: 38), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _actionButton(context, _Action.edit, () { + setState(() => isEditing = true); + }), + if (!isInline) ...[ + _divider(), + _actionButton(context, _Action.duplicate, () { + context.read().add(const ViewEvent.duplicate()); + context.pop(); + }), + _divider(), + _actionButton(context, _Action.delete, () { + context.read().add(const ViewEvent.delete()); + context.pop(); + }), + _divider(), + ], + ], + ), + ); + } + + Widget _actionButton( + BuildContext context, + _Action action, + VoidCallback onTap, + ) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: MobileQuickActionButton( icon: action.icon, text: action.label, color: action.color(context), - onTap: () { - if (action == _Action.edit) { - setState(() => isEditing = true); - } - }, + onTap: onTap, ), ); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/edit_database_view_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/edit_database_view_screen.dart index 8e23ffe275..432daf83ca 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/edit_database_view_screen.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/edit_database_view_screen.dart @@ -209,6 +209,7 @@ class _NameAndIconState extends State<_NameAndIcon> { @override Widget build(BuildContext context) { return FlowyOptionTile.textField( + autofocus: true, controller: textEditingController, onTextChanged: (text) { context.read().add(ViewEvent.rename(text)); diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_option_tile.dart b/frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_option_tile.dart index 6a7b2b6063..85b7cd8e4f 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_option_tile.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_option_tile.dart @@ -27,9 +27,11 @@ class FlowyOptionTile extends StatelessWidget { vertical: 2.0, ), this.isSelected = false, + this.onValueChanged, this.textFieldHintText, this.onTextChanged, this.onTextSubmitted, + this.autofocus, }); factory FlowyOptionTile.text({ @@ -67,6 +69,7 @@ class FlowyOptionTile extends StatelessWidget { Widget? leftIcon, Widget? trailing, String? textFieldHintText, + bool autofocus = false, }) { return FlowyOptionTile._( type: FlowyOptionTileType.textField, @@ -81,6 +84,7 @@ class FlowyOptionTile extends StatelessWidget { textFieldHintText: textFieldHintText, onTextChanged: onTextChanged, onTextSubmitted: onTextSubmitted, + autofocus: autofocus, ); } @@ -114,6 +118,7 @@ class FlowyOptionTile extends StatelessWidget { required String text, required bool isSelected, required void Function(bool value) onValueChanged, + void Function()? onTap, bool showTopBorder = true, bool showBottomBorder = true, Widget? leftIcon, @@ -122,7 +127,8 @@ class FlowyOptionTile extends StatelessWidget { type: FlowyOptionTileType.toggle, text: text, controller: null, - onTap: () => onValueChanged(!isSelected), + onTap: onTap ?? () => onValueChanged(!isSelected), + onValueChanged: onValueChanged, showTopBorder: showTopBorder, showBottomBorder: showBottomBorder, leading: leftIcon, @@ -143,10 +149,14 @@ class FlowyOptionTile extends StatelessWidget { // only used in checkbox or switcher final bool isSelected; + // only used in switcher + final void Function(bool value)? onValueChanged; + // only used in textfield final String? textFieldHintText; final void Function(String value)? onTextChanged; final void Function(String value)? onTextSubmitted; + final bool? autofocus; final FlowyOptionTileType type; @@ -229,6 +239,7 @@ class FlowyOptionTile extends StatelessWidget { alignment: Alignment.center, child: TextField( controller: controller, + autofocus: autofocus ?? false, textInputAction: TextInputAction.done, decoration: InputDecoration( border: InputBorder.none, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/mobile/mobile_tab_bar_header.dart b/frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/mobile/mobile_tab_bar_header.dart index 0816270b5f..2a35a385e8 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/mobile/mobile_tab_bar_header.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/mobile/mobile_tab_bar_header.dart @@ -1,5 +1,6 @@ import 'package:appflowy/plugins/base/emoji/emoji_text.dart'; import 'package:appflowy/plugins/database_view/application/tab_bar_bloc.dart'; +import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database_view/widgets/setting/mobile_database_controls.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; @@ -65,7 +66,7 @@ class _DatabaseViewList extends StatelessWidget { return const SizedBox.shrink(); } - final children = state.tabBars.mapIndexed((index, tabBar) { + final children = state.tabBars.mapIndexed((index, tabBar) { return Padding( padding: EdgeInsetsDirectional.only( start: index == 0 ? 0 : 2, @@ -78,6 +79,8 @@ class _DatabaseViewList extends StatelessWidget { ); }).toList(); + children.insert(0, HSpace(GridSize.leadingHeaderPadding)); + return SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row(children: children), diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/tab_bar_view.dart b/frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/tab_bar_view.dart index 46f6f858cb..ac716bb3f0 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/tab_bar_view.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/tab_bar_view.dart @@ -110,12 +110,9 @@ class _DatabaseTabBarViewState extends State { child: const TabBarHeader(), ); } else { - return Padding( - padding: EdgeInsets.only( - left: GridSize.leadingHeaderPadding, - right: 8, - ), - child: const MobileTabBarHeader(), + return const Padding( + padding: EdgeInsets.only(right: 8), + child: MobileTabBarHeader(), ); } }, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_property.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_property.dart index 816b3a17ee..acfc3e901c 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_property.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/row_property.dart @@ -12,6 +12,7 @@ import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_button.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -314,24 +315,63 @@ class ToggleHiddenFieldsVisibilityButton extends StatelessWidget { ), }; - return SizedBox( - height: 30, - child: FlowyButton( - text: FlowyText.medium(text, color: Theme.of(context).hintColor), - hoverColor: AFThemeExtension.of(context).lightGreyHover, - leftIcon: RotatedBox( - quarterTurns: state.showHiddenFields ? 1 : 3, - child: FlowySvg( - FlowySvgs.arrow_left_s, + if (PlatformExtension.isDesktop) { + return SizedBox( + height: 30, + child: FlowyButton( + text: FlowyText.medium(text, color: Theme.of(context).hintColor), + hoverColor: AFThemeExtension.of(context).lightGreyHover, + leftIcon: RotatedBox( + quarterTurns: state.showHiddenFields ? 1 : 3, + child: FlowySvg( + FlowySvgs.arrow_left_s, + color: Theme.of(context).hintColor, + ), + ), + margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 6), + onTap: () => context.read().add( + const RowDetailEvent.toggleHiddenFieldVisibility(), + ), + ), + ); + } else { + return ConstrainedBox( + constraints: const BoxConstraints(minWidth: double.infinity), + child: TextButton.icon( + style: Theme.of(context).textButtonTheme.style?.copyWith( + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + side: BorderSide.none, + ), + ), + overlayColor: MaterialStateProperty.all( + Theme.of(context).hoverColor, + ), + alignment: AlignmentDirectional.centerStart, + splashFactory: NoSplash.splashFactory, + padding: const MaterialStatePropertyAll( + EdgeInsets.symmetric(vertical: 14, horizontal: 6), + ), + ), + label: FlowyText.medium( + text, + fontSize: 15, color: Theme.of(context).hintColor, ), - ), - margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 6), - onTap: () => context.read().add( - const RowDetailEvent.toggleHiddenFieldVisibility(), + onPressed: () => context + .read() + .add(const RowDetailEvent.toggleHiddenFieldVisibility()), + icon: RotatedBox( + quarterTurns: state.showHiddenFields ? 1 : 3, + child: FlowySvg( + FlowySvgs.arrow_left_s, + color: Theme.of(context).hintColor, ), - ), - ); + ), + ), + ); + } }, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/setting/mobile_database_controls.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/setting/mobile_database_controls.dart index 118a94632e..c2159730f6 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/setting/mobile_database_controls.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/setting/mobile_database_controls.dart @@ -99,9 +99,7 @@ class MobileDatabaseControls extends StatelessWidget { value: context.read(), ), ], - child: MobileDatabaseViewList( - databaseController: controller, - ), + child: const MobileDatabaseViewList(), ); }, ); diff --git a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs index 866040a4c3..edfb0bafac 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs @@ -842,14 +842,14 @@ impl DatabaseEditor { type_option.delete_option(&option.id); } - notify_did_update_database_field(&self.database, field_id)?; - self - .database - .lock() - .fields - .update_field(field_id, |update| { - update.set_type_option(field.field_type, Some(type_option.to_type_option_data())); - }); + let view_editor = self.database_views.get_view_editor(view_id).await?; + update_field_type_option_fn( + &self.database, + &view_editor, + type_option.to_type_option_data(), + field.clone(), + ) + .await?; self .update_cell_with_changeset(view_id, row_id, field_id, cell_changeset)