diff --git a/CHANGELOG.md b/CHANGELOG.md
index ab5db23..fb626dd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+## 4.0.5
+* **FEAT:** https://github.com/feduke-nukem/flutter_easy_dialogs/issues/40
+
## 4.0.4
* **FIX:** Fixed https://github.com/feduke-nukem/flutter_easy_dialogs/issues/37
* **FIX:** Fixed https://github.com/feduke-nukem/flutter_easy_dialogs/issues/38
diff --git a/example/.gitignore b/example/.gitignore
index a8e938c..221b422 100644
--- a/example/.gitignore
+++ b/example/.gitignore
@@ -5,9 +5,11 @@
*.swp
.DS_Store
.atom/
+.build/
.buildlog/
.history
.svn/
+.swiftpm/
migrate_working_dir/
# IntelliJ related
diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist
index 7c56964..1dc6cf7 100644
--- a/example/ios/Flutter/AppFrameworkInfo.plist
+++ b/example/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 12.0
+ 13.0
diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj
index 2d5f6c6..cd7ed99 100644
--- a/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/example/ios/Runner.xcodeproj/project.pbxproj
@@ -275,7 +275,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -352,7 +352,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -401,7 +401,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index 5e31d3d..9c12df5 100644
--- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -26,6 +26,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift
index 70693e4..b636303 100644
--- a/example/ios/Runner/AppDelegate.swift
+++ b/example/ios/Runner/AppDelegate.swift
@@ -1,7 +1,7 @@
import UIKit
import Flutter
-@UIApplicationMain
+@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
diff --git a/example/lib/positioned/screens/positioned_dialog_basic_usage.dart b/example/lib/positioned/screens/positioned_dialog_basic_usage.dart
index d0b8970..c78ff4a 100644
--- a/example/lib/positioned/screens/positioned_dialog_basic_usage.dart
+++ b/example/lib/positioned/screens/positioned_dialog_basic_usage.dart
@@ -40,6 +40,7 @@ class _PositionedDialogManagerBasicUsageScreenState
_dismissibleTap: EasyDialogDismiss.tap(
onDismissed: () => _count++,
willDismiss: () => true,
+ behavior: HitTestBehavior.deferToChild,
),
_dismissibleHorizontalSwipe: EasyDialogDismiss.swipe(
onDismissed: () => _count++,
@@ -53,6 +54,7 @@ class _PositionedDialogManagerBasicUsageScreenState
_dismissibleAnimatedTap: EasyDialogDismiss.animatedTap(
onDismissed: () => _count++,
willDismiss: () => true,
+ behavior: HitTestBehavior.translucent,
),
};
final _animatorsDropDownItems = _animations.entries
@@ -88,6 +90,7 @@ class _PositionedDialogManagerBasicUsageScreenState
EasyDialogPosition _selectedPosition = EasyDialogPosition.top;
late var _selectedDismissible = _dismissibles.values.first;
var _isAutoHide = false;
+ var _isDraggable = false;
var _autoHideDuration = 300.0;
@override
@@ -158,6 +161,15 @@ class _PositionedDialogManagerBasicUsageScreenState
onChanged: (value) => setState(() => _autoHideDuration = value),
),
],
+ CheckboxListTile(
+ contentPadding: const EdgeInsets.symmetric(
+ horizontal: 20.0,
+ vertical: 5.0,
+ ),
+ title: const Text('Draggable'),
+ value: _isDraggable,
+ onChanged: (value) => setState(() => _isDraggable = value!),
+ ),
ElevatedButton(
onPressed: _show,
child: const Text('Show'),
@@ -182,30 +194,45 @@ class _PositionedDialogManagerBasicUsageScreenState
Future _show() async {
final messenger = ScaffoldMessenger.of(context);
- final content = Container(
- height: 150.0,
- color: Colors.blue[900],
- alignment: Alignment.center,
- child: Text(
- _selectedPosition.name,
- style: const TextStyle(
- color: Colors.white,
- fontSize: 30.0,
+ final content = ColoredBox(
+ color: Colors.blue[900]!,
+ child: SizedBox(
+ height: 150.0,
+ width: 50,
+ child: Text(
+ _selectedPosition.name,
+ style: const TextStyle(
+ color: Colors.white,
+ fontSize: 30.0,
+ ),
),
),
);
- final result = await content
+ var dialog = content
.positioned(
position: _selectedPosition,
autoHideDuration: _isAutoHide
? Duration(milliseconds: _autoHideDuration.toInt())
: null,
)
- .decorate(const PositionedShell.banner())
- .decorate(_selectedAnimation)
- .decorate(_selectedDismissible)
- .show();
+ .decorate(const PositionedShell.banner());
+
+ if (_isDraggable) {
+ final screenSize = MediaQuery.sizeOf(context);
+ dialog = dialog.draggable(
+ bounds: Rect.fromLTWH(
+ 0,
+ 0,
+ screenSize.width,
+ screenSize.height,
+ ),
+ );
+ }
+
+ dialog = dialog.decorate(_selectedAnimation).decorate(_selectedDismissible);
+
+ final result = await dialog.show();
if (result == null) return;
messenger
diff --git a/example/pubspec.lock b/example/pubspec.lock
index 3cd213b..5950eff 100644
--- a/example/pubspec.lock
+++ b/example/pubspec.lock
@@ -134,10 +134,10 @@ packages:
dependency: transitive
description:
name: meta
- sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
+ sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
- version: "1.16.0"
+ version: "1.17.0"
path:
dependency: transitive
description:
@@ -195,10 +195,10 @@ packages:
dependency: transitive
description:
name: test_api
- sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
+ sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
url: "https://pub.dev"
source: hosted
- version: "0.7.6"
+ version: "0.7.7"
vector_math:
dependency: transitive
description:
@@ -216,5 +216,5 @@ packages:
source: hosted
version: "15.0.0"
sdks:
- dart: ">=3.7.0-0 <4.0.0"
+ dart: ">=3.8.0-0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"
diff --git a/lib/src/core/easy_dialogs_controller.dart b/lib/src/core/easy_dialogs_controller.dart
index e79ba0b..c256409 100644
--- a/lib/src/core/easy_dialogs_controller.dart
+++ b/lib/src/core/easy_dialogs_controller.dart
@@ -760,11 +760,14 @@ extension EasyDialogsX on EasyDialog {
);
}
- EasyDialog draggable() {
+ EasyDialog draggable({
+ Rect? bounds,
+ }) {
return decorate(
EasyDialogDecoration.builder(
- (context, dialog) => FreePositioned(
+ (_, dialog) => FreePositioned(
child: dialog.content,
+ bounds: bounds,
),
),
);
diff --git a/lib/src/core/widget/free_positioned.dart b/lib/src/core/widget/free_positioned.dart
index 54a403d..dc1ccf7 100644
--- a/lib/src/core/widget/free_positioned.dart
+++ b/lib/src/core/widget/free_positioned.dart
@@ -1,59 +1,119 @@
-import 'package:flutter/widgets.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_easy_dialogs/src/positioned/positioned.dart'
+ show PositionedDialog;
class FreePositioned extends StatefulWidget {
final Widget child;
+ final Rect? bounds;
- const FreePositioned({required this.child});
+ const FreePositioned({
+ required this.child,
+ this.bounds,
+ super.key,
+ });
@override
State createState() => _FreePositionedState();
}
class _FreePositionedState extends State {
- var _x = 0.0;
- var _y = 0.0;
- AlignmentGeometry? _alignment;
+ var _offset = Offset(0.0, 0.0);
+ Offset? _globalOffset;
@override
Widget build(BuildContext context) {
final screenSize = MediaQuery.sizeOf(context);
+ final result = Builder(
+ builder: (context) => GestureDetector(
+ child: widget.child,
+ onPanStart: (details) => _onPanStart(details, context),
+ onPanUpdate: (details) => _onPanUpdate(details, context),
+ ),
+ );
+
+ final alignment = PositionedDialog.maybeOf(context)?.alignment;
+
return Stack(
children: [
Positioned(
- left: _x,
- top: _y,
- child: GestureDetector(
- child: ConstrainedBox(
- constraints: BoxConstraints(
- maxWidth: screenSize.width,
- maxHeight: screenSize.height,
- ),
- // Not the best approach to apply alignment but it works for now,
- // need to be reworked in the future
- child: _alignment == null
- ? widget.child
- : Align(
- alignment: _alignment!,
- child: widget.child,
- ),
- ),
- onPanUpdate: (details) => setState(
- () {
- _x += details.delta.dx;
- _y += details.delta.dy;
- },
+ left: _offset.dx,
+ top: _offset.dy,
+ child: ConstrainedBox(
+ constraints: BoxConstraints(
+ maxWidth: screenSize.width,
+ maxHeight: screenSize.height,
),
+ child: alignment == null
+ ? result
+ : Align(
+ alignment: alignment,
+ child: result,
+ ),
),
),
],
);
}
- @override
- void didChangeDependencies() {
- super.didChangeDependencies();
+ void _onPanStart(DragStartDetails details, BuildContext context) {
+ final renderBox = context.findRenderObject() as RenderBox?;
+ if (renderBox == null) return;
+
+ _globalOffset = renderBox.localToGlobal(Offset.zero);
+ }
+
+ void _onPanUpdate(DragUpdateDetails details, BuildContext context) {
+ final globalOffset = _globalOffset;
+
+ if (globalOffset == null) {
+ return;
+ }
+
+ final deltaX = details.delta.dx;
+ final deltaY = details.delta.dy;
+ final newGlobalX = globalOffset.dx + deltaX;
+ final newGlobalY = globalOffset.dy + deltaY;
+ final oldOffset = _offset;
+
+ final bounds = widget.bounds;
+
+ if (bounds == null) {
+ setState(() {
+ _offset = Offset(_offset.dx + deltaX, _offset.dy + deltaY);
+ _globalOffset = Offset(newGlobalX, newGlobalY);
+ });
+
+ return;
+ }
+
+ final RenderBox? renderBox = context.findRenderObject() as RenderBox?;
+
+ if (renderBox == null) {
+ return;
+ }
+
+ final childSize = renderBox.size;
+
+ final inBoundsX = newGlobalX >= bounds.left &&
+ newGlobalX + childSize.width <= bounds.right;
+ final inBoundsY = newGlobalY >= bounds.top &&
+ newGlobalY + childSize.height <= bounds.bottom;
+
+ if (inBoundsX) {
+ _offset = Offset(_offset.dx + deltaX, _offset.dy);
+ _globalOffset = Offset(newGlobalX, newGlobalY);
+ }
+
+ if (inBoundsY) {
+ _offset = Offset(_offset.dx, _offset.dy + deltaY);
+ _globalOffset = Offset(newGlobalX, newGlobalY);
+ }
+
+ if (oldOffset == _offset) {
+ return;
+ }
- _alignment = context.findAncestorWidgetOfExactType()?.alignment;
+ setState(() {});
}
}
diff --git a/lib/src/positioned/dialog/positioned_dialog.dart b/lib/src/positioned/dialog/positioned_dialog.dart
index ff4414b..f8a703c 100644
--- a/lib/src/positioned/dialog/positioned_dialog.dart
+++ b/lib/src/positioned/dialog/positioned_dialog.dart
@@ -15,6 +15,9 @@ final class PositionedDialog extends EasyDialog {
/// The position where the dialog will be shown.
final EasyDialogPosition position;
+ static EasyDialogPosition? maybeOf(BuildContext context) =>
+ _PositionedDialogScope.maybeOf(context)?.position;
+
/// Creates an instance of [PositionedDialog].
PositionedDialog({
required super.content,
@@ -28,9 +31,12 @@ final class PositionedDialog extends EasyDialog {
@override
EasyOverlayBoxInsertion createInsert(Widget decorated) {
return super.createInsert(
- Align(
- alignment: position.alignment,
- child: decorated,
+ _PositionedDialogScope(
+ child: Align(
+ alignment: position.alignment,
+ child: decorated,
+ ),
+ position: position,
),
);
}
@@ -58,3 +64,20 @@ enum EasyDialogPosition {
const EasyDialogPosition(this.alignment);
}
+
+class _PositionedDialogScope extends InheritedWidget {
+ final EasyDialogPosition position;
+ const _PositionedDialogScope({
+ required super.child,
+ required this.position,
+ });
+
+ static _PositionedDialogScope? maybeOf(BuildContext context) {
+ return context.dependOnInheritedWidgetOfExactType<_PositionedDialogScope>();
+ }
+
+ @override
+ bool updateShouldNotify(_PositionedDialogScope oldWidget) {
+ return oldWidget.position != position;
+ }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index c6e8140..014abb6 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: flutter_easy_dialogs
description: Easy and flexible package for showing dialogs inside your Flutter application without BuildContext.
-version: 4.0.4
+version: 4.0.5
homepage: https://github.com/feduke-nukem/flutter_easy_dialogs
repository: https://github.com/feduke-nukem/flutter_easy_dialogs/tree/main
issue_tracker: https://github.com/feduke-nukem/flutter_easy_dialogs/issues