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

This commit is contained in:
Nathan.fooo 2023-06-21 12:15:32 +08:00 committed by GitHub
parent 197e5b3a79
commit 13ff38756f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 87 additions and 33 deletions

View File

@ -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!",

View File

@ -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;
}

View File

@ -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),
);
},

View File

@ -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,
),
),
);
}

View File

@ -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: [

View File

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

View File

@ -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);

View File

@ -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 {

View File

@ -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));

View File

@ -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()

View File

@ -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();
}
},

View File

@ -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;

View File

@ -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(),

View File

@ -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";
});