|
@@ -1,15 +1,22 @@
|
|
|
+import 'dart:convert';
|
|
|
+import 'dart:io';
|
|
|
+
|
|
|
+import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
|
|
|
+import 'package:appflowy/plugins/document/application/prelude.dart';
|
|
|
import 'package:appflowy/startup/startup.dart';
|
|
|
import 'package:appflowy/util/file_picker/file_picker_service.dart';
|
|
|
import 'package:appflowy/workspace/application/settings/settings_file_exporter_cubit.dart';
|
|
|
+import 'package:appflowy_backend/log.dart';
|
|
|
+import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
|
|
import 'package:dartz/dartz.dart' as dartz;
|
|
|
import 'package:easy_localization/easy_localization.dart';
|
|
|
-import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
|
|
+import 'package:flowy_infra_ui/flowy_infra_ui.dart' hide WidgetBuilder;
|
|
|
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
|
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart';
|
|
|
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
|
|
|
import 'package:flutter/material.dart';
|
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
|
-
|
|
|
+import 'package:path/path.dart' as p;
|
|
|
import '../../../../generated/locale_keys.g.dart';
|
|
|
|
|
|
class FileExporterWidget extends StatefulWidget {
|
|
@@ -22,24 +29,44 @@ class FileExporterWidget extends StatefulWidget {
|
|
|
class _FileExporterWidgetState extends State<FileExporterWidget> {
|
|
|
// Map<String, List<String>> _selectedPages = {};
|
|
|
|
|
|
+ SettingsFileExporterCubit? cubit;
|
|
|
+
|
|
|
@override
|
|
|
Widget build(BuildContext context) {
|
|
|
- return Column(
|
|
|
- crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
- children: [
|
|
|
- FlowyText.medium(
|
|
|
- LocaleKeys.settings_files_selectFiles.tr(),
|
|
|
- fontSize: 16.0,
|
|
|
- ),
|
|
|
- const VSpace(8),
|
|
|
- Expanded(child: _buildFileSelector(context)),
|
|
|
- const VSpace(8),
|
|
|
- _buildButtons(context)
|
|
|
- ],
|
|
|
+ return FutureBuilder<dartz.Either<WorkspaceSettingPB, FlowyError>>(
|
|
|
+ future: FolderEventReadCurrentWorkspace().send(),
|
|
|
+ builder: (context, snapshot) {
|
|
|
+ if (snapshot.hasData &&
|
|
|
+ snapshot.connectionState == ConnectionState.done) {
|
|
|
+ final workspaces = snapshot.data?.getLeftOrNull<WorkspaceSettingPB>();
|
|
|
+ if (workspaces != null) {
|
|
|
+ final views = workspaces.workspace.views;
|
|
|
+ cubit ??= SettingsFileExporterCubit(views: views);
|
|
|
+ return BlocProvider<SettingsFileExporterCubit>.value(
|
|
|
+ value: cubit!,
|
|
|
+ child: Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
+ mainAxisSize: MainAxisSize.min,
|
|
|
+ children: [
|
|
|
+ FlowyText.medium(
|
|
|
+ LocaleKeys.settings_files_selectFiles.tr(),
|
|
|
+ fontSize: 16.0,
|
|
|
+ ),
|
|
|
+ const VSpace(8),
|
|
|
+ const Expanded(child: _ExpandedList()),
|
|
|
+ const VSpace(8),
|
|
|
+ _buildButtons()
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return const CircularProgressIndicator();
|
|
|
+ },
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- Row _buildButtons(BuildContext context) {
|
|
|
+ Widget _buildButtons() {
|
|
|
return Row(
|
|
|
children: [
|
|
|
const Spacer(),
|
|
@@ -55,8 +82,28 @@ class _FileExporterWidgetState extends State<FileExporterWidget> {
|
|
|
onPressed: () async {
|
|
|
await getIt<FilePickerService>()
|
|
|
.getDirectoryPath()
|
|
|
- .then((exportPath) {
|
|
|
- Navigator.of(context).pop();
|
|
|
+ .then((exportPath) async {
|
|
|
+ if (exportPath != null && cubit != null) {
|
|
|
+ final views = cubit!.state.selectedViews;
|
|
|
+ final result =
|
|
|
+ await _AppFlowyFileExporter.exportToPath(exportPath, views);
|
|
|
+ if (result.$1) {
|
|
|
+ // success
|
|
|
+ _showToast(LocaleKeys.settings_files_exportFileSuccess.tr());
|
|
|
+ } else {
|
|
|
+ _showToast(
|
|
|
+ LocaleKeys.settings_files_exportFileFail.tr() +
|
|
|
+ result.$2.join('\n'),
|
|
|
+ );
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ _showToast(LocaleKeys.settings_files_exportFileFail.tr());
|
|
|
+ }
|
|
|
+ if (mounted) {
|
|
|
+ Navigator.of(context).popUntil(
|
|
|
+ (router) => router.settings.name == '/',
|
|
|
+ );
|
|
|
+ }
|
|
|
});
|
|
|
},
|
|
|
),
|
|
@@ -64,24 +111,14 @@ class _FileExporterWidgetState extends State<FileExporterWidget> {
|
|
|
);
|
|
|
}
|
|
|
|
|
|
- FutureBuilder<dartz.Either<WorkspaceSettingPB, FlowyError>>
|
|
|
- _buildFileSelector(BuildContext context) {
|
|
|
- return FutureBuilder<dartz.Either<WorkspaceSettingPB, FlowyError>>(
|
|
|
- future: FolderEventReadCurrentWorkspace().send(),
|
|
|
- builder: (context, snapshot) {
|
|
|
- if (snapshot.hasData &&
|
|
|
- snapshot.connectionState == ConnectionState.done) {
|
|
|
- final workspaces = snapshot.data?.getLeftOrNull<WorkspaceSettingPB>();
|
|
|
- if (workspaces != null) {
|
|
|
- final views = workspaces.workspace.views;
|
|
|
- return BlocProvider<SettingsFileExporterCubit>(
|
|
|
- create: (_) => SettingsFileExporterCubit(views: views),
|
|
|
- child: const _ExpandedList(),
|
|
|
- );
|
|
|
- }
|
|
|
- }
|
|
|
- return const CircularProgressIndicator();
|
|
|
- },
|
|
|
+ void _showToast(String message) {
|
|
|
+ ScaffoldMessenger.of(context).showSnackBar(
|
|
|
+ SnackBar(
|
|
|
+ content: FlowyText(
|
|
|
+ message,
|
|
|
+ color: Theme.of(context).colorScheme.onSurface,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
);
|
|
|
}
|
|
|
}
|
|
@@ -131,9 +168,9 @@ class _ExpandedListState extends State<_ExpandedList> {
|
|
|
final apps = state.views;
|
|
|
final expanded = state.expanded;
|
|
|
final selectedItems = state.selectedItems;
|
|
|
- final isExpaned = expanded[index] == true;
|
|
|
+ final isExpanded = expanded[index] == true;
|
|
|
List<Widget> expandedChildren = [];
|
|
|
- if (isExpaned) {
|
|
|
+ if (isExpanded) {
|
|
|
for (var i = 0; i < selectedItems[index].length; i++) {
|
|
|
final name = apps[index].childViews[i].name;
|
|
|
final checkbox = CheckboxListTile(
|
|
@@ -160,7 +197,7 @@ class _ExpandedListState extends State<_ExpandedList> {
|
|
|
child: ListTile(
|
|
|
title: FlowyText.medium(apps[index].name),
|
|
|
trailing: Icon(
|
|
|
- isExpaned
|
|
|
+ isExpanded
|
|
|
? Icons.arrow_drop_down_rounded
|
|
|
: Icons.arrow_drop_up_rounded,
|
|
|
),
|
|
@@ -182,3 +219,45 @@ extension AppFlowy on dartz.Either {
|
|
|
return null;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+class _AppFlowyFileExporter {
|
|
|
+ static Future<(bool result, List<String> failedNames)> exportToPath(
|
|
|
+ String path,
|
|
|
+ List<ViewPB> views,
|
|
|
+ ) async {
|
|
|
+ final failedFileNames = <String>[];
|
|
|
+ final Map<String, int> names = {};
|
|
|
+ final documentService = DocumentService();
|
|
|
+ for (final view in views) {
|
|
|
+ String? content;
|
|
|
+ String? fileExtension;
|
|
|
+ switch (view.layout) {
|
|
|
+ case ViewLayoutPB.Document:
|
|
|
+ final document = await documentService.openDocument(view: view);
|
|
|
+ document.fold((l) => Log.error(l), (r) {
|
|
|
+ final json = r.toDocument()?.toJson();
|
|
|
+ if (json != null) {
|
|
|
+ content = jsonEncode(json);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ fileExtension = 'afdocument';
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ // TODO(nathan): export the new databse data to json
|
|
|
+ content = null;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (content != null) {
|
|
|
+ final count = names.putIfAbsent(view.name, () => 0);
|
|
|
+ final name = count == 0 ? view.name : '${view.name}($count)';
|
|
|
+ final file = File(p.join(path, '$name.$fileExtension'));
|
|
|
+ await file.writeAsString(content!);
|
|
|
+ names[view.name] = count + 1;
|
|
|
+ } else {
|
|
|
+ failedFileNames.add(view.name);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return (failedFileNames.isEmpty, failedFileNames);
|
|
|
+ }
|
|
|
+}
|