Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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