mirror of
https://github.com/block/buzz.git
synced 2026-10-28 21:11:48 +00:00
## Summary Replace message long-press popovers with the shared grouped bottom sheet: an emoji row, Reply / Copy link / Remind me tiles, and remaining actions in regular sections. Match emoji backgrounds to the sections and use the theme primary color for the selected reaction. Keep the scrolling date above the navigation blur in channels and threads. Restore composer focus when the sheet is canceled, keep section gaps consistent, and soften shared dividers to 7% opacity. The reaction details sheet shows all reactors by default. Emoji count chips toggle your own reaction, show your selected state, and update live without dismissing the sheet. Unselected reactions have visible outlines; adding a supported emoji plays the chat-style burst and respects reduced motion. ### Related issue No matching issue found. Related sheet styling: #4911. ### Testing - Full mobile checks and tests passed, including 23 reaction tests and composer-focus regression coverage. - Analysis and file-size checks passed; signed iOS build installed and launched on a physical iPhone. - Full `just ci` stopped during Rust compilation due to low disk space. - On-device visual capture unavailable. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com>
125 lines
3.8 KiB
Dart
125 lines
3.8 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:buzz/shared/theme/theme.dart';
|
|
import 'package:buzz/shared/widgets/app_list.dart';
|
|
import 'package:buzz/shared/widgets/app_list_card.dart';
|
|
|
|
Widget _host(Widget child) => MaterialApp(
|
|
theme: AppTheme.light(),
|
|
home: Scaffold(body: child),
|
|
);
|
|
|
|
void main() {
|
|
group('AppListRow', () {
|
|
testWidgets('centres the leading icon against a two-line row', (
|
|
tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
_host(
|
|
const AppListRow(
|
|
icon: Icons.dns,
|
|
title: 'Connected to',
|
|
subtitle: 'https://relay.example.com',
|
|
),
|
|
),
|
|
);
|
|
|
|
final row = tester.getRect(find.byType(AppListRow));
|
|
final icon = tester.getRect(find.byType(Icon));
|
|
expect(icon.center.dy, closeTo(row.center.dy, 0.5));
|
|
});
|
|
|
|
testWidgets('centres the trailing control against a two-line row', (
|
|
tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
_host(
|
|
const AppListRow(
|
|
icon: Icons.key,
|
|
title: 'Identity',
|
|
subtitle: 'deadbeef',
|
|
trailing: Icon(Icons.copy, key: Key('trailing')),
|
|
),
|
|
),
|
|
);
|
|
|
|
final row = tester.getRect(find.byType(AppListRow));
|
|
final trailing = tester.getRect(find.byKey(const Key('trailing')));
|
|
expect(trailing.center.dy, closeTo(row.center.dy, 0.5));
|
|
});
|
|
|
|
testWidgets('keeps the value flush against the trailing edge', (
|
|
tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
_host(
|
|
const AppListRow(
|
|
icon: Icons.palette,
|
|
title: 'Theme',
|
|
value: 'Buzz',
|
|
trailing: Icon(Icons.chevron_right, key: Key('chevron')),
|
|
),
|
|
),
|
|
);
|
|
|
|
final value = tester.getRect(find.text('Buzz'));
|
|
final chevron = tester.getRect(find.byKey(const Key('chevron')));
|
|
// Only the row's own inter-widget gap sits between them — no flex slack.
|
|
expect(chevron.left - value.right, closeTo(Grid.xxs, 0.5));
|
|
});
|
|
});
|
|
|
|
group('AppListCard', () {
|
|
testWidgets('separates rows with dividers that clear the icon column', (
|
|
tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
_host(
|
|
const AppListCard(
|
|
label: 'Style',
|
|
children: [
|
|
AppListRow(icon: Icons.sunny, title: 'Appearance'),
|
|
AppListRow(icon: Icons.palette, title: 'Theme'),
|
|
AppListRow(icon: Icons.water_drop, title: 'Accent color'),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
final dividers = find.byType(Divider);
|
|
expect(dividers, findsNWidgets(2));
|
|
|
|
final divider = tester.widget<Divider>(dividers.first);
|
|
final scheme = AppTheme.light().colorScheme;
|
|
// Derived from the text color, not the scheme's border tokens — those sit
|
|
// within a few levels of the card fill and vanish.
|
|
expect(divider.color, scheme.onSurface.withValues(alpha: 0.07));
|
|
|
|
// The indent is painted inside the divider's box, so the line starts at
|
|
// the box's left edge plus the indent — level with the row titles.
|
|
final titleLeft = tester.getRect(find.text('Appearance')).left;
|
|
final lineLeft = tester.getRect(dividers.first).left + divider.indent!;
|
|
expect(lineLeft, closeTo(titleLeft, 0.5));
|
|
});
|
|
|
|
testWidgets('fills with the softest container step', (tester) async {
|
|
await tester.pumpWidget(
|
|
_host(
|
|
const AppListCard(children: [AppListRow(title: 'Remove community')]),
|
|
),
|
|
);
|
|
|
|
final material = tester.widget<Material>(
|
|
find.descendant(
|
|
of: find.byType(AppListCard),
|
|
matching: find.byType(Material),
|
|
),
|
|
);
|
|
expect(
|
|
material.color,
|
|
AppTheme.light().colorScheme.surfaceContainerHighest,
|
|
);
|
|
});
|
|
});
|
|
}
|