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. /// Upload a file to the cloud storage.
Future<Either<FlowyError, UploadedFilePB>> uploadFile({ Future<Either<FlowyError, UploadedFilePB>> uploadFile({
required String localFilePath, required String localFilePath,
bool isAsync = true,
}) async { }) async {
final workspace = await FolderEventReadCurrentWorkspace().send(); final workspace = await FolderEventReadCurrentWorkspace().send();
return workspace.fold((l) async { return workspace.fold((l) async {
final payload = UploadFileParamsPB( final payload = UploadFileParamsPB(
workspaceId: l.id, workspaceId: l.id,
localFilePath: localFilePath, localFilePath: localFilePath,
isAsync: isAsync,
); );
final result = await DocumentEventUploadFile(payload).send(); final result = await DocumentEventUploadFile(payload).send();
return result.swap(); return result.swap();

View File

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

View File

@ -44,6 +44,8 @@ class ImagePlaceholderState extends State<ImagePlaceholder> {
final documentService = DocumentService(); final documentService = DocumentService();
late final editorState = context.read<EditorState>(); late final editorState = context.read<EditorState>();
bool showLoading = false;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Widget child = DecoratedBox( final Widget child = DecoratedBox(
@ -65,9 +67,19 @@ class ImagePlaceholderState extends State<ImagePlaceholder> {
size: Size.square(24), size: Size.square(24),
), ),
const HSpace(10), const HSpace(10),
FlowyText( ...showLoading
LocaleKeys.document_plugins_image_addAnImage.tr(), ? [
), 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; final transaction = editorState.transaction;
String? path; String? path;
String? errorMessage;
CustomImageType imageType = CustomImageType.local; CustomImageType imageType = CustomImageType.local;
// if the user is using local authenticator, we need to save the image to local storage // 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); path = await saveImageToLocalStorage(url);
} else { } else {
// else we should save the image to cloud storage // 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; imageType = CustomImageType.internal;
} }
if (mounted && path == null) { if (mounted && path == null) {
showSnackBarMessage( showSnackBarMessage(
context, context,
LocaleKeys.document_imageBlock_error_invalidImage.tr(), errorMessage == null
? LocaleKeys.document_imageBlock_error_invalidImage.tr()
: ': $errorMessage',
); );
return; return;
} }
@ -244,12 +265,8 @@ class ImagePlaceholderState extends State<ImagePlaceholder> {
final response = await get(uri); final response = await get(uri);
await File(copyToPath).writeAsBytes(response.bodyBytes); await File(copyToPath).writeAsBytes(response.bodyBytes);
await insertLocalImage(copyToPath);
final transaction = editorState.transaction; await File(copyToPath).delete();
transaction.updateNode(widget.node, {
ImageBlockKeys.url: copyToPath,
});
await editorState.apply(transaction);
} catch (e) { } catch (e) {
Log.error('cannot save image file', 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 documentService = DocumentService();
final result = await documentService.uploadFile( final result = await documentService.uploadFile(
localFilePath: localImagePath, localFilePath: localImagePath,
isAsync: false,
); );
return result.fold( return result.fold(
(l) => null, (l) => (null, l.msg),
(r) async { (r) async {
await CustomImageCacheManager().putFile( await CustomImageCacheManager().putFile(
r.url, r.url,
File(localImagePath).readAsBytesSync(), 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) => progressIndicatorBuilder: (context, url, progress) =>
_buildLoading(context), _buildLoading(context),
); );
child = _cacheImage!; child = _cacheImage!;
} else { } else {
// load local file // 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", "successToAddImageToGallery": "Image added to gallery successfully",
"unableToLoadImage": "Unable to load image", "unableToLoadImage": "Unable to load image",
"maximumImageSize": "Maximum supported upload image size is 10MB", "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": { "codeBlock": {
"language": { "language": {