Selaa lähdekoodia

feat: put all user data into a folder when choosing a custom storage path (#2861)

Nathan.fooo 1 vuosi sitten
vanhempi
commit
13ff38756f

+ 1 - 0
frontend/appflowy_flutter/assets/translations/en.json

@@ -223,6 +223,7 @@
       "changeLocationTooltips": "Change the data directory",
       "change": "Change",
       "openLocationTooltips": "Open another data directory",
+      "openCurrentDataFolder": "Open current data directory",
       "recoverLocationTooltips": "Reset to AppFlowy's default data directory",
       "exportFileSuccess": "Export file successfully!",
       "exportFileFail": "Export file failed!",

+ 1 - 1
frontend/appflowy_flutter/integration_test/util/base.dart

@@ -44,7 +44,7 @@ class TestFolder {
 
   /// Get default location under development environment.
   static Future<String> defaultDevelopmentLocation() async {
-    final dir = await appFlowyDocumentDirectory();
+    final dir = await appFlowyApplicationDataDirectory();
     return dir.path;
   }
 

+ 2 - 1
frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart

@@ -7,6 +7,7 @@ import 'package:appflowy/plugins/database_view/application/database_controller.d
 import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
 import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
 import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
+import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
 import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_view.dart';
 import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
 import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
@@ -152,7 +153,7 @@ class _BoardContentState extends State<BoardContent> {
         buildWhen: (previous, current) => previous.groupIds != current.groupIds,
         builder: (context, state) {
           return Padding(
-            padding: const EdgeInsets.symmetric(horizontal: 20),
+            padding: GridSize.contentInsets,
             child: _buildBoard(context),
           );
         },

+ 13 - 9
frontend/appflowy_flutter/lib/plugins/database_view/calendar/presentation/calendar_page.dart

@@ -1,6 +1,7 @@
 import 'package:appflowy/generated/locale_keys.g.dart';
 import 'package:appflowy/plugins/database_view/application/database_controller.dart';
 import 'package:appflowy/plugins/database_view/calendar/application/calendar_bloc.dart';
+import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
 import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_view.dart';
 import 'package:appflowy_backend/protobuf/flowy-database2/calendar_entities.pb.dart';
 import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
@@ -176,15 +177,18 @@ class _CalendarPageState extends State<CalendarPage> {
 
   Widget _buildCalendar(EventController eventController, int firstDayOfWeek) {
     return Expanded(
-      child: MonthView(
-        key: _calendarState,
-        controller: _eventController,
-        cellAspectRatio: .6,
-        startDay: _weekdayFromInt(firstDayOfWeek),
-        borderColor: Theme.of(context).dividerColor,
-        headerBuilder: _headerNavigatorBuilder,
-        weekDayBuilder: _headerWeekDayBuilder,
-        cellBuilder: _calendarDayBuilder,
+      child: Padding(
+        padding: GridSize.contentInsets,
+        child: MonthView(
+          key: _calendarState,
+          controller: _eventController,
+          cellAspectRatio: .6,
+          startDay: _weekdayFromInt(firstDayOfWeek),
+          borderColor: Theme.of(context).dividerColor,
+          headerBuilder: _headerNavigatorBuilder,
+          weekDayBuilder: _headerWeekDayBuilder,
+          cellBuilder: _calendarDayBuilder,
+        ),
       ),
     );
   }

+ 1 - 1
frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/grid_page.dart

@@ -403,7 +403,7 @@ class _GridFooter extends StatelessWidget {
       selector: (state) => state.rowCount,
       builder: (context, rowCount) {
         return Padding(
-          padding: GridSize.footerContentInsets,
+          padding: GridSize.contentInsets,
           child: Row(
             mainAxisAlignment: MainAxisAlignment.start,
             children: [

+ 7 - 0
frontend/appflowy_flutter/lib/plugins/database_view/grid/presentation/layout/sizes.dart

@@ -36,4 +36,11 @@ class GridSize {
         GridSize.headerContainerPadding,
         GridSize.headerContainerPadding,
       );
+
+  static EdgeInsets get contentInsets => EdgeInsets.fromLTRB(
+        GridSize.leadingHeaderPadding,
+        GridSize.headerContainerPadding,
+        GridSize.leadingHeaderPadding,
+        GridSize.headerContainerPadding,
+      );
 }

+ 1 - 1
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/cover_image_picker_bloc.dart

@@ -130,7 +130,7 @@ class CoverImagePickerBloc
   }
 
   Future<String> _coverPath() async {
-    final directory = await getIt<LocalFileStorage>().getPath();
+    final directory = await getIt<ApplicationDataStorage>().getPath();
     return Directory(p.join(directory, 'covers'))
         .create(recursive: true)
         .then((value) => value.path);

+ 1 - 1
frontend/appflowy_flutter/lib/startup/deps_resolver.dart

@@ -51,7 +51,7 @@ void _resolveCommonService(GetIt getIt) async {
   // getIt.registerFactory<KeyValueStorage>(() => RustKeyValue());
   getIt.registerFactory<KeyValueStorage>(() => DartKeyValue());
   getIt.registerFactory<FilePickerService>(() => FilePicker());
-  getIt.registerFactory<LocalFileStorage>(() => LocalFileStorage());
+  getIt.registerFactory<ApplicationDataStorage>(() => ApplicationDataStorage());
 
   getIt.registerFactoryAsync<OpenAIRepository>(
     () async {

+ 1 - 1
frontend/appflowy_flutter/lib/startup/startup.dart

@@ -32,7 +32,7 @@ class FlowyRunner {
     // Specify the env
     initGetIt(getIt, mode, f, config);
 
-    final directory = await getIt<LocalFileStorage>()
+    final directory = await getIt<ApplicationDataStorage>()
         .getPath()
         .then((value) => Directory(value));
 

+ 4 - 2
frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart

@@ -21,7 +21,7 @@ class InitRustSDKTask extends LaunchTask {
 
   @override
   Future<void> initialize(LaunchContext context) async {
-    final dir = directory ?? await appFlowyDocumentDirectory();
+    final dir = directory ?? await appFlowyApplicationDataDirectory();
 
     context.getIt<FlowySDK>().setEnv(getAppFlowyEnv());
     await context.getIt<FlowySDK>().init(dir);
@@ -51,7 +51,9 @@ AppFlowyEnv getAppFlowyEnv() {
   );
 }
 
-Future<Directory> appFlowyDocumentDirectory() async {
+/// The default directory to store the user data. The directory can be
+/// customized by the user via the [ApplicationDataStorage]
+Future<Directory> appFlowyApplicationDataDirectory() async {
   switch (integrationEnv()) {
     case IntegrationMode.develop:
       final Directory documentsDir = await getApplicationSupportDirectory()

+ 3 - 3
frontend/appflowy_flutter/lib/user/presentation/folder/folder_widget.dart

@@ -64,7 +64,7 @@ class _FolderWidgetState extends State<FolderWidget> {
   Future<void> _openFolder() async {
     final path = await getIt<FilePickerService>().getDirectoryPath();
     if (path != null) {
-      await getIt<LocalFileStorage>().setPath(path);
+      await getIt<ApplicationDataStorage>().setCustomPath(path);
       await widget.createFolderCallback();
       setState(() {});
     }
@@ -82,7 +82,7 @@ class FolderOptionsWidget extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return FutureBuilder(
-      future: getIt<LocalFileStorage>().getPath(),
+      future: getIt<ApplicationDataStorage>().getPath(),
       builder: (context, result) {
         final subtitle = result.hasData ? result.data! : '';
         return _FolderCard(
@@ -182,7 +182,7 @@ class CreateFolderWidgetState extends State<CreateFolderWidget> {
                       LocaleKeys.settings_files_locationCannotBeEmpty.tr(),
                     );
                   } else {
-                    await getIt<LocalFileStorage>().setPath(_path);
+                    await getIt<ApplicationDataStorage>().setCustomPath(_path);
                     await widget.onPressedCreate();
                   }
                 },

+ 45 - 8
frontend/appflowy_flutter/lib/workspace/application/settings/settings_location_cubit.dart

@@ -7,6 +7,7 @@ import 'package:appflowy_backend/log.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:path/path.dart' as p;
 
 import '../../../startup/tasks/prelude.dart';
 
@@ -24,27 +25,39 @@ class SettingsLocationCubit extends Cubit<SettingsLocationState> {
     _init();
   }
 
-  Future<void> setPath(String path) async {
-    await getIt<LocalFileStorage>().setPath(path);
+  Future<void> resetDataStoragePathToApplicationDefault() async {
+    final directory = await appFlowyApplicationDataDirectory();
+    await getIt<ApplicationDataStorage>()._setPath(directory.path);
+    emit(SettingsLocationState.didReceivedPath(directory.path));
+  }
+
+  Future<void> setCustomPath(String path) async {
+    await getIt<ApplicationDataStorage>().setCustomPath(path);
     emit(SettingsLocationState.didReceivedPath(path));
   }
 
   Future<void> _init() async {
-    final path = await getIt<LocalFileStorage>().getPath();
+    final path = await getIt<ApplicationDataStorage>().getPath();
     emit(SettingsLocationState.didReceivedPath(path));
   }
 }
 
-class LocalFileStorage {
-  LocalFileStorage();
+class ApplicationDataStorage {
+  ApplicationDataStorage();
   String? _cachePath;
 
-  Future<void> setPath(String path) async {
+  /// Set the custom path to store the data.
+  /// If the path is not exists, the path will be created.
+  /// If the path is invalid, the path will be set to the default path.
+  Future<void> setCustomPath(String path) async {
     if (kIsWeb || Platform.isAndroid || Platform.isIOS) {
       Log.info('LocalFileStorage is not supported on this platform.');
       return;
     }
 
+    // Every custom path will have a folder named `AppFlowyData`
+    const dataFolder = "AppFlowyDataDoNotRename";
+
     if (Platform.isMacOS) {
       // remove the prefix `/Volumes/*`
       path = path.replaceFirst(RegExp(r'^/Volumes/[^/]+'), '');
@@ -52,6 +65,28 @@ class LocalFileStorage {
       path = path.replaceAll('/', '\\');
     }
 
+    // If the path is not ends with `AppFlowyData`, we will append the
+    // `AppFlowyData` to the path. If the path is ends with `AppFlowyData`,
+    // which means the path is the custom path.
+    if (p.basename(path) != dataFolder) {
+      path = p.join(path, dataFolder);
+    }
+
+    // create the directory if not exists.
+    final directory = Directory(path);
+    if (!directory.existsSync()) {
+      await directory.create(recursive: true);
+    }
+
+    _setPath(path);
+  }
+
+  Future<void> _setPath(String path) async {
+    if (kIsWeb || Platform.isAndroid || Platform.isIOS) {
+      Log.info('LocalFileStorage is not supported on this platform.');
+      return;
+    }
+
     await getIt<KeyValueStorage>().set(KVKeys.pathLocation, path);
     // clear the cache path, and not set the cache path to the new path because the set path may be invalid
     _cachePath = null;
@@ -63,10 +98,10 @@ class LocalFileStorage {
     }
 
     final response = await getIt<KeyValueStorage>().get(KVKeys.pathLocation);
-    final String path = await response.fold(
+    String path = await response.fold(
       (error) async {
         // return the default path if the path is not set
-        final directory = await appFlowyDocumentDirectory();
+        final directory = await appFlowyApplicationDataDirectory();
         return directory.path;
       },
       (path) => path,
@@ -76,6 +111,8 @@ class LocalFileStorage {
     // if the path is not exists means the path is invalid, so we should clear the kv store
     if (!Directory(path).existsSync()) {
       await getIt<KeyValueStorage>().clear();
+      final directory = await appFlowyApplicationDataDirectory();
+      path = directory.path;
     }
 
     return path;

+ 6 - 4
frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart

@@ -174,7 +174,7 @@ class _ChangeStoragePathButtonState extends State<_ChangeStoragePathButton> {
           if (path == null || !mounted || widget.usingPath == path) {
             return;
           }
-          await context.read<SettingsLocationCubit>().setPath(path);
+          await context.read<SettingsLocationCubit>().setCustomPath(path);
           await FlowyRunner.run(
             FlowyApp(),
             integrationEnv(),
@@ -202,7 +202,7 @@ class _OpenStorageButton extends StatelessWidget {
   Widget build(BuildContext context) {
     return FlowyIconButton(
       hoverColor: Theme.of(context).colorScheme.secondaryContainer,
-      tooltipText: LocaleKeys.settings_files_openLocationTooltips.tr(),
+      tooltipText: LocaleKeys.settings_files_openCurrentDataFolder.tr(),
       icon: svgWidget(
         'common/open_folder',
         color: Theme.of(context).iconTheme.color,
@@ -242,12 +242,14 @@ class _RecoverDefaultStorageButtonState
       ),
       onPressed: () async {
         // reset to the default directory and reload app
-        final directory = await appFlowyDocumentDirectory();
+        final directory = await appFlowyApplicationDataDirectory();
         final path = directory.path;
         if (!mounted || widget.usingPath == path) {
           return;
         }
-        await context.read<SettingsLocationCubit>().setPath(path);
+        await context
+            .read<SettingsLocationCubit>()
+            .resetDataStoragePathToApplicationDefault();
         await FlowyRunner.run(
           FlowyApp(),
           integrationEnv(),

+ 1 - 1
frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart

@@ -115,7 +115,7 @@ class _DebugToast {
   }
 
   Future<String> _getDocumentPath() async {
-    return appFlowyDocumentDirectory().then((directory) {
+    return appFlowyApplicationDataDirectory().then((directory) {
       final path = directory.path.toString();
       return "Document: $path\n";
     });