feat: optimize image upload process and display an error message if upload fails (#4679)

* chore: optimize image upload

* feat: show upload image status

* chore: upload the ai image to cloud server
This commit is contained in:
Lucas.Xu 2024-02-19 16:24:47 +07:00 committed by GitHub
parent 252699d249
commit 26f8bbf7c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 60 additions and 16 deletions

View File

@ -104,12 +104,14 @@ class DocumentService {
/// Upload a file to the cloud storage.
Future<Either<FlowyError, UploadedFilePB>> uploadFile({
required String localFilePath,
bool isAsync = true,
}) async {
final workspace = await FolderEventReadCurrentWorkspace().send();
return workspace.fold((l) async {
final payload = UploadFileParamsPB(
workspaceId: l.id,
localFilePath: localFilePath,
isAsync: isAsync,
);
final result = await DocumentEventUploadFile(payload).send();
return result.swap();

View File

@ -615,7 +615,7 @@ class DocumentCoverState extends State<DocumentCover> {
details = await saveImageToLocalStorage(details);
} else {
// else we should save the image to cloud storage
details = await saveImageToCloudStorage(details);
(details, _) = await saveImageToCloudStorage(details);
}
}
widget.onChangeCover(type, details);

View File

@ -44,6 +44,8 @@ class ImagePlaceholderState extends State<ImagePlaceholder> {
final documentService = DocumentService();
late final editorState = context.read<EditorState>();
bool showLoading = false;
@override
Widget build(BuildContext context) {
final Widget child = DecoratedBox(
@ -65,9 +67,19 @@ class ImagePlaceholderState extends State<ImagePlaceholder> {
size: Size.square(24),
),
const HSpace(10),
FlowyText(
LocaleKeys.document_plugins_image_addAnImage.tr(),
),
...showLoading
? [
FlowyText(
LocaleKeys.document_imageBlock_imageIsUploading.tr(),
),
const HSpace(8),
const CircularProgressIndicator.adaptive(),
]
: [
FlowyText(
LocaleKeys.document_plugins_image_addAnImage.tr(),
),
],
],
),
),
@ -188,6 +200,7 @@ class ImagePlaceholderState extends State<ImagePlaceholder> {
final transaction = editorState.transaction;
String? path;
String? errorMessage;
CustomImageType imageType = CustomImageType.local;
// if the user is using local authenticator, we need to save the image to local storage
@ -195,14 +208,22 @@ class ImagePlaceholderState extends State<ImagePlaceholder> {
path = await saveImageToLocalStorage(url);
} else {
// else we should save the image to cloud storage
path = await saveImageToCloudStorage(url);
setState(() {
showLoading = true;
});
(path, errorMessage) = await saveImageToCloudStorage(url);
setState(() {
showLoading = false;
});
imageType = CustomImageType.internal;
}
if (mounted && path == null) {
showSnackBarMessage(
context,
LocaleKeys.document_imageBlock_error_invalidImage.tr(),
errorMessage == null
? LocaleKeys.document_imageBlock_error_invalidImage.tr()
: ': $errorMessage',
);
return;
}
@ -244,12 +265,8 @@ class ImagePlaceholderState extends State<ImagePlaceholder> {
final response = await get(uri);
await File(copyToPath).writeAsBytes(response.bodyBytes);
final transaction = editorState.transaction;
transaction.updateNode(widget.node, {
ImageBlockKeys.url: copyToPath,
});
await editorState.apply(transaction);
await insertLocalImage(copyToPath);
await File(copyToPath).delete();
} catch (e) {
Log.error('cannot save image file', e);
}

View File

@ -34,19 +34,22 @@ Future<String?> saveImageToLocalStorage(String localImagePath) async {
}
}
Future<String?> saveImageToCloudStorage(String localImagePath) async {
Future<(String? path, String? errorMessage)> saveImageToCloudStorage(
String localImagePath,
) async {
final documentService = DocumentService();
final result = await documentService.uploadFile(
localFilePath: localImagePath,
isAsync: false,
);
return result.fold(
(l) => null,
(l) => (null, l.msg),
(r) async {
await CustomImageCacheManager().putFile(
r.url,
File(localImagePath).readAsBytesSync(),
);
return r.url;
return (r.url, null);
},
);
}

View File

@ -102,6 +102,7 @@ class _ResizableImageState extends State<ResizableImage> {
progressIndicatorBuilder: (context, url, progress) =>
_buildLoading(context),
);
child = _cacheImage!;
} else {
// load local file

View File

@ -0,0 +1,20 @@
import 'dart:convert';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:http/http.dart' as http;
Future<bool> isImageExistOnCloud({
required String url,
required UserProfilePB userProfilePB,
}) async {
final header = <String, String>{};
final token = userProfilePB.token;
try {
final decodedToken = jsonDecode(token);
header['Authorization'] = 'Bearer ${decodedToken['access_token']}';
final response = await http.get(Uri.http(url), headers: header);
return response.statusCode == 200;
} catch (_) {
return false;
}
}

View File

@ -862,7 +862,8 @@
"successToAddImageToGallery": "Image added to gallery successfully",
"unableToLoadImage": "Unable to load image",
"maximumImageSize": "Maximum supported upload image size is 10MB",
"uploadImageErrorImageSizeTooBig": "Image size must be less than 10MB"
"uploadImageErrorImageSizeTooBig": "Image size must be less than 10MB",
"imageIsUploading": "Image is uploading"
},
"codeBlock": {
"language": {