mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: put all user data into a folder when choosing a custom storage path (#2861)
This commit is contained in:
parent
197e5b3a79
commit
13ff38756f
@ -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!",
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
);
|
||||
},
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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: [
|
||||
|
@ -36,4 +36,11 @@ class GridSize {
|
||||
GridSize.headerContainerPadding,
|
||||
GridSize.headerContainerPadding,
|
||||
);
|
||||
|
||||
static EdgeInsets get contentInsets => EdgeInsets.fromLTRB(
|
||||
GridSize.leadingHeaderPadding,
|
||||
GridSize.headerContainerPadding,
|
||||
GridSize.leadingHeaderPadding,
|
||||
GridSize.headerContainerPadding,
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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();
|
||||
}
|
||||
},
|
||||
|
@ -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;
|
||||
|
@ -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(),
|
||||
|
@ -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";
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user