mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: tabs shortcuts (#3112)
This commit is contained in:
parent
923285bfcf
commit
35a47bfe5d
@ -50,7 +50,7 @@ void main() {
|
|||||||
await tester.tapFirstDayOfWeekStartFromMonday();
|
await tester.tapFirstDayOfWeekStartFromMonday();
|
||||||
|
|
||||||
// Open the other page and open the new calendar page again
|
// Open the other page and open the new calendar page again
|
||||||
await tester.openPage(gettingStated);
|
await tester.openPage(gettingStarted);
|
||||||
await tester.pumpAndSettle(const Duration(milliseconds: 300));
|
await tester.pumpAndSettle(const Duration(milliseconds: 300));
|
||||||
await tester.openPage(name, layout: ViewLayoutPB.Calendar);
|
await tester.openPage(name, layout: ViewLayoutPB.Calendar);
|
||||||
|
|
||||||
|
@ -34,20 +34,20 @@ void main() {
|
|||||||
|
|
||||||
// delete the readme page
|
// delete the readme page
|
||||||
await tester.hoverOnPageName(
|
await tester.hoverOnPageName(
|
||||||
gettingStated,
|
gettingStarted,
|
||||||
onHover: () async => await tester.tapDeletePageButton(),
|
onHover: () async => await tester.tapDeletePageButton(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// the banner should show up and the readme page should be gone
|
// the banner should show up and the readme page should be gone
|
||||||
tester.expectToSeeDocumentBanner();
|
tester.expectToSeeDocumentBanner();
|
||||||
tester.expectNotToSeePageName(gettingStated);
|
tester.expectNotToSeePageName(gettingStarted);
|
||||||
|
|
||||||
// restore the readme page
|
// restore the readme page
|
||||||
await tester.tapRestoreButton();
|
await tester.tapRestoreButton();
|
||||||
|
|
||||||
// the banner should be gone and the readme page should be back
|
// the banner should be gone and the readme page should be back
|
||||||
tester.expectNotToSeeDocumentBanner();
|
tester.expectNotToSeeDocumentBanner();
|
||||||
tester.expectToSeePageName(gettingStated);
|
tester.expectToSeePageName(gettingStarted);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('delete the readme page and delete it permanently',
|
testWidgets('delete the readme page and delete it permanently',
|
||||||
@ -58,20 +58,20 @@ void main() {
|
|||||||
|
|
||||||
// delete the readme page
|
// delete the readme page
|
||||||
await tester.hoverOnPageName(
|
await tester.hoverOnPageName(
|
||||||
gettingStated,
|
gettingStarted,
|
||||||
onHover: () async => await tester.tapDeletePageButton(),
|
onHover: () async => await tester.tapDeletePageButton(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// the banner should show up and the readme page should be gone
|
// the banner should show up and the readme page should be gone
|
||||||
tester.expectToSeeDocumentBanner();
|
tester.expectToSeeDocumentBanner();
|
||||||
tester.expectNotToSeePageName(gettingStated);
|
tester.expectNotToSeePageName(gettingStarted);
|
||||||
|
|
||||||
// delete the page permanently
|
// delete the page permanently
|
||||||
await tester.tapDeletePermanentlyButton();
|
await tester.tapDeletePermanentlyButton();
|
||||||
|
|
||||||
// the banner should be gone and the readme page should be gone
|
// the banner should be gone and the readme page should be gone
|
||||||
tester.expectNotToSeeDocumentBanner();
|
tester.expectNotToSeeDocumentBanner();
|
||||||
tester.expectNotToSeePageName(gettingStated);
|
tester.expectNotToSeePageName(gettingStarted);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// switch to other page and switch back
|
// switch to other page and switch back
|
||||||
await tester.openPage(gettingStated);
|
await tester.openPage(gettingStarted);
|
||||||
await tester.openPage(pageName);
|
await tester.openPage(pageName);
|
||||||
|
|
||||||
// the numbered list should be kept
|
// the numbered list should be kept
|
||||||
@ -91,7 +91,7 @@ void main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// switch to other page and switch back
|
// switch to other page and switch back
|
||||||
await tester.openPage(gettingStated);
|
await tester.openPage(gettingStarted);
|
||||||
await tester.openPage(pageName);
|
await tester.openPage(pageName);
|
||||||
|
|
||||||
// this screenshots are different on different platform, so comment it out temporarily.
|
// this screenshots are different on different platform, so comment it out temporarily.
|
||||||
|
@ -17,7 +17,7 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
// expect to see a getting started page
|
// expect to see a getting started page
|
||||||
tester.expectToSeePageName(gettingStated);
|
tester.expectToSeePageName(gettingStarted);
|
||||||
|
|
||||||
await tester.tapAddViewButton();
|
await tester.tapAddViewButton();
|
||||||
await tester.tapImportButton();
|
await tester.tapImportButton();
|
||||||
|
@ -16,6 +16,7 @@ import 'share_markdown_test.dart' as share_markdown_test;
|
|||||||
import 'switch_folder_test.dart' as switch_folder_test;
|
import 'switch_folder_test.dart' as switch_folder_test;
|
||||||
import 'sidebar/sidebar_test_runner.dart' as sidebar_test_runner;
|
import 'sidebar/sidebar_test_runner.dart' as sidebar_test_runner;
|
||||||
import 'board/board_test_runner.dart' as board_test_runner;
|
import 'board/board_test_runner.dart' as board_test_runner;
|
||||||
|
import 'tabs_test.dart' as tabs_test;
|
||||||
|
|
||||||
/// The main task runner for all integration tests in AppFlowy.
|
/// The main task runner for all integration tests in AppFlowy.
|
||||||
///
|
///
|
||||||
@ -51,6 +52,9 @@ void main() {
|
|||||||
database_view_test.main();
|
database_view_test.main();
|
||||||
database_calendar_test.main();
|
database_calendar_test.main();
|
||||||
|
|
||||||
|
// Tabs
|
||||||
|
tabs_test.main();
|
||||||
|
|
||||||
// board_test.main();
|
// board_test.main();
|
||||||
// empty_document_test.main();
|
// empty_document_test.main();
|
||||||
// smart_menu_test.main();
|
// smart_menu_test.main();
|
||||||
|
@ -16,7 +16,7 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
// expect to see a readme page
|
// expect to see a readme page
|
||||||
tester.expectToSeePageName(gettingStated);
|
tester.expectToSeePageName(gettingStarted);
|
||||||
|
|
||||||
// mock the file picker
|
// mock the file picker
|
||||||
final path = await mockSaveFilePath(
|
final path = await mockSaveFilePath(
|
||||||
@ -43,11 +43,11 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
// expect to see a getting started page
|
// expect to see a getting started page
|
||||||
tester.expectToSeePageName(gettingStated);
|
tester.expectToSeePageName(gettingStarted);
|
||||||
|
|
||||||
// rename the document
|
// rename the document
|
||||||
await tester.hoverOnPageName(
|
await tester.hoverOnPageName(
|
||||||
gettingStated,
|
gettingStarted,
|
||||||
onHover: () async {
|
onHover: () async {
|
||||||
await tester.renamePage('example');
|
await tester.renamePage('example');
|
||||||
},
|
},
|
||||||
|
@ -30,7 +30,7 @@ void main() {
|
|||||||
2,
|
2,
|
||||||
].map((e) => 'document_$e').toList();
|
].map((e) => 'document_$e').toList();
|
||||||
for (var i = 0; i < names.length; i++) {
|
for (var i = 0; i < names.length; i++) {
|
||||||
final parentName = i == 0 ? gettingStated : names[i - 1];
|
final parentName = i == 0 ? gettingStarted : names[i - 1];
|
||||||
await tester.createNewPageWithName(
|
await tester.createNewPageWithName(
|
||||||
name: names[i],
|
name: names[i],
|
||||||
parentName: parentName,
|
parentName: parentName,
|
||||||
@ -44,9 +44,9 @@ void main() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await tester.favoriteViewByName(gettingStated);
|
await tester.favoriteViewByName(gettingStarted);
|
||||||
expect(
|
expect(
|
||||||
tester.findFavoritePageName(gettingStated),
|
tester.findFavoritePageName(gettingStarted),
|
||||||
findsOneWidget,
|
findsOneWidget,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -56,9 +56,9 @@ void main() {
|
|||||||
findsNWidgets(2),
|
findsNWidgets(2),
|
||||||
);
|
);
|
||||||
|
|
||||||
await tester.unfavoriteViewByName(gettingStated);
|
await tester.unfavoriteViewByName(gettingStarted);
|
||||||
expect(
|
expect(
|
||||||
tester.findFavoritePageName(gettingStated),
|
tester.findFavoritePageName(gettingStarted),
|
||||||
findsNothing,
|
findsNothing,
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
@ -84,9 +84,9 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
const name = 'test';
|
const name = 'test';
|
||||||
await tester.favoriteViewByName(gettingStated);
|
await tester.favoriteViewByName(gettingStarted);
|
||||||
await tester.hoverOnPageName(
|
await tester.hoverOnPageName(
|
||||||
gettingStated,
|
gettingStarted,
|
||||||
layout: ViewLayoutPB.Document,
|
layout: ViewLayoutPB.Document,
|
||||||
onHover: () async {
|
onHover: () async {
|
||||||
await tester.renamePage(name);
|
await tester.renamePage(name);
|
||||||
@ -112,7 +112,7 @@ void main() {
|
|||||||
|
|
||||||
final names = [1, 2].map((e) => 'document_$e').toList();
|
final names = [1, 2].map((e) => 'document_$e').toList();
|
||||||
for (var i = 0; i < names.length; i++) {
|
for (var i = 0; i < names.length; i++) {
|
||||||
final parentName = i == 0 ? gettingStated : names[i - 1];
|
final parentName = i == 0 ? gettingStarted : names[i - 1];
|
||||||
await tester.createNewPageWithName(
|
await tester.createNewPageWithName(
|
||||||
name: names[i],
|
name: names[i],
|
||||||
parentName: parentName,
|
parentName: parentName,
|
||||||
@ -120,7 +120,7 @@ void main() {
|
|||||||
);
|
);
|
||||||
tester.expectToSeePageName(names[i], parentName: parentName);
|
tester.expectToSeePageName(names[i], parentName: parentName);
|
||||||
}
|
}
|
||||||
await tester.favoriteViewByName(gettingStated);
|
await tester.favoriteViewByName(gettingStarted);
|
||||||
await tester.favoriteViewByName(names[0]);
|
await tester.favoriteViewByName(names[0]);
|
||||||
await tester.favoriteViewByName(names[1]);
|
await tester.favoriteViewByName(names[1]);
|
||||||
|
|
||||||
@ -154,7 +154,7 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await tester.hoverOnPageName(
|
await tester.hoverOnPageName(
|
||||||
gettingStated,
|
gettingStarted,
|
||||||
layout: ViewLayoutPB.Document,
|
layout: ViewLayoutPB.Document,
|
||||||
onHover: () async {
|
onHover: () async {
|
||||||
await tester.tapDeletePageButton();
|
await tester.tapDeletePageButton();
|
||||||
@ -181,7 +181,7 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.createNewPageWithName();
|
await tester.createNewPageWithName();
|
||||||
await tester.favoriteViewByName(gettingStated);
|
await tester.favoriteViewByName(gettingStarted);
|
||||||
expect(
|
expect(
|
||||||
find.byWidgetPredicate(
|
find.byWidgetPredicate(
|
||||||
(widget) =>
|
(widget) =>
|
||||||
@ -201,9 +201,9 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.createNewPageWithName();
|
await tester.createNewPageWithName();
|
||||||
await tester.favoriteViewByName(gettingStated);
|
await tester.favoriteViewByName(gettingStarted);
|
||||||
await tester.hoverOnPageName(
|
await tester.hoverOnPageName(
|
||||||
gettingStated,
|
gettingStarted,
|
||||||
layout: ViewLayoutPB.Document,
|
layout: ViewLayoutPB.Document,
|
||||||
useLast: false,
|
useLast: false,
|
||||||
onHover: () async {
|
onHover: () async {
|
||||||
|
@ -70,7 +70,7 @@ void main() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
await tester.openPage(gettingStated);
|
await tester.openPage(gettingStarted);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ void main() {
|
|||||||
|
|
||||||
final names = [1, 2, 3, 4].map((e) => 'document_$e').toList();
|
final names = [1, 2, 3, 4].map((e) => 'document_$e').toList();
|
||||||
for (var i = 0; i < names.length; i++) {
|
for (var i = 0; i < names.length; i++) {
|
||||||
final parentName = i == 0 ? gettingStated : names[i - 1];
|
final parentName = i == 0 ? gettingStarted : names[i - 1];
|
||||||
await tester.createNewPageWithName(
|
await tester.createNewPageWithName(
|
||||||
name: names[i],
|
name: names[i],
|
||||||
parentName: parentName,
|
parentName: parentName,
|
||||||
@ -92,7 +92,7 @@ void main() {
|
|||||||
// move the document_3 to the getting started page
|
// move the document_3 to the getting started page
|
||||||
await tester.movePageToOtherPage(
|
await tester.movePageToOtherPage(
|
||||||
name: names[3],
|
name: names[3],
|
||||||
parentName: gettingStated,
|
parentName: gettingStarted,
|
||||||
layout: ViewLayoutPB.Document,
|
layout: ViewLayoutPB.Document,
|
||||||
parentLayout: ViewLayoutPB.Document,
|
parentLayout: ViewLayoutPB.Document,
|
||||||
);
|
);
|
||||||
@ -101,7 +101,7 @@ void main() {
|
|||||||
.view
|
.view
|
||||||
.parentViewId;
|
.parentViewId;
|
||||||
final toId = tester
|
final toId = tester
|
||||||
.widget<SingleInnerViewItem>(tester.findPageName(gettingStated))
|
.widget<SingleInnerViewItem>(tester.findPageName(gettingStarted))
|
||||||
.view
|
.view
|
||||||
.id;
|
.id;
|
||||||
expect(fromId, toId);
|
expect(fromId, toId);
|
||||||
@ -109,13 +109,13 @@ void main() {
|
|||||||
// move the document_2 before document_1
|
// move the document_2 before document_1
|
||||||
await tester.movePageToOtherPage(
|
await tester.movePageToOtherPage(
|
||||||
name: names[2],
|
name: names[2],
|
||||||
parentName: gettingStated,
|
parentName: gettingStarted,
|
||||||
layout: ViewLayoutPB.Document,
|
layout: ViewLayoutPB.Document,
|
||||||
parentLayout: ViewLayoutPB.Document,
|
parentLayout: ViewLayoutPB.Document,
|
||||||
position: DraggableHoverPosition.bottom,
|
position: DraggableHoverPosition.bottom,
|
||||||
);
|
);
|
||||||
final childViews = tester
|
final childViews = tester
|
||||||
.widget<SingleInnerViewItem>(tester.findPageName(gettingStated))
|
.widget<SingleInnerViewItem>(tester.findPageName(gettingStarted))
|
||||||
.view
|
.view
|
||||||
.childViews;
|
.childViews;
|
||||||
expect(
|
expect(
|
||||||
@ -170,7 +170,7 @@ void main() {
|
|||||||
|
|
||||||
// it should not be moved
|
// it should not be moved
|
||||||
final childViews = tester
|
final childViews = tester
|
||||||
.widget<SingleInnerViewItem>(tester.findPageName(gettingStated))
|
.widget<SingleInnerViewItem>(tester.findPageName(gettingStarted))
|
||||||
.view
|
.view
|
||||||
.childViews;
|
.childViews;
|
||||||
expect(
|
expect(
|
||||||
|
@ -1,23 +1,26 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:appflowy/workspace/presentation/home/tabs/flowy_tab.dart';
|
import 'package:appflowy/workspace/presentation/home/tabs/flowy_tab.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/tabs/tabs_manager.dart';
|
import 'package:appflowy/workspace/presentation/home/tabs/tabs_manager.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 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:integration_test/integration_test.dart';
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
import 'util/base.dart';
|
import 'util/base.dart';
|
||||||
import 'util/common_operations.dart';
|
import 'util/common_operations.dart';
|
||||||
|
import 'util/expectation.dart';
|
||||||
|
import 'util/keyboard.dart';
|
||||||
|
|
||||||
const _readmeName = 'Read me';
|
const _documentName = 'First Doc';
|
||||||
const _documentName = 'Document';
|
const _documentTwoName = 'Second Doc';
|
||||||
const _calendarName = 'Calendar';
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
group('Tabs', () {
|
group('Tabs', () {
|
||||||
testWidgets('Open AppFlowy and open/navigate multiple tabs',
|
testWidgets('Open AppFlowy and open/navigate/close tabs', (tester) async {
|
||||||
(tester) async {
|
|
||||||
await tester.initializeAppFlowy();
|
await tester.initializeAppFlowy();
|
||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
@ -29,31 +32,21 @@ void main() {
|
|||||||
findsNothing,
|
findsNothing,
|
||||||
);
|
);
|
||||||
|
|
||||||
await tester.createNewPageWithName(
|
|
||||||
name: _calendarName,
|
|
||||||
layout: ViewLayoutPB.Calendar,
|
|
||||||
);
|
|
||||||
await tester.createNewPageWithName(
|
await tester.createNewPageWithName(
|
||||||
name: _documentName,
|
name: _documentName,
|
||||||
layout: ViewLayoutPB.Document,
|
layout: ViewLayoutPB.Document,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Navigate current view to "Read me" document again
|
await tester.createNewPageWithName(
|
||||||
await tester.tapButtonWithName(_readmeName);
|
name: _documentTwoName,
|
||||||
|
layout: ViewLayoutPB.Document,
|
||||||
|
);
|
||||||
|
|
||||||
/// Open second menu item in a new tab
|
/// Open second menu item in a new tab
|
||||||
await tester.openAppInNewTab(_calendarName);
|
await tester.openAppInNewTab(gettingStarted, ViewLayoutPB.Document);
|
||||||
|
|
||||||
/// Open third menu item in a new tab
|
/// Open third menu item in a new tab
|
||||||
await tester.openAppInNewTab(_documentName);
|
await tester.openAppInNewTab(_documentName, ViewLayoutPB.Document);
|
||||||
|
|
||||||
expect(
|
|
||||||
find.descendant(
|
|
||||||
of: find.byType(TabsManager),
|
|
||||||
matching: find.byType(TabBar),
|
|
||||||
),
|
|
||||||
findsOneWidget,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
find.descendant(
|
find.descendant(
|
||||||
@ -63,13 +56,32 @@ void main() {
|
|||||||
findsNWidgets(3),
|
findsNWidgets(3),
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Navigate to the first tab
|
/// Navigate to the second tab
|
||||||
await tester.tap(
|
await tester.tap(
|
||||||
find.descendant(
|
find.descendant(
|
||||||
of: find.byType(FlowyTab),
|
of: find.byType(FlowyTab),
|
||||||
matching: find.text(_readmeName),
|
matching: find.text(gettingStarted),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Close tab by shortcut
|
||||||
|
await FlowyTestKeyboard.simulateKeyDownEvent(
|
||||||
|
[
|
||||||
|
Platform.isMacOS
|
||||||
|
? LogicalKeyboardKey.meta
|
||||||
|
: LogicalKeyboardKey.control,
|
||||||
|
LogicalKeyboardKey.keyW,
|
||||||
|
],
|
||||||
|
tester: tester,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
find.descendant(
|
||||||
|
of: find.byType(TabBar),
|
||||||
|
matching: find.byType(FlowyTab),
|
||||||
|
),
|
||||||
|
findsNWidgets(2),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
|||||||
|
|
||||||
import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
|
import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
|
||||||
import 'package:appflowy/user/presentation/skip_log_in_screen.dart';
|
import 'package:appflowy/user/presentation/skip_log_in_screen.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/app/section/item.dart';
|
|
||||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_language_view.dart';
|
import 'package:appflowy/workspace/presentation/settings/widgets/settings_language_view.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';
|
||||||
@ -29,7 +28,7 @@ extension CommonOperations on WidgetTester {
|
|||||||
|
|
||||||
/// Tap the + button on the home page.
|
/// Tap the + button on the home page.
|
||||||
Future<void> tapAddViewButton({
|
Future<void> tapAddViewButton({
|
||||||
String name = gettingStated,
|
String name = gettingStarted,
|
||||||
}) async {
|
}) async {
|
||||||
await hoverOnPageName(
|
await hoverOnPageName(
|
||||||
name,
|
name,
|
||||||
@ -211,6 +210,12 @@ extension CommonOperations on WidgetTester {
|
|||||||
await tapButtonWithName(ViewMoreActionType.unFavorite.name);
|
await tapButtonWithName(ViewMoreActionType.unFavorite.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tap the Open in a new tab button
|
||||||
|
Future<void> tapOpenInTabButton() async {
|
||||||
|
await tapPageOptionButton();
|
||||||
|
await tapButtonWithName(ViewMoreActionType.openInNewTab.name);
|
||||||
|
}
|
||||||
|
|
||||||
/// Rename the page.
|
/// Rename the page.
|
||||||
Future<void> renamePage(String name) async {
|
Future<void> renamePage(String name) async {
|
||||||
await tapRenamePageButton();
|
await tapRenamePageButton();
|
||||||
@ -272,7 +277,7 @@ extension CommonOperations on WidgetTester {
|
|||||||
bool openAfterCreated = true,
|
bool openAfterCreated = true,
|
||||||
}) async {
|
}) async {
|
||||||
// create a new page
|
// create a new page
|
||||||
await tapAddViewButton(name: parentName ?? gettingStated);
|
await tapAddViewButton(name: parentName ?? gettingStarted);
|
||||||
await tapButtonWithName(layout.menuName);
|
await tapButtonWithName(layout.menuName);
|
||||||
await pumpAndSettle();
|
await pumpAndSettle();
|
||||||
|
|
||||||
@ -336,11 +341,14 @@ extension CommonOperations on WidgetTester {
|
|||||||
await pumpAndSettle();
|
await pumpAndSettle();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> openAppInNewTab(String name) async {
|
Future<void> openAppInNewTab(String name, ViewLayoutPB layout) async {
|
||||||
await hoverOnPageName(name);
|
await hoverOnPageName(
|
||||||
await tap(find.byType(ViewDisclosureButton));
|
name,
|
||||||
await pumpAndSettle();
|
onHover: () async {
|
||||||
await tap(find.text(LocaleKeys.disclosureAction_openNewTab.tr()));
|
await tapOpenInTabButton();
|
||||||
|
await pumpAndSettle();
|
||||||
|
},
|
||||||
|
);
|
||||||
await pumpAndSettle();
|
await pumpAndSettle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
await tapGoButton();
|
await tapGoButton();
|
||||||
|
|
||||||
// expect to see a readme page
|
// expect to see a readme page
|
||||||
expectToSeePageName(gettingStated);
|
expectToSeePageName(gettingStarted);
|
||||||
|
|
||||||
await tapAddViewButton();
|
await tapAddViewButton();
|
||||||
await tapImportButton();
|
await tapImportButton();
|
||||||
|
@ -11,13 +11,13 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
// const String readme = 'Read me';
|
// const String readme = 'Read me';
|
||||||
const String gettingStated = '⭐️ Getting started';
|
const String gettingStarted = '⭐️ Getting started';
|
||||||
|
|
||||||
extension Expectation on WidgetTester {
|
extension Expectation on WidgetTester {
|
||||||
/// Expect to see the home page and with a default read me page.
|
/// Expect to see the home page and with a default read me page.
|
||||||
void expectToSeeHomePage() {
|
void expectToSeeHomePage() {
|
||||||
expect(find.byType(HomeStack), findsOneWidget);
|
expect(find.byType(HomeStack), findsOneWidget);
|
||||||
expect(find.textContaining(gettingStated), findsWidgets);
|
expect(find.textContaining(gettingStarted), findsWidgets);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expect to see the page name on the home page.
|
/// Expect to see the page name on the home page.
|
||||||
|
@ -8,4 +8,12 @@ extension RawKeyboardExtension on RawKeyboard {
|
|||||||
LogicalKeyboardKey.altRight,
|
LogicalKeyboardKey.altRight,
|
||||||
].contains(key),
|
].contains(key),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
bool get isControlPressed => keysPressed.any(
|
||||||
|
(key) => [
|
||||||
|
LogicalKeyboardKey.control,
|
||||||
|
LogicalKeyboardKey.controlLeft,
|
||||||
|
LogicalKeyboardKey.controlRight,
|
||||||
|
].contains(key),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -35,9 +35,6 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> {
|
|||||||
_listener.start(appsChanged: _handleAppsOrFail);
|
_listener.start(appsChanged: _handleAppsOrFail);
|
||||||
await _fetchApps(emit);
|
await _fetchApps(emit);
|
||||||
},
|
},
|
||||||
openPage: (e) async {
|
|
||||||
emit(state.copyWith(plugin: e.plugin));
|
|
||||||
},
|
|
||||||
createApp: (_CreateApp event) async {
|
createApp: (_CreateApp event) async {
|
||||||
final result = await _workspaceService.createApp(
|
final result = await _workspaceService.createApp(
|
||||||
name: event.name,
|
name: event.name,
|
||||||
@ -110,7 +107,6 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> {
|
|||||||
@freezed
|
@freezed
|
||||||
class MenuEvent with _$MenuEvent {
|
class MenuEvent with _$MenuEvent {
|
||||||
const factory MenuEvent.initial() = _Initial;
|
const factory MenuEvent.initial() = _Initial;
|
||||||
const factory MenuEvent.openPage(Plugin plugin) = _OpenPage;
|
|
||||||
const factory MenuEvent.createApp(String name, {String? desc, int? index}) =
|
const factory MenuEvent.createApp(String name, {String? desc, int? index}) =
|
||||||
_CreateApp;
|
_CreateApp;
|
||||||
const factory MenuEvent.moveApp(int fromIndex, int toIndex) = _MoveApp;
|
const factory MenuEvent.moveApp(int fromIndex, int toIndex) = _MoveApp;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:appflowy/plugins/util.dart';
|
import 'package:appflowy/plugins/util.dart';
|
||||||
import 'package:appflowy/startup/plugin/plugin.dart';
|
import 'package:appflowy/startup/plugin/plugin.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
|
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/home_stack.dart';
|
import 'package:appflowy/workspace/presentation/home/home_stack.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/menu.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/menu.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
@ -21,7 +22,9 @@ class TabsBloc extends Bloc<TabsEvent, TabsState> {
|
|||||||
on<TabsEvent>((event, emit) async {
|
on<TabsEvent>((event, emit) async {
|
||||||
event.when(
|
event.when(
|
||||||
selectTab: (int index) {
|
selectTab: (int index) {
|
||||||
if (index != state.currentIndex) {
|
if (index != state.currentIndex &&
|
||||||
|
index >= 0 &&
|
||||||
|
index < state.pages) {
|
||||||
emit(state.copyWith(newIndex: index));
|
emit(state.copyWith(newIndex: index));
|
||||||
_setLatestOpenView();
|
_setLatestOpenView();
|
||||||
}
|
}
|
||||||
@ -31,6 +34,10 @@ class TabsBloc extends Bloc<TabsEvent, TabsState> {
|
|||||||
emit(state.closeView(pluginId));
|
emit(state.closeView(pluginId));
|
||||||
_setLatestOpenView();
|
_setLatestOpenView();
|
||||||
},
|
},
|
||||||
|
closeCurrentTab: () {
|
||||||
|
emit(state.closeView(state.currentPageManager.plugin.id));
|
||||||
|
_setLatestOpenView();
|
||||||
|
},
|
||||||
openTab: (Plugin plugin, ViewPB view) {
|
openTab: (Plugin plugin, ViewPB view) {
|
||||||
emit(state.openView(plugin, view));
|
emit(state.openView(plugin, view));
|
||||||
_setLatestOpenView(view);
|
_setLatestOpenView(view);
|
||||||
@ -54,4 +61,12 @@ class TabsBloc extends Bloc<TabsEvent, TabsState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds a [TabsEvent.openTab] event for the provided [ViewPB]
|
||||||
|
void openTab(ViewPB view) =>
|
||||||
|
add(TabsEvent.openTab(plugin: view.plugin(), view: view));
|
||||||
|
|
||||||
|
/// Adds a [TabsEvent.openPlugin] event for the provided [ViewPB]
|
||||||
|
void openPlugin(ViewPB view) =>
|
||||||
|
add(TabsEvent.openPlugin(plugin: view.plugin(), view: view));
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ part of 'tabs_bloc.dart';
|
|||||||
class TabsEvent with _$TabsEvent {
|
class TabsEvent with _$TabsEvent {
|
||||||
const factory TabsEvent.moveTab() = _MoveTab;
|
const factory TabsEvent.moveTab() = _MoveTab;
|
||||||
const factory TabsEvent.closeTab(String pluginId) = _CloseTab;
|
const factory TabsEvent.closeTab(String pluginId) = _CloseTab;
|
||||||
|
const factory TabsEvent.closeCurrentTab() = _CloseCurrentTab;
|
||||||
const factory TabsEvent.selectTab(int index) = _SelectTab;
|
const factory TabsEvent.selectTab(int index) = _SelectTab;
|
||||||
const factory TabsEvent.openTab({
|
const factory TabsEvent.openTab({
|
||||||
required Plugin plugin,
|
required Plugin plugin,
|
||||||
|
@ -31,6 +31,11 @@ class TabsState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TabsState closeView(String pluginId) {
|
TabsState closeView(String pluginId) {
|
||||||
|
// Avoid closing the only open tab
|
||||||
|
if (_pageManagers.length == 1) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
_pageManagers.removeWhere((pm) => pm.plugin.id == pluginId);
|
_pageManagers.removeWhere((pm) => pm.plugin.id == pluginId);
|
||||||
|
|
||||||
/// If currentIndex is greater than the amount of allowed indices
|
/// If currentIndex is greater than the amount of allowed indices
|
||||||
@ -79,6 +84,10 @@ class TabsState {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (index == currentIndex) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
return copyWith(newIndex: index);
|
return copyWith(newIndex: index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,9 +112,13 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
|
|||||||
ext: {},
|
ext: {},
|
||||||
openAfterCreate: e.openAfterCreated,
|
openAfterCreate: e.openAfterCreated,
|
||||||
);
|
);
|
||||||
|
|
||||||
emit(
|
emit(
|
||||||
result.fold(
|
result.fold(
|
||||||
(l) => state.copyWith(successOrFailure: left(unit)),
|
(view) => state.copyWith(
|
||||||
|
lastCreatedView: view,
|
||||||
|
successOrFailure: left(unit),
|
||||||
|
),
|
||||||
(error) => state.copyWith(successOrFailure: right(error)),
|
(error) => state.copyWith(successOrFailure: right(error)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -218,6 +222,7 @@ class ViewState with _$ViewState {
|
|||||||
required bool isEditing,
|
required bool isEditing,
|
||||||
required bool isExpanded,
|
required bool isExpanded,
|
||||||
required Either<Unit, FlowyError> successOrFailure,
|
required Either<Unit, FlowyError> successOrFailure,
|
||||||
|
@Default(null) ViewPB? lastCreatedView,
|
||||||
}) = _ViewState;
|
}) = _ViewState;
|
||||||
|
|
||||||
factory ViewState.init(ViewPB view) => ViewState(
|
factory ViewState.init(ViewPB view) => ViewState(
|
||||||
@ -226,5 +231,6 @@ class ViewState with _$ViewState {
|
|||||||
isExpanded: false,
|
isExpanded: false,
|
||||||
isEditing: false,
|
isEditing: false,
|
||||||
successOrFailure: left(unit),
|
successOrFailure: left(unit),
|
||||||
|
lastCreatedView: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,45 +2,102 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:appflowy/workspace/application/appearance.dart';
|
import 'package:appflowy/workspace/application/appearance.dart';
|
||||||
import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
|
import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
|
||||||
|
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hotkey_manager/hotkey_manager.dart';
|
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
typedef KeyDownHandler = void Function(HotKey hotKey);
|
||||||
|
|
||||||
|
/// Helper class that utilizes the global [HotKeyManager] to easily
|
||||||
|
/// add a [HotKey] with different handlers.
|
||||||
|
///
|
||||||
|
/// Makes registration of a [HotKey] simple and easy to read, and makes
|
||||||
|
/// sure the [KeyDownHandler], and other handlers, are grouped with the
|
||||||
|
/// relevant [HotKey].
|
||||||
|
///
|
||||||
|
class HotKeyItem {
|
||||||
|
final HotKey hotKey;
|
||||||
|
final KeyDownHandler? keyDownHandler;
|
||||||
|
|
||||||
|
HotKeyItem({
|
||||||
|
required this.hotKey,
|
||||||
|
this.keyDownHandler,
|
||||||
|
});
|
||||||
|
|
||||||
|
void register() =>
|
||||||
|
hotKeyManager.register(hotKey, keyDownHandler: keyDownHandler);
|
||||||
|
}
|
||||||
|
|
||||||
class HomeHotKeys extends StatelessWidget {
|
class HomeHotKeys extends StatelessWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
const HomeHotKeys({required this.child, Key? key}) : super(key: key);
|
const HomeHotKeys({required this.child, Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final HotKey hotKey = HotKey(
|
// Collapse sidebar menu
|
||||||
KeyCode.backslash,
|
HotKeyItem(
|
||||||
modifiers: [Platform.isMacOS ? KeyModifier.meta : KeyModifier.control],
|
hotKey: HotKey(
|
||||||
// Set hotkey scope (default is HotKeyScope.system)
|
KeyCode.backslash,
|
||||||
scope: HotKeyScope.inapp, // Set as inapp-wide hotkey.
|
modifiers: [Platform.isMacOS ? KeyModifier.meta : KeyModifier.control],
|
||||||
);
|
// Set hotkey scope (default is HotKeyScope.system)
|
||||||
hotKeyManager.register(
|
scope: HotKeyScope.inapp, // Set as inapp-wide hotkey.
|
||||||
hotKey,
|
),
|
||||||
keyDownHandler: (hotKey) {
|
keyDownHandler: (_) => context
|
||||||
context
|
.read<HomeSettingBloc>()
|
||||||
.read<HomeSettingBloc>()
|
.add(const HomeSettingEvent.collapseMenu()),
|
||||||
.add(const HomeSettingEvent.collapseMenu());
|
).register();
|
||||||
},
|
|
||||||
);
|
// Toggle theme mode light/dark
|
||||||
|
HotKeyItem(
|
||||||
|
hotKey: HotKey(
|
||||||
|
KeyCode.keyL,
|
||||||
|
modifiers: [
|
||||||
|
Platform.isMacOS ? KeyModifier.meta : KeyModifier.control,
|
||||||
|
KeyModifier.shift,
|
||||||
|
],
|
||||||
|
scope: HotKeyScope.inapp,
|
||||||
|
),
|
||||||
|
keyDownHandler: (_) =>
|
||||||
|
context.read<AppearanceSettingsCubit>().toggleThemeMode(),
|
||||||
|
).register();
|
||||||
|
|
||||||
|
// Close current tab
|
||||||
|
HotKeyItem(
|
||||||
|
hotKey: HotKey(
|
||||||
|
KeyCode.keyW,
|
||||||
|
modifiers: [Platform.isMacOS ? KeyModifier.meta : KeyModifier.control],
|
||||||
|
scope: HotKeyScope.inapp,
|
||||||
|
),
|
||||||
|
keyDownHandler: (_) =>
|
||||||
|
context.read<TabsBloc>().add(const TabsEvent.closeCurrentTab()),
|
||||||
|
).register();
|
||||||
|
|
||||||
|
// Go to previous tab
|
||||||
|
HotKeyItem(
|
||||||
|
hotKey: HotKey(
|
||||||
|
KeyCode.pageUp,
|
||||||
|
modifiers: [Platform.isMacOS ? KeyModifier.meta : KeyModifier.control],
|
||||||
|
scope: HotKeyScope.inapp,
|
||||||
|
),
|
||||||
|
keyDownHandler: (_) => _selectTab(context, -1),
|
||||||
|
).register();
|
||||||
|
|
||||||
|
// Go to next tab
|
||||||
|
HotKeyItem(
|
||||||
|
hotKey: HotKey(
|
||||||
|
KeyCode.pageDown,
|
||||||
|
modifiers: [Platform.isMacOS ? KeyModifier.meta : KeyModifier.control],
|
||||||
|
scope: HotKeyScope.inapp,
|
||||||
|
),
|
||||||
|
keyDownHandler: (_) => _selectTab(context, 1),
|
||||||
|
).register();
|
||||||
|
|
||||||
final HotKey hotKeyForToggleThemeMode = HotKey(
|
|
||||||
KeyCode.keyL,
|
|
||||||
modifiers: [
|
|
||||||
Platform.isMacOS ? KeyModifier.meta : KeyModifier.control,
|
|
||||||
KeyModifier.shift,
|
|
||||||
],
|
|
||||||
scope: HotKeyScope.inapp,
|
|
||||||
);
|
|
||||||
hotKeyManager.register(
|
|
||||||
hotKeyForToggleThemeMode,
|
|
||||||
keyDownHandler: (_) {
|
|
||||||
context.read<AppearanceSettingsCubit>().toggleThemeMode();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _selectTab(BuildContext context, int change) {
|
||||||
|
final bloc = context.read<TabsBloc>();
|
||||||
|
bloc.add(TabsEvent.selectTab(bloc.state.currentIndex + change));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,12 +123,7 @@ class ViewSectionItem extends StatelessWidget {
|
|||||||
.add(FavoriteEvent.toggle(view));
|
.add(FavoriteEvent.toggle(view));
|
||||||
break;
|
break;
|
||||||
case ViewDisclosureAction.openInNewTab:
|
case ViewDisclosureAction.openInNewTab:
|
||||||
blocContext.read<TabsBloc>().add(
|
blocContext.read<TabsBloc>().openTab(state.view);
|
||||||
TabsEvent.openTab(
|
|
||||||
plugin: state.view.plugin(),
|
|
||||||
view: blocContext.read<ViewBloc>().state.view,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -7,7 +7,6 @@ import 'package:appflowy/startup/startup.dart';
|
|||||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
|
import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
|
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
|
||||||
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
|
import 'package:appflowy/workspace/presentation/home/home_sizes.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-folder2/workspace.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
|
||||||
@ -49,34 +48,18 @@ class HomeMenu extends StatelessWidget {
|
|||||||
return MultiBlocProvider(
|
return MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider<MenuBloc>(
|
BlocProvider<MenuBloc>(
|
||||||
create: (context) {
|
create: (context) => MenuBloc(
|
||||||
final menuBloc = MenuBloc(
|
user: user,
|
||||||
user: user,
|
workspace: workspaceSetting.workspace,
|
||||||
workspace: workspaceSetting.workspace,
|
)..add(const MenuEvent.initial()),
|
||||||
);
|
|
||||||
menuBloc.add(const MenuEvent.initial());
|
|
||||||
return menuBloc;
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (ctx) =>
|
create: (context) =>
|
||||||
getIt<FavoriteBloc>()..add(const FavoriteEvent.initial()),
|
getIt<FavoriteBloc>()..add(const FavoriteEvent.initial()),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
child: MultiBlocListener(
|
child: BlocBuilder<MenuBloc, MenuState>(
|
||||||
listeners: [
|
builder: (context, state) => _renderBody(context),
|
||||||
BlocListener<MenuBloc, MenuState>(
|
|
||||||
listenWhen: (p, c) => p.plugin.id != c.plugin.id,
|
|
||||||
listener: (context, state) {
|
|
||||||
getIt<TabsBloc>().add(
|
|
||||||
TabsEvent.openPlugin(plugin: state.plugin),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
child: BlocBuilder<MenuBloc, MenuState>(
|
|
||||||
builder: (context, state) => _renderBody(context),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
|
import 'package:appflowy/core/raw_keyboard_extension.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
|
||||||
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
|
|
||||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/menu.dart';
|
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/view/view_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:flowy_infra_ui/style_widget/button.dart';
|
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class FavoriteFolder extends StatelessWidget {
|
class FavoriteFolder extends StatelessWidget {
|
||||||
@ -24,6 +23,7 @@ class FavoriteFolder extends StatelessWidget {
|
|||||||
if (views.isEmpty) {
|
if (views.isEmpty) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
return BlocProvider<FolderBloc>(
|
return BlocProvider<FolderBloc>(
|
||||||
create: (context) => FolderBloc(type: FolderCategoryType.favorite)
|
create: (context) => FolderBloc(type: FolderCategoryType.favorite)
|
||||||
..add(
|
..add(
|
||||||
@ -54,11 +54,14 @@ class FavoriteFolder extends StatelessWidget {
|
|||||||
view: view,
|
view: view,
|
||||||
level: 0,
|
level: 0,
|
||||||
onSelected: (view) {
|
onSelected: (view) {
|
||||||
getIt<MenuSharedState>().latestOpenView = view;
|
if (RawKeyboard.instance.isControlPressed) {
|
||||||
context
|
context.read<TabsBloc>().openTab(view);
|
||||||
.read<MenuBloc>()
|
}
|
||||||
.add(MenuEvent.openPage(view.plugin()));
|
|
||||||
|
context.read<TabsBloc>().openPlugin(view);
|
||||||
},
|
},
|
||||||
|
onTertiarySelected: (view) =>
|
||||||
|
context.read<TabsBloc>().openTab(view),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
|
import 'package:appflowy/core/raw_keyboard_extension.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
|
||||||
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
|
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/view/view_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:flowy_infra/image.dart';
|
import 'package:flowy_infra/image.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class PersonalFolder extends StatelessWidget {
|
class PersonalFolder extends StatelessWidget {
|
||||||
@ -52,13 +52,14 @@ class PersonalFolder extends StatelessWidget {
|
|||||||
leftPadding: 16,
|
leftPadding: 16,
|
||||||
isFeedback: false,
|
isFeedback: false,
|
||||||
onSelected: (view) {
|
onSelected: (view) {
|
||||||
getIt<TabsBloc>().add(
|
if (RawKeyboard.instance.isControlPressed) {
|
||||||
TabsEvent.openPlugin(
|
context.read<TabsBloc>().openTab(view);
|
||||||
plugin: view.plugin(),
|
}
|
||||||
view: view,
|
|
||||||
),
|
context.read<TabsBloc>().openPlugin(view);
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
onTertiarySelected: (view) =>
|
||||||
|
context.read<TabsBloc>().openTab(view),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:appflowy/startup/startup.dart';
|
|
||||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
|
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
||||||
@ -7,6 +6,7 @@ import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_new_pa
|
|||||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_top_menu.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_top_menu.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_trash.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_trash.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_user.dart';
|
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_user.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
||||||
show UserProfilePB;
|
show UserProfilePB;
|
||||||
@ -48,17 +48,18 @@ class HomeSideBar extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
child: BlocListener<MenuBloc, MenuState>(
|
child: BlocListener<MenuBloc, MenuState>(
|
||||||
listenWhen: (p, c) => p.plugin.id != c.plugin.id,
|
listenWhen: (p, c) => p.plugin.id != c.plugin.id,
|
||||||
listener: (context, state) => getIt<TabsBloc>().add(
|
listener: (context, state) => context
|
||||||
TabsEvent.openPlugin(plugin: state.plugin),
|
.read<TabsBloc>()
|
||||||
),
|
.add(TabsEvent.openPlugin(plugin: state.plugin)),
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final menuState = context.watch<MenuBloc>().state;
|
final menuState = context.watch<MenuBloc>().state;
|
||||||
final favoriteState = context.watch<FavoriteBloc>().state;
|
final favoriteState = context.watch<FavoriteBloc>().state;
|
||||||
|
|
||||||
return _buildSidebar(
|
return _buildSidebar(
|
||||||
context,
|
context,
|
||||||
menuState,
|
menuState.views,
|
||||||
favoriteState,
|
favoriteState.views,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -68,11 +69,10 @@ class HomeSideBar extends StatelessWidget {
|
|||||||
|
|
||||||
Widget _buildSidebar(
|
Widget _buildSidebar(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
MenuState state,
|
List<ViewPB> views,
|
||||||
FavoriteState favoriteState,
|
List<ViewPB> favoriteViews,
|
||||||
) {
|
) {
|
||||||
final views = state.views;
|
return DecoratedBox(
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||||
border: Border(
|
border: Border(
|
||||||
@ -95,7 +95,7 @@ class HomeSideBar extends StatelessWidget {
|
|||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: SidebarFolder(
|
child: SidebarFolder(
|
||||||
views: views,
|
views: views,
|
||||||
favoriteViews: favoriteState.views,
|
favoriteViews: favoriteViews,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -48,12 +48,7 @@ class SidebarNewPageButton extends StatelessWidget {
|
|||||||
value: '',
|
value: '',
|
||||||
confirm: (value) {
|
confirm: (value) {
|
||||||
if (value.isNotEmpty) {
|
if (value.isNotEmpty) {
|
||||||
context.read<MenuBloc>().add(
|
context.read<MenuBloc>().add(MenuEvent.createApp(value));
|
||||||
MenuEvent.createApp(
|
|
||||||
value,
|
|
||||||
desc: '',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
).show(context);
|
).show(context);
|
||||||
|
@ -19,6 +19,8 @@ import 'package:flowy_infra_ui/style_widget/hover.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
typedef ViewItemOnSelected = void Function(ViewPB);
|
||||||
|
|
||||||
class ViewItem extends StatelessWidget {
|
class ViewItem extends StatelessWidget {
|
||||||
const ViewItem({
|
const ViewItem({
|
||||||
super.key,
|
super.key,
|
||||||
@ -28,6 +30,7 @@ class ViewItem extends StatelessWidget {
|
|||||||
required this.level,
|
required this.level,
|
||||||
this.leftPadding = 10,
|
this.leftPadding = 10,
|
||||||
required this.onSelected,
|
required this.onSelected,
|
||||||
|
this.onTertiarySelected,
|
||||||
this.isFirstChild = false,
|
this.isFirstChild = false,
|
||||||
this.isDraggable = true,
|
this.isDraggable = true,
|
||||||
required this.isFeedback,
|
required this.isFeedback,
|
||||||
@ -46,7 +49,11 @@ class ViewItem extends StatelessWidget {
|
|||||||
// the left padding of the each level = level * leftPadding
|
// the left padding of the each level = level * leftPadding
|
||||||
final double leftPadding;
|
final double leftPadding;
|
||||||
|
|
||||||
final void Function(ViewPB) onSelected;
|
// Selected by normal conventions
|
||||||
|
final ViewItemOnSelected onSelected;
|
||||||
|
|
||||||
|
// Selected by middle mouse button
|
||||||
|
final ViewItemOnSelected? onTertiarySelected;
|
||||||
|
|
||||||
// used for indicating the first child of the parent view, so that we can
|
// used for indicating the first child of the parent view, so that we can
|
||||||
// add top border to the first child
|
// add top border to the first child
|
||||||
@ -62,7 +69,12 @@ class ViewItem extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (_) => ViewBloc(view: view)..add(const ViewEvent.initial()),
|
create: (_) => ViewBloc(view: view)..add(const ViewEvent.initial()),
|
||||||
child: BlocBuilder<ViewBloc, ViewState>(
|
child: BlocConsumer<ViewBloc, ViewState>(
|
||||||
|
listenWhen: (p, c) =>
|
||||||
|
c.lastCreatedView != null &&
|
||||||
|
p.lastCreatedView?.id != c.lastCreatedView!.id,
|
||||||
|
listener: (context, state) =>
|
||||||
|
context.read<TabsBloc>().openPlugin(state.lastCreatedView!),
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
// don't remove this code. it's related to the backend service.
|
// don't remove this code. it's related to the backend service.
|
||||||
view.childViews
|
view.childViews
|
||||||
@ -78,6 +90,7 @@ class ViewItem extends StatelessWidget {
|
|||||||
showActions: state.isEditing,
|
showActions: state.isEditing,
|
||||||
isExpanded: state.isExpanded,
|
isExpanded: state.isExpanded,
|
||||||
onSelected: onSelected,
|
onSelected: onSelected,
|
||||||
|
onTertiarySelected: onTertiarySelected,
|
||||||
isFirstChild: isFirstChild,
|
isFirstChild: isFirstChild,
|
||||||
isDraggable: isDraggable,
|
isDraggable: isDraggable,
|
||||||
isFeedback: isFeedback,
|
isFeedback: isFeedback,
|
||||||
@ -101,6 +114,7 @@ class InnerViewItem extends StatelessWidget {
|
|||||||
required this.leftPadding,
|
required this.leftPadding,
|
||||||
required this.showActions,
|
required this.showActions,
|
||||||
required this.onSelected,
|
required this.onSelected,
|
||||||
|
this.onTertiarySelected,
|
||||||
this.isFirstChild = false,
|
this.isFirstChild = false,
|
||||||
required this.isFeedback,
|
required this.isFeedback,
|
||||||
});
|
});
|
||||||
@ -120,7 +134,8 @@ class InnerViewItem extends StatelessWidget {
|
|||||||
final double leftPadding;
|
final double leftPadding;
|
||||||
|
|
||||||
final bool showActions;
|
final bool showActions;
|
||||||
final void Function(ViewPB) onSelected;
|
final ViewItemOnSelected onSelected;
|
||||||
|
final ViewItemOnSelected? onTertiarySelected;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -131,6 +146,7 @@ class InnerViewItem extends StatelessWidget {
|
|||||||
showActions: showActions,
|
showActions: showActions,
|
||||||
categoryType: categoryType,
|
categoryType: categoryType,
|
||||||
onSelected: onSelected,
|
onSelected: onSelected,
|
||||||
|
onTertiarySelected: onTertiarySelected,
|
||||||
isExpanded: isExpanded,
|
isExpanded: isExpanded,
|
||||||
isDraggable: isDraggable,
|
isDraggable: isDraggable,
|
||||||
leftPadding: leftPadding,
|
leftPadding: leftPadding,
|
||||||
@ -148,6 +164,7 @@ class InnerViewItem extends StatelessWidget {
|
|||||||
view: childView,
|
view: childView,
|
||||||
level: level + 1,
|
level: level + 1,
|
||||||
onSelected: onSelected,
|
onSelected: onSelected,
|
||||||
|
onTertiarySelected: onTertiarySelected,
|
||||||
isDraggable: isDraggable,
|
isDraggable: isDraggable,
|
||||||
leftPadding: leftPadding,
|
leftPadding: leftPadding,
|
||||||
isFeedback: isFeedback,
|
isFeedback: isFeedback,
|
||||||
@ -176,6 +193,7 @@ class InnerViewItem extends StatelessWidget {
|
|||||||
categoryType: categoryType,
|
categoryType: categoryType,
|
||||||
level: level,
|
level: level,
|
||||||
onSelected: onSelected,
|
onSelected: onSelected,
|
||||||
|
onTertiarySelected: onTertiarySelected,
|
||||||
isDraggable: false,
|
isDraggable: false,
|
||||||
leftPadding: leftPadding,
|
leftPadding: leftPadding,
|
||||||
isFeedback: true,
|
isFeedback: true,
|
||||||
@ -206,6 +224,7 @@ class SingleInnerViewItem extends StatefulWidget {
|
|||||||
required this.categoryType,
|
required this.categoryType,
|
||||||
required this.showActions,
|
required this.showActions,
|
||||||
required this.onSelected,
|
required this.onSelected,
|
||||||
|
this.onTertiarySelected,
|
||||||
required this.isFeedback,
|
required this.isFeedback,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -220,7 +239,8 @@ class SingleInnerViewItem extends StatefulWidget {
|
|||||||
|
|
||||||
final bool isDraggable;
|
final bool isDraggable;
|
||||||
final bool showActions;
|
final bool showActions;
|
||||||
final void Function(ViewPB) onSelected;
|
final ViewItemOnSelected onSelected;
|
||||||
|
final ViewItemOnSelected? onTertiarySelected;
|
||||||
final FolderCategoryType categoryType;
|
final FolderCategoryType categoryType;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -279,6 +299,7 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
|
|||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onTap: () => widget.onSelected(widget.view),
|
onTap: () => widget.onSelected(widget.view),
|
||||||
|
onTertiaryTapDown: (_) => widget.onTertiarySelected?.call(widget.view),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 26,
|
height: 26,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@ -377,12 +398,7 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
|
|||||||
context.read<ViewBloc>().add(const ViewEvent.duplicate());
|
context.read<ViewBloc>().add(const ViewEvent.duplicate());
|
||||||
break;
|
break;
|
||||||
case ViewMoreActionType.openInNewTab:
|
case ViewMoreActionType.openInNewTab:
|
||||||
context.read<TabsBloc>().add(
|
context.read<TabsBloc>().openTab(widget.view);
|
||||||
TabsEvent.openTab(
|
|
||||||
plugin: widget.view.plugin(),
|
|
||||||
view: widget.view,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw UnsupportedError('$action is not supported');
|
throw UnsupportedError('$action is not supported');
|
||||||
|
Loading…
Reference in New Issue
Block a user