diff --git a/frontend/appflowy_flutter/ios/Podfile.lock b/frontend/appflowy_flutter/ios/Podfile.lock
index 84858fb29a..cf59c986e6 100644
--- a/frontend/appflowy_flutter/ios/Podfile.lock
+++ b/frontend/appflowy_flutter/ios/Podfile.lock
@@ -48,6 +48,9 @@ PODS:
- fluttertoast (0.0.2):
- Flutter
- Toast
+ - FMDB (2.7.5):
+ - FMDB/standard (= 2.7.5)
+ - FMDB/standard (2.7.5)
- image_gallery_saver (2.0.2):
- Flutter
- image_picker_ios (0.0.1):
@@ -72,6 +75,9 @@ PODS:
- FlutterMacOS
- sign_in_with_apple (0.0.1):
- Flutter
+ - sqflite (0.0.3):
+ - Flutter
+ - FMDB (>= 2.7.5)
- super_native_extensions (0.0.1):
- Flutter
- SwiftyGif (5.4.3)
@@ -99,6 +105,7 @@ DEPENDENCIES:
- rich_clipboard_ios (from `.symlinks/plugins/rich_clipboard_ios/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`)
+ - sqflite (from `.symlinks/plugins/sqflite/ios`)
- super_native_extensions (from `.symlinks/plugins/super_native_extensions/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`)
@@ -107,6 +114,7 @@ SPEC REPOS:
trunk:
- DKImagePickerController
- DKPhotoGallery
+ - FMDB
- ReachabilitySwift
- SDWebImage
- SwiftyGif
@@ -147,6 +155,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sign_in_with_apple:
:path: ".symlinks/plugins/sign_in_with_apple/ios"
+ sqflite:
+ :path: ".symlinks/plugins/sqflite/ios"
super_native_extensions:
:path: ".symlinks/plugins/super_native_extensions/ios"
url_launcher_ios:
@@ -165,6 +175,7 @@ SPEC CHECKSUMS:
flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
fluttertoast: fafc4fa4d01a6a9e4f772ecd190ffa525e9e2d9c
+ FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5
integration_test: 13825b8a9334a850581300559b8839134b124670
@@ -176,6 +187,7 @@ SPEC CHECKSUMS:
SDWebImage: b9a731e1d6307f44ca703b3976d18c24ca561e84
shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c
sign_in_with_apple: f3bf75217ea4c2c8b91823f225d70230119b8440
+ sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
super_native_extensions: 4916b3c627a9c7fffdc48a23a9eca0b1ac228fa7
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
diff --git a/frontend/appflowy_flutter/ios/Runner/Info.plist b/frontend/appflowy_flutter/ios/Runner/Info.plist
index 91ee44ca33..8c605b9d3a 100644
--- a/frontend/appflowy_flutter/ios/Runner/Info.plist
+++ b/frontend/appflowy_flutter/ios/Runner/Info.plist
@@ -1,68 +1,70 @@
-
- NSCameraUsageDescription
- AppFlowy requires access to the camera.
- NSPhotoLibraryUsageDescription
- AppFlowy requires access to the photo library.
- CADisableMinimumFrameDurationOnPhone
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleLocalizations
-
- en
-
- CFBundleName
- AppFlowy
- CFBundlePackageType
- APPL
- CFBundleShortVersionString
- $(FLUTTER_BUILD_NAME)
- CFBundleSignature
- ????
- CFBundleURLTypes
-
-
- CFBundleURLName
-
- CFBundleURLSchemes
-
- appflowy-flutter
-
-
-
- CFBundleVersion
- $(FLUTTER_BUILD_NUMBER)
- LSRequiresIPhoneOS
-
- UIApplicationSupportsIndirectInputEvents
-
- UILaunchStoryboardName
- LaunchScreen
- UIMainStoryboardFile
- Main
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- UISupportedInterfaceOrientations~ipad
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- UIViewControllerBasedStatusBarAppearance
-
-
-
+
+ NSCameraUsageDescription
+ AppFlowy requires access to the camera.
+ NSPhotoLibraryUsageDescription
+ AppFlowy requires access to the photo library.
+ CADisableMinimumFrameDurationOnPhone
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleLocalizations
+
+ en
+
+ FLTEnableImpeller
+
+ CFBundleName
+ AppFlowy
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleURLTypes
+
+
+ CFBundleURLName
+
+ CFBundleURLSchemes
+
+ appflowy-flutter
+
+
+
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ UIApplicationSupportsIndirectInputEvents
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UIViewControllerBasedStatusBarAppearance
+
+
+
\ No newline at end of file
diff --git a/frontend/appflowy_flutter/lib/mobile/application/mobile_router.dart b/frontend/appflowy_flutter/lib/mobile/application/mobile_router.dart
index c32463c7d8..2a8a4ef75f 100644
--- a/frontend/appflowy_flutter/lib/mobile/application/mobile_router.dart
+++ b/frontend/appflowy_flutter/lib/mobile/application/mobile_router.dart
@@ -2,12 +2,22 @@ import 'package:appflowy/mobile/presentation/database/mobile_board_screen.dart';
import 'package:appflowy/mobile/presentation/database/mobile_calendar_screen.dart';
import 'package:appflowy/mobile/presentation/database/mobile_grid_screen.dart';
import 'package:appflowy/mobile/presentation/presentation.dart';
+import 'package:appflowy/startup/startup.dart';
+import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
+import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
+class MobileRouterRecord {
+ PropertyValueNotifier lastPushedRouter =
+ PropertyValueNotifier('');
+}
+
extension MobileRouter on BuildContext {
Future pushView(ViewPB view) async {
+ await FolderEventSetLatestView(ViewIdPB(value: view.id)).send();
+ getIt().lastPushedRouter.value = view.routeName;
push(
Uri(
path: view.routeName,
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart
index 99fef7559e..77a975744f 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart
@@ -117,15 +117,19 @@ class _MobileViewPageState extends State {
appBar: AppBar(
titleSpacing: 0,
title: Row(
+ mainAxisSize: MainAxisSize.min,
children: [
if (icon != null)
FlowyText(
'$icon ',
fontSize: 22.0,
),
- FlowyText.regular(
- view?.name ?? widget.title ?? '',
- fontSize: 14.0,
+ Expanded(
+ child: FlowyText.regular(
+ view?.name ?? widget.title ?? '',
+ fontSize: 14.0,
+ overflow: TextOverflow.ellipsis,
+ ),
),
],
),
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart
index 87da5a6b3c..ec54775d62 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart
@@ -2,7 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/home/mobile_folders.dart';
import 'package:appflowy/mobile/presentation/home/mobile_home_page_header.dart';
-import 'package:appflowy/mobile/presentation/home/mobile_home_page_recent_files.dart';
+import 'package:appflowy/mobile/presentation/home/recent_folder/mobile_home_recent_views.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/workspace/presentation/home/errors/workspace_failed_screen.dart';
@@ -97,8 +97,7 @@ class MobileHomePage extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
// Recent files
- const MobileHomePageRecentFilesWidget(),
- const Divider(),
+ const MobileRecentFolder(),
// Folders
Padding(
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page_recent_files.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page_recent_files.dart
deleted file mode 100644
index 010e8d30a9..0000000000
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page_recent_files.dart
+++ /dev/null
@@ -1,122 +0,0 @@
-import 'package:flowy_infra_ui/flowy_infra_ui.dart';
-import 'package:flutter/material.dart';
-
-// TODO(yijing): replace by real data later
-class MockRecentFile {
- MockRecentFile({
- required this.title,
- });
- final String title;
- final String icon = '🐼';
-
- final image = Image.asset(
- 'assets/images/app_flowy_abstract_cover_1.jpg',
- fit: BoxFit.cover,
- );
-}
-
-final recentFilesList = [
- MockRecentFile(title: 'Work out plan'),
- MockRecentFile(title: 'Travel plan'),
- MockRecentFile(title: 'Meeting notes'),
- MockRecentFile(title: 'Recipes'),
- MockRecentFile(title: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'),
-];
-
-class MobileHomePageRecentFilesWidget extends StatelessWidget {
- const MobileHomePageRecentFilesWidget({super.key});
-
- @override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
- // TODO: implement the details later.
- return SizedBox(
- height: 168,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- const Padding(
- padding: EdgeInsets.symmetric(horizontal: 16),
- child: FlowyText.semibold(
- 'Recent',
- fontSize: 20.0,
- ),
- ),
- Expanded(
- child: ListView.separated(
- separatorBuilder: (context, index) => const HSpace(8),
- padding: const EdgeInsets.symmetric(
- horizontal: 16,
- vertical: 8,
- ),
- scrollDirection: Axis.horizontal,
- itemCount: recentFilesList.length,
- itemBuilder: (context, index) {
- return Container(
- width: 120,
- decoration: BoxDecoration(
- color: theme.colorScheme.background,
- borderRadius: BorderRadius.circular(8),
- border: Border.all(
- color: theme.colorScheme.outline.withOpacity(0.5),
- ),
- ),
- child: Stack(
- children: [
- Align(
- alignment: Alignment.topCenter,
- child: SizedBox(
- height: 60,
- width: double.infinity,
- child: ClipRRect(
- borderRadius: const BorderRadius.only(
- topLeft: Radius.circular(8),
- topRight: Radius.circular(8),
- ),
- child: recentFilesList[index].image,
- ),
- ),
- ),
- Align(
- alignment: Alignment.centerLeft,
- child: Container(
- height: 32,
- width: 32,
- margin: const EdgeInsets.only(left: 8),
- child: Text(
- recentFilesList[index].icon,
- style: const TextStyle(fontSize: 32),
- ),
- ),
- ),
- Align(
- alignment: Alignment.bottomCenter,
- child: Container(
- height: 32,
- width: double.infinity,
- margin: const EdgeInsets.only(
- left: 8,
- right: 8,
- bottom: 8,
- ),
- child: Text(
- recentFilesList[index].title,
- softWrap: true,
- style: theme.textTheme.bodyMedium?.copyWith(
- color: theme.colorScheme.onBackground,
- ),
- maxLines: 2,
- ),
- ),
- ),
- ],
- ),
- );
- },
- ),
- ),
- ],
- ),
- );
- }
-}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/recent_folder/mobile_home_recent_views.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/recent_folder/mobile_home_recent_views.dart
new file mode 100644
index 0000000000..32156f74f1
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/recent_folder/mobile_home_recent_views.dart
@@ -0,0 +1,104 @@
+import 'package:appflowy/generated/locale_keys.g.dart';
+import 'package:appflowy/mobile/application/mobile_router.dart';
+import 'package:appflowy/mobile/presentation/home/recent_folder/mobile_recent_view.dart';
+import 'package:appflowy/startup/startup.dart';
+import 'package:appflowy_backend/dispatch/dispatch.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
+import 'package:dartz/dartz.dart' hide State;
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra_ui/style_widget/text.dart';
+import 'package:flowy_infra_ui/widget/spacing.dart';
+import 'package:flutter/material.dart';
+
+class MobileRecentFolder extends StatefulWidget {
+ const MobileRecentFolder({super.key});
+
+ @override
+ State createState() => _MobileRecentFolderState();
+}
+
+class _MobileRecentFolderState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return ValueListenableBuilder(
+ valueListenable: getIt().lastPushedRouter,
+ builder: (context, value, child) {
+ return FutureBuilder>(
+ future: FolderEventReadRecentViews().send(),
+ builder: (context, snapshot) {
+ final recentViews = snapshot.data
+ ?.fold>(
+ (l) => l.items,
+ (r) => [],
+ )
+ // only keep the first 10 items.
+ .reversed
+ .take(10)
+ .toList();
+
+ if (recentViews == null || recentViews.isEmpty) {
+ return const SizedBox.shrink();
+ }
+
+ return Column(
+ children: [
+ _RecentViews(
+ key: ValueKey(recentViews),
+ // the recent views are in reverse order
+ recentViews: recentViews,
+ ),
+ const VSpace(12.0)
+ ],
+ );
+ },
+ );
+ },
+ );
+ }
+}
+
+class _RecentViews extends StatelessWidget {
+ const _RecentViews({
+ super.key,
+ required this.recentViews,
+ });
+
+ final List recentViews;
+
+ @override
+ Widget build(BuildContext context) {
+ return SizedBox(
+ height: 168,
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ child: FlowyText.semibold(
+ LocaleKeys.sideBar_recent.tr(),
+ fontSize: 20.0,
+ ),
+ ),
+ Expanded(
+ child: ListView.separated(
+ separatorBuilder: (context, index) => const HSpace(8),
+ padding: const EdgeInsets.symmetric(
+ horizontal: 16,
+ vertical: 8,
+ ),
+ scrollDirection: Axis.horizontal,
+ itemCount: recentViews.length,
+ itemBuilder: (context, index) {
+ return MobileRecentView(
+ view: recentViews[index],
+ height: 120,
+ );
+ },
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/recent_folder/mobile_recent_view.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/recent_folder/mobile_recent_view.dart
new file mode 100644
index 0000000000..de67e5b7dc
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/recent_folder/mobile_recent_view.dart
@@ -0,0 +1,208 @@
+import 'dart:io';
+
+import 'package:appflowy/mobile/application/mobile_router.dart';
+import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
+import 'package:appflowy/workspace/application/doc/doc_listener.dart';
+import 'package:appflowy/workspace/application/view/prelude.dart';
+import 'package:appflowy/workspace/application/view/view_ext.dart';
+import 'package:appflowy_backend/dispatch/dispatch.dart';
+import 'package:appflowy_backend/protobuf/flowy-document2/protobuf.dart';
+import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
+import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flowy_infra_ui/style_widget/text.dart';
+import 'package:flutter/material.dart';
+import 'package:string_validator/string_validator.dart';
+
+class MobileRecentView extends StatefulWidget {
+ const MobileRecentView({
+ super.key,
+ required this.view,
+ required this.height,
+ });
+
+ final ViewPB view;
+ final double height;
+
+ @override
+ State createState() => _MobileRecentViewState();
+}
+
+class _MobileRecentViewState extends State {
+ late final ViewListener viewListener;
+ late ViewPB view;
+ late final DocumentListener documentListener;
+
+ @override
+ void initState() {
+ super.initState();
+
+ view = widget.view;
+
+ viewListener = ViewListener(
+ viewId: view.id,
+ )..start(
+ onViewUpdated: (view) {
+ setState(() {
+ this.view = view;
+ });
+ },
+ );
+
+ documentListener = DocumentListener(id: view.id)
+ ..start(
+ didReceiveUpdate: (document) {
+ setState(() {
+ view = view;
+ });
+ },
+ );
+ }
+
+ @override
+ void dispose() {
+ viewListener.stop();
+ documentListener.stop();
+
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final icon = view.icon.value;
+ final theme = Theme.of(context);
+
+ return GestureDetector(
+ onTap: () => context.pushView(view),
+ child: Container(
+ height: widget.height,
+ width: widget.height,
+ decoration: BoxDecoration(
+ color: theme.colorScheme.background,
+ borderRadius: BorderRadius.circular(8),
+ border: Border.all(
+ color: theme.colorScheme.outline.withOpacity(0.5),
+ ),
+ ),
+ child: Stack(
+ children: [
+ Positioned(
+ top: 0,
+ left: 0,
+ right: 0,
+ child: SizedBox(
+ height: widget.height / 2.0,
+ width: double.infinity,
+ child: ClipRRect(
+ borderRadius: const BorderRadius.only(
+ topLeft: Radius.circular(8),
+ topRight: Radius.circular(8),
+ ),
+ child: _buildCoverWidget(),
+ ),
+ ),
+ ),
+ Align(
+ alignment: Alignment.centerLeft,
+ child: Padding(
+ padding: const EdgeInsets.only(left: 4),
+ child: icon.isNotEmpty
+ ? FlowyText(
+ icon,
+ fontSize: 30.0,
+ )
+ : SizedBox.square(
+ dimension: 32.0,
+ child: view.defaultIcon(),
+ ),
+ ),
+ ),
+ Positioned(
+ bottom: 0,
+ left: 0,
+ right: 0,
+ child: Container(
+ height: widget.height / 2.0,
+ width: double.infinity,
+ padding: const EdgeInsets.only(
+ left: 8.0,
+ top: 14.0,
+ right: 8.0,
+ ),
+ child: FlowyText(
+ view.name,
+ maxLines: 2,
+ fontSize: 16.0,
+ overflow: TextOverflow.ellipsis,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ Widget _buildCoverWidget() {
+ return FutureBuilder(
+ future: _getPageNode(),
+ builder: ((context, snapshot) {
+ final node = snapshot.data;
+ final placeholder = Container(
+ color: Theme.of(context).colorScheme.onSecondaryContainer,
+ );
+ if (node == null) {
+ return placeholder;
+ }
+ final type = CoverType.fromString(
+ node.attributes[DocumentHeaderBlockKeys.coverType],
+ );
+ final cover =
+ node.attributes[DocumentHeaderBlockKeys.coverDetails] as String?;
+ if (cover == null) {
+ return placeholder;
+ }
+ switch (type) {
+ case CoverType.file:
+ if (isURL(cover)) {
+ return CachedNetworkImage(
+ imageUrl: cover,
+ fit: BoxFit.cover,
+ );
+ }
+ final imageFile = File(cover);
+ if (!imageFile.existsSync()) {
+ return placeholder;
+ }
+ return Image.file(
+ imageFile,
+ );
+ case CoverType.asset:
+ return Image.asset(
+ cover,
+ fit: BoxFit.cover,
+ );
+ case CoverType.color:
+ final color = cover.tryToColor() ?? Colors.white;
+ return Container(
+ color: color,
+ );
+ case CoverType.none:
+ return placeholder;
+ }
+ }),
+ );
+ }
+
+ Future _getPageNode() async {
+ final data = await DocumentEventGetDocumentData(
+ OpenDocumentPayloadPB(documentId: view.id),
+ ).send();
+ final document = data.fold((l) => l.toDocument(), (r) => null);
+ if (document != null) {
+ return document.root;
+ }
+ return null;
+ }
+}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/widgets/show_flowy_mobile_bottom_sheet.dart b/frontend/appflowy_flutter/lib/mobile/presentation/widgets/show_flowy_mobile_bottom_sheet.dart
index 58d13ce921..b71a664eb5 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/widgets/show_flowy_mobile_bottom_sheet.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/widgets/show_flowy_mobile_bottom_sheet.dart
@@ -8,6 +8,7 @@ Future showFlowyMobileBottomSheet(
}) async {
return showModalBottomSheet(
context: context,
+ isScrollControlled: true,
builder: (context) => Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 32),
child: Column(
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/cover_editor.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/cover_editor.dart
index 5e17a753fd..919451adac 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/cover_editor.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/cover_editor.dart
@@ -529,14 +529,12 @@ class ColorItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return InkWell(
- customBorder: const RoundedRectangleBorder(
- borderRadius: Corners.s6Border,
- ),
- hoverColor: hoverColor,
- onTap: () => onTap(option.colorHex),
- child: Padding(
- padding: const EdgeInsets.only(right: 10.0),
+ return Padding(
+ padding: const EdgeInsets.only(right: 10.0),
+ child: InkWell(
+ customBorder: const CircleBorder(),
+ hoverColor: hoverColor,
+ onTap: () => onTap(option.colorHex),
child: SizedBox.square(
dimension: 25,
child: DecoratedBox(
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart
index 59355d25c1..2905a32715 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart
@@ -2,19 +2,23 @@ import 'dart:io';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
+import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
import 'package:appflowy/plugins/base/icon/icon_picker.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu.dart';
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
import 'package:appflowy/workspace/application/view/view_listener.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
-import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:appflowy_editor/appflowy_editor.dart' hide UploadImageMenu;
import 'package:appflowy_popover/appflowy_popover.dart';
+import 'package:cached_network_image/cached_network_image.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/rounded_button.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
+import 'package:string_validator/string_validator.dart';
import 'cover_editor.dart';
@@ -262,7 +266,9 @@ class _DocumentHeaderToolbarState extends State {
FlowyButton(
leftIconSize: const Size.square(18),
onTap: () => widget.onCoverChanged(
- cover: (CoverType.asset, builtInAssetImages.first),
+ cover: PlatformExtension.isDesktopOrWeb
+ ? (CoverType.asset, builtInAssetImages.first)
+ : (CoverType.color, '0xffe8e0ff'),
),
useIntrinsicWidth: true,
leftIcon: const FlowySvg(FlowySvgs.image_s),
@@ -373,6 +379,12 @@ class DocumentCoverState extends State {
@override
Widget build(BuildContext context) {
+ return PlatformExtension.isDesktopOrWeb
+ ? _buildDesktopCover()
+ : _buildMobileCover();
+ }
+
+ Widget _buildDesktopCover() {
return SizedBox(
height: kCoverHeight,
child: MouseRegion(
@@ -393,10 +405,82 @@ class DocumentCoverState extends State {
);
}
+ Widget _buildMobileCover() {
+ return SizedBox(
+ height: kCoverHeight,
+ child: Stack(
+ children: [
+ SizedBox(
+ height: double.infinity,
+ width: double.infinity,
+ child: _buildCoverImage(),
+ ),
+ Positioned(
+ bottom: 8,
+ right: 12,
+ child: RoundedTextButton(
+ onPressed: () {
+ showFlowyMobileBottomSheet(
+ context,
+ title: LocaleKeys.document_plugins_cover_changeCover.tr(),
+ builder: (context) {
+ return ConstrainedBox(
+ constraints: const BoxConstraints(
+ maxHeight: 340,
+ minHeight: 80,
+ ),
+ child: UploadImageMenu(
+ supportTypes: const [
+ UploadImageType.color,
+ UploadImageType.local,
+ UploadImageType.url,
+ UploadImageType.unsplash,
+ ],
+ onSelectedLocalImage: (path) async {
+ context.pop();
+ widget.onCoverChanged(CoverType.file, path);
+ },
+ onSelectedAIImage: (_) {
+ throw UnimplementedError();
+ },
+ onSelectedNetworkImage: (url) async {
+ context.pop();
+ widget.onCoverChanged(CoverType.file, url);
+ },
+ onSelectedColor: (color) {
+ context.pop();
+ widget.onCoverChanged(CoverType.color, color);
+ },
+ ),
+ );
+ },
+ );
+ },
+ fillColor: Theme.of(context).colorScheme.onSurfaceVariant,
+ width: 120,
+ height: 32,
+ title: LocaleKeys.document_plugins_cover_changeCover.tr(),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
Widget _buildCoverImage() {
+ final detail = widget.coverDetails;
+ if (detail == null) {
+ return const SizedBox.shrink();
+ }
switch (widget.coverType) {
case CoverType.file:
- final imageFile = File(widget.coverDetails ?? "");
+ if (isURL(detail)) {
+ return CachedNetworkImage(
+ imageUrl: detail,
+ fit: BoxFit.cover,
+ );
+ }
+ final imageFile = File(detail);
if (!imageFile.existsSync()) {
WidgetsBinding.instance.addPostFrameCallback((_) {
widget.onCoverChanged(CoverType.none, null);
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/flowy_image_picker.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/flowy_image_picker.dart
new file mode 100644
index 0000000000..a1f4487562
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/flowy_image_picker.dart
@@ -0,0 +1,41 @@
+import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart';
+import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:flowy_infra_ui/style_widget/text.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+class ImagePickerPage extends StatefulWidget {
+ const ImagePickerPage({
+ super.key,
+ // required this.onSelected,
+ });
+
+ // final void Function(EmojiPickerResult) onSelected;
+
+ @override
+ State createState() => _ImagePickerPageState();
+}
+
+class _ImagePickerPageState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ titleSpacing: 0,
+ title: const FlowyText.semibold(
+ 'Page icon',
+ fontSize: 14.0,
+ ),
+ leading: AppBarBackButton(
+ onTap: () => context.pop(),
+ ),
+ ),
+ body: SafeArea(
+ child: UploadImageMenu(
+ onSubmitted: (_) {},
+ onUpload: (_) {},
+ ),
+ ),
+ );
+ }
+}
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_picker_screen.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_picker_screen.dart
new file mode 100644
index 0000000000..0aa10412bf
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_picker_screen.dart
@@ -0,0 +1,15 @@
+import 'package:appflowy/plugins/document/presentation/editor_plugins/image/flowy_image_picker.dart';
+import 'package:flutter/material.dart';
+
+class MobileImagePickerScreen extends StatelessWidget {
+ static const routeName = '/image_picker';
+
+ const MobileImagePickerScreen({
+ super.key,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return const ImagePickerPage();
+ }
+}
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu.dart
index e7a9bfc7de..961e2deab6 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu.dart
@@ -1,12 +1,15 @@
import 'package:appflowy/generated/locale_keys.g.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/header/cover_editor.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/embed_image_url_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/open_ai_image_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/stability_ai_image_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/unsplash_image_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_file_widget.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/user/application/user_service.dart';
-import 'package:appflowy/util/platform_extension.dart';
+import 'package:appflowy_editor/appflowy_editor.dart' hide ColorOption;
import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flutter/material.dart';
@@ -16,7 +19,8 @@ enum UploadImageType {
url,
unsplash,
stabilityAI,
- openAI;
+ openAI,
+ color;
String get description {
switch (this) {
@@ -30,6 +34,8 @@ enum UploadImageType {
return LocaleKeys.document_imageBlock_ai_label.tr();
case UploadImageType.stabilityAI:
return LocaleKeys.document_imageBlock_stability_ai_label.tr();
+ case UploadImageType.color:
+ return LocaleKeys.document_plugins_cover_colors.tr();
}
}
}
@@ -40,12 +46,14 @@ class UploadImageMenu extends StatefulWidget {
required this.onSelectedLocalImage,
required this.onSelectedAIImage,
required this.onSelectedNetworkImage,
+ this.onSelectedColor,
this.supportTypes = UploadImageType.values,
});
final void Function(String? path) onSelectedLocalImage;
final void Function(String url) onSelectedAIImage;
final void Function(String url) onSelectedNetworkImage;
+ final void Function(String color)? onSelectedColor;
final List supportTypes;
@override
@@ -128,18 +136,23 @@ class _UploadImageMenuState extends State {
}
Widget _buildTab() {
- final type = UploadImageType.values[currentTabIndex];
+ final constraints =
+ PlatformExtension.isMobile ? const BoxConstraints(minHeight: 92) : null;
+ final type = values[currentTabIndex];
switch (type) {
case UploadImageType.local:
- return Padding(
+ return Container(
padding: const EdgeInsets.all(8.0),
+ alignment: Alignment.center,
+ constraints: constraints,
child: UploadImageFileWidget(
onPickFile: widget.onSelectedLocalImage,
),
);
case UploadImageType.url:
- return Padding(
+ return Container(
padding: const EdgeInsets.all(8.0),
+ constraints: constraints,
child: EmbedImageUrlWidget(
onSubmit: widget.onSelectedNetworkImage,
),
@@ -156,8 +169,9 @@ class _UploadImageMenuState extends State {
case UploadImageType.openAI:
return supportOpenAI
? Expanded(
- child: Padding(
+ child: Container(
padding: const EdgeInsets.all(8.0),
+ constraints: constraints,
child: OpenAIImageWidget(
onSelectNetworkImage: widget.onSelectedAIImage,
),
@@ -172,7 +186,7 @@ class _UploadImageMenuState extends State {
case UploadImageType.stabilityAI:
return supportStabilityAI
? Expanded(
- child: Padding(
+ child: Container(
padding: const EdgeInsets.all(8.0),
child: StabilityAIImageWidget(
onSelectImage: widget.onSelectedLocalImage,
@@ -186,6 +200,28 @@ class _UploadImageMenuState extends State {
.tr(),
),
);
+ case UploadImageType.color:
+ final theme = Theme.of(context);
+ return Container(
+ constraints: constraints,
+ padding: const EdgeInsets.all(8.0),
+ alignment: Alignment.center,
+ child: CoverColorPicker(
+ pickerBackgroundColor: theme.cardColor,
+ pickerItemHoverColor: theme.hoverColor,
+ backgroundColorOptions: FlowyTint.values
+ .map(
+ (t) => ColorOption(
+ colorHex: t.color(context).toHex(),
+ name: t.tintName(AppFlowyEditorL10n.current),
+ ),
+ )
+ .toList(),
+ onSubmittedBackgroundColorHex: (color) {
+ widget.onSelectedColor?.call(color);
+ },
+ ),
+ );
}
}
}
diff --git a/frontend/appflowy_flutter/lib/startup/deps_resolver.dart b/frontend/appflowy_flutter/lib/startup/deps_resolver.dart
index a8d8bd8faa..acc04b3f0d 100644
--- a/frontend/appflowy_flutter/lib/startup/deps_resolver.dart
+++ b/frontend/appflowy_flutter/lib/startup/deps_resolver.dart
@@ -1,6 +1,7 @@
import 'package:appflowy/core/config/kv.dart';
import 'package:appflowy/core/network_monitor.dart';
import 'package:appflowy/env/env.dart';
+import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/plugins/document/application/prelude.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/openai_client.dart';
@@ -143,6 +144,7 @@ void _resolveHomeDeps(GetIt getIt) {
getIt.registerSingleton(FToast());
getIt.registerSingleton(MenuSharedState());
+ getIt.registerSingleton(MobileRouterRecord());
getIt.registerFactoryParam(
(user, _) => UserListener(userProfile: user),
diff --git a/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart b/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart
index bdb70ab5c4..ea27237129 100644
--- a/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart
+++ b/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart
@@ -4,6 +4,7 @@ import 'package:appflowy/mobile/presentation/database/mobile_grid_screen.dart';
import 'package:appflowy/mobile/presentation/favorite/mobile_favorite_page.dart';
import 'package:appflowy/mobile/presentation/presentation.dart';
import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_picker_screen.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/startup/tasks/app_widget.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
@@ -51,6 +52,7 @@ GoRouter generateRouter(Widget child) {
// emoji picker
_mobileEmojiPickerPageRoute(),
+ _mobileImagePickerPageRoute(),
],
// Desktop and Mobile
@@ -216,6 +218,18 @@ GoRoute _mobileEmojiPickerPageRoute() {
);
}
+GoRoute _mobileImagePickerPageRoute() {
+ return GoRoute(
+ parentNavigatorKey: AppGlobals.rootNavKey,
+ path: MobileImagePickerScreen.routeName,
+ pageBuilder: (context, state) {
+ return const MaterialPage(
+ child: MobileImagePickerScreen(),
+ );
+ },
+ );
+}
+
GoRoute _desktopHomeScreenRoute() {
return GoRoute(
path: DesktopHomeScreen.routeName,
diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock
index 7c005d9ab5..0be54f2fa7 100644
--- a/frontend/appflowy_flutter/pubspec.lock
+++ b/frontend/appflowy_flutter/pubspec.lock
@@ -178,6 +178,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.6.0"
+ cached_network_image:
+ dependency: "direct main"
+ description:
+ name: cached_network_image
+ sha256: f98972704692ba679db144261172a8e20feb145636c617af0eb4022132a6797f
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.3.0"
+ cached_network_image_platform_interface:
+ dependency: transitive
+ description:
+ name: cached_network_image_platform_interface
+ sha256: "56aa42a7a01e3c9db8456d9f3f999931f1e05535b5a424271e9a38cabf066613"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.0"
+ cached_network_image_web:
+ dependency: transitive
+ description:
+ name: cached_network_image_web
+ sha256: "759b9a9f8f6ccbb66c185df805fac107f05730b1dab9c64626d1008cca532257"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.0"
calendar_view:
dependency: "direct main"
description:
@@ -539,6 +563,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.1.3"
+ flutter_cache_manager:
+ dependency: transitive
+ description:
+ name: flutter_cache_manager
+ sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.3.1"
flutter_colorpicker:
dependency: "direct main"
description:
@@ -1082,6 +1114,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.2"
+ octo_image:
+ dependency: transitive
+ description:
+ name: octo_image
+ sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.0"
package_config:
dependency: transitive
description:
@@ -1575,6 +1615,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.1"
+ sqflite:
+ dependency: transitive
+ description:
+ name: sqflite
+ sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.3.0"
+ sqflite_common:
+ dependency: transitive
+ description:
+ name: sqflite_common
+ sha256: "8ed044102f3135add97be8653662052838859f5400075ef227f8ad72ae320803"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.5.0+1"
stack_trace:
dependency: transitive
description:
@@ -1672,6 +1728,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.3.1"
+ synchronized:
+ dependency: transitive
+ description:
+ name: synchronized
+ sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.0"
table_calendar:
dependency: "direct main"
description:
diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml
index ab901ec6f1..8e9eb9aecf 100644
--- a/frontend/appflowy_flutter/pubspec.yaml
+++ b/frontend/appflowy_flutter/pubspec.yaml
@@ -124,6 +124,7 @@ dependencies:
flutter_slidable: ^3.0.0
image_picker: ^1.0.4
image_gallery_saver: ^2.0.3
+ cached_network_image: ^3.3.0
dev_dependencies:
flutter_lints: ^2.0.1
diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json
index 41a9e9a713..95d30f92d2 100644
--- a/frontend/resources/translations/en.json
+++ b/frontend/resources/translations/en.json
@@ -186,7 +186,8 @@
"favorites": "Favorites",
"clickToHidePersonal": "Click to hide personal section",
"clickToHideFavorites": "Click to hide favorite section",
- "addAPage": "Add a page"
+ "addAPage": "Add a page",
+ "recent": "Recent"
},
"notifications": {
"export": {
diff --git a/frontend/rust-lib/flowy-folder2/src/event_handler.rs b/frontend/rust-lib/flowy-folder2/src/event_handler.rs
index d826c202a3..4ab5398a1e 100644
--- a/frontend/rust-lib/flowy-folder2/src/event_handler.rs
+++ b/frontend/rust-lib/flowy-folder2/src/event_handler.rs
@@ -228,6 +228,22 @@ pub(crate) async fn read_favorites_handler(
}
data_result_ok(RepeatedViewPB { items: views })
}
+
+#[tracing::instrument(level = "debug", skip(folder), err)]
+pub(crate) async fn read_recent_views_handler(
+ folder: AFPluginState>,
+) -> DataResult {
+ let folder = upgrade_folder(folder)?;
+ let recent_items = folder.get_all_recent_sections().await;
+ let mut views = vec![];
+ for item in recent_items {
+ if let Ok(view) = folder.get_view_pb(&item.id).await {
+ views.push(view);
+ }
+ }
+ data_result_ok(RepeatedViewPB { items: views })
+}
+
#[tracing::instrument(level = "debug", skip(folder), err)]
pub(crate) async fn read_trash_handler(
folder: AFPluginState>,
diff --git a/frontend/rust-lib/flowy-folder2/src/event_map.rs b/frontend/rust-lib/flowy-folder2/src/event_map.rs
index e6d145c533..f27ff376c5 100644
--- a/frontend/rust-lib/flowy-folder2/src/event_map.rs
+++ b/frontend/rust-lib/flowy-folder2/src/event_map.rs
@@ -36,6 +36,7 @@ pub fn init(folder: Weak) -> AFPlugin {
.event(FolderEvent::GetFolderSnapshots, get_folder_snapshots_handler)
.event(FolderEvent::UpdateViewIcon, update_view_icon_handler)
.event(FolderEvent::ReadFavorites, read_favorites_handler)
+ .event(FolderEvent::ReadRecentViews, read_recent_views_handler)
.event(FolderEvent::ToggleFavorite, toggle_favorites_handler)
}
@@ -145,4 +146,7 @@ pub enum FolderEvent {
#[event(input = "UpdateViewIconPayloadPB")]
UpdateViewIcon = 35,
+
+ #[event(output = "RepeatedViewPB")]
+ ReadRecentViews = 36,
}
diff --git a/frontend/rust-lib/flowy-folder2/src/manager.rs b/frontend/rust-lib/flowy-folder2/src/manager.rs
index 8762299a29..ea4c9e7a5f 100644
--- a/frontend/rust-lib/flowy-folder2/src/manager.rs
+++ b/frontend/rust-lib/flowy-folder2/src/manager.rs
@@ -7,8 +7,8 @@ use collab::core::collab::{CollabRawData, MutexCollab};
use collab::core::collab_state::SyncState;
use collab_entity::CollabType;
use collab_folder::{
- Folder, FolderData, FolderNotify, SectionItem, TrashChange, TrashChangeReceiver, TrashInfo,
- UserId, View, ViewChange, ViewChangeReceiver, ViewLayout, ViewUpdate, Workspace,
+ Folder, FolderData, FolderNotify, Section, SectionItem, TrashChange, TrashChangeReceiver,
+ TrashInfo, UserId, View, ViewChange, ViewChangeReceiver, ViewLayout, ViewUpdate, Workspace,
};
use parking_lot::{Mutex, RwLock};
use tokio_stream::wrappers::WatchStream;
@@ -745,6 +745,7 @@ impl FolderManager {
|| Err(FlowyError::record_not_found()),
|folder| {
folder.set_current_view(view_id);
+ folder.add_recent_view_ids(vec![view_id.to_string()]);
Ok(folder.get_workspace_id())
},
)?;
@@ -800,17 +801,12 @@ impl FolderManager {
#[tracing::instrument(level = "trace", skip(self))]
pub(crate) async fn get_all_favorites(&self) -> Vec {
- self.with_folder(Vec::new, |folder| {
- let trash_ids = folder
- .get_all_trash()
- .into_iter()
- .map(|trash| trash.id)
- .collect::>();
+ self.get_sections(Section::Favorite)
+ }
- let mut views = folder.get_all_favorites();
- views.retain(|view| !trash_ids.contains(&view.id));
- views
- })
+ #[tracing::instrument(level = "trace", skip(self))]
+ pub(crate) async fn get_all_recent_sections(&self) -> Vec {
+ self.get_sections(Section::Recent)
}
#[tracing::instrument(level = "trace", skip(self))]
@@ -1039,6 +1035,26 @@ impl FolderManager {
pub fn get_cloud_service(&self) -> &Arc {
&self.cloud_service
}
+
+ fn get_sections(&self, section_type: Section) -> Vec {
+ self.with_folder(Vec::new, |folder| {
+ let trash_ids = folder
+ .get_all_trash()
+ .into_iter()
+ .map(|trash| trash.id)
+ .collect::>();
+
+ let mut views = match section_type {
+ Section::Favorite => folder.get_all_favorites(),
+ Section::Recent => folder.get_all_recent_sections(),
+ _ => vec![],
+ };
+
+ // filter the views that are in the trash
+ views.retain(|view| !trash_ids.contains(&view.id));
+ views
+ })
+ }
}
/// Listen on the [ViewChange] after create/delete/update events happened