Skip to content

Commit

Permalink
fix(UX-1171): ExtendedTopAppBar padding without leading (#157)
Browse files Browse the repository at this point in the history
test: Add basic tests for ExtendedTopAppBar
ci: Update flutter-code-quality action to use main
  • Loading branch information
thelukewalton authored Aug 23, 2024
1 parent 0b18961 commit 280c63d
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 29 deletions.
6 changes: 4 additions & 2 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ jobs:
with:
cache: true
- run: dart run build_runner build --delete-conflicting-outputs
- uses: ZebraDevs/flutter-code-quality@v1.0.8
- uses: ZebraDevs/flutter-code-quality@main
with:
token: ${{secrets.GITHUB_TOKEN}}
token: ${{secrets.GITHUB_TOKEN}}
coverage-pass-score: '1'


check-secret:
runs-on: ubuntu-latest
Expand Down
20 changes: 10 additions & 10 deletions example/lib/pages/components/top_app_bar_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class _TopAppBarExampleState extends State<TopAppBarExample> {
return ExampleScaffold(
name: TopAppBarExample.name,
child: ColoredBox(
color: colors.surfaceSecondary,
color: colors.surfaceWarm,
child: SingleChildScrollView(
child: Column(
children: [
Expand All @@ -57,7 +57,7 @@ class _TopAppBarExampleState extends State<TopAppBarExample> {
children: [
ZetaAvatar(size: ZetaAvatarSize.xs, image: image),
Padding(
padding: const EdgeInsets.only(left: ZetaSpacing.medium),
padding: EdgeInsets.only(left: ZetaSpacing.medium),
child: Text("Title"),
),
],
Expand Down Expand Up @@ -154,7 +154,7 @@ class _TopAppBarExampleState extends State<TopAppBarExample> {
children: [
ZetaAvatar(size: ZetaAvatarSize.xs, image: image),
Padding(
padding: const EdgeInsets.only(left: ZetaSpacing.medium),
padding: EdgeInsets.only(left: ZetaSpacing.medium),
child: Text("Title"),
),
],
Expand All @@ -178,9 +178,9 @@ class _TopAppBarExampleState extends State<TopAppBarExample> {
child: Container(
width: 800,
height: 800,
color: Zeta.of(context).colors.surfaceSecondary,
color: Zeta.of(context).colors.surfaceSelectedHover,
child: CustomPaint(
painter: Painter(colors: colors),
painter: Painter(zeta: Zeta.of(context)),
size: Size(800, 800),
),
),
Expand Down Expand Up @@ -220,9 +220,9 @@ class _TopAppBarExampleState extends State<TopAppBarExample> {
child: Container(
width: 800,
height: 800,
color: Zeta.of(context).colors.surfaceSecondary,
color: Zeta.of(context).colors.surfaceSelectedHover,
child: CustomPaint(
painter: Painter(colors: colors),
painter: Painter(zeta: Zeta.of(context)),
size: Size(800, 800),
),
),
Expand All @@ -239,17 +239,17 @@ class _TopAppBarExampleState extends State<TopAppBarExample> {
}

class Painter extends CustomPainter {
final ZetaColors colors;
final Zeta zeta;

Painter({super.repaint, required this.colors});
Painter({super.repaint, required this.zeta});

@override
void paint(Canvas canvas, Size size) {
for (double i = -760; i < 760; i += 50) {
var p1 = Offset(i, -10);
var p2 = Offset(800 + i, 810);
var paint = Paint()
..color = colors.primary
..color = zeta.colors.surfaceDefault
..strokeWidth = ZetaSpacing.minimum;
canvas.drawLine(p1, p2, paint);
}
Expand Down
40 changes: 23 additions & 17 deletions lib/src/components/top_app_bar/extended_top_app_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,6 @@ import 'package:flutter/material.dart';

import '../../../zeta_flutter.dart';

const _searchBarOffsetTop = ZetaSpacing.minimum * 1.5;
const _searchBarOffsetRight = ZetaSpacing.minimum * 22;
const _maxExtent = ZetaSpacing.minimum * 26;
const _minExtent = ZetaSpacing.xl_9;
const _leftMin = ZetaSpacing.large;
const _leftMax = ZetaSpacingBase.x12_5;
const _topMin = ZetaSpacing.xl_1;
const _topMax = ZetaSpacing.minimum * 15;

/// Delegate for creating an extended app bar, that grows and shrinks when scrolling.
/// {@category Components}
class ZetaExtendedAppBarDelegate extends SliverPersistentHeaderDelegate {
Expand Down Expand Up @@ -38,26 +29,41 @@ class ZetaExtendedAppBarDelegate extends SliverPersistentHeaderDelegate {
/// If `ZetaTopAppBarType.extend` shrinks. Does not affect other types of app bar.
final bool shrinks;

static const double _maxExtent = 104;
static const double _minExtent = 52;

@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
const searchBarOffsetTop = ZetaSpacing.minimum * 1.5;
const searchBarOffsetRight = ZetaSpacing.minimum * 22;
const maxExtent = ZetaSpacing.minimum * 26;
const leftMin = ZetaSpacing.large;

const topMin = ZetaSpacing.xl_1;
const topMax = ZetaSpacing.minimum * 15;

/// If there is no leading widget, the left margin should not change
/// If there is a leading widget, the left margin should be the same as the leading widget's width plus padding
final leftMax = leading == null ? leftMin : _minExtent + ZetaSpacing.small;

return ConstrainedBox(
constraints: const BoxConstraints(minHeight: ZetaSpacing.xl_9, maxHeight: _maxExtent),
constraints: const BoxConstraints(minHeight: ZetaSpacing.xl_9, maxHeight: maxExtent),
child: ColoredBox(
color: Zeta.of(context).colors.surfacePrimary,
child: Stack(
children: [
Positioned(
top: shrinks
? (_topMax + (-1 * shrinkOffset)).clamp(
_topMin -
? (topMax + (-1 * shrinkOffset)).clamp(
topMin -
(searchController != null && searchController!.isEnabled
? _searchBarOffsetTop
? searchBarOffsetTop
: ZetaSpacing.none),
_topMax,
topMax,
)
: _topMax,
left: shrinks ? ((shrinkOffset / _maxExtent) * ZetaSpacingBase.x50).clamp(_leftMin, _leftMax) : _leftMin,
right: searchController != null && searchController!.isEnabled ? _searchBarOffsetRight : ZetaSpacing.none,
: topMax,
left: shrinks ? ((shrinkOffset / maxExtent) * ZetaSpacingBase.x50).clamp(leftMin, leftMax) : leftMin,
right: searchController != null && searchController!.isEnabled ? searchBarOffsetRight : ZetaSpacing.none,
child: title,
),
if (leading != null) Positioned(top: ZetaSpacing.medium, left: ZetaSpacing.small, child: leading!),
Expand Down
169 changes: 169 additions & 0 deletions test/src/components/top_app_bar/extended_top_app_bar_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:path/path.dart';
import 'package:zeta_flutter/zeta_flutter.dart';

import '../../../test_utils/test_app.dart';
import '../../../test_utils/tolerant_comparator.dart';
import '../../../test_utils/utils.dart';

void main() {
setUpAll(() {
final testUri = Uri.parse(getCurrentPath('top_app_bar'));
goldenFileComparator = TolerantComparator(testUri, tolerance: 0.01);
});

testWidgets('ZetaExtendedAppBarDelegate builds correctly', (WidgetTester tester) async {
const title = Text('Title');
final actions = [IconButton(icon: const Icon(Icons.search), onPressed: () {})];
final leading = IconButton(icon: const Icon(Icons.menu), onPressed: () {});
const boxKey = Key('box');
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(481, 480);

await tester.pumpWidget(
TestApp(
home: Builder(
builder: (context) {
return SizedBox(
child: CustomScrollView(
slivers: [
ZetaTopAppBar.extended(leading: leading, title: title, actions: actions),
SliverToBoxAdapter(
child: Container(
width: 800,
height: 700,
color: Zeta.of(context).colors.surfaceSelectedHover,
key: boxKey,
),
),
],
),
);
},
),
),
);

final boxFinder = find.byKey(boxKey);
expect(boxFinder, findsOneWidget);

await tester.drag(boxFinder.first, const Offset(0, -100));
await tester.pumpAndSettle();

final appBarFinder = find.byType(ZetaTopAppBar);
expect(appBarFinder, findsOneWidget);

final titleFinder = find.descendant(of: appBarFinder, matching: find.byWidget(title));
expect(titleFinder, findsOneWidget);

final actionsFinder = find.descendant(of: appBarFinder, matching: find.byWidget(actions[0]));
expect(actionsFinder, findsOneWidget);

final leadingFinder = find.descendant(of: appBarFinder, matching: find.byWidget(leading));
expect(leadingFinder, findsOneWidget);

await expectLater(
appBarFinder,
matchesGoldenFile(join(getCurrentPath('top_app_bar'), 'extended_app_bar_shrinks.png')),
);
});

testWidgets('ZetaExtendedAppBarDelegate shrinks correctly with padding', (WidgetTester tester) async {
const title = Text('Title');
final actions = [IconButton(icon: const Icon(Icons.search), onPressed: () {})];
final leading = IconButton(icon: const Icon(Icons.menu), onPressed: () {});
const boxKey = Key('box');
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(481, 480);

await tester.pumpWidget(
TestApp(
home: Builder(
builder: (context) {
return SizedBox(
child: CustomScrollView(
slivers: [
ZetaTopAppBar.extended(leading: leading, title: title, actions: actions),
SliverToBoxAdapter(
child: Container(
width: 800,
height: 700,
color: Zeta.of(context).colors.surfaceSelectedHover,
key: boxKey,
),
),
],
),
);
},
),
),
);

final boxFinder = find.byKey(boxKey);
expect(boxFinder, findsOneWidget);

await tester.drag(boxFinder.first, const Offset(0, -100));
await tester.pumpAndSettle();

final appBarFinder = find.byType(ZetaTopAppBar);
expect(appBarFinder, findsOneWidget);

final positionedFinder = find.descendant(of: appBarFinder, matching: find.byType(Positioned));

final positionedWidget = tester.widget<Positioned>(positionedFinder.first);
expect(positionedWidget.left, 60);
});
testWidgets('ZetaExtendedAppBarDelegate shrinks correctly with padding and no leading', (WidgetTester tester) async {
const title = Text('Title');
final actions = [IconButton(icon: const Icon(Icons.search), onPressed: () {})];

const boxKey = Key('box');
tester.view.devicePixelRatio = 1.0;
tester.view.physicalSize = const Size(481, 480);

await tester.pumpWidget(
TestApp(
home: Builder(
builder: (context) {
return SizedBox(
child: CustomScrollView(
slivers: [
ZetaTopAppBar.extended(title: title, actions: actions),
SliverToBoxAdapter(
child: Container(
width: 800,
height: 700,
color: Zeta.of(context).colors.surfaceSelectedHover,
key: boxKey,
),
),
],
),
);
},
),
),
);

final boxFinder = find.byKey(boxKey);
expect(boxFinder, findsOneWidget);

await tester.drag(boxFinder.first, const Offset(0, -100));
await tester.pumpAndSettle();

final appBarFinder = find.byType(ZetaTopAppBar);
expect(appBarFinder, findsOneWidget);

final positionedFinder = find.descendant(of: appBarFinder, matching: find.byType(Positioned));

final positionedWidget = tester.widget<Positioned>(positionedFinder.first);
expect(positionedWidget.left, 16);

await expectLater(
appBarFinder,
matchesGoldenFile(join(getCurrentPath('top_app_bar'), 'extended_app_bar_shrinks_with_no_leading.png')),
);
});
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 280c63d

Please sign in to comment.