diff --git a/lib/common/widgets/avatar_widget.dart b/lib/common/widgets/avatar_widget.dart index c858cf5..f4c3196 100644 --- a/lib/common/widgets/avatar_widget.dart +++ b/lib/common/widgets/avatar_widget.dart @@ -3,25 +3,31 @@ part of petisland.common.widgets; class AvatarWidget extends StatelessWidget { final String url; final EdgeInsetsGeometry paddingDefaultImage; - - const AvatarWidget({ + final bool fullScreenOnTap; + AvatarWidget({ Key key, this.url, this.paddingDefaultImage = const EdgeInsets.all(2.0), + this.fullScreenOnTap = false, }) : super(key: key); @override Widget build(BuildContext context) { - Widget child; if (url != null) { - final type = StringUtils.isImageUrlFormat(url) - ? ImageSources.Server - : ImageSources.Local; - child = _buildImage(type, url); + if (fullScreenOnTap == true) { + return PostImageWidget( + isSquare: false, + imageUrl: url, + shape: BoxShape.circle, + backGroundColor: TColors.white, + ); + } else { + ImageSources type = StringUtils.isImageUrlFormat(url) ? ImageSources.Server : ImageSources.Local; + return _buildImage(type, url); + } } else { - child = buildDefaultAvatar(); + return buildDefaultAvatar(); } - return child; } Widget buildDefaultAvatar() { @@ -38,11 +44,13 @@ class AvatarWidget extends StatelessWidget { } Widget _buildImage(ImageSources type, String url) { - return type == ImageSources.Server - ? TCacheImageWidget( - url: url, - shape: BoxShape.circle, - ) - : Image.file(File(url)); + if (type == ImageSources.Server) { + return TCacheImageWidget( + url: url, + shape: BoxShape.circle, + ); + } else { + return Image.file(File(url)); + } } } diff --git a/lib/common/widgets/preview_image_widget.dart b/lib/common/widgets/preview_image_widget.dart new file mode 100644 index 0000000..c691593 --- /dev/null +++ b/lib/common/widgets/preview_image_widget.dart @@ -0,0 +1,94 @@ +part of petisland.common.widgets; + +class PreviewImage extends StatelessWidget { + static const String name = '/_PreviewImage'; + + final ImageProvider imageProvider; + final Object heroTag; + final bool usePhotoView; + + PreviewImage(this.imageProvider, this.heroTag, {Key key, this.usePhotoView = false}) + : super(key: key); + + @override + Widget build(BuildContext context) { + final Widget background = _backgroundGradient(); + final Widget image = _imageWidget(); + final Widget appbar = _appbar(context); + return Scaffold( + backgroundColor: TColors.transparent, + body: Stack( + fit: StackFit.passthrough, + children: [ + image, + background, + appbar, + ], + ), + ); + } + + Widget _backgroundGradient() { + return Container( + height: kToolbarHeight + 30, + foregroundDecoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment(0.5, 0.07), + end: Alignment(0.5, 3), + colors: [const Color(0x00000000), const Color(0x4d000000)], + ), + ), + ); + } + + void _onClosePressed(BuildContext context) { + Navigator.of(context).pop(); + } + + Widget _imageWidget() { + if (usePhotoView) { + return Container( + child: PhotoView( + heroAttributes: PhotoViewHeroAttributes(tag: heroTag), + filterQuality: FilterQuality.high, + imageProvider: imageProvider, + minScale: PhotoViewComputedScale.contained * 0.8, + maxScale: PhotoViewComputedScale.covered * 1.8, + initialScale: PhotoViewComputedScale.contained * 1.1, + ), + ); + } else { + return Hero( + tag: heroTag, + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: imageProvider, + fit: BoxFit.contain, + ), + ), + ), + ); + } + } + + Widget _appbar(BuildContext context) { + return SizedBox( + height: kToolbarHeight + 30, + child: AppBar( + backgroundColor: TColors.transparent, + elevation: 0, + automaticallyImplyLeading: false, + leading: IconButton( + color: TColors.white, + onPressed: () => _onClosePressed(context), + icon: Icon( + Icons.close, + size: 30, + color: TColors.white, + ), + ), + ), + ); + } +} diff --git a/lib/common/widgets/t_cached_image_widget.dart b/lib/common/widgets/t_cached_image_widget.dart index c0202bb..9c3700b 100644 --- a/lib/common/widgets/t_cached_image_widget.dart +++ b/lib/common/widgets/t_cached_image_widget.dart @@ -102,6 +102,9 @@ class TCacheImageWidget extends StatelessWidget { final double width; final double height; final String url; + final ValueChanged onTapImage; + final Object heroTag; + final BoxFit fit; //default border radius = 4 final BorderRadius borderRadius; @@ -116,25 +119,45 @@ class TCacheImageWidget extends StatelessWidget { this.borderRadius, this.defaultBackgroundColor = TColors.duck_egg_blue, this.shape = BoxShape.rectangle, + this.onTapImage, + this.heroTag, + this.fit = BoxFit.cover, }) : super(key: key); @override Widget build(BuildContext context) { + if (heroTag == null) + return _buildImage(); + else { + return Hero( + tag: heroTag, + child: _buildImage(), + ); + } + } + + Widget _buildImage() { final BoxShape shape = this.shape ?? BoxShape.rectangle; BorderRadius borderRadius; if (shape == BoxShape.rectangle) borderRadius = this.borderRadius ?? BorderRadius.circular(4); + return TBaseCachedImageWidget( url: url, imageBuilder: (_, ImageProvider imageProvider) { - return Container( - width: width, - height: height, - decoration: BoxDecoration( - image: DecorationImage(image: imageProvider, fit: BoxFit.cover), - borderRadius: borderRadius, - color: defaultBackgroundColor, - shape: shape, + return GestureDetector( + onTap: () { + if (onTapImage != null) onTapImage(imageProvider); + }, + child: Container( + width: width, + height: height, + decoration: BoxDecoration( + image: DecorationImage(image: imageProvider, fit: fit), + borderRadius: borderRadius, + color: defaultBackgroundColor, + shape: shape, + ), ), ); }, diff --git a/lib/common/widgets/widgets.dart b/lib/common/widgets/widgets.dart index f0002f2..aa9d207 100644 --- a/lib/common/widgets/widgets.dart +++ b/lib/common/widgets/widgets.dart @@ -14,6 +14,7 @@ import 'package:flutter_template/pet_feed/widget/post/post.dart'; import 'package:petisland_core/module/module.dart'; import 'package:petisland_core/petisland_core.dart' hide Mode; import 'package:petisland_core/utils/utils.dart'; +import 'package:photo_view/photo_view.dart'; import 'package:shimmer/shimmer.dart'; part 'avatar_widget.dart'; @@ -33,3 +34,4 @@ part 'title_input_widget.dart'; part 'title_widget.dart'; part 'user_input_widget.dart'; part 'location_selector_widget.dart'; +part 'preview_image_widget.dart'; \ No newline at end of file diff --git a/lib/pet_feed/widget/post/post_image_widget.dart b/lib/pet_feed/widget/post/post_image_widget.dart index 160edaf..a00740f 100644 --- a/lib/pet_feed/widget/post/post_image_widget.dart +++ b/lib/pet_feed/widget/post/post_image_widget.dart @@ -1,34 +1,57 @@ part of petisland.pet_feed.widget.post; -class PostImageWidget extends StatelessWidget { +class PostImageWidget extends TStatelessWidget { final String imageUrl; final bool isSquare; - final TapImage onTapImage; final Widget imageDefault = DefaultPetImage(); + final Object heroTag = ThinId.randomId(); + final BoxFit fit; + final BoxShape shape; + final Color backGroundColor; - PostImageWidget( - {Key key, this.imageUrl, this.isSquare = true, this.onTapImage}) - : super(key: key); + PostImageWidget({Key key, this.imageUrl, this.isSquare = true, this.fit: BoxFit.cover, this.shape, this.backGroundColor = TColors.duck_egg_blue}) : super(key: key); @override Widget build(BuildContext context) { - final onTap = onTapImage != null - ? () => onTapImage(imageUrl, ImageSources.Server) - : null; - final Widget image = imageUrl != null - ? GestureDetector( - child: _buildImage(imageUrl), - onTap: onTap, - ) - : imageDefault; + final Widget image = imageUrl != null ? _buildImage(imageUrl, context) : imageDefault; return isSquare ? AspectRatio(child: image, aspectRatio: 1) : image; } - Widget _buildImage(String imageUrl) { + Widget _buildImage(String imageUrl, BuildContext context) { final bool isFromServer = StringUtils.isImageUrlFormat(imageUrl); - return isFromServer - ? TCacheImageWidget(url: imageUrl) - : Image.file(File(imageUrl)); + if (isFromServer) { + return TCacheImageWidget( + url: imageUrl, + heroTag: heroTag, + onTapImage: (_) => onTapImge(_, context), + fit: fit, + shape: shape, + defaultBackgroundColor: backGroundColor, + ); + } else { + final imageProvider = FileImage(File(imageUrl)); + return GestureDetector( + onTap: () => onTapImge(imageProvider, context), + child: Hero( + tag: heroTag, + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: imageProvider, + fit: fit, + ), + ), + ), + ), + ); + } + } + + void onTapImge(ImageProvider provider, BuildContext context) { + navigateToScreen( + context: context, + screen: PreviewImage(provider, heroTag, usePhotoView: true), + ); } } diff --git a/lib/pet_feed/widget/post/preview_post_widget.dart b/lib/pet_feed/widget/post/preview_post_widget.dart index 1bd4f91..b7ef67a 100644 --- a/lib/pet_feed/widget/post/preview_post_widget.dart +++ b/lib/pet_feed/widget/post/preview_post_widget.dart @@ -9,9 +9,8 @@ enum ImageSources { class PreviewPostWidget extends StatelessWidget { final Post item; - final TapImage onTapImage; - const PreviewPostWidget({Key key, @required this.item, this.onTapImage}) + const PreviewPostWidget({Key key, @required this.item}) : super(key: key); @override @@ -20,7 +19,7 @@ class PreviewPostWidget extends StatelessWidget { flex: 3, child: Container( margin: const EdgeInsets.only(left: 5), - child: PostImageWidget(imageUrl: item.firstImage, onTapImage: onTapImage), + child: PostImageWidget(imageUrl: item.firstImage), alignment: Alignment.centerLeft, ), ); diff --git a/lib/post/post_detail/widget/post/image_slider_widget.dart b/lib/post/post_detail/widget/post/image_slider_widget.dart index d3a17d1..121f292 100644 --- a/lib/post/post_detail/widget/post/image_slider_widget.dart +++ b/lib/post/post_detail/widget/post/image_slider_widget.dart @@ -4,8 +4,7 @@ class ImageSliderWidget extends StatelessWidget { final List images; final String description; - const ImageSliderWidget( - {Key key, @required this.images, @required this.description}) + const ImageSliderWidget({Key key, @required this.images, @required this.description}) : super(key: key); @override @@ -26,10 +25,7 @@ class ImageSliderWidget extends StatelessWidget { physics: const BouncingScrollPhysics(), itemBuilder: (_, index) { final url = images[index]; - return AspectRatio( - aspectRatio: 1, - child: _buildImage(url), - ); + return PostImageWidget(imageUrl: url); }, separatorBuilder: (BuildContext context, int index) { return const SizedBox(width: 15); @@ -39,11 +35,6 @@ class ImageSliderWidget extends StatelessWidget { ], ); } - - Widget _buildImage(String url) { - final bool isFromServer = StringUtils.isImageUrlFormat(url); - return isFromServer ? TCacheImageWidget(url: url) : Image.file(File(url)); - } } Widget buildTextDescription(BuildContext context, String text) { diff --git a/lib/post/post_edit/widget/image_post_widget.dart b/lib/post/post_edit/widget/image_post_widget.dart index 2001f2a..c47e9e8 100644 --- a/lib/post/post_edit/widget/image_post_widget.dart +++ b/lib/post/post_edit/widget/image_post_widget.dart @@ -14,10 +14,6 @@ class ImagePostWidget extends StatelessWidget { @override Widget build(BuildContext context) { - Widget child = type == ImageSources.Server - ? TCacheImageWidget(borderRadius: BorderRadius.circular(0), url: url) - : Image.file(File(url), fit: BoxFit.cover); - return ClipRRect( borderRadius: BorderRadius.circular(4), child: AspectRatio( @@ -25,7 +21,7 @@ class ImagePostWidget extends StatelessWidget { child: Stack( children: [ imageDefaultWidget(), - AspectRatio(aspectRatio: 1, child: child), + PostImageWidget(imageUrl: url, fit: BoxFit.cover), Positioned( top: 2, right: 2, diff --git a/lib/post/post_edit/widget/summary_info_widget.dart b/lib/post/post_edit/widget/summary_info_widget.dart index 951522a..a3eba59 100644 --- a/lib/post/post_edit/widget/summary_info_widget.dart +++ b/lib/post/post_edit/widget/summary_info_widget.dart @@ -9,7 +9,6 @@ class SummaryInfoWidget extends TStatelessWidget { final String customDefaultTitle; final int maxHeros; final String typeMoney; - static const Widget defaultImage = DefaultPetImage(); SummaryInfoWidget( this.title, { @@ -41,46 +40,12 @@ class SummaryInfoWidget extends TStatelessWidget { } Widget _buildImageWidget(List petImages) { - String petImage; + String url; if (petImages != null && petImages.isNotEmpty) { - petImage = petImages.first; + url = petImages.first; } - bool urlValid = petImage != null && petImage.isNotEmpty; - Widget child; - if (!urlValid) { - child = defaultImage; - } else { - if (isImageUrlFormat(petImage)) { - child = TCacheImageWidget( - borderRadius: BorderRadius.circular(0), - url: petImage, - ); - } else { - child = Image.file( - File(petImage), - fit: BoxFit.cover, - ); - } - child = Stack( - children: [ - Container( - color: TColors.duck_egg_blue, - ), - AspectRatio(child: child, aspectRatio: 1), - ], - ); - } - return Flexible( - flex: 1, - child: ClipRRect( - borderRadius: BorderRadius.circular(4), - child: AspectRatio( - aspectRatio: 1 / 1, - child: child, - ), - ), - ); + return PostImageWidget(imageUrl: url); } Widget _buildInfo(String title, double money, String location) { diff --git a/lib/profile/screen/profile_screen.dart b/lib/profile/screen/profile_screen.dart index 2bbe875..f139994 100644 --- a/lib/profile/screen/profile_screen.dart +++ b/lib/profile/screen/profile_screen.dart @@ -34,6 +34,7 @@ class _ProfileScreenState extends TState { child: AvatarWidget( url: image, paddingDefaultImage: const EdgeInsets.all(15), + fullScreenOnTap: true, ), ), onTapCamera: _handleOnTapCamera, diff --git a/lib/rescue_post/widget/preview_rescue_detail_widget.dart b/lib/rescue_post/widget/preview_rescue_detail_widget.dart index ddffd60..8a5cfd3 100644 --- a/lib/rescue_post/widget/preview_rescue_detail_widget.dart +++ b/lib/rescue_post/widget/preview_rescue_detail_widget.dart @@ -13,7 +13,7 @@ class PreviewRescueDetailWidget extends StatelessWidget { flex: 3, child: Container( margin: const EdgeInsets.only(left: 5), - child: PostImageWidget(imageUrl: rescue.firstImage, onTapImage: onTapImage), + child: PostImageWidget(imageUrl: rescue.firstImage), alignment: Alignment.center, ), ); diff --git a/lib/rescue_post/widget/rescue_detail_summary_widget.dart b/lib/rescue_post/widget/rescue_detail_summary_widget.dart index e34bcd2..eb2d808 100644 --- a/lib/rescue_post/widget/rescue_detail_summary_widget.dart +++ b/lib/rescue_post/widget/rescue_detail_summary_widget.dart @@ -14,7 +14,6 @@ class RescueDetailSummaryWidget extends StatelessWidget { @override Widget build(BuildContext context) { - final imageSliderWidget = _buildImageSlider(rescue.rescueImages); return Flex( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -41,7 +40,7 @@ class RescueDetailSummaryWidget extends StatelessWidget { : [SizedBox()]), _buildLocation(context), const SizedBox(height: 5), - Flexible(child: imageSliderWidget), + Flexible(child: _buildImageSlider(rescue.rescueImages)), const SizedBox(height: 5), _buildHero(context), Divider(), diff --git a/pubspec.yaml b/pubspec.yaml index c6bbb29..dde841f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -28,7 +28,7 @@ dependencies: flutter_webview_plugin: 0.3.11 dropdown_search: 0.4.3 dashed_container: 1.0.1 - typicons_flutter: ^0.3.1 + photo_view: 0.9.2 ddi: any petisland_core: