mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: create database view on same database (#2829)
* feat: create database view on same database * feat: switch tag between views * fix: calendar tool bar * fix: set layout setting * chore: update collab rev * fix: board layout issue * test: add integration tests * test: add calendar start from day test
This commit is contained in:
parent
79fc7c4cfe
commit
e50d708c21
@ -236,6 +236,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"grid": {
|
"grid": {
|
||||||
|
"deleteView": "Are you sure you want to delete this view?",
|
||||||
"settings": {
|
"settings": {
|
||||||
"filter": "Filter",
|
"filter": "Filter",
|
||||||
"sort": "Sort",
|
"sort": "Sort",
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pbenum.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
|
import 'util/database_test_op.dart';
|
||||||
|
import 'util/util.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
group('database', () {
|
||||||
|
const location = 'appflowy';
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
await TestFolder.cleanTestLocation(location);
|
||||||
|
await TestFolder.setTestLocation(location);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
await TestFolder.cleanTestLocation(location);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDownAll(() async {
|
||||||
|
await TestFolder.cleanTestLocation(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('update calendar layout', (tester) async {
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapGoButton();
|
||||||
|
|
||||||
|
await tester.tapAddButton();
|
||||||
|
await tester.tapCreateCalendarButton();
|
||||||
|
|
||||||
|
// open setting
|
||||||
|
await tester.tapDatabaseSettingButton();
|
||||||
|
await tester.tapDatabaseLayoutButton();
|
||||||
|
await tester.selectDatabaseLayoutType(DatabaseLayoutPB.Board);
|
||||||
|
await tester.assertCurrentDatabaseLayoutType(DatabaseLayoutPB.Board);
|
||||||
|
|
||||||
|
await tester.tapDatabaseSettingButton();
|
||||||
|
await tester.tapDatabaseLayoutButton();
|
||||||
|
await tester.selectDatabaseLayoutType(DatabaseLayoutPB.Grid);
|
||||||
|
await tester.assertCurrentDatabaseLayoutType(DatabaseLayoutPB.Grid);
|
||||||
|
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('calendar start from day setting', (tester) async {
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapGoButton();
|
||||||
|
|
||||||
|
// Create calendar view
|
||||||
|
await tester.createNewPageWithName(ViewLayoutPB.Calendar, 'calendar');
|
||||||
|
|
||||||
|
// Open setting
|
||||||
|
await tester.tapDatabaseSettingButton();
|
||||||
|
await tester.tapCalendarLayoutSettingButton();
|
||||||
|
|
||||||
|
// select the first day of week is Monday
|
||||||
|
await tester.tapFirstDayOfWeek();
|
||||||
|
await tester.tapFirstDayOfWeekStartFromMonday();
|
||||||
|
|
||||||
|
// Open the other page and open the new calendar page again
|
||||||
|
await tester.openPage(readme);
|
||||||
|
await tester.pumpAndSettle(const Duration(milliseconds: 300));
|
||||||
|
await tester.openPage('calendar');
|
||||||
|
|
||||||
|
// Open setting again and check the start from Monday is selected
|
||||||
|
await tester.tapDatabaseSettingButton();
|
||||||
|
await tester.tapCalendarLayoutSettingButton();
|
||||||
|
await tester.tapFirstDayOfWeek();
|
||||||
|
tester.assertFirstDayOfWeekStartFromMonday();
|
||||||
|
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
import 'package:appflowy/plugins/database_view/tar_bar/tar_bar_add_button.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
|
import 'util/database_test_op.dart';
|
||||||
|
import 'util/util.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
group('database', () {
|
||||||
|
const location = 'appflowy';
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
await TestFolder.cleanTestLocation(location);
|
||||||
|
await TestFolder.setTestLocation(location);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
await TestFolder.cleanTestLocation(location);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDownAll(() async {
|
||||||
|
await TestFolder.cleanTestLocation(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('create linked view', (tester) async {
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapGoButton();
|
||||||
|
|
||||||
|
await tester.tapAddButton();
|
||||||
|
await tester.tapCreateGridButton();
|
||||||
|
|
||||||
|
// Create board view
|
||||||
|
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board);
|
||||||
|
tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Board);
|
||||||
|
|
||||||
|
// Create grid view
|
||||||
|
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.grid);
|
||||||
|
tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Grid);
|
||||||
|
|
||||||
|
// Create calendar view
|
||||||
|
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.calendar);
|
||||||
|
tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Calendar);
|
||||||
|
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('rename and delete linked view', (tester) async {
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapGoButton();
|
||||||
|
|
||||||
|
await tester.tapAddButton();
|
||||||
|
await tester.tapCreateGridButton();
|
||||||
|
|
||||||
|
// Create board view
|
||||||
|
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board);
|
||||||
|
tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Board);
|
||||||
|
|
||||||
|
// rename board view
|
||||||
|
await tester.renameLinkedView(
|
||||||
|
tester.findTabBarLinkViewByViewLayout(ViewLayoutPB.Board),
|
||||||
|
'new board',
|
||||||
|
);
|
||||||
|
final findBoard = tester.findTabBarLinkViewByViewName('new board');
|
||||||
|
expect(findBoard, findsOneWidget);
|
||||||
|
|
||||||
|
// delete the board
|
||||||
|
await tester.deleteDatebaseView(findBoard);
|
||||||
|
expect(tester.findTabBarLinkViewByViewName('new board'), findsNothing);
|
||||||
|
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('delete the last database view', (tester) async {
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await tester.tapGoButton();
|
||||||
|
|
||||||
|
await tester.tapAddButton();
|
||||||
|
await tester.tapCreateGridButton();
|
||||||
|
|
||||||
|
// Create board view
|
||||||
|
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board);
|
||||||
|
tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Board);
|
||||||
|
|
||||||
|
// delete the board
|
||||||
|
await tester.deleteDatebaseView(
|
||||||
|
tester.findTabBarLinkViewByViewLayout(ViewLayoutPB.Board),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -13,6 +13,8 @@ import 'database_row_page_test.dart' as database_row_page_test;
|
|||||||
import 'database_row_test.dart' as database_row_test;
|
import 'database_row_test.dart' as database_row_test;
|
||||||
import 'database_setting_test.dart' as database_setting_test;
|
import 'database_setting_test.dart' as database_setting_test;
|
||||||
import 'database_filter_test.dart' as database_filter_test;
|
import 'database_filter_test.dart' as database_filter_test;
|
||||||
|
import 'database_view_test.dart' as database_view_test;
|
||||||
|
import 'database_calendar_test.dart' as database_calendar_test;
|
||||||
|
|
||||||
/// The main task runner for all integration tests in AppFlowy.
|
/// The main task runner for all integration tests in AppFlowy.
|
||||||
///
|
///
|
||||||
@ -29,6 +31,8 @@ void main() {
|
|||||||
share_markdown_test.main();
|
share_markdown_test.main();
|
||||||
import_files_test.main();
|
import_files_test.main();
|
||||||
document_with_database_test.main();
|
document_with_database_test.main();
|
||||||
|
|
||||||
|
// Database integration tests
|
||||||
database_cell_test.main();
|
database_cell_test.main();
|
||||||
database_field_test.main();
|
database_field_test.main();
|
||||||
database_share_test.main();
|
database_share_test.main();
|
||||||
@ -36,6 +40,9 @@ void main() {
|
|||||||
database_row_test.main();
|
database_row_test.main();
|
||||||
database_setting_test.main();
|
database_setting_test.main();
|
||||||
database_filter_test.main();
|
database_filter_test.main();
|
||||||
|
database_view_test.main();
|
||||||
|
database_calendar_test.main();
|
||||||
|
|
||||||
// board_test.main();
|
// board_test.main();
|
||||||
// empty_document_test.main();
|
// empty_document_test.main();
|
||||||
// smart_menu_test.main();
|
// smart_menu_test.main();
|
||||||
|
@ -87,6 +87,7 @@ extension AppFlowyTestBase on WidgetTester {
|
|||||||
}) async {
|
}) async {
|
||||||
await tap(
|
await tap(
|
||||||
finder,
|
finder,
|
||||||
|
buttons: buttons,
|
||||||
warnIfMissed: warnIfMissed,
|
warnIfMissed: warnIfMissed,
|
||||||
);
|
);
|
||||||
await pumpAndSettle(Duration(milliseconds: milliseconds));
|
await pumpAndSettle(Duration(milliseconds: milliseconds));
|
||||||
|
@ -47,6 +47,13 @@ extension CommonOperations on WidgetTester {
|
|||||||
await tapButtonWithName(LocaleKeys.grid_menuName.tr());
|
await tapButtonWithName(LocaleKeys.grid_menuName.tr());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tap the create grid button.
|
||||||
|
///
|
||||||
|
/// Must call [tapAddButton] first.
|
||||||
|
Future<void> tapCreateCalendarButton() async {
|
||||||
|
await tapButtonWithName(LocaleKeys.calendar_menuName.tr());
|
||||||
|
}
|
||||||
|
|
||||||
/// Tap the import button.
|
/// Tap the import button.
|
||||||
///
|
///
|
||||||
/// Must call [tapAddButton] first.
|
/// Must call [tapAddButton] first.
|
||||||
@ -142,7 +149,9 @@ extension CommonOperations on WidgetTester {
|
|||||||
|
|
||||||
/// open the page with given name.
|
/// open the page with given name.
|
||||||
Future<void> openPage(String name) async {
|
Future<void> openPage(String name) async {
|
||||||
await tapButton(findPageName(name));
|
final finder = findPageName(name);
|
||||||
|
expect(finder, findsOneWidget);
|
||||||
|
await tapButton(finder);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tap the ... button beside the page name.
|
/// Tap the ... button beside the page name.
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/setting/setting_bloc.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/calendar/presentation/toolbar/calendar_layout_setting.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/checkbox.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/checkbox.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/checklist/checklist.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/checklist/checklist.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/select_option/option_list.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/select_option/option_list.dart';
|
||||||
@ -19,6 +18,9 @@ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/
|
|||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart';
|
||||||
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/tar_bar/tab_bar_view.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/tar_bar/tar_bar_add_button.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/text_field.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/text_field.dart';
|
||||||
@ -29,10 +31,13 @@ import 'package:appflowy/plugins/database_view/widgets/setting/database_setting.
|
|||||||
import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart';
|
import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.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:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text_field.dart';
|
import 'package:flowy_infra_ui/style_widget/text_field.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/text_input.dart';
|
||||||
import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
|
import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
@ -728,6 +733,152 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
await tapButton(button);
|
await tapButton(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> tapCalendarLayoutSettingButton() async {
|
||||||
|
final findSettingItem = find.byType(DatabaseSettingItem);
|
||||||
|
final findLayoutButton = find.byWidgetPredicate(
|
||||||
|
(widget) =>
|
||||||
|
widget is FlowyText &&
|
||||||
|
widget.text == DatabaseSettingAction.showCalendarLayout.title(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final button = find.descendant(
|
||||||
|
of: findSettingItem,
|
||||||
|
matching: findLayoutButton,
|
||||||
|
);
|
||||||
|
|
||||||
|
await tapButton(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> tapFirstDayOfWeek() async {
|
||||||
|
await tapButton(find.byType(FirstDayOfWeek));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> tapFirstDayOfWeekStartFromSunday() async {
|
||||||
|
final finder = find.byWidgetPredicate(
|
||||||
|
(widget) => widget is StartFromButton && widget.dayIndex == 0,
|
||||||
|
);
|
||||||
|
await tapButton(finder);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> tapFirstDayOfWeekStartFromMonday() async {
|
||||||
|
final finder = find.byWidgetPredicate(
|
||||||
|
(widget) => widget is StartFromButton && widget.dayIndex == 1,
|
||||||
|
);
|
||||||
|
await tapButton(finder);
|
||||||
|
|
||||||
|
// Dismiss the popover overlay in cause of obscure the tapButton
|
||||||
|
// in the next test case.
|
||||||
|
await sendKeyEvent(LogicalKeyboardKey.escape);
|
||||||
|
await pumpAndSettle(const Duration(milliseconds: 200));
|
||||||
|
}
|
||||||
|
|
||||||
|
void assertFirstDayOfWeekStartFromMonday() {
|
||||||
|
final finder = find.byWidgetPredicate(
|
||||||
|
(widget) =>
|
||||||
|
widget is StartFromButton &&
|
||||||
|
widget.dayIndex == 1 &&
|
||||||
|
widget.isSelected == true,
|
||||||
|
);
|
||||||
|
expect(finder, findsOneWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
void assertFirstDayOfWeekStartFromSunday() {
|
||||||
|
final finder = find.byWidgetPredicate(
|
||||||
|
(widget) =>
|
||||||
|
widget is StartFromButton &&
|
||||||
|
widget.dayIndex == 0 &&
|
||||||
|
widget.isSelected == true,
|
||||||
|
);
|
||||||
|
expect(finder, findsOneWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> tapCreateLinkedDatabaseViewButton(AddButtonAction action) async {
|
||||||
|
final findAddButton = find.byType(AddDatabaseViewButton);
|
||||||
|
await tapButton(findAddButton);
|
||||||
|
|
||||||
|
final findCreateButton = find.byWidgetPredicate(
|
||||||
|
(widget) =>
|
||||||
|
widget is TarBarAddButtonActionCell && widget.action == action,
|
||||||
|
);
|
||||||
|
await tapButton(findCreateButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
Finder findTabBarLinkViewByViewLayout(ViewLayoutPB layout) {
|
||||||
|
return find.byWidgetPredicate(
|
||||||
|
(widget) => widget is TabBarItemButton && widget.view.layout == layout,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Finder findTabBarLinkViewByViewName(String name) {
|
||||||
|
return find.byWidgetPredicate(
|
||||||
|
(widget) => widget is TabBarItemButton && widget.view.name == name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> renameLinkedView(Finder linkedView, String name) async {
|
||||||
|
await tap(linkedView, buttons: kSecondaryButton);
|
||||||
|
await pumpAndSettle();
|
||||||
|
|
||||||
|
await tapButton(
|
||||||
|
find.byWidgetPredicate(
|
||||||
|
(widget) =>
|
||||||
|
widget is ActionCellWidget &&
|
||||||
|
widget.action == TabBarViewAction.rename,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await enterText(
|
||||||
|
find.descendant(
|
||||||
|
of: find.byType(FlowyFormTextInput),
|
||||||
|
matching: find.byType(TextFormField),
|
||||||
|
),
|
||||||
|
name,
|
||||||
|
);
|
||||||
|
|
||||||
|
final field = find.byWidgetPredicate(
|
||||||
|
(widget) =>
|
||||||
|
widget is PrimaryTextButton &&
|
||||||
|
widget.label == LocaleKeys.button_OK.tr(),
|
||||||
|
);
|
||||||
|
await tapButton(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteDatebaseView(Finder linkedView) async {
|
||||||
|
await tap(linkedView, buttons: kSecondaryButton);
|
||||||
|
await pumpAndSettle();
|
||||||
|
|
||||||
|
await tapButton(
|
||||||
|
find.byWidgetPredicate(
|
||||||
|
(widget) =>
|
||||||
|
widget is ActionCellWidget &&
|
||||||
|
widget.action == TabBarViewAction.delete,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final okButton = find.byWidgetPredicate(
|
||||||
|
(widget) =>
|
||||||
|
widget is PrimaryTextButton &&
|
||||||
|
widget.label == LocaleKeys.button_OK.tr(),
|
||||||
|
);
|
||||||
|
await tapButton(okButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> assertCurrentDatabaseTagIs(DatabaseLayoutPB layout) async {
|
||||||
|
switch (layout) {
|
||||||
|
case DatabaseLayoutPB.Board:
|
||||||
|
expect(find.byType(BoardPage), findsOneWidget);
|
||||||
|
break;
|
||||||
|
case DatabaseLayoutPB.Calendar:
|
||||||
|
expect(find.byType(CalendarPage), findsOneWidget);
|
||||||
|
break;
|
||||||
|
case DatabaseLayoutPB.Grid:
|
||||||
|
expect(find.byType(GridPage), findsOneWidget);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw Exception('Unknown database layout type: $layout');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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(
|
||||||
|
@ -82,6 +82,7 @@ extension Expectation on WidgetTester {
|
|||||||
Finder findPageName(String name) {
|
Finder findPageName(String name) {
|
||||||
return find.byWidgetPredicate(
|
return find.byWidgetPredicate(
|
||||||
(widget) => widget is ViewSectionItem && widget.view.name == name,
|
(widget) => widget is ViewSectionItem && widget.view.name == name,
|
||||||
|
skipOffstage: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
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/layout/calendar_setting_listener.dart';
|
|
||||||
import 'package:appflowy/plugins/database_view/application/view/view_cache.dart';
|
import 'package:appflowy/plugins/database_view/application/view/view_cache.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/calendar_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/calendar_entities.pb.dart';
|
||||||
@ -16,6 +15,7 @@ import 'dart:async';
|
|||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'database_view_service.dart';
|
import 'database_view_service.dart';
|
||||||
import 'defines.dart';
|
import 'defines.dart';
|
||||||
|
import 'layout/layout_service.dart';
|
||||||
import 'layout/layout_setting_listener.dart';
|
import 'layout/layout_setting_listener.dart';
|
||||||
import 'row/row_cache.dart';
|
import 'row/row_cache.dart';
|
||||||
import 'group/group_listener.dart';
|
import 'group/group_listener.dart';
|
||||||
@ -50,16 +50,11 @@ class DatabaseLayoutSettingCallbacks {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class CalendarLayoutCallbacks {
|
|
||||||
final void Function(DatabaseLayoutSettingPB) onCalendarLayoutChanged;
|
|
||||||
|
|
||||||
CalendarLayoutCallbacks({required this.onCalendarLayoutChanged});
|
|
||||||
}
|
|
||||||
|
|
||||||
class DatabaseCallbacks {
|
class DatabaseCallbacks {
|
||||||
OnDatabaseChanged? onDatabaseChanged;
|
OnDatabaseChanged? onDatabaseChanged;
|
||||||
OnFieldsChanged? onFieldsChanged;
|
OnFieldsChanged? onFieldsChanged;
|
||||||
OnFiltersChanged? onFiltersChanged;
|
OnFiltersChanged? onFiltersChanged;
|
||||||
|
OnSortsChanged? onSortsChanged;
|
||||||
OnNumOfRowsChanged? onNumOfRowsChanged;
|
OnNumOfRowsChanged? onNumOfRowsChanged;
|
||||||
OnRowsDeleted? onRowsDeleted;
|
OnRowsDeleted? onRowsDeleted;
|
||||||
OnRowsUpdated? onRowsUpdated;
|
OnRowsUpdated? onRowsUpdated;
|
||||||
@ -70,6 +65,7 @@ class DatabaseCallbacks {
|
|||||||
this.onNumOfRowsChanged,
|
this.onNumOfRowsChanged,
|
||||||
this.onFieldsChanged,
|
this.onFieldsChanged,
|
||||||
this.onFiltersChanged,
|
this.onFiltersChanged,
|
||||||
|
this.onSortsChanged,
|
||||||
this.onRowsUpdated,
|
this.onRowsUpdated,
|
||||||
this.onRowsDeleted,
|
this.onRowsDeleted,
|
||||||
this.onRowsCreated,
|
this.onRowsCreated,
|
||||||
@ -80,15 +76,14 @@ class DatabaseController {
|
|||||||
final String viewId;
|
final String viewId;
|
||||||
final DatabaseViewBackendService _databaseViewBackendSvc;
|
final DatabaseViewBackendService _databaseViewBackendSvc;
|
||||||
final FieldController fieldController;
|
final FieldController fieldController;
|
||||||
DatabaseLayoutPB? databaseLayout;
|
DatabaseLayoutPB databaseLayout;
|
||||||
DatabaseLayoutSettingPB? databaseLayoutSetting;
|
DatabaseLayoutSettingPB? databaseLayoutSetting;
|
||||||
late DatabaseViewCache _viewCache;
|
late DatabaseViewCache _viewCache;
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
DatabaseCallbacks? _databaseCallbacks;
|
final List<DatabaseCallbacks> _databaseCallbacks = [];
|
||||||
GroupCallbacks? _groupCallbacks;
|
final List<GroupCallbacks> _groupCallbacks = [];
|
||||||
DatabaseLayoutSettingCallbacks? _layoutCallbacks;
|
final List<DatabaseLayoutSettingCallbacks> _layoutCallbacks = [];
|
||||||
CalendarLayoutCallbacks? _calendarLayoutCallbacks;
|
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
RowCache get rowCache => _viewCache.rowCache;
|
RowCache get rowCache => _viewCache.rowCache;
|
||||||
@ -96,15 +91,14 @@ class DatabaseController {
|
|||||||
// Listener
|
// Listener
|
||||||
final DatabaseGroupListener _groupListener;
|
final DatabaseGroupListener _groupListener;
|
||||||
final DatabaseLayoutSettingListener _layoutListener;
|
final DatabaseLayoutSettingListener _layoutListener;
|
||||||
final DatabaseCalendarLayoutListener _calendarLayoutListener;
|
|
||||||
|
|
||||||
DatabaseController({required ViewPB view})
|
DatabaseController({required ViewPB view})
|
||||||
: viewId = view.id,
|
: viewId = view.id,
|
||||||
_databaseViewBackendSvc = DatabaseViewBackendService(viewId: view.id),
|
_databaseViewBackendSvc = DatabaseViewBackendService(viewId: view.id),
|
||||||
fieldController = FieldController(viewId: view.id),
|
fieldController = FieldController(viewId: view.id),
|
||||||
_groupListener = DatabaseGroupListener(view.id),
|
_groupListener = DatabaseGroupListener(view.id),
|
||||||
_layoutListener = DatabaseLayoutSettingListener(view.id),
|
databaseLayout = databaseLayoutFromViewLayout(view.layout),
|
||||||
_calendarLayoutListener = DatabaseCalendarLayoutListener(view.id) {
|
_layoutListener = DatabaseLayoutSettingListener(view.id) {
|
||||||
_viewCache = DatabaseViewCache(
|
_viewCache = DatabaseViewCache(
|
||||||
viewId: viewId,
|
viewId: viewId,
|
||||||
fieldController: fieldController,
|
fieldController: fieldController,
|
||||||
@ -115,29 +109,30 @@ class DatabaseController {
|
|||||||
_listenOnLayoutChanged();
|
_listenOnLayoutChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setListener({
|
void addListener({
|
||||||
DatabaseCallbacks? onDatabaseChanged,
|
DatabaseCallbacks? onDatabaseChanged,
|
||||||
DatabaseLayoutSettingCallbacks? onLayoutChanged,
|
DatabaseLayoutSettingCallbacks? onLayoutChanged,
|
||||||
GroupCallbacks? onGroupChanged,
|
GroupCallbacks? onGroupChanged,
|
||||||
CalendarLayoutCallbacks? onCalendarLayoutChanged,
|
|
||||||
}) {
|
}) {
|
||||||
_layoutCallbacks = onLayoutChanged;
|
if (onLayoutChanged != null) {
|
||||||
_databaseCallbacks = onDatabaseChanged;
|
_layoutCallbacks.add(onLayoutChanged);
|
||||||
_groupCallbacks = onGroupChanged;
|
}
|
||||||
_calendarLayoutCallbacks = onCalendarLayoutChanged;
|
|
||||||
|
if (onDatabaseChanged != null) {
|
||||||
|
_databaseCallbacks.add(onDatabaseChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onGroupChanged != null) {
|
||||||
|
_groupCallbacks.add(onGroupChanged);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Either<Unit, FlowyError>> open() async {
|
Future<Either<Unit, FlowyError>> open() async {
|
||||||
return _databaseViewBackendSvc.openGrid().then((result) {
|
return _databaseViewBackendSvc.openDatabase().then((result) {
|
||||||
return result.fold(
|
return result.fold(
|
||||||
(DatabasePB database) async {
|
(DatabasePB database) async {
|
||||||
databaseLayout = database.layoutType;
|
databaseLayout = database.layoutType;
|
||||||
|
|
||||||
// Listen on layout changed if database layout is calendar
|
|
||||||
if (databaseLayout == DatabaseLayoutPB.Calendar) {
|
|
||||||
_listenOnCalendarLayoutChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the actual database field data.
|
// Load the actual database field data.
|
||||||
final fieldsOrFail = await fieldController.loadFields(
|
final fieldsOrFail = await fieldController.loadFields(
|
||||||
fieldIds: database.fields,
|
fieldIds: database.fields,
|
||||||
@ -146,7 +141,9 @@ class DatabaseController {
|
|||||||
(fields) {
|
(fields) {
|
||||||
// Notify the database is changed after the fields are loaded.
|
// Notify the database is changed after the fields are loaded.
|
||||||
// The database won't can't be used until the fields are loaded.
|
// The database won't can't be used until the fields are loaded.
|
||||||
_databaseCallbacks?.onDatabaseChanged?.call(database);
|
for (final callback in _databaseCallbacks) {
|
||||||
|
callback.onDatabaseChanged?.call(database);
|
||||||
|
}
|
||||||
_viewCache.rowCache.setInitialRows(database.rows);
|
_viewCache.rowCache.setInitialRows(database.rows);
|
||||||
return Future(() async {
|
return Future(() async {
|
||||||
await _loadGroups();
|
await _loadGroups();
|
||||||
@ -217,11 +214,14 @@ class DatabaseController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateCalenderLayoutSetting(
|
Future<void> updateLayoutSetting(
|
||||||
CalendarLayoutSettingPB layoutSetting,
|
CalendarLayoutSettingPB calendarlLayoutSetting,
|
||||||
) async {
|
) async {
|
||||||
await _databaseViewBackendSvc
|
await _databaseViewBackendSvc
|
||||||
.updateLayoutSetting(calendarLayoutSetting: layoutSetting)
|
.updateLayoutSetting(
|
||||||
|
calendarLayoutSetting: calendarlLayoutSetting,
|
||||||
|
layoutType: databaseLayout,
|
||||||
|
)
|
||||||
.then((result) {
|
.then((result) {
|
||||||
result.fold((l) => null, (r) => Log.error(r));
|
result.fold((l) => null, (r) => Log.error(r));
|
||||||
});
|
});
|
||||||
@ -232,10 +232,9 @@ class DatabaseController {
|
|||||||
await fieldController.dispose();
|
await fieldController.dispose();
|
||||||
await _groupListener.stop();
|
await _groupListener.stop();
|
||||||
await _viewCache.dispose();
|
await _viewCache.dispose();
|
||||||
_databaseCallbacks = null;
|
_databaseCallbacks.clear();
|
||||||
_groupCallbacks = null;
|
_groupCallbacks.clear();
|
||||||
_layoutCallbacks = null;
|
_layoutCallbacks.clear();
|
||||||
_calendarLayoutCallbacks = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadGroups() async {
|
Future<void> _loadGroups() async {
|
||||||
@ -243,7 +242,9 @@ class DatabaseController {
|
|||||||
return Future(
|
return Future(
|
||||||
() => result.fold(
|
() => result.fold(
|
||||||
(groups) {
|
(groups) {
|
||||||
_groupCallbacks?.onGroupByField?.call(groups.items);
|
for (final callback in _groupCallbacks) {
|
||||||
|
callback.onGroupByField?.call(groups.items);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
(err) => Log.error(err),
|
(err) => Log.error(err),
|
||||||
),
|
),
|
||||||
@ -251,46 +252,63 @@ class DatabaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadLayoutSetting() async {
|
Future<void> _loadLayoutSetting() async {
|
||||||
if (databaseLayout != null) {
|
_databaseViewBackendSvc.getLayoutSetting(databaseLayout).then((result) {
|
||||||
_databaseViewBackendSvc.getLayoutSetting(databaseLayout!).then((result) {
|
|
||||||
result.fold(
|
result.fold(
|
||||||
(newDatabaseLayoutSetting) {
|
(newDatabaseLayoutSetting) {
|
||||||
databaseLayoutSetting = newDatabaseLayoutSetting;
|
databaseLayoutSetting = newDatabaseLayoutSetting;
|
||||||
databaseLayoutSetting?.freeze();
|
databaseLayoutSetting?.freeze();
|
||||||
|
|
||||||
_layoutCallbacks?.onLoadLayout(newDatabaseLayoutSetting);
|
for (final callback in _layoutCallbacks) {
|
||||||
|
callback.onLoadLayout(newDatabaseLayoutSetting);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
(r) => Log.error(r),
|
(r) => Log.error(r),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void _listenOnRowsChanged() {
|
void _listenOnRowsChanged() {
|
||||||
final callbacks = DatabaseViewCallbacks(
|
final callbacks = DatabaseViewCallbacks(
|
||||||
onNumOfRowsChanged: (rows, rowByRowId, reason) {
|
onNumOfRowsChanged: (rows, rowByRowId, reason) {
|
||||||
_databaseCallbacks?.onNumOfRowsChanged?.call(rows, rowByRowId, reason);
|
for (final callback in _databaseCallbacks) {
|
||||||
|
callback.onNumOfRowsChanged?.call(rows, rowByRowId, reason);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onRowsDeleted: (ids) {
|
onRowsDeleted: (ids) {
|
||||||
_databaseCallbacks?.onRowsDeleted?.call(ids);
|
for (final callback in _databaseCallbacks) {
|
||||||
|
callback.onRowsDeleted?.call(ids);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onRowsUpdated: (ids, reason) {
|
onRowsUpdated: (ids, reason) {
|
||||||
_databaseCallbacks?.onRowsUpdated?.call(ids, reason);
|
for (final callback in _databaseCallbacks) {
|
||||||
|
callback.onRowsUpdated?.call(ids, reason);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onRowsCreated: (ids) {
|
onRowsCreated: (ids) {
|
||||||
_databaseCallbacks?.onRowsCreated?.call(ids);
|
for (final callback in _databaseCallbacks) {
|
||||||
|
callback.onRowsCreated?.call(ids);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
_viewCache.setListener(callbacks);
|
_viewCache.addListener(callbacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenOnFieldsChanged() {
|
void _listenOnFieldsChanged() {
|
||||||
fieldController.addListener(
|
fieldController.addListener(
|
||||||
onReceiveFields: (fields) {
|
onReceiveFields: (fields) {
|
||||||
_databaseCallbacks?.onFieldsChanged?.call(UnmodifiableListView(fields));
|
for (final callback in _databaseCallbacks) {
|
||||||
|
callback.onFieldsChanged?.call(UnmodifiableListView(fields));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSorts: (sorts) {
|
||||||
|
for (final callback in _databaseCallbacks) {
|
||||||
|
callback.onSortsChanged?.call(sorts);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onFilters: (filters) {
|
onFilters: (filters) {
|
||||||
_databaseCallbacks?.onFiltersChanged?.call(filters);
|
for (final callback in _databaseCallbacks) {
|
||||||
|
callback.onFiltersChanged?.call(filters);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -301,15 +319,21 @@ class DatabaseController {
|
|||||||
result.fold(
|
result.fold(
|
||||||
(changeset) {
|
(changeset) {
|
||||||
if (changeset.updateGroups.isNotEmpty) {
|
if (changeset.updateGroups.isNotEmpty) {
|
||||||
_groupCallbacks?.onUpdateGroup?.call(changeset.updateGroups);
|
for (final callback in _groupCallbacks) {
|
||||||
|
callback.onUpdateGroup?.call(changeset.updateGroups);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changeset.deletedGroups.isNotEmpty) {
|
if (changeset.deletedGroups.isNotEmpty) {
|
||||||
_groupCallbacks?.onDeleteGroup?.call(changeset.deletedGroups);
|
for (final callback in _groupCallbacks) {
|
||||||
|
callback.onDeleteGroup?.call(changeset.deletedGroups);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final insertedGroup in changeset.insertedGroups) {
|
for (final insertedGroup in changeset.insertedGroups) {
|
||||||
_groupCallbacks?.onInsertGroup?.call(insertedGroup);
|
for (final callback in _groupCallbacks) {
|
||||||
|
callback.onInsertGroup?.call(insertedGroup);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(r) => Log.error(r),
|
(r) => Log.error(r),
|
||||||
@ -318,7 +342,9 @@ class DatabaseController {
|
|||||||
onGroupByNewField: (result) {
|
onGroupByNewField: (result) {
|
||||||
result.fold(
|
result.fold(
|
||||||
(groups) {
|
(groups) {
|
||||||
_groupCallbacks?.onGroupByField?.call(groups);
|
for (final callback in _groupCallbacks) {
|
||||||
|
callback.onGroupByField?.call(groups);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
(r) => Log.error(r),
|
(r) => Log.error(r),
|
||||||
);
|
);
|
||||||
@ -330,24 +356,13 @@ class DatabaseController {
|
|||||||
_layoutListener.start(
|
_layoutListener.start(
|
||||||
onLayoutChanged: (result) {
|
onLayoutChanged: (result) {
|
||||||
result.fold(
|
result.fold(
|
||||||
(newDatabaseLayoutSetting) {
|
(newLayout) {
|
||||||
databaseLayoutSetting = newDatabaseLayoutSetting;
|
databaseLayoutSetting = newLayout;
|
||||||
databaseLayoutSetting?.freeze();
|
databaseLayoutSetting?.freeze();
|
||||||
|
|
||||||
_layoutCallbacks?.onLayoutChanged(newDatabaseLayoutSetting);
|
for (final callback in _layoutCallbacks) {
|
||||||
},
|
callback.onLayoutChanged(newLayout);
|
||||||
(r) => Log.error(r),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenOnCalendarLayoutChanged() {
|
|
||||||
_calendarLayoutListener.start(
|
|
||||||
onCalendarLayoutChanged: (result) {
|
|
||||||
result.fold(
|
|
||||||
(l) {
|
|
||||||
_calendarLayoutCallbacks?.onCalendarLayoutChanged(l);
|
|
||||||
},
|
},
|
||||||
(r) => Log.error(r),
|
(r) => Log.error(r),
|
||||||
);
|
);
|
||||||
|
@ -25,7 +25,7 @@ class DatabaseViewBackendService {
|
|||||||
.then((value) => value.leftMap((l) => l.value));
|
.then((value) => value.leftMap((l) => l.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Either<DatabasePB, FlowyError>> openGrid() async {
|
Future<Either<DatabasePB, FlowyError>> openDatabase() async {
|
||||||
final payload = DatabaseViewIdPB(value: viewId);
|
final payload = DatabaseViewIdPB(value: viewId);
|
||||||
return DatabaseEventGetDatabase(payload).send();
|
return DatabaseEventGetDatabase(payload).send();
|
||||||
}
|
}
|
||||||
@ -113,9 +113,12 @@ class DatabaseViewBackendService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<Either<Unit, FlowyError>> updateLayoutSetting({
|
Future<Either<Unit, FlowyError>> updateLayoutSetting({
|
||||||
|
required DatabaseLayoutPB layoutType,
|
||||||
CalendarLayoutSettingPB? calendarLayoutSetting,
|
CalendarLayoutSettingPB? calendarLayoutSetting,
|
||||||
}) {
|
}) {
|
||||||
final payload = LayoutSettingChangesetPB.create()..viewId = viewId;
|
final payload = LayoutSettingChangesetPB.create()
|
||||||
|
..viewId = viewId
|
||||||
|
..layoutType = layoutType;
|
||||||
if (calendarLayoutSetting != null) {
|
if (calendarLayoutSetting != null) {
|
||||||
payload.calendar = calendarLayoutSetting;
|
payload.calendar = calendarLayoutSetting;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
|
||||||
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_info.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/database_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/database_entities.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
|
|
||||||
@ -10,6 +11,7 @@ import 'row/row_service.dart';
|
|||||||
|
|
||||||
typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldInfo>);
|
typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldInfo>);
|
||||||
typedef OnFiltersChanged = void Function(List<FilterInfo>);
|
typedef OnFiltersChanged = void Function(List<FilterInfo>);
|
||||||
|
typedef OnSortsChanged = void Function(List<SortInfo>);
|
||||||
typedef OnDatabaseChanged = void Function(DatabasePB);
|
typedef OnDatabaseChanged = void Function(DatabasePB);
|
||||||
|
|
||||||
typedef OnRowsCreated = void Function(List<RowId> ids);
|
typedef OnRowsCreated = void Function(List<RowId> ids);
|
||||||
|
@ -15,13 +15,13 @@ class DatabaseLayoutBackendService {
|
|||||||
}) {
|
}) {
|
||||||
final payload = UpdateViewPayloadPB.create()
|
final payload = UpdateViewPayloadPB.create()
|
||||||
..viewId = viewId
|
..viewId = viewId
|
||||||
..layout = _viewLayoutFromDatabaseLayout(layout);
|
..layout = viewLayoutFromDatabaseLayout(layout);
|
||||||
|
|
||||||
return FolderEventUpdateView(payload).send();
|
return FolderEventUpdateView(payload).send();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewLayoutPB _viewLayoutFromDatabaseLayout(DatabaseLayoutPB databaseLayout) {
|
ViewLayoutPB viewLayoutFromDatabaseLayout(DatabaseLayoutPB databaseLayout) {
|
||||||
switch (databaseLayout) {
|
switch (databaseLayout) {
|
||||||
case DatabaseLayoutPB.Board:
|
case DatabaseLayoutPB.Board:
|
||||||
return ViewLayoutPB.Board;
|
return ViewLayoutPB.Board;
|
||||||
@ -33,3 +33,16 @@ ViewLayoutPB _viewLayoutFromDatabaseLayout(DatabaseLayoutPB databaseLayout) {
|
|||||||
throw UnimplementedError;
|
throw UnimplementedError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DatabaseLayoutPB databaseLayoutFromViewLayout(ViewLayoutPB viewLayout) {
|
||||||
|
switch (viewLayout) {
|
||||||
|
case ViewLayoutPB.Board:
|
||||||
|
return DatabaseLayoutPB.Board;
|
||||||
|
case ViewLayoutPB.Calendar:
|
||||||
|
return DatabaseLayoutPB.Calendar;
|
||||||
|
case ViewLayoutPB.Grid:
|
||||||
|
return DatabaseLayoutPB.Grid;
|
||||||
|
default:
|
||||||
|
throw UnimplementedError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,103 +0,0 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
||||||
import 'package:dartz/dartz.dart';
|
|
||||||
|
|
||||||
part 'setting_bloc.freezed.dart';
|
|
||||||
|
|
||||||
class DatabaseSettingBloc
|
|
||||||
extends Bloc<DatabaseSettingEvent, DatabaseSettingState> {
|
|
||||||
final String viewId;
|
|
||||||
DatabaseSettingBloc({required this.viewId})
|
|
||||||
: super(DatabaseSettingState.initial()) {
|
|
||||||
on<DatabaseSettingEvent>(
|
|
||||||
(event, emit) async {
|
|
||||||
event.map(
|
|
||||||
performAction: (_PerformAction value) {
|
|
||||||
emit(state.copyWith(selectedAction: Some(value.action)));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class DatabaseSettingEvent with _$DatabaseSettingEvent {
|
|
||||||
const factory DatabaseSettingEvent.performAction(
|
|
||||||
DatabaseSettingAction action,
|
|
||||||
) = _PerformAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class DatabaseSettingState with _$DatabaseSettingState {
|
|
||||||
const factory DatabaseSettingState({
|
|
||||||
required Option<DatabaseSettingAction> selectedAction,
|
|
||||||
}) = _DatabaseSettingState;
|
|
||||||
|
|
||||||
factory DatabaseSettingState.initial() => DatabaseSettingState(
|
|
||||||
selectedAction: none(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum DatabaseSettingAction {
|
|
||||||
showProperties,
|
|
||||||
showLayout,
|
|
||||||
showGroup,
|
|
||||||
showCalendarLayout,
|
|
||||||
}
|
|
||||||
|
|
||||||
extension DatabaseSettingActionExtension on DatabaseSettingAction {
|
|
||||||
String iconName() {
|
|
||||||
switch (this) {
|
|
||||||
case DatabaseSettingAction.showProperties:
|
|
||||||
return 'grid/setting/properties';
|
|
||||||
case DatabaseSettingAction.showLayout:
|
|
||||||
return 'grid/setting/database_layout';
|
|
||||||
case DatabaseSettingAction.showGroup:
|
|
||||||
return 'grid/setting/group';
|
|
||||||
case DatabaseSettingAction.showCalendarLayout:
|
|
||||||
return 'grid/setting/calendar_layout';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String title() {
|
|
||||||
switch (this) {
|
|
||||||
case DatabaseSettingAction.showProperties:
|
|
||||||
return LocaleKeys.grid_settings_Properties.tr();
|
|
||||||
case DatabaseSettingAction.showLayout:
|
|
||||||
return LocaleKeys.grid_settings_databaseLayout.tr();
|
|
||||||
case DatabaseSettingAction.showGroup:
|
|
||||||
return LocaleKeys.grid_settings_group.tr();
|
|
||||||
case DatabaseSettingAction.showCalendarLayout:
|
|
||||||
return LocaleKeys.calendar_settings_name.tr();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the list of actions that should be shown for the given database layout.
|
|
||||||
List<DatabaseSettingAction> actionsForDatabaseLayout(DatabaseLayoutPB? layout) {
|
|
||||||
switch (layout) {
|
|
||||||
case DatabaseLayoutPB.Board:
|
|
||||||
return [
|
|
||||||
DatabaseSettingAction.showProperties,
|
|
||||||
DatabaseSettingAction.showLayout,
|
|
||||||
DatabaseSettingAction.showGroup,
|
|
||||||
];
|
|
||||||
case DatabaseLayoutPB.Calendar:
|
|
||||||
return [
|
|
||||||
DatabaseSettingAction.showProperties,
|
|
||||||
DatabaseSettingAction.showLayout,
|
|
||||||
DatabaseSettingAction.showCalendarLayout,
|
|
||||||
];
|
|
||||||
case DatabaseLayoutPB.Grid:
|
|
||||||
return [
|
|
||||||
DatabaseSettingAction.showProperties,
|
|
||||||
DatabaseSettingAction.showLayout,
|
|
||||||
];
|
|
||||||
default:
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,290 @@
|
|||||||
|
import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_view.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/tar_bar/tar_bar_add_button.dart';
|
||||||
|
import 'package:appflowy/workspace/application/view/prelude.dart';
|
||||||
|
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||||
|
import 'package:appflowy_backend/log.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
import 'database_controller.dart';
|
||||||
|
import 'database_view_service.dart';
|
||||||
|
|
||||||
|
part 'tar_bar_bloc.freezed.dart';
|
||||||
|
|
||||||
|
class GridTabBarBloc extends Bloc<GridTabBarEvent, GridTabBarState> {
|
||||||
|
GridTabBarBloc({
|
||||||
|
bool isInlineView = false,
|
||||||
|
required ViewPB view,
|
||||||
|
}) : super(GridTabBarState.initial(view)) {
|
||||||
|
on<GridTabBarEvent>(
|
||||||
|
(event, emit) async {
|
||||||
|
event.when(
|
||||||
|
initial: () {
|
||||||
|
_listenInlineViewChanged();
|
||||||
|
_loadChildView();
|
||||||
|
},
|
||||||
|
didLoadChildViews: (List<ViewPB> childViews) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
tabBars: [
|
||||||
|
...state.tabBars,
|
||||||
|
...childViews.map(
|
||||||
|
(newChildView) => TarBar(view: newChildView),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tabBarControllerByViewId: _extendsTabBarController(childViews),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
selectView: (String viewId) {
|
||||||
|
final index =
|
||||||
|
state.tabBars.indexWhere((element) => element.viewId == viewId);
|
||||||
|
if (index != -1) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(selectedIndex: index),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createView: (action) {
|
||||||
|
_createLinkedView(action.name, action.layoutType);
|
||||||
|
},
|
||||||
|
deleteView: (String viewId) async {
|
||||||
|
final result = await ViewBackendService.delete(viewId: viewId);
|
||||||
|
result.fold(
|
||||||
|
(l) {},
|
||||||
|
(r) => Log.error(r),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
renameView: (String viewId, String newName) {
|
||||||
|
ViewBackendService.updateView(viewId: viewId, name: newName);
|
||||||
|
},
|
||||||
|
didUpdateChildViews: (updatePB) async {
|
||||||
|
if (updatePB.createChildViews.isNotEmpty) {
|
||||||
|
final allTabBars = [
|
||||||
|
...state.tabBars,
|
||||||
|
...updatePB.createChildViews.map((e) => TarBar(view: e))
|
||||||
|
];
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
tabBars: allTabBars,
|
||||||
|
selectedIndex: state.tabBars.length,
|
||||||
|
tabBarControllerByViewId:
|
||||||
|
_extendsTabBarController(updatePB.createChildViews),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updatePB.deleteChildViews.isNotEmpty) {
|
||||||
|
final allTabBars = [...state.tabBars];
|
||||||
|
final tabBarControllerByViewId = {
|
||||||
|
...state.tabBarControllerByViewId
|
||||||
|
};
|
||||||
|
var newSelectedIndex = state.selectedIndex;
|
||||||
|
for (final viewId in updatePB.deleteChildViews) {
|
||||||
|
final index = allTabBars.indexWhere(
|
||||||
|
(element) => element.viewId == viewId,
|
||||||
|
);
|
||||||
|
if (index != -1) {
|
||||||
|
final tarBar = allTabBars.removeAt(index);
|
||||||
|
// Dispose the controller when the tab is removed.
|
||||||
|
final controller =
|
||||||
|
tabBarControllerByViewId.remove(tarBar.viewId);
|
||||||
|
controller?.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index == state.selectedIndex) {
|
||||||
|
if (index > 0 && allTabBars.isNotEmpty) {
|
||||||
|
newSelectedIndex = index - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
tabBars: allTabBars,
|
||||||
|
selectedIndex: newSelectedIndex,
|
||||||
|
tabBarControllerByViewId: tabBarControllerByViewId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
viewDidUpdate: (ViewPB updatedView) {
|
||||||
|
final index = state.tabBars.indexWhere(
|
||||||
|
(element) => element.viewId == updatedView.id,
|
||||||
|
);
|
||||||
|
if (index != -1) {
|
||||||
|
final allTabBars = [...state.tabBars];
|
||||||
|
final updatedTabBar = TarBar(view: updatedView);
|
||||||
|
allTabBars[index] = updatedTabBar;
|
||||||
|
emit(state.copyWith(tabBars: allTabBars));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() async {
|
||||||
|
for (final tabBar in state.tabBars) {
|
||||||
|
await state.tabBarControllerByViewId[tabBar.viewId]?.dispose();
|
||||||
|
}
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _listenInlineViewChanged() {
|
||||||
|
final controller = state.tabBarControllerByViewId[state.parentView.id];
|
||||||
|
controller?.onViewUpdated = (newView) {
|
||||||
|
add(GridTabBarEvent.viewDidUpdate(newView));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only listen the child view changes when the parent view is inline.
|
||||||
|
controller?.onViewChildViewChanged = (update) {
|
||||||
|
add(GridTabBarEvent.didUpdateChildViews(update));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create tab bar controllers for the new views and return the updated map.
|
||||||
|
Map<String, DatabaseTarBarController> _extendsTabBarController(
|
||||||
|
List<ViewPB> newViews,
|
||||||
|
) {
|
||||||
|
final tabBarControllerByViewId = {...state.tabBarControllerByViewId};
|
||||||
|
for (final view in newViews) {
|
||||||
|
final controller = DatabaseTarBarController(view: view);
|
||||||
|
controller.onViewUpdated = (newView) {
|
||||||
|
add(GridTabBarEvent.viewDidUpdate(newView));
|
||||||
|
};
|
||||||
|
|
||||||
|
tabBarControllerByViewId[view.id] = controller;
|
||||||
|
}
|
||||||
|
return tabBarControllerByViewId;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _createLinkedView(String name, ViewLayoutPB layoutType) async {
|
||||||
|
final viewId = state.parentView.id;
|
||||||
|
final databaseIdOrError =
|
||||||
|
await DatabaseViewBackendService(viewId: viewId).getDatabaseId();
|
||||||
|
databaseIdOrError.fold(
|
||||||
|
(databaseId) async {
|
||||||
|
final linkedViewOrError =
|
||||||
|
await ViewBackendService.createDatabaseLinkedView(
|
||||||
|
parentViewId: viewId,
|
||||||
|
databaseId: databaseId,
|
||||||
|
layoutType: layoutType,
|
||||||
|
name: name,
|
||||||
|
);
|
||||||
|
|
||||||
|
linkedViewOrError.fold(
|
||||||
|
(linkedView) {},
|
||||||
|
(err) => Log.error(err),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(r) => Log.error(r),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadChildView() async {
|
||||||
|
ViewBackendService.getChildViews(viewId: state.parentView.id)
|
||||||
|
.then((viewsOrFail) {
|
||||||
|
if (isClosed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
viewsOrFail.fold(
|
||||||
|
(views) => add(GridTabBarEvent.didLoadChildViews(views)),
|
||||||
|
(err) => Log.error(err),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class GridTabBarEvent with _$GridTabBarEvent {
|
||||||
|
const factory GridTabBarEvent.initial() = _Initial;
|
||||||
|
const factory GridTabBarEvent.didLoadChildViews(
|
||||||
|
List<ViewPB> childViews,
|
||||||
|
) = _DidLoadChildViews;
|
||||||
|
const factory GridTabBarEvent.selectView(String viewId) = _DidSelectView;
|
||||||
|
const factory GridTabBarEvent.createView(AddButtonAction action) =
|
||||||
|
_CreateView;
|
||||||
|
const factory GridTabBarEvent.renameView(String viewId, String newName) =
|
||||||
|
_RenameView;
|
||||||
|
const factory GridTabBarEvent.deleteView(String viewId) = _DeleteView;
|
||||||
|
const factory GridTabBarEvent.didUpdateChildViews(
|
||||||
|
ChildViewUpdatePB updatePB,
|
||||||
|
) = _DidUpdateChildViews;
|
||||||
|
const factory GridTabBarEvent.viewDidUpdate(ViewPB view) = _ViewDidUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class GridTabBarState with _$GridTabBarState {
|
||||||
|
const factory GridTabBarState({
|
||||||
|
required ViewPB parentView,
|
||||||
|
required int selectedIndex,
|
||||||
|
required List<TarBar> tabBars,
|
||||||
|
required Map<String, DatabaseTarBarController> tabBarControllerByViewId,
|
||||||
|
}) = _GridTabBarState;
|
||||||
|
|
||||||
|
factory GridTabBarState.initial(ViewPB view) {
|
||||||
|
final tabBar = TarBar(view: view);
|
||||||
|
return GridTabBarState(
|
||||||
|
parentView: view,
|
||||||
|
selectedIndex: 0,
|
||||||
|
tabBars: [tabBar],
|
||||||
|
tabBarControllerByViewId: {
|
||||||
|
view.id: DatabaseTarBarController(
|
||||||
|
view: view,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TarBar extends Equatable {
|
||||||
|
final ViewPB view;
|
||||||
|
final DatabaseTabBarItemBuilder _builder;
|
||||||
|
|
||||||
|
String get viewId => view.id;
|
||||||
|
DatabaseTabBarItemBuilder get builder => _builder;
|
||||||
|
ViewLayoutPB get layout => view.layout;
|
||||||
|
|
||||||
|
TarBar({
|
||||||
|
required this.view,
|
||||||
|
}) : _builder = view.tarBarItem();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [view.hashCode];
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef OnViewUpdated = void Function(ViewPB newView);
|
||||||
|
typedef OnViewChildViewChanged = void Function(
|
||||||
|
ChildViewUpdatePB childViewUpdate,
|
||||||
|
);
|
||||||
|
|
||||||
|
class DatabaseTarBarController {
|
||||||
|
ViewPB view;
|
||||||
|
final DatabaseController controller;
|
||||||
|
final ViewListener viewListener;
|
||||||
|
OnViewUpdated? onViewUpdated;
|
||||||
|
OnViewChildViewChanged? onViewChildViewChanged;
|
||||||
|
|
||||||
|
DatabaseTarBarController({
|
||||||
|
required this.view,
|
||||||
|
}) : controller = DatabaseController(view: view),
|
||||||
|
viewListener = ViewListener(viewId: view.id) {
|
||||||
|
viewListener.start(
|
||||||
|
onViewChildViewsUpdated: (update) {
|
||||||
|
onViewChildViewChanged?.call(update);
|
||||||
|
},
|
||||||
|
onViewUpdated: (newView) {
|
||||||
|
view = newView;
|
||||||
|
onViewUpdated?.call(newView);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> dispose() async {
|
||||||
|
await viewListener.stop();
|
||||||
|
await controller.dispose();
|
||||||
|
}
|
||||||
|
}
|
@ -35,7 +35,7 @@ class DatabaseViewCache {
|
|||||||
final String viewId;
|
final String viewId;
|
||||||
late RowCache _rowCache;
|
late RowCache _rowCache;
|
||||||
final DatabaseViewListener _databaseViewListener;
|
final DatabaseViewListener _databaseViewListener;
|
||||||
DatabaseViewCallbacks? _callbacks;
|
final List<DatabaseViewCallbacks> _callbacks = [];
|
||||||
|
|
||||||
UnmodifiableListView<RowInfo> get rowInfos => _rowCache.rowInfos;
|
UnmodifiableListView<RowInfo> get rowInfos => _rowCache.rowInfos;
|
||||||
RowCache get rowCache => _rowCache;
|
RowCache get rowCache => _rowCache;
|
||||||
@ -61,23 +61,29 @@ class DatabaseViewCache {
|
|||||||
_rowCache.applyRowsChanged(changeset);
|
_rowCache.applyRowsChanged(changeset);
|
||||||
|
|
||||||
if (changeset.deletedRows.isNotEmpty) {
|
if (changeset.deletedRows.isNotEmpty) {
|
||||||
_callbacks?.onRowsDeleted?.call(changeset.deletedRows);
|
for (final callback in _callbacks) {
|
||||||
|
callback.onRowsDeleted?.call(changeset.deletedRows);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changeset.updatedRows.isNotEmpty) {
|
if (changeset.updatedRows.isNotEmpty) {
|
||||||
_callbacks?.onRowsUpdated?.call(
|
for (final callback in _callbacks) {
|
||||||
|
callback.onRowsUpdated?.call(
|
||||||
changeset.updatedRows.map((e) => e.rowId).toList(),
|
changeset.updatedRows.map((e) => e.rowId).toList(),
|
||||||
_rowCache.changeReason,
|
_rowCache.changeReason,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (changeset.insertedRows.isNotEmpty) {
|
if (changeset.insertedRows.isNotEmpty) {
|
||||||
_callbacks?.onRowsCreated?.call(
|
for (final callback in _callbacks) {
|
||||||
|
callback.onRowsCreated?.call(
|
||||||
changeset.insertedRows
|
changeset.insertedRows
|
||||||
.map((insertedRow) => insertedRow.rowMeta.id)
|
.map((insertedRow) => insertedRow.rowMeta.id)
|
||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
(err) => Log.error(err),
|
(err) => Log.error(err),
|
||||||
);
|
);
|
||||||
@ -103,21 +109,25 @@ class DatabaseViewCache {
|
|||||||
);
|
);
|
||||||
|
|
||||||
_rowCache.onRowsChanged(
|
_rowCache.onRowsChanged(
|
||||||
(reason) => _callbacks?.onNumOfRowsChanged?.call(
|
(reason) {
|
||||||
|
for (final callback in _callbacks) {
|
||||||
|
callback.onNumOfRowsChanged?.call(
|
||||||
rowInfos,
|
rowInfos,
|
||||||
_rowCache.rowByRowId,
|
_rowCache.rowByRowId,
|
||||||
reason,
|
reason,
|
||||||
),
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
await _databaseViewListener.stop();
|
await _databaseViewListener.stop();
|
||||||
await _rowCache.dispose();
|
await _rowCache.dispose();
|
||||||
_callbacks = null;
|
_callbacks.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setListener(DatabaseViewCallbacks callbacks) {
|
void addListener(DatabaseViewCallbacks callbacks) {
|
||||||
_callbacks = callbacks;
|
_callbacks.add(callbacks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,9 +28,10 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
FieldController get fieldController => databaseController.fieldController;
|
FieldController get fieldController => databaseController.fieldController;
|
||||||
String get viewId => databaseController.viewId;
|
String get viewId => databaseController.viewId;
|
||||||
|
|
||||||
BoardBloc({required ViewPB view})
|
BoardBloc({
|
||||||
: databaseController = DatabaseController(view: view),
|
required ViewPB view,
|
||||||
super(BoardState.initial(view.id)) {
|
required this.databaseController,
|
||||||
|
}) : super(BoardState.initial(view.id)) {
|
||||||
boardController = AppFlowyBoardController(
|
boardController = AppFlowyBoardController(
|
||||||
onMoveGroup: (
|
onMoveGroup: (
|
||||||
fromGroupId,
|
fromGroupId,
|
||||||
@ -166,7 +167,6 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
await databaseController.dispose();
|
|
||||||
for (final controller in groupControllers.values) {
|
for (final controller in groupControllers.values) {
|
||||||
controller.dispose();
|
controller.dispose();
|
||||||
}
|
}
|
||||||
@ -233,7 +233,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
databaseController.setListener(
|
databaseController.addListener(
|
||||||
onDatabaseChanged: onDatabaseChanged,
|
onDatabaseChanged: onDatabaseChanged,
|
||||||
onGroupChanged: onGroupChanged,
|
onGroupChanged: onGroupChanged,
|
||||||
);
|
);
|
||||||
|
@ -1,19 +1,14 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/util.dart';
|
import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_view.dart';
|
||||||
import 'package:appflowy/startup/plugin/plugin.dart';
|
import 'package:appflowy/startup/plugin/plugin.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/home_stack.dart';
|
|
||||||
import 'package:appflowy/workspace/presentation/widgets/left_bar_item.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'presentation/board_page.dart';
|
|
||||||
|
|
||||||
class BoardPluginBuilder implements PluginBuilder {
|
class BoardPluginBuilder implements PluginBuilder {
|
||||||
@override
|
@override
|
||||||
Plugin build(dynamic data) {
|
Plugin build(dynamic data) {
|
||||||
if (data is ViewPB) {
|
if (data is ViewPB) {
|
||||||
return BoardPlugin(pluginType: pluginType, view: data);
|
return DatabaseTabBarViewPlugin(pluginType: pluginType, view: data);
|
||||||
} else {
|
} else {
|
||||||
throw FlowyPluginException.invalidData;
|
throw FlowyPluginException.invalidData;
|
||||||
}
|
}
|
||||||
@ -36,55 +31,3 @@ class BoardPluginConfig implements PluginConfig {
|
|||||||
@override
|
@override
|
||||||
bool get creatable => true;
|
bool get creatable => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BoardPlugin extends Plugin {
|
|
||||||
@override
|
|
||||||
final ViewPluginNotifier notifier;
|
|
||||||
final PluginType _pluginType;
|
|
||||||
|
|
||||||
BoardPlugin({
|
|
||||||
required ViewPB view,
|
|
||||||
required PluginType pluginType,
|
|
||||||
bool listenOnViewChanged = false,
|
|
||||||
}) : _pluginType = pluginType,
|
|
||||||
notifier = ViewPluginNotifier(
|
|
||||||
view: view,
|
|
||||||
listenOnViewChanged: listenOnViewChanged,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
PluginWidgetBuilder get widgetBuilder =>
|
|
||||||
BoardPluginWidgetBuilder(notifier: notifier);
|
|
||||||
|
|
||||||
@override
|
|
||||||
PluginId get id => notifier.view.id;
|
|
||||||
|
|
||||||
@override
|
|
||||||
PluginType get pluginType => _pluginType;
|
|
||||||
}
|
|
||||||
|
|
||||||
class BoardPluginWidgetBuilder extends PluginWidgetBuilder {
|
|
||||||
final ViewPluginNotifier notifier;
|
|
||||||
BoardPluginWidgetBuilder({required this.notifier, Key? key});
|
|
||||||
|
|
||||||
ViewPB get view => notifier.view;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget get leftBarItem => ViewLeftBarItem(view: view);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildWidget({PluginContext? context}) {
|
|
||||||
notifier.isDeleted.addListener(() {
|
|
||||||
notifier.isDeleted.value.fold(() => null, (deletedView) {
|
|
||||||
if (deletedView.hasIndex()) {
|
|
||||||
context?.onDeleted(view, deletedView.index);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return BoardPage(key: ValueKey(view.id), view: view);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<NavigationItem> get navigationItems => [this];
|
|
||||||
}
|
|
||||||
|
@ -3,9 +3,11 @@
|
|||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
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/field/field_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
|
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_view.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||||
@ -24,11 +26,48 @@ import '../../widgets/card/card_cell_builder.dart';
|
|||||||
import '../../widgets/row/cell_builder.dart';
|
import '../../widgets/row/cell_builder.dart';
|
||||||
import '../application/board_bloc.dart';
|
import '../application/board_bloc.dart';
|
||||||
import '../../widgets/card/card.dart';
|
import '../../widgets/card/card.dart';
|
||||||
import 'toolbar/board_toolbar.dart';
|
import 'toolbar/board_setting_bar.dart';
|
||||||
|
|
||||||
|
class BoardPageTabBarBuilderImpl implements DatabaseTabBarItemBuilder {
|
||||||
|
@override
|
||||||
|
Widget content(
|
||||||
|
BuildContext context,
|
||||||
|
ViewPB view,
|
||||||
|
DatabaseController controller,
|
||||||
|
) {
|
||||||
|
return BoardPage(
|
||||||
|
key: _makeValueKey(controller),
|
||||||
|
view: view,
|
||||||
|
databaseController: controller,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget settingBar(BuildContext context, DatabaseController controller) {
|
||||||
|
return BoardSettingBar(
|
||||||
|
key: _makeValueKey(controller),
|
||||||
|
databaseController: controller,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget settingBarExtension(
|
||||||
|
BuildContext context,
|
||||||
|
DatabaseController controller,
|
||||||
|
) {
|
||||||
|
return SizedBox.fromSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
ValueKey _makeValueKey(DatabaseController controller) {
|
||||||
|
return ValueKey(controller.viewId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class BoardPage extends StatelessWidget {
|
class BoardPage extends StatelessWidget {
|
||||||
|
final DatabaseController databaseController;
|
||||||
BoardPage({
|
BoardPage({
|
||||||
required this.view,
|
required this.view,
|
||||||
|
required this.databaseController,
|
||||||
Key? key,
|
Key? key,
|
||||||
this.onEditStateChanged,
|
this.onEditStateChanged,
|
||||||
}) : super(key: ValueKey(view.id));
|
}) : super(key: ValueKey(view.id));
|
||||||
@ -41,8 +80,10 @@ class BoardPage extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) =>
|
create: (context) => BoardBloc(
|
||||||
BoardBloc(view: view)..add(const BoardEvent.initial()),
|
view: view,
|
||||||
|
databaseController: databaseController,
|
||||||
|
)..add(const BoardEvent.initial()),
|
||||||
child: BlocBuilder<BoardBloc, BoardState>(
|
child: BlocBuilder<BoardBloc, BoardState>(
|
||||||
buildWhen: (p, c) => p.loadingState != c.loadingState,
|
buildWhen: (p, c) => p.loadingState != c.loadingState,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
@ -110,14 +151,9 @@ class _BoardContentState extends State<BoardContent> {
|
|||||||
child: BlocBuilder<BoardBloc, BoardState>(
|
child: BlocBuilder<BoardBloc, BoardState>(
|
||||||
buildWhen: (previous, current) => previous.groupIds != current.groupIds,
|
buildWhen: (previous, current) => previous.groupIds != current.groupIds,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final column = Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [const _ToolbarBlocAdaptor(), _buildBoard(context)],
|
|
||||||
);
|
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
child: column,
|
child: _buildBoard(context),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -125,8 +161,7 @@ class _BoardContentState extends State<BoardContent> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBoard(BuildContext context) {
|
Widget _buildBoard(BuildContext context) {
|
||||||
return Expanded(
|
return AppFlowyBoard(
|
||||||
child: AppFlowyBoard(
|
|
||||||
boardScrollController: scrollManager,
|
boardScrollController: scrollManager,
|
||||||
scrollController: ScrollController(),
|
scrollController: ScrollController(),
|
||||||
controller: context.read<BoardBloc>().boardController,
|
controller: context.read<BoardBloc>().boardController,
|
||||||
@ -141,7 +176,6 @@ class _BoardContentState extends State<BoardContent> {
|
|||||||
config: AppFlowyBoardConfig(
|
config: AppFlowyBoardConfig(
|
||||||
groupBackgroundColor: Theme.of(context).colorScheme.surfaceVariant,
|
groupBackgroundColor: Theme.of(context).colorScheme.surfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,17 +369,6 @@ class _BoardContentState extends State<BoardContent> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ToolbarBlocAdaptor extends StatelessWidget {
|
|
||||||
const _ToolbarBlocAdaptor({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<BoardBloc, BoardState>(
|
|
||||||
builder: (context, state) => const BoardToolbar(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget? _buildHeaderIcon(GroupData customData) {
|
Widget? _buildHeaderIcon(GroupData customData) {
|
||||||
Widget? widget;
|
Widget? widget;
|
||||||
switch (customData.fieldType) {
|
switch (customData.fieldType) {
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import 'package:appflowy/plugins/database_view/board/application/board_bloc.dart';
|
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart';
|
import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
|
|
||||||
class BoardToolbar extends StatelessWidget {
|
class BoardSettingBar extends StatelessWidget {
|
||||||
const BoardToolbar({
|
final DatabaseController databaseController;
|
||||||
|
const BoardSettingBar({
|
||||||
|
required this.databaseController,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@ -15,9 +16,7 @@ class BoardToolbar extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
SettingButton(
|
SettingButton(databaseController: databaseController),
|
||||||
databaseController: context.read<BoardBloc>().databaseController,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
@ -27,9 +27,8 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
|||||||
CellCache get cellCache => databaseController.rowCache.cellCache;
|
CellCache get cellCache => databaseController.rowCache.cellCache;
|
||||||
RowCache get rowCache => databaseController.rowCache;
|
RowCache get rowCache => databaseController.rowCache;
|
||||||
|
|
||||||
CalendarBloc({required ViewPB view})
|
CalendarBloc({required ViewPB view, required this.databaseController})
|
||||||
: databaseController = DatabaseController(view: view),
|
: super(CalendarState.initial()) {
|
||||||
super(CalendarState.initial()) {
|
|
||||||
on<CalendarEvent>(
|
on<CalendarEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.when(
|
await event.when(
|
||||||
@ -39,6 +38,12 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
|||||||
_loadAllEvents();
|
_loadAllEvents();
|
||||||
},
|
},
|
||||||
didReceiveCalendarSettings: (CalendarLayoutSettingPB settings) {
|
didReceiveCalendarSettings: (CalendarLayoutSettingPB settings) {
|
||||||
|
// If the field id changed, reload all events
|
||||||
|
state.settings.fold(() => null, (oldSetting) {
|
||||||
|
if (oldSetting.fieldId != settings.fieldId) {
|
||||||
|
_loadAllEvents();
|
||||||
|
}
|
||||||
|
});
|
||||||
emit(state.copyWith(settings: Some(settings)));
|
emit(state.copyWith(settings: Some(settings)));
|
||||||
},
|
},
|
||||||
didReceiveDatabaseUpdate: (DatabasePB database) {
|
didReceiveDatabaseUpdate: (DatabasePB database) {
|
||||||
@ -53,10 +58,6 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
didReceiveNewLayoutField: (CalendarLayoutSettingPB layoutSettings) {
|
|
||||||
_loadAllEvents();
|
|
||||||
emit(state.copyWith(settings: Some(layoutSettings)));
|
|
||||||
},
|
|
||||||
createEvent: (DateTime date, String title) async {
|
createEvent: (DateTime date, String title) async {
|
||||||
await _createEvent(date, title);
|
await _createEvent(date, title);
|
||||||
},
|
},
|
||||||
@ -105,12 +106,6 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> close() async {
|
|
||||||
await databaseController.dispose();
|
|
||||||
return super.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
FieldInfo? _getCalendarFieldInfo(String fieldId) {
|
FieldInfo? _getCalendarFieldInfo(String fieldId) {
|
||||||
final fieldInfos = databaseController.fieldController.fieldInfos;
|
final fieldInfos = databaseController.fieldController.fieldInfos;
|
||||||
final index = fieldInfos.indexWhere(
|
final index = fieldInfos.indexWhere(
|
||||||
@ -149,7 +144,9 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
|||||||
|
|
||||||
Future<void> _createEvent(DateTime date, String title) async {
|
Future<void> _createEvent(DateTime date, String title) async {
|
||||||
return state.settings.fold(
|
return state.settings.fold(
|
||||||
() => null,
|
() {
|
||||||
|
Log.warn('Calendar settings not found');
|
||||||
|
},
|
||||||
(settings) async {
|
(settings) async {
|
||||||
final dateField = _getCalendarFieldInfo(settings.fieldId);
|
final dateField = _getCalendarFieldInfo(settings.fieldId);
|
||||||
final titleField = _getTitleFieldInfo();
|
final titleField = _getTitleFieldInfo();
|
||||||
@ -207,7 +204,7 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
|||||||
Future<void> _updateCalendarLayoutSetting(
|
Future<void> _updateCalendarLayoutSetting(
|
||||||
CalendarLayoutSettingPB layoutSetting,
|
CalendarLayoutSettingPB layoutSetting,
|
||||||
) async {
|
) async {
|
||||||
return databaseController.updateCalenderLayoutSetting(layoutSetting);
|
return databaseController.updateLayoutSetting(layoutSetting);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<CalendarEventData<CalendarDayEvent>?> _loadEvent(RowId rowId) async {
|
Future<CalendarEventData<CalendarDayEvent>?> _loadEvent(RowId rowId) async {
|
||||||
@ -333,14 +330,9 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
|||||||
onLoadLayout: _didReceiveLayoutSetting,
|
onLoadLayout: _didReceiveLayoutSetting,
|
||||||
);
|
);
|
||||||
|
|
||||||
final onCalendarLayoutFieldChanged = CalendarLayoutCallbacks(
|
databaseController.addListener(
|
||||||
onCalendarLayoutChanged: _didReceiveNewLayoutField,
|
|
||||||
);
|
|
||||||
|
|
||||||
databaseController.setListener(
|
|
||||||
onDatabaseChanged: onDatabaseChanged,
|
onDatabaseChanged: onDatabaseChanged,
|
||||||
onLayoutChanged: onLayoutChanged,
|
onLayoutChanged: onLayoutChanged,
|
||||||
onCalendarLayoutChanged: onCalendarLayoutFieldChanged,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,13 +345,6 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _didReceiveNewLayoutField(DatabaseLayoutSettingPB layoutSetting) {
|
|
||||||
if (layoutSetting.hasCalendar()) {
|
|
||||||
if (isClosed) return;
|
|
||||||
add(CalendarEvent.didReceiveNewLayoutField(layoutSetting.calendar));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isEventDayChanged(CalendarEventData<CalendarDayEvent> event) {
|
bool isEventDayChanged(CalendarEventData<CalendarDayEvent> event) {
|
||||||
final index = state.allEvents.indexWhere(
|
final index = state.allEvents.indexWhere(
|
||||||
(element) => element.event!.eventId == event.event!.eventId,
|
(element) => element.event!.eventId == event.event!.eventId,
|
||||||
@ -426,10 +411,6 @@ class CalendarEvent with _$CalendarEvent {
|
|||||||
|
|
||||||
const factory CalendarEvent.didReceiveDatabaseUpdate(DatabasePB database) =
|
const factory CalendarEvent.didReceiveDatabaseUpdate(DatabasePB database) =
|
||||||
_ReceiveDatabaseUpdate;
|
_ReceiveDatabaseUpdate;
|
||||||
|
|
||||||
const factory CalendarEvent.didReceiveNewLayoutField(
|
|
||||||
CalendarLayoutSettingPB layoutSettings,
|
|
||||||
) = _DidReceiveNewLayoutField;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
|
@ -0,0 +1,167 @@
|
|||||||
|
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
|
||||||
|
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||||
|
import 'package:appflowy_backend/log.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
import '../../application/database_controller.dart';
|
||||||
|
import '../../application/row/row_cache.dart';
|
||||||
|
|
||||||
|
part 'unschedule_event_bloc.freezed.dart';
|
||||||
|
|
||||||
|
class UnscheduleEventsBloc
|
||||||
|
extends Bloc<UnscheduleEventsEvent, UnscheduleEventsState> {
|
||||||
|
final DatabaseController databaseController;
|
||||||
|
Map<String, FieldInfo> fieldInfoByFieldId = {};
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
String get viewId => databaseController.viewId;
|
||||||
|
FieldController get fieldController => databaseController.fieldController;
|
||||||
|
CellCache get cellCache => databaseController.rowCache.cellCache;
|
||||||
|
RowCache get rowCache => databaseController.rowCache;
|
||||||
|
|
||||||
|
UnscheduleEventsBloc({
|
||||||
|
required this.databaseController,
|
||||||
|
}) : super(UnscheduleEventsState.initial()) {
|
||||||
|
on<UnscheduleEventsEvent>(
|
||||||
|
(event, emit) async {
|
||||||
|
await event.when(
|
||||||
|
initial: () async {
|
||||||
|
_startListening();
|
||||||
|
_loadAllEvents();
|
||||||
|
},
|
||||||
|
didLoadAllEvents: (events) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
allEvents: events,
|
||||||
|
unscheduleEvents:
|
||||||
|
events.where((element) => !element.isScheduled).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
didDeleteEvents: (List<RowId> deletedRowIds) {
|
||||||
|
final events = [...state.allEvents];
|
||||||
|
events.retainWhere(
|
||||||
|
(element) => !deletedRowIds.contains(element.rowMeta.id),
|
||||||
|
);
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
allEvents: events,
|
||||||
|
unscheduleEvents:
|
||||||
|
events.where((element) => !element.isScheduled).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
didReceiveEvent: (CalendarEventPB event) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
allEvents: [...state.allEvents, event],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<CalendarEventPB?> _loadEvent(
|
||||||
|
RowId rowId,
|
||||||
|
) async {
|
||||||
|
final payload = RowIdPB(viewId: viewId, rowId: rowId);
|
||||||
|
return DatabaseEventGetCalendarEvent(payload).send().then(
|
||||||
|
(result) => result.fold(
|
||||||
|
(eventPB) => eventPB,
|
||||||
|
(r) {
|
||||||
|
Log.error(r);
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadAllEvents() async {
|
||||||
|
final payload = CalendarEventRequestPB.create()..viewId = viewId;
|
||||||
|
DatabaseEventGetAllCalendarEvents(payload).send().then((result) {
|
||||||
|
result.fold(
|
||||||
|
(events) {
|
||||||
|
if (!isClosed) {
|
||||||
|
add(UnscheduleEventsEvent.didLoadAllEvents(events.items));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(r) => Log.error(r),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startListening() {
|
||||||
|
final onDatabaseChanged = DatabaseCallbacks(
|
||||||
|
onRowsCreated: (rowIds) async {
|
||||||
|
if (isClosed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (final id in rowIds) {
|
||||||
|
final event = await _loadEvent(id);
|
||||||
|
if (event != null && !isClosed) {
|
||||||
|
add(UnscheduleEventsEvent.didReceiveEvent(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRowsDeleted: (rowIds) {
|
||||||
|
if (isClosed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
add(UnscheduleEventsEvent.didDeleteEvents(rowIds));
|
||||||
|
},
|
||||||
|
onRowsUpdated: (rowIds, reason) async {
|
||||||
|
if (isClosed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (final id in rowIds) {
|
||||||
|
final event = await _loadEvent(id);
|
||||||
|
if (event != null) {
|
||||||
|
add(UnscheduleEventsEvent.didDeleteEvents([id]));
|
||||||
|
add(UnscheduleEventsEvent.didReceiveEvent(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
databaseController.addListener(onDatabaseChanged: onDatabaseChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class UnscheduleEventsEvent with _$UnscheduleEventsEvent {
|
||||||
|
const factory UnscheduleEventsEvent.initial() = _InitialCalendar;
|
||||||
|
|
||||||
|
// Called after loading all the current evnets
|
||||||
|
const factory UnscheduleEventsEvent.didLoadAllEvents(
|
||||||
|
List<CalendarEventPB> events,
|
||||||
|
) = _ReceiveUnscheduleEventsEvents;
|
||||||
|
|
||||||
|
const factory UnscheduleEventsEvent.didDeleteEvents(List<RowId> rowIds) =
|
||||||
|
_DidDeleteEvents;
|
||||||
|
|
||||||
|
const factory UnscheduleEventsEvent.didReceiveEvent(
|
||||||
|
CalendarEventPB event,
|
||||||
|
) = _DidReceiveEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class UnscheduleEventsState with _$UnscheduleEventsState {
|
||||||
|
const factory UnscheduleEventsState({
|
||||||
|
required Option<DatabasePB> database,
|
||||||
|
required List<CalendarEventPB> allEvents,
|
||||||
|
required List<CalendarEventPB> unscheduleEvents,
|
||||||
|
}) = _UnscheduleEventsState;
|
||||||
|
|
||||||
|
factory UnscheduleEventsState.initial() => UnscheduleEventsState(
|
||||||
|
database: none(),
|
||||||
|
allEvents: [],
|
||||||
|
unscheduleEvents: [],
|
||||||
|
);
|
||||||
|
}
|
@ -1,19 +1,14 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_view.dart';
|
||||||
import 'package:appflowy/startup/plugin/plugin.dart';
|
import 'package:appflowy/startup/plugin/plugin.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/home_stack.dart';
|
|
||||||
import 'package:appflowy/workspace/presentation/widgets/left_bar_item.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import '../../util.dart';
|
|
||||||
import 'presentation/calendar_page.dart';
|
|
||||||
|
|
||||||
class CalendarPluginBuilder extends PluginBuilder {
|
class CalendarPluginBuilder extends PluginBuilder {
|
||||||
@override
|
@override
|
||||||
Plugin build(dynamic data) {
|
Plugin build(dynamic data) {
|
||||||
if (data is ViewPB) {
|
if (data is ViewPB) {
|
||||||
return CalendarPlugin(pluginType: pluginType, view: data);
|
return DatabaseTabBarViewPlugin(pluginType: pluginType, view: data);
|
||||||
} else {
|
} else {
|
||||||
throw FlowyPluginException.invalidData;
|
throw FlowyPluginException.invalidData;
|
||||||
}
|
}
|
||||||
@ -36,55 +31,3 @@ class CalendarPluginConfig implements PluginConfig {
|
|||||||
@override
|
@override
|
||||||
bool get creatable => true;
|
bool get creatable => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
class CalendarPlugin extends Plugin {
|
|
||||||
@override
|
|
||||||
final ViewPluginNotifier notifier;
|
|
||||||
final PluginType _pluginType;
|
|
||||||
|
|
||||||
CalendarPlugin({
|
|
||||||
required ViewPB view,
|
|
||||||
required PluginType pluginType,
|
|
||||||
bool listenOnViewChanged = false,
|
|
||||||
}) : _pluginType = pluginType,
|
|
||||||
notifier = ViewPluginNotifier(
|
|
||||||
view: view,
|
|
||||||
listenOnViewChanged: listenOnViewChanged,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
PluginWidgetBuilder get widgetBuilder =>
|
|
||||||
CalendarPluginWidgetBuilder(notifier: notifier);
|
|
||||||
|
|
||||||
@override
|
|
||||||
PluginId get id => notifier.view.id;
|
|
||||||
|
|
||||||
@override
|
|
||||||
PluginType get pluginType => _pluginType;
|
|
||||||
}
|
|
||||||
|
|
||||||
class CalendarPluginWidgetBuilder extends PluginWidgetBuilder {
|
|
||||||
final ViewPluginNotifier notifier;
|
|
||||||
CalendarPluginWidgetBuilder({required this.notifier, Key? key});
|
|
||||||
|
|
||||||
ViewPB get view => notifier.view;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget get leftBarItem => ViewLeftBarItem(view: view);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildWidget({PluginContext? context}) {
|
|
||||||
notifier.isDeleted.addListener(() {
|
|
||||||
notifier.isDeleted.value.fold(() => null, (deletedView) {
|
|
||||||
if (deletedView.hasIndex()) {
|
|
||||||
context?.onDeleted(view, deletedView.index);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return CalendarPage(key: ValueKey(view.id), view: view);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<NavigationItem> get navigationItems => [this];
|
|
||||||
}
|
|
||||||
|
@ -309,7 +309,7 @@ class _EventCard extends StatelessWidget {
|
|||||||
cellBuilder: cellBuilder,
|
cellBuilder: cellBuilder,
|
||||||
openCard: (context) => showEventDetails(
|
openCard: (context) => showEventDetails(
|
||||||
context: context,
|
context: context,
|
||||||
event: event,
|
event: event.event,
|
||||||
viewId: viewId,
|
viewId: viewId,
|
||||||
rowCache: rowCache,
|
rowCache: rowCache,
|
||||||
),
|
),
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/calendar/application/calendar_bloc.dart';
|
import 'package:appflowy/plugins/database_view/calendar/application/calendar_bloc.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_view.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/calendar_entities.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:calendar_view/calendar_view.dart';
|
import 'package:calendar_view/calendar_view.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@ -15,11 +18,51 @@ import '../../widgets/row/cell_builder.dart';
|
|||||||
import '../../widgets/row/row_detail.dart';
|
import '../../widgets/row/row_detail.dart';
|
||||||
import 'calendar_day.dart';
|
import 'calendar_day.dart';
|
||||||
import 'layout/sizes.dart';
|
import 'layout/sizes.dart';
|
||||||
import 'toolbar/calendar_toolbar.dart';
|
import 'toolbar/calendar_setting_bar.dart';
|
||||||
|
|
||||||
|
class CalendarPageTabBarBuilderImpl implements DatabaseTabBarItemBuilder {
|
||||||
|
@override
|
||||||
|
Widget content(
|
||||||
|
BuildContext context,
|
||||||
|
ViewPB view,
|
||||||
|
DatabaseController controller,
|
||||||
|
) {
|
||||||
|
return CalendarPage(
|
||||||
|
key: _makeValueKey(controller),
|
||||||
|
view: view,
|
||||||
|
databaseController: controller,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget settingBar(BuildContext context, DatabaseController controller) {
|
||||||
|
return CalendarSettingBar(
|
||||||
|
key: _makeValueKey(controller),
|
||||||
|
databaseController: controller,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget settingBarExtension(
|
||||||
|
BuildContext context,
|
||||||
|
DatabaseController controller,
|
||||||
|
) {
|
||||||
|
return SizedBox.fromSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
ValueKey _makeValueKey(DatabaseController controller) {
|
||||||
|
return ValueKey(controller.viewId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class CalendarPage extends StatefulWidget {
|
class CalendarPage extends StatefulWidget {
|
||||||
final ViewPB view;
|
final ViewPB view;
|
||||||
const CalendarPage({required this.view, super.key});
|
final DatabaseController databaseController;
|
||||||
|
const CalendarPage({
|
||||||
|
required this.view,
|
||||||
|
required this.databaseController,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CalendarPage> createState() => _CalendarPageState();
|
State<CalendarPage> createState() => _CalendarPageState();
|
||||||
@ -33,8 +76,10 @@ class _CalendarPageState extends State<CalendarPage> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_calendarState = GlobalKey<MonthViewState>();
|
_calendarState = GlobalKey<MonthViewState>();
|
||||||
_calendarBloc = CalendarBloc(view: widget.view)
|
_calendarBloc = CalendarBloc(
|
||||||
..add(const CalendarEvent.initial());
|
view: widget.view,
|
||||||
|
databaseController: widget.databaseController,
|
||||||
|
)..add(const CalendarEvent.initial());
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
@ -79,7 +124,7 @@ class _CalendarPageState extends State<CalendarPage> {
|
|||||||
if (state.editingEvent != null) {
|
if (state.editingEvent != null) {
|
||||||
showEventDetails(
|
showEventDetails(
|
||||||
context: context,
|
context: context,
|
||||||
event: state.editingEvent!.event!,
|
event: state.editingEvent!.event!.event,
|
||||||
viewId: widget.view.id,
|
viewId: widget.view.id,
|
||||||
rowCache: _calendarBloc.rowCache,
|
rowCache: _calendarBloc.rowCache,
|
||||||
);
|
);
|
||||||
@ -115,8 +160,6 @@ class _CalendarPageState extends State<CalendarPage> {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
// const _ToolbarBlocAdaptor(),
|
|
||||||
const CalendarToolbar(),
|
|
||||||
_buildCalendar(
|
_buildCalendar(
|
||||||
_eventController,
|
_eventController,
|
||||||
state.settings
|
state.settings
|
||||||
@ -238,12 +281,12 @@ class _CalendarPageState extends State<CalendarPage> {
|
|||||||
|
|
||||||
void showEventDetails({
|
void showEventDetails({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required CalendarDayEvent event,
|
required CalendarEventPB event,
|
||||||
required String viewId,
|
required String viewId,
|
||||||
required RowCache rowCache,
|
required RowCache rowCache,
|
||||||
}) {
|
}) {
|
||||||
final dataController = RowController(
|
final dataController = RowController(
|
||||||
rowMeta: event.event.rowMeta,
|
rowMeta: event.rowMeta,
|
||||||
viewId: viewId,
|
viewId: viewId,
|
||||||
rowCache: rowCache,
|
rowCache: rowCache,
|
||||||
);
|
);
|
||||||
|
@ -351,21 +351,16 @@ class FirstDayOfWeek extends StatelessWidget {
|
|||||||
final symbols =
|
final symbols =
|
||||||
DateFormat.EEEE(context.locale.toLanguageTag()).dateSymbols;
|
DateFormat.EEEE(context.locale.toLanguageTag()).dateSymbols;
|
||||||
// starts from sunday
|
// starts from sunday
|
||||||
final items = symbols.WEEKDAYS.asMap().entries.map((entry) {
|
const len = 2;
|
||||||
final index = entry.key;
|
final items = symbols.WEEKDAYS.take(len).indexed.map((entry) {
|
||||||
final string = entry.value;
|
return StartFromButton(
|
||||||
return SizedBox(
|
title: entry.$2,
|
||||||
height: GridSize.popoverItemHeight,
|
dayIndex: entry.$1,
|
||||||
child: FlowyButton(
|
isSelected: firstDayOfWeek == entry.$1,
|
||||||
text: FlowyText.medium(string),
|
onTap: (index) {
|
||||||
onTap: () {
|
|
||||||
onUpdated(index);
|
onUpdated(index);
|
||||||
popoverMutex.close();
|
popoverMutex.close();
|
||||||
},
|
},
|
||||||
rightIcon: firstDayOfWeek == index
|
|
||||||
? const FlowySvg(name: 'grid/checkmark')
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
@ -376,7 +371,7 @@ class FirstDayOfWeek extends StatelessWidget {
|
|||||||
itemBuilder: (context, index) => items[index],
|
itemBuilder: (context, index) => items[index],
|
||||||
separatorBuilder: (context, index) =>
|
separatorBuilder: (context, index) =>
|
||||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||||
itemCount: 2,
|
itemCount: len,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -426,3 +421,29 @@ enum CalendarLayoutSettingAction {
|
|||||||
showWeekNumber,
|
showWeekNumber,
|
||||||
showTimeLine,
|
showTimeLine,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class StartFromButton extends StatelessWidget {
|
||||||
|
final int dayIndex;
|
||||||
|
final String title;
|
||||||
|
final bool isSelected;
|
||||||
|
final void Function(int) onTap;
|
||||||
|
const StartFromButton({
|
||||||
|
required this.title,
|
||||||
|
required this.dayIndex,
|
||||||
|
required this.onTap,
|
||||||
|
required this.isSelected,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
height: GridSize.popoverItemHeight,
|
||||||
|
child: FlowyButton(
|
||||||
|
text: FlowyText.medium(title),
|
||||||
|
onTap: () => onTap(dayIndex),
|
||||||
|
rightIcon: isSelected ? const FlowySvg(name: 'grid/checkmark') : null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,178 @@
|
|||||||
|
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/row/row_cache.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/calendar/application/unschedule_event_bloc.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/calendar_entities.pb.dart';
|
||||||
|
import 'package:appflowy_popover/appflowy_popover.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:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class CalendarSettingBar extends StatelessWidget {
|
||||||
|
final DatabaseController databaseController;
|
||||||
|
const CalendarSettingBar({
|
||||||
|
required this.databaseController,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
UnscheduleEventsButton(databaseController: databaseController),
|
||||||
|
SettingButton(
|
||||||
|
databaseController: databaseController,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnscheduleEventsButton extends StatefulWidget {
|
||||||
|
final DatabaseController databaseController;
|
||||||
|
const UnscheduleEventsButton({
|
||||||
|
required this.databaseController,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<UnscheduleEventsButton> createState() => _UnscheduleEventsButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UnscheduleEventsButtonState extends State<UnscheduleEventsButton> {
|
||||||
|
late final PopoverController _popoverController;
|
||||||
|
late final UnscheduleEventsBloc _bloc;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_bloc = UnscheduleEventsBloc(databaseController: widget.databaseController)
|
||||||
|
..add(const UnscheduleEventsEvent.initial());
|
||||||
|
_popoverController = PopoverController();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
dispose() {
|
||||||
|
_bloc.close();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppFlowyPopover(
|
||||||
|
direction: PopoverDirection.bottomWithCenterAligned,
|
||||||
|
controller: _popoverController,
|
||||||
|
offset: const Offset(0, 8),
|
||||||
|
constraints: const BoxConstraints(maxWidth: 300, maxHeight: 600),
|
||||||
|
child: BlocProvider.value(
|
||||||
|
value: _bloc,
|
||||||
|
child: BlocBuilder<UnscheduleEventsBloc, UnscheduleEventsState>(
|
||||||
|
buildWhen: (previous, current) =>
|
||||||
|
previous.unscheduleEvents.length !=
|
||||||
|
current.unscheduleEvents.length,
|
||||||
|
builder: (context, state) {
|
||||||
|
return FlowyTextButton(
|
||||||
|
"${LocaleKeys.calendar_settings_noDateTitle.tr()} (${state.unscheduleEvents.length})",
|
||||||
|
fillColor: Colors.transparent,
|
||||||
|
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||||
|
padding: GridSize.typeOptionContentInsets,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
popupBuilder: (context) {
|
||||||
|
return UnscheduleEventsList(
|
||||||
|
viewId: _bloc.viewId,
|
||||||
|
rowCache: _bloc.rowCache,
|
||||||
|
controller: _popoverController,
|
||||||
|
unscheduleEvents: _bloc.state.unscheduleEvents,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnscheduleEventsList extends StatelessWidget {
|
||||||
|
final String viewId;
|
||||||
|
final RowCache rowCache;
|
||||||
|
final PopoverController controller;
|
||||||
|
final List<CalendarEventPB> unscheduleEvents;
|
||||||
|
const UnscheduleEventsList({
|
||||||
|
required this.viewId,
|
||||||
|
required this.controller,
|
||||||
|
required this.unscheduleEvents,
|
||||||
|
required this.rowCache,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final cells = <Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
|
||||||
|
child: FlowyText.medium(
|
||||||
|
LocaleKeys.calendar_settings_clickToAdd.tr(),
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const VSpace(6),
|
||||||
|
...unscheduleEvents.map(
|
||||||
|
(e) => UnscheduledEventCell(
|
||||||
|
event: e,
|
||||||
|
onPressed: () {
|
||||||
|
showEventDetails(
|
||||||
|
context: context,
|
||||||
|
event: e,
|
||||||
|
viewId: viewId,
|
||||||
|
rowCache: rowCache,
|
||||||
|
);
|
||||||
|
controller.close();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
return ListView.separated(
|
||||||
|
itemBuilder: (context, index) => cells[index],
|
||||||
|
itemCount: cells.length,
|
||||||
|
separatorBuilder: (context, index) =>
|
||||||
|
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||||
|
shrinkWrap: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnscheduledEventCell extends StatelessWidget {
|
||||||
|
final CalendarEventPB event;
|
||||||
|
final VoidCallback onPressed;
|
||||||
|
const UnscheduledEventCell({
|
||||||
|
required this.event,
|
||||||
|
required this.onPressed,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
height: GridSize.popoverItemHeight,
|
||||||
|
child: FlowyButton(
|
||||||
|
text: FlowyText.medium(
|
||||||
|
event.title.isEmpty
|
||||||
|
? LocaleKeys.calendar_defaultNewCalendarTitle.tr()
|
||||||
|
: event.title,
|
||||||
|
),
|
||||||
|
onTap: onPressed,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,136 +0,0 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
|
||||||
import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart';
|
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
|
||||||
import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart';
|
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
|
||||||
import 'package:calendar_view/calendar_view.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:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
|
|
||||||
import '../../application/calendar_bloc.dart';
|
|
||||||
|
|
||||||
class CalendarToolbar extends StatelessWidget {
|
|
||||||
const CalendarToolbar({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SizedBox(
|
|
||||||
height: 40,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
const _UnscheduleEventsButton(),
|
|
||||||
SettingButton(
|
|
||||||
databaseController: context.read<CalendarBloc>().databaseController,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _UnscheduleEventsButton extends StatefulWidget {
|
|
||||||
const _UnscheduleEventsButton({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<_UnscheduleEventsButton> createState() =>
|
|
||||||
_UnscheduleEventsButtonState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _UnscheduleEventsButtonState extends State<_UnscheduleEventsButton> {
|
|
||||||
late final PopoverController _controller;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_controller = PopoverController();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<CalendarBloc, CalendarState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
final unscheduledEvents = state.allEvents
|
|
||||||
.where((e) => e.date == DateTime.fromMillisecondsSinceEpoch(0))
|
|
||||||
.toList();
|
|
||||||
final viewId = context.read<CalendarBloc>().viewId;
|
|
||||||
final rowCache = context.read<CalendarBloc>().rowCache;
|
|
||||||
return AppFlowyPopover(
|
|
||||||
direction: PopoverDirection.bottomWithCenterAligned,
|
|
||||||
controller: _controller,
|
|
||||||
offset: const Offset(0, 8),
|
|
||||||
constraints: const BoxConstraints(maxWidth: 300, maxHeight: 600),
|
|
||||||
child: FlowyTextButton(
|
|
||||||
"${LocaleKeys.calendar_settings_noDateTitle.tr()} (${unscheduledEvents.length})",
|
|
||||||
fillColor: Colors.transparent,
|
|
||||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
|
||||||
padding: GridSize.typeOptionContentInsets,
|
|
||||||
),
|
|
||||||
popupBuilder: (context) {
|
|
||||||
final cells = <Widget>[
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4),
|
|
||||||
child: FlowyText.medium(
|
|
||||||
// LocaleKeys.calendar_settings_noDateHint.tr(),
|
|
||||||
LocaleKeys.calendar_settings_clickToAdd.tr(),
|
|
||||||
color: Theme.of(context).hintColor,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const VSpace(6),
|
|
||||||
...unscheduledEvents.map(
|
|
||||||
(e) => _UnscheduledEventItem(
|
|
||||||
event: e,
|
|
||||||
onPressed: () {
|
|
||||||
showEventDetails(
|
|
||||||
context: context,
|
|
||||||
event: e.event!,
|
|
||||||
viewId: viewId,
|
|
||||||
rowCache: rowCache,
|
|
||||||
);
|
|
||||||
_controller.close();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
];
|
|
||||||
return ListView.separated(
|
|
||||||
itemBuilder: (context, index) => cells[index],
|
|
||||||
itemCount: cells.length,
|
|
||||||
separatorBuilder: (context, index) =>
|
|
||||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
|
||||||
shrinkWrap: true,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _UnscheduledEventItem extends StatelessWidget {
|
|
||||||
final CalendarEventData<CalendarDayEvent> event;
|
|
||||||
final VoidCallback onPressed;
|
|
||||||
const _UnscheduledEventItem({
|
|
||||||
required this.event,
|
|
||||||
required this.onPressed,
|
|
||||||
Key? key,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SizedBox(
|
|
||||||
height: GridSize.popoverItemHeight,
|
|
||||||
child: FlowyButton(
|
|
||||||
text: FlowyText.medium(
|
|
||||||
event.title.isEmpty
|
|
||||||
? LocaleKeys.calendar_defaultNewCalendarTitle.tr()
|
|
||||||
: event.title,
|
|
||||||
),
|
|
||||||
onTap: onPressed,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,17 +3,17 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
|||||||
|
|
||||||
part 'grid_accessory_bloc.freezed.dart';
|
part 'grid_accessory_bloc.freezed.dart';
|
||||||
|
|
||||||
class GridAccessoryMenuBloc
|
class DatabaseViewSettingExtensionBloc extends Bloc<
|
||||||
extends Bloc<GridAccessoryMenuEvent, GridAccessoryMenuState> {
|
DatabaseViewSettingExtensionEvent, DatabaseViewSettingExtensionState> {
|
||||||
final String viewId;
|
final String viewId;
|
||||||
|
|
||||||
GridAccessoryMenuBloc({required this.viewId})
|
DatabaseViewSettingExtensionBloc({required this.viewId})
|
||||||
: super(
|
: super(
|
||||||
GridAccessoryMenuState.initial(
|
DatabaseViewSettingExtensionState.initial(
|
||||||
viewId,
|
viewId,
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
on<GridAccessoryMenuEvent>(
|
on<DatabaseViewSettingExtensionEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
event.when(
|
event.when(
|
||||||
initial: () {},
|
initial: () {},
|
||||||
@ -27,22 +27,25 @@ class GridAccessoryMenuBloc
|
|||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class GridAccessoryMenuEvent with _$GridAccessoryMenuEvent {
|
class DatabaseViewSettingExtensionEvent
|
||||||
const factory GridAccessoryMenuEvent.initial() = _Initial;
|
with _$DatabaseViewSettingExtensionEvent {
|
||||||
const factory GridAccessoryMenuEvent.toggleMenu() = _MenuVisibleChange;
|
const factory DatabaseViewSettingExtensionEvent.initial() = _Initial;
|
||||||
|
const factory DatabaseViewSettingExtensionEvent.toggleMenu() =
|
||||||
|
_MenuVisibleChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class GridAccessoryMenuState with _$GridAccessoryMenuState {
|
class DatabaseViewSettingExtensionState
|
||||||
const factory GridAccessoryMenuState({
|
with _$DatabaseViewSettingExtensionState {
|
||||||
|
const factory DatabaseViewSettingExtensionState({
|
||||||
required String viewId,
|
required String viewId,
|
||||||
required bool isVisible,
|
required bool isVisible,
|
||||||
}) = _GridAccessoryMenuState;
|
}) = _DatabaseViewSettingExtensionState;
|
||||||
|
|
||||||
factory GridAccessoryMenuState.initial(
|
factory DatabaseViewSettingExtensionState.initial(
|
||||||
String viewId,
|
String viewId,
|
||||||
) =>
|
) =>
|
||||||
GridAccessoryMenuState(
|
DatabaseViewSettingExtensionState(
|
||||||
viewId: viewId,
|
viewId: viewId,
|
||||||
isVisible: false,
|
isVisible: false,
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
|
import 'package:appflowy/plugins/database_view/application/row/row_cache.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/filter/filter_info.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_info.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
@ -65,15 +67,25 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
didReceveFilters: (List<FilterInfo> filters) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
reorderable: filters.isEmpty && state.sorts.isEmpty,
|
||||||
|
filters: filters,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
didReceveSorts: (List<SortInfo> sorts) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(
|
||||||
|
reorderable: sorts.isEmpty && state.filters.isEmpty,
|
||||||
|
sorts: sorts,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> close() async {
|
|
||||||
await databaseController.dispose();
|
|
||||||
return super.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RowCache getRowCache(RowId rowId) {
|
RowCache getRowCache(RowId rowId) {
|
||||||
@ -93,17 +105,29 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRowsUpdated: (rows, reason) {
|
onRowsUpdated: (rows, reason) {
|
||||||
|
if (!isClosed) {
|
||||||
add(
|
add(
|
||||||
GridEvent.didLoadRows(databaseController.rowCache.rowInfos, reason),
|
GridEvent.didLoadRows(databaseController.rowCache.rowInfos, reason),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onFieldsChanged: (fields) {
|
onFieldsChanged: (fields) {
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
add(GridEvent.didReceiveFieldUpdate(fields));
|
add(GridEvent.didReceiveFieldUpdate(fields));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onFiltersChanged: (filters) {
|
||||||
|
if (!isClosed) {
|
||||||
|
add(GridEvent.didReceveFilters(filters));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSortsChanged: (sorts) {
|
||||||
|
if (!isClosed) {
|
||||||
|
add(GridEvent.didReceveSorts(sorts));
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
databaseController.setListener(onDatabaseChanged: onDatabaseChanged);
|
databaseController.addListener(onDatabaseChanged: onDatabaseChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _openGrid(Emitter<GridState> emit) async {
|
Future<void> _openGrid(Emitter<GridState> emit) async {
|
||||||
@ -138,6 +162,11 @@ class GridEvent with _$GridEvent {
|
|||||||
const factory GridEvent.didReceiveGridUpdate(
|
const factory GridEvent.didReceiveGridUpdate(
|
||||||
DatabasePB grid,
|
DatabasePB grid,
|
||||||
) = _DidReceiveGridUpdate;
|
) = _DidReceiveGridUpdate;
|
||||||
|
|
||||||
|
const factory GridEvent.didReceveFilters(List<FilterInfo> filters) =
|
||||||
|
_DidReceiveFilters;
|
||||||
|
const factory GridEvent.didReceveSorts(List<SortInfo> sorts) =
|
||||||
|
_DidReceiveSorts;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@ -149,7 +178,10 @@ class GridState with _$GridState {
|
|||||||
required List<RowInfo> rowInfos,
|
required List<RowInfo> rowInfos,
|
||||||
required int rowCount,
|
required int rowCount,
|
||||||
required GridLoadingState loadingState,
|
required GridLoadingState loadingState,
|
||||||
|
required bool reorderable,
|
||||||
required RowsChangedReason reason,
|
required RowsChangedReason reason,
|
||||||
|
required List<SortInfo> sorts,
|
||||||
|
required List<FilterInfo> filters,
|
||||||
}) = _GridState;
|
}) = _GridState;
|
||||||
|
|
||||||
factory GridState.initial(String viewId) => GridState(
|
factory GridState.initial(String viewId) => GridState(
|
||||||
@ -158,8 +190,11 @@ class GridState with _$GridState {
|
|||||||
rowCount: 0,
|
rowCount: 0,
|
||||||
grid: none(),
|
grid: none(),
|
||||||
viewId: viewId,
|
viewId: viewId,
|
||||||
|
reorderable: true,
|
||||||
loadingState: const _Loading(),
|
loadingState: const _Loading(),
|
||||||
reason: const InitialListState(),
|
reason: const InitialListState(),
|
||||||
|
filters: [],
|
||||||
|
sorts: [],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,19 +1,14 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/util.dart';
|
import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_view.dart';
|
||||||
import 'package:appflowy/startup/plugin/plugin.dart';
|
import 'package:appflowy/startup/plugin/plugin.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/home_stack.dart';
|
|
||||||
import 'package:appflowy/workspace/presentation/widgets/left_bar_item.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'presentation/grid_page.dart';
|
|
||||||
|
|
||||||
class GridPluginBuilder implements PluginBuilder {
|
class GridPluginBuilder implements PluginBuilder {
|
||||||
@override
|
@override
|
||||||
Plugin build(dynamic data) {
|
Plugin build(dynamic data) {
|
||||||
if (data is ViewPB) {
|
if (data is ViewPB) {
|
||||||
return GridPlugin(pluginType: pluginType, view: data);
|
return DatabaseTabBarViewPlugin(pluginType: pluginType, view: data);
|
||||||
} else {
|
} else {
|
||||||
throw FlowyPluginException.invalidData;
|
throw FlowyPluginException.invalidData;
|
||||||
}
|
}
|
||||||
@ -36,55 +31,3 @@ class GridPluginConfig implements PluginConfig {
|
|||||||
@override
|
@override
|
||||||
bool get creatable => true;
|
bool get creatable => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
class GridPlugin extends Plugin {
|
|
||||||
@override
|
|
||||||
final ViewPluginNotifier notifier;
|
|
||||||
final PluginType _pluginType;
|
|
||||||
|
|
||||||
GridPlugin({
|
|
||||||
required ViewPB view,
|
|
||||||
required PluginType pluginType,
|
|
||||||
bool listenOnViewChanged = false,
|
|
||||||
}) : _pluginType = pluginType,
|
|
||||||
notifier = ViewPluginNotifier(
|
|
||||||
view: view,
|
|
||||||
listenOnViewChanged: listenOnViewChanged,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
PluginWidgetBuilder get widgetBuilder =>
|
|
||||||
GridPluginWidgetBuilder(notifier: notifier);
|
|
||||||
|
|
||||||
@override
|
|
||||||
PluginId get id => notifier.view.id;
|
|
||||||
|
|
||||||
@override
|
|
||||||
PluginType get pluginType => _pluginType;
|
|
||||||
}
|
|
||||||
|
|
||||||
class GridPluginWidgetBuilder extends PluginWidgetBuilder {
|
|
||||||
final ViewPluginNotifier notifier;
|
|
||||||
ViewPB get view => notifier.view;
|
|
||||||
|
|
||||||
GridPluginWidgetBuilder({required this.notifier, Key? key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget get leftBarItem => ViewLeftBarItem(view: view);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildWidget({PluginContext? context}) {
|
|
||||||
notifier.isDeleted.addListener(() {
|
|
||||||
notifier.isDeleted.value.fold(() => null, (deletedView) {
|
|
||||||
if (deletedView.hasIndex()) {
|
|
||||||
context?.onDeleted(view, deletedView.index);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return GridPage(key: ValueKey(view.id), view: view);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<NavigationItem> get navigationItems => [this];
|
|
||||||
}
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
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/tar_bar/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:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@ -15,25 +17,77 @@ import 'package:linked_scroll_controller/linked_scroll_controller.dart';
|
|||||||
import '../../application/field/field_controller.dart';
|
import '../../application/field/field_controller.dart';
|
||||||
import '../../application/row/row_cache.dart';
|
import '../../application/row/row_cache.dart';
|
||||||
import '../../application/row/row_data_controller.dart';
|
import '../../application/row/row_data_controller.dart';
|
||||||
import '../../application/setting/setting_bloc.dart';
|
|
||||||
import '../application/filter/filter_menu_bloc.dart';
|
|
||||||
import '../application/grid_bloc.dart';
|
import '../application/grid_bloc.dart';
|
||||||
import '../../application/database_controller.dart';
|
import '../../application/database_controller.dart';
|
||||||
import '../application/sort/sort_menu_bloc.dart';
|
|
||||||
import 'grid_scroll.dart';
|
import 'grid_scroll.dart';
|
||||||
|
import '../../tar_bar/tab_bar_view.dart';
|
||||||
import 'layout/layout.dart';
|
import 'layout/layout.dart';
|
||||||
import 'layout/sizes.dart';
|
import 'layout/sizes.dart';
|
||||||
import 'widgets/accessory_menu.dart';
|
|
||||||
import 'widgets/row/row.dart';
|
import 'widgets/row/row.dart';
|
||||||
import 'widgets/footer/grid_footer.dart';
|
import 'widgets/footer/grid_footer.dart';
|
||||||
import 'widgets/header/grid_header.dart';
|
import 'widgets/header/grid_header.dart';
|
||||||
import '../../widgets/row/row_detail.dart';
|
import '../../widgets/row/row_detail.dart';
|
||||||
import 'widgets/shortcuts.dart';
|
import 'widgets/shortcuts.dart';
|
||||||
import 'widgets/toolbar/grid_toolbar.dart';
|
|
||||||
|
class ToggleExtensionNotifier extends ChangeNotifier {
|
||||||
|
bool _isToggled = false;
|
||||||
|
|
||||||
|
get isToggled => _isToggled;
|
||||||
|
|
||||||
|
void toggle() {
|
||||||
|
_isToggled = !_isToggled;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridPageTabBarBuilderImpl implements DatabaseTabBarItemBuilder {
|
||||||
|
final _toggleExtension = ToggleExtensionNotifier();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget content(
|
||||||
|
BuildContext context,
|
||||||
|
ViewPB view,
|
||||||
|
DatabaseController controller,
|
||||||
|
) {
|
||||||
|
return GridPage(
|
||||||
|
key: _makeValueKey(controller),
|
||||||
|
view: view,
|
||||||
|
databaseController: controller,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget settingBar(BuildContext context, DatabaseController controller) {
|
||||||
|
return GridSettingBar(
|
||||||
|
key: _makeValueKey(controller),
|
||||||
|
controller: controller,
|
||||||
|
toggleExtension: _toggleExtension,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget settingBarExtension(
|
||||||
|
BuildContext context,
|
||||||
|
DatabaseController controller,
|
||||||
|
) {
|
||||||
|
return DatabaseViewSettingExtension(
|
||||||
|
key: _makeValueKey(controller),
|
||||||
|
viewId: controller.viewId,
|
||||||
|
databaseController: controller,
|
||||||
|
toggleExtension: _toggleExtension,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ValueKey _makeValueKey(DatabaseController controller) {
|
||||||
|
return ValueKey(controller.viewId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class GridPage extends StatefulWidget {
|
class GridPage extends StatefulWidget {
|
||||||
|
final DatabaseController databaseController;
|
||||||
const GridPage({
|
const GridPage({
|
||||||
required this.view,
|
required this.view,
|
||||||
|
required this.databaseController,
|
||||||
this.onDeleted,
|
this.onDeleted,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
@ -46,12 +100,9 @@ class GridPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _GridPageState extends State<GridPage> {
|
class _GridPageState extends State<GridPage> {
|
||||||
late DatabaseController databaseController;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
databaseController = DatabaseController(view: widget.view);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -61,24 +112,9 @@ class _GridPageState extends State<GridPage> {
|
|||||||
BlocProvider<GridBloc>(
|
BlocProvider<GridBloc>(
|
||||||
create: (context) => GridBloc(
|
create: (context) => GridBloc(
|
||||||
view: widget.view,
|
view: widget.view,
|
||||||
databaseController: databaseController,
|
databaseController: widget.databaseController,
|
||||||
)..add(const GridEvent.initial()),
|
)..add(const GridEvent.initial()),
|
||||||
),
|
),
|
||||||
BlocProvider<GridFilterMenuBloc>(
|
|
||||||
create: (context) => GridFilterMenuBloc(
|
|
||||||
viewId: widget.view.id,
|
|
||||||
fieldController: databaseController.fieldController,
|
|
||||||
)..add(const GridFilterMenuEvent.initial()),
|
|
||||||
),
|
|
||||||
BlocProvider<SortMenuBloc>(
|
|
||||||
create: (context) => SortMenuBloc(
|
|
||||||
viewId: widget.view.id,
|
|
||||||
fieldController: databaseController.fieldController,
|
|
||||||
)..add(const SortMenuEvent.initial()),
|
|
||||||
),
|
|
||||||
BlocProvider<DatabaseSettingBloc>(
|
|
||||||
create: (context) => DatabaseSettingBloc(viewId: widget.view.id),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
child: BlocBuilder<GridBloc, GridState>(
|
child: BlocBuilder<GridBloc, GridState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
@ -87,9 +123,7 @@ class _GridPageState extends State<GridPage> {
|
|||||||
const Center(child: CircularProgressIndicator.adaptive()),
|
const Center(child: CircularProgressIndicator.adaptive()),
|
||||||
finish: (result) => result.successOrFail.fold(
|
finish: (result) => result.successOrFail.fold(
|
||||||
(_) => GridShortcuts(
|
(_) => GridShortcuts(
|
||||||
child: FlowyGrid(
|
child: GridPageContent(view: widget.view),
|
||||||
viewId: widget.view.id,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(err) => FlowyErrorPage(err.toString()),
|
(err) => FlowyErrorPage(err.toString()),
|
||||||
),
|
),
|
||||||
@ -100,18 +134,18 @@ class _GridPageState extends State<GridPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FlowyGrid extends StatefulWidget {
|
class GridPageContent extends StatefulWidget {
|
||||||
final String viewId;
|
final ViewPB view;
|
||||||
const FlowyGrid({
|
const GridPageContent({
|
||||||
required this.viewId,
|
required this.view,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FlowyGrid> createState() => _FlowyGridState();
|
State<GridPageContent> createState() => _GridPageContentState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FlowyGridState extends State<FlowyGrid> {
|
class _GridPageContentState extends State<GridPageContent> {
|
||||||
final _scrollController = GridScrollController(
|
final _scrollController = GridScrollController(
|
||||||
scrollGroupController: LinkedScrollControllerGroup(),
|
scrollGroupController: LinkedScrollControllerGroup(),
|
||||||
);
|
);
|
||||||
@ -135,55 +169,61 @@ class _FlowyGridState extends State<FlowyGrid> {
|
|||||||
buildWhen: (previous, current) => previous.fields != current.fields,
|
buildWhen: (previous, current) => previous.fields != current.fields,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final contentWidth = GridLayout.headerWidth(state.fields.value);
|
final contentWidth = GridLayout.headerWidth(state.fields.value);
|
||||||
final child = _WrapScrollView(
|
|
||||||
scrollController: _scrollController,
|
|
||||||
contentWidth: contentWidth,
|
|
||||||
child: _GridRows(
|
|
||||||
viewId: widget.viewId,
|
|
||||||
verticalScrollController: _scrollController.verticalController,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const GridToolbar(),
|
_GridHeader(headerScrollController: headerScrollController),
|
||||||
GridAccessoryMenu(viewId: state.viewId),
|
_GridRows(
|
||||||
_gridHeader(context, state.viewId),
|
viewId: state.viewId,
|
||||||
Flexible(child: child),
|
contentWidth: contentWidth,
|
||||||
const _RowCountBadge(),
|
scrollController: _scrollController,
|
||||||
|
),
|
||||||
|
const _GridFooter(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget _gridHeader(BuildContext context, String viewId) {
|
class _GridHeader extends StatelessWidget {
|
||||||
final fieldController =
|
final ScrollController headerScrollController;
|
||||||
context.read<GridBloc>().databaseController.fieldController;
|
const _GridHeader({required this.headerScrollController});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<GridBloc, GridState>(
|
||||||
|
builder: (context, state) {
|
||||||
return GridHeaderSliverAdaptor(
|
return GridHeaderSliverAdaptor(
|
||||||
viewId: viewId,
|
viewId: state.viewId,
|
||||||
fieldController: fieldController,
|
fieldController:
|
||||||
|
context.read<GridBloc>().databaseController.fieldController,
|
||||||
anchorScrollController: headerScrollController,
|
anchorScrollController: headerScrollController,
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GridRows extends StatelessWidget {
|
class _GridRows extends StatelessWidget {
|
||||||
final String viewId;
|
final String viewId;
|
||||||
|
final double contentWidth;
|
||||||
|
final GridScrollController scrollController;
|
||||||
|
|
||||||
const _GridRows({
|
const _GridRows({
|
||||||
required this.viewId,
|
required this.viewId,
|
||||||
required this.verticalScrollController,
|
required this.contentWidth,
|
||||||
|
required this.scrollController,
|
||||||
});
|
});
|
||||||
|
|
||||||
final ScrollController verticalScrollController;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final filterState = context.watch<GridFilterMenuBloc>().state;
|
return Flexible(
|
||||||
final sortState = context.watch<SortMenuBloc>().state;
|
child: _WrapScrollView(
|
||||||
|
scrollController: scrollController,
|
||||||
return BlocBuilder<GridBloc, GridState>(
|
contentWidth: contentWidth,
|
||||||
|
child: BlocBuilder<GridBloc, GridState>(
|
||||||
buildWhen: (previous, current) => current.reason.maybeWhen(
|
buildWhen: (previous, current) => current.reason.maybeWhen(
|
||||||
reorderRows: () => true,
|
reorderRows: () => true,
|
||||||
reorderSingleRow: (reorderRow, rowInfo) => true,
|
reorderSingleRow: (reorderRow, rowInfo) => true,
|
||||||
@ -203,14 +243,15 @@ class _GridRows extends StatelessWidget {
|
|||||||
/// This is a workaround related to
|
/// This is a workaround related to
|
||||||
/// https://github.com/flutter/flutter/issues/25652
|
/// https://github.com/flutter/flutter/issues/25652
|
||||||
cacheExtent: 5000,
|
cacheExtent: 5000,
|
||||||
scrollController: verticalScrollController,
|
scrollController: scrollController.verticalController,
|
||||||
buildDefaultDragHandles: false,
|
buildDefaultDragHandles: false,
|
||||||
proxyDecorator: (child, index, animation) => Material(
|
proxyDecorator: (child, index, animation) => Material(
|
||||||
color: Colors.white.withOpacity(.1),
|
color: Colors.white.withOpacity(.1),
|
||||||
child: Opacity(opacity: .5, child: child),
|
child: Opacity(opacity: .5, child: child),
|
||||||
),
|
),
|
||||||
onReorder: (fromIndex, newIndex) {
|
onReorder: (fromIndex, newIndex) {
|
||||||
final toIndex = newIndex > fromIndex ? newIndex - 1 : newIndex;
|
final toIndex =
|
||||||
|
newIndex > fromIndex ? newIndex - 1 : newIndex;
|
||||||
if (fromIndex == toIndex) {
|
if (fromIndex == toIndex) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -225,16 +266,17 @@ class _GridRows extends StatelessWidget {
|
|||||||
return _renderRow(
|
return _renderRow(
|
||||||
context,
|
context,
|
||||||
rowInfo.rowId,
|
rowInfo.rowId,
|
||||||
|
isDraggable: state.reorderable,
|
||||||
index: index,
|
index: index,
|
||||||
isSortEnabled: sortState.sortInfos.isNotEmpty,
|
|
||||||
isFilterEnabled: filterState.filters.isNotEmpty,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return const _GridFooter(key: Key('gridFooter'));
|
return const GridRowBottomBar(key: Key('gridFooter'));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,8 +284,7 @@ class _GridRows extends StatelessWidget {
|
|||||||
BuildContext context,
|
BuildContext context,
|
||||||
RowId rowId, {
|
RowId rowId, {
|
||||||
int? index,
|
int? index,
|
||||||
bool isSortEnabled = false,
|
required bool isDraggable,
|
||||||
bool isFilterEnabled = false,
|
|
||||||
Animation<double>? animation,
|
Animation<double>? animation,
|
||||||
}) {
|
}) {
|
||||||
final rowCache = context.read<GridBloc>().getRowCache(rowId);
|
final rowCache = context.read<GridBloc>().getRowCache(rowId);
|
||||||
@ -265,7 +306,7 @@ class _GridRows extends StatelessWidget {
|
|||||||
rowId: rowId,
|
rowId: rowId,
|
||||||
viewId: viewId,
|
viewId: viewId,
|
||||||
index: index,
|
index: index,
|
||||||
isDraggable: !isSortEnabled && !isFilterEnabled,
|
isDraggable: isDraggable,
|
||||||
dataController: dataController,
|
dataController: dataController,
|
||||||
cellBuilder: GridCellBuilder(cellCache: dataController.cellCache),
|
cellBuilder: GridCellBuilder(cellCache: dataController.cellCache),
|
||||||
openDetailPage: (context, cellBuilder) {
|
openDetailPage: (context, cellBuilder) {
|
||||||
@ -320,22 +361,6 @@ class _GridRows extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GridFooter extends StatelessWidget {
|
|
||||||
const _GridFooter({
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
padding: GridSize.footerContentInsets,
|
|
||||||
height: GridSize.footerHeight,
|
|
||||||
margin: const EdgeInsets.only(bottom: 200),
|
|
||||||
child: const GridAddRowButton(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _WrapScrollView extends StatelessWidget {
|
class _WrapScrollView extends StatelessWidget {
|
||||||
const _WrapScrollView({
|
const _WrapScrollView({
|
||||||
required this.contentWidth,
|
required this.contentWidth,
|
||||||
@ -366,8 +391,8 @@ class _WrapScrollView extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RowCountBadge extends StatelessWidget {
|
class _GridFooter extends StatelessWidget {
|
||||||
const _RowCountBadge();
|
const _GridFooter();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -1,90 +0,0 @@
|
|||||||
import 'package:appflowy/plugins/database_view/grid/application/filter/filter_menu_bloc.dart';
|
|
||||||
import 'package:appflowy/plugins/database_view/grid/application/grid_accessory_bloc.dart';
|
|
||||||
import 'package:appflowy/plugins/database_view/grid/application/sort/sort_menu_bloc.dart';
|
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
|
|
||||||
import '../layout/sizes.dart';
|
|
||||||
import 'filter/filter_menu.dart';
|
|
||||||
import 'sort/sort_menu.dart';
|
|
||||||
|
|
||||||
class GridAccessoryMenu extends StatelessWidget {
|
|
||||||
final String viewId;
|
|
||||||
const GridAccessoryMenu({required this.viewId, Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocProvider(
|
|
||||||
create: (context) => GridAccessoryMenuBloc(viewId: viewId),
|
|
||||||
child: MultiBlocListener(
|
|
||||||
listeners: [
|
|
||||||
BlocListener<GridFilterMenuBloc, GridFilterMenuState>(
|
|
||||||
listenWhen: (p, c) => p.isVisible != c.isVisible,
|
|
||||||
listener: (context, state) => context
|
|
||||||
.read<GridAccessoryMenuBloc>()
|
|
||||||
.add(const GridAccessoryMenuEvent.toggleMenu()),
|
|
||||||
),
|
|
||||||
BlocListener<SortMenuBloc, SortMenuState>(
|
|
||||||
listenWhen: (p, c) => p.isVisible != c.isVisible,
|
|
||||||
listener: (context, state) => context
|
|
||||||
.read<GridAccessoryMenuBloc>()
|
|
||||||
.add(const GridAccessoryMenuEvent.toggleMenu()),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
child: BlocBuilder<GridAccessoryMenuBloc, GridAccessoryMenuState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
if (state.isVisible) {
|
|
||||||
return const _AccessoryMenu();
|
|
||||||
} else {
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _AccessoryMenu extends StatelessWidget {
|
|
||||||
const _AccessoryMenu({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocBuilder<GridAccessoryMenuBloc, GridAccessoryMenuState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return _wrapPadding(
|
|
||||||
Column(
|
|
||||||
children: [
|
|
||||||
Divider(
|
|
||||||
height: 1.0,
|
|
||||||
color: AFThemeExtension.of(context).toggleOffFill,
|
|
||||||
),
|
|
||||||
const VSpace(6),
|
|
||||||
const IntrinsicHeight(
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
SortMenu(),
|
|
||||||
HSpace(6),
|
|
||||||
FilterMenu(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _wrapPadding(Widget child) {
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: GridSize.leadingHeaderPadding,
|
|
||||||
vertical: 6,
|
|
||||||
),
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
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/grid/application/filter/filter_menu_bloc.dart';
|
import 'package:appflowy/plugins/database_view/grid/application/filter/filter_menu_bloc.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';
|
||||||
@ -12,11 +13,22 @@ import 'create_filter_list.dart';
|
|||||||
import 'filter_menu_item.dart';
|
import 'filter_menu_item.dart';
|
||||||
|
|
||||||
class FilterMenu extends StatelessWidget {
|
class FilterMenu extends StatelessWidget {
|
||||||
const FilterMenu({Key? key}) : super(key: key);
|
final FieldController fieldController;
|
||||||
|
const FilterMenu({
|
||||||
|
required this.fieldController,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<GridFilterMenuBloc, GridFilterMenuState>(
|
return BlocProvider<GridFilterMenuBloc>(
|
||||||
|
create: (context) => GridFilterMenuBloc(
|
||||||
|
viewId: fieldController.viewId,
|
||||||
|
fieldController: fieldController,
|
||||||
|
)..add(
|
||||||
|
const GridFilterMenuEvent.initial(),
|
||||||
|
),
|
||||||
|
child: BlocBuilder<GridFilterMenuBloc, GridFilterMenuState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final List<Widget> children = [];
|
final List<Widget> children = [];
|
||||||
children.addAll(
|
children.addAll(
|
||||||
@ -43,6 +55,7 @@ class FilterMenu extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
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: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/image.dart';
|
import 'package:flowy_infra/image.dart';
|
||||||
@ -27,3 +28,19 @@ class GridAddRowButton extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class GridRowBottomBar extends StatelessWidget {
|
||||||
|
const GridRowBottomBar({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: GridSize.footerContentInsets,
|
||||||
|
height: GridSize.footerHeight,
|
||||||
|
margin: const EdgeInsets.only(bottom: 200),
|
||||||
|
child: const GridAddRowButton(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/application/sort/sort_menu_bloc.dart';
|
import 'package:appflowy/plugins/database_view/grid/application/sort/sort_menu_bloc.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
@ -13,11 +14,20 @@ import 'sort_editor.dart';
|
|||||||
import 'sort_info.dart';
|
import 'sort_info.dart';
|
||||||
|
|
||||||
class SortMenu extends StatelessWidget {
|
class SortMenu extends StatelessWidget {
|
||||||
const SortMenu({Key? key}) : super(key: key);
|
final FieldController fieldController;
|
||||||
|
const SortMenu({
|
||||||
|
required this.fieldController,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<SortMenuBloc, SortMenuState>(
|
return BlocProvider<SortMenuBloc>(
|
||||||
|
create: (context) => SortMenuBloc(
|
||||||
|
viewId: fieldController.viewId,
|
||||||
|
fieldController: fieldController,
|
||||||
|
)..add(const SortMenuEvent.initial()),
|
||||||
|
child: BlocBuilder<SortMenuBloc, SortMenuState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state.sortInfos.isNotEmpty) {
|
if (state.sortInfos.isNotEmpty) {
|
||||||
return AppFlowyPopover(
|
return AppFlowyPopover(
|
||||||
@ -37,6 +47,7 @@ class SortMenu extends StatelessWidget {
|
|||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
|
||||||
import 'package:appflowy/plugins/database_view/application/layout/layout_bloc.dart';
|
import 'package:appflowy/plugins/database_view/application/layout/layout_bloc.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pb.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flowy_infra/image.dart';
|
import 'package:flowy_infra/image.dart';
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
@ -67,34 +66,6 @@ class _DatabaseLayoutListState extends State<DatabaseLayoutList> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DatabaseLayoutExtension on DatabaseLayoutPB {
|
|
||||||
String layoutName() {
|
|
||||||
switch (this) {
|
|
||||||
case DatabaseLayoutPB.Board:
|
|
||||||
return LocaleKeys.board_menuName.tr();
|
|
||||||
case DatabaseLayoutPB.Calendar:
|
|
||||||
return LocaleKeys.calendar_menuName.tr();
|
|
||||||
case DatabaseLayoutPB.Grid:
|
|
||||||
return LocaleKeys.grid_menuName.tr();
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String iconName() {
|
|
||||||
switch (this) {
|
|
||||||
case DatabaseLayoutPB.Board:
|
|
||||||
return 'editor/board';
|
|
||||||
case DatabaseLayoutPB.Calendar:
|
|
||||||
return "editor/grid";
|
|
||||||
case DatabaseLayoutPB.Grid:
|
|
||||||
return "editor/grid";
|
|
||||||
default:
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DatabaseViewLayoutCell extends StatelessWidget {
|
class DatabaseViewLayoutCell extends StatelessWidget {
|
||||||
final bool isSelected;
|
final bool isSelected;
|
||||||
final DatabaseLayoutPB databaseLayout;
|
final DatabaseLayoutPB databaseLayout;
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
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:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import 'filter_button.dart';
|
||||||
|
import 'sort_button.dart';
|
||||||
|
|
||||||
|
class GridSettingBar extends StatelessWidget {
|
||||||
|
final DatabaseController controller;
|
||||||
|
final ToggleExtensionNotifier toggleExtension;
|
||||||
|
const GridSettingBar({
|
||||||
|
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: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
SizedBox(width: GridSize.leadingHeaderPadding),
|
||||||
|
const Spacer(),
|
||||||
|
const FilterButton(),
|
||||||
|
const SortButton(),
|
||||||
|
SettingButton(
|
||||||
|
databaseController: controller,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,41 +0,0 @@
|
|||||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
|
||||||
import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
|
|
||||||
import '../../layout/sizes.dart';
|
|
||||||
import 'filter_button.dart';
|
|
||||||
import '../../../../widgets/setting/setting_button.dart';
|
|
||||||
import 'sort_button.dart';
|
|
||||||
|
|
||||||
class GridToolbarContext {
|
|
||||||
final String viewId;
|
|
||||||
final FieldController fieldController;
|
|
||||||
GridToolbarContext({
|
|
||||||
required this.viewId,
|
|
||||||
required this.fieldController,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class GridToolbar extends StatelessWidget {
|
|
||||||
const GridToolbar({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SizedBox(
|
|
||||||
height: 40,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
SizedBox(width: GridSize.leadingHeaderPadding),
|
|
||||||
const Spacer(),
|
|
||||||
const FilterButton(),
|
|
||||||
const SortButton(),
|
|
||||||
SettingButton(
|
|
||||||
databaseController: context.read<GridBloc>().databaseController,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,98 @@
|
|||||||
|
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/grid/application/grid_accessory_bloc.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
|
||||||
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../application/field/field_controller.dart';
|
||||||
|
import '../grid/presentation/layout/sizes.dart';
|
||||||
|
import '../grid/presentation/widgets/filter/filter_menu.dart';
|
||||||
|
import '../grid/presentation/widgets/sort/sort_menu.dart';
|
||||||
|
|
||||||
|
class DatabaseViewSettingExtension extends StatelessWidget {
|
||||||
|
final String viewId;
|
||||||
|
final DatabaseController databaseController;
|
||||||
|
final ToggleExtensionNotifier toggleExtension;
|
||||||
|
const DatabaseViewSettingExtension({
|
||||||
|
required this.viewId,
|
||||||
|
required this.databaseController,
|
||||||
|
required this.toggleExtension,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
|
value: toggleExtension,
|
||||||
|
child: Consumer<ToggleExtensionNotifier>(
|
||||||
|
builder: (context, value, child) {
|
||||||
|
if (value.isToggled) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (context) =>
|
||||||
|
DatabaseViewSettingExtensionBloc(viewId: viewId),
|
||||||
|
child: _DatabaseViewSettingContent(
|
||||||
|
fieldController: databaseController.fieldController,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DatabaseViewSettingContent extends StatelessWidget {
|
||||||
|
final FieldController fieldController;
|
||||||
|
const _DatabaseViewSettingContent({
|
||||||
|
required this.fieldController,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<DatabaseViewSettingExtensionBloc,
|
||||||
|
DatabaseViewSettingExtensionState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final children = <Widget>[
|
||||||
|
Divider(
|
||||||
|
height: 1.0,
|
||||||
|
color: AFThemeExtension.of(context).toggleOffFill,
|
||||||
|
),
|
||||||
|
const VSpace(6),
|
||||||
|
IntrinsicHeight(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
SortMenu(
|
||||||
|
fieldController: fieldController,
|
||||||
|
),
|
||||||
|
const HSpace(6),
|
||||||
|
FilterMenu(
|
||||||
|
fieldController: fieldController,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
return _wrapPadding(
|
||||||
|
Column(children: children),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _wrapPadding(Widget child) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: GridSize.leadingHeaderPadding,
|
||||||
|
vertical: 6,
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,415 @@
|
|||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/application/tar_bar_bloc.dart';
|
||||||
|
import 'package:appflowy/plugins/util.dart';
|
||||||
|
import 'package:appflowy/startup/plugin/plugin.dart';
|
||||||
|
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/home/home_stack.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/widgets/left_bar_item.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flowy_infra/image.dart';
|
||||||
|
import 'package:flowy_infra/size.dart';
|
||||||
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import '../application/database_controller.dart';
|
||||||
|
import '../grid/presentation/layout/sizes.dart';
|
||||||
|
import 'tar_bar_add_button.dart';
|
||||||
|
|
||||||
|
abstract class DatabaseTabBarItemBuilder {
|
||||||
|
const DatabaseTabBarItemBuilder();
|
||||||
|
|
||||||
|
/// Returns the content of the tab bar item. The content is shown when the tab
|
||||||
|
/// bar item is selected. It can be any kind of database view.
|
||||||
|
Widget content(
|
||||||
|
BuildContext context,
|
||||||
|
ViewPB view,
|
||||||
|
DatabaseController controller,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Returns the setting bar of the tab bar item. The setting bar is shown on the
|
||||||
|
/// top right conner when the tab bar item is selected.
|
||||||
|
Widget settingBar(
|
||||||
|
BuildContext context,
|
||||||
|
DatabaseController controller,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget settingBarExtension(
|
||||||
|
BuildContext context,
|
||||||
|
DatabaseController controller,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class DatabaseTabBarView extends StatefulWidget {
|
||||||
|
final ViewPB view;
|
||||||
|
const DatabaseTabBarView({
|
||||||
|
required this.view,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DatabaseTabBarView> createState() => _DatabaseTabBarViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DatabaseTabBarViewState extends State<DatabaseTabBarView> {
|
||||||
|
PageController? _pageController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_pageController = PageController(
|
||||||
|
initialPage: 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider<GridTabBarBloc>(
|
||||||
|
create: (context) => GridTabBarBloc(view: widget.view)
|
||||||
|
..add(
|
||||||
|
const GridTabBarEvent.initial(),
|
||||||
|
),
|
||||||
|
child: MultiBlocListener(
|
||||||
|
listeners: [
|
||||||
|
BlocListener<GridTabBarBloc, GridTabBarState>(
|
||||||
|
listenWhen: (p, c) => p.selectedIndex != c.selectedIndex,
|
||||||
|
listener: (context, state) {
|
||||||
|
_pageController?.animateToPage(
|
||||||
|
state.selectedIndex,
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.ease,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
BlocBuilder<GridTabBarBloc, GridTabBarState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return const Flexible(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(left: 50),
|
||||||
|
child: DatabaseTabBar(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
BlocBuilder<GridTabBarBloc, GridTabBarState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return SizedBox(
|
||||||
|
width: 300,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 50),
|
||||||
|
child: pageSettingBarFromState(state),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
BlocBuilder<GridTabBarBloc, GridTabBarState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return pageSettingBarExtensionFromState(state);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: BlocBuilder<GridTabBarBloc, GridTabBarState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return PageView(
|
||||||
|
pageSnapping: false,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
controller: _pageController,
|
||||||
|
children: pageContentFromState(state),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> pageContentFromState(GridTabBarState state) {
|
||||||
|
return state.tabBars.map((tabBar) {
|
||||||
|
final controller =
|
||||||
|
state.tabBarControllerByViewId[tabBar.viewId]!.controller;
|
||||||
|
return tabBar.builder.content(
|
||||||
|
context,
|
||||||
|
tabBar.view,
|
||||||
|
controller,
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget pageSettingBarFromState(GridTabBarState state) {
|
||||||
|
if (state.tabBars.length < state.selectedIndex) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
final tarBar = state.tabBars[state.selectedIndex];
|
||||||
|
final controller =
|
||||||
|
state.tabBarControllerByViewId[tarBar.viewId]!.controller;
|
||||||
|
return tarBar.builder.settingBar(
|
||||||
|
context,
|
||||||
|
controller,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget pageSettingBarExtensionFromState(GridTabBarState state) {
|
||||||
|
if (state.tabBars.length < state.selectedIndex) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
final tarBar = state.tabBars[state.selectedIndex];
|
||||||
|
final controller =
|
||||||
|
state.tabBarControllerByViewId[tarBar.viewId]!.controller;
|
||||||
|
return tarBar.builder.settingBarExtension(
|
||||||
|
context,
|
||||||
|
controller,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DatabaseTabBarViewPlugin extends Plugin {
|
||||||
|
@override
|
||||||
|
final ViewPluginNotifier notifier;
|
||||||
|
final PluginType _pluginType;
|
||||||
|
|
||||||
|
DatabaseTabBarViewPlugin({
|
||||||
|
required ViewPB view,
|
||||||
|
required PluginType pluginType,
|
||||||
|
}) : _pluginType = pluginType,
|
||||||
|
notifier = ViewPluginNotifier(view: view);
|
||||||
|
|
||||||
|
@override
|
||||||
|
PluginWidgetBuilder get widgetBuilder => DatabasePluginWidgetBuilder(
|
||||||
|
notifier: notifier,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
PluginId get id => notifier.view.id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
PluginType get pluginType => _pluginType;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DatabasePluginWidgetBuilder extends PluginWidgetBuilder {
|
||||||
|
final ViewPluginNotifier notifier;
|
||||||
|
|
||||||
|
DatabasePluginWidgetBuilder({
|
||||||
|
required this.notifier,
|
||||||
|
Key? key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget get leftBarItem => ViewLeftBarItem(view: notifier.view);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildWidget({PluginContext? context}) {
|
||||||
|
notifier.isDeleted.addListener(() {
|
||||||
|
notifier.isDeleted.value.fold(() => null, (deletedView) {
|
||||||
|
if (deletedView.hasIndex()) {
|
||||||
|
context?.onDeleted(notifier.view, deletedView.index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return DatabaseTabBarView(
|
||||||
|
key: ValueKey(notifier.view.id),
|
||||||
|
view: notifier.view,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<NavigationItem> get navigationItems => [this];
|
||||||
|
}
|
||||||
|
|
||||||
|
class DatabaseTabBar extends StatefulWidget {
|
||||||
|
const DatabaseTabBar({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DatabaseTabBar> createState() => _DatabaseTabBarState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DatabaseTabBarState extends State<DatabaseTabBar> {
|
||||||
|
final _scrollController = ScrollController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<GridTabBarBloc, GridTabBarState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
final children = state.tabBars.indexed.map((indexed) {
|
||||||
|
final isSelected = state.selectedIndex == indexed.$1;
|
||||||
|
final tabBar = indexed.$2;
|
||||||
|
return DatabaseTabBarItem(
|
||||||
|
key: ValueKey(tabBar.viewId),
|
||||||
|
view: tabBar.view,
|
||||||
|
isSelected: isSelected,
|
||||||
|
onTap: (selectedView) {
|
||||||
|
context.read<GridTabBarBloc>().add(
|
||||||
|
GridTabBarEvent.selectView(selectedView.id),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
controller: _scrollController,
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: IntrinsicWidth(
|
||||||
|
child: Row(children: children),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AddDatabaseViewButton(
|
||||||
|
onTap: (action) async {
|
||||||
|
context.read<GridTabBarBloc>().add(
|
||||||
|
GridTabBarEvent.createView(action),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DatabaseTabBarItem extends StatelessWidget {
|
||||||
|
final bool isSelected;
|
||||||
|
final ViewPB view;
|
||||||
|
final Function(ViewPB) onTap;
|
||||||
|
const DatabaseTabBarItem({
|
||||||
|
required this.view,
|
||||||
|
required this.isSelected,
|
||||||
|
required this.onTap,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(minWidth: 80, maxWidth: 160),
|
||||||
|
child: IntrinsicWidth(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
TabBarItemButton(
|
||||||
|
view: view,
|
||||||
|
onTap: () => onTap(view),
|
||||||
|
),
|
||||||
|
if (isSelected)
|
||||||
|
Divider(
|
||||||
|
height: 1,
|
||||||
|
thickness: 2,
|
||||||
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TabBarItemButton extends StatelessWidget {
|
||||||
|
final ViewPB view;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
const TabBarItemButton({
|
||||||
|
required this.view,
|
||||||
|
required this.onTap,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return PopoverActionList<TabBarViewAction>(
|
||||||
|
direction: PopoverDirection.bottomWithCenterAligned,
|
||||||
|
actions: TabBarViewAction.values,
|
||||||
|
buildChild: (controller) {
|
||||||
|
return FlowyButton(
|
||||||
|
radius: Corners.s5Border,
|
||||||
|
hoverColor: AFThemeExtension.of(context).greyHover,
|
||||||
|
onTap: onTap,
|
||||||
|
onSecondaryTap: () {
|
||||||
|
controller.show();
|
||||||
|
},
|
||||||
|
text: FlowyText.medium(
|
||||||
|
view.name,
|
||||||
|
maxLines: 1,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
margin: GridSize.cellContentInsets,
|
||||||
|
leftIcon: svgWidget(
|
||||||
|
view.iconName,
|
||||||
|
color: Theme.of(context).iconTheme.color,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onSelected: (action, controller) {
|
||||||
|
switch (action) {
|
||||||
|
case TabBarViewAction.rename:
|
||||||
|
NavigatorTextFieldDialog(
|
||||||
|
title: LocaleKeys.menuAppHeader_renameDialog.tr(),
|
||||||
|
value: view.name,
|
||||||
|
confirm: (newValue) {
|
||||||
|
context.read<GridTabBarBloc>().add(
|
||||||
|
GridTabBarEvent.renameView(view.id, newValue),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
).show(context);
|
||||||
|
break;
|
||||||
|
case TabBarViewAction.delete:
|
||||||
|
NavigatorAlertDialog(
|
||||||
|
title: LocaleKeys.grid_deleteView.tr(),
|
||||||
|
confirm: () {
|
||||||
|
context.read<GridTabBarBloc>().add(
|
||||||
|
GridTabBarEvent.deleteView(view.id),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
).show(context);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
controller.close();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TabBarViewAction implements ActionCell {
|
||||||
|
rename,
|
||||||
|
delete;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name {
|
||||||
|
switch (this) {
|
||||||
|
case TabBarViewAction.rename:
|
||||||
|
return LocaleKeys.disclosureAction_rename.tr();
|
||||||
|
case TabBarViewAction.delete:
|
||||||
|
return LocaleKeys.disclosureAction_delete.tr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget icon(Color iconColor) {
|
||||||
|
switch (this) {
|
||||||
|
case TabBarViewAction.rename:
|
||||||
|
return const FlowySvg(name: 'editor/edit');
|
||||||
|
case TabBarViewAction.delete:
|
||||||
|
return const FlowySvg(name: 'editor/delete');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget? leftIcon(Color iconColor) => icon(iconColor);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget? rightIcon(Color iconColor) => null;
|
||||||
|
}
|
@ -0,0 +1,156 @@
|
|||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flowy_infra/image.dart';
|
||||||
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/extension.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class AddDatabaseViewButton extends StatefulWidget {
|
||||||
|
final Function(AddButtonAction) onTap;
|
||||||
|
const AddDatabaseViewButton({
|
||||||
|
required this.onTap,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AddDatabaseViewButton> createState() => _AddDatabaseViewButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AddDatabaseViewButtonState extends State<AddDatabaseViewButton> {
|
||||||
|
final popoverController = PopoverController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AppFlowyPopover(
|
||||||
|
controller: popoverController,
|
||||||
|
constraints: BoxConstraints.loose(const Size(200, 400)),
|
||||||
|
direction: PopoverDirection.bottomWithLeftAligned,
|
||||||
|
offset: const Offset(0, 8),
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
triggerActions: PopoverTriggerFlags.none,
|
||||||
|
child: FlowyIconButton(
|
||||||
|
iconPadding: const EdgeInsets.all(4),
|
||||||
|
hoverColor: AFThemeExtension.of(context).greyHover,
|
||||||
|
onPressed: () => popoverController.show(),
|
||||||
|
icon: svgWidget(
|
||||||
|
'home/add',
|
||||||
|
color: Theme.of(context).colorScheme.tertiary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
popupBuilder: (BuildContext context) {
|
||||||
|
return TarBarAddButtonAction(
|
||||||
|
onTap: (action) {
|
||||||
|
popoverController.close();
|
||||||
|
widget.onTap(action);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TarBarAddButtonAction extends StatelessWidget {
|
||||||
|
final Function(AddButtonAction) onTap;
|
||||||
|
const TarBarAddButtonAction({
|
||||||
|
required this.onTap,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final cells = AddButtonAction.values.map((layout) {
|
||||||
|
return TarBarAddButtonActionCell(
|
||||||
|
action: layout,
|
||||||
|
onTap: onTap,
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
return ListView.separated(
|
||||||
|
controller: ScrollController(),
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: cells.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) => cells[index],
|
||||||
|
separatorBuilder: (BuildContext context, int index) =>
|
||||||
|
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 6.0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TarBarAddButtonActionCell extends StatelessWidget {
|
||||||
|
final AddButtonAction action;
|
||||||
|
final void Function(AddButtonAction) onTap;
|
||||||
|
const TarBarAddButtonActionCell({
|
||||||
|
required this.action,
|
||||||
|
required this.onTap,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
height: GridSize.popoverItemHeight,
|
||||||
|
child: FlowyButton(
|
||||||
|
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||||
|
text: FlowyText.medium(
|
||||||
|
action.title,
|
||||||
|
color: AFThemeExtension.of(context).textColor,
|
||||||
|
),
|
||||||
|
leftIcon: svgWidget(
|
||||||
|
action.iconName,
|
||||||
|
color: Theme.of(context).iconTheme.color,
|
||||||
|
),
|
||||||
|
onTap: () => onTap(action),
|
||||||
|
).padding(horizontal: 6.0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String get iconName {
|
||||||
|
switch (this) {
|
||||||
|
case AddButtonAction.board:
|
||||||
|
return 'editor/board';
|
||||||
|
case AddButtonAction.calendar:
|
||||||
|
return "editor/grid";
|
||||||
|
case AddButtonAction.grid:
|
||||||
|
return "editor/grid";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
|
extension DatabaseLayoutExtension on DatabaseLayoutPB {
|
||||||
|
String layoutName() {
|
||||||
|
switch (this) {
|
||||||
|
case DatabaseLayoutPB.Board:
|
||||||
|
return LocaleKeys.board_menuName.tr();
|
||||||
|
case DatabaseLayoutPB.Calendar:
|
||||||
|
return LocaleKeys.calendar_menuName.tr();
|
||||||
|
case DatabaseLayoutPB.Grid:
|
||||||
|
return LocaleKeys.grid_menuName.tr();
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String iconName() {
|
||||||
|
switch (this) {
|
||||||
|
case DatabaseLayoutPB.Board:
|
||||||
|
return 'editor/board';
|
||||||
|
case DatabaseLayoutPB.Calendar:
|
||||||
|
return "editor/grid";
|
||||||
|
case DatabaseLayoutPB.Grid:
|
||||||
|
return "editor/grid";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
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/setting/setting_bloc.dart';
|
|
||||||
import 'package:flowy_infra/image.dart';
|
import 'package:flowy_infra/image.dart';
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||||
@ -9,6 +8,7 @@ import 'package:flowy_infra_ui/widget/spacing.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../../grid/presentation/layout/sizes.dart';
|
import '../../grid/presentation/layout/sizes.dart';
|
||||||
|
import 'setting_button.dart';
|
||||||
|
|
||||||
class DatabaseSettingList extends StatelessWidget {
|
class DatabaseSettingList extends StatelessWidget {
|
||||||
final DatabaseController databaseContoller;
|
final DatabaseController databaseContoller;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
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/database_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/setting/setting_bloc.dart';
|
|
||||||
import 'package:appflowy/plugins/database_view/calendar/presentation/toolbar/calendar_layout_setting.dart';
|
import 'package:appflowy/plugins/database_view/calendar/presentation/toolbar/calendar_layout_setting.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/group/database_group.dart';
|
import 'package:appflowy/plugins/database_view/widgets/group/database_group.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/calendar_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/calendar_entities.pb.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/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
@ -97,7 +97,7 @@ class _DatabaseSettingListPopoverState
|
|||||||
case DatabaseSettingAction.showLayout:
|
case DatabaseSettingAction.showLayout:
|
||||||
return DatabaseLayoutList(
|
return DatabaseLayoutList(
|
||||||
viewId: widget.databaseController.viewId,
|
viewId: widget.databaseController.viewId,
|
||||||
currentLayout: widget.databaseController.databaseLayout!,
|
currentLayout: widget.databaseController.databaseLayout,
|
||||||
);
|
);
|
||||||
case DatabaseSettingAction.showGroup:
|
case DatabaseSettingAction.showGroup:
|
||||||
return DatabaseGroupList(
|
return DatabaseGroupList(
|
||||||
@ -132,7 +132,7 @@ class ICalendarSettingImpl extends ICalendarSetting {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void updateLayoutSettings(CalendarLayoutSettingPB layoutSettings) {
|
void updateLayoutSettings(CalendarLayoutSettingPB layoutSettings) {
|
||||||
_databaseController.updateCalenderLayoutSetting(layoutSettings);
|
_databaseController.updateLayoutSetting(layoutSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -140,3 +140,63 @@ class ICalendarSettingImpl extends ICalendarSetting {
|
|||||||
return _databaseController.databaseLayoutSetting?.calendar;
|
return _databaseController.databaseLayoutSetting?.calendar;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum DatabaseSettingAction {
|
||||||
|
showProperties,
|
||||||
|
showLayout,
|
||||||
|
showGroup,
|
||||||
|
showCalendarLayout,
|
||||||
|
}
|
||||||
|
|
||||||
|
extension DatabaseSettingActionExtension on DatabaseSettingAction {
|
||||||
|
String iconName() {
|
||||||
|
switch (this) {
|
||||||
|
case DatabaseSettingAction.showProperties:
|
||||||
|
return 'grid/setting/properties';
|
||||||
|
case DatabaseSettingAction.showLayout:
|
||||||
|
return 'grid/setting/database_layout';
|
||||||
|
case DatabaseSettingAction.showGroup:
|
||||||
|
return 'grid/setting/group';
|
||||||
|
case DatabaseSettingAction.showCalendarLayout:
|
||||||
|
return 'grid/setting/calendar_layout';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String title() {
|
||||||
|
switch (this) {
|
||||||
|
case DatabaseSettingAction.showProperties:
|
||||||
|
return LocaleKeys.grid_settings_Properties.tr();
|
||||||
|
case DatabaseSettingAction.showLayout:
|
||||||
|
return LocaleKeys.grid_settings_databaseLayout.tr();
|
||||||
|
case DatabaseSettingAction.showGroup:
|
||||||
|
return LocaleKeys.grid_settings_group.tr();
|
||||||
|
case DatabaseSettingAction.showCalendarLayout:
|
||||||
|
return LocaleKeys.calendar_settings_name.tr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the list of actions that should be shown for the given database layout.
|
||||||
|
List<DatabaseSettingAction> actionsForDatabaseLayout(DatabaseLayoutPB? layout) {
|
||||||
|
switch (layout) {
|
||||||
|
case DatabaseLayoutPB.Board:
|
||||||
|
return [
|
||||||
|
DatabaseSettingAction.showProperties,
|
||||||
|
DatabaseSettingAction.showLayout,
|
||||||
|
DatabaseSettingAction.showGroup,
|
||||||
|
];
|
||||||
|
case DatabaseLayoutPB.Calendar:
|
||||||
|
return [
|
||||||
|
DatabaseSettingAction.showProperties,
|
||||||
|
DatabaseSettingAction.showLayout,
|
||||||
|
DatabaseSettingAction.showCalendarLayout,
|
||||||
|
];
|
||||||
|
case DatabaseLayoutPB.Grid:
|
||||||
|
return [
|
||||||
|
DatabaseSettingAction.showProperties,
|
||||||
|
DatabaseSettingAction.showLayout,
|
||||||
|
];
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -50,10 +50,7 @@ class DocumentPlugin extends Plugin<int> {
|
|||||||
required ViewPB view,
|
required ViewPB view,
|
||||||
bool listenOnViewChanged = false,
|
bool listenOnViewChanged = false,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : notifier = ViewPluginNotifier(
|
}) : notifier = ViewPluginNotifier(view: view) {
|
||||||
view: view,
|
|
||||||
listenOnViewChanged: listenOnViewChanged,
|
|
||||||
) {
|
|
||||||
_pluginType = pluginType;
|
_pluginType = pluginType;
|
||||||
_documentAppearanceCubit.fetch();
|
_documentAppearanceCubit.fetch();
|
||||||
}
|
}
|
||||||
|
@ -91,10 +91,12 @@ class _BuiltInPageWidgetState extends State<BuiltInPageWidget> {
|
|||||||
onExit: (_) => widget.editorState.service.scrollService?.enable(),
|
onExit: (_) => widget.editorState.service.scrollService?.enable(),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 400,
|
height: 400,
|
||||||
child: Stack(
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildMenu(context, viewPB),
|
_buildMenu(context, viewPB),
|
||||||
_buildPage(context, viewPB),
|
Expanded(child: _buildPage(context, viewPB)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -114,10 +116,7 @@ class _BuiltInPageWidgetState extends State<BuiltInPageWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildMenu(BuildContext context, ViewPB viewPB) {
|
Widget _buildMenu(BuildContext context, ViewPB viewPB) {
|
||||||
return Positioned(
|
return Row(
|
||||||
top: 5,
|
|
||||||
left: 5,
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
@ -134,12 +133,6 @@ class _BuiltInPageWidgetState extends State<BuiltInPageWidget> {
|
|||||||
color: Theme.of(context).iconTheme.color,
|
color: Theme.of(context).iconTheme.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Name
|
|
||||||
const Space(7, 0),
|
|
||||||
FlowyText.medium(
|
|
||||||
viewPB.name,
|
|
||||||
fontSize: 16.0,
|
|
||||||
),
|
|
||||||
// setting
|
// setting
|
||||||
const Space(7, 0),
|
const Space(7, 0),
|
||||||
PopoverActionList<_ActionWrapper>(
|
PopoverActionList<_ActionWrapper>(
|
||||||
@ -175,7 +168,6 @@ class _BuiltInPageWidgetState extends State<BuiltInPageWidget> {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ extension InsertDatabase on EditorState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final prefix = _referencedDatabasePrefix(childView.layout);
|
final prefix = _referencedDatabasePrefix(childView.layout);
|
||||||
final ref = await ViewBackendService.createDatabaseReferenceView(
|
final ref = await ViewBackendService.createDatabaseLinkedView(
|
||||||
parentViewId: childView.id,
|
parentViewId: childView.id,
|
||||||
name: "$prefix ${childView.name}",
|
name: "$prefix ${childView.name}",
|
||||||
layoutType: childView.layout,
|
layoutType: childView.layout,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/insert_page_command.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/insert_page_command.dart';
|
||||||
|
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_service.dart';
|
import 'package:appflowy/workspace/application/view/view_service.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:appflowy_editor/appflowy_editor.dart';
|
||||||
@ -212,7 +213,7 @@ class _LinkToPageMenuState extends State<LinkToPageMenu> {
|
|||||||
FlowyButton(
|
FlowyButton(
|
||||||
isSelected: index == _selectedIndex,
|
isSelected: index == _selectedIndex,
|
||||||
leftIcon: svgWidget(
|
leftIcon: svgWidget(
|
||||||
_iconName(value),
|
value.iconName,
|
||||||
color: Theme.of(context).iconTheme.color,
|
color: Theme.of(context).iconTheme.color,
|
||||||
),
|
),
|
||||||
text: FlowyText.regular(value.name),
|
text: FlowyText.regular(value.name),
|
||||||
@ -238,19 +239,6 @@ class _LinkToPageMenuState extends State<LinkToPageMenu> {
|
|||||||
future: items,
|
future: items,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _iconName(ViewPB viewPB) {
|
|
||||||
switch (viewPB.layout) {
|
|
||||||
case ViewLayoutPB.Grid:
|
|
||||||
return 'editor/grid';
|
|
||||||
case ViewLayoutPB.Board:
|
|
||||||
return 'editor/board';
|
|
||||||
case ViewLayoutPB.Calendar:
|
|
||||||
return 'editor/calendar';
|
|
||||||
default:
|
|
||||||
throw Exception('Unknown layout type');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension on ViewLayoutPB {
|
extension on ViewLayoutPB {
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
import 'package:appflowy/startup/plugin/plugin.dart';
|
import 'package:appflowy/startup/plugin/plugin.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
|
||||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
|
||||||
import 'package:appflowy/workspace/application/view/view_listener.dart';
|
import 'package:appflowy/workspace/application/view/view_listener.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../workspace/presentation/home/home_stack.dart';
|
|
||||||
|
|
||||||
class ViewPluginNotifier extends PluginNotifier<Option<DeletedViewPB>> {
|
class ViewPluginNotifier extends PluginNotifier<Option<DeletedViewPB>> {
|
||||||
final ViewListener? _viewListener;
|
final ViewListener? _viewListener;
|
||||||
ViewPB view;
|
ViewPB view;
|
||||||
@ -18,21 +14,10 @@ class ViewPluginNotifier extends PluginNotifier<Option<DeletedViewPB>> {
|
|||||||
|
|
||||||
ViewPluginNotifier({
|
ViewPluginNotifier({
|
||||||
required this.view,
|
required this.view,
|
||||||
required bool listenOnViewChanged,
|
|
||||||
}) : _viewListener = ViewListener(viewId: view.id) {
|
}) : _viewListener = ViewListener(viewId: view.id) {
|
||||||
if (listenOnViewChanged) {
|
|
||||||
_viewListener?.start(
|
_viewListener?.start(
|
||||||
onViewUpdated: (updatedView) {
|
onViewUpdated: (updatedView) {
|
||||||
// If the layout is changed, we need to create a new plugin for it.
|
|
||||||
if (view.layout != updatedView.layout) {
|
|
||||||
getIt<HomeStackManager>().setPlugin(
|
|
||||||
updatedView.plugin(
|
|
||||||
listenOnViewChanged: listenOnViewChanged,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
view = updatedView;
|
view = updatedView;
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onViewMoveToTrash: (result) {
|
onViewMoveToTrash: (result) {
|
||||||
result.fold(
|
result.fold(
|
||||||
@ -42,7 +27,6 @@ class ViewPluginNotifier extends PluginNotifier<Option<DeletedViewPB>> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
|
||||||
import 'package:appflowy/startup/plugin/plugin.dart';
|
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/workspace/application/app/app_listener.dart';
|
import 'package:appflowy/workspace/application/view/prelude.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_service.dart';
|
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/menu.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/menu.dart';
|
||||||
import 'package:expandable/expandable.dart';
|
import 'package:expandable/expandable.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
@ -18,11 +16,11 @@ part 'app_bloc.freezed.dart';
|
|||||||
|
|
||||||
class AppBloc extends Bloc<AppEvent, AppState> {
|
class AppBloc extends Bloc<AppEvent, AppState> {
|
||||||
final ViewBackendService appService;
|
final ViewBackendService appService;
|
||||||
final AppListener appListener;
|
final ViewListener viewListener;
|
||||||
|
|
||||||
AppBloc({required ViewPB view})
|
AppBloc({required ViewPB view})
|
||||||
: appService = ViewBackendService(),
|
: appService = ViewBackendService(),
|
||||||
appListener = AppListener(viewId: view.id),
|
viewListener = ViewListener(viewId: view.id),
|
||||||
super(AppState.initial(view)) {
|
super(AppState.initial(view)) {
|
||||||
on<AppEvent>((event, emit) async {
|
on<AppEvent>((event, emit) async {
|
||||||
await event.map(
|
await event.map(
|
||||||
@ -47,10 +45,10 @@ class AppBloc extends Bloc<AppEvent, AppState> {
|
|||||||
},
|
},
|
||||||
appDidUpdate: (e) async {
|
appDidUpdate: (e) async {
|
||||||
final latestCreatedView = state.latestCreatedView;
|
final latestCreatedView = state.latestCreatedView;
|
||||||
final views = e.app.childViews;
|
final views = e.view.childViews;
|
||||||
AppState newState = state.copyWith(
|
AppState newState = state.copyWith(
|
||||||
views: views,
|
views: views,
|
||||||
view: e.app,
|
view: e.view,
|
||||||
);
|
);
|
||||||
if (latestCreatedView != null) {
|
if (latestCreatedView != null) {
|
||||||
final index = views
|
final index = views
|
||||||
@ -67,8 +65,8 @@ class AppBloc extends Bloc<AppEvent, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _startListening() {
|
void _startListening() {
|
||||||
appListener.start(
|
viewListener.start(
|
||||||
onAppUpdated: (app) {
|
onViewUpdated: (app) {
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
add(AppEvent.appDidUpdate(app));
|
add(AppEvent.appDidUpdate(app));
|
||||||
}
|
}
|
||||||
@ -110,7 +108,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
|
|||||||
parentViewId: state.view.id,
|
parentViewId: state.view.id,
|
||||||
name: value.name,
|
name: value.name,
|
||||||
desc: value.desc ?? "",
|
desc: value.desc ?? "",
|
||||||
layoutType: value.pluginBuilder.layoutType!,
|
layoutType: value.layoutType,
|
||||||
initialDataBytes: value.initialDataBytes,
|
initialDataBytes: value.initialDataBytes,
|
||||||
ext: value.ext ?? {},
|
ext: value.ext ?? {},
|
||||||
openAfterCreate: true,
|
openAfterCreate: true,
|
||||||
@ -131,13 +129,13 @@ class AppBloc extends Bloc<AppEvent, AppState> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
await appListener.stop();
|
await viewListener.stop();
|
||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadViews(Emitter<AppState> emit) async {
|
Future<void> _loadViews(Emitter<AppState> emit) async {
|
||||||
final viewsOrFailed =
|
final viewsOrFailed =
|
||||||
await ViewBackendService.getViews(viewId: state.view.id);
|
await ViewBackendService.getChildViews(viewId: state.view.id);
|
||||||
viewsOrFailed.fold(
|
viewsOrFailed.fold(
|
||||||
(views) => emit(state.copyWith(views: views)),
|
(views) => emit(state.copyWith(views: views)),
|
||||||
(error) {
|
(error) {
|
||||||
@ -153,7 +151,7 @@ class AppEvent with _$AppEvent {
|
|||||||
const factory AppEvent.initial() = Initial;
|
const factory AppEvent.initial() = Initial;
|
||||||
const factory AppEvent.createView(
|
const factory AppEvent.createView(
|
||||||
String name,
|
String name,
|
||||||
PluginBuilder pluginBuilder, {
|
ViewLayoutPB layoutType, {
|
||||||
String? desc,
|
String? desc,
|
||||||
|
|
||||||
/// ~~The initial data should be the JSON of the document~~
|
/// ~~The initial data should be the JSON of the document~~
|
||||||
@ -172,7 +170,7 @@ class AppEvent with _$AppEvent {
|
|||||||
const factory AppEvent.delete() = DeleteApp;
|
const factory AppEvent.delete() = DeleteApp;
|
||||||
const factory AppEvent.deleteView(String viewId) = DeleteView;
|
const factory AppEvent.deleteView(String viewId) = DeleteView;
|
||||||
const factory AppEvent.rename(String newName) = Rename;
|
const factory AppEvent.rename(String newName) = Rename;
|
||||||
const factory AppEvent.appDidUpdate(ViewPB app) = AppDidUpdate;
|
const factory AppEvent.appDidUpdate(ViewPB view) = AppDidUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@ -191,7 +189,7 @@ class AppState with _$AppState {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppViewDataContext extends ChangeNotifier {
|
class ViewDataContext extends ChangeNotifier {
|
||||||
final String viewId;
|
final String viewId;
|
||||||
final ValueNotifier<List<ViewPB>> _viewsNotifier = ValueNotifier([]);
|
final ValueNotifier<List<ViewPB>> _viewsNotifier = ValueNotifier([]);
|
||||||
final ValueNotifier<ViewPB?> _selectedViewNotifier = ValueNotifier(null);
|
final ValueNotifier<ViewPB?> _selectedViewNotifier = ValueNotifier(null);
|
||||||
@ -199,7 +197,7 @@ class AppViewDataContext extends ChangeNotifier {
|
|||||||
ExpandableController expandController =
|
ExpandableController expandController =
|
||||||
ExpandableController(initialExpanded: false);
|
ExpandableController(initialExpanded: false);
|
||||||
|
|
||||||
AppViewDataContext({required this.viewId}) {
|
ViewDataContext({required this.viewId}) {
|
||||||
_setLatestView(getIt<MenuSharedState>().latestOpenView);
|
_setLatestView(getIt<MenuSharedState>().latestOpenView);
|
||||||
_menuSharedStateListener =
|
_menuSharedStateListener =
|
||||||
getIt<MenuSharedState>().addLatestViewListener((view) {
|
getIt<MenuSharedState>().addLatestViewListener((view) {
|
||||||
@ -207,7 +205,7 @@ class AppViewDataContext extends ChangeNotifier {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
VoidCallback addSelectedViewChangeListener(void Function(ViewPB?) callback) {
|
VoidCallback onViewSelected(void Function(ViewPB?) callback) {
|
||||||
listener() {
|
listener() {
|
||||||
callback(_selectedViewNotifier.value);
|
callback(_selectedViewNotifier.value);
|
||||||
}
|
}
|
||||||
@ -216,7 +214,7 @@ class AppViewDataContext extends ChangeNotifier {
|
|||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeSelectedViewListener(VoidCallback listener) {
|
void removeOnViewSelectedListener(VoidCallback listener) {
|
||||||
_selectedViewNotifier.removeListener(listener);
|
_selectedViewNotifier.removeListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,7 +233,6 @@ class AppViewDataContext extends ChangeNotifier {
|
|||||||
set views(List<ViewPB> views) {
|
set views(List<ViewPB> views) {
|
||||||
if (_viewsNotifier.value != views) {
|
if (_viewsNotifier.value != views) {
|
||||||
_viewsNotifier.value = views;
|
_viewsNotifier.value = views;
|
||||||
_expandIfNeed();
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -243,7 +240,7 @@ class AppViewDataContext extends ChangeNotifier {
|
|||||||
UnmodifiableListView<ViewPB> get views =>
|
UnmodifiableListView<ViewPB> get views =>
|
||||||
UnmodifiableListView(_viewsNotifier.value);
|
UnmodifiableListView(_viewsNotifier.value);
|
||||||
|
|
||||||
VoidCallback addViewsChangeListener(
|
VoidCallback onViewsChanged(
|
||||||
void Function(UnmodifiableListView<ViewPB>) callback,
|
void Function(UnmodifiableListView<ViewPB>) callback,
|
||||||
) {
|
) {
|
||||||
listener() {
|
listener() {
|
||||||
@ -254,7 +251,7 @@ class AppViewDataContext extends ChangeNotifier {
|
|||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeViewsListener(VoidCallback listener) {
|
void removeOnViewChangedListener(VoidCallback listener) {
|
||||||
_viewsNotifier.removeListener(listener);
|
_viewsNotifier.removeListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,7 +260,10 @@ class AppViewDataContext extends ChangeNotifier {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_viewsNotifier.value.contains(_selectedViewNotifier.value)) {
|
if (!_viewsNotifier.value
|
||||||
|
.map((e) => e.id)
|
||||||
|
.toList()
|
||||||
|
.contains(_selectedViewNotifier.value?.id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,61 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:appflowy/core/notification/folder_notification.dart';
|
|
||||||
import 'package:dartz/dartz.dart';
|
|
||||||
import 'package:appflowy_backend/log.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-notification/subject.pb.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/notification.pb.dart';
|
|
||||||
import 'package:appflowy_backend/rust_stream.dart';
|
|
||||||
|
|
||||||
typedef AppDidUpdateCallback = void Function(ViewPB app);
|
|
||||||
typedef ViewsDidChangeCallback = void Function(
|
|
||||||
Either<List<ViewPB>, FlowyError> viewsOrFailed,
|
|
||||||
);
|
|
||||||
|
|
||||||
class AppListener {
|
|
||||||
StreamSubscription<SubscribeObject>? _subscription;
|
|
||||||
AppDidUpdateCallback? _updated;
|
|
||||||
FolderNotificationParser? _parser;
|
|
||||||
String viewId;
|
|
||||||
|
|
||||||
AppListener({
|
|
||||||
required this.viewId,
|
|
||||||
});
|
|
||||||
|
|
||||||
void start({AppDidUpdateCallback? onAppUpdated}) {
|
|
||||||
_updated = onAppUpdated;
|
|
||||||
_parser = FolderNotificationParser(id: viewId, callback: _handleCallback);
|
|
||||||
_subscription =
|
|
||||||
RustStreamReceiver.listen((observable) => _parser?.parse(observable));
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleCallback(
|
|
||||||
FolderNotification ty,
|
|
||||||
Either<Uint8List, FlowyError> result,
|
|
||||||
) {
|
|
||||||
switch (ty) {
|
|
||||||
case FolderNotification.DidUpdateView:
|
|
||||||
case FolderNotification.DidUpdateChildViews:
|
|
||||||
if (_updated != null) {
|
|
||||||
result.fold(
|
|
||||||
(payload) {
|
|
||||||
final app = ViewPB.fromBuffer(payload);
|
|
||||||
_updated!(app);
|
|
||||||
},
|
|
||||||
(error) => Log.error(error),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> stop() async {
|
|
||||||
_parser = null;
|
|
||||||
await _subscription?.cancel();
|
|
||||||
_updated = null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +1 @@
|
|||||||
export 'app_bloc.dart';
|
export 'app_bloc.dart';
|
||||||
export 'app_listener.dart';
|
|
||||||
|
@ -12,65 +12,58 @@ part 'menu_view_section_bloc.freezed.dart';
|
|||||||
class ViewSectionBloc extends Bloc<ViewSectionEvent, ViewSectionState> {
|
class ViewSectionBloc extends Bloc<ViewSectionEvent, ViewSectionState> {
|
||||||
void Function()? _viewsListener;
|
void Function()? _viewsListener;
|
||||||
void Function()? _selectedViewlistener;
|
void Function()? _selectedViewlistener;
|
||||||
final AppViewDataContext _appViewData;
|
final ViewDataContext _appViewData;
|
||||||
|
|
||||||
ViewSectionBloc({
|
ViewSectionBloc({
|
||||||
required AppViewDataContext appViewData,
|
required ViewDataContext appViewData,
|
||||||
}) : _appViewData = appViewData,
|
}) : _appViewData = appViewData,
|
||||||
super(ViewSectionState.initial(appViewData)) {
|
super(ViewSectionState.initial(appViewData)) {
|
||||||
on<ViewSectionEvent>((event, emit) async {
|
on<ViewSectionEvent>((event, emit) async {
|
||||||
await event.map(
|
await event.when(
|
||||||
initial: (e) async {
|
initial: () async {
|
||||||
_startListening();
|
_startListening();
|
||||||
},
|
},
|
||||||
setSelectedView: (_SetSelectedView value) {
|
setSelectedView: (view) {
|
||||||
_setSelectView(value, emit);
|
emit(state.copyWith(selectedView: view));
|
||||||
},
|
},
|
||||||
didReceiveViewUpdated: (_DidReceiveViewUpdated value) {
|
didReceiveViewUpdated: (views) {
|
||||||
emit(state.copyWith(views: value.views));
|
emit(state.copyWith(views: views));
|
||||||
},
|
},
|
||||||
moveView: (_MoveView value) async {
|
moveView: (fromIndex, toIndex) async {
|
||||||
_moveView(value, emit);
|
_moveView(fromIndex, toIndex, emit);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _startListening() {
|
void _startListening() {
|
||||||
_viewsListener = _appViewData.addViewsChangeListener((views) {
|
_viewsListener = _appViewData.onViewsChanged((views) {
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
add(ViewSectionEvent.didReceiveViewUpdated(views));
|
add(ViewSectionEvent.didReceiveViewUpdated(views));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_selectedViewlistener = _appViewData.addSelectedViewChangeListener((view) {
|
_selectedViewlistener = _appViewData.onViewSelected((view) {
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
add(ViewSectionEvent.setSelectedView(view));
|
add(ViewSectionEvent.setSelectedView(view));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _setSelectView(_SetSelectedView value, Emitter<ViewSectionState> emit) {
|
|
||||||
if (state.views.contains(value.view)) {
|
|
||||||
emit(state.copyWith(selectedView: value.view));
|
|
||||||
} else {
|
|
||||||
emit(state.copyWith(selectedView: null));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _moveView(
|
Future<void> _moveView(
|
||||||
_MoveView value,
|
int fromIndex,
|
||||||
|
int toIndex,
|
||||||
Emitter<ViewSectionState> emit,
|
Emitter<ViewSectionState> emit,
|
||||||
) async {
|
) async {
|
||||||
if (value.fromIndex < state.views.length) {
|
if (fromIndex < state.views.length) {
|
||||||
final viewId = state.views[value.fromIndex].id;
|
final viewId = state.views[fromIndex].id;
|
||||||
final views = List<ViewPB>.from(state.views);
|
final views = List<ViewPB>.from(state.views);
|
||||||
views.insert(value.toIndex, views.removeAt(value.fromIndex));
|
views.insert(toIndex, views.removeAt(fromIndex));
|
||||||
emit(state.copyWith(views: views));
|
emit(state.copyWith(views: views));
|
||||||
|
|
||||||
final result = await ViewBackendService.moveView(
|
final result = await ViewBackendService.moveView(
|
||||||
viewId: viewId,
|
viewId: viewId,
|
||||||
fromIndex: value.fromIndex,
|
fromIndex: fromIndex,
|
||||||
toIndex: value.toIndex,
|
toIndex: toIndex,
|
||||||
);
|
);
|
||||||
result.fold((l) => null, (err) => Log.error(err));
|
result.fold((l) => null, (err) => Log.error(err));
|
||||||
}
|
}
|
||||||
@ -79,11 +72,11 @@ class ViewSectionBloc extends Bloc<ViewSectionEvent, ViewSectionState> {
|
|||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
if (_selectedViewlistener != null) {
|
if (_selectedViewlistener != null) {
|
||||||
_appViewData.removeSelectedViewListener(_selectedViewlistener!);
|
_appViewData.removeOnViewSelectedListener(_selectedViewlistener!);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_viewsListener != null) {
|
if (_viewsListener != null) {
|
||||||
_appViewData.removeViewsListener(_viewsListener!);
|
_appViewData.removeOnViewChangedListener(_viewsListener!);
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.close();
|
return super.close();
|
||||||
@ -108,7 +101,7 @@ class ViewSectionState with _$ViewSectionState {
|
|||||||
ViewPB? selectedView,
|
ViewPB? selectedView,
|
||||||
}) = _ViewSectionState;
|
}) = _ViewSectionState;
|
||||||
|
|
||||||
factory ViewSectionState.initial(AppViewDataContext appViewData) =>
|
factory ViewSectionState.initial(ViewDataContext appViewData) =>
|
||||||
ViewSectionState(
|
ViewSectionState(
|
||||||
views: appViewData.views,
|
views: appViewData.views,
|
||||||
selectedView: appViewData.selectedView,
|
selectedView: appViewData.selectedView,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:appflowy/plugins/database_view/board/board.dart';
|
import 'package:appflowy/plugins/database_view/board/presentation/board_page.dart';
|
||||||
import 'package:appflowy/plugins/database_view/calendar/calendar.dart';
|
import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/grid.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/tar_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';
|
||||||
import 'package:flowy_infra/image.dart';
|
import 'package:flowy_infra/image.dart';
|
||||||
@ -62,22 +63,11 @@ extension ViewExtension on ViewPB {
|
|||||||
Plugin plugin({bool listenOnViewChanged = false}) {
|
Plugin plugin({bool listenOnViewChanged = false}) {
|
||||||
switch (layout) {
|
switch (layout) {
|
||||||
case ViewLayoutPB.Board:
|
case ViewLayoutPB.Board:
|
||||||
return BoardPlugin(
|
|
||||||
view: this,
|
|
||||||
pluginType: pluginType,
|
|
||||||
listenOnViewChanged: listenOnViewChanged,
|
|
||||||
);
|
|
||||||
case ViewLayoutPB.Calendar:
|
case ViewLayoutPB.Calendar:
|
||||||
return CalendarPlugin(
|
|
||||||
view: this,
|
|
||||||
pluginType: pluginType,
|
|
||||||
listenOnViewChanged: listenOnViewChanged,
|
|
||||||
);
|
|
||||||
case ViewLayoutPB.Grid:
|
case ViewLayoutPB.Grid:
|
||||||
return GridPlugin(
|
return DatabaseTabBarViewPlugin(
|
||||||
view: this,
|
view: this,
|
||||||
pluginType: pluginType,
|
pluginType: pluginType,
|
||||||
listenOnViewChanged: listenOnViewChanged,
|
|
||||||
);
|
);
|
||||||
case ViewLayoutPB.Document:
|
case ViewLayoutPB.Document:
|
||||||
return DocumentPlugin(
|
return DocumentPlugin(
|
||||||
@ -88,4 +78,31 @@ extension ViewExtension on ViewPB {
|
|||||||
}
|
}
|
||||||
throw UnimplementedError;
|
throw UnimplementedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DatabaseTabBarItemBuilder tarBarItem() {
|
||||||
|
switch (layout) {
|
||||||
|
case ViewLayoutPB.Board:
|
||||||
|
return BoardPageTabBarBuilderImpl();
|
||||||
|
case ViewLayoutPB.Calendar:
|
||||||
|
return CalendarPageTabBarBuilderImpl();
|
||||||
|
case ViewLayoutPB.Grid:
|
||||||
|
return GridPageTabBarBuilderImpl();
|
||||||
|
case ViewLayoutPB.Document:
|
||||||
|
throw UnimplementedError;
|
||||||
|
}
|
||||||
|
throw UnimplementedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
String get iconName {
|
||||||
|
switch (layout) {
|
||||||
|
case ViewLayoutPB.Grid:
|
||||||
|
return 'editor/grid';
|
||||||
|
case ViewLayoutPB.Board:
|
||||||
|
return 'editor/board';
|
||||||
|
case ViewLayoutPB.Calendar:
|
||||||
|
return 'editor/calendar';
|
||||||
|
default:
|
||||||
|
throw Exception('Unknown layout type');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ typedef MoveToTrashNotifiedValue = Either<DeletedViewPB, FlowyError>;
|
|||||||
class ViewListener {
|
class ViewListener {
|
||||||
StreamSubscription<SubscribeObject>? _subscription;
|
StreamSubscription<SubscribeObject>? _subscription;
|
||||||
void Function(UpdateViewNotifiedValue)? _updatedViewNotifier;
|
void Function(UpdateViewNotifiedValue)? _updatedViewNotifier;
|
||||||
|
void Function(ChildViewUpdatePB)? _updateViewChildViewsNotifier;
|
||||||
void Function(DeleteViewNotifyValue)? _deletedNotifier;
|
void Function(DeleteViewNotifyValue)? _deletedNotifier;
|
||||||
void Function(RestoreViewNotifiedValue)? _restoredNotifier;
|
void Function(RestoreViewNotifiedValue)? _restoredNotifier;
|
||||||
void Function(MoveToTrashNotifiedValue)? _moveToTrashNotifier;
|
void Function(MoveToTrashNotifiedValue)? _moveToTrashNotifier;
|
||||||
@ -35,6 +36,7 @@ class ViewListener {
|
|||||||
|
|
||||||
void start({
|
void start({
|
||||||
void Function(UpdateViewNotifiedValue)? onViewUpdated,
|
void Function(UpdateViewNotifiedValue)? onViewUpdated,
|
||||||
|
void Function(ChildViewUpdatePB)? onViewChildViewsUpdated,
|
||||||
void Function(DeleteViewNotifyValue)? onViewDeleted,
|
void Function(DeleteViewNotifyValue)? onViewDeleted,
|
||||||
void Function(RestoreViewNotifiedValue)? onViewRestored,
|
void Function(RestoreViewNotifiedValue)? onViewRestored,
|
||||||
void Function(MoveToTrashNotifiedValue)? onViewMoveToTrash,
|
void Function(MoveToTrashNotifiedValue)? onViewMoveToTrash,
|
||||||
@ -48,6 +50,7 @@ class ViewListener {
|
|||||||
_deletedNotifier = onViewDeleted;
|
_deletedNotifier = onViewDeleted;
|
||||||
_restoredNotifier = onViewRestored;
|
_restoredNotifier = onViewRestored;
|
||||||
_moveToTrashNotifier = onViewMoveToTrash;
|
_moveToTrashNotifier = onViewMoveToTrash;
|
||||||
|
_updateViewChildViewsNotifier = onViewChildViewsUpdated;
|
||||||
|
|
||||||
_parser = FolderNotificationParser(
|
_parser = FolderNotificationParser(
|
||||||
id: viewId,
|
id: viewId,
|
||||||
@ -74,6 +77,15 @@ class ViewListener {
|
|||||||
(error) => Log.error(error),
|
(error) => Log.error(error),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case FolderNotification.DidUpdateChildViews:
|
||||||
|
result.fold(
|
||||||
|
(payload) {
|
||||||
|
final pb = ChildViewUpdatePB.fromBuffer(payload);
|
||||||
|
_updateViewChildViewsNotifier?.call(pb);
|
||||||
|
},
|
||||||
|
(error) => Log.error(error),
|
||||||
|
);
|
||||||
|
break;
|
||||||
case FolderNotification.DidDeleteView:
|
case FolderNotification.DidDeleteView:
|
||||||
result.fold(
|
result.fold(
|
||||||
(payload) => _deletedNotifier?.call(left(ViewPB.fromBuffer(payload))),
|
(payload) => _deletedNotifier?.call(left(ViewPB.fromBuffer(payload))),
|
||||||
|
@ -73,7 +73,7 @@ class ViewBackendService {
|
|||||||
return FolderEventCreateOrphanView(payload).send();
|
return FolderEventCreateOrphanView(payload).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<Either<ViewPB, FlowyError>> createDatabaseReferenceView({
|
static Future<Either<ViewPB, FlowyError>> createDatabaseLinkedView({
|
||||||
required String parentViewId,
|
required String parentViewId,
|
||||||
required String databaseId,
|
required String databaseId,
|
||||||
required ViewLayoutPB layoutType,
|
required ViewLayoutPB layoutType,
|
||||||
@ -91,14 +91,14 @@ class ViewBackendService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a list of views that are the children of the given [viewId].
|
/// Returns a list of views that are the children of the given [viewId].
|
||||||
static Future<Either<List<ViewPB>, FlowyError>> getViews({
|
static Future<Either<List<ViewPB>, FlowyError>> getChildViews({
|
||||||
required String viewId,
|
required String viewId,
|
||||||
}) {
|
}) {
|
||||||
final payload = ViewIdPB.create()..value = viewId;
|
final payload = ViewIdPB.create()..value = viewId;
|
||||||
|
|
||||||
return FolderEventReadView(payload).send().then((result) {
|
return FolderEventReadView(payload).send().then((result) {
|
||||||
return result.fold(
|
return result.fold(
|
||||||
(app) => left(app.childViews),
|
(view) => left(view.childViews),
|
||||||
(error) => right(error),
|
(error) => right(error),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -163,7 +163,7 @@ class ViewBackendService {
|
|||||||
if (workspaces != null) {
|
if (workspaces != null) {
|
||||||
final views = workspaces.workspace.views;
|
final views = workspaces.workspace.views;
|
||||||
for (final view in views) {
|
for (final view in views) {
|
||||||
final childViews = await getViews(viewId: view.id).then(
|
final childViews = await getChildViews(viewId: view.id).then(
|
||||||
(value) => value
|
(value) => value
|
||||||
.getLeftOrNull<List<ViewPB>>()
|
.getLeftOrNull<List<ViewPB>>()
|
||||||
?.where((e) => e.layout == layoutType)
|
?.where((e) => e.layout == layoutType)
|
||||||
|
@ -113,7 +113,7 @@ class MenuAppHeader extends StatelessWidget {
|
|||||||
context.read<AppBloc>().add(
|
context.read<AppBloc>().add(
|
||||||
AppEvent.createView(
|
AppEvent.createView(
|
||||||
name ?? LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
name ?? LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||||
pluginBuilder,
|
pluginBuilder.layoutType!,
|
||||||
initialDataBytes: initialDataBytes,
|
initialDataBytes: initialDataBytes,
|
||||||
openAfterCreated: openAfterCreated,
|
openAfterCreated: openAfterCreated,
|
||||||
),
|
),
|
||||||
|
@ -17,11 +17,11 @@ class MenuApp extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MenuAppState extends State<MenuApp> {
|
class _MenuAppState extends State<MenuApp> {
|
||||||
late AppViewDataContext viewDataContext;
|
late ViewDataContext viewDataContext;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
viewDataContext = AppViewDataContext(viewId: widget.view.id);
|
viewDataContext = ViewDataContext(viewId: widget.view.id);
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ class _MenuAppState extends State<MenuApp> {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return ChangeNotifierProvider.value(
|
return ChangeNotifierProvider.value(
|
||||||
value: viewDataContext,
|
value: viewDataContext,
|
||||||
child: Consumer<AppViewDataContext>(
|
child: Consumer<ViewDataContext>(
|
||||||
builder: (context, viewDataContext, _) {
|
builder: (context, viewDataContext, _) {
|
||||||
return expandableWrapper(context, viewDataContext);
|
return expandableWrapper(context, viewDataContext);
|
||||||
},
|
},
|
||||||
@ -70,7 +70,7 @@ class _MenuAppState extends State<MenuApp> {
|
|||||||
|
|
||||||
ExpandableNotifier expandableWrapper(
|
ExpandableNotifier expandableWrapper(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
AppViewDataContext viewDataContext,
|
ViewDataContext viewDataContext,
|
||||||
) {
|
) {
|
||||||
return ExpandableNotifier(
|
return ExpandableNotifier(
|
||||||
controller: viewDataContext.expandController,
|
controller: viewDataContext.expandController,
|
||||||
|
@ -24,12 +24,12 @@ class ViewSectionItem extends StatelessWidget {
|
|||||||
final ViewPB view;
|
final ViewPB view;
|
||||||
final void Function(ViewPB) onSelected;
|
final void Function(ViewPB) onSelected;
|
||||||
|
|
||||||
ViewSectionItem({
|
const ViewSectionItem({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.view,
|
required this.view,
|
||||||
required this.isSelected,
|
required this.isSelected,
|
||||||
required this.onSelected,
|
required this.onSelected,
|
||||||
}) : super(key: ValueKey('$view.hashCode/$isSelected'));
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -11,7 +11,7 @@ import 'package:reorderables/reorderables.dart';
|
|||||||
import 'item.dart';
|
import 'item.dart';
|
||||||
|
|
||||||
class ViewSection extends StatelessWidget {
|
class ViewSection extends StatelessWidget {
|
||||||
final AppViewDataContext appViewData;
|
final ViewDataContext appViewData;
|
||||||
const ViewSection({Key? key, required this.appViewData}) : super(key: key);
|
const ViewSection({Key? key, required this.appViewData}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -47,10 +47,11 @@ class ViewSection extends StatelessWidget {
|
|||||||
ViewSectionState state,
|
ViewSectionState state,
|
||||||
) {
|
) {
|
||||||
final children = state.views.map((view) {
|
final children = state.views.map((view) {
|
||||||
|
final isSelected = _isViewSelected(state, view.id);
|
||||||
return ViewSectionItem(
|
return ViewSectionItem(
|
||||||
key: ValueKey(view.id),
|
|
||||||
view: view,
|
view: view,
|
||||||
isSelected: _isViewSelected(state, view.id),
|
key: ValueKey('$view.hashCode/$isSelected'),
|
||||||
|
isSelected: isSelected,
|
||||||
onSelected: (view) => getIt<MenuSharedState>().latestOpenView = view,
|
onSelected: (view) => getIt<MenuSharedState>().latestOpenView = view,
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
|
|||||||
class FlowyButton extends StatelessWidget {
|
class FlowyButton extends StatelessWidget {
|
||||||
final Widget text;
|
final Widget text;
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
|
final VoidCallback? onSecondaryTap;
|
||||||
final void Function(bool)? onHover;
|
final void Function(bool)? onHover;
|
||||||
final EdgeInsets? margin;
|
final EdgeInsets? margin;
|
||||||
final Widget? leftIcon;
|
final Widget? leftIcon;
|
||||||
@ -25,6 +26,7 @@ class FlowyButton extends StatelessWidget {
|
|||||||
Key? key,
|
Key? key,
|
||||||
required this.text,
|
required this.text,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
|
this.onSecondaryTap,
|
||||||
this.onHover,
|
this.onHover,
|
||||||
this.margin,
|
this.margin,
|
||||||
this.leftIcon,
|
this.leftIcon,
|
||||||
@ -45,6 +47,7 @@ class FlowyButton extends StatelessWidget {
|
|||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
|
onSecondaryTap: onSecondaryTap,
|
||||||
child: FlowyHover(
|
child: FlowyHover(
|
||||||
style: HoverStyle(
|
style: HoverStyle(
|
||||||
borderRadius: radius ?? Corners.s6Border,
|
borderRadius: radius ?? Corners.s6Border,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/board/application/board_bloc.dart';
|
import 'package:appflowy/plugins/database_view/board/application/board_bloc.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
@ -12,8 +13,11 @@ void main() {
|
|||||||
|
|
||||||
test('create kanban baord card', () async {
|
test('create kanban baord card', () async {
|
||||||
final context = await boardTest.createTestBoard();
|
final context = await boardTest.createTestBoard();
|
||||||
final boardBloc = BoardBloc(view: context.gridView)
|
final databaseController = DatabaseController(view: context.gridView);
|
||||||
..add(const BoardEvent.initial());
|
final boardBloc = BoardBloc(
|
||||||
|
view: context.gridView,
|
||||||
|
databaseController: databaseController,
|
||||||
|
)..add(const BoardEvent.initial());
|
||||||
await boardResponseFuture();
|
await boardResponseFuture();
|
||||||
|
|
||||||
final groupId = boardBloc.state.groupIds.first;
|
final groupId = boardBloc.state.groupIds.first;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.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/board/application/board_bloc.dart';
|
import 'package:appflowy/plugins/database_view/board/application/board_bloc.dart';
|
||||||
@ -14,8 +15,10 @@ void main() {
|
|||||||
|
|
||||||
test('create build-in kanban board test', () async {
|
test('create build-in kanban board test', () async {
|
||||||
final context = await boardTest.createTestBoard();
|
final context = await boardTest.createTestBoard();
|
||||||
final boardBloc = BoardBloc(view: context.gridView)
|
final boardBloc = BoardBloc(
|
||||||
..add(const BoardEvent.initial());
|
view: context.gridView,
|
||||||
|
databaseController: DatabaseController(view: context.gridView),
|
||||||
|
)..add(const BoardEvent.initial());
|
||||||
await boardResponseFuture();
|
await boardResponseFuture();
|
||||||
|
|
||||||
assert(boardBloc.groupControllers.values.length == 4);
|
assert(boardBloc.groupControllers.values.length == 4);
|
||||||
@ -24,8 +27,10 @@ void main() {
|
|||||||
|
|
||||||
test('edit kanban board field name test', () async {
|
test('edit kanban board field name test', () async {
|
||||||
final context = await boardTest.createTestBoard();
|
final context = await boardTest.createTestBoard();
|
||||||
final boardBloc = BoardBloc(view: context.gridView)
|
final boardBloc = BoardBloc(
|
||||||
..add(const BoardEvent.initial());
|
view: context.gridView,
|
||||||
|
databaseController: DatabaseController(view: context.gridView),
|
||||||
|
)..add(const BoardEvent.initial());
|
||||||
await boardResponseFuture();
|
await boardResponseFuture();
|
||||||
|
|
||||||
final fieldInfo = context.singleSelectFieldContext();
|
final fieldInfo = context.singleSelectFieldContext();
|
||||||
@ -58,8 +63,10 @@ void main() {
|
|||||||
|
|
||||||
test('create a new field in kanban board test', () async {
|
test('create a new field in kanban board test', () async {
|
||||||
final context = await boardTest.createTestBoard();
|
final context = await boardTest.createTestBoard();
|
||||||
final boardBloc = BoardBloc(view: context.gridView)
|
final boardBloc = BoardBloc(
|
||||||
..add(const BoardEvent.initial());
|
view: context.gridView,
|
||||||
|
databaseController: DatabaseController(view: context.gridView),
|
||||||
|
)..add(const BoardEvent.initial());
|
||||||
await boardResponseFuture();
|
await boardResponseFuture();
|
||||||
|
|
||||||
await context.createField(FieldType.Checkbox);
|
await context.createField(FieldType.Checkbox);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/setting/group_bloc.dart';
|
import 'package:appflowy/plugins/database_view/application/setting/group_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/board/application/board_bloc.dart';
|
import 'package:appflowy/plugins/database_view/board/application/board_bloc.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||||
@ -15,8 +16,10 @@ void main() {
|
|||||||
// Group by checkbox field
|
// Group by checkbox field
|
||||||
test('group by checkbox field test', () async {
|
test('group by checkbox field test', () async {
|
||||||
final context = await boardTest.createTestBoard();
|
final context = await boardTest.createTestBoard();
|
||||||
final boardBloc = BoardBloc(view: context.gridView)
|
final boardBloc = BoardBloc(
|
||||||
..add(const BoardEvent.initial());
|
view: context.gridView,
|
||||||
|
databaseController: DatabaseController(view: context.gridView),
|
||||||
|
)..add(const BoardEvent.initial());
|
||||||
await boardResponseFuture();
|
await boardResponseFuture();
|
||||||
|
|
||||||
// assert the initial values
|
// assert the initial values
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
|
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
|
||||||
|
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/setting/group_bloc.dart';
|
import 'package:appflowy/plugins/database_view/application/setting/group_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/board/application/board_bloc.dart';
|
import 'package:appflowy/plugins/database_view/board/application/board_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor_bloc.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor_bloc.dart';
|
||||||
@ -39,8 +40,10 @@ void main() {
|
|||||||
await boardResponseFuture();
|
await boardResponseFuture();
|
||||||
|
|
||||||
//assert only have the 'No status' group
|
//assert only have the 'No status' group
|
||||||
final boardBloc = BoardBloc(view: context.gridView)
|
final boardBloc = BoardBloc(
|
||||||
..add(const BoardEvent.initial());
|
view: context.gridView,
|
||||||
|
databaseController: DatabaseController(view: context.gridView),
|
||||||
|
)..add(const BoardEvent.initial());
|
||||||
await boardResponseFuture();
|
await boardResponseFuture();
|
||||||
assert(
|
assert(
|
||||||
boardBloc.groupControllers.values.length == 1,
|
boardBloc.groupControllers.values.length == 1,
|
||||||
@ -92,8 +95,10 @@ void main() {
|
|||||||
await boardResponseFuture();
|
await boardResponseFuture();
|
||||||
|
|
||||||
// assert there are only three group
|
// assert there are only three group
|
||||||
final boardBloc = BoardBloc(view: context.gridView)
|
final boardBloc = BoardBloc(
|
||||||
..add(const BoardEvent.initial());
|
view: context.gridView,
|
||||||
|
databaseController: DatabaseController(view: context.gridView),
|
||||||
|
)..add(const BoardEvent.initial());
|
||||||
await boardResponseFuture();
|
await boardResponseFuture();
|
||||||
assert(
|
assert(
|
||||||
boardBloc.groupControllers.values.length == 3,
|
boardBloc.groupControllers.values.length == 3,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_editor_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/board/application/board_bloc.dart';
|
import 'package:appflowy/plugins/database_view/board/application/board_bloc.dart';
|
||||||
import 'package:bloc_test/bloc_test.dart';
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
@ -39,8 +40,12 @@ void main() {
|
|||||||
);
|
);
|
||||||
blocTest<BoardBloc, BoardState>(
|
blocTest<BoardBloc, BoardState>(
|
||||||
'assert the number of groups is 1',
|
'assert the number of groups is 1',
|
||||||
build: () =>
|
build: () => BoardBloc(
|
||||||
BoardBloc(view: context.gridView)..add(const BoardEvent.initial()),
|
view: context.gridView,
|
||||||
|
databaseController: DatabaseController(view: context.gridView),
|
||||||
|
)..add(
|
||||||
|
const BoardEvent.initial(),
|
||||||
|
),
|
||||||
wait: boardResponseDuration(),
|
wait: boardResponseDuration(),
|
||||||
verify: (bloc) {
|
verify: (bloc) {
|
||||||
assert(
|
assert(
|
||||||
|
@ -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/grid/grid.dart';
|
|
||||||
import 'package:appflowy/workspace/application/view/view_service.dart';
|
import 'package:appflowy/workspace/application/view/view_service.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pbenum.dart';
|
||||||
|
|
||||||
import '../util.dart';
|
import '../util.dart';
|
||||||
|
|
||||||
Future<GridTestContext> createTestFilterGrid(AppFlowyGridTest gridTest) async {
|
Future<GridTestContext> createTestFilterGrid(AppFlowyGridTest gridTest) async {
|
||||||
final app = await gridTest.unitTest.createTestApp();
|
final app = await gridTest.unitTest.createTestApp();
|
||||||
final builder = GridPluginBuilder();
|
|
||||||
final context = await ViewBackendService.createView(
|
final context = await ViewBackendService.createView(
|
||||||
parentViewId: app.id,
|
parentViewId: app.id,
|
||||||
name: "Filter Grid",
|
name: "Filter Grid",
|
||||||
layoutType: builder.layoutType!,
|
layoutType: ViewLayoutPB.Grid,
|
||||||
openAfterCreate: true,
|
openAfterCreate: true,
|
||||||
).then((result) {
|
).then((result) {
|
||||||
return result.fold(
|
return result.fold(
|
||||||
|
@ -9,7 +9,6 @@ import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
|
|||||||
import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
|
||||||
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/grid/application/row/row_bloc.dart';
|
import 'package:appflowy/plugins/database_view/grid/application/row/row_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/grid.dart';
|
|
||||||
import 'package:appflowy/workspace/application/view/view_service.dart';
|
import 'package:appflowy/workspace/application/view/view_service.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart';
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart';
|
||||||
@ -172,11 +171,10 @@ class AppFlowyGridTest {
|
|||||||
|
|
||||||
Future<GridTestContext> createTestGrid() async {
|
Future<GridTestContext> createTestGrid() async {
|
||||||
final app = await unitTest.createTestApp();
|
final app = await unitTest.createTestApp();
|
||||||
final builder = GridPluginBuilder();
|
|
||||||
final context = await ViewBackendService.createView(
|
final context = await ViewBackendService.createView(
|
||||||
parentViewId: app.id,
|
parentViewId: app.id,
|
||||||
name: "Test Grid",
|
name: "Test Grid",
|
||||||
layoutType: builder.layoutType!,
|
layoutType: ViewLayoutPB.Grid,
|
||||||
openAfterCreate: true,
|
openAfterCreate: true,
|
||||||
).then((result) {
|
).then((result) {
|
||||||
return result.fold(
|
return result.fold(
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import 'package:appflowy/plugins/database_view/grid/grid.dart';
|
|
||||||
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
|
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
|
||||||
import 'package:appflowy/plugins/document/document.dart';
|
|
||||||
import 'package:appflowy/workspace/application/app/app_bloc.dart';
|
import 'package:appflowy/workspace/application/app/app_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/menu/menu_view_section_bloc.dart';
|
import 'package:appflowy/workspace/application/menu/menu_view_section_bloc.dart';
|
||||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
import 'package:appflowy_backend/dispatch/dispatch.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';
|
||||||
import '../../util.dart';
|
import '../../util.dart';
|
||||||
|
|
||||||
@ -41,11 +40,11 @@ void main() {
|
|||||||
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
|
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
|
|
||||||
bloc.add(AppEvent.createView("1", DocumentPluginBuilder()));
|
bloc.add(const AppEvent.createView("1", ViewLayoutPB.Document));
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
bloc.add(AppEvent.createView("2", DocumentPluginBuilder()));
|
bloc.add(const AppEvent.createView("2", ViewLayoutPB.Document));
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
bloc.add(AppEvent.createView("3", DocumentPluginBuilder()));
|
bloc.add(const AppEvent.createView("3", ViewLayoutPB.Document));
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
|
|
||||||
assert(bloc.state.views[0].name == '1');
|
assert(bloc.state.views[0].name == '1');
|
||||||
@ -58,15 +57,15 @@ void main() {
|
|||||||
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
|
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
|
|
||||||
bloc.add(AppEvent.createView("1", DocumentPluginBuilder()));
|
bloc.add(const AppEvent.createView("1", ViewLayoutPB.Document));
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
bloc.add(AppEvent.createView("2", DocumentPluginBuilder()));
|
bloc.add(const AppEvent.createView("2", ViewLayoutPB.Document));
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
bloc.add(AppEvent.createView("3", DocumentPluginBuilder()));
|
bloc.add(const AppEvent.createView("3", ViewLayoutPB.Document));
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
assert(bloc.state.views.length == 3);
|
assert(bloc.state.views.length == 3);
|
||||||
|
|
||||||
final appViewData = AppViewDataContext(viewId: app.id);
|
final appViewData = ViewDataContext(viewId: app.id);
|
||||||
appViewData.views = bloc.state.views;
|
appViewData.views = bloc.state.views;
|
||||||
|
|
||||||
final viewSectionBloc = ViewSectionBloc(
|
final viewSectionBloc = ViewSectionBloc(
|
||||||
@ -91,14 +90,14 @@ void main() {
|
|||||||
"assert initial latest create view is null after initialize",
|
"assert initial latest create view is null after initialize",
|
||||||
);
|
);
|
||||||
|
|
||||||
bloc.add(AppEvent.createView("1", DocumentPluginBuilder()));
|
bloc.add(const AppEvent.createView("1", ViewLayoutPB.Document));
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
assert(
|
assert(
|
||||||
bloc.state.latestCreatedView!.id == bloc.state.views.last.id,
|
bloc.state.latestCreatedView!.id == bloc.state.views.last.id,
|
||||||
"create a view and assert the latest create view is this view",
|
"create a view and assert the latest create view is this view",
|
||||||
);
|
);
|
||||||
|
|
||||||
bloc.add(AppEvent.createView("2", DocumentPluginBuilder()));
|
bloc.add(const AppEvent.createView("2", ViewLayoutPB.Document));
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
assert(
|
assert(
|
||||||
bloc.state.latestCreatedView!.id == bloc.state.views.last.id,
|
bloc.state.latestCreatedView!.id == bloc.state.views.last.id,
|
||||||
@ -111,12 +110,12 @@ void main() {
|
|||||||
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
|
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
|
|
||||||
bloc.add(AppEvent.createView("document 1", DocumentPluginBuilder()));
|
bloc.add(const AppEvent.createView("document 1", ViewLayoutPB.Document));
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
final document1 = bloc.state.latestCreatedView;
|
final document1 = bloc.state.latestCreatedView;
|
||||||
assert(document1!.name == "document 1");
|
assert(document1!.name == "document 1");
|
||||||
|
|
||||||
bloc.add(AppEvent.createView("document 2", DocumentPluginBuilder()));
|
bloc.add(const AppEvent.createView("document 2", ViewLayoutPB.Document));
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
final document2 = bloc.state.latestCreatedView;
|
final document2 = bloc.state.latestCreatedView;
|
||||||
assert(document2!.name == "document 2");
|
assert(document2!.name == "document 2");
|
||||||
@ -138,12 +137,12 @@ void main() {
|
|||||||
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
|
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
|
|
||||||
bloc.add(AppEvent.createView("document 1", DocumentPluginBuilder()));
|
bloc.add(const AppEvent.createView("document 1", ViewLayoutPB.Document));
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
final document = bloc.state.latestCreatedView;
|
final document = bloc.state.latestCreatedView;
|
||||||
assert(document!.name == "document 1");
|
assert(document!.name == "document 1");
|
||||||
|
|
||||||
bloc.add(AppEvent.createView("grid 2", GridPluginBuilder()));
|
bloc.add(const AppEvent.createView("grid 2", ViewLayoutPB.Grid));
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
final grid = bloc.state.latestCreatedView;
|
final grid = bloc.state.latestCreatedView;
|
||||||
assert(grid!.name == "grid 2");
|
assert(grid!.name == "grid 2");
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
import 'package:appflowy/plugins/database_view/calendar/calendar.dart';
|
|
||||||
import 'package:appflowy/plugins/database_view/board/board.dart';
|
|
||||||
import 'package:appflowy/plugins/database_view/grid/grid.dart';
|
|
||||||
import 'package:appflowy/plugins/document/document.dart';
|
|
||||||
import 'package:appflowy/workspace/application/app/app_bloc.dart';
|
import 'package:appflowy/workspace/application/app/app_bloc.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,7 +14,7 @@ void main() {
|
|||||||
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
|
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
|
|
||||||
bloc.add(AppEvent.createView("Test document", DocumentPluginBuilder()));
|
bloc.add(const AppEvent.createView("Test document", ViewLayoutPB.Document));
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
|
|
||||||
assert(bloc.state.views.length == 1);
|
assert(bloc.state.views.length == 1);
|
||||||
@ -31,7 +27,7 @@ void main() {
|
|||||||
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
|
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
|
|
||||||
bloc.add(AppEvent.createView("Test grid", GridPluginBuilder()));
|
bloc.add(const AppEvent.createView("Test grid", ViewLayoutPB.Grid));
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
|
|
||||||
assert(bloc.state.views.length == 1);
|
assert(bloc.state.views.length == 1);
|
||||||
@ -44,7 +40,7 @@ void main() {
|
|||||||
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
|
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
|
|
||||||
bloc.add(AppEvent.createView("Test board", BoardPluginBuilder()));
|
bloc.add(const AppEvent.createView("Test board", ViewLayoutPB.Board));
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
|
|
||||||
assert(bloc.state.views.length == 1);
|
assert(bloc.state.views.length == 1);
|
||||||
@ -57,7 +53,7 @@ void main() {
|
|||||||
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
|
final bloc = AppBloc(view: app)..add(const AppEvent.initial());
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
|
|
||||||
bloc.add(AppEvent.createView("Test calendar", CalendarPluginBuilder()));
|
bloc.add(const AppEvent.createView("Test calendar", ViewLayoutPB.Calendar));
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
|
|
||||||
assert(bloc.state.views.length == 1);
|
assert(bloc.state.views.length == 1);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
|
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
|
||||||
import 'package:appflowy/plugins/document/document.dart';
|
|
||||||
import 'package:appflowy/workspace/application/app/app_bloc.dart';
|
import 'package:appflowy/workspace/application/app/app_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/home/home_bloc.dart';
|
import 'package:appflowy/workspace/application/home/home_bloc.dart';
|
||||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||||
@ -41,7 +40,8 @@ void main() {
|
|||||||
final appBloc = AppBloc(view: app)..add(const AppEvent.initial());
|
final appBloc = AppBloc(view: app)..add(const AppEvent.initial());
|
||||||
assert(appBloc.state.latestCreatedView == null);
|
assert(appBloc.state.latestCreatedView == null);
|
||||||
|
|
||||||
appBloc.add(AppEvent.createView("New document", DocumentPluginBuilder()));
|
appBloc
|
||||||
|
.add(const AppEvent.createView("New document", ViewLayoutPB.Document));
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
|
|
||||||
assert(appBloc.state.latestCreatedView != null);
|
assert(appBloc.state.latestCreatedView != null);
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:appflowy/plugins/document/document.dart';
|
|
||||||
import 'package:appflowy/plugins/trash/application/trash_bloc.dart';
|
import 'package:appflowy/plugins/trash/application/trash_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/app/app_bloc.dart';
|
import 'package:appflowy/workspace/application/app/app_bloc.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
@ -20,25 +19,25 @@ class TrashTestContext {
|
|||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
|
|
||||||
appBloc.add(
|
appBloc.add(
|
||||||
AppEvent.createView(
|
const AppEvent.createView(
|
||||||
"Document 1",
|
"Document 1",
|
||||||
DocumentPluginBuilder(),
|
ViewLayoutPB.Document,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
|
|
||||||
appBloc.add(
|
appBloc.add(
|
||||||
AppEvent.createView(
|
const AppEvent.createView(
|
||||||
"Document 2",
|
"Document 2",
|
||||||
DocumentPluginBuilder(),
|
ViewLayoutPB.Document,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
|
|
||||||
appBloc.add(
|
appBloc.add(
|
||||||
AppEvent.createView(
|
const AppEvent.createView(
|
||||||
"Document 3",
|
"Document 3",
|
||||||
DocumentPluginBuilder(),
|
ViewLayoutPB.Document,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import 'package:appflowy/plugins/document/document.dart';
|
|
||||||
import 'package:appflowy/workspace/application/app/app_bloc.dart';
|
import 'package:appflowy/workspace/application/app/app_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_bloc.dart';
|
import 'package:appflowy/workspace/application/view/view_bloc.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';
|
||||||
|
|
||||||
import '../../util.dart';
|
import '../../util.dart';
|
||||||
@ -16,10 +16,7 @@ void main() {
|
|||||||
|
|
||||||
final appBloc = AppBloc(view: app)..add(const AppEvent.initial());
|
final appBloc = AppBloc(view: app)..add(const AppEvent.initial());
|
||||||
appBloc.add(
|
appBloc.add(
|
||||||
AppEvent.createView(
|
const AppEvent.createView("Test document", ViewLayoutPB.Document),
|
||||||
"Test document",
|
|
||||||
DocumentPluginBuilder(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
@ -38,10 +35,7 @@ void main() {
|
|||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
|
|
||||||
appBloc.add(
|
appBloc.add(
|
||||||
AppEvent.createView(
|
const AppEvent.createView("Test document", ViewLayoutPB.Document),
|
||||||
"Test document",
|
|
||||||
DocumentPluginBuilder(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
|
|
||||||
@ -61,10 +55,7 @@ void main() {
|
|||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
|
|
||||||
appBloc.add(
|
appBloc.add(
|
||||||
AppEvent.createView(
|
const AppEvent.createView("Test document", ViewLayoutPB.Document),
|
||||||
"Test document",
|
|
||||||
DocumentPluginBuilder(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
await blocResponseFuture();
|
await blocResponseFuture();
|
||||||
expect(appBloc.state.views.length, 1);
|
expect(appBloc.state.views.length, 1);
|
||||||
|
@ -34,12 +34,12 @@ default = ["custom-protocol"]
|
|||||||
custom-protocol = ["tauri/custom-protocol"]
|
custom-protocol = ["tauri/custom-protocol"]
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e9f7fc" }
|
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "06e942" }
|
||||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e9f7fc" }
|
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "06e942" }
|
||||||
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e9f7fc" }
|
collab-persistence = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "06e942" }
|
||||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e9f7fc" }
|
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "06e942" }
|
||||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e9f7fc" }
|
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "06e942" }
|
||||||
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e9f7fc" }
|
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "06e942" }
|
||||||
|
|
||||||
#collab = { path = "../../AppFlowy-Collab/collab" }
|
#collab = { path = "../../AppFlowy-Collab/collab" }
|
||||||
#collab-folder = { path = "../../AppFlowy-Collab/collab-folder" }
|
#collab-folder = { path = "../../AppFlowy-Collab/collab-folder" }
|
||||||
|
20
frontend/rust-lib/Cargo.lock
generated
20
frontend/rust-lib/Cargo.lock
generated
@ -85,7 +85,7 @@ checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "appflowy-integrate"
|
name = "appflowy-integrate"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e9f7fc#e9f7fc76192f2dbb2379f1a02310047763bd4426"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=06e942#06e942cb6433c94b5ecfe1d431b64bba625fc09c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"collab",
|
"collab",
|
||||||
@ -887,7 +887,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab"
|
name = "collab"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e9f7fc#e9f7fc76192f2dbb2379f1a02310047763bd4426"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=06e942#06e942cb6433c94b5ecfe1d431b64bba625fc09c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -905,7 +905,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-client-ws"
|
name = "collab-client-ws"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e9f7fc#e9f7fc76192f2dbb2379f1a02310047763bd4426"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=06e942#06e942cb6433c94b5ecfe1d431b64bba625fc09c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"collab-sync",
|
"collab-sync",
|
||||||
@ -923,7 +923,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-database"
|
name = "collab-database"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e9f7fc#e9f7fc76192f2dbb2379f1a02310047763bd4426"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=06e942#06e942cb6433c94b5ecfe1d431b64bba625fc09c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -949,7 +949,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-derive"
|
name = "collab-derive"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e9f7fc#e9f7fc76192f2dbb2379f1a02310047763bd4426"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=06e942#06e942cb6433c94b5ecfe1d431b64bba625fc09c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -961,7 +961,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-document"
|
name = "collab-document"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e9f7fc#e9f7fc76192f2dbb2379f1a02310047763bd4426"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=06e942#06e942cb6433c94b5ecfe1d431b64bba625fc09c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"collab",
|
"collab",
|
||||||
@ -979,7 +979,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-folder"
|
name = "collab-folder"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e9f7fc#e9f7fc76192f2dbb2379f1a02310047763bd4426"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=06e942#06e942cb6433c94b5ecfe1d431b64bba625fc09c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -999,7 +999,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-persistence"
|
name = "collab-persistence"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e9f7fc#e9f7fc76192f2dbb2379f1a02310047763bd4426"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=06e942#06e942cb6433c94b5ecfe1d431b64bba625fc09c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode",
|
"bincode",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -1019,7 +1019,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-plugins"
|
name = "collab-plugins"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e9f7fc#e9f7fc76192f2dbb2379f1a02310047763bd4426"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=06e942#06e942cb6433c94b5ecfe1d431b64bba625fc09c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -1050,7 +1050,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "collab-sync"
|
name = "collab-sync"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=e9f7fc#e9f7fc76192f2dbb2379f1a02310047763bd4426"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=06e942#06e942cb6433c94b5ecfe1d431b64bba625fc09c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"collab",
|
"collab",
|
||||||
|
@ -33,11 +33,11 @@ opt-level = 3
|
|||||||
incremental = false
|
incremental = false
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e9f7fc" }
|
collab = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "06e942" }
|
||||||
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e9f7fc" }
|
collab-folder = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "06e942" }
|
||||||
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e9f7fc" }
|
collab-document = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "06e942" }
|
||||||
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e9f7fc" }
|
collab-database = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "06e942" }
|
||||||
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "e9f7fc" }
|
appflowy-integrate = { git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "06e942" }
|
||||||
|
|
||||||
#collab = { path = "../AppFlowy-Collab/collab" }
|
#collab = { path = "../AppFlowy-Collab/collab" }
|
||||||
#collab-folder = { path = "../AppFlowy-Collab/collab-folder" }
|
#collab-folder = { path = "../AppFlowy-Collab/collab-folder" }
|
||||||
|
@ -276,7 +276,7 @@ impl FolderOperationHandler for DatabaseFolderOperation {
|
|||||||
|
|
||||||
FutureResult::new(async move {
|
FutureResult::new(async move {
|
||||||
database_manager
|
database_manager
|
||||||
.create_linked_view(name, layout, params.database_id, database_view_id)
|
.create_linked_view(name, layout.into(), params.database_id, database_view_id)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
@ -110,6 +110,9 @@ pub struct CalendarEventPB {
|
|||||||
|
|
||||||
#[pb(index = 4)]
|
#[pb(index = 4)]
|
||||||
pub timestamp: i64,
|
pub timestamp: i64,
|
||||||
|
|
||||||
|
#[pb(index = 5)]
|
||||||
|
pub is_scheduled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, ProtoBuf)]
|
#[derive(Debug, Clone, Default, ProtoBuf)]
|
||||||
|
@ -23,6 +23,9 @@ pub struct DatabasePB {
|
|||||||
|
|
||||||
#[pb(index = 4)]
|
#[pb(index = 4)]
|
||||||
pub layout_type: DatabaseLayoutPB,
|
pub layout_type: DatabaseLayoutPB,
|
||||||
|
|
||||||
|
#[pb(index = 5)]
|
||||||
|
pub is_linked: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(ProtoBuf, Default)]
|
#[derive(ProtoBuf, Default)]
|
||||||
|
@ -7,7 +7,7 @@ use appflowy_integrate::{CollabPersistenceConfig, RocksCollabDB};
|
|||||||
use collab::core::collab::MutexCollab;
|
use collab::core::collab::MutexCollab;
|
||||||
use collab_database::database::DatabaseData;
|
use collab_database::database::DatabaseData;
|
||||||
use collab_database::user::{DatabaseCollabBuilder, UserDatabase as InnerUserDatabase};
|
use collab_database::user::{DatabaseCollabBuilder, UserDatabase as InnerUserDatabase};
|
||||||
use collab_database::views::{CreateDatabaseParams, CreateViewParams};
|
use collab_database::views::{CreateDatabaseParams, CreateViewParams, DatabaseLayout};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
@ -16,6 +16,7 @@ use flowy_task::TaskDispatcher;
|
|||||||
|
|
||||||
use crate::entities::{DatabaseDescriptionPB, DatabaseLayoutPB, RepeatedDatabaseDescriptionPB};
|
use crate::entities::{DatabaseDescriptionPB, DatabaseLayoutPB, RepeatedDatabaseDescriptionPB};
|
||||||
use crate::services::database::{DatabaseEditor, MutexDatabase};
|
use crate::services::database::{DatabaseEditor, MutexDatabase};
|
||||||
|
use crate::services::database_view::DatabaseLayoutDepsResolver;
|
||||||
use crate::services::share::csv::{CSVFormat, CSVImporter, ImportResult};
|
use crate::services::share::csv::{CSVFormat, CSVImporter, ImportResult};
|
||||||
|
|
||||||
pub trait DatabaseUser2: Send + Sync {
|
pub trait DatabaseUser2: Send + Sync {
|
||||||
@ -179,18 +180,28 @@ impl DatabaseManager2 {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A linked view is a view that is linked to existing database.
|
||||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||||
pub async fn create_linked_view(
|
pub async fn create_linked_view(
|
||||||
&self,
|
&self,
|
||||||
name: String,
|
name: String,
|
||||||
layout: DatabaseLayoutPB,
|
layout: DatabaseLayout,
|
||||||
database_id: String,
|
database_id: String,
|
||||||
database_view_id: String,
|
database_view_id: String,
|
||||||
) -> FlowyResult<()> {
|
) -> FlowyResult<()> {
|
||||||
self.with_user_database(
|
self.with_user_database(
|
||||||
Err(FlowyError::internal().context("Create database view failed")),
|
Err(FlowyError::internal().context("Create database view failed")),
|
||||||
|user_database| {
|
|user_database| {
|
||||||
let params = CreateViewParams::new(database_id, database_view_id, name, layout.into());
|
let mut params = CreateViewParams::new(database_id.clone(), database_view_id, name, layout);
|
||||||
|
if let Some(database) = user_database.get_database(&database_id) {
|
||||||
|
if let Some((field, layout_setting)) = DatabaseLayoutDepsResolver::new(database, layout)
|
||||||
|
.resolve_deps_when_create_database_linked_view()
|
||||||
|
{
|
||||||
|
params = params
|
||||||
|
.with_deps_fields(vec![field])
|
||||||
|
.with_layout_setting(layout_setting);
|
||||||
|
}
|
||||||
|
};
|
||||||
user_database.create_database_linked_view(params)?;
|
user_database.create_database_linked_view(params)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
|
@ -206,7 +206,7 @@ impl DatabaseEditor {
|
|||||||
.map(|field| field.id)
|
.map(|field| field.id)
|
||||||
.collect()
|
.collect()
|
||||||
});
|
});
|
||||||
database.get_fields(view_id, Some(field_ids))
|
database.get_fields_in_view(view_id, Some(field_ids))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_field(&self, params: FieldChangesetParams) -> FlowyResult<()> {
|
pub async fn update_field(&self, params: FieldChangesetParams) -> FlowyResult<()> {
|
||||||
@ -442,7 +442,7 @@ impl DatabaseEditor {
|
|||||||
self
|
self
|
||||||
.database
|
.database
|
||||||
.lock()
|
.lock()
|
||||||
.create_default_field(view_id, name, field_type.into(), |field| {
|
.create_field_with_mut(view_id, name, field_type.into(), |field| {
|
||||||
field
|
field
|
||||||
.type_options
|
.type_options
|
||||||
.insert(field_type.to_string(), type_option_data.clone());
|
.insert(field_type.to_string(), type_option_data.clone());
|
||||||
@ -932,11 +932,12 @@ impl DatabaseEditor {
|
|||||||
|
|
||||||
pub async fn group_by_field(&self, view_id: &str, field_id: &str) -> FlowyResult<()> {
|
pub async fn group_by_field(&self, view_id: &str, field_id: &str) -> FlowyResult<()> {
|
||||||
let view = self.database_views.get_view_editor(view_id).await?;
|
let view = self.database_views.get_view_editor(view_id).await?;
|
||||||
view.v_update_grouping_field(field_id).await?;
|
view.v_grouping_by_field(field_id).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_layout_setting(&self, view_id: &str, layout_setting: LayoutSettingParams) {
|
pub async fn set_layout_setting(&self, view_id: &str, layout_setting: LayoutSettingParams) {
|
||||||
|
tracing::trace!("set_layout_setting: {:?}", layout_setting);
|
||||||
if let Ok(view) = self.database_views.get_view_editor(view_id).await {
|
if let Ok(view) = self.database_views.get_view_editor(view_id).await {
|
||||||
let _ = view.v_set_layout_settings(layout_setting).await;
|
let _ = view.v_set_layout_settings(layout_setting).await;
|
||||||
};
|
};
|
||||||
@ -1042,7 +1043,7 @@ impl DatabaseEditor {
|
|||||||
.await
|
.await
|
||||||
.ok_or_else(FlowyError::record_not_found)?;
|
.ok_or_else(FlowyError::record_not_found)?;
|
||||||
let rows = database_view.v_get_rows().await;
|
let rows = database_view.v_get_rows().await;
|
||||||
let (database_id, fields) = {
|
let (database_id, fields, is_linked) = {
|
||||||
let database = self.database.lock();
|
let database = self.database.lock();
|
||||||
let database_id = database.get_database_id();
|
let database_id = database.get_database_id();
|
||||||
let fields = database
|
let fields = database
|
||||||
@ -1051,7 +1052,8 @@ impl DatabaseEditor {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(FieldIdPB::from)
|
.map(FieldIdPB::from)
|
||||||
.collect();
|
.collect();
|
||||||
(database_id, fields)
|
let is_linked = database.is_inline_view(view_id);
|
||||||
|
(database_id, fields, is_linked)
|
||||||
};
|
};
|
||||||
|
|
||||||
let rows = rows
|
let rows = rows
|
||||||
@ -1063,6 +1065,7 @@ impl DatabaseEditor {
|
|||||||
fields,
|
fields,
|
||||||
rows,
|
rows,
|
||||||
layout_type: view.layout.into(),
|
layout_type: view.layout.into(),
|
||||||
|
is_linked,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1082,7 +1085,7 @@ impl DatabaseEditor {
|
|||||||
self
|
self
|
||||||
.database
|
.database
|
||||||
.lock()
|
.lock()
|
||||||
.get_fields(view_id, None)
|
.get_fields_in_view(view_id, None)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|f| FieldType::from(f.field_type).is_auto_update())
|
.filter(|f| FieldType::from(f.field_type).is_auto_update())
|
||||||
.collect::<Vec<Field>>()
|
.collect::<Vec<Field>>()
|
||||||
@ -1139,13 +1142,17 @@ struct DatabaseViewDataImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DatabaseViewData for DatabaseViewDataImpl {
|
impl DatabaseViewData for DatabaseViewDataImpl {
|
||||||
|
fn get_database(&self) -> Arc<InnerDatabase> {
|
||||||
|
self.database.lock().clone()
|
||||||
|
}
|
||||||
|
|
||||||
fn get_view(&self, view_id: &str) -> Fut<Option<DatabaseView>> {
|
fn get_view(&self, view_id: &str) -> Fut<Option<DatabaseView>> {
|
||||||
let view = self.database.lock().get_view(view_id);
|
let view = self.database.lock().get_view(view_id);
|
||||||
to_fut(async move { view })
|
to_fut(async move { view })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<Field>>> {
|
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<Field>>> {
|
||||||
let fields = self.database.lock().get_fields(view_id, field_ids);
|
let fields = self.database.lock().get_fields_in_view(view_id, field_ids);
|
||||||
to_fut(async move { fields.into_iter().map(Arc::new).collect() })
|
to_fut(async move { fields.into_iter().map(Arc::new).collect() })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1166,7 +1173,7 @@ impl DatabaseViewData for DatabaseViewDataImpl {
|
|||||||
field_type: FieldType,
|
field_type: FieldType,
|
||||||
type_option_data: TypeOptionData,
|
type_option_data: TypeOptionData,
|
||||||
) -> Fut<Field> {
|
) -> Fut<Field> {
|
||||||
let (_, field) = self.database.lock().create_default_field(
|
let (_, field) = self.database.lock().create_field_with_mut(
|
||||||
view_id,
|
view_id,
|
||||||
name.to_string(),
|
name.to_string(),
|
||||||
field_type.clone().into(),
|
field_type.clone().into(),
|
||||||
|
@ -0,0 +1,115 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use collab_database::database::{gen_field_id, Database};
|
||||||
|
use collab_database::fields::Field;
|
||||||
|
use collab_database::views::{DatabaseLayout, LayoutSetting};
|
||||||
|
|
||||||
|
use crate::entities::FieldType;
|
||||||
|
use crate::services::field::DateTypeOption;
|
||||||
|
use crate::services::setting::CalendarLayoutSetting;
|
||||||
|
|
||||||
|
/// When creating a database, we need to resolve the dependencies of the views. Different database
|
||||||
|
/// view has different dependencies. For example, a calendar view depends on a date field.
|
||||||
|
pub struct DatabaseLayoutDepsResolver {
|
||||||
|
pub database: Arc<Database>,
|
||||||
|
/// The new database layout.
|
||||||
|
pub database_layout: DatabaseLayout,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DatabaseLayoutDepsResolver {
|
||||||
|
pub fn new(database: Arc<Database>, database_layout: DatabaseLayout) -> Self {
|
||||||
|
Self {
|
||||||
|
database,
|
||||||
|
database_layout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve_deps_when_create_database_linked_view(&self) -> Option<(Field, LayoutSetting)> {
|
||||||
|
match self.database_layout {
|
||||||
|
DatabaseLayout::Grid => None,
|
||||||
|
DatabaseLayout::Board => None,
|
||||||
|
DatabaseLayout::Calendar => {
|
||||||
|
let field = self.create_date_field();
|
||||||
|
let layout_setting: LayoutSetting = CalendarLayoutSetting::new(field.id.clone()).into();
|
||||||
|
Some((field, layout_setting))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the new layout type is a calendar and there is not date field in the database, it will add
|
||||||
|
/// a new date field to the database and create the corresponding layout setting.
|
||||||
|
pub fn resolve_deps_when_update_layout_type(&self, view_id: &str) {
|
||||||
|
let fields = self.database.get_fields(None);
|
||||||
|
// Insert the layout setting if it's not exist
|
||||||
|
match &self.database_layout {
|
||||||
|
DatabaseLayout::Grid => {},
|
||||||
|
DatabaseLayout::Board => {},
|
||||||
|
DatabaseLayout::Calendar => {
|
||||||
|
let date_field_id = match fields
|
||||||
|
.into_iter()
|
||||||
|
.find(|field| FieldType::from(field.field_type) == FieldType::DateTime)
|
||||||
|
{
|
||||||
|
None => {
|
||||||
|
tracing::trace!("Create a new date field after layout type change");
|
||||||
|
let field = self.create_date_field();
|
||||||
|
let field_id = field.id.clone();
|
||||||
|
self.database.create_field(field);
|
||||||
|
field_id
|
||||||
|
},
|
||||||
|
Some(date_field) => date_field.id,
|
||||||
|
};
|
||||||
|
self.create_calendar_layout_setting_if_need(view_id, &date_field_id);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_calendar_layout_setting_if_need(&self, view_id: &str, field_id: &str) {
|
||||||
|
if self
|
||||||
|
.database
|
||||||
|
.get_layout_setting::<CalendarLayoutSetting>(view_id, &self.database_layout)
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
let layout_setting = CalendarLayoutSetting::new(field_id.to_string());
|
||||||
|
self
|
||||||
|
.database
|
||||||
|
.insert_layout_setting(view_id, &self.database_layout, layout_setting);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_date_field(&self) -> Field {
|
||||||
|
let field_type = FieldType::DateTime;
|
||||||
|
let default_date_type_option = DateTypeOption::default();
|
||||||
|
let field_id = gen_field_id();
|
||||||
|
Field::new(
|
||||||
|
field_id,
|
||||||
|
"Date".to_string(),
|
||||||
|
field_type.clone().into(),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.with_type_option_data(field_type, default_date_type_option.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub async fn v_get_layout_settings(&self, layout_ty: &DatabaseLayout) -> LayoutSettingParams {
|
||||||
|
// let mut layout_setting = LayoutSettingParams::default();
|
||||||
|
// match layout_ty {
|
||||||
|
// DatabaseLayout::Grid => {},
|
||||||
|
// DatabaseLayout::Board => {},
|
||||||
|
// DatabaseLayout::Calendar => {
|
||||||
|
// if let Some(value) = self.delegate.get_layout_setting(&self.view_id, layout_ty) {
|
||||||
|
// let calendar_setting = CalendarLayoutSetting::from(value);
|
||||||
|
// // Check the field exist or not
|
||||||
|
// if let Some(field) = self.delegate.get_field(&calendar_setting.field_id).await {
|
||||||
|
// let field_type = FieldType::from(field.field_type);
|
||||||
|
//
|
||||||
|
// // Check the type of field is Datetime or not
|
||||||
|
// if field_type == FieldType::DateTime {
|
||||||
|
// layout_setting.calendar = Some(calendar_setting);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// layout_setting
|
||||||
|
// }
|
@ -1,3 +1,9 @@
|
|||||||
|
pub use layout_deps::*;
|
||||||
|
pub use notifier::*;
|
||||||
|
pub use view_editor::*;
|
||||||
|
pub use views::*;
|
||||||
|
|
||||||
|
mod layout_deps;
|
||||||
mod notifier;
|
mod notifier;
|
||||||
mod view_editor;
|
mod view_editor;
|
||||||
mod view_filter;
|
mod view_filter;
|
||||||
@ -5,7 +11,3 @@ mod view_group;
|
|||||||
mod view_sort;
|
mod view_sort;
|
||||||
mod views;
|
mod views;
|
||||||
// mod trait_impl;
|
// mod trait_impl;
|
||||||
|
|
||||||
pub use notifier::*;
|
|
||||||
pub use view_editor::*;
|
|
||||||
pub use views::*;
|
|
||||||
|
@ -2,7 +2,7 @@ use std::borrow::Cow;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use collab_database::database::{gen_database_filter_id, gen_database_sort_id};
|
use collab_database::database::{gen_database_filter_id, gen_database_sort_id, Database};
|
||||||
use collab_database::fields::{Field, TypeOptionData};
|
use collab_database::fields::{Field, TypeOptionData};
|
||||||
use collab_database::rows::{Cells, Row, RowCell, RowId, RowMeta};
|
use collab_database::rows::{Cells, Row, RowCell, RowId, RowMeta};
|
||||||
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting};
|
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting};
|
||||||
@ -30,10 +30,10 @@ use crate::services::database_view::view_group::{
|
|||||||
use crate::services::database_view::view_sort::make_sort_controller;
|
use crate::services::database_view::view_sort::make_sort_controller;
|
||||||
use crate::services::database_view::{
|
use crate::services::database_view::{
|
||||||
notify_did_update_filter, notify_did_update_group_rows, notify_did_update_num_of_groups,
|
notify_did_update_filter, notify_did_update_group_rows, notify_did_update_num_of_groups,
|
||||||
notify_did_update_setting, notify_did_update_sort, DatabaseViewChangedNotifier,
|
notify_did_update_setting, notify_did_update_sort, DatabaseLayoutDepsResolver,
|
||||||
DatabaseViewChangedReceiverRunner,
|
DatabaseViewChangedNotifier, DatabaseViewChangedReceiverRunner,
|
||||||
};
|
};
|
||||||
use crate::services::field::{DateTypeOption, TypeOptionCellDataHandler};
|
use crate::services::field::TypeOptionCellDataHandler;
|
||||||
use crate::services::filter::{
|
use crate::services::filter::{
|
||||||
Filter, FilterChangeset, FilterController, FilterType, UpdatedFilterType,
|
Filter, FilterChangeset, FilterController, FilterType, UpdatedFilterType,
|
||||||
};
|
};
|
||||||
@ -44,6 +44,8 @@ use crate::services::setting::CalendarLayoutSetting;
|
|||||||
use crate::services::sort::{DeletedSortType, Sort, SortChangeset, SortController, SortType};
|
use crate::services::sort::{DeletedSortType, Sort, SortChangeset, SortController, SortType};
|
||||||
|
|
||||||
pub trait DatabaseViewData: Send + Sync + 'static {
|
pub trait DatabaseViewData: Send + Sync + 'static {
|
||||||
|
fn get_database(&self) -> Arc<Database>;
|
||||||
|
|
||||||
fn get_view(&self, view_id: &str) -> Fut<Option<DatabaseView>>;
|
fn get_view(&self, view_id: &str) -> Fut<Option<DatabaseView>>;
|
||||||
/// If the field_ids is None, then it will return all the field revisions
|
/// If the field_ids is None, then it will return all the field revisions
|
||||||
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<Field>>>;
|
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<Field>>>;
|
||||||
@ -438,7 +440,7 @@ impl DatabaseViewEditor {
|
|||||||
pub async fn v_initialize_new_group(&self, field_id: &str) -> FlowyResult<()> {
|
pub async fn v_initialize_new_group(&self, field_id: &str) -> FlowyResult<()> {
|
||||||
let is_grouping_field = self.is_grouping_field(field_id).await;
|
let is_grouping_field = self.is_grouping_field(field_id).await;
|
||||||
if !is_grouping_field {
|
if !is_grouping_field {
|
||||||
self.v_update_grouping_field(field_id).await?;
|
self.v_grouping_by_field(field_id).await?;
|
||||||
|
|
||||||
if let Some(view) = self.delegate.get_view(&self.view_id).await {
|
if let Some(view) = self.delegate.get_view(&self.view_id).await {
|
||||||
let setting = database_view_setting_pb_from_view(view);
|
let setting = database_view_setting_pb_from_view(view);
|
||||||
@ -607,7 +609,11 @@ impl DatabaseViewEditor {
|
|||||||
// Check the type of field is Datetime or not
|
// Check the type of field is Datetime or not
|
||||||
if field_type == FieldType::DateTime {
|
if field_type == FieldType::DateTime {
|
||||||
layout_setting.calendar = Some(calendar_setting);
|
layout_setting.calendar = Some(calendar_setting);
|
||||||
|
} else {
|
||||||
|
tracing::warn!("The field of calendar setting is not datetime type")
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
tracing::warn!("The field of calendar setting is not exist");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -713,7 +719,7 @@ impl DatabaseViewEditor {
|
|||||||
|
|
||||||
/// Called when a grouping field is updated.
|
/// Called when a grouping field is updated.
|
||||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||||
pub async fn v_update_grouping_field(&self, field_id: &str) -> FlowyResult<()> {
|
pub async fn v_grouping_by_field(&self, field_id: &str) -> FlowyResult<()> {
|
||||||
if let Some(field) = self.delegate.get_field(field_id).await {
|
if let Some(field) = self.delegate.get_field(field_id).await {
|
||||||
let new_group_controller =
|
let new_group_controller =
|
||||||
new_group_controller_with_field(self.view_id.clone(), self.delegate.clone(), field).await?;
|
new_group_controller_with_field(self.view_id.clone(), self.delegate.clone(), field).await?;
|
||||||
@ -770,12 +776,23 @@ impl DatabaseViewEditor {
|
|||||||
date_field_id: date_field.id.clone(),
|
date_field_id: date_field.id.clone(),
|
||||||
title,
|
title,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
is_scheduled: timestamp != 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn v_get_all_calendar_events(&self) -> Option<Vec<CalendarEventPB>> {
|
pub async fn v_get_all_calendar_events(&self) -> Option<Vec<CalendarEventPB>> {
|
||||||
let layout_ty = DatabaseLayout::Calendar;
|
let layout_ty = DatabaseLayout::Calendar;
|
||||||
let calendar_setting = self.v_get_layout_settings(&layout_ty).await.calendar?;
|
let calendar_setting = match self.v_get_layout_settings(&layout_ty).await.calendar {
|
||||||
|
None => {
|
||||||
|
// When create a new calendar view, the calendar setting should be created
|
||||||
|
tracing::error!(
|
||||||
|
"Calendar layout setting not found in database view:{}",
|
||||||
|
self.view_id
|
||||||
|
);
|
||||||
|
return None;
|
||||||
|
},
|
||||||
|
Some(calendar_setting) => calendar_setting,
|
||||||
|
};
|
||||||
|
|
||||||
// Text
|
// Text
|
||||||
let primary_field = self.delegate.get_primary_field().await?;
|
let primary_field = self.delegate.get_primary_field().await?;
|
||||||
@ -822,6 +839,7 @@ impl DatabaseViewEditor {
|
|||||||
date_field_id: calendar_setting.field_id.clone(),
|
date_field_id: calendar_setting.field_id.clone(),
|
||||||
title,
|
title,
|
||||||
timestamp,
|
timestamp,
|
||||||
|
is_scheduled: timestamp != 0,
|
||||||
};
|
};
|
||||||
events.push(event);
|
events.push(event);
|
||||||
}
|
}
|
||||||
@ -829,57 +847,25 @@ impl DatabaseViewEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip_all)]
|
#[tracing::instrument(level = "trace", skip_all)]
|
||||||
pub async fn v_update_layout_type(&self, layout_type: DatabaseLayout) -> FlowyResult<()> {
|
pub async fn v_update_layout_type(&self, new_layout_type: DatabaseLayout) -> FlowyResult<()> {
|
||||||
self
|
self
|
||||||
.delegate
|
.delegate
|
||||||
.update_layout_type(&self.view_id, &layout_type);
|
.update_layout_type(&self.view_id, &new_layout_type);
|
||||||
|
|
||||||
// Update the layout type in the database might add a new field to the database. If the new
|
// using the {} brackets to denote the lifetime of the resolver. Because the DatabaseLayoutDepsResolver
|
||||||
// layout type is a calendar and there is not date field in the database, it will add a new
|
// is not sync and send, so we can't pass it to the async block.
|
||||||
// date field to the database and create the corresponding layout setting.
|
|
||||||
//
|
|
||||||
let fields = self.delegate.get_fields(&self.view_id, None).await;
|
|
||||||
let date_field_id = match fields
|
|
||||||
.into_iter()
|
|
||||||
.find(|field| FieldType::from(field.field_type) == FieldType::DateTime)
|
|
||||||
{
|
{
|
||||||
None => {
|
let resolver = DatabaseLayoutDepsResolver::new(self.delegate.get_database(), new_layout_type);
|
||||||
tracing::trace!("Create a new date field after layout type change");
|
resolver.resolve_deps_when_update_layout_type(&self.view_id);
|
||||||
let default_date_type_option = DateTypeOption::default();
|
}
|
||||||
let field = self
|
|
||||||
.delegate
|
|
||||||
.create_field(
|
|
||||||
&self.view_id,
|
|
||||||
"Date",
|
|
||||||
FieldType::DateTime,
|
|
||||||
default_date_type_option.into(),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
field.id
|
|
||||||
},
|
|
||||||
Some(date_field) => date_field.id.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let layout_setting = self.v_get_layout_settings(&layout_type).await;
|
// initialize the group controller if the current layout support grouping
|
||||||
match layout_type {
|
*self.group_controller.write().await =
|
||||||
DatabaseLayout::Grid => {},
|
new_group_controller(self.view_id.clone(), self.delegate.clone()).await?;
|
||||||
DatabaseLayout::Board => {},
|
|
||||||
DatabaseLayout::Calendar => {
|
|
||||||
if layout_setting.calendar.is_none() {
|
|
||||||
let layout_setting = CalendarLayoutSetting::new(date_field_id.clone());
|
|
||||||
self
|
|
||||||
.v_set_layout_settings(LayoutSettingParams {
|
|
||||||
layout_type,
|
|
||||||
calendar: Some(layout_setting),
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
let payload = DatabaseLayoutMetaPB {
|
let payload = DatabaseLayoutMetaPB {
|
||||||
view_id: self.view_id.clone(),
|
view_id: self.view_id.clone(),
|
||||||
layout: layout_type.into(),
|
layout: new_layout_type.into(),
|
||||||
};
|
};
|
||||||
send_notification(&self.view_id, DatabaseNotification::DidUpdateDatabaseLayout)
|
send_notification(&self.view_id, DatabaseNotification::DidUpdateDatabaseLayout)
|
||||||
.payload(payload)
|
.payload(payload)
|
||||||
|
@ -94,7 +94,7 @@ impl DatabaseViews {
|
|||||||
// If the id of the grouping field is equal to the updated field's id, then we need to
|
// If the id of the grouping field is equal to the updated field's id, then we need to
|
||||||
// update the group setting
|
// update the group setting
|
||||||
if view_editor.is_grouping_field(field_id).await {
|
if view_editor.is_grouping_field(field_id).await {
|
||||||
view_editor.v_update_grouping_field(field_id).await?;
|
view_editor.v_grouping_by_field(field_id).await?;
|
||||||
}
|
}
|
||||||
view_editor
|
view_editor
|
||||||
.v_did_update_field_type_option(field_id, old_field)
|
.v_did_update_field_type_option(field_id, old_field)
|
||||||
|
@ -1,18 +1,21 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::Formatter;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use collab_database::fields::Field;
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use flowy_error::{FlowyError, FlowyResult};
|
||||||
|
use lib_infra::future::Fut;
|
||||||
|
|
||||||
use crate::entities::{GroupChangesPB, GroupPB, InsertedGroupPB};
|
use crate::entities::{GroupChangesPB, GroupPB, InsertedGroupPB};
|
||||||
use crate::services::field::RowSingleCellData;
|
use crate::services::field::RowSingleCellData;
|
||||||
use crate::services::group::{
|
use crate::services::group::{
|
||||||
default_group_setting, GeneratedGroups, Group, GroupChangeset, GroupData, GroupSetting,
|
default_group_setting, GeneratedGroups, Group, GroupChangeset, GroupData, GroupSetting,
|
||||||
};
|
};
|
||||||
use collab_database::fields::Field;
|
|
||||||
use flowy_error::{FlowyError, FlowyResult};
|
|
||||||
use indexmap::IndexMap;
|
|
||||||
use lib_infra::future::Fut;
|
|
||||||
use serde::de::DeserializeOwned;
|
|
||||||
use serde::Serialize;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::Formatter;
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub trait GroupSettingReader: Send + Sync + 'static {
|
pub trait GroupSettingReader: Send + Sync + 'static {
|
||||||
fn get_group_setting(&self, view_id: &str) -> Fut<Option<Arc<GroupSetting>>>;
|
fn get_group_setting(&self, view_id: &str) -> Fut<Option<Arc<GroupSetting>>>;
|
||||||
@ -361,10 +364,10 @@ where
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
if let Some(group) = update_group {
|
if let Some(group) = update_group {
|
||||||
self.group_by_id.get_mut(&group.id).map(|group_data| {
|
if let Some(group_data) = self.group_by_id.get_mut(&group.id) {
|
||||||
group_data.name = group.name.clone();
|
group_data.name = group.name.clone();
|
||||||
group_data.is_visible = group.visible;
|
group_data.is_visible = group.visible;
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use crate::entities::FieldType;
|
|
||||||
use crate::services::cell::stringify_cell_data;
|
|
||||||
use collab_database::database::Database;
|
use collab_database::database::Database;
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
|
||||||
use flowy_error::{FlowyError, FlowyResult};
|
use flowy_error::{FlowyError, FlowyResult};
|
||||||
use indexmap::IndexMap;
|
|
||||||
|
use crate::entities::FieldType;
|
||||||
|
use crate::services::cell::stringify_cell_data;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum CSVFormat {
|
pub enum CSVFormat {
|
||||||
@ -20,7 +21,7 @@ impl CSVExport {
|
|||||||
pub fn export_database(&self, database: &Database, style: CSVFormat) -> FlowyResult<String> {
|
pub fn export_database(&self, database: &Database, style: CSVFormat) -> FlowyResult<String> {
|
||||||
let mut wtr = csv::Writer::from_writer(vec![]);
|
let mut wtr = csv::Writer::from_writer(vec![]);
|
||||||
let inline_view_id = database.get_inline_view_id();
|
let inline_view_id = database.get_inline_view_id();
|
||||||
let fields = database.get_fields(&inline_view_id, None);
|
let fields = database.get_fields_in_view(&inline_view_id, None);
|
||||||
|
|
||||||
// Write fields
|
// Write fields
|
||||||
let field_records = fields
|
let field_records = fields
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
use crate::document::util::{default_collab_builder, gen_document_id, gen_id, FakeUser};
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use collab_document::blocks::{Block, BlockAction, BlockActionPayload, BlockActionType};
|
use collab_document::blocks::{Block, BlockAction, BlockActionPayload, BlockActionType};
|
||||||
|
|
||||||
use flowy_document2::document_block_keys::PARAGRAPH_BLOCK_TYPE;
|
use flowy_document2::document_block_keys::PARAGRAPH_BLOCK_TYPE;
|
||||||
use flowy_document2::document_data::default_document_data;
|
use flowy_document2::document_data::default_document_data;
|
||||||
use flowy_document2::manager::DocumentManager;
|
use flowy_document2::manager::DocumentManager;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::Arc;
|
use crate::document::util::{default_collab_builder, gen_document_id, gen_id, FakeUser};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn undo_redo_test() {
|
async fn undo_redo_test() {
|
||||||
@ -38,22 +41,22 @@ async fn undo_redo_test() {
|
|||||||
action: BlockActionType::Insert,
|
action: BlockActionType::Insert,
|
||||||
payload: BlockActionPayload {
|
payload: BlockActionPayload {
|
||||||
block: text_block,
|
block: text_block,
|
||||||
parent_id: Some(page_id.clone()),
|
parent_id: Some(page_id),
|
||||||
prev_id: None,
|
prev_id: None,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
document.apply_action(vec![insert_text_action]);
|
document.apply_action(vec![insert_text_action]);
|
||||||
|
|
||||||
let can_undo = document.can_undo();
|
let can_undo = document.can_undo();
|
||||||
assert_eq!(can_undo, true);
|
assert!(can_undo);
|
||||||
// undo the insert
|
// undo the insert
|
||||||
let undo = document.undo();
|
let undo = document.undo();
|
||||||
assert_eq!(undo, true);
|
assert!(undo);
|
||||||
assert_eq!(document.get_block(&text_block_id), None);
|
assert_eq!(document.get_block(&text_block_id), None);
|
||||||
|
|
||||||
let can_redo = document.can_redo();
|
let can_redo = document.can_redo();
|
||||||
assert!(can_redo);
|
assert!(can_redo);
|
||||||
// redo the insert
|
// redo the insert
|
||||||
let redo = document.redo();
|
let redo = document.redo();
|
||||||
assert_eq!(redo, true);
|
assert!(redo);
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,21 @@ use flowy_error::ErrorCode;
|
|||||||
use crate::entities::parser::view::{ViewDesc, ViewIdentify, ViewName, ViewThumbnail};
|
use crate::entities::parser::view::{ViewDesc, ViewIdentify, ViewName, ViewThumbnail};
|
||||||
use crate::view_operation::gen_view_id;
|
use crate::view_operation::gen_view_id;
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||||
|
pub struct ChildViewUpdatePB {
|
||||||
|
#[pb(index = 1)]
|
||||||
|
pub parent_view_id: String,
|
||||||
|
|
||||||
|
#[pb(index = 2)]
|
||||||
|
pub create_child_views: Vec<ViewPB>,
|
||||||
|
|
||||||
|
#[pb(index = 3)]
|
||||||
|
pub delete_child_views: Vec<String>,
|
||||||
|
|
||||||
|
#[pb(index = 4)]
|
||||||
|
pub update_child_views: Vec<ViewPB>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||||
pub struct ViewPB {
|
pub struct ViewPB {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
|
@ -18,8 +18,9 @@ use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
|||||||
|
|
||||||
use crate::deps::{FolderCloudService, FolderUser};
|
use crate::deps::{FolderCloudService, FolderUser};
|
||||||
use crate::entities::{
|
use crate::entities::{
|
||||||
view_pb_with_child_views, CreateViewParams, CreateWorkspaceParams, DeletedViewPB,
|
view_pb_with_child_views, view_pb_without_child_views, ChildViewUpdatePB, CreateViewParams,
|
||||||
RepeatedTrashPB, RepeatedViewPB, RepeatedWorkspacePB, UpdateViewParams, ViewPB, WorkspacePB,
|
CreateWorkspaceParams, DeletedViewPB, RepeatedTrashPB, RepeatedViewPB, RepeatedWorkspacePB,
|
||||||
|
UpdateViewParams, ViewPB, WorkspacePB,
|
||||||
};
|
};
|
||||||
use crate::notification::{
|
use crate::notification::{
|
||||||
send_notification, send_workspace_notification, send_workspace_setting_notification,
|
send_notification, send_workspace_notification, send_workspace_setting_notification,
|
||||||
@ -257,7 +258,6 @@ impl Folder2Manager {
|
|||||||
folder.insert_view(view.clone());
|
folder.insert_view(view.clone());
|
||||||
});
|
});
|
||||||
|
|
||||||
notify_parent_view_did_change(self.mutex_folder.clone(), vec![view.parent_view_id.clone()]);
|
|
||||||
Ok(view)
|
Ok(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,6 +332,7 @@ impl Folder2Manager {
|
|||||||
#[tracing::instrument(level = "debug", skip(self), err)]
|
#[tracing::instrument(level = "debug", skip(self), err)]
|
||||||
pub async fn move_view_to_trash(&self, view_id: &str) -> FlowyResult<()> {
|
pub async fn move_view_to_trash(&self, view_id: &str) -> FlowyResult<()> {
|
||||||
self.with_folder((), |folder| {
|
self.with_folder((), |folder| {
|
||||||
|
let view = folder.views.get_view(view_id);
|
||||||
folder.add_trash(vec![view_id.to_string()]);
|
folder.add_trash(vec![view_id.to_string()]);
|
||||||
|
|
||||||
// notify the parent view that the view is moved to trash
|
// notify the parent view that the view is moved to trash
|
||||||
@ -341,6 +342,13 @@ impl Folder2Manager {
|
|||||||
index: None,
|
index: None,
|
||||||
})
|
})
|
||||||
.send();
|
.send();
|
||||||
|
|
||||||
|
if let Some(view) = view {
|
||||||
|
notify_child_views_changed(
|
||||||
|
view_pb_without_child_views(view),
|
||||||
|
ChildViewChangeReason::DidDeleteView,
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -415,6 +423,7 @@ impl Folder2Manager {
|
|||||||
.set_cover_url_if_not_none(params.cover_url)
|
.set_cover_url_if_not_none(params.cover_url)
|
||||||
.done()
|
.done()
|
||||||
});
|
});
|
||||||
|
|
||||||
Some((old_view, new_view))
|
Some((old_view, new_view))
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -632,10 +641,25 @@ fn listen_on_view_change(mut rx: ViewChangeReceiver, weak_mutex_folder: &Weak<Mu
|
|||||||
tracing::trace!("Did receive view change: {:?}", value);
|
tracing::trace!("Did receive view change: {:?}", value);
|
||||||
match value {
|
match value {
|
||||||
ViewChange::DidCreateView { view } => {
|
ViewChange::DidCreateView { view } => {
|
||||||
|
notify_child_views_changed(
|
||||||
|
view_pb_without_child_views(view.clone()),
|
||||||
|
ChildViewChangeReason::DidCreateView,
|
||||||
|
);
|
||||||
notify_parent_view_did_change(folder.clone(), vec![view.parent_view_id]);
|
notify_parent_view_did_change(folder.clone(), vec![view.parent_view_id]);
|
||||||
},
|
},
|
||||||
ViewChange::DidDeleteView { views: _ } => {},
|
ViewChange::DidDeleteView { views } => {
|
||||||
|
for view in views {
|
||||||
|
notify_child_views_changed(
|
||||||
|
view_pb_without_child_views(view),
|
||||||
|
ChildViewChangeReason::DidDeleteView,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
ViewChange::DidUpdate { view } => {
|
ViewChange::DidUpdate { view } => {
|
||||||
|
notify_child_views_changed(
|
||||||
|
view_pb_without_child_views(view.clone()),
|
||||||
|
ChildViewChangeReason::DidUpdateView,
|
||||||
|
);
|
||||||
notify_parent_view_did_change(folder.clone(), vec![view.parent_view_id]);
|
notify_parent_view_did_change(folder.clone(), vec![view.parent_view_id]);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -764,7 +788,7 @@ fn notify_parent_view_did_change<T: AsRef<str>>(
|
|||||||
|
|
||||||
// Post the notification
|
// Post the notification
|
||||||
let parent_view_pb = view_pb_with_child_views(parent_view, child_views);
|
let parent_view_pb = view_pb_with_child_views(parent_view, child_views);
|
||||||
send_notification(parent_view_id, FolderNotification::DidUpdateChildViews)
|
send_notification(parent_view_id, FolderNotification::DidUpdateView)
|
||||||
.payload(parent_view_pb)
|
.payload(parent_view_pb)
|
||||||
.send();
|
.send();
|
||||||
}
|
}
|
||||||
@ -773,6 +797,38 @@ fn notify_parent_view_did_change<T: AsRef<str>>(
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum ChildViewChangeReason {
|
||||||
|
DidCreateView,
|
||||||
|
DidDeleteView,
|
||||||
|
DidUpdateView,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Notify the the list of parent view ids that its child views were changed.
|
||||||
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
|
fn notify_child_views_changed(view_pb: ViewPB, reason: ChildViewChangeReason) {
|
||||||
|
let parent_view_id = view_pb.parent_view_id.clone();
|
||||||
|
let mut payload = ChildViewUpdatePB {
|
||||||
|
parent_view_id: view_pb.parent_view_id.clone(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
match reason {
|
||||||
|
ChildViewChangeReason::DidCreateView => {
|
||||||
|
payload.create_child_views.push(view_pb);
|
||||||
|
},
|
||||||
|
ChildViewChangeReason::DidDeleteView => {
|
||||||
|
payload.delete_child_views.push(view_pb.id);
|
||||||
|
},
|
||||||
|
ChildViewChangeReason::DidUpdateView => {
|
||||||
|
payload.update_child_views.push(view_pb);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
send_notification(&parent_view_id, FolderNotification::DidUpdateChildViews)
|
||||||
|
.payload(payload)
|
||||||
|
.send();
|
||||||
|
}
|
||||||
|
|
||||||
fn folder_not_init_error() -> FlowyError {
|
fn folder_not_init_error() -> FlowyError {
|
||||||
FlowyError::internal().context("Folder not initialized")
|
FlowyError::internal().context("Folder not initialized")
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
use crate::entities::{view_pb_without_child_views, WorkspacePB, WorkspaceSettingPB};
|
|
||||||
use collab_folder::core::{View, Workspace};
|
use collab_folder::core::{View, Workspace};
|
||||||
|
|
||||||
use flowy_derive::ProtoBuf_Enum;
|
use flowy_derive::ProtoBuf_Enum;
|
||||||
use flowy_notification::NotificationBuilder;
|
use flowy_notification::NotificationBuilder;
|
||||||
use lib_dispatch::prelude::ToBytes;
|
use lib_dispatch::prelude::ToBytes;
|
||||||
|
|
||||||
|
use crate::entities::{view_pb_without_child_views, WorkspacePB, WorkspaceSettingPB};
|
||||||
|
|
||||||
const OBSERVABLE_CATEGORY: &str = "Workspace";
|
const OBSERVABLE_CATEGORY: &str = "Workspace";
|
||||||
|
|
||||||
#[derive(ProtoBuf_Enum, Debug, Default)]
|
#[derive(ProtoBuf_Enum, Debug, Default)]
|
||||||
@ -18,9 +20,7 @@ pub(crate) enum FolderNotification {
|
|||||||
DidUpdateWorkspaceViews = 3,
|
DidUpdateWorkspaceViews = 3,
|
||||||
/// Trigger when the settings of the workspace are changed. The changes including the latest visiting view, etc
|
/// Trigger when the settings of the workspace are changed. The changes including the latest visiting view, etc
|
||||||
DidUpdateWorkspaceSetting = 4,
|
DidUpdateWorkspaceSetting = 4,
|
||||||
|
|
||||||
DidUpdateView = 29,
|
DidUpdateView = 29,
|
||||||
/// Trigger when the properties including rename,update description of the view are changed
|
|
||||||
DidUpdateChildViews = 30,
|
DidUpdateChildViews = 30,
|
||||||
/// Trigger after deleting the view
|
/// Trigger after deleting the view
|
||||||
DidDeleteView = 31,
|
DidDeleteView = 31,
|
||||||
|
@ -41,7 +41,7 @@ pub fn gen_insert_block_action(document: OpenDocumentData) -> BlockActionPB {
|
|||||||
let data = block.data.clone();
|
let data = block.data.clone();
|
||||||
let new_block_id = gen_id();
|
let new_block_id = gen_id();
|
||||||
let new_block = BlockPB {
|
let new_block = BlockPB {
|
||||||
id: new_block_id.clone(),
|
id: new_block_id,
|
||||||
ty: block.ty.clone(),
|
ty: block.ty.clone(),
|
||||||
data,
|
data,
|
||||||
parent_id: page_id.clone(),
|
parent_id: page_id.clone(),
|
||||||
|
Loading…
Reference in New Issue
Block a user