Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consume scroll notification #13

Merged
merged 3 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/flutter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,7 @@ jobs:
run: dart pub global activate pana

- name: Analyze project source with pana
run: pana
run: pana

- name: Run flutter test
run: flutter test
3 changes: 2 additions & 1 deletion lib/src/scroll_shadow.dart
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@ class _ScrollShadowState extends State<ScrollShadow> {
reachedStart = metrics.pixels <= metrics.minScrollExtent;
reachedEnd = metrics.pixels >= metrics.maxScrollExtent;
_animate = true;
return false;
// Consume the notification to prevent possible ScrollShadow ancestor to interpret them a second timme
return true;
}

Axis? _axis;
Expand Down
2 changes: 1 addition & 1 deletion pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ packages:
source: hosted
version: "1.1.1"
collection:
dependency: transitive
dependency: "direct dev"
description:
name: collection
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ dev_dependencies:
flutter_lints: ^2.0.2
flutter_test:
sdk: flutter
collection: ^1.17.2

flutter: null
157 changes: 157 additions & 0 deletions test/both_axis_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_scroll_shadow/flutter_scroll_shadow.dart';

const _verticalShadowSize = 40.0;
const _verticalShadowColor = Colors.pink;

const _horizontalShadowSize = 15.0;
const _horizontalShadowColor = Colors.green;

class _BothAxisShadow extends StatelessWidget {
const _BothAxisShadow();

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
// Constraint the app size to be sure to have scroll shadows
child: SizedBox(
height: 300,
width: 300,
child: ScrollShadow(
size: _verticalShadowSize,
color: _verticalShadowColor,
child: ListView.builder(itemBuilder: (context, rowIndex) => _MyRow(rowIndex))),
),
),
));
}
}

class _MyRow extends StatelessWidget {
_MyRow(this.rowIndex);

final int rowIndex;
final controller = ScrollController();

@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Text(
'Row $rowIndex',
style: const TextStyle(fontWeight: FontWeight.bold),
),
ScrollShadow(
size: _horizontalShadowSize,
color: _horizontalShadowColor,
child: Scrollbar(
scrollbarOrientation: ScrollbarOrientation.bottom,
thumbVisibility: true,
trackVisibility: true,
controller: controller,
child: SingleChildScrollView(
controller: controller,
scrollDirection: Axis.horizontal,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: List.generate(
20,
(columnIndex) => Padding(
padding: const EdgeInsets.all(8.0),
child: Text('R${rowIndex}C$columnIndex'),
)),
),
),
),
),
),
],
),
),
);
}
}

enum _Direction {
start,
end,
}

AnimatedOpacity _findVerticalShadow(_Direction direction) {
final finder = find.byWidgetPredicate((widget) {
if (widget is! AnimatedOpacity) return false;

final child = widget.child;
if (child is! Container) return false;

if (child.constraints?.maxWidth != 40.0) return false;

// From this point, we know that 'widget' is a shadow created by ScrollShadow
// Every following check throw instead of just returning false

final decoration = child.decoration as BoxDecoration;

final gradient = decoration.gradient as LinearGradient;

expect(gradient.begin, Alignment.bottomCenter);
expect(gradient.end, Alignment.topCenter);

if (const ListEquality<Color>()
.equals(gradient.colors, [_verticalShadowColor.withOpacity(0), _verticalShadowColor])) {
return direction == _Direction.start;
} else if (const ListEquality<Color>()
.equals(gradient.colors, [_verticalShadowColor, _verticalShadowColor.withOpacity(0)])) {
return direction == _Direction.end;
} else {
fail('The gradient colors should be one of the above');
}
});
final elementsFound = finder.evaluate();
expect(elementsFound.length, 1);
return elementsFound.first.widget as AnimatedOpacity;
}

void main() {
testWidgets('Double axis shadow', (tester) async {
await tester.pumpWidget(const _BothAxisShadow());
await tester.pumpAndSettle(const Duration(seconds: 1));

// hitTestable guarantee the widget is visible, and not just in the widget tree
expect(find.text('Row 0').hitTestable(), findsOneWidget);
expect(find.text('R0C0').hitTestable(), findsOneWidget);

// Only bottom shadow
expect(_findVerticalShadow(_Direction.start).opacity, 0.0);
expect(_findVerticalShadow(_Direction.end).opacity, 1.0);

/// Scroll the first row to the right
await tester.fling(find.text('R0C0'), const Offset(-400, 0), 500);
await tester.pumpAndSettle(const Duration(seconds: 1));

expect(find.text('Row 0').hitTestable(), findsOneWidget);
expect(find.text('R0C0').hitTestable(), findsNothing);

// Still only bottom shadow as we scroll the horizontal row
expect(_findVerticalShadow(_Direction.start).opacity, 0.0);
expect(_findVerticalShadow(_Direction.end).opacity, 1.0);

/// Scroll downward
await tester.fling(find.text('Row 0'), const Offset(0, -100), 500);
await tester.pumpAndSettle(const Duration(seconds: 1));

expect(find.text('Row 0').hitTestable(), findsNothing);
expect(find.text('R0C0').hitTestable(), findsNothing);

// Both top and bottom shadow
expect(_findVerticalShadow(_Direction.start).opacity, 1.0);
expect(_findVerticalShadow(_Direction.end).opacity, 1.0);
});
}