diff --git a/example/assets/neat.gif b/example/assets/neat.gif new file mode 100644 index 00000000..438b1109 Binary files /dev/null and b/example/assets/neat.gif differ diff --git a/example/assets/peanut.gif b/example/assets/peanut.gif deleted file mode 100644 index 1ab1e82e..00000000 Binary files a/example/assets/peanut.gif and /dev/null differ diff --git a/example/lib/screens/app_bar.dart b/example/lib/screens/common/app_bar.dart similarity index 67% rename from example/lib/screens/app_bar.dart rename to example/lib/screens/common/app_bar.dart index f292083c..38f02b12 100644 --- a/example/lib/screens/app_bar.dart +++ b/example/lib/screens/common/app_bar.dart @@ -48,3 +48,38 @@ class ExampleAppBar extends StatelessWidget { ))); } } + +class ExampleAppBarLayout extends StatelessWidget { + const ExampleAppBarLayout({ + Key key, + @required this.title, + this.showGoBack, + this.child, + }) : super(key: key); + + final String title; + final bool showGoBack; + final Widget child; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color.fromARGB(255, 255, 255, 255), + body: Container( + height: MediaQuery.of(context).size.height, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ExampleAppBar( + title: title, + showGoBack: showGoBack, + ), + Expanded( + child: child, + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/screens/common/common_example_wrapper.dart b/example/lib/screens/common/common_example_wrapper.dart new file mode 100644 index 00000000..5a06e6af --- /dev/null +++ b/example/lib/screens/common/common_example_wrapper.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:photo_view/photo_view.dart'; + +class CommonExampleRouteWrapper extends StatelessWidget { + const CommonExampleRouteWrapper({ + this.imageProvider, + this.loadingBuilder, + this.backgroundDecoration, + this.minScale, + this.maxScale, + this.initialScale, + this.basePosition = Alignment.center, + this.filterQuality = FilterQuality.none, + }); + + final ImageProvider imageProvider; + final LoadingBuilder loadingBuilder; + final Decoration backgroundDecoration; + final dynamic minScale; + final dynamic maxScale; + final dynamic initialScale; + final Alignment basePosition; + final FilterQuality filterQuality; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + constraints: BoxConstraints.expand( + height: MediaQuery.of(context).size.height, + ), + child: PhotoView( + imageProvider: imageProvider, + loadingBuilder: loadingBuilder, + backgroundDecoration: backgroundDecoration, + minScale: minScale, + maxScale: maxScale, + initialScale: initialScale, + basePosition: basePosition, + filterQuality: filterQuality, + ), + ), + ); + } +} diff --git a/example/lib/screens/common/example_button.dart b/example/lib/screens/common/example_button.dart new file mode 100644 index 00000000..01dd911d --- /dev/null +++ b/example/lib/screens/common/example_button.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; + +class ExampleButtonNode extends StatelessWidget { + const ExampleButtonNode({ + this.title, + this.onPressed, + }); + + final String title; + final Function onPressed; + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric( + vertical: 20.0, + ), + child: Column( + children: [ + Text( + title, + textAlign: TextAlign.center, + style: const TextStyle( + color: Colors.black, + fontSize: 21.0, + fontWeight: FontWeight.w600, + ), + ), + Container( + margin: const EdgeInsets.only( + top: 10.0, + ), + child: RaisedButton( + onPressed: onPressed, + child: const Text("Open example"), + color: Colors.amber, + ), + ) + ], + ), + ); + } +} diff --git a/example/lib/screens/examples/common_use_cases_examples.dart b/example/lib/screens/examples/common_use_cases_examples.dart new file mode 100644 index 00000000..d3d92c65 --- /dev/null +++ b/example/lib/screens/examples/common_use_cases_examples.dart @@ -0,0 +1,165 @@ +import 'package:flutter/material.dart'; +import 'package:photo_view/photo_view.dart'; +import 'package:photo_view_example/screens/common/app_bar.dart'; +import 'package:photo_view_example/screens/common/common_example_wrapper.dart'; +import 'package:photo_view_example/screens/common/example_button.dart'; + +class CommonUseCasesExamples extends StatelessWidget { + @override + Widget build(BuildContext context) { + return ExampleAppBarLayout( + title: "Common use cases", + showGoBack: true, + child: ListView( + children: [ + ExampleButtonNode( + title: "Large Image", + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const CommonExampleRouteWrapper( + imageProvider: const AssetImage("assets/large-image.jpg"), + ), + ), + ); + }, + ), + ExampleButtonNode( + title: "Large Image (filter quality: medium)", + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const CommonExampleRouteWrapper( + imageProvider: const AssetImage("assets/large-image.jpg"), + filterQuality: FilterQuality.medium, + ), + ), + ); + }, + ), + ExampleButtonNode( + title: "Small Image (custom background)", + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const CommonExampleRouteWrapper( + imageProvider: const AssetImage("assets/small-image.jpg"), + backgroundDecoration: BoxDecoration( + gradient: LinearGradient( + colors: [Colors.white, Colors.grey], + stops: [0.1, 1.0], + ), + ), + ), + ), + ); + }, + ), + ExampleButtonNode( + title: "Small Image (custom alignment)", + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const CommonExampleRouteWrapper( + imageProvider: const AssetImage("assets/small-image.jpg"), + backgroundDecoration: BoxDecoration( + color: Colors.white, + ), + basePosition: Alignment(0.5, 0.0), + ), + ), + ); + }, + ), + ExampleButtonNode( + title: "Animated GIF", + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const CommonExampleRouteWrapper( + imageProvider: const AssetImage("assets/neat.gif"), + ), + ), + ); + }, + ), + ExampleButtonNode( + title: "Limited scale", + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => CommonExampleRouteWrapper( + imageProvider: const AssetImage("assets/large-image.jpg"), + minScale: PhotoViewComputedScale.contained * 0.8, + maxScale: PhotoViewComputedScale.covered * 1.1, + initialScale: PhotoViewComputedScale.covered * 1.1, + ), + ), + ); + }, + ), + ExampleButtonNode( + title: "Custom Initial scale", + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => CommonExampleRouteWrapper( + imageProvider: const AssetImage("assets/large-image.jpg"), + initialScale: PhotoViewComputedScale.contained * 0.7, + ), + ), + ); + }, + ), + ExampleButtonNode( + title: "One tap to dismiss", + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const OneTapWrapper( + imageProvider: const AssetImage("assets/large-image.jpg"), + ), + ), + ); + }, + ), + ], + ), + ); + } +} + +class OneTapWrapper extends StatelessWidget { + const OneTapWrapper({ + this.imageProvider, + }); + + final ImageProvider imageProvider; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + constraints: BoxConstraints.expand( + height: MediaQuery.of(context).size.height, + ), + child: GestureDetector( + onTapDown: (_) { + Navigator.pop(context); + }, + child: PhotoView( + imageProvider: imageProvider, + ), + ), + ), + ); + } +} diff --git a/example/lib/screens/examples/controller_example.dart b/example/lib/screens/examples/controller_example.dart index 2c45b4a8..fe6221c8 100644 --- a/example/lib/screens/examples/controller_example.dart +++ b/example/lib/screens/examples/controller_example.dart @@ -2,7 +2,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:photo_view/photo_view.dart'; -import 'package:photo_view_example/screens/app_bar.dart'; +import 'package:photo_view_example/screens/common/app_bar.dart'; class ControllerExample extends StatefulWidget { @override @@ -52,51 +52,37 @@ class _ControllerExampleState extends State { @override Widget build(BuildContext context) { - return Scaffold( - body: Container( - height: MediaQuery.of(context).size.height, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, + return ExampleAppBarLayout( + title: "Controller", + showGoBack: true, + child: ClipRect( + child: Stack( children: [ - const ExampleAppBar( - title: "Controller Examples", - showGoBack: true, + Positioned.fill( + child: PhotoView( + imageProvider: const AssetImage("assets/large-image.jpg"), + controller: controller, + scaleStateController: scaleStateController, + enableRotation: true, + initialScale: minScale, + minScale: minScale, + maxScale: maxScale, + ), ), - Flexible( - flex: 1, - child: ClipRect( - child: Stack( - children: [ - Positioned.fill( - child: PhotoView( - imageProvider: - const AssetImage("assets/large-image.jpg"), - controller: controller, - scaleStateController: scaleStateController, - enableRotation: true, - initialScale: minScale, - minScale: minScale, - maxScale: maxScale, - ), - ), - Positioned( - bottom: 0, - height: 290, - left: 0, - right: 0, - child: Container( - padding: const EdgeInsets.all(30.0), - child: StreamBuilder( - stream: controller.outputStateStream, - initialData: controller.value, - builder: _streamBuild, - ), - ), - ) - ], + Positioned( + bottom: 0, + height: 290, + left: 0, + right: 0, + child: Container( + padding: const EdgeInsets.all(30.0), + child: StreamBuilder( + stream: controller.outputStateStream, + initialData: controller.value, + builder: _streamBuild, ), ), - ), + ) ], ), ), diff --git a/example/lib/screens/examples/custom_child_examples.dart b/example/lib/screens/examples/custom_child_examples.dart index c2df0e1f..cc4062db 100644 --- a/example/lib/screens/examples/custom_child_examples.dart +++ b/example/lib/screens/examples/custom_child_examples.dart @@ -1,61 +1,52 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:photo_view/photo_view.dart'; -import 'package:photo_view_example/screens/app_bar.dart'; +import 'package:photo_view_example/screens/common/app_bar.dart'; class CustomChildExample extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - body: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, + return ExampleAppBarLayout( + title: "Custom child", + showGoBack: true, + child: Column( children: [ - const ExampleAppBar( - title: "Custom child Example", - showGoBack: true, + Container( + padding: const EdgeInsets.all(20.0), + child: const Text( + "Example of usage with something different than a image", + style: const TextStyle(fontSize: 18.0), + ), ), - Expanded( - child: Column( - children: [ - Container( - padding: const EdgeInsets.all(20.0), - child: const Text( - "Example of usage with something different than a image", - style: const TextStyle(fontSize: 18.0), - ), - ), - Container( - margin: const EdgeInsets.symmetric( - vertical: 20.0, - horizontal: 20.0, - ), - height: 450.0, - child: ClipRect( - child: PhotoView.customChild( - child: Container( - decoration: - const BoxDecoration(color: Colors.lightGreenAccent), - padding: const EdgeInsets.all(10.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - "Hello there, this is a text, that is a svg:", - style: const TextStyle(fontSize: 12.0), - textAlign: TextAlign.center, - ), - SvgPicture.asset( - "assets/firefox.svg", - height: 250.0, - ) - ], - ), + Container( + margin: const EdgeInsets.symmetric( + vertical: 20.0, + horizontal: 20.0, + ), + height: 450.0, + child: ClipRect( + child: PhotoView.customChild( + child: Container( + decoration: + const BoxDecoration(color: Colors.lightGreenAccent), + padding: const EdgeInsets.all(10.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + "Hello there, this is a text, that is a svg:", + style: const TextStyle(fontSize: 12.0), + textAlign: TextAlign.center, ), - initialScale: 1.0, - ), + SvgPicture.asset( + "assets/firefox.svg", + height: 250.0, + ) + ], ), ), - ], + initialScale: 1.0, + ), ), ), ], diff --git a/example/lib/screens/examples/dialog_example.dart b/example/lib/screens/examples/dialog_example.dart index d72b1043..4f96fde3 100644 --- a/example/lib/screens/examples/dialog_example.dart +++ b/example/lib/screens/examples/dialog_example.dart @@ -1,22 +1,13 @@ import 'package:flutter/material.dart'; import 'package:photo_view/photo_view.dart'; -import 'package:photo_view_example/screens/app_bar.dart'; +import 'package:photo_view_example/screens/common/app_bar.dart'; -class DialogExample extends StatelessWidget { +class DialogExample extends StatefulWidget { @override - Widget build(BuildContext context) { - return Scaffold( - body: DialogExampleInner(), - ); - } + _DialogExampleState createState() => _DialogExampleState(); } -class DialogExampleInner extends StatefulWidget { - @override - _DialogExampleInnerState createState() => _DialogExampleInnerState(); -} - -class _DialogExampleInnerState extends State { +class _DialogExampleState extends State { VoidCallback openDialog(BuildContext context) => () { showDialog( context: context, @@ -43,8 +34,9 @@ class _DialogExampleInnerState extends State { return PhotoViewGestureDetectorScope( axis: Axis.vertical, child: PhotoView( - backgroundDecoration: - BoxDecoration(color: Colors.black.withAlpha(240)), + backgroundDecoration: BoxDecoration( + color: Colors.black.withAlpha(240), + ), imageProvider: const AssetImage("assets/large-image.jpg"), heroAttributes: const PhotoViewHeroAttributes(tag: "someTag"), ), @@ -78,38 +70,31 @@ class _DialogExampleInnerState extends State { @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const ExampleAppBar( - title: "Dialog Example", - showGoBack: true, - ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - decoration: BoxDecoration(color: Colors.red), - ), - RaisedButton( - child: const Text("Dialog"), - onPressed: openDialog(context), - ), - const Divider(), - RaisedButton( - child: const Text("Bottom sheet"), - onPressed: openBottomSheet(context), - ), - const Divider(), - RaisedButton( - child: const Text("Bottom sheet tight mode"), - onPressed: openBottomSheetModal(context), - ), - ], + return ExampleAppBarLayout( + title: "Dialogs integration", + showGoBack: true, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + decoration: const BoxDecoration(color: Colors.red), + ), + RaisedButton( + child: const Text("Dialog"), + onPressed: openDialog(context), + ), + const Divider(), + RaisedButton( + child: const Text("Bottom sheet"), + onPressed: openBottomSheet(context), + ), + const Divider(), + RaisedButton( + child: const Text("Bottom sheet tight mode"), + onPressed: openBottomSheetModal(context), ), - ), - ], + ], + ), ); } } diff --git a/example/lib/screens/examples/full_screen_examples.dart b/example/lib/screens/examples/full_screen_examples.dart deleted file mode 100644 index 159784b6..00000000 --- a/example/lib/screens/examples/full_screen_examples.dart +++ /dev/null @@ -1,291 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:photo_view/photo_view.dart'; -import 'package:photo_view_example/screens/app_bar.dart'; - -class FullScreenExamples extends StatelessWidget { - @override - Widget build(BuildContext context) { - return Scaffold( - body: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const ExampleAppBar( - title: "Full Screen Examples", - showGoBack: true, - ), - Expanded( - child: ListView( - children: [ - ExampleButtonNode( - title: "Large Image", - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const FullScreenWrapper( - imageProvider: - const AssetImage("assets/large-image.jpg"), - ), - ), - ); - }, - ), - ExampleButtonNode( - title: "Large Image (filter quality: medium)", - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const FullScreenWrapper( - imageProvider: - const AssetImage("assets/large-image.jpg"), - filterQuality: FilterQuality.medium, - ), - ), - ); - }, - ), - ExampleButtonNode( - title: "Small Image (custom background)", - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const FullScreenWrapper( - imageProvider: - const AssetImage("assets/small-image.jpg"), - backgroundDecoration: BoxDecoration( - gradient: LinearGradient( - colors: [Colors.white, Colors.grey], - stops: [0.1, 1.0], - ), - ), - ), - ), - ); - }, - ), - ExampleButtonNode( - title: "Small Image (custom alignment)", - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const FullScreenWrapper( - imageProvider: - const AssetImage("assets/small-image.jpg"), - backgroundDecoration: BoxDecoration( - color: Colors.white, - ), - basePosition: Alignment(0.5, 0.0), - ), - ), - ); - }, - ), - ExampleButtonNode( - title: "Image from the internet (with custom loader)", - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => FullScreenWrapper( - imageProvider: const NetworkImage( - "https://source.unsplash.com/1900x3600/?camera,paper"), - loadingBuilder: (context, event) { - if (event == null) { - return const Center( - child: Text("Loading"), - ); - } - final value = event.cumulativeBytesLoaded / - event.expectedTotalBytes; - - final percentage = (100 * value).floor(); - return Center( - child: Text("$percentage%"), - ); - }, - ), - ), - ); - }, - ), - ExampleButtonNode( - title: "Animated GIF", - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const FullScreenWrapper( - imageProvider: const AssetImage("assets/peanut.gif"), - //backgroundDecoration: - // BoxDecoration(color: Colors.white), - //axScale: 2.0, - ), - ), - ); - }, - ), - ExampleButtonNode( - title: "Limited scale", - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => FullScreenWrapper( - imageProvider: - const AssetImage("assets/large-image.jpg"), - minScale: PhotoViewComputedScale.contained * 0.8, - maxScale: PhotoViewComputedScale.covered * 1.1, - initialScale: PhotoViewComputedScale.covered * 1.1, - ), - ), - ); - }, - ), - ExampleButtonNode( - title: "Custom Initial scale", - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => FullScreenWrapper( - imageProvider: - const AssetImage("assets/large-image.jpg"), - initialScale: PhotoViewComputedScale.contained * 0.7, - ), - ), - ); - }, - ), - ExampleButtonNode( - title: "One tap to dismiss", - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const OneTapWrapper( - imageProvider: - const AssetImage("assets/large-image.jpg"), - ), - ), - ); - }, - ), - ], - ), - ), - ], - ), - ); - } -} - -class ExampleButtonNode extends StatelessWidget { - const ExampleButtonNode({ - this.title, - this.onPressed, - }); - - final String title; - final Function onPressed; - - @override - Widget build(BuildContext context) { - return Container( - margin: const EdgeInsets.symmetric( - vertical: 20.0, - ), - child: Column( - children: [ - Text( - title, - style: const TextStyle( - color: Colors.black, - fontSize: 21.0, - fontWeight: FontWeight.w600), - ), - Container( - margin: const EdgeInsets.only( - top: 10.0, - ), - child: RaisedButton( - onPressed: onPressed, - child: const Text("Open example"), - color: Colors.amber, - )) - ], - )); - } -} - -class FullScreenWrapper extends StatelessWidget { - const FullScreenWrapper({ - this.imageProvider, - this.loadingBuilder, - this.backgroundDecoration, - this.minScale, - this.maxScale, - this.initialScale, - this.basePosition = Alignment.center, - this.filterQuality = FilterQuality.none, - }); - - final ImageProvider imageProvider; - final LoadingBuilder loadingBuilder; - final Decoration backgroundDecoration; - final dynamic minScale; - final dynamic maxScale; - final dynamic initialScale; - final Alignment basePosition; - final FilterQuality filterQuality; - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Container( - constraints: BoxConstraints.expand( - height: MediaQuery.of(context).size.height, - ), - child: PhotoView( - imageProvider: imageProvider, - loadingBuilder: loadingBuilder, - backgroundDecoration: backgroundDecoration, - minScale: minScale, - maxScale: maxScale, - initialScale: initialScale, - basePosition: basePosition, - filterQuality: filterQuality, - ), - ), - ); - } -} - -class OneTapWrapper extends StatelessWidget { - const OneTapWrapper({ - this.imageProvider, - }); - - final ImageProvider imageProvider; - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Container( - constraints: BoxConstraints.expand( - height: MediaQuery.of(context).size.height, - ), - child: GestureDetector( - onTapDown: (_) { - Navigator.pop(context); - }, - child: PhotoView( - imageProvider: imageProvider, - ), - ), - ), - ); - } -} diff --git a/example/lib/screens/examples/gallery/gallery_example.dart b/example/lib/screens/examples/gallery/gallery_example.dart index af993927..f7b3ac42 100644 --- a/example/lib/screens/examples/gallery/gallery_example.dart +++ b/example/lib/screens/examples/gallery/gallery_example.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view_gallery.dart'; -import 'package:photo_view_example/screens/app_bar.dart'; +import 'package:photo_view_example/screens/common/app_bar.dart'; import 'package:photo_view_example/screens/examples/gallery/gallery_example_item.dart'; class GalleryExample extends StatefulWidget { @@ -15,61 +15,52 @@ class _GalleryExampleState extends State { @override Widget build(BuildContext context) { - return Scaffold( - body: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const ExampleAppBar( - title: "Gallery Example", - showGoBack: true, - ), - Expanded( - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - GalleryExampleItemThumbnail( - galleryExampleItem: galleryItems[0], - onTap: () { - open(context, 0); - }, - ), - GalleryExampleItemThumbnail( - galleryExampleItem: galleryItems[2], - onTap: () { - open(context, 2); - }, - ), - GalleryExampleItemThumbnail( - galleryExampleItem: galleryItems[3], - onTap: () { - open(context, 3); - }, - ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text("Vertical"), - Checkbox( - value: verticalGallery, - onChanged: (value) { - setState(() { - verticalGallery = value; - }); - }, - ), - ], - ), - ], - ), + return ExampleAppBarLayout( + title: "Gallery Example", + showGoBack: true, + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GalleryExampleItemThumbnail( + galleryExampleItem: galleryItems[0], + onTap: () { + open(context, 0); + }, + ), + GalleryExampleItemThumbnail( + galleryExampleItem: galleryItems[2], + onTap: () { + open(context, 2); + }, + ), + GalleryExampleItemThumbnail( + galleryExampleItem: galleryItems[3], + onTap: () { + open(context, 3); + }, + ), + ], ), - ), - ], + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text("Vertical"), + Checkbox( + value: verticalGallery, + onChanged: (value) { + setState(() { + verticalGallery = value; + }); + }, + ), + ], + ), + ], + ), ), ); } diff --git a/example/lib/screens/examples/hero_example.dart b/example/lib/screens/examples/hero_example.dart index 971465c3..736c7bdd 100644 --- a/example/lib/screens/examples/hero_example.dart +++ b/example/lib/screens/examples/hero_example.dart @@ -1,48 +1,39 @@ import 'package:flutter/material.dart'; import 'package:photo_view/photo_view.dart'; -import 'package:photo_view_example/screens/app_bar.dart'; +import 'package:photo_view_example/screens/common/app_bar.dart'; class HeroExample extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - body: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const ExampleAppBar( - title: "Hero Example", - showGoBack: true, - ), - Expanded( - child: Center( - child: GestureDetector( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const HeroPhotoViewWrapper( - imageProvider: AssetImage("assets/large-image.jpg"), - ), - ), - ); - }, - child: Container( - child: Hero( - tag: "someTag", - child: Image.asset("assets/large-image.jpg", width: 150.0), - ), + return ExampleAppBarLayout( + title: "Hero", + showGoBack: true, + child: Center( + child: GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const HeroPhotoViewRouteWrapper( + imageProvider: AssetImage("assets/large-image.jpg"), ), ), + ); + }, + child: Container( + child: Hero( + tag: "someTag", + child: Image.asset("assets/large-image.jpg", width: 150.0), ), - ) - ], + ), + ), ), ); } } -class HeroPhotoViewWrapper extends StatelessWidget { - const HeroPhotoViewWrapper({ +class HeroPhotoViewRouteWrapper extends StatelessWidget { + const HeroPhotoViewRouteWrapper({ this.imageProvider, this.loadingBuilder, this.backgroundDecoration, diff --git a/example/lib/screens/examples/inline_examples.dart b/example/lib/screens/examples/inline_examples.dart index 70be4bb9..06e09c7c 100644 --- a/example/lib/screens/examples/inline_examples.dart +++ b/example/lib/screens/examples/inline_examples.dart @@ -1,45 +1,35 @@ import 'package:flutter/material.dart'; import 'package:photo_view/photo_view.dart'; - -import '../app_bar.dart'; +import 'package:photo_view_example/screens/common/app_bar.dart'; class InlineExample extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - body: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, + return ExampleAppBarLayout( + title: "Inline usage", + showGoBack: true, + child: ListView( children: [ - const ExampleAppBar( - title: "Inline Examples", - showGoBack: true, + Container( + padding: const EdgeInsets.all(20.0), + child: const Text( + "Example of usage in a contained context", + style: const TextStyle(fontSize: 18.0), + ), ), - Expanded( - child: ListView( - children: [ - Container( - padding: const EdgeInsets.all(20.0), - child: const Text( - "Example of usage in a contained context", - style: const TextStyle(fontSize: 18.0), - ), - ), - Container( - margin: const EdgeInsets.symmetric( - vertical: 20.0, - horizontal: 20.0, - ), - height: 200.0, - child: ClipRect( - child: PhotoView( - imageProvider: const AssetImage("assets/large-image.jpg"), - maxScale: PhotoViewComputedScale.covered * 2.0, - minScale: PhotoViewComputedScale.contained * 0.8, - initialScale: PhotoViewComputedScale.covered, - ), - ), - ) - ], + Container( + margin: const EdgeInsets.symmetric( + vertical: 20.0, + horizontal: 20.0, + ), + height: 200.0, + child: ClipRect( + child: PhotoView( + imageProvider: const AssetImage("assets/large-image.jpg"), + maxScale: PhotoViewComputedScale.covered * 2.0, + minScale: PhotoViewComputedScale.contained * 0.8, + initialScale: PhotoViewComputedScale.covered, + ), ), ) ], diff --git a/example/lib/screens/examples/network-images.dart b/example/lib/screens/examples/network-images.dart new file mode 100644 index 00000000..6bad6041 --- /dev/null +++ b/example/lib/screens/examples/network-images.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:photo_view_example/screens/common/app_bar.dart'; +import 'package:photo_view_example/screens/common/common_example_wrapper.dart'; +import 'package:photo_view_example/screens/common/example_button.dart'; + +class NetworkExamples extends StatelessWidget { + @override + Widget build(BuildContext context) { + return ExampleAppBarLayout( + title: "Network", + showGoBack: true, + child: ListView( + children: [ + ExampleButtonNode( + title: "Image from the internet (with custom loader)", + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => CommonExampleRouteWrapper( + imageProvider: const NetworkImage( + "https://source.unsplash.com/1900x3600/?camera,paper"), + loadingBuilder: (context, event) { + if (event == null) { + return const Center( + child: Text("Loading"), + ); + } + final value = event.cumulativeBytesLoaded / + event.expectedTotalBytes; + + final percentage = (100 * value).floor(); + return Center( + child: Text("$percentage%"), + ); + }, + ), + ), + ); + }, + ), + ExampleButtonNode( + title: "Image from the internet (with custom loader)", + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const CommonExampleRouteWrapper( + imageProvider: const NetworkImage( + "https://pudim.com.br/sss.jpg", + ), + ), + ), + ); + }, + ) + ], + ), + ); + } +} diff --git a/example/lib/screens/examples/rotation_examples.dart b/example/lib/screens/examples/rotation_examples.dart index 12e53aa0..b23d287b 100644 --- a/example/lib/screens/examples/rotation_examples.dart +++ b/example/lib/screens/examples/rotation_examples.dart @@ -1,42 +1,34 @@ import 'package:flutter/material.dart'; import 'package:photo_view/photo_view.dart'; - -import '../app_bar.dart'; +import 'package:photo_view_example/screens/common/app_bar.dart'; class RotationExamples extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - body: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, + return ExampleAppBarLayout( + title: "Rotation Example", + showGoBack: true, + child: Column( children: [ - const ExampleAppBar( - title: "Rotation Example", - showGoBack: true, + Container( + padding: const EdgeInsets.all(20.0), + child: const Text( + "Example using option enableRotation, just pinch an rotate", + style: const TextStyle(fontSize: 18.0), + ), ), - Expanded( - child: Column( - children: [ - Container( - padding: const EdgeInsets.all(20.0), - child: const Text( - "Example using option enableRotation, just pinch an rotate", - style: const TextStyle(fontSize: 18.0), - ), + Container( + margin: const EdgeInsets.symmetric(vertical: 20.0), + height: 300.0, + child: ClipRect( + child: PhotoView( + imageProvider: const AssetImage("assets/large-image.jpg"), + maxScale: PhotoViewComputedScale.covered, + initialScale: PhotoViewComputedScale.contained * 0.8, + enableRotation: true, ), - Container( - margin: const EdgeInsets.symmetric(vertical: 20.0), - height: 300.0, - child: ClipRect( - child: PhotoView( - imageProvider: const AssetImage("assets/large-image.jpg"), - maxScale: PhotoViewComputedScale.covered, - initialScale: PhotoViewComputedScale.contained * 0.8, - enableRotation: true, - ), - )), - ], - )) + ), + ), ], ), ); diff --git a/example/lib/screens/home_screen.dart b/example/lib/screens/home_screen.dart index 30df8b33..74a130b3 100644 --- a/example/lib/screens/home_screen.dart +++ b/example/lib/screens/home_screen.dart @@ -1,14 +1,16 @@ import 'package:flutter/material.dart'; -import 'package:photo_view_example/screens/app_bar.dart'; +import 'package:photo_view_example/screens/common/app_bar.dart'; import 'package:photo_view_example/screens/examples/controller_example.dart'; import 'package:photo_view_example/screens/examples/custom_child_examples.dart'; import 'package:photo_view_example/screens/examples/dialog_example.dart'; -import 'package:photo_view_example/screens/examples/full_screen_examples.dart'; +import 'package:photo_view_example/screens/examples/common_use_cases_examples.dart'; import 'package:photo_view_example/screens/examples/gallery/gallery_example.dart'; import 'package:photo_view_example/screens/examples/hero_example.dart'; import 'package:photo_view_example/screens/examples/inline_examples.dart'; import 'package:photo_view_example/screens/examples/rotation_examples.dart'; +import 'examples/network-images.dart'; + class HomeScreen extends StatelessWidget { @override Widget build(BuildContext context) { @@ -28,70 +30,114 @@ class HomeScreen extends StatelessWidget { Expanded( child: ListView( children: [ - _buildItem(context, onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => FullScreenExamples(), - ), - ); - }, text: "Full screen"), - _buildItem(context, onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ControllerExample(), - ), - ); - }, text: "Controller"), - _buildItem(context, onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => InlineExample(), - ), - ); - }, text: "Part of the screen"), - _buildItem(context, onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => RotationExamples(), - ), - ); - }, text: "Rotation Gesture"), - _buildItem(context, onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => HeroExample(), - ), - ); - }, text: "Hero animation"), - _buildItem(context, onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => GalleryExample(), - ), - ); - }, text: "Gallery"), - _buildItem(context, onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => CustomChildExample(), - ), - ); - }, text: "Custom child"), - _buildItem(context, onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => DialogExample(), - ), - ); - }, text: "Integrated to dialogs"), + _buildItem( + context, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => CommonUseCasesExamples(), + ), + ); + }, + text: "Common use cases", + ), + _buildItem( + context, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => NetworkExamples(), + ), + ); + }, + text: "Network images", + ), + _buildItem( + context, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ControllerExample(), + ), + ); + }, + text: "Controller", + ), + _buildItem( + context, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => InlineExample(), + ), + ); + }, + text: "Part of the screen", + ), + _buildItem( + context, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => RotationExamples(), + ), + ); + }, + text: "Rotation Gesture", + ), + _buildItem( + context, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => HeroExample(), + ), + ); + }, + text: "Hero animation", + ), + _buildItem( + context, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => GalleryExample(), + ), + ); + }, + text: "Gallery", + ), + _buildItem( + context, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => CustomChildExample(), + ), + ); + }, + text: "Custom child", + ), + _buildItem( + context, + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DialogExample(), + ), + ); + }, + text: "Integrated to dialogs", + ), ], ), ), diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 82cf430d..0db1b17c 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -23,7 +23,7 @@ dev_dependencies: flutter: uses-material-design: true assets: - - assets/peanut.gif + - assets/neat.gif - assets/large-image.jpg - assets/small-image.jpg - assets/gallery1.jpg diff --git a/lib/photo_view.dart b/lib/photo_view.dart index 2770db70..fcea553a 100644 --- a/lib/photo_view.dart +++ b/lib/photo_view.dart @@ -393,7 +393,6 @@ class PhotoView extends StatefulWidget { class _PhotoViewState extends State { Size _childSize; bool _loading; - bool _loadFailed; ImageChunkEvent _imageChunkEvent; bool _controlledController; @@ -411,34 +410,30 @@ class _PhotoViewState extends State { ImageInfo info, bool synchronousCall, ) { - if (!completer.isCompleted) { - completer.complete(info); - if (mounted) { - final setupCallback = () { - _childSize = Size( - info.image.width.toDouble(), - info.image.height.toDouble(), - ); - _loading = false; - _imageChunkEvent = null; - }; - synchronousCall ? setupCallback() : setState(setupCallback); - } + if (completer.isCompleted) { + return; + } + completer.complete(info); + if (mounted) { + final setupCallback = () { + _childSize = Size( + info.image.width.toDouble(), + info.image.height.toDouble(), + ); + _loading = false; + _imageChunkEvent = null; + }; + synchronousCall ? setupCallback() : setState(setupCallback); } }, onChunk: (event) { if (mounted) { setState(() => _imageChunkEvent = event); } }, onError: (exception, stackTrace) { - if (!mounted) { + if (completer.isCompleted) { return; } - setState(() { - _loadFailed = true; - }); - FlutterError.reportError( - FlutterErrorDetails(exception: exception, stack: stackTrace), - ); + completer.completeError(exception, stackTrace); }); stream.addListener(listener); completer.future.then((_) { @@ -524,18 +519,16 @@ class _PhotoViewState extends State { @override Widget build(BuildContext context) { - return _loadFailed == true - ? _buildLoadFailed() - : LayoutBuilder( - builder: ( - BuildContext context, - BoxConstraints constraints, - ) { - return widget.child == null - ? _buildImage(context, constraints) - : _buildCustomChild(context, constraints); - }, - ); + return LayoutBuilder( + builder: ( + BuildContext context, + BoxConstraints constraints, + ) { + return widget.child == null + ? _buildImage(context, constraints) + : _buildCustomChild(context, constraints); + }, + ); } Widget _buildCustomChild(BuildContext context, BoxConstraints constraints) { @@ -577,11 +570,13 @@ class _PhotoViewState extends State { return FutureBuilder( future: _getImage(), builder: (BuildContext context, AsyncSnapshot info) { + if (info.hasError) { + return _buildLoadFailed(); + } if (info.hasData) { return _buildWrapperImage(context, constraints); - } else { - return _buildLoading(); } + return _buildLoading(); }); }