gallery_photo_view.dart 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. import 'dart:typed_data';
  2. import 'package:cached_network_image/cached_network_image.dart';
  3. import 'package:dio/dio.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:image_gallery_saver/image_gallery_saver.dart';
  6. import 'package:permission_handler/permission_handler.dart';
  7. import 'package:photo_view/photo_view.dart';
  8. import 'package:photo_view/photo_view_gallery.dart';
  9. import 'package:sport/application.dart';
  10. import 'package:sport/bean/image.dart' as photo;
  11. import 'package:sport/utils/toast.dart';
  12. import 'package:sport/widgets/dialog/modal_bottom_action.dart';
  13. import 'package:sport/widgets/dialog/request_dialog.dart';
  14. import 'package:sport/widgets/loading.dart';
  15. void open(BuildContext context, final int index, List<photo.Image> images) {
  16. Navigator.push(
  17. context,
  18. FadeRoute(
  19. page: GalleryPhotoViewWrapper(
  20. galleryItems: images,
  21. backgroundDecoration: const BoxDecoration(
  22. color: Colors.black,
  23. ),
  24. initialIndex: index,
  25. scrollDirection: Axis.horizontal,
  26. loadingBuilder: (_, __) => RequestLoadingWidget(),
  27. ),
  28. ),
  29. );
  30. }
  31. class FadeRoute extends PageRouteBuilder {
  32. final Widget page;
  33. FadeRoute({this.page})
  34. : super(
  35. pageBuilder: (
  36. BuildContext context,
  37. Animation<double> animation,
  38. Animation<double> secondaryAnimation,
  39. ) =>
  40. page,
  41. transitionsBuilder: (
  42. BuildContext context,
  43. Animation<double> animation,
  44. Animation<double> secondaryAnimation,
  45. Widget child,
  46. ) =>
  47. FadeTransition(
  48. opacity: animation,
  49. child: child,
  50. ),
  51. );
  52. }
  53. class GalleryPhotoViewWrapper extends StatefulWidget {
  54. GalleryPhotoViewWrapper({
  55. this.loadingBuilder,
  56. this.backgroundDecoration,
  57. this.minScale,
  58. this.maxScale,
  59. this.initialIndex,
  60. @required this.galleryItems,
  61. this.scrollDirection = Axis.horizontal,
  62. }) : pageController = PageController(initialPage: initialIndex);
  63. final LoadingBuilder loadingBuilder;
  64. final Decoration backgroundDecoration;
  65. final dynamic minScale;
  66. final dynamic maxScale;
  67. final int initialIndex;
  68. final PageController pageController;
  69. final List<photo.Image> galleryItems;
  70. final Axis scrollDirection;
  71. @override
  72. State<StatefulWidget> createState() {
  73. return _GalleryPhotoViewWrapperState();
  74. }
  75. }
  76. class _GalleryPhotoViewWrapperState extends State<GalleryPhotoViewWrapper> {
  77. int currentIndex;
  78. @override
  79. void initState() {
  80. currentIndex = widget.initialIndex;
  81. super.initState();
  82. }
  83. void onPageChanged(int index) {
  84. setState(() {
  85. currentIndex = index;
  86. });
  87. }
  88. @override
  89. Widget build(BuildContext context) {
  90. return Scaffold(
  91. body: Stack(
  92. children: <Widget>[
  93. Container(
  94. decoration: widget.backgroundDecoration,
  95. constraints: BoxConstraints.expand(
  96. height: MediaQuery.of(context).size.height,
  97. ),
  98. child: GestureDetector(
  99. onLongPress: () async {
  100. await showActionDialog(context, {
  101. "保存至本地": () async {
  102. await request(context, () async {
  103. var file = await save(widget.galleryItems[currentIndex].src);
  104. if (file != null) {
  105. ToastUtil.show("已保存图片至:$file");
  106. }
  107. });
  108. }
  109. });
  110. },
  111. child: PhotoViewGallery.builder(
  112. scrollPhysics: const BouncingScrollPhysics(),
  113. builder: _buildItem,
  114. itemCount: widget.galleryItems.length,
  115. loadingBuilder: widget.loadingBuilder,
  116. backgroundDecoration: widget.backgroundDecoration,
  117. pageController: widget.pageController,
  118. onPageChanged: onPageChanged,
  119. scrollDirection: widget.scrollDirection,
  120. ),
  121. ),
  122. ),
  123. Positioned(
  124. child: SafeArea(
  125. child: IconButton(
  126. icon: Image.asset("lib/assets/img/topbar_return_white.png"),
  127. onPressed: () {
  128. Navigator.maybePop(context);
  129. },
  130. ),
  131. ))
  132. ],
  133. ),
  134. );
  135. }
  136. Future<String> save(String url) async {
  137. var permission = await Application.requestPermission(Permission.storage);
  138. if (!permission) {
  139. ToastUtil.show("没有相应权限!");
  140. return null;
  141. }
  142. var response = await Dio().get(url, options: Options(responseType: ResponseType.bytes));
  143. final result = await ImageGallerySaver.saveImage(Uint8List.fromList(response.data), quality: 100, name: "shoes_${DateTime.now().millisecondsSinceEpoch}");
  144. print(result);
  145. return result;
  146. }
  147. PhotoViewGalleryPageOptions _buildItem(BuildContext context, int index) {
  148. final photo.Image item = widget.galleryItems[index];
  149. return PhotoViewGalleryPageOptions(
  150. imageProvider: CachedNetworkImageProvider(item.src),
  151. initialScale: PhotoViewComputedScale.contained,
  152. minScale: PhotoViewComputedScale.contained * (0.5 + index / 10),
  153. heroAttributes: PhotoViewHeroAttributes(tag: item.id ?? item.src),
  154. onTapUp: (
  155. _,
  156. __,
  157. ___,
  158. ) {
  159. Navigator.of(context).pop();
  160. });
  161. }
  162. }