2022-05-20 15:55:27 +02:00
|
|
|
import 'dart:io';
|
2023-09-04 17:28:19 +08:00
|
|
|
|
2024-07-08 13:45:57 +08:00
|
|
|
import 'package:appflowy/user/application/user_service.dart';
|
2023-06-10 22:38:25 +08:00
|
|
|
import 'package:appflowy/workspace/application/export/document_exporter.dart';
|
2024-07-22 13:35:42 +08:00
|
|
|
import 'package:appflowy/workspace/application/settings/share/export_service.dart';
|
2024-07-08 13:45:57 +08:00
|
|
|
import 'package:appflowy/workspace/application/view/view_listener.dart';
|
|
|
|
import 'package:appflowy/workspace/application/view/view_service.dart';
|
2024-10-31 14:38:32 +08:00
|
|
|
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
2024-04-11 20:01:36 +08:00
|
|
|
import 'package:appflowy_backend/log.dart';
|
2024-11-13 20:36:35 +08:00
|
|
|
import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart';
|
2023-01-08 12:10:53 +08:00
|
|
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
2023-12-31 07:29:40 +08:00
|
|
|
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
2024-07-08 13:45:57 +08:00
|
|
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
2024-02-24 20:54:10 +07:00
|
|
|
import 'package:appflowy_result/appflowy_result.dart';
|
2023-09-04 17:28:19 +08:00
|
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
|
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
|
|
|
2024-10-03 14:31:04 +08:00
|
|
|
import 'constants.dart';
|
2021-11-10 14:46:59 +08:00
|
|
|
|
2024-10-03 14:31:04 +08:00
|
|
|
part 'share_bloc.freezed.dart';
|
2024-07-08 13:45:57 +08:00
|
|
|
|
2024-07-22 13:35:42 +08:00
|
|
|
class ShareBloc extends Bloc<ShareEvent, ShareState> {
|
|
|
|
ShareBloc({
|
2024-04-11 20:01:36 +08:00
|
|
|
required this.view,
|
2024-07-22 13:35:42 +08:00
|
|
|
}) : super(ShareState.initial()) {
|
|
|
|
on<ShareEvent>((event, emit) async {
|
2024-04-11 20:01:36 +08:00
|
|
|
await event.when(
|
2024-07-08 13:45:57 +08:00
|
|
|
initial: () async {
|
|
|
|
viewListener = ViewListener(viewId: view.id)
|
|
|
|
..start(
|
|
|
|
onViewUpdated: (value) {
|
2024-10-03 14:31:04 +08:00
|
|
|
add(ShareEvent.updateViewName(value.name, value.id));
|
2024-07-08 13:45:57 +08:00
|
|
|
},
|
|
|
|
onViewMoveToTrash: (p0) {
|
2024-07-22 13:35:42 +08:00
|
|
|
add(const ShareEvent.setPublishStatus(false));
|
2024-07-08 13:45:57 +08:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2024-07-22 13:35:42 +08:00
|
|
|
add(const ShareEvent.updatePublishStatus());
|
2024-07-08 13:45:57 +08:00
|
|
|
},
|
2024-10-31 14:38:32 +08:00
|
|
|
share: (type, path) async => _share(
|
|
|
|
type,
|
|
|
|
path,
|
|
|
|
emit,
|
|
|
|
),
|
|
|
|
publish: (nameSpace, publishName, selectedViewIds) => _publish(
|
|
|
|
nameSpace,
|
|
|
|
publishName,
|
|
|
|
selectedViewIds,
|
|
|
|
emit,
|
|
|
|
),
|
|
|
|
unPublish: () async => _unpublish(emit),
|
|
|
|
updatePublishStatus: () async => _updatePublishStatus(emit),
|
2024-10-03 14:31:04 +08:00
|
|
|
updateViewName: (viewName, viewId) async {
|
2024-11-05 09:23:38 +08:00
|
|
|
emit(
|
|
|
|
state.copyWith(
|
|
|
|
viewName: viewName,
|
|
|
|
viewId: viewId,
|
|
|
|
updatePathNameResult: null,
|
|
|
|
publishResult: null,
|
|
|
|
unpublishResult: null,
|
|
|
|
),
|
|
|
|
);
|
2024-07-08 13:45:57 +08:00
|
|
|
},
|
|
|
|
setPublishStatus: (isPublished) {
|
|
|
|
emit(
|
|
|
|
state.copyWith(
|
|
|
|
isPublished: isPublished,
|
|
|
|
url: isPublished ? state.url : '',
|
|
|
|
),
|
|
|
|
);
|
|
|
|
},
|
2024-10-31 14:38:32 +08:00
|
|
|
updatePathName: (pathName) async => _updatePathName(
|
|
|
|
pathName,
|
|
|
|
emit,
|
|
|
|
),
|
2024-04-11 20:01:36 +08:00
|
|
|
);
|
|
|
|
});
|
2022-05-20 15:55:27 +02:00
|
|
|
}
|
2023-06-10 22:38:25 +08:00
|
|
|
|
2024-04-11 20:01:36 +08:00
|
|
|
final ViewPB view;
|
2024-07-08 13:45:57 +08:00
|
|
|
late final ViewListener viewListener;
|
|
|
|
|
2024-07-22 13:35:42 +08:00
|
|
|
late final documentExporter = DocumentExporter(view);
|
2024-07-08 13:45:57 +08:00
|
|
|
|
|
|
|
@override
|
|
|
|
Future<void> close() async {
|
|
|
|
await viewListener.stop();
|
|
|
|
return super.close();
|
|
|
|
}
|
|
|
|
|
2024-10-31 14:38:32 +08:00
|
|
|
Future<void> _share(
|
|
|
|
ShareType type,
|
|
|
|
String? path,
|
|
|
|
Emitter<ShareState> emit,
|
|
|
|
) async {
|
|
|
|
if (ShareType.unimplemented.contains(type)) {
|
|
|
|
Log.error('DocumentShareType $type is not implemented');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
emit(state.copyWith(isLoading: true));
|
|
|
|
|
|
|
|
final result = await _export(type, path);
|
|
|
|
|
|
|
|
emit(
|
|
|
|
state.copyWith(
|
|
|
|
isLoading: false,
|
|
|
|
exportResult: result,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _publish(
|
|
|
|
String nameSpace,
|
|
|
|
String publishName,
|
|
|
|
List<String> selectedViewIds,
|
|
|
|
Emitter<ShareState> emit,
|
|
|
|
) async {
|
|
|
|
// set space name
|
|
|
|
try {
|
|
|
|
final result =
|
|
|
|
await ViewBackendService.getPublishNameSpace().getOrThrow();
|
|
|
|
|
|
|
|
await ViewBackendService.publish(
|
|
|
|
view,
|
|
|
|
name: publishName,
|
|
|
|
selectedViewIds: selectedViewIds,
|
|
|
|
).getOrThrow();
|
|
|
|
|
|
|
|
emit(
|
|
|
|
state.copyWith(
|
|
|
|
isPublished: true,
|
|
|
|
publishResult: FlowySuccess(null),
|
|
|
|
unpublishResult: null,
|
|
|
|
namespace: result.namespace,
|
|
|
|
pathName: publishName,
|
|
|
|
url: ShareConstants.buildPublishUrl(
|
|
|
|
nameSpace: result.namespace,
|
|
|
|
publishName: publishName,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
|
|
|
|
Log.info('publish success: ${result.namespace}/$publishName');
|
|
|
|
} catch (e) {
|
|
|
|
Log.error('publish error: $e');
|
|
|
|
|
|
|
|
emit(
|
|
|
|
state.copyWith(
|
|
|
|
isPublished: false,
|
|
|
|
publishResult: FlowyResult.failure(
|
|
|
|
FlowyError(msg: 'publish error: $e'),
|
|
|
|
),
|
|
|
|
unpublishResult: null,
|
|
|
|
url: '',
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _unpublish(Emitter<ShareState> emit) async {
|
|
|
|
emit(
|
|
|
|
state.copyWith(
|
|
|
|
publishResult: null,
|
|
|
|
unpublishResult: null,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
|
|
|
|
final result = await ViewBackendService.unpublish(view);
|
|
|
|
final isPublished = !result.isSuccess;
|
|
|
|
result.onFailure((f) {
|
|
|
|
Log.error('unpublish error: $f');
|
|
|
|
});
|
|
|
|
|
|
|
|
emit(
|
|
|
|
state.copyWith(
|
|
|
|
isPublished: isPublished,
|
|
|
|
publishResult: null,
|
|
|
|
unpublishResult: result,
|
|
|
|
url: result.fold((_) => '', (_) => state.url),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _updatePublishStatus(Emitter<ShareState> emit) async {
|
|
|
|
final publishInfo = await ViewBackendService.getPublishInfo(view);
|
|
|
|
final enablePublish = await UserBackendService.getCurrentUserProfile().fold(
|
|
|
|
(v) => v.authenticator == AuthenticatorPB.AppFlowyCloud,
|
|
|
|
(p) => false,
|
|
|
|
);
|
2024-11-13 20:36:35 +08:00
|
|
|
|
|
|
|
Log.info(
|
|
|
|
'get publish info: $publishInfo for view: ${view.name}(${view.id})',
|
|
|
|
);
|
|
|
|
|
2024-10-31 14:38:32 +08:00
|
|
|
String workspaceId = state.workspaceId;
|
|
|
|
if (workspaceId.isEmpty) {
|
2024-11-13 20:36:35 +08:00
|
|
|
workspaceId = await UserBackendService.getCurrentWorkspace().fold(
|
|
|
|
(s) => s.id,
|
|
|
|
(f) => '',
|
|
|
|
);
|
2024-10-31 14:38:32 +08:00
|
|
|
}
|
2024-11-13 20:36:35 +08:00
|
|
|
|
|
|
|
final (isPublished, namespace, pathName, url) = publishInfo.fold(
|
|
|
|
(s) {
|
|
|
|
return (
|
|
|
|
// if the unpublishedAtTimestampSec is not set, it means the view is not unpublished.
|
|
|
|
!s.hasUnpublishedAtTimestampSec(),
|
|
|
|
s.namespace,
|
|
|
|
s.publishName,
|
|
|
|
ShareConstants.buildPublishUrl(
|
2024-10-31 14:38:32 +08:00
|
|
|
nameSpace: s.namespace,
|
|
|
|
publishName: s.publishName,
|
|
|
|
),
|
2024-11-13 20:36:35 +08:00
|
|
|
);
|
|
|
|
},
|
|
|
|
(f) => (false, '', '', ''),
|
|
|
|
);
|
|
|
|
|
|
|
|
emit(
|
|
|
|
state.copyWith(
|
|
|
|
isPublished: isPublished,
|
|
|
|
namespace: namespace,
|
|
|
|
pathName: pathName,
|
|
|
|
url: url,
|
|
|
|
viewName: view.name,
|
|
|
|
enablePublish: enablePublish,
|
|
|
|
workspaceId: workspaceId,
|
|
|
|
viewId: view.id,
|
|
|
|
),
|
|
|
|
);
|
2024-10-31 14:38:32 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> _updatePathName(
|
|
|
|
String pathName,
|
|
|
|
Emitter<ShareState> emit,
|
|
|
|
) async {
|
|
|
|
emit(
|
|
|
|
state.copyWith(
|
|
|
|
updatePathNameResult: null,
|
|
|
|
),
|
|
|
|
);
|
2024-11-13 20:36:35 +08:00
|
|
|
|
|
|
|
if (pathName.isEmpty) {
|
|
|
|
emit(
|
|
|
|
state.copyWith(
|
|
|
|
updatePathNameResult: FlowyResult.failure(
|
|
|
|
FlowyError(
|
|
|
|
code: ErrorCode.ViewNameInvalid,
|
|
|
|
msg: 'Path name is invalid',
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-10-31 14:38:32 +08:00
|
|
|
final request = SetPublishNamePB()
|
|
|
|
..viewId = view.id
|
|
|
|
..newName = pathName;
|
|
|
|
final result = await FolderEventSetPublishName(request).send();
|
|
|
|
emit(
|
|
|
|
state.copyWith(
|
|
|
|
updatePathNameResult: result,
|
|
|
|
publishResult: null,
|
|
|
|
unpublishResult: null,
|
|
|
|
pathName: result.fold(
|
|
|
|
(_) => pathName,
|
|
|
|
(f) => state.pathName,
|
|
|
|
),
|
|
|
|
url: result.fold(
|
|
|
|
(s) => ShareConstants.buildPublishUrl(
|
|
|
|
nameSpace: state.namespace,
|
|
|
|
publishName: pathName,
|
|
|
|
),
|
|
|
|
(f) => state.url,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2024-07-22 13:35:42 +08:00
|
|
|
Future<FlowyResult<ShareType, FlowyError>> _export(
|
|
|
|
ShareType type,
|
2024-07-08 13:45:57 +08:00
|
|
|
String? path,
|
|
|
|
) async {
|
2024-07-22 13:35:42 +08:00
|
|
|
final FlowyResult<String, FlowyError> result;
|
|
|
|
if (type == ShareType.csv) {
|
|
|
|
final exportResult = await BackendExportService.exportDatabaseAsCSV(
|
|
|
|
view.id,
|
|
|
|
);
|
|
|
|
result = exportResult.fold(
|
|
|
|
(s) => FlowyResult.success(s.data),
|
|
|
|
(f) => FlowyResult.failure(f),
|
|
|
|
);
|
2024-08-28 18:53:16 +08:00
|
|
|
} else if (type == ShareType.rawDatabaseData) {
|
|
|
|
final exportResult = await BackendExportService.exportDatabaseAsRawData(
|
|
|
|
view.id,
|
|
|
|
);
|
|
|
|
result = exportResult.fold(
|
|
|
|
(s) => FlowyResult.success(s.data),
|
|
|
|
(f) => FlowyResult.failure(f),
|
|
|
|
);
|
2024-07-22 13:35:42 +08:00
|
|
|
} else {
|
|
|
|
result = await documentExporter.export(type.documentExportType);
|
|
|
|
}
|
2024-07-08 13:45:57 +08:00
|
|
|
return result.fold(
|
|
|
|
(s) {
|
|
|
|
if (path != null) {
|
|
|
|
switch (type) {
|
2024-07-22 13:35:42 +08:00
|
|
|
case ShareType.markdown:
|
|
|
|
case ShareType.html:
|
|
|
|
case ShareType.csv:
|
2024-08-28 18:53:16 +08:00
|
|
|
case ShareType.json:
|
|
|
|
case ShareType.rawDatabaseData:
|
2024-07-22 13:35:42 +08:00
|
|
|
File(path).writeAsStringSync(s);
|
|
|
|
return FlowyResult.success(type);
|
2024-07-08 13:45:57 +08:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return FlowyResult.failure(FlowyError());
|
|
|
|
},
|
|
|
|
(f) => FlowyResult.failure(f),
|
|
|
|
);
|
|
|
|
}
|
2024-04-11 20:01:36 +08:00
|
|
|
}
|
|
|
|
|
2024-07-22 13:35:42 +08:00
|
|
|
enum ShareType {
|
|
|
|
// available in document
|
2024-04-11 20:01:36 +08:00
|
|
|
markdown,
|
|
|
|
html,
|
|
|
|
text,
|
2024-07-22 13:35:42 +08:00
|
|
|
link,
|
2024-08-28 18:53:16 +08:00
|
|
|
json,
|
2024-07-22 13:35:42 +08:00
|
|
|
|
|
|
|
// only available in database
|
2024-08-28 18:53:16 +08:00
|
|
|
csv,
|
|
|
|
rawDatabaseData;
|
2024-04-11 20:01:36 +08:00
|
|
|
|
2024-07-22 13:35:42 +08:00
|
|
|
static List<ShareType> get unimplemented => [link];
|
2024-04-11 20:01:36 +08:00
|
|
|
|
2024-07-22 13:35:42 +08:00
|
|
|
DocumentExportType get documentExportType {
|
2024-04-11 20:01:36 +08:00
|
|
|
switch (this) {
|
2024-07-22 13:35:42 +08:00
|
|
|
case ShareType.markdown:
|
2024-04-11 20:01:36 +08:00
|
|
|
return DocumentExportType.markdown;
|
2024-07-22 13:35:42 +08:00
|
|
|
case ShareType.html:
|
2024-04-11 20:01:36 +08:00
|
|
|
return DocumentExportType.html;
|
2024-07-22 13:35:42 +08:00
|
|
|
case ShareType.text:
|
2024-04-11 20:01:36 +08:00
|
|
|
return DocumentExportType.text;
|
2024-08-28 18:53:16 +08:00
|
|
|
case ShareType.json:
|
|
|
|
return DocumentExportType.json;
|
2024-07-22 13:35:42 +08:00
|
|
|
case ShareType.csv:
|
|
|
|
throw UnsupportedError('DocumentShareType.csv is not supported');
|
|
|
|
case ShareType.link:
|
2024-04-11 20:01:36 +08:00
|
|
|
throw UnsupportedError('DocumentShareType.link is not supported');
|
2024-08-28 18:53:16 +08:00
|
|
|
case ShareType.rawDatabaseData:
|
|
|
|
throw UnsupportedError(
|
|
|
|
'DocumentShareType.rawDatabaseData is not supported',
|
|
|
|
);
|
2024-04-11 20:01:36 +08:00
|
|
|
}
|
|
|
|
}
|
2021-11-10 14:46:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
@freezed
|
2024-07-22 13:35:42 +08:00
|
|
|
class ShareEvent with _$ShareEvent {
|
|
|
|
const factory ShareEvent.initial() = _Initial;
|
|
|
|
const factory ShareEvent.share(
|
|
|
|
ShareType type,
|
2024-07-08 13:45:57 +08:00
|
|
|
String? path,
|
|
|
|
) = _Share;
|
2024-07-22 13:35:42 +08:00
|
|
|
const factory ShareEvent.publish(
|
2024-07-08 13:45:57 +08:00
|
|
|
String nameSpace,
|
|
|
|
String pageId,
|
2024-07-22 13:35:42 +08:00
|
|
|
List<String> selectedViewIds,
|
2024-07-08 13:45:57 +08:00
|
|
|
) = _Publish;
|
2024-07-22 13:35:42 +08:00
|
|
|
const factory ShareEvent.unPublish() = _UnPublish;
|
2024-10-03 14:31:04 +08:00
|
|
|
const factory ShareEvent.updateViewName(String name, String viewId) =
|
|
|
|
_UpdateViewName;
|
2024-07-22 13:35:42 +08:00
|
|
|
const factory ShareEvent.updatePublishStatus() = _UpdatePublishStatus;
|
|
|
|
const factory ShareEvent.setPublishStatus(bool isPublished) =
|
2024-07-08 13:45:57 +08:00
|
|
|
_SetPublishStatus;
|
2024-10-31 14:38:32 +08:00
|
|
|
const factory ShareEvent.updatePathName(String pathName) = _UpdatePathName;
|
2021-11-10 14:46:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
@freezed
|
2024-07-22 13:35:42 +08:00
|
|
|
class ShareState with _$ShareState {
|
|
|
|
const factory ShareState({
|
2024-07-08 13:45:57 +08:00
|
|
|
required bool isPublished,
|
|
|
|
required bool isLoading,
|
|
|
|
required String url,
|
|
|
|
required String viewName,
|
|
|
|
required bool enablePublish,
|
2024-07-22 13:35:42 +08:00
|
|
|
FlowyResult<ShareType, FlowyError>? exportResult,
|
2024-07-08 13:45:57 +08:00
|
|
|
FlowyResult<void, FlowyError>? publishResult,
|
|
|
|
FlowyResult<void, FlowyError>? unpublishResult,
|
2024-10-31 14:38:32 +08:00
|
|
|
FlowyResult<void, FlowyError>? updatePathNameResult,
|
2024-10-03 14:31:04 +08:00
|
|
|
required String viewId,
|
|
|
|
required String workspaceId,
|
2024-10-31 14:38:32 +08:00
|
|
|
required String namespace,
|
|
|
|
required String pathName,
|
2024-07-22 13:35:42 +08:00
|
|
|
}) = _ShareState;
|
2024-07-08 13:45:57 +08:00
|
|
|
|
2024-07-22 13:35:42 +08:00
|
|
|
factory ShareState.initial() => const ShareState(
|
2024-07-08 13:45:57 +08:00
|
|
|
isLoading: false,
|
|
|
|
isPublished: false,
|
|
|
|
enablePublish: true,
|
|
|
|
url: '',
|
|
|
|
viewName: '',
|
2024-10-03 14:31:04 +08:00
|
|
|
viewId: '',
|
|
|
|
workspaceId: '',
|
2024-10-31 14:38:32 +08:00
|
|
|
namespace: '',
|
|
|
|
pathName: '',
|
2024-07-08 13:45:57 +08:00
|
|
|
);
|
2021-11-10 14:46:59 +08:00
|
|
|
}
|