diff --git a/dev/bots/check_code_samples.dart b/dev/bots/check_code_samples.dart index ca65f36464..6cc04b2eaf 100644 --- a/dev/bots/check_code_samples.dart +++ b/dev/bots/check_code_samples.dart @@ -321,6 +321,4 @@ final Set _knownMissingTests = { 'examples/api/test/widgets/interactive_viewer/interactive_viewer.constrained.0_test.dart', 'examples/api/test/widgets/interactive_viewer/interactive_viewer.transformation_controller.0_test.dart', 'examples/api/test/widgets/notification_listener/notification.0_test.dart', - 'examples/api/test/widgets/overscroll_indicator/glowing_overscroll_indicator.1_test.dart', - 'examples/api/test/widgets/overscroll_indicator/glowing_overscroll_indicator.0_test.dart', }; diff --git a/examples/api/lib/widgets/overscroll_indicator/glowing_overscroll_indicator.0.dart b/examples/api/lib/widgets/overscroll_indicator/glowing_overscroll_indicator.0.dart index fc695e47e5..a1f47605c0 100644 --- a/examples/api/lib/widgets/overscroll_indicator/glowing_overscroll_indicator.0.dart +++ b/examples/api/lib/widgets/overscroll_indicator/glowing_overscroll_indicator.0.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; /// Flutter code sample for [GlowingOverscrollIndicator]. @@ -14,6 +15,8 @@ class GlowingOverscrollIndicatorExampleApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + scrollBehavior: const AlwaysGlow(), + theme: ThemeData(colorSchemeSeed: Colors.amber), home: Scaffold( appBar: AppBar(title: const Text('GlowingOverscrollIndicator Sample')), body: const GlowingOverscrollIndicatorExample(), @@ -22,12 +25,37 @@ class GlowingOverscrollIndicatorExampleApp extends StatelessWidget { } } +const Set allPointers = {...PointerDeviceKind.values}; + +// Passing this class into the MaterialApp constructor ensures that a +// GlowingOverscrollIndicator is created, regardless of the target platform. +class AlwaysGlow extends MaterialScrollBehavior { + const AlwaysGlow(); + + @override + Set get dragDevices => allPointers; + + @override + Widget buildOverscrollIndicator( + BuildContext context, + Widget child, + ScrollableDetails details, + ) { + return GlowingOverscrollIndicator( + axisDirection: details.direction, + color: Colors.amberAccent, + child: child, + ); + } +} + class GlowingOverscrollIndicatorExample extends StatelessWidget { const GlowingOverscrollIndicatorExample({super.key}); @override Widget build(BuildContext context) { - final double leadingPaintOffset = MediaQuery.of(context).padding.top + AppBar().preferredSize.height; + final double leadingPaintOffset = MediaQuery.paddingOf(context).top + kToolbarHeight; + return NotificationListener( onNotification: (OverscrollIndicatorNotification notification) { if (notification.leading) { @@ -35,17 +63,33 @@ class GlowingOverscrollIndicatorExample extends StatelessWidget { } return false; }, - child: CustomScrollView( + child: const CustomScrollView( slivers: [ - const SliverAppBar(title: Text('Custom PaintOffset')), + SliverAppBar(title: Text('Custom PaintOffset')), SliverToBoxAdapter( - child: Container( - color: Colors.amberAccent, - height: 100, - child: const Center(child: Text('Glow all day!')), + child: DefaultTextStyle( + style: TextStyle( + color: Colors.amberAccent, + fontSize: 24, + fontWeight: FontWeight.w600, + ), + child: ColoredBox( + color: Colors.grey, + child: SizedBox( + width: double.infinity, + height: 80, + child: Center(child: Text('Glow all day!')), + ), + ), + ), + ), + SliverFillRemaining( + child: Icon( + Icons.sunny, + color: Colors.amberAccent, + size: 128, ), ), - const SliverFillRemaining(child: FlutterLogo()), ], ), ); diff --git a/examples/api/lib/widgets/overscroll_indicator/glowing_overscroll_indicator.1.dart b/examples/api/lib/widgets/overscroll_indicator/glowing_overscroll_indicator.1.dart index a9a5532bf9..7501442b63 100644 --- a/examples/api/lib/widgets/overscroll_indicator/glowing_overscroll_indicator.1.dart +++ b/examples/api/lib/widgets/overscroll_indicator/glowing_overscroll_indicator.1.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; /// Flutter code sample for [GlowingOverscrollIndicator]. @@ -14,6 +15,8 @@ class GlowingOverscrollIndicatorExampleApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + scrollBehavior: const AlwaysGlow(), + theme: ThemeData(colorSchemeSeed: Colors.amber), home: Scaffold( appBar: AppBar(title: const Text('GlowingOverscrollIndicator Sample')), body: const GlowingOverscrollIndicatorExample(), @@ -22,6 +25,31 @@ class GlowingOverscrollIndicatorExampleApp extends StatelessWidget { } } +const Set allPointers = {...PointerDeviceKind.values}; + +// Passing this class into the MaterialApp constructor ensures that a +// GlowingOverscrollIndicator is created, regardless of the target platform. +class AlwaysGlow extends MaterialScrollBehavior { + const AlwaysGlow(); + + @override + Set get dragDevices => allPointers; + + @override + Widget buildOverscrollIndicator( + BuildContext context, + Widget child, + ScrollableDetails details, + ) { + return GlowingOverscrollIndicator( + axisDirection: details.direction, + color: Colors.amberAccent, + child: child, + ); + } +} + + class GlowingOverscrollIndicatorExample extends StatelessWidget { const GlowingOverscrollIndicatorExample({super.key}); @@ -33,16 +61,32 @@ class GlowingOverscrollIndicatorExample extends StatelessWidget { SliverAppBar(title: Text('Custom NestedScrollViews')), ]; }, - body: CustomScrollView( + body: const CustomScrollView( slivers: [ SliverToBoxAdapter( - child: Container( - color: Colors.amberAccent, - height: 100, - child: const Center(child: Text('Glow all day!')), + child: DefaultTextStyle( + style: TextStyle( + color: Colors.amberAccent, + fontSize: 24, + fontWeight: FontWeight.w600, + ), + child: ColoredBox( + color: Colors.grey, + child: SizedBox( + width: double.infinity, + height: 80, + child: Center(child: Text('Glow all day!')), + ), + ), + ), + ), + SliverFillRemaining( + child: Icon( + Icons.sunny, + color: Colors.amberAccent, + size: 128, ), ), - const SliverFillRemaining(child: FlutterLogo()), ], ), ); diff --git a/examples/api/test/widgets/overscroll_indicator/glowing_overscroll_indicator.0_test.dart b/examples/api/test/widgets/overscroll_indicator/glowing_overscroll_indicator.0_test.dart new file mode 100644 index 0000000000..b02678741e --- /dev/null +++ b/examples/api/test/widgets/overscroll_indicator/glowing_overscroll_indicator.0_test.dart @@ -0,0 +1,85 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_api_samples/widgets/overscroll_indicator/glowing_overscroll_indicator.0.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Displays widget tree when the example app is run', (WidgetTester tester) async { + await tester.pumpWidget(const example.GlowingOverscrollIndicatorExampleApp()); + + expect(find.descendant( + of: find.byType(Scaffold), + matching: find.widgetWithText(AppBar, 'GlowingOverscrollIndicator Sample'), + ), findsOne); + + final Finder customScrollViewFinder = find.byType(CustomScrollView); + final Finder sliverAppBarFinder = find.descendant( + of: customScrollViewFinder, + matching: find.widgetWithText(SliverAppBar, 'Custom PaintOffset'), + ); + + expect(sliverAppBarFinder, findsOne); + + expect(find.descendant( + of: customScrollViewFinder, + matching: find.widgetWithText(Center, 'Glow all day!'), + ), findsOne); + + expect(find.descendant( + of: customScrollViewFinder, + matching: find.byType(SliverToBoxAdapter), + ), findsOne); + + expect(find.descendant( + of: customScrollViewFinder, + matching: find.widgetWithIcon(SliverFillRemaining, Icons.sunny), + ), findsOne); + + expect(find.descendant( + of: customScrollViewFinder, + matching: find.byType(GlowingOverscrollIndicator), + ), findsOne); + + // Check if GlowingOverscrollIndicator overlays the SliverAppBar. + final RenderBox overscrollIndicator = tester.renderObject( + find.descendant( + of: customScrollViewFinder, + matching: find.byType(GlowingOverscrollIndicator), + ), + ); + final RenderSliver sliverAppBar = tester.renderObject(sliverAppBarFinder); + final Matrix4 transform = overscrollIndicator.getTransformTo(sliverAppBar); + final Offset? offset = MatrixUtils.getAsTranslation(transform); + expect(offset?.dy, 0); + }); + + testWidgets('Triggers a notification listener when the screen is dragged', (WidgetTester tester) async { + bool overscrollNotified = false; + double leadingPaintOffset = 0.0; + + await tester.pumpWidget( + NotificationListener( + onNotification: (OverscrollIndicatorNotification notification) { + overscrollNotified = true; + leadingPaintOffset = notification.paintOffset; + return false; + }, + child: const example.GlowingOverscrollIndicatorExampleApp(), + ), + ); + + expect(leadingPaintOffset, 0); + expect(overscrollNotified, isFalse); + final BuildContext context = tester.element(find.byType(MaterialApp)); + + await tester.drag(find.byType(CustomScrollView), const Offset(0, 500)); + await tester.pump(); + + expect(leadingPaintOffset, MediaQuery.paddingOf(context).top + kToolbarHeight); + expect(overscrollNotified, isTrue); + }); +} diff --git a/examples/api/test/widgets/overscroll_indicator/glowing_overscroll_indicator.1_test.dart b/examples/api/test/widgets/overscroll_indicator/glowing_overscroll_indicator.1_test.dart new file mode 100644 index 0000000000..baffbcefd8 --- /dev/null +++ b/examples/api/test/widgets/overscroll_indicator/glowing_overscroll_indicator.1_test.dart @@ -0,0 +1,60 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_api_samples/widgets/overscroll_indicator/glowing_overscroll_indicator.1.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Displays widget tree when the example app is run', (WidgetTester tester) async { + await tester.pumpWidget(const example.GlowingOverscrollIndicatorExampleApp()); + + expect(find.descendant( + of: find.byType(Scaffold), + matching: find.widgetWithText(AppBar, 'GlowingOverscrollIndicator Sample'), + ), findsOne); + + expect(find.descendant( + of: find.byType(NestedScrollView), + matching: find.widgetWithText(SliverAppBar, 'Custom NestedScrollViews'), + ), findsOne); + + expect(find.descendant( + of: find.byType(NestedScrollView), + matching: find.widgetWithText(Center, 'Glow all day!'), + ), findsOne); + + final Finder customScrollViewFinder = find.byType(CustomScrollView); + + expect(find.descendant( + of: customScrollViewFinder, + matching: find.byType(SliverToBoxAdapter), + ), findsOne); + + expect(find.descendant( + of: customScrollViewFinder, + matching: find.widgetWithIcon(SliverFillRemaining, Icons.sunny), + ), findsOne); + + expect(find.descendant( + of: customScrollViewFinder, + matching: find.byType(GlowingOverscrollIndicator), + ), findsOne); + + // Check if GlowingOverscrollIndicator starts just after the SliverAppBar. + final RenderBox overscrollIndicator = tester.renderObject( + find.descendant( + of: customScrollViewFinder, + matching: find.byType(GlowingOverscrollIndicator), + ), + ); + final RenderSliver sliverAppBar = tester.renderObject( + find.widgetWithText(SliverAppBar, 'Custom NestedScrollViews'), + ); + final Matrix4 transform = overscrollIndicator.getTransformTo(sliverAppBar); + final Offset? offset = MatrixUtils.getAsTranslation(transform); + expect(offset?.dy, sliverAppBar.geometry?.paintExtent); + }); +}