mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: improve mobile grid page (#3939)
* chore: more typo * chore: improve appearance of mobile grid page * fix: focus problems with editable grid cells * chore: apply suggestions from Mathias * revert: the dragged header looks ugly * chore: more suggestions from Mathias * chore: more tarbars * fix: scrollbar padding is a bit off * chore: add launch tasks and fix android debug * chore: more mobile grid improvement * fix: initial attempt to fix cell focus bug * chore: fix grid cell lazy loading * chore: fix cell focus problems * chore: update same changes to desktop * fix: revert accessory changes due to regression * chore: new database view name i18n * chore: add mobile tab bar header * fix: fiz zuh eentuhgrashun tastes * chore: rudimentary grid header * style: code style stuffz * chore: restore android standard lib --------- Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
This commit is contained in:
parent
b00d29d0cd
commit
16467e9c13
33
frontend/.vscode/launch.json
vendored
33
frontend/.vscode/launch.json
vendored
@ -44,6 +44,17 @@
|
|||||||
},
|
},
|
||||||
"cwd": "${workspaceRoot}/appflowy_flutter"
|
"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",
|
"name": "AF-iOS: Clean + Rebuild All",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
@ -55,6 +66,17 @@
|
|||||||
},
|
},
|
||||||
"cwd": "${workspaceRoot}/appflowy_flutter"
|
"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",
|
"name": "AF-iOS-Simulator: Clean + Rebuild All",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
@ -66,6 +88,17 @@
|
|||||||
},
|
},
|
||||||
"cwd": "${workspaceRoot}/appflowy_flutter"
|
"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",
|
"name": "AF-Android: Clean + Rebuild All",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
|
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:appflowy_backend/protobuf/flowy-folder2/view.pbenum.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:integration_test/integration_test.dart';
|
import 'package:integration_test/integration_test.dart';
|
||||||
@ -16,7 +16,7 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.grid);
|
await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Grid);
|
||||||
|
|
||||||
// create a field
|
// create a field
|
||||||
await tester.scrollToRight(find.byType(GridPage));
|
await tester.scrollToRight(find.byType(GridPage));
|
||||||
@ -34,7 +34,7 @@ void main() {
|
|||||||
await tester.findFieldWithName('New field 1');
|
await tester.findFieldWithName('New field 1');
|
||||||
|
|
||||||
// go back to linked database view, expect field to be hidden
|
// 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');
|
await tester.noFieldWithName('New field 1');
|
||||||
|
|
||||||
// use the settings button to show the field
|
// use the settings button to show the field
|
||||||
|
@ -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-database2/setting_entities.pbenum.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
@ -18,15 +17,15 @@ void main() {
|
|||||||
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
|
|
||||||
// Create board view
|
// Create board view
|
||||||
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board);
|
await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Board);
|
||||||
tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Board);
|
tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Board);
|
||||||
|
|
||||||
// Create grid view
|
// Create grid view
|
||||||
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.grid);
|
await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Grid);
|
||||||
tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Grid);
|
tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Grid);
|
||||||
|
|
||||||
// Create calendar view
|
// Create calendar view
|
||||||
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.calendar);
|
await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Calendar);
|
||||||
tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Calendar);
|
tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Calendar);
|
||||||
|
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
@ -39,7 +38,7 @@ void main() {
|
|||||||
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
|
|
||||||
// Create board view
|
// Create board view
|
||||||
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board);
|
await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Board);
|
||||||
tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Board);
|
tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Board);
|
||||||
|
|
||||||
// rename board view
|
// rename board view
|
||||||
@ -64,7 +63,7 @@ void main() {
|
|||||||
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
|
||||||
|
|
||||||
// Create board view
|
// Create board view
|
||||||
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board);
|
await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Board);
|
||||||
tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Board);
|
tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Board);
|
||||||
|
|
||||||
// delete the board
|
// delete the board
|
||||||
|
@ -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/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/grid_layout.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/sort_button.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/desktop/tab_bar_add_button.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_header.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.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/accessory/cell_accessory.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart';
|
||||||
@ -1381,13 +1381,15 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
await tapButton(unscheduledEvent);
|
await tapButton(unscheduledEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> tapCreateLinkedDatabaseViewButton(AddButtonAction action) async {
|
Future<void> tapCreateLinkedDatabaseViewButton(
|
||||||
|
DatabaseLayoutPB layoutType,
|
||||||
|
) async {
|
||||||
final findAddButton = find.byType(AddDatabaseViewButton);
|
final findAddButton = find.byType(AddDatabaseViewButton);
|
||||||
await tapButton(findAddButton);
|
await tapButton(findAddButton);
|
||||||
|
|
||||||
final findCreateButton = find.byWidgetPredicate(
|
final findCreateButton = find.byWidgetPredicate(
|
||||||
(widget) =>
|
(widget) =>
|
||||||
widget is TarBarAddButtonActionCell && widget.action == action,
|
widget is TabBarAddButtonActionCell && widget.action == layoutType,
|
||||||
);
|
);
|
||||||
await tapButton(findCreateButton);
|
await tapButton(findCreateButton);
|
||||||
}
|
}
|
||||||
@ -1554,7 +1556,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
Future<void> selectDatabaseLayoutType(DatabaseLayoutPB layout) async {
|
Future<void> selectDatabaseLayoutType(DatabaseLayoutPB layout) async {
|
||||||
final findLayoutCell = find.byType(DatabaseViewLayoutCell);
|
final findLayoutCell = find.byType(DatabaseViewLayoutCell);
|
||||||
final findText = find.byWidgetPredicate(
|
final findText = find.byWidgetPredicate(
|
||||||
(widget) => widget is FlowyText && widget.text == layout.layoutName(),
|
(widget) => widget is FlowyText && widget.text == layout.layoutName,
|
||||||
);
|
);
|
||||||
|
|
||||||
final button = find.descendant(
|
final button = find.descendant(
|
||||||
|
@ -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/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/prelude.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||||
import 'package:appflowy_backend/log.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_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
@ -48,8 +50,8 @@ class DatabaseTabBarBloc
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
createView: (action) {
|
createView: (layout) {
|
||||||
_createLinkedView(action.name, action.layoutType);
|
_createLinkedView(layout.layoutType, layout.layoutName);
|
||||||
},
|
},
|
||||||
deleteView: (String viewId) async {
|
deleteView: (String viewId) async {
|
||||||
final result = await ViewBackendService.delete(viewId: viewId);
|
final result = await ViewBackendService.delete(viewId: viewId);
|
||||||
@ -89,10 +91,10 @@ class DatabaseTabBarBloc
|
|||||||
(element) => element.viewId == viewId,
|
(element) => element.viewId == viewId,
|
||||||
);
|
);
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
final tarBar = allTabBars.removeAt(index);
|
final tabBar = allTabBars.removeAt(index);
|
||||||
// Dispose the controller when the tab is removed.
|
// Dispose the controller when the tab is removed.
|
||||||
final controller =
|
final controller =
|
||||||
tabBarControllerByViewId.remove(tarBar.viewId);
|
tabBarControllerByViewId.remove(tabBar.viewId);
|
||||||
controller?.dispose();
|
controller?.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,7 +165,7 @@ class DatabaseTabBarBloc
|
|||||||
return tabBarControllerByViewId;
|
return tabBarControllerByViewId;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _createLinkedView(String name, ViewLayoutPB layoutType) async {
|
Future<void> _createLinkedView(ViewLayoutPB layoutType, String name) async {
|
||||||
final viewId = state.parentView.id;
|
final viewId = state.parentView.id;
|
||||||
final databaseIdOrError =
|
final databaseIdOrError =
|
||||||
await DatabaseViewBackendService(viewId: viewId).getDatabaseId();
|
await DatabaseViewBackendService(viewId: viewId).getDatabaseId();
|
||||||
@ -207,7 +209,7 @@ class DatabaseTabBarEvent with _$DatabaseTabBarEvent {
|
|||||||
List<ViewPB> childViews,
|
List<ViewPB> childViews,
|
||||||
) = _DidLoadChildViews;
|
) = _DidLoadChildViews;
|
||||||
const factory DatabaseTabBarEvent.selectView(String viewId) = _DidSelectView;
|
const factory DatabaseTabBarEvent.selectView(String viewId) = _DidSelectView;
|
||||||
const factory DatabaseTabBarEvent.createView(AddButtonAction action) =
|
const factory DatabaseTabBarEvent.createView(DatabaseLayoutPB layout) =
|
||||||
_CreateView;
|
_CreateView;
|
||||||
const factory DatabaseTabBarEvent.renameView(String viewId, String newName) =
|
const factory DatabaseTabBarEvent.renameView(String viewId, String newName) =
|
||||||
_RenameView;
|
_RenameView;
|
||||||
@ -252,7 +254,9 @@ class DatabaseTabBar extends Equatable {
|
|||||||
|
|
||||||
DatabaseTabBar({
|
DatabaseTabBar({
|
||||||
required this.view,
|
required this.view,
|
||||||
}) : _builder = view.tarBarItem();
|
}) : _builder = PlatformExtension.isMobile
|
||||||
|
? view.mobileTabBarItem()
|
||||||
|
: view.tabBarItem();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [view.hashCode];
|
List<Object?> get props => [view.hashCode];
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
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/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/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/plugins/database_view/widgets/row/cell_builder.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui_web.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();
|
final _toggleExtension = ToggleExtensionNotifier();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -255,6 +256,15 @@ class _GridRows extends StatelessWidget {
|
|||||||
GridState state,
|
GridState state,
|
||||||
List<RowInfo> rowInfos,
|
List<RowInfo> 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(
|
return ReorderableListView.builder(
|
||||||
/// TODO(Xazin): Resolve inconsistent scrollbar behavior
|
/// TODO(Xazin): Resolve inconsistent scrollbar behavior
|
||||||
/// This is a workaround related to
|
/// This is a workaround related to
|
||||||
@ -274,18 +284,7 @@ class _GridRows extends StatelessWidget {
|
|||||||
context.read<GridBloc>().add(GridEvent.moveRow(fromIndex, toIndex));
|
context.read<GridBloc>().add(GridEvent.moveRow(fromIndex, toIndex));
|
||||||
},
|
},
|
||||||
itemCount: rowInfos.length + 1, // the extra item is the footer
|
itemCount: rowInfos.length + 1, // the extra item is the footer
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) => children[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'));
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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<MobileGridPage> createState() => _MobileGridPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MobileGridPageState extends State<MobileGridPage> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MultiBlocProvider(
|
||||||
|
providers: [
|
||||||
|
BlocProvider<GridBloc>(
|
||||||
|
create: (context) => GridBloc(
|
||||||
|
view: widget.view,
|
||||||
|
databaseController: widget.databaseController,
|
||||||
|
)..add(const GridEvent.initial()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: BlocBuilder<GridBloc, GridState>(
|
||||||
|
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<GridPageContent> createState() => _GridPageContentState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GridPageContentState extends State<GridPageContent> {
|
||||||
|
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<GridBloc, GridState>(
|
||||||
|
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<GridBloc, GridState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return GridHeaderSliverAdaptor(
|
||||||
|
viewId: state.viewId,
|
||||||
|
fieldController:
|
||||||
|
context.read<GridBloc>().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<GridBloc, GridState>(
|
||||||
|
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<RowInfo> 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<GridBloc>().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<double>? animation,
|
||||||
|
}) {
|
||||||
|
final rowCache = context.read<GridBloc>().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<GridBloc>().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<GridBloc, GridState, int>(
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
|||||||
import 'package:appflowy/generated/locale_keys.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/application/grid_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.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:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
|
|
||||||
@ -15,16 +16,18 @@ class GridAddRowButton extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final color =
|
||||||
|
PlatformExtension.isMobile ? null : Theme.of(context).hintColor;
|
||||||
return FlowyButton(
|
return FlowyButton(
|
||||||
text: FlowyText.medium(
|
text: FlowyText.medium(
|
||||||
LocaleKeys.grid_row_newRow.tr(),
|
LocaleKeys.grid_row_newRow.tr(),
|
||||||
color: Theme.of(context).colorScheme.tertiary,
|
color: color,
|
||||||
),
|
),
|
||||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||||
onTap: () => context.read<GridBloc>().add(const GridEvent.createRow()),
|
onTap: () => context.read<GridBloc>().add(const GridEvent.createRow()),
|
||||||
leftIcon: FlowySvg(
|
leftIcon: FlowySvg(
|
||||||
FlowySvgs.add_s,
|
FlowySvgs.add_s,
|
||||||
color: Theme.of(context).colorScheme.tertiary,
|
color: color,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,6 @@ class _GridFieldCellState extends State<GridFieldCell> {
|
|||||||
width: state.width,
|
width: state.width,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
fit: StackFit.expand,
|
|
||||||
children: [button, line],
|
children: [button, line],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -114,7 +113,6 @@ class _GridHeaderCellContainer extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
final decoration = BoxDecoration(
|
final decoration = BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
top: borderSide,
|
|
||||||
right: borderSide,
|
right: borderSide,
|
||||||
bottom: borderSide,
|
bottom: borderSide,
|
||||||
),
|
),
|
||||||
@ -123,10 +121,7 @@ class _GridHeaderCellContainer extends StatelessWidget {
|
|||||||
return Container(
|
return Container(
|
||||||
width: width,
|
width: width,
|
||||||
decoration: decoration,
|
decoration: decoration,
|
||||||
child: ConstrainedBox(
|
|
||||||
constraints: const BoxConstraints.expand(),
|
|
||||||
child: child,
|
child: child,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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/field_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.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/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_backend/log.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
@ -52,10 +54,7 @@ class _GridHeaderSliverAdaptorState extends State<GridHeaderSliverAdaptor> {
|
|||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
controller: widget.anchorScrollController,
|
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) {
|
builder: (context, state) {
|
||||||
final cells = state.fields
|
final cells = state.fields
|
||||||
.map(
|
.map(
|
||||||
(fieldInfo) => GridFieldCell(
|
(fieldInfo) => PlatformExtension.isDesktop
|
||||||
|
? GridFieldCell(
|
||||||
key: _getKeyById(fieldInfo.id),
|
key: _getKeyById(fieldInfo.id),
|
||||||
viewId: widget.viewId,
|
viewId: widget.viewId,
|
||||||
fieldInfo: fieldInfo,
|
fieldInfo: fieldInfo,
|
||||||
|
)
|
||||||
|
: MobileFieldButton(
|
||||||
|
key: _getKeyById(fieldInfo.id),
|
||||||
|
viewId: widget.viewId,
|
||||||
|
field: fieldInfo,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
return Container(
|
return ColoredBox(
|
||||||
color: Theme.of(context).colorScheme.surface,
|
color: Theme.of(context).colorScheme.surface,
|
||||||
child: RepaintBoundary(
|
child: RepaintBoundary(
|
||||||
child: ReorderableRow(
|
child: ReorderableRow(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
scrollController: ScrollController(),
|
scrollController: ScrollController(),
|
||||||
header: const _CellLeading(),
|
header: const _CellLeading(),
|
||||||
needsLongPressDraggable: false,
|
needsLongPressDraggable: PlatformExtension.isMobile,
|
||||||
footer: _CellTrailing(viewId: widget.viewId),
|
footer: _CellTrailing(viewId: widget.viewId),
|
||||||
onReorder: (int oldIndex, int newIndex) {
|
onReorder: (int oldIndex, int newIndex) {
|
||||||
_onReorder(cells, oldIndex, context, newIndex);
|
_onReorder(
|
||||||
|
cells,
|
||||||
|
oldIndex,
|
||||||
|
context,
|
||||||
|
newIndex,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
children: cells,
|
children: cells,
|
||||||
),
|
),
|
||||||
@ -123,13 +133,13 @@ class _GridHeaderState extends State<_GridHeader> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onReorder(
|
void _onReorder(
|
||||||
List<GridFieldCell> cells,
|
List<Widget> cells,
|
||||||
int oldIndex,
|
int oldIndex,
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
int newIndex,
|
int newIndex,
|
||||||
) {
|
) {
|
||||||
if (cells.length > oldIndex) {
|
if (cells.length > oldIndex) {
|
||||||
final field = cells[oldIndex].fieldInfo.field;
|
final field = (cells[oldIndex] as GridFieldCell).fieldInfo.field;
|
||||||
context
|
context
|
||||||
.read<GridHeaderBloc>()
|
.read<GridHeaderBloc>()
|
||||||
.add(GridHeaderEvent.moveField(field, oldIndex, newIndex));
|
.add(GridHeaderEvent.moveField(field, oldIndex, newIndex));
|
||||||
@ -159,7 +169,7 @@ class _CellTrailing extends StatelessWidget {
|
|||||||
return Container(
|
return Container(
|
||||||
width: GridSize.trailHeaderPadding,
|
width: GridSize.trailHeaderPadding,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(top: borderSide, bottom: borderSide),
|
border: Border(bottom: borderSide),
|
||||||
),
|
),
|
||||||
padding: GridSize.headerContentInsets,
|
padding: GridSize.headerContentInsets,
|
||||||
child: CreateFieldButton(viewId: viewId),
|
child: CreateFieldButton(viewId: viewId),
|
||||||
@ -189,8 +199,14 @@ class _CreateFieldButtonState extends State<CreateFieldButton> {
|
|||||||
constraints: BoxConstraints.loose(const Size(240, 600)),
|
constraints: BoxConstraints.loose(const Size(240, 600)),
|
||||||
triggerActions: PopoverTriggerFlags.none,
|
triggerActions: PopoverTriggerFlags.none,
|
||||||
child: FlowyButton(
|
child: FlowyButton(
|
||||||
|
margin: PlatformExtension.isDesktop
|
||||||
|
? GridSize.cellContentInsets
|
||||||
|
: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
|
||||||
radius: BorderRadius.zero,
|
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,
|
hoverColor: AFThemeExtension.of(context).greyHover,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final result = await TypeOptionBackendService.createFieldTypeOption(
|
final result = await TypeOptionBackendService.createFieldTypeOption(
|
||||||
|
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -54,20 +54,16 @@ class _GridRowState extends State<GridRow> {
|
|||||||
rowId: widget.rowId,
|
rowId: widget.rowId,
|
||||||
dataController: widget.dataController,
|
dataController: widget.dataController,
|
||||||
viewId: widget.viewId,
|
viewId: widget.viewId,
|
||||||
);
|
)..add(const RowEvent.initial());
|
||||||
_rowBloc.add(const RowEvent.initial());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider.value(
|
return BlocProvider.value(
|
||||||
value: _rowBloc,
|
value: _rowBloc,
|
||||||
child: _RowEnterRegion(
|
|
||||||
child: BlocBuilder<RowBloc, RowState>(
|
child: BlocBuilder<RowBloc, RowState>(
|
||||||
// The row need to rebuild when the cell count changes.
|
// The row need to rebuild when the cell count changes.
|
||||||
buildWhen: (p, c) =>
|
buildWhen: (p, c) => p.rowSource != c.rowSource,
|
||||||
p.cellByFieldId.length != c.cellByFieldId.length ||
|
|
||||||
p.rowSource != c.rowSource,
|
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final content = Expanded(
|
final content = Expanded(
|
||||||
child: RowContent(
|
child: RowContent(
|
||||||
@ -79,8 +75,9 @@ class _GridRowState extends State<GridRow> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return Row(
|
return _RowEnterRegion(
|
||||||
key: ValueKey(state.rowSource),
|
key: ValueKey(state.rowSource),
|
||||||
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
_RowLeading(
|
_RowLeading(
|
||||||
index: widget.index,
|
index: widget.index,
|
||||||
@ -88,10 +85,10 @@ class _GridRowState extends State<GridRow> {
|
|||||||
),
|
),
|
||||||
content,
|
content,
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,20 +152,19 @@ class _RowLeadingState extends State<_RowLeading> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const InsertRowButton(),
|
const InsertRowButton(),
|
||||||
if (isDraggable) ...[
|
if (isDraggable)
|
||||||
ReorderableDragStartListener(
|
ReorderableDragStartListener(
|
||||||
index: widget.index!,
|
index: widget.index!,
|
||||||
child: RowMenuButton(
|
child: RowMenuButton(
|
||||||
isDragEnabled: isDraggable,
|
isDragEnabled: isDraggable,
|
||||||
openMenu: popoverController.show,
|
openMenu: popoverController.show,
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
] else ...[
|
else
|
||||||
RowMenuButton(
|
RowMenuButton(
|
||||||
openMenu: popoverController.show,
|
openMenu: popoverController.show,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,8 +249,6 @@ class RowContent extends StatelessWidget {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return IntrinsicHeight(
|
return IntrinsicHeight(
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
..._makeCells(context, state.cellByFieldId),
|
..._makeCells(context, state.cellByFieldId),
|
||||||
@ -277,7 +271,6 @@ class RowContent extends StatelessWidget {
|
|||||||
return CellContainer(
|
return CellContainer(
|
||||||
width: cellId.fieldInfo.fieldSettings?.width.toDouble() ?? 140,
|
width: cellId.fieldInfo.fieldSettings?.width.toDouble() ?? 140,
|
||||||
isPrimary: cellId.fieldInfo.field.isPrimary,
|
isPrimary: cellId.fieldInfo.field.isPrimary,
|
||||||
cellContainerNotifier: CellContainerNotifier(child),
|
|
||||||
accessoryBuilder: (buildContext) {
|
accessoryBuilder: (buildContext) {
|
||||||
final builder = child.accessoryBuilder;
|
final builder = child.accessoryBuilder;
|
||||||
final List<GridCellAccessoryBuilder> accessories = [];
|
final List<GridCellAccessoryBuilder> accessories = [];
|
||||||
@ -317,7 +310,6 @@ class RowContent extends StatelessWidget {
|
|||||||
bottom: BorderSide(color: Theme.of(context).dividerColor),
|
bottom: BorderSide(color: Theme.of(context).dividerColor),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: const SizedBox.shrink(),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ class DatabaseViewLayoutCell extends StatelessWidget {
|
|||||||
child: FlowyButton(
|
child: FlowyButton(
|
||||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||||
text: FlowyText.medium(
|
text: FlowyText.medium(
|
||||||
databaseLayout.layoutName(),
|
databaseLayout.layoutName,
|
||||||
color: AFThemeExtension.of(context).textColor,
|
color: AFThemeExtension.of(context).textColor,
|
||||||
),
|
),
|
||||||
leftIcon: FlowySvg(
|
leftIcon: FlowySvg(
|
||||||
|
@ -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<GridFilterMenuBloc>(
|
||||||
|
create: (context) => GridFilterMenuBloc(
|
||||||
|
viewId: controller.viewId,
|
||||||
|
fieldController: controller.fieldController,
|
||||||
|
)..add(const GridFilterMenuEvent.initial()),
|
||||||
|
),
|
||||||
|
BlocProvider<SortMenuBloc>(
|
||||||
|
create: (context) => SortMenuBloc(
|
||||||
|
viewId: controller.viewId,
|
||||||
|
fieldController: controller.fieldController,
|
||||||
|
)..add(const SortMenuEvent.initial()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: MultiBlocListener(
|
||||||
|
listeners: [
|
||||||
|
BlocListener<GridFilterMenuBloc, GridFilterMenuState>(
|
||||||
|
listenWhen: (p, c) => p.isVisible != c.isVisible,
|
||||||
|
listener: (context, state) => toggleExtension.toggle(),
|
||||||
|
),
|
||||||
|
BlocListener<SortMenuBloc, SortMenuState>(
|
||||||
|
listenWhen: (p, c) => p.isVisible != c.isVisible,
|
||||||
|
listener: (context, state) => toggleExtension.toggle(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: ValueListenableBuilder<bool>(
|
||||||
|
valueListenable: controller.isLoading,
|
||||||
|
builder: (context, isLoading, child) {
|
||||||
|
if (isLoading) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
return IconButton(
|
||||||
|
onPressed: () {},
|
||||||
|
icon: const FlowySvg(
|
||||||
|
FlowySvgs.m_setting_m,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,15 @@
|
|||||||
import 'package:appflowy/plugins/database_view/application/database_controller.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/grid/application/grid_accessory_bloc.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/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:flowy_infra_ui/widget/spacing.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:provider/provider.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 {
|
class DatabaseViewSettingExtension extends StatelessWidget {
|
||||||
final String viewId;
|
final String viewId;
|
||||||
final DatabaseController databaseController;
|
final DatabaseController databaseController;
|
||||||
@ -57,26 +56,32 @@ class _DatabaseViewSettingContent extends StatelessWidget {
|
|||||||
return BlocBuilder<DatabaseViewSettingExtensionBloc,
|
return BlocBuilder<DatabaseViewSettingExtensionBloc,
|
||||||
DatabaseViewSettingExtensionState>(
|
DatabaseViewSettingExtensionState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return _wrapPadding(
|
return Padding(
|
||||||
Row(
|
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: [
|
children: [
|
||||||
SortMenu(fieldController: fieldController),
|
SortMenu(fieldController: fieldController),
|
||||||
const HSpace(6),
|
const HSpace(6),
|
||||||
FilterMenu(fieldController: fieldController),
|
FilterMenu(fieldController: fieldController),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _wrapPadding(Widget child) {
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: GridSize.leadingHeaderPadding,
|
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,7 +1,8 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.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:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/size.dart';
|
import 'package:flowy_infra/size.dart';
|
||||||
@ -11,7 +12,7 @@ import 'package:flowy_infra_ui/style_widget/extension.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class AddDatabaseViewButton extends StatefulWidget {
|
class AddDatabaseViewButton extends StatefulWidget {
|
||||||
final Function(AddButtonAction) onTap;
|
final Function(DatabaseLayoutPB) onTap;
|
||||||
const AddDatabaseViewButton({
|
const AddDatabaseViewButton({
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
super.key,
|
super.key,
|
||||||
@ -57,7 +58,7 @@ class _AddDatabaseViewButtonState extends State<AddDatabaseViewButton> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
popupBuilder: (BuildContext context) {
|
popupBuilder: (BuildContext context) {
|
||||||
return TarBarAddButtonAction(
|
return TabBarAddButtonAction(
|
||||||
onTap: (action) {
|
onTap: (action) {
|
||||||
popoverController.close();
|
popoverController.close();
|
||||||
widget.onTap(action);
|
widget.onTap(action);
|
||||||
@ -68,38 +69,37 @@ class _AddDatabaseViewButtonState extends State<AddDatabaseViewButton> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TarBarAddButtonAction extends StatelessWidget {
|
class TabBarAddButtonAction extends StatelessWidget {
|
||||||
final Function(AddButtonAction) onTap;
|
final Function(DatabaseLayoutPB) onTap;
|
||||||
const TarBarAddButtonAction({
|
const TabBarAddButtonAction({
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final cells = AddButtonAction.values.map((layout) {
|
final cells = DatabaseLayoutPB.values.map((layout) {
|
||||||
return TarBarAddButtonActionCell(
|
return TabBarAddButtonActionCell(
|
||||||
action: layout,
|
action: layout,
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
return ListView.separated(
|
return ListView.separated(
|
||||||
controller: ScrollController(),
|
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: cells.length,
|
itemCount: cells.length,
|
||||||
itemBuilder: (BuildContext context, int index) => cells[index],
|
itemBuilder: (BuildContext context, int index) => cells[index],
|
||||||
separatorBuilder: (BuildContext context, int index) =>
|
separatorBuilder: (BuildContext context, int index) =>
|
||||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 6.0),
|
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TarBarAddButtonActionCell extends StatelessWidget {
|
class TabBarAddButtonActionCell extends StatelessWidget {
|
||||||
final AddButtonAction action;
|
final DatabaseLayoutPB action;
|
||||||
final void Function(AddButtonAction) onTap;
|
final void Function(DatabaseLayoutPB) onTap;
|
||||||
const TarBarAddButtonActionCell({
|
const TabBarAddButtonActionCell({
|
||||||
required this.action,
|
required this.action,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
super.key,
|
super.key,
|
||||||
@ -112,7 +112,7 @@ class TarBarAddButtonActionCell extends StatelessWidget {
|
|||||||
child: FlowyButton(
|
child: FlowyButton(
|
||||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||||
text: FlowyText.medium(
|
text: FlowyText.medium(
|
||||||
'${LocaleKeys.grid_createView.tr()} ${action.title}',
|
'${LocaleKeys.grid_createView.tr()} ${action.layoutName}',
|
||||||
color: AFThemeExtension.of(context).textColor,
|
color: AFThemeExtension.of(context).textColor,
|
||||||
),
|
),
|
||||||
leftIcon: FlowySvg(
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.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/application/view/view_ext.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||||
@ -12,20 +13,16 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import '../application/tab_bar_bloc.dart';
|
|
||||||
import 'tab_bar_add_button.dart';
|
import 'tab_bar_add_button.dart';
|
||||||
|
|
||||||
class TabBarHeader extends StatefulWidget {
|
class TabBarHeader extends StatelessWidget {
|
||||||
const TabBarHeader({super.key});
|
const TabBarHeader({super.key});
|
||||||
|
|
||||||
@override
|
|
||||||
State<TabBarHeader> createState() => _TabBarHeaderState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _TabBarHeaderState extends State<TabBarHeader> {
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Stack(
|
return SizedBox(
|
||||||
|
height: 30,
|
||||||
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
@ -54,7 +51,7 @@ class _TabBarHeaderState extends State<TabBarHeader> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
const VSpace(3),
|
const VSpace(3),
|
||||||
pageSettingBarFromState(state),
|
pageSettingBarFromState(context, state),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -63,20 +60,21 @@ class _TabBarHeaderState extends State<TabBarHeader> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget pageSettingBarFromState(DatabaseTabBarState state) {
|
Widget pageSettingBarFromState(
|
||||||
|
BuildContext context,
|
||||||
|
DatabaseTabBarState state,
|
||||||
|
) {
|
||||||
if (state.tabBars.length < state.selectedIndex) {
|
if (state.tabBars.length < state.selectedIndex) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
final tarBar = state.tabBars[state.selectedIndex];
|
final tabBar = state.tabBars[state.selectedIndex];
|
||||||
final controller =
|
final controller =
|
||||||
state.tabBarControllerByViewId[tarBar.viewId]!.controller;
|
state.tabBarControllerByViewId[tabBar.viewId]!.controller;
|
||||||
return tarBar.builder.settingBar(
|
return tabBar.builder.settingBar(context, controller);
|
||||||
context,
|
|
||||||
controller,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,9 +119,9 @@ class _DatabaseTabBarState extends State<DatabaseTabBar> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
AddDatabaseViewButton(
|
AddDatabaseViewButton(
|
||||||
onTap: (action) async {
|
onTap: (layoutType) async {
|
||||||
context.read<DatabaseTabBarBloc>().add(
|
context.read<DatabaseTabBarBloc>().add(
|
||||||
DatabaseTabBarEvent.createView(action),
|
DatabaseTabBarEvent.createView(layoutType),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
@ -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<DatabaseTabBarBloc, DatabaseTabBarState>(
|
||||||
|
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),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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/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/database_view/widgets/share_button.dart';
|
||||||
import 'package:appflowy/plugins/util.dart';
|
import 'package:appflowy/plugins/util.dart';
|
||||||
import 'package:appflowy/startup/plugin/plugin.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/tab_bar_item.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart';
|
import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.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/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
import '../application/database_controller.dart';
|
import 'desktop/tab_bar_header.dart';
|
||||||
import 'tab_bar_header.dart';
|
|
||||||
|
|
||||||
abstract class DatabaseTabBarItemBuilder {
|
abstract class DatabaseTabBarItemBuilder {
|
||||||
const DatabaseTabBarItemBuilder();
|
const DatabaseTabBarItemBuilder();
|
||||||
@ -94,12 +96,11 @@ class _DatabaseTabBarViewState extends State<DatabaseTabBarView> {
|
|||||||
if (value) {
|
if (value) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
return const SizedBox(
|
return Padding(
|
||||||
height: 30,
|
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||||
child: Padding(
|
child: PlatformExtension.isDesktop
|
||||||
padding: EdgeInsets.symmetric(horizontal: 40),
|
? const TabBarHeader()
|
||||||
child: TabBarHeader(),
|
: const MobileTabBarHeader(),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -145,10 +146,10 @@ class _DatabaseTabBarViewState extends State<DatabaseTabBarView> {
|
|||||||
if (state.tabBars.length < state.selectedIndex) {
|
if (state.tabBars.length < state.selectedIndex) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
final tarBar = state.tabBars[state.selectedIndex];
|
final tabBar = state.tabBars[state.selectedIndex];
|
||||||
final controller =
|
final controller =
|
||||||
state.tabBarControllerByViewId[tarBar.viewId]!.controller;
|
state.tabBarControllerByViewId[tabBar.viewId]!.controller;
|
||||||
return tarBar.builder.settingBarExtension(
|
return tabBar.builder.settingBarExtension(
|
||||||
context,
|
context,
|
||||||
controller,
|
controller,
|
||||||
);
|
);
|
||||||
|
@ -1,30 +1,34 @@
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.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-database2/setting_entities.pbenum.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pbenum.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
extension DatabaseLayoutExtension on DatabaseLayoutPB {
|
extension DatabaseLayoutExtension on DatabaseLayoutPB {
|
||||||
String layoutName() {
|
String get layoutName {
|
||||||
switch (this) {
|
return switch (this) {
|
||||||
case DatabaseLayoutPB.Board:
|
DatabaseLayoutPB.Board => LocaleKeys.board_menuName.tr(),
|
||||||
return LocaleKeys.board_menuName.tr();
|
DatabaseLayoutPB.Calendar => LocaleKeys.calendar_menuName.tr(),
|
||||||
case DatabaseLayoutPB.Calendar:
|
DatabaseLayoutPB.Grid => LocaleKeys.grid_menuName.tr(),
|
||||||
return LocaleKeys.calendar_menuName.tr();
|
_ => "",
|
||||||
case DatabaseLayoutPB.Grid:
|
};
|
||||||
return LocaleKeys.grid_menuName.tr();
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ViewLayoutPB get layoutType {
|
||||||
|
return switch (this) {
|
||||||
|
DatabaseLayoutPB.Board => ViewLayoutPB.Board,
|
||||||
|
DatabaseLayoutPB.Calendar => ViewLayoutPB.Calendar,
|
||||||
|
DatabaseLayoutPB.Grid => ViewLayoutPB.Grid,
|
||||||
|
_ => throw UnimplementedError(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
FlowySvgData get icon {
|
FlowySvgData get icon {
|
||||||
switch (this) {
|
return switch (this) {
|
||||||
case DatabaseLayoutPB.Board:
|
DatabaseLayoutPB.Board => FlowySvgs.board_s,
|
||||||
return FlowySvgs.board_s;
|
DatabaseLayoutPB.Calendar => FlowySvgs.date_s,
|
||||||
case DatabaseLayoutPB.Calendar:
|
DatabaseLayoutPB.Grid => FlowySvgs.grid_s,
|
||||||
case DatabaseLayoutPB.Grid:
|
_ => throw UnimplementedError(),
|
||||||
return FlowySvgs.grid_s;
|
};
|
||||||
}
|
|
||||||
throw UnimplementedError();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
|
|||||||
import '../../application/cell/cell_service.dart';
|
import '../../application/cell/cell_service.dart';
|
||||||
import 'accessory/cell_accessory.dart';
|
import 'accessory/cell_accessory.dart';
|
||||||
import 'accessory/cell_shortcuts.dart';
|
import 'accessory/cell_shortcuts.dart';
|
||||||
|
import 'cells/cell_container.dart';
|
||||||
import 'cells/checkbox_cell/checkbox_cell.dart';
|
import 'cells/checkbox_cell/checkbox_cell.dart';
|
||||||
import 'cells/checklist_cell/checklist_cell.dart';
|
import 'cells/checklist_cell/checklist_cell.dart';
|
||||||
import 'cells/date_cell/date_cell.dart';
|
import 'cells/date_cell/date_cell.dart';
|
||||||
@ -200,7 +201,7 @@ class BlankCell extends StatelessWidget {
|
|||||||
abstract class CellEditable {
|
abstract class CellEditable {
|
||||||
RequestFocusListener get requestFocus;
|
RequestFocusListener get requestFocus;
|
||||||
|
|
||||||
ValueNotifier<bool> get onCellFocus;
|
CellContainerNotifier get cellContainerNotifier;
|
||||||
|
|
||||||
// ValueNotifier<bool> get onCellEditing;
|
// ValueNotifier<bool> get onCellEditing;
|
||||||
}
|
}
|
||||||
@ -223,11 +224,11 @@ abstract class GridCellWidget extends StatefulWidget
|
|||||||
GridCellWidget({super.key});
|
GridCellWidget({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final ValueNotifier<bool> onCellFocus = ValueNotifier<bool>(false);
|
final CellContainerNotifier cellContainerNotifier = CellContainerNotifier();
|
||||||
|
|
||||||
// When the cell is focused, we assume that the accessory also be hovered.
|
// When the cell is focused, we assume that the accessory also be hovered.
|
||||||
@override
|
@override
|
||||||
ValueNotifier<bool> get onAccessoryHover => onCellFocus;
|
ValueNotifier<bool> get onAccessoryHover => ValueNotifier(false);
|
||||||
|
|
||||||
// @override
|
// @override
|
||||||
// final ValueNotifier<bool> onCellEditing = ValueNotifier<bool>(false);
|
// final ValueNotifier<bool> onCellEditing = ValueNotifier<bool>(false);
|
||||||
@ -278,17 +279,19 @@ abstract class GridEditableTextCell<T extends GridCellWidget>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
super.initState();
|
||||||
widget.shortcutHandlers[CellKeyboardKey.onEnter] =
|
widget.shortcutHandlers[CellKeyboardKey.onEnter] =
|
||||||
() => focusNode.unfocus();
|
() => focusNode.unfocus();
|
||||||
_listenOnFocusNodeChanged();
|
_listenOnFocusNodeChanged();
|
||||||
super.initState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant T oldWidget) {
|
void didUpdateWidget(covariant T oldWidget) {
|
||||||
if (oldWidget != this) {
|
// if (!focusNode.hasFocus && widget.cellContainerNotifier.isFocus) {
|
||||||
_listenOnFocusNodeChanged();
|
// focusNode.requestFocus();
|
||||||
}
|
// } else if (focusNode.hasFocus && !widget.cellContainerNotifier.isFocus) {
|
||||||
|
// focusNode.unfocus();
|
||||||
|
// }
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,15 +305,15 @@ abstract class GridEditableTextCell<T extends GridCellWidget>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void requestBeginFocus() {
|
void requestBeginFocus() {
|
||||||
if (focusNode.hasFocus == false && focusNode.canRequestFocus) {
|
if (!focusNode.hasFocus && focusNode.canRequestFocus) {
|
||||||
FocusScope.of(context).requestFocus(focusNode);
|
FocusScope.of(context).requestFocus(focusNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenOnFocusNodeChanged() {
|
void _listenOnFocusNodeChanged() {
|
||||||
widget.onCellFocus.value = focusNode.hasFocus;
|
widget.cellContainerNotifier.isFocus = focusNode.hasFocus;
|
||||||
focusNode.setListener(() {
|
focusNode.setListener(() {
|
||||||
widget.onCellFocus.value = focusNode.hasFocus;
|
widget.cellContainerNotifier.isFocus = focusNode.hasFocus;
|
||||||
focusChanged();
|
focusChanged();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -13,28 +13,26 @@ class CellContainer extends StatelessWidget {
|
|||||||
final AccessoryBuilder? accessoryBuilder;
|
final AccessoryBuilder? accessoryBuilder;
|
||||||
final double width;
|
final double width;
|
||||||
final bool isPrimary;
|
final bool isPrimary;
|
||||||
final CellContainerNotifier cellContainerNotifier;
|
|
||||||
|
|
||||||
const CellContainer({
|
const CellContainer({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.child,
|
required this.child,
|
||||||
required this.width,
|
required this.width,
|
||||||
required this.isPrimary,
|
required this.isPrimary,
|
||||||
required this.cellContainerNotifier,
|
|
||||||
this.accessoryBuilder,
|
this.accessoryBuilder,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ChangeNotifierProvider.value(
|
return ChangeNotifierProvider.value(
|
||||||
value: cellContainerNotifier,
|
value: child.cellContainerNotifier,
|
||||||
child: Selector<CellContainerNotifier, bool>(
|
child: Selector<CellContainerNotifier, bool>(
|
||||||
selector: (context, notifier) => notifier.isFocus,
|
selector: (context, notifier) => notifier.isFocus,
|
||||||
builder: (privderContext, isFocus, _) {
|
builder: (providerContext, isFocus, _) {
|
||||||
Widget container = Center(child: GridCellShortcuts(child: child));
|
Widget container = Center(child: GridCellShortcuts(child: child));
|
||||||
|
|
||||||
if (accessoryBuilder != null) {
|
if (accessoryBuilder != null) {
|
||||||
final accessories = accessoryBuilder!(
|
final accessories = accessoryBuilder!.call(
|
||||||
GridCellAccessoryBuildContext(
|
GridCellAccessoryBuildContext(
|
||||||
anchorContext: context,
|
anchorContext: context,
|
||||||
isCellEditing: isFocus,
|
isCellEditing: isFocus,
|
||||||
@ -52,7 +50,11 @@ class CellContainer extends StatelessWidget {
|
|||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: () => child.requestFocus.notify(),
|
onTap: () {
|
||||||
|
if (!isFocus) {
|
||||||
|
child.requestFocus.notify();
|
||||||
|
}
|
||||||
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: BoxConstraints(maxWidth: width, minHeight: 46),
|
constraints: BoxConstraints(maxWidth: width, minHeight: 46),
|
||||||
decoration: _makeBoxDecoration(context, isFocus),
|
decoration: _makeBoxDecoration(context, isFocus),
|
||||||
@ -81,9 +83,6 @@ class CellContainer extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _GridCellEnterRegion extends StatelessWidget {
|
class _GridCellEnterRegion extends StatelessWidget {
|
||||||
final Widget child;
|
|
||||||
final List<GridCellAccessoryBuilder> accessories;
|
|
||||||
final bool isPrimary;
|
|
||||||
const _GridCellEnterRegion({
|
const _GridCellEnterRegion({
|
||||||
required this.child,
|
required this.child,
|
||||||
required this.accessories,
|
required this.accessories,
|
||||||
@ -91,6 +90,10 @@ class _GridCellEnterRegion extends StatelessWidget {
|
|||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
final List<GridCellAccessoryBuilder> accessories;
|
||||||
|
final bool isPrimary;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Selector2<RegionStateNotifier, CellContainerNotifier, bool>(
|
return Selector2<RegionStateNotifier, CellContainerNotifier, bool>(
|
||||||
@ -99,6 +102,7 @@ class _GridCellEnterRegion extends StatelessWidget {
|
|||||||
(cellNotifier.onEnter || regionNotifier.onEnter && isPrimary),
|
(cellNotifier.onEnter || regionNotifier.onEnter && isPrimary),
|
||||||
builder: (context, showAccessory, _) {
|
builder: (context, showAccessory, _) {
|
||||||
final List<Widget> children = [child];
|
final List<Widget> children = [child];
|
||||||
|
|
||||||
if (showAccessory) {
|
if (showAccessory) {
|
||||||
children.add(
|
children.add(
|
||||||
CellAccessoryContainer(accessories: accessories).positioned(
|
CellAccessoryContainer(accessories: accessories).positioned(
|
||||||
@ -110,11 +114,9 @@ class _GridCellEnterRegion extends StatelessWidget {
|
|||||||
return MouseRegion(
|
return MouseRegion(
|
||||||
cursor: SystemMouseCursors.click,
|
cursor: SystemMouseCursors.click,
|
||||||
onEnter: (p) =>
|
onEnter: (p) =>
|
||||||
Provider.of<CellContainerNotifier>(context, listen: false)
|
CellContainerNotifier.of(context, listen: false).onEnter = true,
|
||||||
.onEnter = true,
|
|
||||||
onExit: (p) =>
|
onExit: (p) =>
|
||||||
Provider.of<CellContainerNotifier>(context, listen: false)
|
CellContainerNotifier.of(context, listen: false).onEnter = false,
|
||||||
.onEnter = false,
|
|
||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: AlignmentDirectional.center,
|
alignment: AlignmentDirectional.center,
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
@ -127,24 +129,9 @@ class _GridCellEnterRegion extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class CellContainerNotifier extends ChangeNotifier {
|
class CellContainerNotifier extends ChangeNotifier {
|
||||||
final CellEditable cellEditable;
|
|
||||||
VoidCallback? _onCellFocusListener;
|
|
||||||
bool _isFocus = false;
|
bool _isFocus = false;
|
||||||
bool _onEnter = 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) {
|
set isFocus(bool value) {
|
||||||
if (_isFocus != value) {
|
if (_isFocus != value) {
|
||||||
_isFocus = value;
|
_isFocus = value;
|
||||||
@ -162,4 +149,8 @@ class CellContainerNotifier extends ChangeNotifier {
|
|||||||
bool get isFocus => _isFocus;
|
bool get isFocus => _isFocus;
|
||||||
|
|
||||||
bool get onEnter => _onEnter;
|
bool get onEnter => _onEnter;
|
||||||
|
|
||||||
|
static CellContainerNotifier of(BuildContext context, {bool listen = true}) {
|
||||||
|
return Provider.of<CellContainerNotifier>(context, listen: listen);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,16 +144,16 @@ class GridChecklistCellState extends GridCellState<GridChecklistCell> {
|
|||||||
constraints: BoxConstraints.loose(const Size(360, 400)),
|
constraints: BoxConstraints.loose(const Size(360, 400)),
|
||||||
direction: PopoverDirection.bottomWithLeftAligned,
|
direction: PopoverDirection.bottomWithLeftAligned,
|
||||||
triggerActions: PopoverTriggerFlags.none,
|
triggerActions: PopoverTriggerFlags.none,
|
||||||
popupBuilder: (BuildContext context) {
|
popupBuilder: (BuildContext popoverContext) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
widget.onCellFocus.value = true;
|
widget.cellContainerNotifier.isFocus = true;
|
||||||
});
|
});
|
||||||
return GridChecklistCellEditor(
|
return GridChecklistCellEditor(
|
||||||
cellController: widget.cellControllerBuilder.build()
|
cellController: widget.cellControllerBuilder.build()
|
||||||
as ChecklistCellController,
|
as ChecklistCellController,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onClose: () => widget.onCellFocus.value = false,
|
onClose: () => widget.cellContainerNotifier.isFocus = false,
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
@ -87,11 +87,11 @@ class _DateCellState extends GridCellState<GridDateCell> {
|
|||||||
return DateCellEditor(
|
return DateCellEditor(
|
||||||
cellController:
|
cellController:
|
||||||
widget.cellControllerBuilder.build() as DateCellController,
|
widget.cellControllerBuilder.build() as DateCellController,
|
||||||
onDismissed: () => widget.onCellFocus.value = false,
|
onDismissed: () => widget.cellContainerNotifier.isFocus = false,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onClose: () {
|
onClose: () {
|
||||||
widget.onCellFocus.value = false;
|
widget.cellContainerNotifier.isFocus = false;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -108,7 +108,7 @@ class _DateCellState extends GridCellState<GridDateCell> {
|
|||||||
@override
|
@override
|
||||||
void requestBeginFocus() {
|
void requestBeginFocus() {
|
||||||
_popover.show();
|
_popover.show();
|
||||||
widget.onCellFocus.value = true;
|
widget.cellContainerNotifier.isFocus = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -80,9 +80,14 @@ class _NumberCellState extends GridEditableTextCell<GridNumberCell> {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
|
focusedBorder: InputBorder.none,
|
||||||
|
enabledBorder: InputBorder.none,
|
||||||
|
errorBorder: InputBorder.none,
|
||||||
|
disabledBorder: InputBorder.none,
|
||||||
hintText: widget.cellStyle.placeholder,
|
hintText: widget.cellStyle.placeholder,
|
||||||
isDense: true,
|
isDense: true,
|
||||||
),
|
),
|
||||||
|
onTapOutside: (_) => focusNode.unfocus(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -64,7 +64,8 @@ class _SingleSelectCellState extends GridCellState<GridSingleSelectCell> {
|
|||||||
return SelectOptionWrap(
|
return SelectOptionWrap(
|
||||||
selectOptions: state.selectedOptions,
|
selectOptions: state.selectedOptions,
|
||||||
cellStyle: widget.cellStyle,
|
cellStyle: widget.cellStyle,
|
||||||
onCellEditing: widget.onCellFocus,
|
onCellEditing: (isFocus) =>
|
||||||
|
widget.cellContainerNotifier.isFocus = isFocus,
|
||||||
popoverController: _popover,
|
popoverController: _popover,
|
||||||
cellControllerBuilder: widget.cellControllerBuilder,
|
cellControllerBuilder: widget.cellControllerBuilder,
|
||||||
);
|
);
|
||||||
@ -127,7 +128,8 @@ class _MultiSelectCellState extends GridCellState<GridMultiSelectCell> {
|
|||||||
return SelectOptionWrap(
|
return SelectOptionWrap(
|
||||||
selectOptions: state.selectedOptions,
|
selectOptions: state.selectedOptions,
|
||||||
cellStyle: widget.cellStyle,
|
cellStyle: widget.cellStyle,
|
||||||
onCellEditing: widget.onCellFocus,
|
onCellEditing: (isFocus) =>
|
||||||
|
widget.cellContainerNotifier.isFocus = isFocus,
|
||||||
popoverController: _popover,
|
popoverController: _popover,
|
||||||
cellControllerBuilder: widget.cellControllerBuilder,
|
cellControllerBuilder: widget.cellControllerBuilder,
|
||||||
);
|
);
|
||||||
@ -151,7 +153,7 @@ class SelectOptionWrap extends StatefulWidget {
|
|||||||
final SelectOptionCellStyle? cellStyle;
|
final SelectOptionCellStyle? cellStyle;
|
||||||
final CellControllerBuilder cellControllerBuilder;
|
final CellControllerBuilder cellControllerBuilder;
|
||||||
final PopoverController popoverController;
|
final PopoverController popoverController;
|
||||||
final ValueNotifier onCellEditing;
|
final void Function(bool) onCellEditing;
|
||||||
|
|
||||||
const SelectOptionWrap({
|
const SelectOptionWrap({
|
||||||
required this.selectOptions,
|
required this.selectOptions,
|
||||||
@ -179,16 +181,16 @@ class _SelectOptionWrapState extends State<SelectOptionWrap> {
|
|||||||
constraints: constraints,
|
constraints: constraints,
|
||||||
margin: EdgeInsets.zero,
|
margin: EdgeInsets.zero,
|
||||||
direction: PopoverDirection.bottomWithLeftAligned,
|
direction: PopoverDirection.bottomWithLeftAligned,
|
||||||
popupBuilder: (BuildContext context) {
|
popupBuilder: (BuildContext popoverContext) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
widget.onCellEditing.value = true;
|
widget.onCellEditing(true);
|
||||||
});
|
});
|
||||||
return SelectOptionCellEditor(
|
return SelectOptionCellEditor(
|
||||||
cellController: widget.cellControllerBuilder.build()
|
cellController: widget.cellControllerBuilder.build()
|
||||||
as SelectOptionCellController,
|
as SelectOptionCellController,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onClose: () => widget.onCellEditing.value = false,
|
onClose: () => widget.onCellEditing(false),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: widget.cellStyle?.cellPadding ?? GridSize.cellContentInsets,
|
padding: widget.cellStyle?.cellPadding ?? GridSize.cellContentInsets,
|
||||||
child: child,
|
child: child,
|
||||||
|
@ -122,9 +122,14 @@ class _GridTextCellState extends GridEditableTextCell<GridTextCell> {
|
|||||||
bottom: GridSize.cellContentInsets.bottom,
|
bottom: GridSize.cellContentInsets.bottom,
|
||||||
),
|
),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
|
focusedBorder: InputBorder.none,
|
||||||
|
enabledBorder: InputBorder.none,
|
||||||
|
errorBorder: InputBorder.none,
|
||||||
|
disabledBorder: InputBorder.none,
|
||||||
hintText: widget.cellStyle.placeholder,
|
hintText: widget.cellStyle.placeholder,
|
||||||
isDense: true,
|
isDense: true,
|
||||||
),
|
),
|
||||||
|
onTapOutside: (_) => focusNode.unfocus(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -110,7 +110,6 @@ class GridURLCell extends GridCellWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _GridURLCellState extends GridEditableTextCell<GridURLCell> {
|
class _GridURLCellState extends GridEditableTextCell<GridURLCell> {
|
||||||
final _popoverController = PopoverController();
|
|
||||||
late final URLCellBloc _cellBloc;
|
late final URLCellBloc _cellBloc;
|
||||||
late final TextEditingController _controller;
|
late final TextEditingController _controller;
|
||||||
|
|
||||||
@ -167,10 +166,15 @@ class _GridURLCellState extends GridEditableTextCell<GridURLCell> {
|
|||||||
bottom: GridSize.cellContentInsets.bottom,
|
bottom: GridSize.cellContentInsets.bottom,
|
||||||
),
|
),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
|
focusedBorder: InputBorder.none,
|
||||||
|
enabledBorder: InputBorder.none,
|
||||||
|
errorBorder: InputBorder.none,
|
||||||
|
disabledBorder: InputBorder.none,
|
||||||
hintText: widget.cellStyle?.placeholder,
|
hintText: widget.cellStyle?.placeholder,
|
||||||
hintStyle: style.copyWith(color: Theme.of(context).hintColor),
|
hintStyle: style.copyWith(color: Theme.of(context).hintColor),
|
||||||
isDense: true,
|
isDense: true,
|
||||||
),
|
),
|
||||||
|
onTapOutside: (_) => focusNode.unfocus(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -184,12 +188,6 @@ class _GridURLCellState extends GridEditableTextCell<GridURLCell> {
|
|||||||
return super.focusChanged();
|
return super.focusChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void requestBeginFocus() {
|
|
||||||
widget.onCellFocus.value = true;
|
|
||||||
_popoverController.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String? onCopy() => _cellBloc.state.content;
|
String? onCopy() => _cellBloc.state.content;
|
||||||
}
|
}
|
||||||
|
@ -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/board/presentation/board_page.dart';
|
||||||
import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_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/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/database_view/tab_bar/tab_bar_view.dart';
|
||||||
import 'package:appflowy/plugins/document/document.dart';
|
import 'package:appflowy/plugins/document/document.dart';
|
||||||
import 'package:appflowy/startup/plugin/plugin.dart';
|
import 'package:appflowy/startup/plugin/plugin.dart';
|
||||||
@ -90,19 +91,31 @@ extension ViewExtension on ViewPB {
|
|||||||
throw UnimplementedError;
|
throw UnimplementedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
DatabaseTabBarItemBuilder tarBarItem() {
|
DatabaseTabBarItemBuilder tabBarItem() {
|
||||||
switch (layout) {
|
switch (layout) {
|
||||||
case ViewLayoutPB.Board:
|
case ViewLayoutPB.Board:
|
||||||
return BoardPageTabBarBuilderImpl();
|
return BoardPageTabBarBuilderImpl();
|
||||||
case ViewLayoutPB.Calendar:
|
case ViewLayoutPB.Calendar:
|
||||||
return CalendarPageTabBarBuilderImpl();
|
return CalendarPageTabBarBuilderImpl();
|
||||||
case ViewLayoutPB.Grid:
|
case ViewLayoutPB.Grid:
|
||||||
return GridPageTabBarBuilderImpl();
|
return DesktopGridTabBarBuilderImpl();
|
||||||
case ViewLayoutPB.Document:
|
default:
|
||||||
throw UnimplementedError;
|
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;
|
FlowySvgData get iconData => layout.icon;
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ class StyledSingleChildScrollView extends StatefulWidget {
|
|||||||
|
|
||||||
const StyledSingleChildScrollView({
|
const StyledSingleChildScrollView({
|
||||||
Key? key,
|
Key? key,
|
||||||
@required this.child,
|
required this.child,
|
||||||
this.contentSize,
|
this.contentSize,
|
||||||
this.axis = Axis.vertical,
|
this.axis = Axis.vertical,
|
||||||
this.trackColor,
|
this.trackColor,
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class FlowyText extends StatelessWidget {
|
class FlowyText extends StatelessWidget {
|
||||||
@ -91,12 +89,6 @@ class FlowyText extends StatelessWidget {
|
|||||||
maxLines: maxLines,
|
maxLines: maxLines,
|
||||||
textAlign: textAlign,
|
textAlign: textAlign,
|
||||||
overflow: overflow ?? TextOverflow.clip,
|
overflow: overflow ?? TextOverflow.clip,
|
||||||
textHeightBehavior: Platform.isAndroid || Platform.isIOS
|
|
||||||
? const TextHeightBehavior(
|
|
||||||
applyHeightToFirstAscent: false,
|
|
||||||
applyHeightToLastDescent: false,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||||
fontSize: fontSize,
|
fontSize: fontSize,
|
||||||
fontWeight: fontWeight,
|
fontWeight: fontWeight,
|
||||||
|
Loading…
Reference in New Issue
Block a user