Skip to content

Commit 12a0af6

Browse files
PIG208gnprice
authored andcommitted
emoji: Fix bottom padding of emoji picker
Previously, the body of the bottom sheet was wrapped in `SafeArea`. This pads the bottom unconditionally, shifting the bottom of the list view above the device bottom padding. This is undesirable, because the area beneath the bottom padding is still visible, and the list view should extend to the bottom regardless of the bottom inset. By just removing the `SafeArea`, the list view extends to the bottom. However, if the bottom padding is more than 8px, we can't scroll past the last entry in the list view. Essentially, we want the behavior of `SilverSafeArea.minimum` — the bottom edge of the list entries should always be padded by at least 8px, so that we can scroll past the shadow; and if the bottom padding is more than 8px, use that as the padding instead. Signed-off-by: Zixuan James Li <[email protected]>
1 parent a8c9d14 commit 12a0af6

File tree

3 files changed

+116
-18
lines changed

3 files changed

+116
-18
lines changed

lib/widgets/emoji_reaction.dart

+25-18
Original file line numberDiff line numberDiff line change
@@ -417,20 +417,21 @@ void showEmojiPickerSheet({
417417
// on my iPhone 13 Pro but is marked as "much slower":
418418
// https://api.flutter.dev/flutter/dart-ui/Clip.html
419419
clipBehavior: Clip.antiAlias,
420+
// The bottom inset is left for [builder] to handle;
421+
// see [EmojiPicker] and its [CustomScrollView] for how we do that.
420422
useSafeArea: true,
421423
isScrollControlled: true,
422424
builder: (BuildContext context) {
423-
return SafeArea(
424-
child: Padding(
425-
// By default, when software keyboard is opened, the ListView
426-
// expands behind the software keyboard — resulting in some
427-
// list entries being covered by the keyboard. Add explicit
428-
// bottom padding the size of the keyboard, which fixes this.
429-
padding: EdgeInsets.only(bottom: MediaQuery.viewInsetsOf(context).bottom),
430-
// For _EmojiPickerItem, and RealmContentNetworkImage used in ImageEmojiWidget.
431-
child: PerAccountStoreWidget(
432-
accountId: store.accountId,
433-
child: EmojiPicker(pageContext: pageContext, message: message))));
425+
return Padding(
426+
// By default, when software keyboard is opened, the ListView
427+
// expands behind the software keyboard — resulting in some
428+
// list entries being covered by the keyboard. Add explicit
429+
// bottom padding the size of the keyboard, which fixes this.
430+
padding: EdgeInsets.only(bottom: MediaQuery.viewInsetsOf(context).bottom),
431+
// For _EmojiPickerItem, and RealmContentNetworkImage used in ImageEmojiWidget.
432+
child: PerAccountStoreWidget(
433+
accountId: store.accountId,
434+
child: EmojiPicker(pageContext: pageContext, message: message)));
434435
});
435436
}
436437

@@ -531,13 +532,19 @@ class _EmojiPickerState extends State<EmojiPicker> with PerAccountStoreAwareStat
531532
Expanded(child: InsetShadowBox(
532533
top: 8, bottom: 8,
533534
color: designVariables.bgContextMenu,
534-
child: ListView.builder(
535-
padding: const EdgeInsets.symmetric(vertical: 8),
536-
itemCount: _resultsToDisplay.length,
537-
itemBuilder: (context, i) => EmojiPickerListEntry(
538-
pageContext: widget.pageContext,
539-
emoji: _resultsToDisplay[i].candidate,
540-
message: widget.message)))),
535+
child: CustomScrollView(
536+
slivers: [
537+
SliverPadding(
538+
padding: EdgeInsets.only(top: 8),
539+
sliver: SliverSafeArea(
540+
minimum: EdgeInsets.only(bottom: 8),
541+
sliver: SliverList.builder(
542+
itemCount: _resultsToDisplay.length,
543+
itemBuilder: (context, i) => EmojiPickerListEntry(
544+
pageContext: widget.pageContext,
545+
emoji: _resultsToDisplay[i].candidate,
546+
message: widget.message)))),
547+
]))),
541548
]);
542549
}
543550
}

test/flutter_checks.dart

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ extension PaintChecks on Subject<Paint> {
1111
Subject<Shader?> get shader => has((x) => x.shader, 'shader');
1212
}
1313

14+
extension OffsetChecks on Subject<Offset> {
15+
Subject<double> get dx => has((x) => x.dx, 'dx');
16+
Subject<double> get dy => has((x) => x.dy, 'dy');
17+
}
18+
1419
extension RectChecks on Subject<Rect> {
1520
Subject<double> get left => has((d) => d.left, 'left');
1621
Subject<double> get top => has((d) => d.top, 'top');

test/widgets/emoji_reaction_test.dart

+86
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,92 @@ void main() {
466466

467467
debugNetworkImageHttpClientProvider = null;
468468
});
469+
470+
group('handle view paddings', () {
471+
const screenHeight = 400.0;
472+
473+
late Rect scrollViewRect;
474+
final scrollViewFinder = findInPicker(find.bySubtype<ScrollView>());
475+
476+
Rect getListEntriesRect(WidgetTester tester) =>
477+
tester.getRect(find.byType(EmojiPickerListEntry).first)
478+
.expandToInclude(tester.getRect(find.byType(EmojiPickerListEntry).last));
479+
480+
Future<void> prepare(WidgetTester tester, {
481+
required FakeViewPadding viewPadding,
482+
}) async {
483+
addTearDown(tester.view.reset);
484+
tester.view.physicalSize = Size(640, screenHeight);
485+
// This makes it easier to convert between device pixels used for
486+
// [FakeViewPadding] and logical pixels used in tests.
487+
// If needed, there is a clearer way to implement this generally.
488+
// See comment: https://github.com/zulip/zulip-flutter/pull/1315/files#r1962703436
489+
tester.view.devicePixelRatio = 1.0;
490+
491+
tester.view.viewPadding = viewPadding;
492+
tester.view.padding = viewPadding;
493+
494+
final message = eg.streamMessage();
495+
await setupEmojiPicker(tester,
496+
message: message, narrow: TopicNarrow.ofMessage(message));
497+
498+
scrollViewRect = tester.getRect(scrollViewFinder);
499+
// The scroll view should expand all the way to the bottom of the
500+
// screen, even if there is device bottom padding.
501+
check(scrollViewRect)
502+
..bottom.equals(screenHeight)
503+
// There should always be enough entries to overflow the scroll view.
504+
..height.isLessThan(getListEntriesRect(tester).height);
505+
}
506+
507+
testWidgets('no view padding', (tester) async {
508+
await prepare(tester, viewPadding: FakeViewPadding.zero);
509+
510+
// The top edge of the list entries is padded by 8px from the top edge
511+
// of the scroll view; the bottom edge is out of view.
512+
Rect listEntriesRect = getListEntriesRect(tester);
513+
check(scrollViewRect)
514+
..top.equals(listEntriesRect.top - 8)
515+
..bottom.isLessThan(listEntriesRect.bottom);
516+
517+
// Scroll to the very bottom of the list with a large offset.
518+
await tester.drag(scrollViewFinder, Offset(0, -500));
519+
await tester.pump();
520+
// The top edge of the list entries is out of view;
521+
// the bottom is padded by 8px, the minimum padding, from the bottom
522+
// edge of the scroll view.
523+
listEntriesRect = getListEntriesRect(tester);
524+
check(scrollViewRect)
525+
..top.isGreaterThan(listEntriesRect.top)
526+
..bottom.equals(listEntriesRect.bottom + 8);
527+
528+
debugNetworkImageHttpClientProvider = null;
529+
});
530+
531+
testWidgets('with bottom view padding', (tester) async {
532+
await prepare(tester, viewPadding: FakeViewPadding(bottom: 10));
533+
534+
// The top edge of the list entries is padded by 8px from the top edge
535+
// of the scroll view; the bottom edge is out of view.
536+
Rect listEntriesRect = getListEntriesRect(tester);
537+
check(scrollViewRect)
538+
..top.equals(listEntriesRect.top - 8)
539+
..bottom.isLessThan(listEntriesRect.bottom);
540+
541+
// Scroll to the very bottom of the list with a large offset.
542+
await tester.drag(scrollViewFinder, Offset(0, -500));
543+
await tester.pump();
544+
// The top edge of the list entries is out of view;
545+
// the bottom edge is padded by 10px from the bottom edge of the scroll
546+
// view, because the view bottom padding is larger than the minimum 8px.
547+
listEntriesRect = getListEntriesRect(tester);
548+
check(scrollViewRect)
549+
..top.isGreaterThan(listEntriesRect.top)
550+
..bottom.equals(listEntriesRect.bottom + 10);
551+
552+
debugNetworkImageHttpClientProvider = null;
553+
});
554+
});
469555
});
470556
}
471557

0 commit comments

Comments
 (0)