mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: adjust cover plugin and support recent section on mobile platform (#3921)
This commit is contained in:
parent
765103dd22
commit
7cee8e392f
@ -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
|
||||
|
@ -1,68 +1,70 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>AppFlowy requires access to the camera.</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>AppFlowy requires access to the photo library.</string>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleLocalizations</key>
|
||||
<array>
|
||||
<string>en</string>
|
||||
</array>
|
||||
<key>CFBundleName</key>
|
||||
<string>AppFlowy</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string></string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>appflowy-flutter</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
<dict>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>AppFlowy requires access to the camera.</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>AppFlowy requires access to the photo library.</string>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true />
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleLocalizations</key>
|
||||
<array>
|
||||
<string>en</string>
|
||||
</array>
|
||||
<key>FLTEnableImpeller</key>
|
||||
<false />
|
||||
<key>CFBundleName</key>
|
||||
<string>AppFlowy</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string></string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>appflowy-flutter</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true />
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true />
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false />
|
||||
</dict>
|
||||
</plist>
|
@ -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<String> lastPushedRouter =
|
||||
PropertyValueNotifier<String>('');
|
||||
}
|
||||
|
||||
extension MobileRouter on BuildContext {
|
||||
Future<void> pushView(ViewPB view) async {
|
||||
await FolderEventSetLatestView(ViewIdPB(value: view.id)).send();
|
||||
getIt<MobileRouterRecord>().lastPushedRouter.value = view.routeName;
|
||||
push(
|
||||
Uri(
|
||||
path: view.routeName,
|
||||
|
@ -117,15 +117,19 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -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(
|
||||
|
@ -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>[
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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<MobileRecentFolder> createState() => _MobileRecentFolderState();
|
||||
}
|
||||
|
||||
class _MobileRecentFolderState extends State<MobileRecentFolder> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: getIt<MobileRouterRecord>().lastPushedRouter,
|
||||
builder: (context, value, child) {
|
||||
return FutureBuilder<Either<RepeatedViewPB, FlowyError>>(
|
||||
future: FolderEventReadRecentViews().send(),
|
||||
builder: (context, snapshot) {
|
||||
final recentViews = snapshot.data
|
||||
?.fold<List<ViewPB>>(
|
||||
(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<ViewPB> 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,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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<MobileRecentView> createState() => _MobileRecentViewState();
|
||||
}
|
||||
|
||||
class _MobileRecentViewState extends State<MobileRecentView> {
|
||||
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<Node?>(
|
||||
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<Node?> _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;
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ Future<T?> showFlowyMobileBottomSheet<T>(
|
||||
}) async {
|
||||
return showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 32),
|
||||
child: Column(
|
||||
|
@ -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(
|
||||
|
@ -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<DocumentHeaderToolbar> {
|
||||
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<DocumentCover> {
|
||||
|
||||
@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<DocumentCover> {
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -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<ImagePickerPage> createState() => _ImagePickerPageState();
|
||||
}
|
||||
|
||||
class _ImagePickerPageState extends State<ImagePickerPage> {
|
||||
@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: (_) {},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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<UploadImageType> supportTypes;
|
||||
|
||||
@override
|
||||
@ -128,18 +136,23 @@ class _UploadImageMenuState extends State<UploadImageMenu> {
|
||||
}
|
||||
|
||||
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<UploadImageMenu> {
|
||||
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<UploadImageMenu> {
|
||||
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<UploadImageMenu> {
|
||||
.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<ColorOption>(
|
||||
(t) => ColorOption(
|
||||
colorHex: t.color(context).toHex(),
|
||||
name: t.tintName(AppFlowyEditorL10n.current),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onSubmittedBackgroundColorHex: (color) {
|
||||
widget.onSelectedColor?.call(color);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<UserListener, UserProfilePB, void>(
|
||||
(user, _) => UserListener(userProfile: user),
|
||||
|
@ -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,
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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": {
|
||||
|
@ -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<Weak<FolderManager>>,
|
||||
) -> DataResult<RepeatedViewPB, FlowyError> {
|
||||
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<Weak<FolderManager>>,
|
||||
|
@ -36,6 +36,7 @@ pub fn init(folder: Weak<FolderManager>) -> 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,
|
||||
}
|
||||
|
@ -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<SectionItem> {
|
||||
self.with_folder(Vec::new, |folder| {
|
||||
let trash_ids = folder
|
||||
.get_all_trash()
|
||||
.into_iter()
|
||||
.map(|trash| trash.id)
|
||||
.collect::<Vec<String>>();
|
||||
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<SectionItem> {
|
||||
self.get_sections(Section::Recent)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip(self))]
|
||||
@ -1039,6 +1035,26 @@ impl FolderManager {
|
||||
pub fn get_cloud_service(&self) -> &Arc<dyn FolderCloudService> {
|
||||
&self.cloud_service
|
||||
}
|
||||
|
||||
fn get_sections(&self, section_type: Section) -> Vec<SectionItem> {
|
||||
self.with_folder(Vec::new, |folder| {
|
||||
let trash_ids = folder
|
||||
.get_all_trash()
|
||||
.into_iter()
|
||||
.map(|trash| trash.id)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user