diff --git a/frontend/.vscode/launch.json b/frontend/.vscode/launch.json index 34866a9537..c8504d7e48 100644 --- a/frontend/.vscode/launch.json +++ b/frontend/.vscode/launch.json @@ -44,6 +44,17 @@ }, "cwd": "${workspaceRoot}/appflowy_flutter" }, + { + "name": "AF-iOS: Build All", + "request": "launch", + "program": "./lib/main.dart", + "type": "dart", + "preLaunchTask": "AF: Build Appflowy Core For iOS", + "env": { + "RUST_LOG": "trace" + }, + "cwd": "${workspaceRoot}/appflowy_flutter" + }, { "name": "AF-iOS: Clean + Rebuild All", "request": "launch", @@ -55,6 +66,17 @@ }, "cwd": "${workspaceRoot}/appflowy_flutter" }, + { + "name": "AF-iOS-Simulator: Build All", + "request": "launch", + "program": "./lib/main.dart", + "type": "dart", + "preLaunchTask": "AF: Build Appflowy Core For iOS Simulator", + "env": { + "RUST_LOG": "trace" + }, + "cwd": "${workspaceRoot}/appflowy_flutter" + }, { "name": "AF-iOS-Simulator: Clean + Rebuild All", "request": "launch", @@ -66,6 +88,17 @@ }, "cwd": "${workspaceRoot}/appflowy_flutter" }, + { + "name": "AF-Android: Build All", + "request": "launch", + "program": "./lib/main.dart", + "type": "dart", + "preLaunchTask": "AF: Build Appflowy Core For Android", + "env": { + "RUST_LOG": "trace" + }, + "cwd": "${workspaceRoot}/appflowy_flutter" + }, { "name": "AF-Android: Clean + Rebuild All", "request": "launch", diff --git a/frontend/appflowy_flutter/integration_test/database_field_settings_test.dart b/frontend/appflowy_flutter/integration_test/database_field_settings_test.dart index 909d13b49a..4e41eb1c48 100644 --- a/frontend/appflowy_flutter/integration_test/database_field_settings_test.dart +++ b/frontend/appflowy_flutter/integration_test/database_field_settings_test.dart @@ -1,5 +1,5 @@ import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart'; -import 'package:appflowy/plugins/database_view/tab_bar/tab_bar_add_button.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pbenum.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -16,7 +16,7 @@ void main() { await tester.tapGoButton(); await tester.createNewPageWithName(layout: ViewLayoutPB.Grid); - await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.grid); + await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Grid); // create a field await tester.scrollToRight(find.byType(GridPage)); @@ -34,7 +34,7 @@ void main() { await tester.findFieldWithName('New field 1'); // go back to linked database view, expect field to be hidden - await tester.tapTabBarLinkedViewByViewName('grid'); + await tester.tapTabBarLinkedViewByViewName('Grid'); await tester.noFieldWithName('New field 1'); // use the settings button to show the field diff --git a/frontend/appflowy_flutter/integration_test/database_view_test.dart b/frontend/appflowy_flutter/integration_test/database_view_test.dart index 740d1232ec..735cc532b0 100644 --- a/frontend/appflowy_flutter/integration_test/database_view_test.dart +++ b/frontend/appflowy_flutter/integration_test/database_view_test.dart @@ -1,4 +1,3 @@ -import 'package:appflowy/plugins/database_view/tab_bar/tab_bar_add_button.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -18,15 +17,15 @@ void main() { await tester.createNewPageWithName(layout: ViewLayoutPB.Grid); // Create board view - await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board); + await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Board); tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Board); // Create grid view - await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.grid); + await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Grid); tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Grid); // Create calendar view - await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.calendar); + await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Calendar); tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Calendar); await tester.pumpAndSettle(); @@ -39,7 +38,7 @@ void main() { await tester.createNewPageWithName(layout: ViewLayoutPB.Grid); // Create board view - await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board); + await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Board); tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Board); // rename board view @@ -64,7 +63,7 @@ void main() { await tester.createNewPageWithName(layout: ViewLayoutPB.Grid); // Create board view - await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board); + await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Board); tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Board); // delete the board diff --git a/frontend/appflowy_flutter/integration_test/util/database_test_op.dart b/frontend/appflowy_flutter/integration_test/util/database_test_op.dart index 98ae23b2a2..e99dbdc0df 100644 --- a/frontend/appflowy_flutter/integration_test/util/database_test_op.dart +++ b/frontend/appflowy_flutter/integration_test/util/database_test_op.dart @@ -36,8 +36,8 @@ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/so import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/filter_button.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_layout.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/sort_button.dart'; -import 'package:appflowy/plugins/database_view/tab_bar/tab_bar_header.dart'; -import 'package:appflowy/plugins/database_view/tab_bar/tab_bar_add_button.dart'; +import 'package:appflowy/plugins/database_view/tab_bar/desktop/tab_bar_add_button.dart'; +import 'package:appflowy/plugins/database_view/tab_bar/desktop/tab_bar_header.dart'; import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart'; import 'package:appflowy/plugins/database_view/widgets/row/accessory/cell_accessory.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart'; @@ -1381,13 +1381,15 @@ extension AppFlowyDatabaseTest on WidgetTester { await tapButton(unscheduledEvent); } - Future tapCreateLinkedDatabaseViewButton(AddButtonAction action) async { + Future tapCreateLinkedDatabaseViewButton( + DatabaseLayoutPB layoutType, + ) async { final findAddButton = find.byType(AddDatabaseViewButton); await tapButton(findAddButton); final findCreateButton = find.byWidgetPredicate( (widget) => - widget is TarBarAddButtonActionCell && widget.action == action, + widget is TabBarAddButtonActionCell && widget.action == layoutType, ); await tapButton(findCreateButton); } @@ -1554,7 +1556,7 @@ extension AppFlowyDatabaseTest on WidgetTester { Future selectDatabaseLayoutType(DatabaseLayoutPB layout) async { final findLayoutCell = find.byType(DatabaseViewLayoutCell); final findText = find.byWidgetPredicate( - (widget) => widget is FlowyText && widget.text == layout.layoutName(), + (widget) => widget is FlowyText && widget.text == layout.layoutName, ); final button = find.descendant( diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/tab_bar_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/tab_bar_bloc.dart index 91bf95f9a6..dac1deb609 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/tab_bar_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/application/tab_bar_bloc.dart @@ -1,9 +1,11 @@ -import 'package:appflowy/plugins/database_view/tab_bar/tab_bar_add_button.dart'; import 'package:appflowy/plugins/database_view/tab_bar/tab_bar_view.dart'; +import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart'; import 'package:appflowy/workspace/application/view/prelude.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -48,8 +50,8 @@ class DatabaseTabBarBloc ); } }, - createView: (action) { - _createLinkedView(action.name, action.layoutType); + createView: (layout) { + _createLinkedView(layout.layoutType, layout.layoutName); }, deleteView: (String viewId) async { final result = await ViewBackendService.delete(viewId: viewId); @@ -89,10 +91,10 @@ class DatabaseTabBarBloc (element) => element.viewId == viewId, ); if (index != -1) { - final tarBar = allTabBars.removeAt(index); + final tabBar = allTabBars.removeAt(index); // Dispose the controller when the tab is removed. final controller = - tabBarControllerByViewId.remove(tarBar.viewId); + tabBarControllerByViewId.remove(tabBar.viewId); controller?.dispose(); } @@ -163,7 +165,7 @@ class DatabaseTabBarBloc return tabBarControllerByViewId; } - Future _createLinkedView(String name, ViewLayoutPB layoutType) async { + Future _createLinkedView(ViewLayoutPB layoutType, String name) async { final viewId = state.parentView.id; final databaseIdOrError = await DatabaseViewBackendService(viewId: viewId).getDatabaseId(); @@ -207,7 +209,7 @@ class DatabaseTabBarEvent with _$DatabaseTabBarEvent { List childViews, ) = _DidLoadChildViews; const factory DatabaseTabBarEvent.selectView(String viewId) = _DidSelectView; - const factory DatabaseTabBarEvent.createView(AddButtonAction action) = + const factory DatabaseTabBarEvent.createView(DatabaseLayoutPB layout) = _CreateView; const factory DatabaseTabBarEvent.renameView(String viewId, String newName) = _RenameView; @@ -252,7 +254,9 @@ class DatabaseTabBar extends Equatable { DatabaseTabBar({ required this.view, - }) : _builder = view.tarBarItem(); + }) : _builder = PlatformExtension.isMobile + ? view.mobileTabBarItem() + : view.tabBarItem(); @override List get props => [view.hashCode]; diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart index ced36703d6..9c845787e0 100755 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart @@ -1,9 +1,10 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database_view/application/row/row_service.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_setting_bar.dart'; -import 'package:appflowy/plugins/database_view/tab_bar/setting_menu.dart'; +import 'package:appflowy/plugins/database_view/tab_bar/desktop/setting_menu.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cell_builder.dart'; import 'package:appflowy_backend/log.dart'; +import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui_web.dart'; @@ -40,7 +41,7 @@ class ToggleExtensionNotifier extends ChangeNotifier { } } -class GridPageTabBarBuilderImpl implements DatabaseTabBarItemBuilder { +class DesktopGridTabBarBuilderImpl implements DatabaseTabBarItemBuilder { final _toggleExtension = ToggleExtensionNotifier(); @override @@ -255,6 +256,15 @@ class _GridRows extends StatelessWidget { GridState state, List rowInfos, ) { + final children = rowInfos.mapIndexed((index, rowInfo) { + return _renderRow( + context, + rowInfo.rowId, + isDraggable: state.reorderable, + index: index, + ); + }).toList() + ..add(const GridRowBottomBar(key: Key('gridFooter'))); return ReorderableListView.builder( /// TODO(Xazin): Resolve inconsistent scrollbar behavior /// This is a workaround related to @@ -274,18 +284,7 @@ class _GridRows extends StatelessWidget { context.read().add(GridEvent.moveRow(fromIndex, toIndex)); }, itemCount: rowInfos.length + 1, // the extra item is the footer - itemBuilder: (context, index) { - if (index < rowInfos.length) { - final rowInfo = rowInfos[index]; - return _renderRow( - context, - rowInfo.rowId, - isDraggable: state.reorderable, - index: index, - ); - } - return const GridRowBottomBar(key: Key('gridFooter')); - }, + itemBuilder: (context, index) => children[index], ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/mobile_grid_page.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/mobile_grid_page.dart new file mode 100644 index 0000000000..b559e55692 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/mobile_grid_page.dart @@ -0,0 +1,436 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/database_view/application/database_controller.dart'; +import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; +import 'package:appflowy/plugins/database_view/application/row/row_cache.dart'; +import 'package:appflowy/plugins/database_view/application/row/row_controller.dart'; +import 'package:appflowy/plugins/database_view/application/row/row_service.dart'; +import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart'; +import 'package:appflowy/plugins/database_view/tab_bar/tab_bar_view.dart'; +import 'package:appflowy/plugins/database_view/widgets/row/cell_builder.dart'; +import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart'; +import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/theme_extension.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flowy_infra_ui/style_widget/scrolling/styled_scrollview.dart'; +import 'package:flowy_infra_ui/widget/error_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:linked_scroll_controller/linked_scroll_controller.dart'; + +import 'grid_page.dart'; +import 'grid_scroll.dart'; +import 'layout/layout.dart'; +import 'layout/sizes.dart'; +import 'widgets/footer/grid_footer.dart'; +import 'widgets/header/grid_header.dart'; +import 'widgets/row/row.dart'; +import 'widgets/shortcuts.dart'; +import 'widgets/toolbar/mobile_grid_setting.dart'; + +class MobileGridTabBarBuilderImpl implements DatabaseTabBarItemBuilder { + final _toggleExtension = ToggleExtensionNotifier(); + + @override + Widget content( + BuildContext context, + ViewPB view, + DatabaseController controller, + bool shrinkWrap, + ) { + return MobileGridPage( + key: _makeValueKey(controller), + view: view, + databaseController: controller, + ); + } + + @override + Widget settingBar(BuildContext context, DatabaseController controller) { + return MobileGridSettingButton( + key: _makeValueKey(controller), + controller: controller, + toggleExtension: _toggleExtension, + ); + } + + @override + Widget settingBarExtension( + BuildContext context, + DatabaseController controller, + ) { + return const SizedBox.shrink(); + } + + ValueKey _makeValueKey(DatabaseController controller) { + return ValueKey(controller.viewId); + } +} + +class MobileGridPage extends StatefulWidget { + final DatabaseController databaseController; + const MobileGridPage({ + required this.view, + required this.databaseController, + this.onDeleted, + super.key, + }); + + final ViewPB view; + final VoidCallback? onDeleted; + + @override + State createState() => _MobileGridPageState(); +} + +class _MobileGridPageState extends State { + @override + Widget build(BuildContext context) { + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => GridBloc( + view: widget.view, + databaseController: widget.databaseController, + )..add(const GridEvent.initial()), + ), + ], + child: BlocBuilder( + builder: (context, state) { + return state.loadingState.map( + loading: (_) => + const Center(child: CircularProgressIndicator.adaptive()), + finish: (result) => result.successOrFail.fold( + (_) => GridShortcuts( + child: GridPageContent(view: widget.view), + ), + (err) => FlowyErrorPage.message( + err.toString(), + howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(), + ), + ), + ); + }, + ), + ); + } +} + +class GridPageContent extends StatefulWidget { + final ViewPB view; + const GridPageContent({ + required this.view, + super.key, + }); + + @override + State createState() => _GridPageContentState(); +} + +class _GridPageContentState extends State { + final _scrollController = GridScrollController( + scrollGroupController: LinkedScrollControllerGroup(), + ); + late final ScrollController headerScrollController; + + @override + void initState() { + super.initState(); + headerScrollController = _scrollController.linkHorizontalController(); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + buildWhen: (previous, current) => previous.fields != current.fields, + builder: (context, state) { + final contentWidth = GridLayout.headerWidth(state.fields.fields); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(right: 14), + child: + _GridHeader(headerScrollController: headerScrollController), + ), + _GridRows( + viewId: state.viewId, + contentWidth: contentWidth, + scrollController: _scrollController, + ), + ], + ); + }, + ); + } +} + +class _GridHeader extends StatelessWidget { + final ScrollController headerScrollController; + const _GridHeader({required this.headerScrollController}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return GridHeaderSliverAdaptor( + viewId: state.viewId, + fieldController: + context.read().databaseController.fieldController, + anchorScrollController: headerScrollController, + ); + }, + ); + } +} + +class _GridRows extends StatelessWidget { + final String viewId; + final double contentWidth; + final GridScrollController scrollController; + + const _GridRows({ + required this.viewId, + required this.contentWidth, + required this.scrollController, + }); + + @override + Widget build(BuildContext context) { + return Expanded( + child: _WrapScrollView( + scrollController: scrollController, + contentWidth: contentWidth, + child: BlocBuilder( + buildWhen: (previous, current) => current.reason.maybeWhen( + reorderRows: () => true, + reorderSingleRow: (reorderRow, rowInfo) => true, + delete: (item) => true, + insert: (item) => true, + orElse: () => false, + ), + builder: (context, state) { + final rowInfos = state.rowInfos; + final behavior = ScrollConfiguration.of(context).copyWith( + scrollbars: false, + physics: const ClampingScrollPhysics(), + ); + return ScrollConfiguration( + behavior: behavior, + child: _renderList(context, state, rowInfos), + ); + }, + ), + ), + ); + } + + Widget _renderList( + BuildContext context, + GridState state, + List rowInfos, + ) { + final children = rowInfos.mapIndexed((index, rowInfo) { + return ReorderableDelayedDragStartListener( + key: ValueKey(rowInfo.rowMeta.id), + index: index, + child: _renderRow( + context, + rowInfo.rowId, + isDraggable: state.reorderable, + index: index, + ), + ); + }).toList(); + + return ReorderableListView.builder( + scrollController: scrollController.verticalController, + buildDefaultDragHandles: false, + proxyDecorator: (child, index, animation) => Material( + color: Colors.transparent, + child: child, + ), + onReorder: (fromIndex, newIndex) { + final toIndex = newIndex > fromIndex ? newIndex - 1 : newIndex; + if (fromIndex == toIndex) { + return; + } + context.read().add(GridEvent.moveRow(fromIndex, toIndex)); + }, + itemCount: rowInfos.length, + itemBuilder: (context, index) => children[index], + footer: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: GridSize.footerContentInsets, + child: const SizedBox( + height: 42, + child: GridAddRowButton( + key: Key('gridFooter'), + ), + ), + ), + Container( + height: 30, + alignment: AlignmentDirectional.centerStart, + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), + child: const _GridFooter(), + ), + ], + ), + ); + } + + Widget _renderRow( + BuildContext context, + RowId rowId, { + int? index, + required bool isDraggable, + Animation? animation, + }) { + final rowCache = context.read().getRowCache(rowId); + final rowMeta = rowCache.getRow(rowId)?.rowMeta; + + /// Return placeholder widget if the rowMeta is null. + if (rowMeta == null) return const SizedBox.shrink(); + + final fieldController = + context.read().databaseController.fieldController; + final dataController = RowController( + viewId: viewId, + rowMeta: rowMeta, + rowCache: rowCache, + ); + + final child = GridRow( + key: ValueKey(rowMeta.id), + rowId: rowId, + viewId: viewId, + index: index, + isDraggable: isDraggable, + dataController: dataController, + cellBuilder: GridCellBuilder(cellCache: dataController.cellCache), + openDetailPage: (context, cellBuilder) { + _openRowDetailPage( + context, + rowId, + fieldController, + rowCache, + cellBuilder, + ); + }, + ); + + if (animation != null) { + return SizeTransition( + sizeFactor: animation, + child: child, + ); + } + + return child; + } + + void _openRowDetailPage( + BuildContext context, + RowId rowId, + FieldController fieldController, + RowCache rowCache, + GridCellBuilder cellBuilder, + ) { + final rowMeta = rowCache.getRow(rowId)?.rowMeta; + // Most of the cases, the rowMeta should not be null. + if (rowMeta != null) { + final dataController = RowController( + viewId: viewId, + rowMeta: rowMeta, + rowCache: rowCache, + ); + + FlowyOverlay.show( + context: context, + builder: (BuildContext context) { + return RowDetailPage( + cellBuilder: cellBuilder, + rowController: dataController, + ); + }, + ); + } else { + Log.warn('RowMeta is null for rowId: $rowId'); + } + } +} + +class _WrapScrollView extends StatelessWidget { + const _WrapScrollView({ + required this.contentWidth, + required this.scrollController, + required this.child, + }); + + final GridScrollController scrollController; + final double contentWidth; + final Widget child; + + @override + Widget build(BuildContext context) { + return ScrollbarListStack( + axis: Axis.vertical, + controller: scrollController.verticalController, + barSize: GridSize.scrollBarSize, + autoHideScrollbar: false, + child: StyledSingleChildScrollView( + autoHideScrollbar: false, + controller: scrollController.horizontalController, + axis: Axis.horizontal, + child: SizedBox( + width: contentWidth, + child: child, + ), + ), + ); + } +} + +class _GridFooter extends StatelessWidget { + const _GridFooter(); + + @override + Widget build(BuildContext context) { + return BlocSelector( + selector: (state) => state.rowCount, + builder: (context, rowCount) { + return Padding( + padding: GridSize.contentInsets, + child: RichText( + text: TextSpan( + text: "${LocaleKeys.grid_row_count.tr()} :", + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).hintColor, + ), + children: [ + TextSpan( + text: ' $rowCount', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: AFThemeExtension.of(context).gridRowCountColor, + ), + ), + ], + ), + ), + ); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/footer/grid_footer.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/footer/grid_footer.dart index 1cece2db59..e7533b10ab 100755 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/footer/grid_footer.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/footer/grid_footer.dart @@ -2,6 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; @@ -15,16 +16,18 @@ class GridAddRowButton extends StatelessWidget { @override Widget build(BuildContext context) { + final color = + PlatformExtension.isMobile ? null : Theme.of(context).hintColor; return FlowyButton( text: FlowyText.medium( LocaleKeys.grid_row_newRow.tr(), - color: Theme.of(context).colorScheme.tertiary, + color: color, ), hoverColor: AFThemeExtension.of(context).lightGreyHover, onTap: () => context.read().add(const GridEvent.createRow()), leftIcon: FlowySvg( FlowySvgs.add_s, - color: Theme.of(context).colorScheme.tertiary, + color: color, ), ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell.dart index 6d832235c0..9dd538d958 100755 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/field_cell.dart @@ -81,7 +81,6 @@ class _GridFieldCellState extends State { width: state.width, child: Stack( alignment: Alignment.centerRight, - fit: StackFit.expand, children: [button, line], ), ); @@ -114,7 +113,6 @@ class _GridHeaderCellContainer extends StatelessWidget { ); final decoration = BoxDecoration( border: Border( - top: borderSide, right: borderSide, bottom: borderSide, ), @@ -123,10 +121,7 @@ class _GridHeaderCellContainer extends StatelessWidget { return Container( width: width, decoration: decoration, - child: ConstrainedBox( - constraints: const BoxConstraints.expand(), - child: child, - ), + child: child, ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/grid_header.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/grid_header.dart index 022991015a..d580496bef 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/grid_header.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/grid_header.dart @@ -3,7 +3,9 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart'; import 'package:appflowy/plugins/database_view/grid/application/grid_header_bloc.dart'; +import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/mobile_field_cell.dart'; import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:easy_localization/easy_localization.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra/theme_extension.dart'; @@ -52,10 +54,7 @@ class _GridHeaderSliverAdaptorState extends State { return SingleChildScrollView( scrollDirection: Axis.horizontal, controller: widget.anchorScrollController, - child: SizedBox( - height: GridSize.headerHeight, - child: _GridHeader(viewId: widget.viewId), - ), + child: _GridHeader(viewId: widget.viewId), ); }, ), @@ -94,25 +93,36 @@ class _GridHeaderState extends State<_GridHeader> { builder: (context, state) { final cells = state.fields .map( - (fieldInfo) => GridFieldCell( - key: _getKeyById(fieldInfo.id), - viewId: widget.viewId, - fieldInfo: fieldInfo, - ), + (fieldInfo) => PlatformExtension.isDesktop + ? GridFieldCell( + key: _getKeyById(fieldInfo.id), + viewId: widget.viewId, + fieldInfo: fieldInfo, + ) + : MobileFieldButton( + key: _getKeyById(fieldInfo.id), + viewId: widget.viewId, + field: fieldInfo, + ), ) .toList(); - return Container( + return ColoredBox( color: Theme.of(context).colorScheme.surface, child: RepaintBoundary( child: ReorderableRow( - crossAxisAlignment: CrossAxisAlignment.stretch, + crossAxisAlignment: CrossAxisAlignment.center, scrollController: ScrollController(), header: const _CellLeading(), - needsLongPressDraggable: false, + needsLongPressDraggable: PlatformExtension.isMobile, footer: _CellTrailing(viewId: widget.viewId), onReorder: (int oldIndex, int newIndex) { - _onReorder(cells, oldIndex, context, newIndex); + _onReorder( + cells, + oldIndex, + context, + newIndex, + ); }, children: cells, ), @@ -123,13 +133,13 @@ class _GridHeaderState extends State<_GridHeader> { } void _onReorder( - List cells, + List cells, int oldIndex, BuildContext context, int newIndex, ) { if (cells.length > oldIndex) { - final field = cells[oldIndex].fieldInfo.field; + final field = (cells[oldIndex] as GridFieldCell).fieldInfo.field; context .read() .add(GridHeaderEvent.moveField(field, oldIndex, newIndex)); @@ -159,7 +169,7 @@ class _CellTrailing extends StatelessWidget { return Container( width: GridSize.trailHeaderPadding, decoration: BoxDecoration( - border: Border(top: borderSide, bottom: borderSide), + border: Border(bottom: borderSide), ), padding: GridSize.headerContentInsets, child: CreateFieldButton(viewId: viewId), @@ -189,8 +199,14 @@ class _CreateFieldButtonState extends State { constraints: BoxConstraints.loose(const Size(240, 600)), triggerActions: PopoverTriggerFlags.none, child: FlowyButton( + margin: PlatformExtension.isDesktop + ? GridSize.cellContentInsets + : const EdgeInsets.symmetric(vertical: 12, horizontal: 8), radius: BorderRadius.zero, - text: FlowyText.medium(LocaleKeys.grid_field_newProperty.tr()), + text: FlowyText.medium( + LocaleKeys.grid_field_newProperty.tr(), + overflow: TextOverflow.ellipsis, + ), hoverColor: AFThemeExtension.of(context).greyHover, onTap: () async { final result = await TypeOptionBackendService.createFieldTypeOption( diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/mobile_field_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/mobile_field_cell.dart new file mode 100755 index 0000000000..996de3ad03 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/header/mobile_field_cell.dart @@ -0,0 +1,65 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/plugins/database_view/application/field/field_info.dart'; + +import 'package:flowy_infra/theme_extension.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; + +import 'field_type_extension.dart'; + +class MobileFieldButton extends StatelessWidget { + final String viewId; + final FieldInfo field; + final int? maxLines; + final BorderRadius? radius; + final EdgeInsets? margin; + + const MobileFieldButton({ + required this.viewId, + required this.field, + this.maxLines = 1, + this.radius = BorderRadius.zero, + this.margin, + super.key, + }); + + @override + Widget build(BuildContext context) { + final border = BorderSide( + color: Theme.of(context).dividerColor, + width: 1.0, + ); + return Container( + width: field.fieldSettings!.width.toDouble(), + decoration: BoxDecoration( + border: Border(right: border, bottom: border), + ), + child: TextButton( + onLongPress: () { + debugPrint("gimme the bottom drawer"); + }, + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), + ), + onPressed: () {}, + child: Row( + children: [ + FlowySvg( + field.fieldType.icon(), + color: Theme.of(context).iconTheme.color, + ), + const HSpace(6), + Expanded( + child: FlowyText.medium( + field.name, + maxLines: maxLines, + overflow: TextOverflow.ellipsis, + color: AFThemeExtension.of(context).textColor, + ), + ), + ], + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/row.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/row.dart index 97311b51b5..d11b787f9c 100755 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/row.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/row/row.dart @@ -54,33 +54,30 @@ class _GridRowState extends State { rowId: widget.rowId, dataController: widget.dataController, viewId: widget.viewId, - ); - _rowBloc.add(const RowEvent.initial()); + )..add(const RowEvent.initial()); } @override Widget build(BuildContext context) { return BlocProvider.value( value: _rowBloc, - child: _RowEnterRegion( - child: BlocBuilder( - // The row need to rebuild when the cell count changes. - buildWhen: (p, c) => - p.cellByFieldId.length != c.cellByFieldId.length || - p.rowSource != c.rowSource, - builder: (context, state) { - final content = Expanded( - child: RowContent( - builder: widget.cellBuilder, - onExpand: () => widget.openDetailPage( - context, - widget.cellBuilder, - ), + child: BlocBuilder( + // The row need to rebuild when the cell count changes. + buildWhen: (p, c) => p.rowSource != c.rowSource, + builder: (context, state) { + final content = Expanded( + child: RowContent( + builder: widget.cellBuilder, + onExpand: () => widget.openDetailPage( + context, + widget.cellBuilder, ), - ); + ), + ); - return Row( - key: ValueKey(state.rowSource), + return _RowEnterRegion( + key: ValueKey(state.rowSource), + child: Row( children: [ _RowLeading( index: widget.index, @@ -88,9 +85,9 @@ class _GridRowState extends State { ), content, ], - ); - }, - ), + ), + ); + }, ), ); } @@ -155,19 +152,18 @@ class _RowLeadingState extends State<_RowLeading> { mainAxisAlignment: MainAxisAlignment.center, children: [ const InsertRowButton(), - if (isDraggable) ...[ + if (isDraggable) ReorderableDragStartListener( index: widget.index!, child: RowMenuButton( isDragEnabled: isDraggable, openMenu: popoverController.show, ), - ), - ] else ...[ + ) + else RowMenuButton( openMenu: popoverController.show, ), - ], ], ); } @@ -253,8 +249,6 @@ class RowContent extends StatelessWidget { builder: (context, state) { return IntrinsicHeight( child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ ..._makeCells(context, state.cellByFieldId), @@ -277,7 +271,6 @@ class RowContent extends StatelessWidget { return CellContainer( width: cellId.fieldInfo.fieldSettings?.width.toDouble() ?? 140, isPrimary: cellId.fieldInfo.field.isPrimary, - cellContainerNotifier: CellContainerNotifier(child), accessoryBuilder: (buildContext) { final builder = child.accessoryBuilder; final List accessories = []; @@ -317,7 +310,6 @@ class RowContent extends StatelessWidget { bottom: BorderSide(color: Theme.of(context).dividerColor), ), ), - child: const SizedBox.shrink(), ), ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_layout.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_layout.dart index 3f9754582d..cb865b9288 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_layout.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/grid_layout.dart @@ -90,7 +90,7 @@ class DatabaseViewLayoutCell extends StatelessWidget { child: FlowyButton( hoverColor: AFThemeExtension.of(context).lightGreyHover, text: FlowyText.medium( - databaseLayout.layoutName(), + databaseLayout.layoutName, color: AFThemeExtension.of(context).textColor, ), leftIcon: FlowySvg( diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/mobile_grid_setting.dart b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/mobile_grid_setting.dart new file mode 100644 index 0000000000..aa63dd4719 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/widgets/toolbar/mobile_grid_setting.dart @@ -0,0 +1,63 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/plugins/database_view/application/database_controller.dart'; +import 'package:appflowy/plugins/database_view/grid/application/filter/filter_menu_bloc.dart'; +import 'package:appflowy/plugins/database_view/grid/application/sort/sort_menu_bloc.dart'; +import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MobileGridSettingButton extends StatelessWidget { + final DatabaseController controller; + final ToggleExtensionNotifier toggleExtension; + const MobileGridSettingButton({ + required this.controller, + required this.toggleExtension, + super.key, + }); + + @override + Widget build(BuildContext context) { + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => GridFilterMenuBloc( + viewId: controller.viewId, + fieldController: controller.fieldController, + )..add(const GridFilterMenuEvent.initial()), + ), + BlocProvider( + create: (context) => SortMenuBloc( + viewId: controller.viewId, + fieldController: controller.fieldController, + )..add(const SortMenuEvent.initial()), + ), + ], + child: MultiBlocListener( + listeners: [ + BlocListener( + listenWhen: (p, c) => p.isVisible != c.isVisible, + listener: (context, state) => toggleExtension.toggle(), + ), + BlocListener( + listenWhen: (p, c) => p.isVisible != c.isVisible, + listener: (context, state) => toggleExtension.toggle(), + ), + ], + child: ValueListenableBuilder( + valueListenable: controller.isLoading, + builder: (context, isLoading, child) { + if (isLoading) { + return const SizedBox.shrink(); + } + return IconButton( + onPressed: () {}, + icon: const FlowySvg( + FlowySvgs.m_setting_m, + ), + ); + }, + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/setting_menu.dart b/frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/desktop/setting_menu.dart similarity index 62% rename from frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/setting_menu.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/desktop/setting_menu.dart index e7888cd542..67dbfd1ab4 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/setting_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/desktop/setting_menu.dart @@ -1,16 +1,15 @@ import 'package:appflowy/plugins/database_view/application/database_controller.dart'; +import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/grid/application/grid_accessory_bloc.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart'; +import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart'; +import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/filter_menu.dart'; +import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_menu.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; -import '../application/field/field_controller.dart'; -import '../grid/presentation/layout/sizes.dart'; -import '../grid/presentation/widgets/filter/filter_menu.dart'; -import '../grid/presentation/widgets/sort/sort_menu.dart'; - class DatabaseViewSettingExtension extends StatelessWidget { final String viewId; final DatabaseController databaseController; @@ -57,26 +56,32 @@ class _DatabaseViewSettingContent extends StatelessWidget { return BlocBuilder( builder: (context, state) { - return _wrapPadding( - Row( - children: [ - SortMenu(fieldController: fieldController), - const HSpace(6), - FilterMenu(fieldController: fieldController), - ], + return Padding( + padding: EdgeInsets.symmetric( + horizontal: GridSize.leadingHeaderPadding, + ), + child: DecoratedBox( + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Theme.of(context).dividerColor, + width: 1.0, + ), + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + SortMenu(fieldController: fieldController), + const HSpace(6), + FilterMenu(fieldController: fieldController), + ], + ), + ), ), ); }, ); } - - Widget _wrapPadding(Widget child) { - return Padding( - padding: EdgeInsets.symmetric( - horizontal: GridSize.leadingHeaderPadding, - vertical: 8, - ), - child: child, - ); - } } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/tab_bar_add_button.dart b/frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/desktop/tab_bar_add_button.dart similarity index 65% rename from frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/tab_bar_add_button.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/desktop/tab_bar_add_button.dart index 3f80cc9509..4a96d43d15 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/tab_bar_add_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/desktop/tab_bar_add_button.dart @@ -1,7 +1,8 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; +import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/size.dart'; @@ -11,7 +12,7 @@ import 'package:flowy_infra_ui/style_widget/extension.dart'; import 'package:flutter/material.dart'; class AddDatabaseViewButton extends StatefulWidget { - final Function(AddButtonAction) onTap; + final Function(DatabaseLayoutPB) onTap; const AddDatabaseViewButton({ required this.onTap, super.key, @@ -57,7 +58,7 @@ class _AddDatabaseViewButtonState extends State { ), ), popupBuilder: (BuildContext context) { - return TarBarAddButtonAction( + return TabBarAddButtonAction( onTap: (action) { popoverController.close(); widget.onTap(action); @@ -68,38 +69,37 @@ class _AddDatabaseViewButtonState extends State { } } -class TarBarAddButtonAction extends StatelessWidget { - final Function(AddButtonAction) onTap; - const TarBarAddButtonAction({ +class TabBarAddButtonAction extends StatelessWidget { + final Function(DatabaseLayoutPB) onTap; + const TabBarAddButtonAction({ required this.onTap, super.key, }); @override Widget build(BuildContext context) { - final cells = AddButtonAction.values.map((layout) { - return TarBarAddButtonActionCell( + final cells = DatabaseLayoutPB.values.map((layout) { + return TabBarAddButtonActionCell( action: layout, onTap: onTap, ); }).toList(); return ListView.separated( - controller: ScrollController(), shrinkWrap: true, itemCount: cells.length, itemBuilder: (BuildContext context, int index) => cells[index], separatorBuilder: (BuildContext context, int index) => VSpace(GridSize.typeOptionSeparatorHeight), - padding: const EdgeInsets.symmetric(vertical: 6.0), + padding: const EdgeInsets.symmetric(vertical: 4.0), ); } } -class TarBarAddButtonActionCell extends StatelessWidget { - final AddButtonAction action; - final void Function(AddButtonAction) onTap; - const TarBarAddButtonActionCell({ +class TabBarAddButtonActionCell extends StatelessWidget { + final DatabaseLayoutPB action; + final void Function(DatabaseLayoutPB) onTap; + const TabBarAddButtonActionCell({ required this.action, required this.onTap, super.key, @@ -112,7 +112,7 @@ class TarBarAddButtonActionCell extends StatelessWidget { child: FlowyButton( hoverColor: AFThemeExtension.of(context).lightGreyHover, text: FlowyText.medium( - '${LocaleKeys.grid_createView.tr()} ${action.title}', + '${LocaleKeys.grid_createView.tr()} ${action.layoutName}', color: AFThemeExtension.of(context).textColor, ), leftIcon: FlowySvg( @@ -124,46 +124,3 @@ class TarBarAddButtonActionCell extends StatelessWidget { ); } } - -enum AddButtonAction { - grid, - calendar, - board; - - String get title { - switch (this) { - case AddButtonAction.board: - return LocaleKeys.board_menuName.tr(); - case AddButtonAction.calendar: - return LocaleKeys.calendar_menuName.tr(); - case AddButtonAction.grid: - return LocaleKeys.grid_menuName.tr(); - default: - return ""; - } - } - - ViewLayoutPB get layoutType { - switch (this) { - case AddButtonAction.board: - return ViewLayoutPB.Board; - case AddButtonAction.calendar: - return ViewLayoutPB.Calendar; - case AddButtonAction.grid: - return ViewLayoutPB.Grid; - default: - return ViewLayoutPB.Grid; - } - } - - FlowySvgData get icon { - switch (this) { - case AddButtonAction.board: - return FlowySvgs.board_s; - case AddButtonAction.calendar: - return FlowySvgs.date_s; - case AddButtonAction.grid: - return FlowySvgs.grid_s; - } - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/tab_bar_header.dart b/frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/desktop/tab_bar_header.dart similarity index 79% rename from frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/tab_bar_header.dart rename to frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/desktop/tab_bar_header.dart index b83664b725..4b4281e9bc 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/tab_bar_header.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/desktop/tab_bar_header.dart @@ -1,5 +1,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/database_view/application/tab_bar_bloc.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; @@ -12,71 +13,68 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../application/tab_bar_bloc.dart'; import 'tab_bar_add_button.dart'; -class TabBarHeader extends StatefulWidget { +class TabBarHeader extends StatelessWidget { const TabBarHeader({super.key}); - @override - State createState() => _TabBarHeaderState(); -} - -class _TabBarHeaderState extends State { @override Widget build(BuildContext context) { - return Stack( - children: [ - Positioned( - bottom: 0, - left: 0, - right: 0, - child: Divider( - color: Theme.of(context).dividerColor, - height: 1, - thickness: 1, + return SizedBox( + height: 30, + child: Stack( + children: [ + Positioned( + bottom: 0, + left: 0, + right: 0, + child: Divider( + color: Theme.of(context).dividerColor, + height: 1, + thickness: 1, + ), ), - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - BlocBuilder( - builder: (context, state) { - return const Flexible( - child: DatabaseTabBar(), - ); - }, - ), - BlocBuilder( - builder: (context, state) { - return SizedBox( - width: 200, - child: Column( - children: [ - const VSpace(3), - pageSettingBarFromState(state), - ], - ), - ); - }, - ), - ], - ), - ], + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + BlocBuilder( + builder: (context, state) { + return const Flexible( + child: DatabaseTabBar(), + ); + }, + ), + BlocBuilder( + builder: (context, state) { + return SizedBox( + width: 200, + child: Column( + children: [ + const VSpace(3), + pageSettingBarFromState(context, state), + ], + ), + ); + }, + ), + ], + ), + ], + ), ); } - Widget pageSettingBarFromState(DatabaseTabBarState state) { + Widget pageSettingBarFromState( + BuildContext context, + DatabaseTabBarState state, + ) { if (state.tabBars.length < state.selectedIndex) { return const SizedBox.shrink(); } - final tarBar = state.tabBars[state.selectedIndex]; + final tabBar = state.tabBars[state.selectedIndex]; final controller = - state.tabBarControllerByViewId[tarBar.viewId]!.controller; - return tarBar.builder.settingBar( - context, - controller, - ); + state.tabBarControllerByViewId[tabBar.viewId]!.controller; + return tabBar.builder.settingBar(context, controller); } } @@ -121,9 +119,9 @@ class _DatabaseTabBarState extends State { ), ), AddDatabaseViewButton( - onTap: (action) async { + onTap: (layoutType) async { context.read().add( - DatabaseTabBarEvent.createView(action), + DatabaseTabBarEvent.createView(layoutType), ); }, ), 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 new file mode 100644 index 0000000000..7ce0c158a2 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database_view/tab_bar/mobile/mobile_tab_bar_header.dart @@ -0,0 +1,52 @@ +import 'package:appflowy/plugins/database_view/application/tab_bar_bloc.dart'; +import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart'; +import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/mobile_grid_setting.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MobileTabBarHeader extends StatelessWidget { + const MobileTabBarHeader({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + final currentView = state.tabBars.firstWhereIndexedOrNull( + (index, tabBar) => index == state.selectedIndex, + ); + + if (currentView == null) { + return const SizedBox.shrink(); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Row( + children: [ + Expanded( + child: Text( + currentView.view.name, + style: Theme.of(context).textTheme.titleLarge, + overflow: TextOverflow.ellipsis, + ), + ), + MobileGridSettingButton( + controller: state + .tabBarControllerByViewId[currentView.viewId]! + .controller, + toggleExtension: ToggleExtensionNotifier(), + ), + ], + ), + ), + const Divider(height: 1, thickness: 1), + ], + ); + }, + ); + } +} 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 1d80c56a2b..446c80105a 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 @@ -1,4 +1,6 @@ +import 'package:appflowy/plugins/database_view/application/database_controller.dart'; import 'package:appflowy/plugins/database_view/application/tab_bar_bloc.dart'; +import 'package:appflowy/plugins/database_view/tab_bar/mobile/mobile_tab_bar_header.dart'; import 'package:appflowy/plugins/database_view/widgets/share_button.dart'; import 'package:appflowy/plugins/util.dart'; import 'package:appflowy/startup/plugin/plugin.dart'; @@ -6,11 +8,11 @@ import 'package:appflowy/workspace/presentation/home/home_stack.dart'; import 'package:appflowy/workspace/presentation/widgets/tab_bar_item.dart'; import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../application/database_controller.dart'; -import 'tab_bar_header.dart'; +import 'desktop/tab_bar_header.dart'; abstract class DatabaseTabBarItemBuilder { const DatabaseTabBarItemBuilder(); @@ -94,12 +96,11 @@ class _DatabaseTabBarViewState extends State { if (value) { return const SizedBox.shrink(); } - return const SizedBox( - height: 30, - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 40), - child: TabBarHeader(), - ), + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 40), + child: PlatformExtension.isDesktop + ? const TabBarHeader() + : const MobileTabBarHeader(), ); }, ); @@ -145,10 +146,10 @@ class _DatabaseTabBarViewState extends State { if (state.tabBars.length < state.selectedIndex) { return const SizedBox.shrink(); } - final tarBar = state.tabBars[state.selectedIndex]; + final tabBar = state.tabBars[state.selectedIndex]; final controller = - state.tabBarControllerByViewId[tarBar.viewId]!.controller; - return tarBar.builder.settingBarExtension( + state.tabBarControllerByViewId[tabBar.viewId]!.controller; + return tabBar.builder.settingBarExtension( context, controller, ); diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/database_layout_ext.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/database_layout_ext.dart index 33b357a119..ed507e5ae6 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/database_layout_ext.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/database_layout_ext.dart @@ -1,30 +1,34 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder2/view.pbenum.dart'; import 'package:easy_localization/easy_localization.dart'; extension DatabaseLayoutExtension on DatabaseLayoutPB { - String layoutName() { - switch (this) { - case DatabaseLayoutPB.Board: - return LocaleKeys.board_menuName.tr(); - case DatabaseLayoutPB.Calendar: - return LocaleKeys.calendar_menuName.tr(); - case DatabaseLayoutPB.Grid: - return LocaleKeys.grid_menuName.tr(); - default: - return ""; - } + String get layoutName { + return switch (this) { + DatabaseLayoutPB.Board => LocaleKeys.board_menuName.tr(), + DatabaseLayoutPB.Calendar => LocaleKeys.calendar_menuName.tr(), + DatabaseLayoutPB.Grid => LocaleKeys.grid_menuName.tr(), + _ => "", + }; + } + + ViewLayoutPB get layoutType { + return switch (this) { + DatabaseLayoutPB.Board => ViewLayoutPB.Board, + DatabaseLayoutPB.Calendar => ViewLayoutPB.Calendar, + DatabaseLayoutPB.Grid => ViewLayoutPB.Grid, + _ => throw UnimplementedError(), + }; } FlowySvgData get icon { - switch (this) { - case DatabaseLayoutPB.Board: - return FlowySvgs.board_s; - case DatabaseLayoutPB.Calendar: - case DatabaseLayoutPB.Grid: - return FlowySvgs.grid_s; - } - throw UnimplementedError(); + return switch (this) { + DatabaseLayoutPB.Board => FlowySvgs.board_s, + DatabaseLayoutPB.Calendar => FlowySvgs.date_s, + DatabaseLayoutPB.Grid => FlowySvgs.grid_s, + _ => throw UnimplementedError(), + }; } } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cell_builder.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cell_builder.dart index aa5a187942..f302a561fa 100755 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cell_builder.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cell_builder.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import '../../application/cell/cell_service.dart'; import 'accessory/cell_accessory.dart'; import 'accessory/cell_shortcuts.dart'; +import 'cells/cell_container.dart'; import 'cells/checkbox_cell/checkbox_cell.dart'; import 'cells/checklist_cell/checklist_cell.dart'; import 'cells/date_cell/date_cell.dart'; @@ -200,7 +201,7 @@ class BlankCell extends StatelessWidget { abstract class CellEditable { RequestFocusListener get requestFocus; - ValueNotifier get onCellFocus; + CellContainerNotifier get cellContainerNotifier; // ValueNotifier get onCellEditing; } @@ -223,11 +224,11 @@ abstract class GridCellWidget extends StatefulWidget GridCellWidget({super.key}); @override - final ValueNotifier onCellFocus = ValueNotifier(false); + final CellContainerNotifier cellContainerNotifier = CellContainerNotifier(); // When the cell is focused, we assume that the accessory also be hovered. @override - ValueNotifier get onAccessoryHover => onCellFocus; + ValueNotifier get onAccessoryHover => ValueNotifier(false); // @override // final ValueNotifier onCellEditing = ValueNotifier(false); @@ -278,17 +279,19 @@ abstract class GridEditableTextCell @override void initState() { + super.initState(); widget.shortcutHandlers[CellKeyboardKey.onEnter] = () => focusNode.unfocus(); _listenOnFocusNodeChanged(); - super.initState(); } @override void didUpdateWidget(covariant T oldWidget) { - if (oldWidget != this) { - _listenOnFocusNodeChanged(); - } + // if (!focusNode.hasFocus && widget.cellContainerNotifier.isFocus) { + // focusNode.requestFocus(); + // } else if (focusNode.hasFocus && !widget.cellContainerNotifier.isFocus) { + // focusNode.unfocus(); + // } super.didUpdateWidget(oldWidget); } @@ -302,15 +305,15 @@ abstract class GridEditableTextCell @override void requestBeginFocus() { - if (focusNode.hasFocus == false && focusNode.canRequestFocus) { + if (!focusNode.hasFocus && focusNode.canRequestFocus) { FocusScope.of(context).requestFocus(focusNode); } } void _listenOnFocusNodeChanged() { - widget.onCellFocus.value = focusNode.hasFocus; + widget.cellContainerNotifier.isFocus = focusNode.hasFocus; focusNode.setListener(() { - widget.onCellFocus.value = focusNode.hasFocus; + widget.cellContainerNotifier.isFocus = focusNode.hasFocus; focusChanged(); }); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/cell_container.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/cell_container.dart index cdfe42634c..80d5ec5070 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/cell_container.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/cell_container.dart @@ -13,28 +13,26 @@ class CellContainer extends StatelessWidget { final AccessoryBuilder? accessoryBuilder; final double width; final bool isPrimary; - final CellContainerNotifier cellContainerNotifier; const CellContainer({ Key? key, required this.child, required this.width, required this.isPrimary, - required this.cellContainerNotifier, this.accessoryBuilder, }) : super(key: key); @override Widget build(BuildContext context) { return ChangeNotifierProvider.value( - value: cellContainerNotifier, + value: child.cellContainerNotifier, child: Selector( selector: (context, notifier) => notifier.isFocus, - builder: (privderContext, isFocus, _) { + builder: (providerContext, isFocus, _) { Widget container = Center(child: GridCellShortcuts(child: child)); if (accessoryBuilder != null) { - final accessories = accessoryBuilder!( + final accessories = accessoryBuilder!.call( GridCellAccessoryBuildContext( anchorContext: context, isCellEditing: isFocus, @@ -52,7 +50,11 @@ class CellContainer extends StatelessWidget { return GestureDetector( behavior: HitTestBehavior.opaque, - onTap: () => child.requestFocus.notify(), + onTap: () { + if (!isFocus) { + child.requestFocus.notify(); + } + }, child: Container( constraints: BoxConstraints(maxWidth: width, minHeight: 46), decoration: _makeBoxDecoration(context, isFocus), @@ -81,9 +83,6 @@ class CellContainer extends StatelessWidget { } class _GridCellEnterRegion extends StatelessWidget { - final Widget child; - final List accessories; - final bool isPrimary; const _GridCellEnterRegion({ required this.child, required this.accessories, @@ -91,6 +90,10 @@ class _GridCellEnterRegion extends StatelessWidget { Key? key, }) : super(key: key); + final Widget child; + final List accessories; + final bool isPrimary; + @override Widget build(BuildContext context) { return Selector2( @@ -99,6 +102,7 @@ class _GridCellEnterRegion extends StatelessWidget { (cellNotifier.onEnter || regionNotifier.onEnter && isPrimary), builder: (context, showAccessory, _) { final List children = [child]; + if (showAccessory) { children.add( CellAccessoryContainer(accessories: accessories).positioned( @@ -110,11 +114,9 @@ class _GridCellEnterRegion extends StatelessWidget { return MouseRegion( cursor: SystemMouseCursors.click, onEnter: (p) => - Provider.of(context, listen: false) - .onEnter = true, + CellContainerNotifier.of(context, listen: false).onEnter = true, onExit: (p) => - Provider.of(context, listen: false) - .onEnter = false, + CellContainerNotifier.of(context, listen: false).onEnter = false, child: Stack( alignment: AlignmentDirectional.center, fit: StackFit.expand, @@ -127,24 +129,9 @@ class _GridCellEnterRegion extends StatelessWidget { } class CellContainerNotifier extends ChangeNotifier { - final CellEditable cellEditable; - VoidCallback? _onCellFocusListener; bool _isFocus = false; bool _onEnter = false; - CellContainerNotifier(this.cellEditable) { - _onCellFocusListener = () => isFocus = cellEditable.onCellFocus.value; - cellEditable.onCellFocus.addListener(_onCellFocusListener!); - } - - @override - void dispose() { - if (_onCellFocusListener != null) { - cellEditable.onCellFocus.removeListener(_onCellFocusListener!); - } - super.dispose(); - } - set isFocus(bool value) { if (_isFocus != value) { _isFocus = value; @@ -162,4 +149,8 @@ class CellContainerNotifier extends ChangeNotifier { bool get isFocus => _isFocus; bool get onEnter => _onEnter; + + static CellContainerNotifier of(BuildContext context, {bool listen = true}) { + return Provider.of(context, listen: listen); + } } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell.dart index 949a1138e0..5296737e56 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell.dart @@ -144,16 +144,16 @@ class GridChecklistCellState extends GridCellState { constraints: BoxConstraints.loose(const Size(360, 400)), direction: PopoverDirection.bottomWithLeftAligned, triggerActions: PopoverTriggerFlags.none, - popupBuilder: (BuildContext context) { + popupBuilder: (BuildContext popoverContext) { WidgetsBinding.instance.addPostFrameCallback((_) { - widget.onCellFocus.value = true; + widget.cellContainerNotifier.isFocus = true; }); return GridChecklistCellEditor( cellController: widget.cellControllerBuilder.build() as ChecklistCellController, ); }, - onClose: () => widget.onCellFocus.value = false, + onClose: () => widget.cellContainerNotifier.isFocus = false, child: Align( alignment: Alignment.centerLeft, child: Padding( diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cell.dart index f5d88256d9..64e8dc61fe 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/date_cell/date_cell.dart @@ -87,11 +87,11 @@ class _DateCellState extends GridCellState { return DateCellEditor( cellController: widget.cellControllerBuilder.build() as DateCellController, - onDismissed: () => widget.onCellFocus.value = false, + onDismissed: () => widget.cellContainerNotifier.isFocus = false, ); }, onClose: () { - widget.onCellFocus.value = false; + widget.cellContainerNotifier.isFocus = false; }, ); }, @@ -108,7 +108,7 @@ class _DateCellState extends GridCellState { @override void requestBeginFocus() { _popover.show(); - widget.onCellFocus.value = true; + widget.cellContainerNotifier.isFocus = true; } @override diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/number_cell/number_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/number_cell/number_cell.dart index 4dee176bbe..8fe810831a 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/number_cell/number_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/number_cell/number_cell.dart @@ -80,9 +80,14 @@ class _NumberCellState extends GridEditableTextCell { decoration: InputDecoration( contentPadding: EdgeInsets.zero, border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + disabledBorder: InputBorder.none, hintText: widget.cellStyle.placeholder, isDense: true, ), + onTapOutside: (_) => focusNode.unfocus(), ), ), ), diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_cell.dart index 38f05212ee..cf814e2a9a 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/select_option_cell/select_option_cell.dart @@ -64,7 +64,8 @@ class _SingleSelectCellState extends GridCellState { return SelectOptionWrap( selectOptions: state.selectedOptions, cellStyle: widget.cellStyle, - onCellEditing: widget.onCellFocus, + onCellEditing: (isFocus) => + widget.cellContainerNotifier.isFocus = isFocus, popoverController: _popover, cellControllerBuilder: widget.cellControllerBuilder, ); @@ -127,7 +128,8 @@ class _MultiSelectCellState extends GridCellState { return SelectOptionWrap( selectOptions: state.selectedOptions, cellStyle: widget.cellStyle, - onCellEditing: widget.onCellFocus, + onCellEditing: (isFocus) => + widget.cellContainerNotifier.isFocus = isFocus, popoverController: _popover, cellControllerBuilder: widget.cellControllerBuilder, ); @@ -151,7 +153,7 @@ class SelectOptionWrap extends StatefulWidget { final SelectOptionCellStyle? cellStyle; final CellControllerBuilder cellControllerBuilder; final PopoverController popoverController; - final ValueNotifier onCellEditing; + final void Function(bool) onCellEditing; const SelectOptionWrap({ required this.selectOptions, @@ -179,16 +181,16 @@ class _SelectOptionWrapState extends State { constraints: constraints, margin: EdgeInsets.zero, direction: PopoverDirection.bottomWithLeftAligned, - popupBuilder: (BuildContext context) { + popupBuilder: (BuildContext popoverContext) { WidgetsBinding.instance.addPostFrameCallback((_) { - widget.onCellEditing.value = true; + widget.onCellEditing(true); }); return SelectOptionCellEditor( cellController: widget.cellControllerBuilder.build() as SelectOptionCellController, ); }, - onClose: () => widget.onCellEditing.value = false, + onClose: () => widget.onCellEditing(false), child: Padding( padding: widget.cellStyle?.cellPadding ?? GridSize.cellContentInsets, child: child, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/text_cell/text_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/text_cell/text_cell.dart index 84de14129b..f10507da52 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/text_cell/text_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/text_cell/text_cell.dart @@ -122,9 +122,14 @@ class _GridTextCellState extends GridEditableTextCell { bottom: GridSize.cellContentInsets.bottom, ), border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + disabledBorder: InputBorder.none, hintText: widget.cellStyle.placeholder, isDense: true, ), + onTapOutside: (_) => focusNode.unfocus(), ), ), ], diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/url_cell/url_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/url_cell/url_cell.dart index eaf050421c..41aa3b7cfc 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/url_cell/url_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/url_cell/url_cell.dart @@ -110,7 +110,6 @@ class GridURLCell extends GridCellWidget { } class _GridURLCellState extends GridEditableTextCell { - final _popoverController = PopoverController(); late final URLCellBloc _cellBloc; late final TextEditingController _controller; @@ -167,10 +166,15 @@ class _GridURLCellState extends GridEditableTextCell { bottom: GridSize.cellContentInsets.bottom, ), border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + disabledBorder: InputBorder.none, hintText: widget.cellStyle?.placeholder, hintStyle: style.copyWith(color: Theme.of(context).hintColor), isDense: true, ), + onTapOutside: (_) => focusNode.unfocus(), ), ); }, @@ -184,12 +188,6 @@ class _GridURLCellState extends GridEditableTextCell { return super.focusChanged(); } - @override - void requestBeginFocus() { - widget.onCellFocus.value = true; - _popoverController.show(); - } - @override String? onCopy() => _cellBloc.state.content; } diff --git a/frontend/appflowy_flutter/lib/workspace/application/view/view_ext.dart b/frontend/appflowy_flutter/lib/workspace/application/view/view_ext.dart index 0cd995847b..19f79f0078 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/view/view_ext.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/view/view_ext.dart @@ -2,6 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/plugins/database_view/board/presentation/board_page.dart'; import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart'; +import 'package:appflowy/plugins/database_view/grid/presentation/mobile_grid_page.dart'; import 'package:appflowy/plugins/database_view/tab_bar/tab_bar_view.dart'; import 'package:appflowy/plugins/document/document.dart'; import 'package:appflowy/startup/plugin/plugin.dart'; @@ -90,18 +91,30 @@ extension ViewExtension on ViewPB { throw UnimplementedError; } - DatabaseTabBarItemBuilder tarBarItem() { + DatabaseTabBarItemBuilder tabBarItem() { switch (layout) { case ViewLayoutPB.Board: return BoardPageTabBarBuilderImpl(); case ViewLayoutPB.Calendar: return CalendarPageTabBarBuilderImpl(); case ViewLayoutPB.Grid: - return GridPageTabBarBuilderImpl(); - case ViewLayoutPB.Document: + return DesktopGridTabBarBuilderImpl(); + default: + throw UnimplementedError; + } + } + + DatabaseTabBarItemBuilder mobileTabBarItem() { + switch (layout) { + case ViewLayoutPB.Board: + return BoardPageTabBarBuilderImpl(); + case ViewLayoutPB.Calendar: + return CalendarPageTabBarBuilderImpl(); + case ViewLayoutPB.Grid: + return MobileGridTabBarBuilderImpl(); + default: throw UnimplementedError; } - throw UnimplementedError; } FlowySvgData get iconData => layout.icon; diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/scrolling/styled_scrollview.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/scrolling/styled_scrollview.dart index 1b10d7335d..ac8bd140b2 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/scrolling/styled_scrollview.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/scrolling/styled_scrollview.dart @@ -17,7 +17,7 @@ class StyledSingleChildScrollView extends StatefulWidget { const StyledSingleChildScrollView({ Key? key, - @required this.child, + required this.child, this.contentSize, this.axis = Axis.vertical, this.trackColor, diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart index 35d8029841..e90b45fdf8 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:flutter/material.dart'; class FlowyText extends StatelessWidget { @@ -91,12 +89,6 @@ class FlowyText extends StatelessWidget { maxLines: maxLines, textAlign: textAlign, overflow: overflow ?? TextOverflow.clip, - textHeightBehavior: Platform.isAndroid || Platform.isIOS - ? const TextHeightBehavior( - applyHeightToFirstAscent: false, - applyHeightToLastDescent: false, - ) - : null, style: Theme.of(context).textTheme.bodyMedium!.copyWith( fontSize: fontSize, fontWeight: fontWeight,