251 lines
6.6 KiB
Dart
Raw Normal View History

feat: Customize the storage folder path (#1538) * feat: support customize folder path * feat: add l10n and optimize the logic * chore: code refactor * feat: add file read/write permission for macOS * fix: add toast for restoring path * feat: fetch apps and show them * feat: fetch apps and show them * feat: implement select document logic * feat: l10n and add select item callback * feat: add space between tile * chore: move file exporter to settings * chore: update UI * feat: support customizing folder when launching the app * feat: auto register after customizing folder * feat: l10n * feat: l10n * chore: reinitialize flowy sdk when calling init_sdk * chore: remove flowysdk const keyword to make sure it can be rebuild * chore: clear kv values when user logout * chore: replace current workspace id key in kv.db * feat: add config.name as a part of seesion_cache_key * feat: support open folder when launching * chore: fix some bugs * chore: dart fix & flutter analyze * chore: wrap 'sign up with ramdom user' as interface * feat: dismiss settings view after changing the folder * fix: read kv value after initializaing with new path * chore: remove user_id prefix from current workspace key * fix: move open latest view action to bloc * test: add test utils for integration tests * chore: move integration_test to its parent directory * test: add integration_test ci * test: switch to B from A, then switch to A again * chore: fix warings and format code and fix tests * chore: remove comment out codes * chore: rename some properties name and optimize the logic * chore: abstract logic of settings file exporter widget to cubit * chore: abstract location customizer view from file system view * chore: abstract settings page index to enum type * chore: remove the redundant underscore * test: fix integration test error * chore: enable integration test for windows and ubuntu * feat: abstract file picker as service and mock it under integration test * chore: fix bloc test Co-authored-by: nathan <nathan@appflowy.io>
2022-12-20 11:14:42 +08:00
import 'dart:io';
import 'package:app_flowy/util/file_picker/file_picker_service.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/style_widget/text_field.dart';
import 'package:flowy_infra_ui/widget/rounded_button.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import '../../../generated/locale_keys.g.dart';
import '../../../startup/startup.dart';
import '../../../workspace/application/settings/settings_location_cubit.dart';
import '../../../workspace/presentation/home/toast.dart';
enum _FolderPage {
options,
create,
open,
}
class FolderWidget extends StatefulWidget {
const FolderWidget({
Key? key,
required this.createFolderCallback,
}) : super(key: key);
final Future<void> Function() createFolderCallback;
@override
State<FolderWidget> createState() => _FolderWidgetState();
}
class _FolderWidgetState extends State<FolderWidget> {
var page = _FolderPage.options;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 250,
child: _mapIndexToWidget(context),
);
}
Widget _mapIndexToWidget(BuildContext context) {
switch (page) {
case _FolderPage.options:
return FolderOptionsWidget(
onPressedCreate: () {
setState(() => page = _FolderPage.create);
},
onPressedOpen: () {
_openFolder();
},
);
case _FolderPage.create:
return CreateFolderWidget(
onPressedBack: () {
setState(() => page = _FolderPage.options);
},
onPressedCreate: widget.createFolderCallback,
);
case _FolderPage.open:
return Container();
}
}
Future<void> _openFolder() async {
final directory = await getIt<FilePickerService>().getDirectoryPath();
if (directory != null) {
await getIt<SettingsLocationCubit>().setLocation(directory);
await widget.createFolderCallback();
}
}
}
class FolderOptionsWidget extends StatelessWidget {
const FolderOptionsWidget({
Key? key,
required this.onPressedCreate,
required this.onPressedOpen,
}) : super(key: key);
final VoidCallback onPressedCreate;
final VoidCallback onPressedOpen;
@override
Widget build(BuildContext context) {
return ListView(
shrinkWrap: true,
children: <Widget>[
Card(
child: ListTile(
title: FlowyText.medium(
LocaleKeys.settings_files_createNewFolder.tr(),
),
subtitle: FlowyText.regular(
LocaleKeys.settings_files_createNewFolderDesc.tr(),
),
trailing: _buildTextButton(
context,
LocaleKeys.settings_files_create.tr(),
onPressedCreate,
),
),
),
Card(
child: ListTile(
title: FlowyText.medium(
LocaleKeys.settings_files_openFolder.tr(),
),
subtitle: FlowyText.regular(
LocaleKeys.settings_files_openFolderDesc.tr(),
),
trailing: _buildTextButton(
context,
LocaleKeys.settings_files_open.tr(),
onPressedOpen,
),
),
),
],
);
}
}
class CreateFolderWidget extends StatefulWidget {
const CreateFolderWidget({
Key? key,
required this.onPressedBack,
required this.onPressedCreate,
}) : super(key: key);
final VoidCallback onPressedBack;
final Future<void> Function() onPressedCreate;
@override
State<CreateFolderWidget> createState() => CreateFolderWidgetState();
}
@visibleForTesting
class CreateFolderWidgetState extends State<CreateFolderWidget> {
var _folderName = 'appflowy';
@visibleForTesting
var directory = '';
final _fToast = FToast();
@override
void initState() {
super.initState();
_fToast.init(context);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Align(
alignment: Alignment.centerLeft,
child: TextButton.icon(
onPressed: widget.onPressedBack,
icon: const Icon(Icons.arrow_back_rounded),
label: const Text('Back'),
),
),
Card(
child: ListTile(
title: FlowyText.medium(
LocaleKeys.settings_files_location.tr(),
),
subtitle: FlowyText.regular(
LocaleKeys.settings_files_locationDesc.tr(),
),
trailing: SizedBox(
width: 100,
height: 36,
child: FlowyTextField(
hintText: LocaleKeys.settings_files_folderHintText.tr(),
onChanged: (name) {
_folderName = name;
},
onSubmitted: (name) {
setState(() {
_folderName = name;
});
},
),
),
),
),
Card(
child: ListTile(
title: FlowyText.medium(LocaleKeys.settings_files_location.tr()),
subtitle: FlowyText.regular(_path),
trailing: _buildTextButton(
context, LocaleKeys.settings_files_browser.tr(), () async {
final dir = await getIt<FilePickerService>().getDirectoryPath();
if (dir != null) {
setState(() {
directory = dir;
});
}
}),
),
),
Card(
child: _buildTextButton(context, 'create', () async {
if (_path.isEmpty) {
_showToast(LocaleKeys.settings_files_locationCannotBeEmpty.tr());
} else {
await getIt<SettingsLocationCubit>().setLocation(_path);
await widget.onPressedCreate();
}
}),
)
],
);
}
String get _path {
if (directory.isEmpty) return '';
final String path;
if (Platform.isMacOS) {
path = directory.replaceAll('/Volumes/Macintosh HD', '');
} else {
path = directory;
}
return '$path/$_folderName';
}
void _showToast(String message) {
_fToast.showToast(
child: FlowyMessageToast(message: message),
gravity: ToastGravity.CENTER,
);
}
}
Widget _buildTextButton(
BuildContext context, String title, VoidCallback onPressed) {
return SizedBox(
width: 70,
height: 36,
child: RoundedTextButton(
title: title,
onPressed: onPressed,
),
);
}