import 'dart:io'; import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/services.dart'; import 'package:photo_view/photo_view_gallery.dart'; import 'package:pixes/components/md.dart'; import 'package:pixes/components/page_route.dart'; import 'package:pixes/foundation/app.dart'; import 'package:pixes/foundation/cache_manager.dart'; import 'package:pixes/foundation/image_provider.dart'; import 'package:pixes/pages/main_page.dart'; import 'package:pixes/utils/io.dart'; import 'package:pixes/utils/translation.dart'; import 'package:share_plus/share_plus.dart'; import 'package:window_manager/window_manager.dart'; class ImagePage extends StatefulWidget { const ImagePage(this.urls, {this.initialPage = 1, super.key}); final List urls; final int initialPage; static show(List urls, {int initialPage = 1}) { App.rootNavigatorKey.currentState ?.push(AppPageRoute( builder: (context) => ImagePage(urls, initialPage: initialPage))); } @override State createState() => _ImagePageState(); } class _ImagePageState extends State with WindowListener { int windowButtonKey = 0; @override void initState() { windowManager.addListener(this); super.initState(); } @override void dispose() { windowManager.removeListener(this); super.dispose(); } @override void onWindowMaximize() { setState(() { windowButtonKey++; }); } @override void onWindowUnmaximize() { setState(() { windowButtonKey++; }); } late var controller = PageController(initialPage: widget.initialPage); late int currentPage = widget.initialPage; var menuController = FlyoutController(); Future getFile() async { var image = widget.urls[currentPage]; if(image.startsWith("file://")){ return File(image.replaceFirst("file://", "")); } var file = await CacheManager().findCache(image); return file == null ? null : File(file); } String getExtensionName() { var fileName = widget.urls[currentPage].split('/').last; if(fileName.contains('.')){ return '.${fileName.split('.').last}'; } return '.jpg'; } void showMenu() { menuController.showFlyout(builder: (context) => MenuFlyout( items: [ MenuFlyoutItem(text: Text("Save to".tl), onPressed: () async{ var file = await getFile(); if(file != null){ var fileName = file.path.split('/').last; if(!fileName.contains('.')){ fileName += getExtensionName(); } saveFile(file, fileName); } }), MenuFlyoutItem(text: Text("Share".tl), onPressed: () async{ var file = await getFile(); if(file != null){ var ext = getExtensionName(); var fileName = file.path.split('/').last; if(!fileName.contains('.')){ fileName += ext; } var mediaType = switch(ext){ 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif', 'webp' => 'image/webp', _ => 'application/octet-stream' }; Share.shareXFiles([XFile(file.path, mimeType: mediaType, name: fileName)]); } }), ], )); } @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top), color: FluentTheme.of(context).micaBackgroundColor, child: Listener( onPointerSignal: (event) { if(event is PointerScrollEvent && !HardwareKeyboard.instance.isControlPressed) { if(event.scrollDelta.dy > 0 && controller.page!.toInt() < widget.urls.length - 1) { controller.jumpToPage(controller.page!.toInt() + 1); } else if(event.scrollDelta.dy < 0 && controller.page!.toInt() > 0){ controller.jumpToPage(controller.page!.toInt() - 1); } } }, child: LayoutBuilder( builder: (context, constrains) { var height = constrains.maxHeight; return Stack( children: [ Positioned.fill(child: PhotoViewGallery.builder( pageController: controller, backgroundDecoration: const BoxDecoration( color: Colors.transparent ), itemCount: widget.urls.length, builder: (context, index) { var image = widget.urls[index]; return PhotoViewGalleryPageOptions( imageProvider: image.startsWith("file://") ? FileImage(File(image.replaceFirst("file://", ""))) : CachedImageProvider(image) as ImageProvider, ); }, onPageChanged: (index) { setState(() { currentPage = index; }); }, )), Positioned( top: 0, left: 0, right: 0, child: SizedBox( height: 36, child: Row( children: [ const SizedBox(width: 6,), IconButton( icon: const Icon(FluentIcons.back).paddingAll(2), onPressed: () => context.pop() ), const Expanded( child: DragToMoveArea(child: SizedBox.expand(),), ), buildActions(), if(App.isDesktop) WindowButtons(key: ValueKey(windowButtonKey),), ], ), ), ), Positioned( left: 0, top: height / 2 - 9, child: IconButton( icon: const Icon(FluentIcons.chevron_left, size: 18,), onPressed: () { controller.previousPage( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); }, ).paddingAll(8), ), Positioned( right: 0, top: height / 2 - 9, child: IconButton( icon: const Icon(FluentIcons.chevron_right, size: 18), onPressed: () { controller.nextPage( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); }, ).paddingAll(8), ), Positioned( left: 12, bottom: 8, child: Text( "${currentPage + 1}/${widget.urls.length}", ), ) ], ); }, ), ), ); } Widget buildActions() { var width = MediaQuery.of(context).size.width; return FlyoutTarget( controller: menuController, child: width > 600 ? Button( onPressed: showMenu, child: const Row( children: [ Icon( MdIcons.menu, size: 18, ), SizedBox( width: 8, ), Text('Actions'), ], )) : IconButton( icon: const Icon( MdIcons.more_horiz, size: 20, ), onPressed: showMenu), ); } }