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'; /// A builder that includes an Offset to draw the context menu at. typedef ContextMenuBuilder = Widget Function( BuildContext context, Offset offset); 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 { late ScrollController _scrollController; List imageList = []; int pageNum = 1; int pageSize = 10; bool hasMore = true; bool loading = false; 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; } } Future loadImages() async { loading = true; if (!hasMore) { loading = false; return; } print(pageNum); var resp = await dio.get("/image/history", queryParameters: {"page": pageNum, "size": pageSize}); ImageListResp imageListResp = ImageListResp.fromJson(resp.data); hasMore = imageListResp.hasNext; pageNum = pageNum + 1; if (mounted) { setState(() { imageList.addAll(imageListResp.list); }); loading = false; } } @override void initState() { // TODO: implement initState _pagingController.addPageRequestListener((pageKey) { _fetchPage(pageKey); }); super.initState(); // print("init"); // // _scrollController = ScrollController(initialScrollOffset: 5.0) // ..addListener(() { // if (_scrollController.offset >= // _scrollController.position.maxScrollExtent && // !_scrollController.position.outOfRange) { // print("到底了 controller $loading"); // if (!loading) { // loadImages(); // } // } // }); // loadImages(); } @override Widget build(BuildContext context) { return PagedGridView( pagingController: _pagingController, builderDelegate: PagedChildBuilderDelegate( itemBuilder: (context, item, index) => ImageItem(image: item)), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: MediaQuery.of(context).size.width > 640 ? 5 : 3), ); // return GridView.builder( // itemCount: imageList.length, // controller: _scrollController, // gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( // crossAxisCount: MediaQuery.of(context).size.width > 640 ? 5 : 3), // itemBuilder: (BuildContext context, int index) { // return ImageItem(image: imageList[index]); // }); } } 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 { final ContextMenuController _contextMenuController = ContextMenuController(); @override void dispose() { // TODO: implement dispose if (_contextMenuController.isShown) { _contextMenuController.remove(); } super.dispose(); } void _showContextMenu(Offset position) { _contextMenuController.show( context: context, contextMenuBuilder: (BuildContext context) { return AdaptiveTextSelectionToolbar.buttonItems( anchors: TextSelectionToolbarAnchors( primaryAnchor: position, ), buttonItems: [ ContextMenuButtonItem( onPressed: () { ContextMenuController.removeAny(); showDialog( context: context, builder: (BuildContext context) => AlertDialog( title: const Text('删除图片'), content: const Text("确认删除图片"), actions: [ TextButton( onPressed: () => Navigator.pop(context, 'Cancel'), child: const Text('Cancel'), ), TextButton( onPressed: () { Navigator.pop(context, 'OK'); dio .delete("/image/delete/${widget.image.id}") .then((resp) { ref .read(uniqueIdProvider.notifier) .updateId(); // context.go("/"); }); }, child: const Text('OK'), ), ], )); // _showDialog(context); }, label: '删除', ), ContextMenuButtonItem( onPressed: () { ContextMenuController.removeAny(); // _showDialog(context); Clipboard.setData(ClipboardData( text: "${dio.options.baseUrl}/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, )); }, label: '复制链接', ), ContextMenuButtonItem( onPressed: () { ContextMenuController.removeAny(); // _showDialog(context); }, label: '查看详情', ), ], ); }, ); } @override Widget build(BuildContext context) { return CupertinoContextMenu( actions: [ CupertinoContextMenuAction( trailingIcon: CupertinoIcons.delete, child: const Text('删除'), onPressed: () { Navigator.pop(context); // Navigator.pop(context); // showDialog( // context: context, // builder: (BuildContext dialogContext) => AlertDialog( // title: const Text('删除图片'), // content: const Text("确认删除图片"), // actions: [ // TextButton( // onPressed: () => // Navigator.pop(dialogContext, 'Cancel'), // child: const Text('Cancel'), // ), // TextButton( // onPressed: () { // Navigator.pop(dialogContext, 'OK'); // // Navigator.pop(context); // dio // .delete("/image/delete/${widget.image.id}") // .then((resp) { // ref.read(uniqueIdProvider.notifier).updateId(); // // context.go("/"); // }); // }, // child: const Text('OK'), // ), // ], // )); }, ) ], child: GestureDetector( onSecondaryTapUp: (TapUpDetails details) { _showContextMenu(details.globalPosition); }, onTap: () { 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); }, ), )); } }