fix: create new page error on mobile (#4984)

* chore: update translations

* fix: remove sidebar_root_view_bloc

* fix: remove sidebar_root_view_bloc

* chore: fix ios ci

* feat: customize image cache path
This commit is contained in:
Lucas.Xu 2024-03-25 22:08:52 +07:00 committed by GitHub
parent 701384cd74
commit de3e7ca9be
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 158 additions and 230 deletions

View File

@ -0,0 +1,49 @@
// ignore_for_file: unused_import
import 'dart:io';
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/editor/mobile_editor_screen.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/plugins/document/document_page.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/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_user_view.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/uuid.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('create new page', () {
testWidgets('create document', (tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.local,
);
// click the anonymousSignInButton
final anonymousSignInButton = find.byType(SignInAnonymousButton);
expect(anonymousSignInButton, findsOneWidget);
await tester.tapButton(anonymousSignInButton);
// tap the create page button
final createPageButton = find.byKey(mobileCreateNewPageButtonKey);
await tester.tapButton(createPageButton);
expect(find.byType(MobileDocumentScreen), findsOneWidget);
});
});
}

View File

@ -3,7 +3,7 @@ import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/presentation/home/favorite_folder/mobile_home_favorite_folder.dart';
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_state_container.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
import 'package:appflowy/workspace/application/menu/sidebar_root_views_bloc.dart';
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:easy_localization/easy_localization.dart';
@ -27,9 +27,9 @@ class MobileFavoritePageFolder extends StatelessWidget {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (_) => SidebarRootViewsBloc()
create: (_) => SidebarSectionsBloc()
..add(
SidebarRootViewsEvent.initial(
SidebarSectionsEvent.initial(
userProfile,
workspaceSetting.workspaceId,
),
@ -41,7 +41,7 @@ class MobileFavoritePageFolder extends StatelessWidget {
],
child: MultiBlocListener(
listeners: [
BlocListener<SidebarRootViewsBloc, SidebarRootViewState>(
BlocListener<SidebarSectionsBloc, SidebarSectionsState>(
listenWhen: (p, c) =>
p.lastCreatedRootView?.id != c.lastCreatedRootView?.id,
listener: (context, state) =>

View File

@ -4,11 +4,13 @@ import 'package:appflowy/mobile/presentation/favorite/mobile_favorite_folder.dar
import 'package:appflowy/mobile/presentation/home/mobile_home_page_header.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/workspace/application/user/prelude.dart';
import 'package:appflowy/workspace/presentation/home/errors/workspace_failed_screen.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MobileFavoriteScreen extends StatelessWidget {
const MobileFavoriteScreen({
@ -50,9 +52,15 @@ class MobileFavoriteScreen extends StatelessWidget {
return Scaffold(
body: SafeArea(
child: MobileFavoritePage(
userProfile: userProfile,
workspaceSetting: workspaceSetting,
child: BlocProvider(
create: (_) => UserWorkspaceBloc(userProfile: userProfile)
..add(
const UserWorkspaceEvent.initial(),
),
child: MobileFavoritePage(
userProfile: userProfile,
workspaceSetting: workspaceSetting,
),
),
),
);

View File

@ -3,6 +3,7 @@ import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/mobile/presentation/home/section_folder/mobile_home_section_folder.dart';
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
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/user/user_workspace_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
@ -61,17 +62,20 @@ class MobileFolders extends StatelessWidget {
? [
MobileSectionFolder(
title: LocaleKeys.sideBar_public.tr(),
categoryType: FolderCategoryType.public,
views: state.section.publicViews,
),
const VSpace(8.0),
MobileSectionFolder(
title: LocaleKeys.sideBar_private.tr(),
categoryType: FolderCategoryType.private,
views: state.section.privateViews,
),
]
: [
MobileSectionFolder(
title: LocaleKeys.sideBar_personal.tr(),
categoryType: FolderCategoryType.public,
views: state.section.publicViews,
),
],

View File

@ -14,15 +14,17 @@ class MobileSectionFolder extends StatelessWidget {
super.key,
required this.title,
required this.views,
required this.categoryType,
});
final String title;
final List<ViewPB> views;
final FolderCategoryType categoryType;
@override
Widget build(BuildContext context) {
return BlocProvider<FolderBloc>(
create: (context) => FolderBloc(type: FolderCategoryType.private)
create: (context) => FolderBloc(type: categoryType)
..add(
const FolderEvent.initial(),
),
@ -50,7 +52,7 @@ class MobileSectionFolder extends StatelessWidget {
key: ValueKey(
'${FolderCategoryType.private.name} ${view.id}',
),
categoryType: FolderCategoryType.private,
categoryType: categoryType,
isFirstChild: view.id == views.first.id,
view: view,
level: 0,

View File

@ -1,12 +1,15 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/menu/sidebar_root_views_bloc.dart';
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.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';
@visibleForTesting
const Key mobileCreateNewPageButtonKey = Key('mobileCreateNewPageButtonKey');
class MobileSectionFolderHeader extends StatefulWidget {
const MobileSectionFolderHeader({
super.key,
@ -60,6 +63,7 @@ class _MobileSectionFolderHeaderState extends State<MobileSectionFolderHeader> {
),
),
FlowyIconButton(
key: mobileCreateNewPageButtonKey,
hoverColor: Theme.of(context).colorScheme.secondaryContainer,
iconPadding: const EdgeInsets.all(2),
height: iconSize,
@ -69,11 +73,11 @@ class _MobileSectionFolderHeaderState extends State<MobileSectionFolderHeader> {
size: Size.square(iconSize),
),
onPressed: () {
context.read<SidebarRootViewsBloc>().add(
SidebarRootViewsEvent.createRootView(
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
context.read<SidebarSectionsBloc>().add(
SidebarSectionsEvent.createRootViewInSection(
name: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
index: 0,
viewSection: ViewSectionPB.Private,
viewSection: ViewSectionPB.Public,
),
);
},

View File

@ -1,16 +1,26 @@
import 'package:appflowy/shared/appflowy_cache_manager.dart';
import 'package:appflowy/startup/tasks/prelude.dart';
import 'package:file/file.dart' hide FileSystem;
import 'package:file/local.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:path/path.dart' as p;
class CustomImageCacheManager extends CacheManager
with ImageCacheManager
implements ICache {
CustomImageCacheManager._() : super(Config(key));
CustomImageCacheManager._()
: super(
Config(
key,
fileSystem: CustomIOFileSystem(key),
),
);
factory CustomImageCacheManager() => _instance;
static final CustomImageCacheManager _instance = CustomImageCacheManager._();
static const key = 'appflowy_image_cache';
static const key = 'image_cache';
@override
Future<int> cacheSize() async {
@ -24,3 +34,28 @@ class CustomImageCacheManager extends CacheManager
await emptyCache();
}
}
class CustomIOFileSystem implements FileSystem {
CustomIOFileSystem(this._cacheKey) : _fileDir = createDirectory(_cacheKey);
final Future<Directory> _fileDir;
final String _cacheKey;
static Future<Directory> createDirectory(String key) async {
final baseDir = await appFlowyApplicationDataDirectory();
final path = p.join(baseDir.path, key);
const fs = LocalFileSystem();
final directory = fs.directory(path);
await directory.create(recursive: true);
return directory;
}
@override
Future<File> createFile(String name) async {
final directory = await _fileDir;
if (!(await directory.exists())) {
await createDirectory(_cacheKey);
}
return directory.childFile(name);
}
}

View File

@ -83,9 +83,9 @@ enum FeatureFlag {
switch (this) {
case FeatureFlag.collaborativeWorkspace:
return true;
return false;
case FeatureFlag.membersSettings:
return true;
return false;
case FeatureFlag.syncDocument:
return false;
case FeatureFlag.unknown:

View File

@ -1,2 +1,2 @@
export 'menu_user_bloc.dart';
export 'sidebar_root_views_bloc.dart';
export 'sidebar_sections_bloc.dart';

View File

@ -1,175 +0,0 @@
import 'dart:async';
import 'package:appflowy/workspace/application/workspace/workspace_listener.dart';
import 'package:appflowy/workspace/application/workspace/workspace_service.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:appflowy_result/appflowy_result.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'sidebar_root_views_bloc.freezed.dart';
class SidebarRootViewsBloc
extends Bloc<SidebarRootViewsEvent, SidebarRootViewState> {
SidebarRootViewsBloc() : super(SidebarRootViewState.initial()) {
_dispatch();
}
late WorkspaceService _workspaceService;
WorkspaceListener? _listener;
@override
Future<void> close() async {
await _listener?.stop();
return super.close();
}
void _dispatch() {
on<SidebarRootViewsEvent>(
(event, emit) async {
await event.when(
initial: (userProfile, workspaceId) async {
_initial(userProfile, workspaceId);
await _fetchRootViews(emit);
},
reset: (userProfile, workspaceId) async {
await _listener?.stop();
_initial(userProfile, workspaceId);
await _fetchRootViews(emit);
},
createRootView: (name, desc, index, section) async {
final result = await _workspaceService.createView(
name: name,
desc: desc,
index: index,
viewSection: section,
);
result.fold(
(view) => emit(state.copyWith(lastCreatedRootView: view)),
(error) {
Log.error(error);
emit(
state.copyWith(
successOrFailure: FlowyResult.failure(error),
),
);
},
);
},
didReceiveViews: (viewsOrFailure) async {
// emit(
// viewsOrFailure.fold(
// (views) => state.copyWith(
// views: views,
// successOrFailure: FlowyResult.success(null),
// ),
// (err) =>
// state.copyWith(successOrFailure: FlowyResult.failure(err)),
// ),
// );
},
moveRootView: (int fromIndex, int toIndex) {
// if (state.views.length > fromIndex) {
// final view = state.views[fromIndex];
// _workspaceService.moveApp(
// appId: view.id,
// fromIndex: fromIndex,
// toIndex: toIndex,
// );
// final views = List<ViewPB>.from(state.views);
// views.insert(toIndex, views.removeAt(fromIndex));
// emit(state.copyWith(views: views));
// }
},
);
},
);
}
Future<void> _fetchRootViews(
Emitter<SidebarRootViewState> emit,
) async {
try {
final publicViews = await _workspaceService.getPublicViews().getOrThrow();
final privateViews =
await _workspaceService.getPrivateViews().getOrThrow();
emit(
state.copyWith(
publicViews: publicViews,
privateViews: privateViews,
),
);
} catch (e) {
Log.error(e);
// TODO: handle error
// emit(
// state.copyWith(
// successOrFailure: FlowyResult.failure(e),
// ),
// );
}
}
void _handleAppsOrFail(FlowyResult<List<ViewPB>, FlowyError> viewsOrFail) {
viewsOrFail.fold(
(views) => add(
SidebarRootViewsEvent.didReceiveViews(FlowyResult.success(views)),
),
(error) => add(
SidebarRootViewsEvent.didReceiveViews(FlowyResult.failure(error)),
),
);
}
void _initial(UserProfilePB userProfile, String workspaceId) {
_workspaceService = WorkspaceService(workspaceId: workspaceId);
_listener = WorkspaceListener(
user: userProfile,
workspaceId: workspaceId,
)..start(appsChanged: _handleAppsOrFail);
}
}
@freezed
class SidebarRootViewsEvent with _$SidebarRootViewsEvent {
const factory SidebarRootViewsEvent.initial(
UserProfilePB userProfile,
String workspaceId,
) = _Initial;
const factory SidebarRootViewsEvent.reset(
UserProfilePB userProfile,
String workspaceId,
) = _Reset;
const factory SidebarRootViewsEvent.createRootView(
String name, {
String? desc,
int? index,
required ViewSectionPB viewSection,
}) = _createRootView;
const factory SidebarRootViewsEvent.moveRootView(
int fromIndex,
int toIndex,
) = _MoveRootView;
const factory SidebarRootViewsEvent.didReceiveViews(
FlowyResult<List<ViewPB>, FlowyError> appsOrFail,
) = _ReceiveApps;
}
@freezed
class SidebarRootViewState with _$SidebarRootViewState {
const factory SidebarRootViewState({
@Default([]) List<ViewPB> privateViews,
@Default([]) List<ViewPB> publicViews,
required FlowyResult<void, FlowyError> successOrFailure,
@Default(null) ViewPB? lastCreatedRootView,
}) = _SidebarRootViewState;
factory SidebarRootViewState.initial() => SidebarRootViewState(
successOrFailure: FlowyResult.success(null),
);
}

View File

@ -18,12 +18,16 @@ class SectionFolder extends StatelessWidget {
required this.categoryType,
required this.views,
this.isHoverEnabled = true,
required this.expandButtonTooltip,
required this.addButtonTooltip,
});
final String title;
final FolderCategoryType categoryType;
final List<ViewPB> views;
final bool isHoverEnabled;
final String expandButtonTooltip;
final String addButtonTooltip;
@override
Widget build(BuildContext context) {
@ -97,20 +101,4 @@ class SectionFolder extends StatelessWidget {
),
);
}
String get expandButtonTooltip {
return switch (categoryType) {
FolderCategoryType.public => LocaleKeys.sideBar_clickToHidePublic.tr(),
FolderCategoryType.private => LocaleKeys.sideBar_clickToHidePrivate.tr(),
_ => '',
};
}
String get addButtonTooltip {
return switch (categoryType) {
FolderCategoryType.public => LocaleKeys.sideBar_addAPageToPublic.tr(),
FolderCategoryType.private => LocaleKeys.sideBar_addAPageToPrivate.tr(),
_ => '',
};
}
}

View File

@ -93,6 +93,8 @@ class PrivateSectionFolder extends SectionFolder {
}) : super(
title: LocaleKeys.sideBar_private.tr(),
categoryType: FolderCategoryType.private,
expandButtonTooltip: LocaleKeys.sideBar_clickToHidePrivate.tr(),
addButtonTooltip: LocaleKeys.sideBar_addAPageToPrivate.tr(),
);
}
@ -103,6 +105,8 @@ class PublicSectionFolder extends SectionFolder {
}) : super(
title: LocaleKeys.sideBar_public.tr(),
categoryType: FolderCategoryType.public,
expandButtonTooltip: LocaleKeys.sideBar_clickToHidePublic.tr(),
addButtonTooltip: LocaleKeys.sideBar_addAPageToPublic.tr(),
);
}
@ -113,5 +117,7 @@ class PersonalSectionFolder extends SectionFolder {
}) : super(
title: LocaleKeys.sideBar_personal.tr(),
categoryType: FolderCategoryType.public,
expandButtonTooltip: LocaleKeys.sideBar_clickToHidePersonal.tr(),
addButtonTooltip: LocaleKeys.sideBar_addAPage.tr(),
);
}

View File

@ -79,16 +79,15 @@ class SettingsMenu extends StatelessWidget {
icon: Icons.people,
changeSelectedPage: changeSelectedPage,
),
// enable in v0.5.3 temporarily
// if (kDebugMode)
SettingsMenuElement(
// no need to translate this page
page: SettingsPage.featureFlags,
selectedPage: currentPage,
label: 'Feature Flags',
icon: Icons.flag,
changeSelectedPage: changeSelectedPage,
),
// SettingsMenuElement(
// // no need to translate this page
// page: SettingsPage.featureFlags,
// selectedPage: currentPage,
// label: 'Feature Flags',
// icon: Icons.flag,
// changeSelectedPage: changeSelectedPage,
// ),
],
),
);

View File

@ -459,7 +459,7 @@ packages:
source: hosted
version: "2.1.2"
file:
dependency: transitive
dependency: "direct main"
description:
name: file
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
@ -565,10 +565,11 @@ packages:
flutter_cache_manager:
dependency: "direct main"
description:
name: flutter_cache_manager
sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba"
url: "https://pub.dev"
source: hosted
path: flutter_cache_manager
ref: HEAD
resolved-ref: fbab857b1b1d209240a146d32f496379b9f62276
url: "https://github.com/LucasXu0/flutter_cache_manager.git"
source: git
version: "3.3.1"
flutter_chat_types:
dependency: transitive

View File

@ -131,6 +131,7 @@ dependencies:
flutter_cache_manager: ^3.3.1
share_plus: ^7.2.1
sheet:
file: ^7.0.0
dev_dependencies:
flutter_lints: ^3.0.1
@ -177,6 +178,12 @@ dependency_overrides:
uuid: ^4.1.0
flutter_cache_manager:
git:
url: https://github.com/LucasXu0/flutter_cache_manager.git
commit: fbab857b1b1d209240a146d32f496379b9f62276
path: flutter_cache_manager
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your

View File

@ -211,13 +211,13 @@
"private": "Private",
"public": "Public",
"favorites": "Favorites",
"clickToHidePrivate": "Click to hide private section\nPages you created here are only visible to you",
"clickToHidePublic": "Click to hide public section\nPages you created here are visible to every member",
"clickToHidePersonal": "Click to hide personal section",
"clickToHideFavorites": "Click to hide favorite section",
"clickToHidePrivate": "Click to hide private space\nPages you created here are only visible to you",
"clickToHidePublic": "Click to hide public space\nPages you created here are visible to every member",
"clickToHidePersonal": "Click to hide personal space",
"clickToHideFavorites": "Click to hide favorite space",
"addAPage": "Add a page",
"addAPageToPrivate": "Add a page to private section",
"addAPageToPublic": "Add a page to public section",
"addAPageToPrivate": "Add a page to private space",
"addAPageToPublic": "Add a page to public space",
"recent": "Recent"
},
"notifications": {