Skip to content

Commit

Permalink
Merge pull request #21 from thesmythgroup/add_swipe_to_dismiss
Browse files Browse the repository at this point in the history
feat: Add option to allow swipe-down to dismiss
  • Loading branch information
jfahrenkrug authored Aug 4, 2022
2 parents 1b02490 + f9851a3 commit 9c1b1ef
Show file tree
Hide file tree
Showing 11 changed files with 254 additions and 100 deletions.
7 changes: 7 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"recommendations": [
"dart-code.dart-code",
"dart-code.flutter"
]
}

7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"[dart]": {
"editor.defaultFormatter": "Dart-Code.dart-code",
"editor.formatOnSave": false
},
"dart.lineLength": 120
}
2 changes: 2 additions & 0 deletions example/ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,7 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
</dict>
</plist>
11 changes: 4 additions & 7 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,17 +58,14 @@ class _MyHomePageState extends State<MyHomePage> {
ElevatedButton(
child: const Text("Show Single Image"),
onPressed: () {
showImageViewer(
context,
Image.network("https://picsum.photos/id/1001/5616/3744")
.image);
showImageViewer(context, Image.network("https://picsum.photos/id/1001/5616/3744").image,
swipeDismissible: true);
}),
ElevatedButton(
child: const Text("Show Multiple Images (Simple)"),
onPressed: () {
MultiImageProvider multiImageProvider =
MultiImageProvider(_imageProviders);
showImageViewerPager(context, multiImageProvider);
MultiImageProvider multiImageProvider = MultiImageProvider(_imageProviders);
showImageViewerPager(context, multiImageProvider, swipeDismissible: true);
}),
ElevatedButton(
child: const Text("Show Multiple Images (Custom)"),
Expand Down
8 changes: 8 additions & 0 deletions example/windows/flutter/generated_plugins.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
list(APPEND FLUTTER_PLUGIN_LIST
)

list(APPEND FLUTTER_FFI_PLUGIN_LIST
)

set(PLUGIN_BUNDLED_LIBRARIES)

foreach(plugin ${FLUTTER_PLUGIN_LIST})
Expand All @@ -13,3 +16,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST})
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
endforeach(plugin)

foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
endforeach(ffi_plugin)
107 changes: 27 additions & 80 deletions lib/easy_image_viewer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

import 'src/easy_image_provider.dart';
import 'src/easy_image_view_pager.dart';
import 'src/easy_image_viewer_dismissible_dialog.dart';
import 'src/single_image_provider.dart';

export 'src/easy_image_provider.dart' show EasyImageProvider;
Expand All @@ -25,26 +25,27 @@ const _defaultCloseButtonTooltip = 'Close';
/// Setting [immersive] to false will prevent the top and bottom bars from being hidden.
/// The optional [onViewerDismissed] callback function is called when the dialog is closed.
/// The optional [useSafeArea] boolean defaults to false and is passed to [showDialog].
/// The optional [swipeDismissible] boolean defaults to false allows swipe-down-to-dismiss.
/// The [backgroundColor] defaults to black, but can be set to any other color.
/// The [closeButtonTooltip] text is displayed when the user long-presses on the
/// close button and is used for accessibility.
/// The [closeButtonColor] defaults to white, but can be set to any other color.
Future<Dialog?> showImageViewer(
BuildContext context, ImageProvider imageProvider,
Future<Dialog?> showImageViewer(BuildContext context, ImageProvider imageProvider,
{bool immersive = true,
void Function()? onViewerDismissed,
bool useSafeArea = false,
bool swipeDismissible = false,
Color backgroundColor = _defaultBackgroundColor,
String closeButtonTooltip = _defaultCloseButtonTooltip,
Color closeButtonColor = _defaultCloseButtonColor}) {
return showImageViewerPager(context, SingleImageProvider(imageProvider),
immersive: immersive,
onViewerDismissed:
onViewerDismissed != null ? (_) => onViewerDismissed() : null,
useSafeArea: useSafeArea,
backgroundColor: backgroundColor,
closeButtonTooltip: closeButtonTooltip,
closeButtonColor: closeButtonColor);
immersive: immersive,
onViewerDismissed: onViewerDismissed != null ? (_) => onViewerDismissed() : null,
useSafeArea: useSafeArea,
swipeDismissible: swipeDismissible,
backgroundColor: backgroundColor,
closeButtonTooltip: closeButtonTooltip,
closeButtonColor: closeButtonColor);
}

/// Shows the images provided by the [imageProvider] in a full-screen PageView [Dialog].
Expand All @@ -54,16 +55,17 @@ Future<Dialog?> showImageViewer(
/// The optional [onViewerDismissed] callback function is called with the index of
/// the image that is displayed when the dialog is closed.
/// The optional [useSafeArea] boolean defaults to false and is passed to [showDialog].
/// The optional [swipeDismissible] boolean defaults to false allows swipe-down-to-dismiss.
/// The [backgroundColor] defaults to black, but can be set to any other color.
/// The [closeButtonTooltip] text is displayed when the user long-presses on the
/// close button and is used for accessibility.
/// The [closeButtonColor] defaults to white, but can be set to any other color.
Future<Dialog?> showImageViewerPager(
BuildContext context, EasyImageProvider imageProvider,
Future<Dialog?> showImageViewerPager(BuildContext context, EasyImageProvider imageProvider,
{bool immersive = true,
void Function(int)? onPageChanged,
void Function(int)? onViewerDismissed,
bool useSafeArea = false,
bool swipeDismissible = false,
Color backgroundColor = _defaultBackgroundColor,
String closeButtonTooltip = _defaultCloseButtonTooltip,
Color closeButtonColor = _defaultCloseButtonColor}) {
Expand All @@ -72,73 +74,18 @@ Future<Dialog?> showImageViewerPager(
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive);
}

void Function()? internalPageChangeListener;
final pageController =
PageController(initialPage: imageProvider.initialIndex);

if (onPageChanged != null) {
internalPageChangeListener = () {
onPageChanged(pageController.page?.round() ?? 0);
};
pageController.addListener(internalPageChangeListener);
}

// internal function to be called whenever the dialog
// is dismissed, whether through the Android back button,
// or through the "x" close button.
handleDismissal() {
if (onViewerDismissed != null) {
onViewerDismissed(
pageController.page?.round() ?? 0);
}

if (immersive) {
SystemChrome.setEnabledSystemUIMode(
SystemUiMode.edgeToEdge);
}
if (internalPageChangeListener != null) {
pageController
.removeListener(internalPageChangeListener);
}
pageController.dispose();
}

return showDialog<Dialog>(
context: context,
useSafeArea: useSafeArea,
builder: (context) {
return WillPopScope(
onWillPop: () async {
handleDismissal();
return true;
},
child: Dialog(
backgroundColor: backgroundColor,
insetPadding: const EdgeInsets.all(0),
// We set the shape here to ensure no rounded corners allow any of the
// underlying view to show. We want the whole background to be covered.
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero),
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: <Widget>[
EasyImageViewPager(
easyImageProvider: imageProvider,
pageController: pageController),
Positioned(
top: 5,
right: 5,
child: IconButton(
icon: const Icon(Icons.close),
color: closeButtonColor,
tooltip: closeButtonTooltip,
onPressed: () {
Navigator.of(context).pop();

handleDismissal();
},
))
]))
);
});
context: context,
useSafeArea: useSafeArea,
builder: (context) {
return EasyImageViewerDismissibleDialog(imageProvider,
immersive: immersive,
onPageChanged: onPageChanged,
onViewerDismissed: onViewerDismissed,
useSafeArea: useSafeArea,
swipeDismissible: swipeDismissible,
backgroundColor: backgroundColor,
closeButtonColor: closeButtonColor,
closeButtonTooltip: closeButtonTooltip);
});
}
21 changes: 12 additions & 9 deletions lib/src/easy_image_view_pager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,40 +17,43 @@ class EasyImageViewPager extends StatefulWidget {
final EasyImageProvider easyImageProvider;
final PageController pageController;

/// Callback for when the scale has changed, only invoked at the end of
/// an interaction.
final void Function(double)? onScaleChanged;

/// Create new instance, using the [easyImageProvider] to populate the [PageView],
/// and the [pageController] to control the initial image index to display.
const EasyImageViewPager(
{Key? key, required this.easyImageProvider, required this.pageController})
{Key? key, required this.easyImageProvider, required this.pageController, this.onScaleChanged})
: super(key: key);

@override
_EasyImageViewPagerState createState() =>
// ignore: no_logic_in_create_state
_EasyImageViewPagerState(pageController: pageController);
_EasyImageViewPagerState createState() => _EasyImageViewPagerState();
}

class _EasyImageViewPagerState extends State<EasyImageViewPager> {
final PageController pageController;
bool _pagingEnabled = true;

_EasyImageViewPagerState({required this.pageController}) : super();

@override
Widget build(BuildContext context) {
return PageView.builder(
physics: _pagingEnabled
? const PageScrollPhysics()
: const NeverScrollableScrollPhysics(),
key: const Key('easy_image_view_page_view'),
key: GlobalObjectKey(widget.easyImageProvider),
itemCount: widget.easyImageProvider.imageCount,
controller: pageController,
controller: widget.pageController,
scrollBehavior: MouseEnabledScrollBehavior(),
itemBuilder: (context, index) {
final image = widget.easyImageProvider.imageBuilder(context, index);
return EasyImageView(
key: Key('easy_image_view_$index'),
imageProvider: image,
onScaleChanged: (scale) {
if (widget.onScaleChanged != null) {
widget.onScaleChanged!(scale);
}

setState(() {
_pagingEnabled = scale <= 1.0;
});
Expand Down
Loading

0 comments on commit 9c1b1ef

Please sign in to comment.