Skip to content

Commit

Permalink
Refactor draggable feature for dialogs (issue #23)
Browse files Browse the repository at this point in the history
- Removed isDraggable property from PositionedDialog and related usage in positioned_dialog_basic_usage.dart
- Introduced FreePositioned widget to handle draggable functionality
- Added draggable method in EasyDialogsController to apply draggable decoration
- Updated PositionedDialog to use Align widget instead of _FreePositioned
- Updated tests to cover new draggable feature
- Updated CHANGELOG.md to reflect changes
  • Loading branch information
feduke-nukem committed Mar 16, 2024
1 parent 7fd8cb0 commit e6d1f32
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 101 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
## 3.2.0
* **FEATURE:** Added drag ability to Positioned Dialog ([issue 23](https://github.com/feduke-nukem/flutter_easy_dialogs/issues/23))
* **FEATURE:** Added draggable ability for the dialogs ([issue 23](https://github.com/feduke-nukem/flutter_easy_dialogs/issues/23))

## 3.1.2
* **FIX:** Fixed "Concurrent modification during iteration" in `hideWhere` #20
Expand Down
52 changes: 21 additions & 31 deletions example/lib/positioned/screens/positioned_dialog_basic_usage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ class _PositionedDialogManagerBasicUsageScreenState
EasyDialogPosition _selectedPosition = EasyDialogPosition.top;
late var _selectedDismissible = _dismissibles.values.first;
var _isAutoHide = false;
var _isDraggable = false;
var _autoHideDuration = 300.0;

@override
Expand Down Expand Up @@ -141,15 +140,6 @@ class _PositionedDialogManagerBasicUsageScreenState
),
],
),
CheckboxListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 20.0,
vertical: 5.0,
),
title: const Text('Draggable'),
value: _isDraggable,
onChanged: (value) => setState(() => _isDraggable = value!),
),
CheckboxListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 20.0,
Expand Down Expand Up @@ -195,31 +185,31 @@ class _PositionedDialogManagerBasicUsageScreenState
Future<void> _show() async {
final messenger = ScaffoldMessenger.of(context);

final result = await FlutterEasyDialogs.show<int>(
EasyDialog.positioned(
isDraggable: _isDraggable,
position: _selectedPosition,
decoration: const PositionedShell.banner()
.chained(_selectedAnimation)
.chained(_selectedDismissible),
autoHideDuration: _isAutoHide
? Duration(milliseconds: _autoHideDuration.toInt())
: null,
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 = 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 result = await content
.positioned(
position: _selectedPosition,
autoHideDuration: _isAutoHide
? Duration(milliseconds: _autoHideDuration.toInt())
: null,
)
.decorate(const PositionedShell.banner())
.decorate(_selectedAnimation)
.decorate(_selectedDismissible)
.show();

if (result == null) return;
messenger
..clearSnackBars()
Expand Down
14 changes: 11 additions & 3 deletions lib/src/core/easy_dialogs_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

import 'package:flutter_easy_dialogs/flutter_easy_dialogs.dart';
import 'package:flutter_easy_dialogs/src/core/widget/free_positioned.dart';

import 'core.dart';

Expand Down Expand Up @@ -349,7 +350,6 @@ abstract base class EasyDialog
/// Shortcut for [PositionedDialog].
factory EasyDialog.positioned({
required Widget content,
bool isDraggable,
EasyDialogPosition position,
EasyDialogAnimationConfiguration animationConfiguration,
EasyDialogDecoration<EasyDialog> decoration,
Expand Down Expand Up @@ -736,6 +736,16 @@ extension EasyDialogsX on EasyDialog {
),
);
}

EasyDialog draggable() {
return decorate(
EasyDialogDecoration.builder(
(context, dialog) => FreePositioned(
child: dialog.content,
),
),
);
}
}

/// {@category Getting started}
Expand All @@ -747,12 +757,10 @@ extension EasyDialogWidgetX on Widget {
PositionedDialog.defaultAnimationConfiguration,
Duration? autoHideDuration = PositionedDialog.defaultAutoHideDuration,
EasyDialogDecoration decoration = const EasyDialogDecoration.none(),
bool isDraggable = false,
}) {
return PositionedDialog(
content: this,
position: position,
isDraggable: isDraggable,
decoration: decoration,
animationConfiguration: animationConfiguration,
autoHideDuration: autoHideDuration,
Expand Down
59 changes: 59 additions & 0 deletions lib/src/core/widget/free_positioned.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import 'package:flutter/widgets.dart';

class FreePositioned extends StatefulWidget {
final Widget child;

const FreePositioned({required this.child});

@override
State<FreePositioned> createState() => _FreePositionedState();
}

class _FreePositionedState extends State<FreePositioned> {
var _x = 0.0;
var _y = 0.0;
AlignmentGeometry? _alignment;

@override
Widget build(BuildContext context) {
final screenSize = MediaQuery.sizeOf(context);

return Stack(
children: [
Positioned(
left: _x,
top: _y,
child: GestureDetector(
onPanUpdate: (details) => setState(
() {
_x += details.delta.dx;
_y += details.delta.dy;
},
),
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,
),
),
),
),
],
);
}

@override
void didChangeDependencies() {
super.didChangeDependencies();

_alignment = context.findAncestorWidgetOfExactType<Align>()?.alignment;
}
}
18 changes: 10 additions & 8 deletions lib/src/flutter_easy_dialogs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,21 @@ final class FlutterEasyDialogs extends StatelessWidget {
EasyDialogIdentifier identifier, {
bool instantly = false,
Object? result,
}) =>
controller.hide(
identifier,
instantly: instantly,
result: result,
);
}) {
return controller.hide(
identifier,
instantly: instantly,
result: result,
);
}

/// {@macro easy_dialogs_controller.hideWhere}
static Future<void> hideWhere<T extends EasyDialog>(
bool Function(T dialog) test, {
bool instantly = false,
}) =>
controller.hideWhere<T>(test, instantly: instantly);
}) {
return controller.hideWhere<T>(test, instantly: instantly);
}

/// For using in [MaterialApp.builder].
static const builder = _builder;
Expand Down
62 changes: 4 additions & 58 deletions lib/src/positioned/dialog/positioned_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,10 @@ final class PositionedDialog extends EasyDialog {
/// The position where the dialog will be shown.
final EasyDialogPosition position;

final bool isDraggable;

/// Creates an instance of [PositionedDialog].
PositionedDialog({
required super.content,
this.position = defaultPosition,
this.isDraggable = false,
super.decoration,
super.animationConfiguration = defaultAnimationConfiguration,
super.autoHideDuration = defaultAutoHideDuration,
Expand All @@ -41,15 +38,10 @@ final class PositionedDialog extends EasyDialog {
EasyOverlayBoxInsertion<EasyDialog> createInsert(Widget decorated) {
return PositionedDialogInsert(
position: position,
dialog: isDraggable
? _FreePositioned(
alignment: position.alignment,
child: decorated,
)
: Align(
alignment: position.alignment,
child: decorated,
),
dialog: Align(
alignment: position.alignment,
child: decorated,
),
);
}

Expand Down Expand Up @@ -133,49 +125,3 @@ final class PositionedDialogRemove
return container.remove(position);
}
}

class _FreePositioned extends StatefulWidget {
final Widget child;
final AlignmentGeometry alignment;

const _FreePositioned({
required this.child,
required this.alignment,
});

@override
State<_FreePositioned> createState() => _FreePositionedState();
}

class _FreePositionedState extends State<_FreePositioned> {
var _x = 0.0;
var _y = 0.0;

@override
Widget build(BuildContext context) {
final screenSize = MediaQuery.sizeOf(context);

return Positioned(
left: _x,
top: _y,
child: GestureDetector(
onPanUpdate: (details) => setState(
() {
_x += details.delta.dx;
_y += details.delta.dy;
},
),
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: screenSize.width,
maxHeight: screenSize.height,
),
child: Align(
alignment: widget.alignment,
child: widget.child,
),
),
),
);
}
}
14 changes: 14 additions & 0 deletions test/src/core/easy_dialog_controller_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,7 @@ void main() {
.fade()
.expansion()
.animatedTap()
.draggable()
.fadeBackground()
.blurBackground()
.tap()
Expand Down Expand Up @@ -524,6 +525,19 @@ void main() {
},
);
});

group('identifier', () {
group('value dialog identifier', () {
test('created instances with same identity equals', () {
final identity = 'test';
final first = ValueDialogIdentifier(identity);
final second = ValueDialogIdentifier(identity);

expect(first, equals(second));
expect(first.hashCode, equals(second.hashCode));
});
});
});
}

final class _TestDecoration extends EasyDialogDecoration<PositionedDialog> {
Expand Down
32 changes: 32 additions & 0 deletions test/src/core/widget/free_positioned_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:flutter_easy_dialogs/src/core/widget/free_positioned.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
testWidgets('drag and position changes', (widgetTester) async {
final key = GlobalKey();
final widget = Container(
height: 100,
width: 100,
color: Colors.red,
key: key,
);
await widgetTester.pumpWidget(
MaterialApp(
home: FreePositioned(child: widget),
),
);

expect(find.byKey(key), findsOneWidget);

final box = key.currentContext!.findRenderObject() as RenderBox;
final positionBeforeDrag = box.localToGlobal(Offset.zero);

await widgetTester.drag(find.byKey(key), const Offset(10, 10));
await widgetTester.pumpAndSettle();

final positionAfterDrag = box.localToGlobal(Offset.zero);

expect(positionBeforeDrag, isNot(equals(positionAfterDrag)));
});
}

0 comments on commit e6d1f32

Please sign in to comment.