import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:image_gallery_saver/image_gallery_saver.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import '../controls/audio_video/av_control.dart'; import '../controls/login_aware_cached_network_image.dart'; import '../friendica_client/friendica_client.dart'; import '../globals.dart'; import '../models/attachment_media_type_enum.dart'; import '../models/media_attachment.dart'; import '../services/auth_service.dart'; import '../utils/snackbar_builder.dart'; class MediaViewerScreen extends StatefulWidget { final List attachments; final int initialIndex; const MediaViewerScreen({ super.key, required this.attachments, this.initialIndex = 0, }); @override State createState() => _MediaViewerScreenState(); } class _MediaViewerScreenState extends State { var currentIndex = 0; @override void initState() { super.initState(); currentIndex = widget.initialIndex; } void nextAttachment() { if (currentIndex >= widget.attachments.length - 1) { return; } setState(() { currentIndex++; }); } void previousAttachment() { if (currentIndex < 1) { return; } setState(() { currentIndex--; }); } Future saveImage( BuildContext context, MediaAttachment attachment, ) async { buildSnackbar(context, 'Downloading full image to save locally'); final appsDir = await getApplicationDocumentsDirectory(); final filename = p.basename(attachment.fullFileUri.path); final bytesResult = await RemoteFileClient(getIt().currentProfile) .getFileBytes(attachment.uri); if (bytesResult.isFailure && context.mounted) { buildSnackbar(context, 'Error getting full size version of file: ${bytesResult.error}'); return; } if (Platform.isAndroid || Platform.isIOS) { final saveResult = await ImageGallerySaver.saveImage(bytesResult.value); if (context.mounted) { if (saveResult['isSuccess']) { buildSnackbar(context, 'Image saved to gallery'); } else { buildSnackbar( context, 'Unable to save to gallery, check permissions'); } } } else { final location = await FilePicker.platform.saveFile( dialogTitle: 'Save Image', fileName: filename, initialDirectory: appsDir.path, ); if (location != null) { await File(location).writeAsBytes(bytesResult.value); } } } @override Widget build(BuildContext context) { final currentAttachment = widget.attachments[currentIndex]; final width = MediaQuery.of(context).size.width; final height = MediaQuery.of(context).size.height * 1.0; final mediaHeight = height; late final bool canSave; late final Widget mediaViewer; switch (currentAttachment.explicitType) { case AttachmentMediaType.image: canSave = true; mediaViewer = InteractiveViewer( maxScale: 10.0, scaleFactor: 400, child: LoginAwareCachedNetworkImage( imageUrl: currentAttachment.mainUri.toString()), ); break; case AttachmentMediaType.unknown: case AttachmentMediaType.video: canSave = false; if (Platform.isLinux) { mediaViewer = Text( 'No media viewer for ${currentAttachment.explicitType.name} type for link ${currentAttachment.fullFileUri}'); } else { mediaViewer = SizedBox( width: width, height: mediaHeight, child: AVControl( videoUrl: currentAttachment.fullFileUri.toString(), description: currentAttachment.description, ), ); } break; } return Scaffold( appBar: AppBar( actions: [ if (canSave) IconButton( onPressed: () => saveImage(context, currentAttachment), icon: const Icon(Icons.download)) ], ), body: SafeArea( child: Stack( children: [ SizedBox( height: height, width: width, child: mediaViewer, ), if (widget.attachments.length > 1) ...[ Positioned( bottom: mediaHeight * 0.5, child: Opacity( opacity: 0.8, child: currentIndex < 1 ? null : Container( color: Colors.black, child: IconButton( color: Colors.white, onPressed: previousAttachment, icon: const Icon(Icons.chevron_left), ), ), ), ), Positioned( bottom: mediaHeight * 0.5, right: 0.0, child: Opacity( opacity: 0.8, child: currentIndex >= widget.attachments.length - 1 ? null : Container( color: Colors.black, child: IconButton( color: Colors.white, onPressed: nextAttachment, icon: const Icon(Icons.chevron_right), ), ), ), ), if (currentAttachment.description.isNotEmpty) Positioned( bottom: 5.0, left: 5.0, child: ElevatedButton( onPressed: () async => await showConfirmDialog( context, currentAttachment.description, ), style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context) .scaffoldBackgroundColor .withOpacity(0.7)), child: const Text('ALT'), ), ), ] ], ), ), ); } }