import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:momo/models/image_list_resp.dart'; import 'package:momo/models/image_resp.dart'; import 'package:momo/provider/rerender.dart'; import 'package:momo/request/http_client.dart'; import 'package:url_launcher/url_launcher.dart'; class Gallery extends ConsumerWidget { const Gallery({Key? key}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { UniqueKey key = ref.watch(uniqueIdProvider); return Container( color: Colors.white, child: ImageGrid( key: key, ), ); } } class ImageGrid extends StatefulWidget { const ImageGrid({Key? key}) : super(key: key); @override State createState() => _ImageGridState(); } class _ImageGridState extends State { int pageSize = 10; final PagingController _pagingController = PagingController(firstPageKey: 1); Future _fetchPage(int pageKey) async { try { final resp = await dio.get("/image/history", queryParameters: {"page": pageKey, "size": pageSize}); ImageListResp imageListResp = ImageListResp.fromJson(resp.data); final newItems = imageListResp.list; final isLastPage = !imageListResp.hasNext; if (isLastPage) { _pagingController.appendLastPage(newItems); } else { final nextPageKey = pageKey + 1; _pagingController.appendPage(newItems, nextPageKey); } } catch (error) { _pagingController.error = error; } } @override void initState() { // TODO: implement initState _pagingController.addPageRequestListener((pageKey) { _fetchPage(pageKey); }); super.initState(); } @override Widget build(BuildContext context) { return RefreshIndicator( child: PagedGridView( pagingController: _pagingController, builderDelegate: PagedChildBuilderDelegate( itemBuilder: (context, item, index) => ImageItem(image: item)), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: MediaQuery.of(context).size.width > 640 ? 5 : 3), ), onRefresh: () => Future.sync(() => _pagingController.refresh())); } } class ImageItem extends ConsumerStatefulWidget { const ImageItem({Key? key, required this.image}) : super(key: key); final ImageResp image; @override ConsumerState createState() => _ImageItemState(); } class _ImageItemState extends ConsumerState { Future _launchInBrowser(Uri url) async { if (!await launchUrl( url, mode: LaunchMode.externalApplication, )) { throw Exception('Could not launch $url'); } } @override Widget build(BuildContext context) { return CupertinoContextMenu.builder( enableHapticFeedback: true, actions: [ CupertinoContextMenuAction( trailingIcon: CupertinoIcons.delete, onPressed: () { Navigator.of(context, rootNavigator: true).pop(); Clipboard.setData(ClipboardData( text: "https://raichi.hodokencho.com/api/image/${widget.image.file_path}")); ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Icon( Icons.check_circle, color: Colors.white, ), SizedBox( width: 10, ), Text("已拷贝到剪贴板") ], ), backgroundColor: Colors.lightGreen, )); }, child: const Text('复制链接'), ), CupertinoContextMenuAction( trailingIcon: CupertinoIcons.search, onPressed: () { Navigator.of(context, rootNavigator: true).pop(); final imageUrl = "https://raichi.hodokencho.com/api/image/${widget.image.file_path}"; final launchUrl = "https://saucenao.com/search.php?url=${Uri.encodeComponent(imageUrl)}"; _launchInBrowser(Uri.parse(launchUrl)); }, child: const Text('在SauceNAO中搜索'), ), ], builder: (BuildContext context, Animation animation) { return GestureDetector( onTap: () { Navigator.of( context, ).maybePop(); Future.delayed(const Duration(milliseconds: 120)).then((value) { context.go(Uri( path: "/detail", queryParameters: {"id": "${widget.image.id}"}).toString()); }); }, child: Image.network( kIsWeb ? "/images-api/?url=${dio.options.baseUrl}/image/${widget.image.file_path}&w=512" : 'https://raichi.hodokencho.com/images-api/?url=${dio.options.baseUrl}/image/${widget.image.file_path}&w=512', fit: BoxFit.cover, errorBuilder: (BuildContext context, o, err) { return const Icon(Icons.error_outline); }, ), ); }, ); } }