import 'package:dismissible_page/dismissible_page.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view_gallery.dart'; import 'package:provider/provider.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/user_directory.dart'; import 'package:surface/types/attachment.dart'; import 'package:surface/widgets/account/account_image.dart'; import 'package:surface/widgets/universal_image.dart'; import 'package:uuid/uuid.dart'; class AttachmentZoomView extends StatefulWidget { final Iterable data; final int? initialIndex; final List? heroTags; const AttachmentZoomView({ super.key, required this.data, this.initialIndex, this.heroTags, }); @override State createState() => _AttachmentZoomViewState(); } class _AttachmentZoomViewState extends State { late final PageController _pageController = PageController(initialPage: widget.initialIndex ?? 0); void _updatePage() { setState(() {}); } @override void initState() { super.initState(); _pageController.addListener(_updatePage); Future.delayed(const Duration(milliseconds: 100), _updatePage); } @override void dispose() { _pageController.removeListener(_updatePage); _pageController.dispose(); super.dispose(); } Color get _unFocusColor => Theme.of(context).colorScheme.onSurface.withOpacity(0.75); @override Widget build(BuildContext context) { final sn = context.read(); final uuid = Uuid(); final metaTextStyle = GoogleFonts.roboto( fontSize: 12, color: _unFocusColor, height: 1, ); return DismissiblePage( onDismissed: () { Navigator.of(context).pop(); }, direction: DismissiblePageDismissDirection.down, backgroundColor: Colors.transparent, isFullScreen: true, child: Stack( children: [ Builder(builder: (context) { if (widget.data.length == 1) { final heroTag = widget.heroTags?.first ?? uuid.v4(); return Hero( tag: 'attachment-${widget.data.first.rid}-$heroTag', child: PhotoView( key: Key( 'attachment-detail-${widget.data.first.rid}-$heroTag'), backgroundDecoration: BoxDecoration(color: Colors.transparent), imageProvider: UniversalImage.provider( sn.getAttachmentUrl(widget.data.first.rid), ), ), ); } return PhotoViewGallery.builder( pageController: _pageController, scrollPhysics: const BouncingScrollPhysics(), builder: (context, idx) { final heroTag = widget.heroTags?.elementAt(idx) ?? uuid.v4(); return PhotoViewGalleryPageOptions( imageProvider: UniversalImage.provider( sn.getAttachmentUrl(widget.data.elementAt(idx).rid), ), heroAttributes: PhotoViewHeroAttributes( tag: 'attachment-${widget.data.first.rid}-$heroTag', ), ); }, itemCount: widget.data.length, loadingBuilder: (context, event) => Center( child: SizedBox( width: 20.0, height: 20.0, child: CircularProgressIndicator( value: event == null ? 0 : event.cumulativeBytesLoaded / (event.expectedTotalBytes ?? 1), ), ), ), backgroundDecoration: BoxDecoration(color: Colors.transparent), ); }), Align( alignment: Alignment.bottomCenter, child: IgnorePointer( child: Container( height: 300, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.bottomCenter, end: Alignment.topCenter, colors: [ Theme.of(context).colorScheme.surface, Colors.transparent, ], ), ), ), ), ), Positioned( left: 16, right: 16, bottom: 16, child: Material( color: Colors.transparent, child: Builder(builder: (context) { final ud = context.read(); final item = widget.data.elementAt( widget.data.length > 1 ? _pageController.page?.round() ?? 0 : 0, ); final account = ud.getAccountFromCache(item.accountId); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (item.accountId > 0) Row( children: [ IgnorePointer( child: AccountImage( content: account!.avatar, radius: 19, ), ), const Gap(8), Expanded( child: IgnorePointer( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'attachmentUploadBy'.tr(), style: Theme.of(context).textTheme.bodySmall, ), Text( account.nick, style: Theme.of(context).textTheme.bodyMedium, ), ], ), ), ), if (widget.data.length > 1) IgnorePointer( child: Text( '${(_pageController.page?.round() ?? 0) + 1}/${widget.data.length}', style: GoogleFonts.robotoMono(fontSize: 13), ).padding(right: 8), ), ], ), const Gap(4), IgnorePointer( child: Text( item.alt, maxLines: 2, overflow: TextOverflow.ellipsis, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w500, ), ), ), const Gap(2), IgnorePointer( child: Wrap( spacing: 6, children: [ if (item.metadata['exif'] == null) Text( '#${item.rid}', style: metaTextStyle, ), if (item.metadata['exif']?['Model'] != null) Text( 'attachmentShotOn'.tr(args: [ item.metadata['exif']?['Model'], ]), style: metaTextStyle, ).padding(right: 2), if (item.metadata['exif']?['ShutterSpeed'] != null) Text( item.metadata['exif']?['ShutterSpeed'], style: metaTextStyle, ).padding(right: 2), if (item.metadata['exif']?['ISO'] != null) Text( 'ISO${item.metadata['exif']?['ISO']}', style: metaTextStyle, ).padding(right: 2), if (item.metadata['exif']?['Aperture'] != null) Text( 'f/${item.metadata['exif']?['Aperture']}', style: metaTextStyle, ).padding(right: 2), if (item.metadata['exif']?['Megapixels'] != null && item.metadata['exif']?['Model'] != null) Text( '${item.metadata['exif']?['Megapixels']}MP', style: metaTextStyle, ) else Text( '${item.size} Bytes', style: metaTextStyle, ), Text( '${item.metadata['width']}x${item.metadata['height']}', style: metaTextStyle, ), if (item.metadata['ratio'] != null) Text( (item.metadata['ratio'] as num) .toStringAsFixed(2), style: metaTextStyle, ), Text( item.mimetype, style: metaTextStyle, ), ], ), ), ], ); }), ), ), ], ), ); } }