feat: support editing name when creating a new page on mobile (#6501)

* feat: support editing name when creating a new page on mobile

* chore: add defaultName in layout extension

* test: add cover title test on mobile

* fix: cover title test on mobile

* feat: add integration runner 4

* chore: update translations

* chore: disable subpage feature
This commit is contained in:
Lucas 2024-10-08 14:29:07 +08:00 committed by GitHub
parent ba59514464
commit 5f1f536181
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 306 additions and 125 deletions

View File

@ -52,7 +52,7 @@ runs:
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
sudo apt-get update
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev network-manager libmpv-dev mpv
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev network-manager
shell: bash
- name: Enable Flutter Desktop

View File

@ -173,7 +173,7 @@ jobs:
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
sudo apt-get update
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev libmpv-dev mpv
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev
fi
shell: bash
@ -292,7 +292,7 @@ jobs:
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
sudo apt-get update
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev libmpv-dev mpv
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev
shell: bash
- name: Enable Flutter Desktop
@ -339,12 +339,6 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
- name: Install video dependency
run: |
sudo apt-get update
sudo apt-get -y install libmpv-dev mpv
shell: bash
- name: Flutter Integration Test 1
uses: ./.github/actions/flutter_integration_test
with:
@ -369,12 +363,6 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
- name: Install video dependency
run: |
sudo apt-get update
sudo apt-get -y install libmpv-dev mpv
shell: bash
- name: Flutter Integration Test 2
uses: ./.github/actions/flutter_integration_test
with:
@ -399,12 +387,6 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
- name: Install video dependency
run: |
sudo apt-get update
sudo apt-get -y install libmpv-dev mpv
shell: bash
- name: Flutter Integration Test 3
uses: ./.github/actions/flutter_integration_test
with:
@ -413,3 +395,27 @@ jobs:
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
rust_target: ${{ matrix.target }}
integration_test_4:
needs: [prepare-linux]
if: github.event.pull_request.draft != true
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
include:
- os: ubuntu-latest
target: "x86_64-unknown-linux-gnu"
runs-on: ${{ matrix.os }}
steps:
- name: Checkout source code
uses: actions/checkout@v4
- name: Flutter Integration Test 4
uses: ./.github/actions/flutter_integration_test
with:
test_path: integration_test/desktop_runner_4.dart
flutter_version: ${{ env.FLUTTER_VERSION }}
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
rust_target: ${{ matrix.target }}

View File

@ -368,7 +368,7 @@ jobs:
sudo apt-get update
sudo apt-get install -y build-essential libsqlite3-dev libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
sudo apt-get install keybinder-3.0
sudo apt-get install -y alien libnotify-dev libmpv-dev mpv
sudo apt-get install -y alien libnotify-dev
source $HOME/.cargo/env
cargo install --force cargo-make
cargo install --force duckscript_cli

View File

@ -24,7 +24,7 @@ import 'document_with_outline_block_test.dart' as document_with_outline_block;
import 'document_with_toggle_list_test.dart' as document_with_toggle_list_test;
import 'edit_document_test.dart' as document_edit_test;
void startTesting() {
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// Document integration tests
@ -47,4 +47,6 @@ void startTesting() {
document_more_actions_test.main();
document_with_file_test.main();
document_shortcuts_test.main();
// Don't add new tests here. Add them to document_test_runner_2.dart
}

View File

@ -0,0 +1,14 @@
import 'package:integration_test/integration_test.dart';
import 'document_app_lifecycle_test.dart' as document_app_lifecycle_test;
import 'document_title_test.dart' as document_title_test;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// Document integration tests
document_title_test.main();
// Disable subPage test temporarily, enable it in version 0.7.2
// document_sub_page_test.main();
document_app_lifecycle_test.main();
}

View File

@ -1,6 +1,6 @@
import 'package:integration_test/integration_test.dart';
import 'desktop/document/document_test_runner.dart' as document_test_runner;
import 'desktop/document/document_test_runner_1.dart' as document_test_runner;
import 'desktop/uncategorized/empty_test.dart' as first_test;
import 'desktop/uncategorized/switch_folder_test.dart' as switch_folder_test;
@ -15,7 +15,7 @@ Future<void> runIntegration1OnDesktop() async {
first_test.main();
switch_folder_test.main();
document_test_runner.startTesting();
document_test_runner.main();
// DON'T add more tests here. This is the first test runner for desktop.
// DON'T add more tests here.
}

View File

@ -3,16 +3,12 @@ import 'package:integration_test/integration_test.dart';
import 'desktop/board/board_test_runner.dart' as board_test_runner;
import 'desktop/database/database_row_cover_test.dart'
as database_row_cover_test;
import 'desktop/document/document_title_test.dart' as document_title_test;
import 'desktop/document/document_app_lifecycle_test.dart'
as document_app_lifecycle_test;
import 'desktop/document/document_sub_page_test.dart' as document_sub_page_test;
import 'desktop/grid/grid_edit_row_test.dart' as grid_edit_row_test_runner;
import 'desktop/grid/grid_filter_and_sort_test.dart'
as grid_filter_and_sort_test_runner;
import 'desktop/grid/grid_reopen_test.dart' as grid_reopen_test_runner;
import 'desktop/grid/grid_reorder_row_test.dart'
as grid_reorder_row_test_runner;
import 'desktop/grid/grid_edit_row_test.dart' as grid_edit_row_test_runner;
import 'desktop/grid/grid_row_test.dart' as grid_create_row_test_runner;
import 'desktop/settings/settings_runner.dart' as settings_test_runner;
import 'desktop/sidebar/sidebar_test_runner.dart' as sidebar_test_runner;
@ -51,7 +47,6 @@ Future<void> runIntegration3OnDesktop() async {
grid_filter_and_sort_test_runner.main();
grid_edit_row_test_runner.main();
zoom_in_out_test.main();
document_title_test.main();
document_sub_page_test.main();
document_app_lifecycle_test.main();
// DON'T add more tests here. Add them to desktop_runner_4.dart
}

View File

@ -0,0 +1,17 @@
import 'package:integration_test/integration_test.dart';
import 'desktop/document/document_test_runner_2.dart' as document_test_runner_2;
import 'desktop/uncategorized/empty_test.dart' as first_test;
Future<void> main() async {
await runIntegration4OnDesktop();
}
Future<void> runIntegration4OnDesktop() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// This test must be run first, otherwise the CI will fail.
first_test.main();
document_test_runner_2.main();
}

View File

@ -0,0 +1,12 @@
import 'package:integration_test/integration_test.dart';
import 'page_style_test.dart' as page_style_test;
import 'title_test.dart' as title_test;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// Document integration tests
title_test.main();
page_style_test.main();
}

View File

@ -0,0 +1,76 @@
// ignore_for_file: unused_import
import 'dart:io';
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar_actions.dart';
import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_buttons.dart';
import 'package:appflowy/mobile/presentation/home/home.dart';
import 'package:appflowy/mobile/presentation/home/section_folder/mobile_home_section_folder_header.dart';
import 'package:appflowy/mobile/presentation/presentation.dart';
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_layout.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
import '../../shared/dir.dart';
import '../../shared/mock/mock_file_picker.dart';
import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('document title:', () {
testWidgets('create a new page, the title should be empty', (tester) async {
await tester.launchInAnonymousMode();
final createPageButton = find.byKey(
BottomNavigationBarItemType.add.valueKey,
);
await tester.tapButton(createPageButton);
expect(find.byType(MobileDocumentScreen), findsOneWidget);
final title = tester.editor.findDocumentTitle('');
expect(title, findsOneWidget);
final textField = tester.widget<TextField>(title);
expect(textField.focusNode!.hasFocus, isTrue);
// input new name and press done button
const name = 'test document';
await tester.enterText(title, name);
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pumpAndSettle();
final newTitle = tester.editor.findDocumentTitle(name);
expect(newTitle, findsOneWidget);
expect(textField.controller!.text, name);
// the document should get focus
final editor = tester.widget<AppFlowyEditorPage>(
find.byType(AppFlowyEditorPage),
);
expect(
editor.editorState.selection,
Selection.collapsed(Position(path: [0])),
);
});
});
}

View File

@ -3,6 +3,7 @@ import 'dart:io';
import 'desktop_runner_1.dart';
import 'desktop_runner_2.dart';
import 'desktop_runner_3.dart';
import 'desktop_runner_4.dart';
import 'mobile_runner.dart';
/// The main task runner for all integration tests in AppFlowy.
@ -17,6 +18,7 @@ Future<void> main() async {
await runIntegration1OnDesktop();
await runIntegration2OnDesktop();
await runIntegration3OnDesktop();
await runIntegration4OnDesktop();
} else if (Platform.isIOS || Platform.isAndroid) {
await runIntegrationOnMobile();
} else {

View File

@ -1,11 +1,5 @@
import 'dart:io';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space_menu.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy/core/config/kv.dart';
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
@ -17,10 +11,12 @@ import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/presentation/screens/screens.dart';
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/footer/sidebar_footer.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_new_page_button.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space_header.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space_menu.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
@ -40,6 +36,10 @@ import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'emoji.dart';
@ -324,14 +324,10 @@ extension CommonOperations on WidgetTester {
}
await pumpAndSettle();
final defaultPageName = layout == ViewLayoutPB.Document
? '' // the document name is empty by default
: LocaleKeys.menuAppHeader_defaultNewPageName.tr();
// hover on it and change it's name
if (name != null) {
await hoverOnPageName(
defaultPageName,
layout.defaultName,
layout: layout,
onHover: () async {
await renamePage(name);
@ -345,7 +341,7 @@ extension CommonOperations on WidgetTester {
if (openAfterCreated) {
await openPage(
// if the name is null, use the default name
name ?? defaultPageName,
name ?? layout.defaultName,
layout: layout,
);
await pumpAndSettle();

View File

@ -1,14 +1,12 @@
import 'dart:async';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_add_button.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/draggable_option_button.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/cover_editor.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/cover_title.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_cover_widget.dart';
@ -19,8 +17,11 @@ import 'package:appflowy/shared/icon_emoji_picker/emoji_skin_tone.dart';
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:universal_platform/universal_platform.dart';
import 'util.dart';
@ -330,8 +331,12 @@ class EditorOperations {
}
Finder findDocumentTitle(String? title) {
final parent = UniversalPlatform.isDesktop
? find.byType(CoverTitle)
: find.byType(DocumentImmersiveCover);
return find.descendant(
of: find.byType(CoverTitle),
of: parent,
matching: find.byWidgetPredicate(
(widget) {
if (widget is! TextField) {

View File

@ -7,6 +7,7 @@ import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/recent/recent_views_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
@ -93,7 +94,7 @@ enum MobilePaneActionType {
Navigator.of(sheetContext).pop();
viewBloc.add(
ViewEvent.createView(
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
layout.defaultName,
layout,
section: spaceType!.toViewSectionPB,
),

View File

@ -93,7 +93,7 @@ class MobileSpace extends StatelessWidget {
Navigator.of(sheetContext).pop();
context.read<SpaceBloc>().add(
SpaceEvent.createPage(
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
name: layout.defaultName,
layout: layout,
index: 0,
),

View File

@ -1,4 +1,3 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/presentation/home/favorite_folder/favorite_space.dart';
import 'package:appflowy/mobile/presentation/home/home_space/home_space.dart';
@ -11,10 +10,10 @@ import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -187,7 +186,7 @@ class _MobileSpaceTabState extends State<MobileSpaceTab>
if (context.read<SpaceBloc>().state.spaces.isNotEmpty) {
context.read<SpaceBloc>().add(
SpaceEvent.createPage(
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
name: layout.defaultName,
layout: layout,
),
);
@ -195,7 +194,7 @@ class _MobileSpaceTabState extends State<MobileSpaceTab>
// only support create document in section
context.read<SidebarSectionsBloc>().add(
SidebarSectionsEvent.createRootViewInSection(
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
name: layout.defaultName,
index: 0,
viewSection: FolderSpaceType.public.toViewSectionPB,
),

View File

@ -29,23 +29,41 @@ final PropertyValueNotifier<ViewLayoutPB?> mobileCreateNewPageNotifier =
final ValueNotifier<BottomNavigationBarActionType> bottomNavigationBarType =
ValueNotifier(BottomNavigationBarActionType.home);
const _homeLabel = 'home';
const _addLabel = 'add';
const _notificationLabel = 'notification';
enum BottomNavigationBarItemType {
home,
add,
notification;
String get label {
return switch (this) {
BottomNavigationBarItemType.home => 'home',
BottomNavigationBarItemType.add => 'add',
BottomNavigationBarItemType.notification => 'notification',
};
}
ValueKey get valueKey {
return ValueKey(label);
}
}
final _items = <BottomNavigationBarItem>[
const BottomNavigationBarItem(
label: _homeLabel,
icon: FlowySvg(FlowySvgs.m_home_unselected_m),
activeIcon: FlowySvg(FlowySvgs.m_home_selected_m, blendMode: null),
BottomNavigationBarItem(
key: BottomNavigationBarItemType.home.valueKey,
label: BottomNavigationBarItemType.home.label,
icon: const FlowySvg(FlowySvgs.m_home_unselected_m),
activeIcon: const FlowySvg(FlowySvgs.m_home_selected_m, blendMode: null),
),
const BottomNavigationBarItem(
label: _addLabel,
icon: FlowySvg(FlowySvgs.m_home_add_m),
BottomNavigationBarItem(
key: BottomNavigationBarItemType.add.valueKey,
label: BottomNavigationBarItemType.add.label,
icon: const FlowySvg(FlowySvgs.m_home_add_m),
),
const BottomNavigationBarItem(
label: _notificationLabel,
icon: _NotificationNavigationBarItemIcon(),
activeIcon: _NotificationNavigationBarItemIcon(
BottomNavigationBarItem(
key: BottomNavigationBarItemType.notification.valueKey,
label: BottomNavigationBarItemType.notification.label,
icon: const _NotificationNavigationBarItemIcon(),
activeIcon: const _NotificationNavigationBarItemIcon(
isActive: true,
),
),
@ -234,11 +252,11 @@ class _HomePageNavigationBar extends StatelessWidget {
closePopupMenu();
final label = _items[bottomBarIndex].label;
if (label == _addLabel) {
if (label == BottomNavigationBarItemType.add.label) {
// show an add dialog
mobileCreateNewPageNotifier.value = ViewLayoutPB.Document;
return;
} else if (label == _notificationLabel) {
} else if (label == BottomNavigationBarItemType.notification.label) {
getIt<ReminderBloc>().add(const ReminderEvent.refresh());
}
// When navigating to a new branch, it's recommended to use the goBranch

View File

@ -1,6 +1,7 @@
import 'dart:io';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
@ -8,6 +9,7 @@ import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -260,7 +262,9 @@ class _SingleMobileInnerViewItemState extends State<SingleMobileInnerViewItem> {
// title
Expanded(
child: FlowyText.regular(
widget.view.name,
widget.view.name.isEmpty
? LocaleKeys.menuAppHeader_defaultNewPageName.tr()
: widget.view.name,
fontSize: 16.0,
figmaLineHeight: 20.0,
overflow: TextOverflow.ellipsis,

View File

@ -1,7 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/plugins/document/application/document_bloc.dart';
@ -28,6 +26,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:cross_file/cross_file.dart';
import 'package:desktop_drop/desktop_drop.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';
import 'package:universal_platform/universal_platform.dart';
@ -162,6 +161,8 @@ class _DocumentPageState extends State<DocumentPage>
child = BlocBuilder<DocumentPageStyleBloc, DocumentPageStyleState>(
builder: (context, styleState) => AppFlowyEditorPage(
editorState: state.editorState!,
// if the view's name is empty, focus on the title
autoFocus: widget.view.name.isEmpty ? false : null,
styleCustomizer: EditorStyleCustomizer(
context: context,
width: width,

View File

@ -1,8 +1,5 @@
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy/plugins/document/application/document_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_configuration.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/background_color/theme_background_color.dart';
@ -21,6 +18,8 @@ import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:collection/collection.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/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:universal_platform/universal_platform.dart';
@ -382,7 +381,8 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage>
dateOrReminderSlashMenuItem,
photoGallerySlashMenuItem,
fileSlashMenuItem,
subPageSlashMenuItem,
// disable subPageSlashMenuItem temporarily, enable it in version 0.7.2
// subPageSlashMenuItem,
];
}

View File

@ -15,6 +15,7 @@ import 'package:appflowy/shared/google_fonts_extension.dart';
import 'package:appflowy/util/string_extension.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
@ -57,6 +58,9 @@ class _DocumentImmersiveCoverState extends State<DocumentImmersiveCover> {
void initState() {
super.initState();
selectionNotifier?.addListener(_unfocus);
if (widget.view.name.isEmpty) {
focusNode.requestFocus();
}
}
@override
@ -161,12 +165,12 @@ class _DocumentImmersiveCoverState extends State<DocumentImmersiveCover> {
controller: textEditingController,
focusNode: focusNode,
minFontSize: 18.0,
decoration: const InputDecoration(
decoration: InputDecoration(
border: InputBorder.none,
enabledBorder: InputBorder.none,
disabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
hintText: '',
hintText: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
contentPadding: EdgeInsets.zero,
),
scrollController: scrollController,
@ -183,11 +187,15 @@ class _DocumentImmersiveCoverState extends State<DocumentImmersiveCover> {
const Duration(milliseconds: 300),
() => _rename(name),
),
onSubmitted: (name) => Debounce.debounce(
onSubmitted: (name) {
// focus on the document
_createNewLine();
Debounce.debounce(
'rename',
const Duration(milliseconds: 300),
() => _rename(name),
),
);
},
);
}
@ -314,4 +322,35 @@ class _DocumentImmersiveCoverState extends State<DocumentImmersiveCover> {
scrollController.position.jumpTo(0);
context.read<ViewBloc>().add(ViewEvent.rename(name));
}
Future<void> _createNewLine() async {
focusNode.unfocus();
final selection = textEditingController.selection;
final text = textEditingController.text;
// split the text into two lines based on the cursor position
final parts = [
text.substring(0, selection.baseOffset),
text.substring(selection.baseOffset),
];
textEditingController.text = parts[0];
final editorState = context.read<DocumentBloc>().state.editorState;
if (editorState == null) {
Log.info('editorState is null when creating new line');
return;
}
final transaction = editorState.transaction;
transaction.insertNode([0], paragraphNode(text: parts[1]));
await editorState.apply(transaction);
// update selection instead of using afterSelection in transaction,
// because it will cause the cursor to jump
await editorState.updateSelectionWithReason(
Selection.collapsed(Position(path: [0])),
// trigger the keyboard service.
reason: SelectionUpdateReason.uiEvent,
);
}
}

View File

@ -1,6 +1,7 @@
import 'dart:convert';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/plugins/ai_chat/chat.dart';
import 'package:appflowy/plugins/database/board/presentation/board_page.dart';
@ -15,6 +16,7 @@ import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
class PluginArgumentKeys {
@ -297,6 +299,11 @@ extension ViewLayoutExtension on ViewLayoutPB {
ViewLayoutPB.Document || ViewLayoutPB.Chat => false,
_ => throw Exception('Unknown layout type'),
};
String get defaultName => switch (this) {
ViewLayoutPB.Document => '',
_ => LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
};
}
extension ViewFinder on List<ViewPB> {

View File

@ -862,13 +862,6 @@ void moveViewCrossSpace(
return;
}
final fromSection = spaceType == FolderSpaceType.public
? ViewSectionPB.Private
: ViewSectionPB.Public;
final toSection = spaceType == FolderSpaceType.public
? ViewSectionPB.Public
: ViewSectionPB.Private;
final currentSpace = context.read<SpaceBloc>().state.currentSpace;
if (currentSpace != null &&
toSpace != null &&
@ -884,14 +877,8 @@ void moveViewCrossSpace(
from,
toId,
null,
fromSection,
toSection,
),
);
context.read<ViewBloc>().add(
ViewEvent.updateViewVisibility(
from,
spaceType == FolderSpaceType.public,
null,
null,
),
);
}

View File

@ -876,7 +876,7 @@
"itemThree": "5 GB",
"itemFour": "yes",
"itemFive": "yes",
"itemSix": "100 lifetime",
"itemSix": "20 lifetime",
"itemFileUpload": "Up to 7 MB",
"intelligentSearch": "Intelligent search"
},

View File

@ -854,7 +854,7 @@
"itemThree": "5 GB",
"itemFour": "はい",
"itemFive": "はい",
"itemSix": "100回のライフタイムレスポンス",
"itemSix": "20回のライフタイムレスポンス",
"itemFileUpload": "最大7 MB",
"intelligentSearch": "インテリジェント検索"
},

View File

@ -817,7 +817,7 @@
"itemThree": "5 GB",
"itemFour": "evet",
"itemFive": "evet",
"itemSix": "100 ömür boyu",
"itemSix": "20 ömür boyu",
"itemSeven": " "
},
"proLabels": {

View File

@ -856,7 +856,7 @@
"itemThree": "5 ГБ",
"itemFour": "так",
"itemFive": "так",
"itemSix": "100 років життя",
"itemSix": "20 років життя",
"itemFileUpload": "До 7 Мб",
"intelligentSearch": "Інтелектуальний пошук"
},

View File

@ -854,7 +854,7 @@
"itemThree": "5 GB",
"itemFour": "Đúng",
"itemFive": "Đúng",
"itemSix": "100 trọn đời",
"itemSix": "20 trọn đời",
"intelligentSearch": "Tìm kiếm thông minh"
},
"proLabels": {