Add ability to add existing images to post

codemagic-setup
Hank Grabowski 2022-12-26 22:00:28 -05:00
rodzic 1215363dc4
commit 292b560b41
7 zmienionych plików z 214 dodań i 15 usunięć

Wyświetl plik

@ -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,
),
),
]);
}
}

Wyświetl plik

@ -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(

Wyświetl plik

@ -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 ?? [];

Wyświetl plik

@ -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),
],

Wyświetl plik

@ -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,
),
);
});
}
}

Wyświetl plik

@ -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;

Wyświetl plik

@ -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');