Skip to content

Commit

Permalink
Fix: Cupertino style modal transition not working with NavigationSheet (
Browse files Browse the repository at this point in the history
#182)

Fixes #168.

The `SheetExtentScope` has been modified to no longer rely on an
inherited sheet controller as a fallback. Instead, the sheet controller
is now explicitly provided in the constructor of `SheetExtentScope` when
necessary.

Additional changes:
- Added a new example demonstrating an iOS-style modal NavigationSheet
with go_router.
- Added  a regression test for the issue.
- Added more tests to cover the functionality around inheriting a sheet
controller.
- Bumped the version number to 0.8.1.
  • Loading branch information
fujidaiti authored Jun 22, 2024
1 parent eb029fd commit c582cbd
Show file tree
Hide file tree
Showing 13 changed files with 253 additions and 6 deletions.
7 changes: 7 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@
"program": "lib/tutorial/draggable_sheet.dart",
"cwd": "./cookbook"
},
{
"name": "iOS-Style Modal Navigation Sheet (with go_router)",
"request": "launch",
"type": "dart",
"program": "lib/tutorial/ios_style_declarative_modal_navigation_sheet.dart",
"cwd": "./cookbook"
},
{
"name": "Declarative Navigation Sheet",
"request": "launch",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:smooth_sheets/smooth_sheets.dart';

/// Example code of iOS style modal `NavigationSheet` with go_router.
void main() {
runApp(const _App());
}

final transitionObserver = NavigationSheetTransitionObserver();

final router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) {
return const _Home();
},
routes: [
ShellRoute(
observers: [transitionObserver],
pageBuilder: (context, state, child) {
return CupertinoModalSheetPage(
key: state.pageKey,
child: _Modal(nestedNavigator: child),
);
},
routes: [
GoRoute(
path: 'modal',
pageBuilder: (context, state) {
return DraggableNavigationSheetPage(
key: state.pageKey,
child: Container(color: Colors.white),
);
},
),
],
)
]),
],
);

class _App extends StatelessWidget {
const _App();

@override
Widget build(BuildContext context) {
return MaterialApp.router(routerConfig: router);
}
}

class _Home extends StatelessWidget {
const _Home();

@override
Widget build(BuildContext context) {
return CupertinoStackedTransition(
cornerRadius: Tween(begin: 0, end: 20),
child: Scaffold(
body: Center(
child: TextButton(
onPressed: () => context.go('/modal'),
child: const Text('Show Sheet'),
),
),
),
);
}
}

class _Modal extends StatelessWidget {
const _Modal({
required this.nestedNavigator,
});

final Widget nestedNavigator;

@override
Widget build(BuildContext context) {
return NavigationSheet(
transitionObserver: transitionObserver,
child: Material(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
clipBehavior: Clip.antiAlias,
child: nestedNavigator,
),
);
}
}
4 changes: 4 additions & 0 deletions package/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 0.8.1 Jun 23, 2024

- Fix: Cupertino style modal transition not working with NavigationSheet (#182)

## 0.8.0 Jun 22, 2024

This version contains some breaking changes. See the [migration guide](https://github.com/fujidaiti/smooth_sheets/blob/main/docs/migration-guide-0.8.x.md) for more details.
Expand Down
1 change: 1 addition & 0 deletions package/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ See also:
- [declarative_modal_sheet.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/cookbook/lib/tutorial/declarative_modal_sheet.dart), a tutorial of integration with declarative navigation using [go_router](https://pub.dev/packages/go_router) package.
- [imperative_modal_sheet.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/cookbook/lib/tutorial/imperative_modal_sheet.dart), a tutorial of integration with imperative Navigator API.
- [cupertino_modal_sheet.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/cookbook/lib/tutorial/cupertino_modal_sheet.dart), a tutorial of iOS style modal sheets.
- [ios_style_declarative_modal_navigation_sheet.dart](https://github.com/fujidaiti/smooth_sheets/blob/main/cookbook/lib/tutorial/ios_style_declarative_modal_navigation_sheet.dart), an example of iOS-style modal NavigationSheet with go_router.
- [showcase/todo_list](https://github.com/fujidaiti/smooth_sheets/blob/main/cookbook/lib/showcase/todo_list), which uses SheetDismissible to show a confirmation dialog when the user tries to discard the todo editing sheet without saving the content.

<br/>
Expand Down
1 change: 1 addition & 0 deletions package/lib/src/draggable/draggable_sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class DraggableSheet extends StatelessWidget {
final keyboardDismissBehavior =
this.keyboardDismissBehavior ?? theme?.keyboardDismissBehavior;
final gestureTamper = TamperSheetGesture.maybeOf(context);
final controller = this.controller ?? SheetControllerScope.maybeOf(context);

Widget result = DraggableSheetExtentScope(
controller: controller,
Expand Down
3 changes: 3 additions & 0 deletions package/lib/src/foundation/sheet_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ class SheetController extends ChangeNotifier

SheetStatus? get status => _client?.status;

/// Whether a [SheetExtent] is attached to this controller.
bool get hasClient => _client != null;

@override
void addListener(VoidCallback listener, {bool fireImmediately = false}) {
if (fireImmediately) {
Expand Down
6 changes: 2 additions & 4 deletions package/lib/src/foundation/sheet_extent_scope.dart
Original file line number Diff line number Diff line change
Expand Up @@ -206,11 +206,9 @@ abstract class SheetExtentScopeState<E extends SheetExtent,
}

void _rewireControllerAndScope() {
final newController =
widget.controller ?? SheetControllerScope.maybeOf(context);
if (_controller != newController) {
if (_controller != widget.controller) {
_controller?.detach(_extent);
_controller = newController?..attach(_extent);
_controller = widget.controller?..attach(_extent);
}
}

Expand Down
4 changes: 3 additions & 1 deletion package/lib/src/navigation/navigation_sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,12 @@ class _NavigationSheetState extends State<NavigationSheet>
final keyboardDismissBehavior =
widget.keyboardDismissBehavior ?? theme?.keyboardDismissBehavior;
final gestureTamper = TamperSheetGesture.maybeOf(context);
final controller =
widget.controller ?? SheetControllerScope.maybeOf(context);

Widget result = NavigationSheetExtentScope(
key: _scopeKey,
controller: widget.controller,
controller: controller,
gestureTamperer: gestureTamper,
debugLabel: kDebugMode ? 'NavigationSheet' : null,
child: NavigationSheetViewport(
Expand Down
1 change: 1 addition & 0 deletions package/lib/src/scrollable/scrollable_sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class ScrollableSheet extends StatelessWidget {
final keyboardDismissBehavior =
this.keyboardDismissBehavior ?? theme?.keyboardDismissBehavior;
final gestureTamper = TamperSheetGesture.maybeOf(context);
final controller = this.controller ?? SheetControllerScope.maybeOf(context);

Widget result = ScrollableSheetExtentScope(
controller: controller,
Expand Down
2 changes: 1 addition & 1 deletion package/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: smooth_sheets
description: Sheet widgets with smooth motion and great flexibility. Also supports nested navigation in both imperative and declarative ways.
version: 0.8.0
version: 0.8.1
repository: https://github.com/fujidaiti/smooth_sheets
screenshots:
- description: Practical examples of smooth_sheets.
Expand Down
49 changes: 49 additions & 0 deletions package/test/draggable/draggable_sheet_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:smooth_sheets/smooth_sheets.dart';
import 'package:smooth_sheets/src/foundation/sheet_controller.dart';

class _TestWidget extends StatelessWidget {
const _TestWidget({
this.contentKey,
this.contentHeight = 500,
});

final Key? contentKey;
final double contentHeight;

@override
Widget build(BuildContext context) {
final content = Container(
key: contentKey,
color: Colors.white,
height: contentHeight,
width: double.infinity,
);

return Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: DraggableSheet(
child: content,
),
),
);
}
}

void main() {
testWidgets('Inherited controller should be attached', (tester) async {
final controller = SheetController();
await tester.pumpWidget(
SheetControllerScope(
controller: controller,
child: const _TestWidget(),
),
);

expect(controller.hasClient, isTrue,
reason: 'The controller should have a client.');
});
}
27 changes: 27 additions & 0 deletions package/test/navigation/navigation_sheet_test.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:smooth_sheets/smooth_sheets.dart';
import 'package:smooth_sheets/src/foundation/sheet_controller.dart';

class _TestWidget extends StatelessWidget {
const _TestWidget(
Expand Down Expand Up @@ -244,6 +245,32 @@ void main() {
},
);

// Regression test for https://github.com/fujidaiti/smooth_sheets/issues/168
testWidgets('Inherited controller should be attached', (tester) async {
final controller = SheetController();
await tester.pumpWidget(
SheetControllerScope(
controller: controller,
child: _TestWidget(
transitionObserver,
initialRoute: 'first',
routes: {
'first': () => _TestDraggablePageWidget.createRoute(
key: const Key('First'),
label: 'First',
height: 300,
minExtent: const Extent.pixels(0),
physics: const ClampingSheetPhysics(),
),
},
),
),
);

expect(controller.hasClient, isTrue,
reason: 'The controller should have a client.');
});

// Regression test for https://github.com/fujidaiti/smooth_sheets/issues/139
testWidgets(
'Works with DropdownButton without crashing',
Expand Down
62 changes: 62 additions & 0 deletions package/test/scrollable/scrollable_sheet_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:smooth_sheets/smooth_sheets.dart';
import 'package:smooth_sheets/src/foundation/sheet_controller.dart';

class _TestWidget extends StatelessWidget {
const _TestWidget({
this.contentKey,
this.contentHeight,
this.scrollPhysics = const ClampingScrollPhysics(),
});

final Key? contentKey;
final double? contentHeight;
final ScrollPhysics scrollPhysics;

@override
Widget build(BuildContext context) {
Widget content = Material(
color: Colors.white,
child: ListView(
key: contentKey,
physics: scrollPhysics,
children: List.generate(
30,
(index) => ListTile(
title: Text('Item $index'),
),
),
),
);

if (contentHeight case final height?) {
content = SizedBox(height: height, child: content);
}

return Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: ScrollableSheet(
child: content,
),
),
);
}
}

void main() {
testWidgets('Inherited controller should be attached', (tester) async {
final controller = SheetController();
await tester.pumpWidget(
SheetControllerScope(
controller: controller,
child: const _TestWidget(),
),
);

expect(controller.hasClient, isTrue,
reason: 'The controller should have a client.');
});
}

0 comments on commit c582cbd

Please sign in to comment.