kopia lustrzana https://gitlab.com/mysocialportal/relatica
Add ability to add existing images to post
rodzic
1215363dc4
commit
292b560b41
|
@ -0,0 +1,63 @@
|
|||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../models/image_entry.dart';
|
||||
import '../../screens/existing_image_selector_screen.dart';
|
||||
import '../padding.dart';
|
||||
|
||||
class GallerySelectorControl extends StatefulWidget {
|
||||
final List<ImageEntry> entries;
|
||||
|
||||
const GallerySelectorControl({super.key, required this.entries});
|
||||
|
||||
@override
|
||||
State<GallerySelectorControl> createState() => _GallerySelectorControlState();
|
||||
}
|
||||
|
||||
class _GallerySelectorControlState extends State<GallerySelectorControl> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const thumbnailSize = 50.0;
|
||||
return Column(children: [
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
||||
Text(
|
||||
'Existing Images',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
final result = await Navigator.of(context).push<List<ImageEntry>>(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ExistingImageSelectorScreen()));
|
||||
if (result != null) {
|
||||
setState(() {
|
||||
widget.entries.clear();
|
||||
widget.entries.addAll(result);
|
||||
});
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.photo_library),
|
||||
),
|
||||
]),
|
||||
const VerticalDivider(),
|
||||
if (widget.entries.isNotEmpty)
|
||||
SizedBox(
|
||||
height: thumbnailSize,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
separatorBuilder: (context, index) => const HorizontalPadding(
|
||||
width: 5,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
return CachedNetworkImage(
|
||||
width: thumbnailSize,
|
||||
height: thumbnailSize,
|
||||
imageUrl: widget.entries[index].thumbnailUrl,
|
||||
);
|
||||
},
|
||||
itemCount: widget.entries.length,
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import 'package:provider/provider.dart';
|
|||
import 'package:result_monad/result_monad.dart';
|
||||
|
||||
import '../../models/gallery_data.dart';
|
||||
import '../../models/media_attachment_uploads/entry_media_items.dart';
|
||||
import '../../models/media_attachment_uploads/new_entry_media_items.dart';
|
||||
import '../../services/gallery_service.dart';
|
||||
import '../../services/media_upload_attachment_helper.dart';
|
||||
import '../../utils/snackbar_builder.dart';
|
||||
|
@ -14,7 +14,7 @@ import 'media_upload_editor_control.dart';
|
|||
final _logger = Logger('$MediaUploadsControl');
|
||||
|
||||
class MediaUploadsControl extends StatefulWidget {
|
||||
final EntryMediaItems entryMediaItems;
|
||||
final NewEntryMediaItems entryMediaItems;
|
||||
|
||||
const MediaUploadsControl({super.key, required this.entryMediaItems});
|
||||
|
||||
|
@ -37,7 +37,7 @@ class _MediaUploadsControlState extends State<MediaUploadsControl> {
|
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Images',
|
||||
'New Images',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
Row(
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import 'media_upload_attachment.dart';
|
||||
|
||||
class EntryMediaItems {
|
||||
class NewEntryMediaItems {
|
||||
String albumName;
|
||||
|
||||
final List<MediaUploadAttachment> attachments;
|
||||
|
||||
EntryMediaItems({
|
||||
NewEntryMediaItems({
|
||||
this.albumName = '',
|
||||
List<MediaUploadAttachment>? existingAttachments,
|
||||
}) : attachments = existingAttachments ?? [];
|
|
@ -5,10 +5,12 @@ import 'package:logging/logging.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../controls/entry_media_attachments/gallery_selector_control.dart';
|
||||
import '../controls/entry_media_attachments/media_uploads_control.dart';
|
||||
import '../controls/padding.dart';
|
||||
import '../controls/timeline/status_header_control.dart';
|
||||
import '../models/media_attachment_uploads/entry_media_items.dart';
|
||||
import '../models/image_entry.dart';
|
||||
import '../models/media_attachment_uploads/new_entry_media_items.dart';
|
||||
import '../models/timeline_entry.dart';
|
||||
import '../services/timeline_manager.dart';
|
||||
import '../utils/snackbar_builder.dart';
|
||||
|
@ -29,7 +31,8 @@ class _EditorScreenState extends State<EditorScreen> {
|
|||
final spoilerController = TextEditingController();
|
||||
final localEntryTemporaryId = const Uuid().v4();
|
||||
TimelineEntry? parentEntry;
|
||||
final entryMediaItems = EntryMediaItems();
|
||||
final newMediaItems = NewEntryMediaItems();
|
||||
final existingMediaItems = <ImageEntry>[];
|
||||
|
||||
var isSubmitting = false;
|
||||
|
||||
|
@ -70,7 +73,8 @@ class _EditorScreenState extends State<EditorScreen> {
|
|||
contentController.text,
|
||||
spoilerText: spoilerController.text,
|
||||
inReplyToId: widget.parentId,
|
||||
mediaItems: entryMediaItems,
|
||||
newMediaItems: newMediaItems,
|
||||
existingMediaItems: existingMediaItems,
|
||||
);
|
||||
setState(() {
|
||||
isSubmitting = false;
|
||||
|
@ -133,8 +137,10 @@ class _EditorScreenState extends State<EditorScreen> {
|
|||
),
|
||||
),
|
||||
const VerticalPadding(),
|
||||
GallerySelectorControl(entries: existingMediaItems),
|
||||
const VerticalPadding(),
|
||||
MediaUploadsControl(
|
||||
entryMediaItems: entryMediaItems,
|
||||
entryMediaItems: newMediaItems,
|
||||
),
|
||||
buildButtonBar(context, manager),
|
||||
],
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../models/gallery_data.dart';
|
||||
import '../models/image_entry.dart';
|
||||
import '../services/gallery_service.dart';
|
||||
|
||||
class ExistingImageSelectorScreen extends StatefulWidget {
|
||||
@override
|
||||
State<ExistingImageSelectorScreen> createState() =>
|
||||
_ExistingImageSelectorScreenState();
|
||||
}
|
||||
|
||||
class _ExistingImageSelectorScreenState
|
||||
extends State<ExistingImageSelectorScreen> {
|
||||
GalleryData? selectedGallery;
|
||||
final selectedImages = <ImageEntry>[];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final service = context.watch<GalleryService>();
|
||||
final title = selectedImages.isEmpty
|
||||
? 'Select Image(s)'
|
||||
: '${selectedImages.length} selected';
|
||||
final galleries = service.getGalleries();
|
||||
final List<ImageEntry> images = selectedGallery == null
|
||||
? []
|
||||
: service
|
||||
.getGalleryImageList(selectedGallery!.name)
|
||||
.getValueOrElse(() => []);
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(title),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: selectedImages.isEmpty
|
||||
? null
|
||||
: () {
|
||||
Navigator.of(context).pop(selectedImages);
|
||||
},
|
||||
tooltip: 'Attach selected files',
|
||||
icon: Icon(Icons.attach_file),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: selectedImages.isEmpty
|
||||
? null
|
||||
: () {
|
||||
setState(() {
|
||||
selectedImages.clear();
|
||||
});
|
||||
},
|
||||
tooltip: 'Clear Selection',
|
||||
icon: Icon(Icons.remove_circle_outline),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
DropdownButton<GalleryData>(
|
||||
value: selectedGallery,
|
||||
items: galleries
|
||||
.map((g) => DropdownMenuItem(value: g, child: Text(g.name)))
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
selectedGallery = value;
|
||||
});
|
||||
}),
|
||||
const VerticalDivider(),
|
||||
Expanded(child: buildGrid(context, images)),
|
||||
],
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildGrid(BuildContext context, List<ImageEntry> images) {
|
||||
const thumbnailDimension = 100.0;
|
||||
return GridView.builder(
|
||||
itemCount: images.length,
|
||||
padding: const EdgeInsets.all(5.0),
|
||||
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: thumbnailDimension),
|
||||
itemBuilder: (context, index) {
|
||||
final image = images[index];
|
||||
final imageWidget = CachedNetworkImage(
|
||||
width: thumbnailDimension,
|
||||
height: thumbnailDimension,
|
||||
imageUrl: image.thumbnailUrl,
|
||||
);
|
||||
final selected = selectedImages.contains(image);
|
||||
|
||||
final tileWidget = selected
|
||||
? Stack(
|
||||
children: [
|
||||
imageWidget,
|
||||
Positioned(
|
||||
child: Icon(
|
||||
Icons.check_circle,
|
||||
color: Colors.green,
|
||||
))
|
||||
],
|
||||
)
|
||||
: imageWidget;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(2.0),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
if (selected) {
|
||||
selectedImages.remove(image);
|
||||
} else {
|
||||
selectedImages.add(image);
|
||||
}
|
||||
});
|
||||
},
|
||||
child: tileWidget,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -8,7 +8,8 @@ import '../globals.dart';
|
|||
import '../models/TimelineIdentifiers.dart';
|
||||
import '../models/entry_tree_item.dart';
|
||||
import '../models/exec_error.dart';
|
||||
import '../models/media_attachment_uploads/entry_media_items.dart';
|
||||
import '../models/image_entry.dart';
|
||||
import '../models/media_attachment_uploads/new_entry_media_items.dart';
|
||||
import '../models/timeline_entry.dart';
|
||||
import 'auth_service.dart';
|
||||
import 'media_upload_attachment_helper.dart';
|
||||
|
@ -67,7 +68,8 @@ class EntryManagerService extends ChangeNotifier {
|
|||
String text, {
|
||||
String spoilerText = '',
|
||||
String inReplyToId = '',
|
||||
required EntryMediaItems mediaItems,
|
||||
required NewEntryMediaItems mediaItems,
|
||||
required List<ImageEntry> existingMediaItems,
|
||||
}) async {
|
||||
_logger.finest('Creating new post: $text');
|
||||
final auth = getIt<AuthService>();
|
||||
|
@ -79,7 +81,7 @@ class EntryManagerService extends ChangeNotifier {
|
|||
|
||||
final client = clientResult.value;
|
||||
|
||||
final mediaIds = <String>[];
|
||||
final mediaIds = existingMediaItems.map((m) => m.scales.first.id).toList();
|
||||
for (final item in mediaItems.attachments) {
|
||||
if (item.isExistingServerItem) {
|
||||
continue;
|
||||
|
|
|
@ -7,7 +7,8 @@ import '../models/TimelineIdentifiers.dart';
|
|||
import '../models/entry_tree_item.dart';
|
||||
import '../models/exec_error.dart';
|
||||
import '../models/group_data.dart';
|
||||
import '../models/media_attachment_uploads/entry_media_items.dart';
|
||||
import '../models/image_entry.dart';
|
||||
import '../models/media_attachment_uploads/new_entry_media_items.dart';
|
||||
import '../models/timeline.dart';
|
||||
import '../models/timeline_entry.dart';
|
||||
import 'auth_service.dart';
|
||||
|
@ -65,13 +66,15 @@ class TimelineManager extends ChangeNotifier {
|
|||
String text, {
|
||||
String spoilerText = '',
|
||||
String inReplyToId = '',
|
||||
required EntryMediaItems mediaItems,
|
||||
required NewEntryMediaItems newMediaItems,
|
||||
required List<ImageEntry> existingMediaItems,
|
||||
}) async {
|
||||
final result = await getIt<EntryManagerService>().createNewStatus(
|
||||
text,
|
||||
spoilerText: spoilerText,
|
||||
inReplyToId: inReplyToId,
|
||||
mediaItems: mediaItems,
|
||||
mediaItems: newMediaItems,
|
||||
existingMediaItems: existingMediaItems,
|
||||
);
|
||||
if (result.isSuccess) {
|
||||
_logger.finest('Notifying listeners of new status created');
|
||||
|
|
Ładowanie…
Reference in New Issue