Skip to content

Commit

Permalink
Add ability to upload multiple images at a time for a post/comment (#…
Browse files Browse the repository at this point in the history
…1480)

added ability to upload multiple images at a time for a post/comment body
  • Loading branch information
hjiangsu authored Jul 10, 2024
1 parent 3e3f4d5 commit 108605b
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 32 deletions.
17 changes: 13 additions & 4 deletions lib/comment/cubit/create_comment_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,27 @@ class CreateCommentCubit extends Cubit<CreateCommentState> {
emit(state.copyWith(status: CreateCommentStatus.initial, message: null));
}

Future<void> uploadImage(String imageFile) async {
Future<void> uploadImages(List<String> imageFiles) async {
Account? account = await fetchActiveProfileAccount();
if (account == null) return;

PictrsApi pictrs = PictrsApi(account.instance!);
List<String> urls = [];

emit(state.copyWith(status: CreateCommentStatus.imageUploadInProgress));

try {
PictrsUpload result = await pictrs.upload(filePath: imageFile, auth: account.jwt);
String url = "https://${account.instance!}/pictrs/image/${result.files[0].file}";
for (String imageFile in imageFiles) {
PictrsUpload result = await pictrs.upload(filePath: imageFile, auth: account.jwt);
String url = "https://${account.instance!}/pictrs/image/${result.files[0].file}";

urls.add(url);

// Add a delay between each upload to avoid possible rate limiting
await Future.wait(urls.map((url) => Future.delayed(const Duration(milliseconds: 500))));
}

emit(state.copyWith(status: CreateCommentStatus.imageUploadSuccess, imageUrl: url));
emit(state.copyWith(status: CreateCommentStatus.imageUploadSuccess, imageUrls: urls));
} catch (e) {
emit(state.copyWith(status: CreateCommentStatus.imageUploadFailure, message: e.toString()));
}
Expand Down
12 changes: 6 additions & 6 deletions lib/comment/cubit/create_comment_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class CreateCommentState extends Equatable {
const CreateCommentState({
this.status = CreateCommentStatus.initial,
this.commentView,
this.imageUrl,
this.imageUrls,
this.message,
});

Expand All @@ -26,26 +26,26 @@ class CreateCommentState extends Equatable {
/// The result of the created or edited comment
final CommentView? commentView;

/// The url of the uploaded image
final String? imageUrl;
/// The urls of the uploaded images
final List<String>? imageUrls;

/// The info or error message to be displayed as a snackbar
final String? message;

CreateCommentState copyWith({
required CreateCommentStatus status,
CommentView? commentView,
String? imageUrl,
List<String>? imageUrls,
String? message,
}) {
return CreateCommentState(
status: status,
commentView: commentView ?? this.commentView,
imageUrl: imageUrl ?? this.imageUrl,
imageUrls: imageUrls ?? this.imageUrls,
message: message ?? this.message,
);
}

@override
List<dynamic> get props => [status, commentView, imageUrl, message];
List<dynamic> get props => [status, commentView, imageUrls, message];
}
7 changes: 4 additions & 3 deletions lib/comment/view/create_comment_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,8 @@ class _CreateCommentPageState extends State<CreateCommentPage> {

switch (state.status) {
case CreateCommentStatus.imageUploadSuccess:
_bodyTextController.text = _bodyTextController.text.replaceRange(_bodyTextController.selection.end, _bodyTextController.selection.end, "![](${state.imageUrl})");
String markdownImages = state.imageUrls?.map((url) => '![]($url)').join('\n\n') ?? '';
_bodyTextController.text = _bodyTextController.text.replaceRange(_bodyTextController.selection.end, _bodyTextController.selection.end, markdownImages);
break;
case CreateCommentStatus.imageUploadFailure:
showSnackbar(l10n.postUploadImageError, leadingIcon: Icons.warning_rounded, leadingIconColor: theme.colorScheme.errorContainer);
Expand Down Expand Up @@ -471,8 +472,8 @@ class _CreateCommentPageState extends State<CreateCommentPage> {
customImageButtonAction: () async {
if (state.status == CreateCommentStatus.imageUploadInProgress) return;

String imagePath = await selectImageToUpload();
if (context.mounted) context.read<CreateCommentCubit>().uploadImage(imagePath);
List<String> imagesPath = await selectImagesToUpload(allowMultiple: true);
if (context.mounted) context.read<CreateCommentCubit>().uploadImages(imagesPath);
},
getAlternativeSelection: () => replyViewSelection,
),
Expand Down
15 changes: 8 additions & 7 deletions lib/community/pages/create_post_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ class _CreatePostPageState extends State<CreatePostPage> {

if (widget.image != null) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (context.mounted) context.read<CreatePostCubit>().uploadImage(widget.image!.path, isPostImage: true);
if (context.mounted) context.read<CreatePostCubit>().uploadImages([widget.image!.path], isPostImage: true);
});
}

Expand Down Expand Up @@ -331,10 +331,11 @@ class _CreatePostPageState extends State<CreatePostPage> {

switch (state.status) {
case CreatePostStatus.imageUploadSuccess:
_bodyTextController.text = _bodyTextController.text.replaceRange(_bodyTextController.selection.end, _bodyTextController.selection.end, "![](${state.imageUrl})");
String markdownImages = state.imageUrls?.map((url) => '![]($url)').join('\n\n') ?? '';
_bodyTextController.text = _bodyTextController.text.replaceRange(_bodyTextController.selection.end, _bodyTextController.selection.end, markdownImages);
break;
case CreatePostStatus.postImageUploadSuccess:
_urlTextController.text = state.imageUrl ?? '';
_urlTextController.text = state.imageUrls?.first ?? '';
break;
case CreatePostStatus.imageUploadFailure:
case CreatePostStatus.postImageUploadFailure:
Expand Down Expand Up @@ -460,8 +461,8 @@ class _CreatePostPageState extends State<CreatePostPage> {
onPressed: () async {
if (state.status == CreatePostStatus.postImageUploadInProgress) return;

String imagePath = await selectImageToUpload();
if (context.mounted) context.read<CreatePostCubit>().uploadImage(imagePath, isPostImage: true);
List<String> imagesPath = await selectImagesToUpload();
if (context.mounted) context.read<CreatePostCubit>().uploadImages(imagesPath, isPostImage: true);
},
icon: state.status == CreatePostStatus.postImageUploadInProgress
? const SizedBox(
Expand Down Expand Up @@ -604,8 +605,8 @@ class _CreatePostPageState extends State<CreatePostPage> {
customImageButtonAction: () async {
if (state.status == CreatePostStatus.imageUploadInProgress) return;

String imagePath = await selectImageToUpload();
if (context.mounted) context.read<CreatePostCubit>().uploadImage(imagePath, isPostImage: false);
List<String> imagesPath = await selectImagesToUpload(allowMultiple: true);
if (context.mounted) context.read<CreatePostCubit>().uploadImages(imagesPath, isPostImage: false);
},
),
),
Expand Down
16 changes: 12 additions & 4 deletions lib/post/cubit/create_post_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,27 @@ class CreatePostCubit extends Cubit<CreatePostState> {
emit(state.copyWith(status: CreatePostStatus.initial, message: null));
}

Future<void> uploadImage(String imageFile, {bool isPostImage = false}) async {
Future<void> uploadImages(List<String> imageFiles, {bool isPostImage = false}) async {
Account? account = await fetchActiveProfileAccount();
if (account == null) return;

PictrsApi pictrs = PictrsApi(account.instance!);
List<String> urls = [];

isPostImage ? emit(state.copyWith(status: CreatePostStatus.postImageUploadInProgress)) : emit(state.copyWith(status: CreatePostStatus.imageUploadInProgress));

try {
PictrsUpload result = await pictrs.upload(filePath: imageFile, auth: account.jwt);
String url = "https://${account.instance!}/pictrs/image/${result.files[0].file}";
for (String imageFile in imageFiles) {
PictrsUpload result = await pictrs.upload(filePath: imageFile, auth: account.jwt);
String url = "https://${account.instance!}/pictrs/image/${result.files[0].file}";

isPostImage ? emit(state.copyWith(status: CreatePostStatus.postImageUploadSuccess, imageUrl: url)) : emit(state.copyWith(status: CreatePostStatus.imageUploadSuccess, imageUrl: url));
urls.add(url);

// Add a delay between each upload to avoid possible rate limiting
await Future.wait(urls.map((url) => Future.delayed(const Duration(milliseconds: 500))));
}

isPostImage ? emit(state.copyWith(status: CreatePostStatus.postImageUploadSuccess, imageUrls: urls)) : emit(state.copyWith(status: CreatePostStatus.imageUploadSuccess, imageUrls: urls));
} catch (e) {
isPostImage
? emit(state.copyWith(status: CreatePostStatus.postImageUploadFailure, message: e.toString()))
Expand Down
12 changes: 6 additions & 6 deletions lib/post/cubit/create_post_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class CreatePostState extends Equatable {
const CreatePostState({
this.status = CreatePostStatus.initial,
this.postViewMedia,
this.imageUrl,
this.imageUrls,
this.message,
});

Expand All @@ -29,26 +29,26 @@ class CreatePostState extends Equatable {
/// The result of the created or edited post
final PostViewMedia? postViewMedia;

/// The url of the uploaded image
final String? imageUrl;
/// The urls of the uploaded images
final List<String>? imageUrls;

/// The info or error message to be displayed as a snackbar
final String? message;

CreatePostState copyWith({
required CreatePostStatus status,
PostViewMedia? postViewMedia,
String? imageUrl,
List<String>? imageUrls,
String? message,
}) {
return CreatePostState(
status: status,
postViewMedia: postViewMedia ?? this.postViewMedia,
imageUrl: imageUrl ?? this.imageUrl,
imageUrls: imageUrls ?? this.imageUrls,
message: message ?? this.message,
);
}

@override
List<dynamic> get props => [status, postViewMedia, imageUrl, message];
List<dynamic> get props => [status, postViewMedia, imageUrls, message];
}
9 changes: 7 additions & 2 deletions lib/utils/media/image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,16 @@ void uploadImage(BuildContext context, ImageBloc imageBloc, {bool postImage = fa
}
}

Future<String> selectImageToUpload() async {
Future<List<String>> selectImagesToUpload({bool allowMultiple = false}) async {
final ImagePicker picker = ImagePicker();

if (allowMultiple) {
List<XFile>? files = await picker.pickMultiImage();
return files.map((file) => file.path).toList();
}

XFile? file = await picker.pickImage(source: ImageSource.gallery);
return file!.path;
return [file!.path];
}

void showImageViewer(BuildContext context, {String? url, Uint8List? bytes, int? postId, void Function()? navigateToPost}) {
Expand Down

0 comments on commit 108605b

Please sign in to comment.