♻️ Refactored attachment meta editor

This commit is contained in:
2025-04-05 14:41:11 +08:00
parent 60afc96da2
commit 9d685fa0d9
11 changed files with 457 additions and 318 deletions

View File

@@ -63,7 +63,8 @@ class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
}
} else if (_file != null) {
try {
final place = await attach.chunkedUploadInitialize(await _file!.length(), _file!.name, widget.pool, null);
final place = await attach.chunkedUploadInitialize(
await _file!.length(), _file!.name, widget.pool, null);
final attachment = await attach.chunkedUploadParts(
_file!,
@@ -109,11 +110,17 @@ class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
child: Column(
children: [
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
),
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
leading: const Icon(Symbols.add_photo_alternate),
trailing: const Icon(Symbols.chevron_right),
title: Text('addAttachmentFromAlbum').tr(),
subtitle: _file == null ? Text('unset').tr() : Text('waitingForUpload').tr(),
subtitle: _file == null
? Text('unset').tr()
: Text('waitingForUpload').tr(),
onTap: () {
_pickMedia();
},

View File

@@ -0,0 +1,311 @@
import 'dart:io';
import 'dart:ui';
import 'package:croppy/croppy.dart';
import 'package:dismissible_page/dismissible_page.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:gap/gap.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/controllers/post_write_controller.dart';
import 'package:surface/providers/sn_attachment.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/attachment.dart';
import 'package:surface/widgets/attachment/attachment_input.dart';
import 'package:surface/widgets/attachment/attachment_zoom.dart';
import 'package:surface/widgets/attachment/pending_attachment_alt.dart';
import 'package:surface/widgets/attachment/pending_attachment_boost.dart';
import 'package:surface/widgets/attachment/pending_attachment_compress.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/loading_indicator.dart';
class PendingAttachmentActionSheet extends StatefulWidget {
final PostWriteMedia media;
const PendingAttachmentActionSheet({super.key, required this.media});
@override
State<PendingAttachmentActionSheet> createState() =>
_PendingAttachmentActionSheetState();
}
class _PendingAttachmentActionSheetState
extends State<PendingAttachmentActionSheet> {
bool _isBusy = false;
Future<void> _cropImage() async {
final result = (!kIsWeb && (Platform.isIOS || Platform.isMacOS))
? await showCupertinoImageCropper(
// ignore: use_build_context_synchronously
context,
// ignore: use_build_context_synchronously
imageProvider: widget.media.getImageProvider(context)!,
)
: await showMaterialImageCropper(
// ignore: use_build_context_synchronously
context,
// ignore: use_build_context_synchronously
imageProvider: widget.media.getImageProvider(context)!,
);
if (result == null) return;
final rawBytes =
(await result.uiImage.toByteData(format: ImageByteFormat.png))!
.buffer
.asUint8List();
if (!mounted) return;
final updatedMedia = PostWriteMedia.fromBytes(
rawBytes, widget.media.name, widget.media.type);
Navigator.pop(context, updatedMedia);
}
Future<void> _setThumbnail() async {
final thumbnail = await showDialog<SnAttachment?>(
context: context,
builder: (context) => AttachmentInputDialog(
title: 'attachmentSetThumbnail'.tr(),
pool: 'interactive',
analyzeNow: true,
),
);
if (thumbnail == null) return;
if (!mounted) return;
try {
final attach = context.read<SnAttachmentProvider>();
final newAttach = await attach.updateOne(widget.media.attachment!,
thumbnailId: thumbnail.id);
if (mounted) Navigator.pop(context, newAttach);
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
}
}
Future<void> _deleteAttachment() async {
if (_isBusy) return;
if (widget.media.attachment == null) return;
try {
setState(() => _isBusy = true);
final sn = context.read<SnNetworkProvider>();
await sn.client
.delete('/cgi/uc/attachments/${widget.media.attachment!.id}');
if (!mounted) return;
Navigator.pop(context, false);
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
Future<void> _createBoost() async {
final result = await showDialog<SnAttachmentBoost?>(
context: context,
builder: (context) => PendingAttachmentBoostDialog(
media: widget.media,
),
);
if (result == null) return;
final newAttach = widget.media.attachment!
.copyWith(boosts: [...widget.media.attachment!.boosts, result]);
final newMedia = PostWriteMedia(newAttach);
if (!mounted) return;
Navigator.pop(context, newMedia);
}
Future<void> _compressVideo() async {
final result = await showDialog<PostWriteMedia?>(
context: context,
builder: (context) => PendingVideoCompressDialog(media: widget.media),
);
if (result == null) return;
if (!mounted) return;
Navigator.pop(context, result);
}
Future<void> _setAlt() async {
final result = await showDialog<SnAttachment?>(
context: context,
builder: (context) => PendingAttachmentAltDialog(media: widget.media),
);
if (result == null) return;
if (!mounted) return;
Navigator.pop(context, PostWriteMedia(result));
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(Symbols.edit, size: 24),
const Gap(16),
Text('attachmentEditor')
.tr()
.textStyle(Theme.of(context).textTheme.titleLarge!),
],
).padding(horizontal: 20, top: 16, bottom: 12),
if (widget.media.attachment == null)
Text('attachmentEditorUnUploadHint')
.tr()
.textStyle(Theme.of(context).textTheme.bodyMedium!)
.padding(horizontal: 20, bottom: 8)
.opacity(0.8)
else
Card(
margin: EdgeInsets.zero,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(widget.media.attachment!.alt),
Row(
spacing: 6,
children: [
Text(widget.media.attachment!.size.formatBytes()),
Text(
widget.media.attachment!.mimetype,
style: GoogleFonts.robotoMono(),
),
],
),
Text('attachmentEditorUploadHint')
.tr()
.textStyle(Theme.of(context).textTheme.bodyMedium!)
.opacity(0.8),
],
).padding(horizontal: 16, vertical: 8),
).padding(horizontal: 16, bottom: 8),
LoadingIndicator(isActive: _isBusy),
if (widget.media.attachment == null)
Expanded(
child: ListView(
children: [
ListTile(
minTileHeight: 48,
leading: const Icon(Symbols.upload),
contentPadding: EdgeInsets.symmetric(horizontal: 24),
title: Text('attachmentUpload').tr(),
onTap: () => Navigator.pop(context, true),
),
if (widget.media.type == SnMediaType.video)
ListTile(
minTileHeight: 48,
leading: const Icon(Symbols.compress),
title: Text('attachmentCompressVideo').tr(),
onTap: () => _compressVideo(),
),
if (widget.media.type == SnMediaType.image)
ListTile(
minTileHeight: 48,
leading: const Icon(Symbols.crop),
contentPadding: EdgeInsets.symmetric(horizontal: 24),
title: Text('crop').tr(),
onTap: () => _cropImage(),
),
ListTile(
minTileHeight: 48,
leading: const Icon(Symbols.delete),
contentPadding: EdgeInsets.symmetric(horizontal: 24),
title: Text('delete').tr(),
onTap: () => Navigator.pop(context, false),
),
],
),
)
else
Expanded(
child: ListView(
children: [
ListTile(
minTileHeight: 48,
leading: const Icon(Symbols.preview),
contentPadding: EdgeInsets.symmetric(horizontal: 24),
title: Text('preview').tr(),
onTap: () {
context.pushTransparentRoute(
AttachmentZoomView(data: [widget.media.attachment!]),
rootNavigator: true,
);
},
),
ListTile(
minTileHeight: 48,
leading: const Icon(Symbols.copy_all),
contentPadding: EdgeInsets.symmetric(horizontal: 24),
title: Text('attachmentCopyRandomId').tr(),
onTap: () {
Clipboard.setData(
ClipboardData(
text: widget.media.attachment!.rid,
),
);
Navigator.pop(context);
},
),
ListTile(
minTileHeight: 48,
leading: const Icon(Symbols.add_link),
contentPadding: EdgeInsets.symmetric(horizontal: 24),
title: Text('attachmentInsertLink').tr(),
onTap: () {
Navigator.pop(context, 'link');
},
),
ListTile(
minTileHeight: 48,
leading: const Icon(Symbols.bolt),
contentPadding: EdgeInsets.symmetric(horizontal: 24),
title: Text('attachmentBoost').tr(),
onTap: () => _createBoost(),
),
ListTile(
minTileHeight: 48,
leading: const Icon(Symbols.thumbnail_bar),
contentPadding: EdgeInsets.symmetric(horizontal: 24),
title: Text('attachmentSetThumbnail').tr(),
onTap: () => _setThumbnail(),
),
ListTile(
minTileHeight: 48,
leading: const Icon(Symbols.description),
contentPadding: EdgeInsets.symmetric(horizontal: 24),
title: Text('attachmentSetAlt').tr(),
onTap: () => _setAlt(),
),
ListTile(
minTileHeight: 48,
leading: const Icon(Symbols.link_off),
contentPadding: EdgeInsets.symmetric(horizontal: 24),
title: Text('unlink').tr(),
onTap: () => Navigator.pop(context, false),
),
ListTile(
minTileHeight: 48,
leading: const Icon(Symbols.delete),
contentPadding: EdgeInsets.symmetric(horizontal: 24),
title: Text('delete').tr(),
onTap: () => _deleteAttachment(),
),
],
),
),
],
);
}
}

View File

@@ -10,10 +10,12 @@ class PendingAttachmentAltDialog extends StatefulWidget {
const PendingAttachmentAltDialog({super.key, required this.media});
@override
State<PendingAttachmentAltDialog> createState() => _PendingAttachmentAltDialogState();
State<PendingAttachmentAltDialog> createState() =>
_PendingAttachmentAltDialogState();
}
class _PendingAttachmentAltDialogState extends State<PendingAttachmentAltDialog> {
class _PendingAttachmentAltDialogState
extends State<PendingAttachmentAltDialog> {
final _contentController = TextEditingController();
@override
@@ -63,7 +65,7 @@ class _PendingAttachmentAltDialogState extends State<PendingAttachmentAltDialog>
controller: _contentController,
decoration: InputDecoration(
labelText: 'fieldAttachmentAlt'.tr(),
border: const UnderlineInputBorder(),
border: const OutlineInputBorder(),
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),
@@ -71,9 +73,11 @@ class _PendingAttachmentAltDialogState extends State<PendingAttachmentAltDialog>
),
actions: [
TextButton(
onPressed: _isBusy ? null : () {
Navigator.pop(context);
},
onPressed: _isBusy
? null
: () {
Navigator.pop(context);
},
child: Text('dialogDismiss'.tr()),
),
TextButton(

View File

@@ -14,10 +14,12 @@ class PendingAttachmentBoostDialog extends StatefulWidget {
const PendingAttachmentBoostDialog({super.key, required this.media});
@override
State<PendingAttachmentBoostDialog> createState() => _PendingAttachmentBoostDialogState();
State<PendingAttachmentBoostDialog> createState() =>
_PendingAttachmentBoostDialogState();
}
class _PendingAttachmentBoostDialogState extends State<PendingAttachmentBoostDialog> {
class _PendingAttachmentBoostDialogState
extends State<PendingAttachmentBoostDialog> {
List<SnAttachmentDestination>? _regions;
SnAttachmentDestination? _selectedRegion;
@@ -84,17 +86,23 @@ class _PendingAttachmentBoostDialogState extends State<PendingAttachmentBoostDia
children: _regions!.map(
(ele) {
return RadioListTile(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
),
title: Text(ele.label).tr(),
subtitle: Text(
'attachmentDestinationRegion${ele.region}'.trExists()
? 'attachmentDestinationRegion${ele.region}'.tr()
? 'attachmentDestinationRegion${ele.region}'
.tr()
: ele.region,
),
selected: _selectedRegion == ele,
value: ele,
groupValue: _selectedRegion,
onChanged: (value) {
if (value != null) setState(() => _selectedRegion = value);
if (value != null) {
setState(() => _selectedRegion = value);
}
},
);
},
@@ -105,9 +113,11 @@ class _PendingAttachmentBoostDialogState extends State<PendingAttachmentBoostDia
),
actions: [
TextButton(
onPressed: _isBusy ? null : () {
Navigator.pop(context);
},
onPressed: _isBusy
? null
: () {
Navigator.pop(context);
},
child: Text('dialogDismiss'.tr()),
),
TextButton(

View File

@@ -1,3 +1,5 @@
// ignore_for_file: unused_import
import 'dart:io';
import 'dart:ui';
@@ -22,9 +24,9 @@ import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/attachment.dart';
import 'package:surface/widgets/attachment/attachment_input.dart';
import 'package:surface/widgets/attachment/attachment_zoom.dart';
import 'package:surface/widgets/attachment/pending_attachment_actions.dart';
import 'package:surface/widgets/attachment/pending_attachment_alt.dart';
import 'package:surface/widgets/attachment/pending_attachment_boost.dart';
import 'package:surface/widgets/context_menu.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/universal_image.dart';
@@ -50,232 +52,6 @@ class PostMediaPendingList extends StatelessWidget {
this.onUpdateBusy,
});
Future<void> _cropImage(BuildContext context, int idx) async {
final media = attachments[idx];
final result = (!kIsWeb && (Platform.isIOS || Platform.isMacOS))
? await showCupertinoImageCropper(
// ignore: use_build_context_synchronously
context,
// ignore: use_build_context_synchronously
imageProvider: media.getImageProvider(context)!,
)
: await showMaterialImageCropper(
// ignore: use_build_context_synchronously
context,
// ignore: use_build_context_synchronously
imageProvider: media.getImageProvider(context)!,
);
if (result == null) return;
final rawBytes =
(await result.uiImage.toByteData(format: ImageByteFormat.png))!
.buffer
.asUint8List();
if (onUpdate != null) {
final updatedMedia = PostWriteMedia.fromBytes(
rawBytes,
media.name,
media.type,
);
await onUpdate!(idx, updatedMedia);
}
}
Future<void> _setThumbnail(BuildContext context, int idx) async {
if (idx == -1) {
// Thumbnail only can set on video or audio. And thumbnail of the post must be an image, so it's not possible to set thumbnail on the post thumbnail.
return;
} else if (attachments[idx].attachment == null) {
return;
}
final thumbnail = await showDialog<SnAttachment?>(
context: context,
builder: (context) => AttachmentInputDialog(
title: 'attachmentSetThumbnail'.tr(),
pool: 'interactive',
analyzeNow: true,
),
);
if (thumbnail == null) return;
if (!context.mounted) return;
try {
final attach = context.read<SnAttachmentProvider>();
final newAttach = await attach.updateOne(
attachments[idx].attachment!,
thumbnailId: thumbnail.id,
);
onUpdate!(idx, PostWriteMedia(newAttach));
} catch (err) {
if (!context.mounted) return;
context.showErrorDialog(err);
}
}
Future<void> _deleteAttachment(BuildContext context, int idx) async {
final media = attachments[idx];
if (media.attachment == null) return;
try {
onUpdateBusy?.call(true);
final sn = context.read<SnNetworkProvider>();
await sn.client.delete('/cgi/uc/attachments/${media.attachment!.id}');
onRemove!(idx);
} catch (err) {
if (!context.mounted) return;
context.showErrorDialog(err);
} finally {
onUpdateBusy?.call(false);
}
}
Future<void> _createBoost(BuildContext context, int idx) async {
if (attachments[idx].attachment == null) return;
final result = await showDialog<SnAttachmentBoost?>(
context: context,
builder: (context) =>
PendingAttachmentBoostDialog(media: attachments[idx]),
);
if (result == null) return;
final newAttach = attachments[idx].attachment!.copyWith(
boosts: [...attachments[idx].attachment!.boosts, result],
);
final newMedia = PostWriteMedia(newAttach);
onUpdate!(idx, newMedia);
}
Future<void> _compressVideo(BuildContext context, int idx) async {
final result = await showDialog<PostWriteMedia?>(
context: context,
builder: (context) => PendingVideoCompressDialog(media: attachments[idx]),
);
if (result == null) return;
onUpdate!(idx, result);
}
Future<void> _setAlt(BuildContext context, int idx) async {
final result = await showDialog<SnAttachment?>(
context: context,
builder: (context) => PendingAttachmentAltDialog(media: attachments[idx]),
);
if (result == null) return;
onUpdate!(idx, PostWriteMedia(result));
}
ContextMenu _createContextMenu(
BuildContext context, int idx, PostWriteMedia media) {
final canCompressVideo =
!kIsWeb && (Platform.isAndroid || Platform.isIOS || Platform.isMacOS);
return ContextMenu(
entries: [
if (media.attachment == null &&
media.type == SnMediaType.video &&
canCompressVideo)
MenuItem(
label: 'attachmentCompressVideo'.tr(),
icon: Symbols.compress,
onSelected: () {
_compressVideo(context, idx);
},
),
if (media.attachment != null)
MenuItem(
label: 'attachmentSetAlt'.tr(),
icon: Symbols.description,
onSelected: () {
_setAlt(context, idx);
},
),
if (media.attachment != null)
MenuItem(
label: 'attachmentBoost'.tr(),
icon: Symbols.bolt,
onSelected: () {
_createBoost(context, idx);
},
),
if (media.attachment != null && media.type == SnMediaType.video)
MenuItem(
label: 'attachmentSetThumbnail'.tr(),
icon: Symbols.image,
onSelected: () {
_setThumbnail(context, idx);
},
),
if (media.attachment == null && onUpload != null)
MenuItem(
label: 'attachmentUpload'.tr(),
icon: Symbols.upload,
onSelected: () {
onUpload!(idx);
}),
if (media.attachment != null && onInsertLink != null)
MenuItem(
label: 'attachmentInsertLink'.tr(),
icon: Symbols.add_link,
onSelected: () {
onInsertLink!(idx);
},
),
if (media.type == SnMediaType.image && media.attachment != null)
MenuItem(
label: 'preview'.tr(),
icon: Symbols.preview,
onSelected: () {
context.pushTransparentRoute(
AttachmentZoomView(data: [media.attachment!]),
rootNavigator: true,
);
},
),
if (media.type == SnMediaType.image && media.attachment == null)
MenuItem(
label: 'crop'.tr(),
icon: Symbols.crop,
onSelected: () => _cropImage(context, idx),
),
if (media.attachment != null)
MenuItem(
label: 'attachmentCopyRandomId'.tr(),
icon: Symbols.content_copy,
onSelected: () {
Clipboard.setData(ClipboardData(text: media.attachment!.rid));
},
),
if (media.attachment != null && onRemove != null)
MenuItem(
label: 'delete'.tr(),
icon: Symbols.delete,
onSelected: isBusy ? null : () => _deleteAttachment(context, idx),
),
if (media.attachment == null && onRemove != null)
MenuItem(
label: 'delete'.tr(),
icon: Symbols.delete,
onSelected: () {
onRemove!(idx);
},
)
else if (onRemove != null)
MenuItem(
label: 'unlink'.tr(),
icon: Symbols.link_off,
onSelected: () {
onRemove!(idx);
},
),
],
);
}
@override
Widget build(BuildContext context) {
return Container(
@@ -287,9 +63,27 @@ class PostMediaPendingList extends StatelessWidget {
itemCount: attachments.length,
itemBuilder: (context, idx) {
final media = attachments[idx];
return ContextMenuArea(
contextMenu: _createContextMenu(context, idx, media),
return GestureDetector(
child: _PostMediaPendingItem(media: media),
onTap: () {
showModalBottomSheet(
context: context,
builder: (context) => PendingAttachmentActionSheet(
media: media,
),
).then((value) async {
if (value is PostWriteMedia) {
await onUpdate!(idx, value);
}
if (value == 'link') {
onInsertLink!(idx);
} else if (value == false) {
onRemove!(idx);
} else if (value == true) {
onUpload!(idx);
}
});
},
);
},
),
@@ -300,9 +94,7 @@ class PostMediaPendingList extends StatelessWidget {
class _PostMediaPendingItem extends StatelessWidget {
final PostWriteMedia media;
const _PostMediaPendingItem({
required this.media,
});
const _PostMediaPendingItem({required this.media});
@override
Widget build(BuildContext context) {
@@ -321,34 +113,36 @@ class _PostMediaPendingItem extends StatelessWidget {
AspectRatio(
aspectRatio: 1,
child: switch (media.type) {
SnMediaType.image =>
LayoutBuilder(builder: (context, constraints) {
return Image(
image: media.getImageProvider(
context,
width:
(constraints.maxWidth * devicePixelRatio).round(),
height:
(constraints.maxHeight * devicePixelRatio).round(),
)!,
fit: BoxFit.contain,
);
}),
SnMediaType.image => LayoutBuilder(
builder: (context, constraints) {
return Image(
image: media.getImageProvider(
context,
width:
(constraints.maxWidth * devicePixelRatio).round(),
height: (constraints.maxHeight * devicePixelRatio)
.round(),
)!,
fit: BoxFit.contain,
);
},
),
SnMediaType.video => Stack(
fit: StackFit.expand,
children: [
if (media.attachment?.thumbnail != null)
AutoResizeUniversalImage(sn.getAttachmentUrl(
media.attachment!.thumbnail!.rid)),
const Icon(Symbols.videocam,
color: Colors.white,
shadows: [
Shadow(
const Icon(
Symbols.videocam,
color: Colors.white,
shadows: [
Shadow(
offset: Offset(1, 1),
blurRadius: 8.0,
color: Color.fromARGB(255, 0, 0, 0),
),
]),
color: Color.fromARGB(255, 0, 0, 0))
],
),
],
),
SnMediaType.audio => Stack(
@@ -357,15 +151,16 @@ class _PostMediaPendingItem extends StatelessWidget {
if (media.attachment?.thumbnail != null)
AutoResizeUniversalImage(sn.getAttachmentUrl(
media.attachment!.thumbnail!.rid)),
const Icon(Symbols.audio_file,
color: Colors.white,
shadows: [
Shadow(
const Icon(
Symbols.audio_file,
color: Colors.white,
shadows: [
Shadow(
offset: Offset(1, 1),
blurRadius: 8.0,
color: Color.fromARGB(255, 0, 0, 0),
),
]),
color: Color.fromARGB(255, 0, 0, 0))
],
),
],
),
_ => Container(
@@ -387,11 +182,8 @@ class _PostMediaPendingItem extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (media.attachment != null)
Text(
media.attachment!.alt,
maxLines: 1,
overflow: TextOverflow.ellipsis,
)
Text(media.attachment!.alt,
maxLines: 1, overflow: TextOverflow.ellipsis)
else if (media.file != null)
Text(media.file!.name,
maxLines: 1, overflow: TextOverflow.ellipsis)
@@ -468,11 +260,8 @@ class AddPostMediaButton extends StatelessWidget {
final VisualDensity? visualDensity;
final Function(Iterable<PostWriteMedia>) onAdd;
const AddPostMediaButton({
super.key,
required this.onAdd,
this.visualDensity,
});
const AddPostMediaButton(
{super.key, required this.onAdd, this.visualDensity});
void _takeMedia(bool isVideo) async {
final picker = ImagePicker();
@@ -487,17 +276,13 @@ class AddPostMediaButton extends StatelessWidget {
final picker = ImagePicker();
final result = await picker.pickMultipleMedia();
if (result.isEmpty) return;
onAdd(
result.map((e) => PostWriteMedia.fromFile(e)),
);
onAdd(result.map((e) => PostWriteMedia.fromFile(e)));
}
void _selectFile() async {
final result = await FilePicker.platform.pickFiles(type: FileType.any);
if (result == null) return;
onAdd(
result.files.map((e) => PostWriteMedia.fromFile(e.xFile)),
);
onAdd(result.files.map((e) => PostWriteMedia.fromFile(e.xFile)));
}
void _pasteMedia() async {
@@ -505,10 +290,7 @@ class AddPostMediaButton extends StatelessWidget {
if (imageBytes == null) return;
onAdd([
PostWriteMedia.fromBytes(
imageBytes,
'attachmentPastedImage'.tr(),
SnMediaType.image,
),
imageBytes, 'attachmentPastedImage'.tr(), SnMediaType.image)
]);
}
@@ -556,19 +338,15 @@ class AddPostMediaButton extends StatelessWidget {
final attach = context.read<SnAttachmentProvider>();
final attachment = await attach.getOne(randomId);
onAdd([
PostWriteMedia(attachment),
]);
onAdd([PostWriteMedia(attachment)]);
}
@override
Widget build(BuildContext context) {
return PopupMenuButton(
style: ButtonStyle(visualDensity: visualDensity),
icon: Icon(
Symbols.add_photo_alternate,
color: Theme.of(context).colorScheme.primary,
),
icon: Icon(Symbols.add_photo_alternate,
color: Theme.of(context).colorScheme.primary),
itemBuilder: (context) => [
if (!kIsWeb &&
!Platform.isLinux &&
@@ -595,7 +373,7 @@ class AddPostMediaButton extends StatelessWidget {
children: [
const Icon(Symbols.videocam),
const Gap(16),
Text('addAttachmentFromCameraVideo').tr(),
Text('addAttachmentFromCameraVideo').tr()
],
),
onTap: () {
@@ -607,7 +385,7 @@ class AddPostMediaButton extends StatelessWidget {
children: [
const Icon(Symbols.photo_library),
const Gap(16),
Text('addAttachmentFromAlbum').tr(),
Text('addAttachmentFromAlbum').tr()
],
),
onTap: () {
@@ -619,7 +397,7 @@ class AddPostMediaButton extends StatelessWidget {
children: [
const Icon(Symbols.file_upload),
const Gap(16),
Text('addAttachmentFromFiles').tr(),
Text('addAttachmentFromFiles').tr()
],
),
onTap: () {
@@ -627,13 +405,11 @@ class AddPostMediaButton extends StatelessWidget {
},
),
PopupMenuItem(
child: Row(
children: [
const Icon(Symbols.link),
const Gap(16),
Text('addAttachmentFromRandomId').tr(),
],
),
child: Row(children: [
const Icon(Symbols.link),
const Gap(16),
Text('addAttachmentFromRandomId').tr()
]),
onTap: () {
_linkRandomId(context);
},
@@ -643,7 +419,7 @@ class AddPostMediaButton extends StatelessWidget {
children: [
const Icon(Symbols.content_paste),
const Gap(16),
Text('addAttachmentFromClipboard').tr(),
Text('addAttachmentFromClipboard').tr()
],
),
onTap: () {