mirror of
https://github.com/block/buzz.git
synced 2026-11-05 17:01:35 +00:00
mobile: tap the settings version footer to copy it (#9021)
## Summary The version line at the bottom of mobile Settings (for example `v0.27.0 (36)`) couldn't be copied, which made it awkward to paste into bug reports. Tapping it now copies the whole string and shows a "Version copied" toast, matching the existing "Copy public key" row. It's keyboard-focusable and has a 48dp tap target. Partial selection isn't supported; it's whole-string only.  ### Related issue N/A ### Validation - Added test: tapping the footer, or reaching it with Tab and pressing Enter, puts `v0.16.0 (432)` on the clipboard and shows the toast. The test also checks that the tap target is at least 48dp tall. - `just mobile-check` and `just mobile-test` pass. - Automated simulator run (debug build, iOS 27): opened Settings, tapped the version, confirmed via `simctl pbpaste` that the clipboard held the exact footer text and the toast appeared; then confirmed the neighboring "Copy public key" row still copies the npub and Settings still closes. Human testing waived per the mobile testing exception. A full recording was posted in Buzz instead of here because it shows internal channel names. 🤖 Generated with [Claude Code](https://claude.com/claude-code) buzz-review-completed --------- Signed-off-by: Tom Brow <tomb@squareup.com> Co-authored-by: Tom Brow <tomb@squareup.com> Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -150,15 +150,41 @@ class _VersionFooter extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final label = buildNumber.isEmpty
|
||||
? 'v$version'
|
||||
: 'v$version ($buildNumber)';
|
||||
return SafeArea(
|
||||
top: false,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: Grid.xs, top: Grid.xxs),
|
||||
child: Center(
|
||||
child: Text(
|
||||
buildNumber.isEmpty ? 'v$version' : 'v$version ($buildNumber)',
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: context.colors.onSurfaceVariant.withValues(alpha: 0.6),
|
||||
child: Center(
|
||||
child: Semantics(
|
||||
button: true,
|
||||
onTapHint: 'Copy version',
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(Grid.xs),
|
||||
onTap: () =>
|
||||
copyToClipboard(context, label, message: 'Version copied'),
|
||||
// Keep the text where it was and grow the tap target downward
|
||||
// to the platform minimum.
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
minHeight: kMinInteractiveDimension,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
Grid.xs,
|
||||
Grid.xxs,
|
||||
Grid.xs,
|
||||
Grid.xs,
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: context.colors.onSurfaceVariant.withValues(
|
||||
alpha: 0.6,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -92,6 +92,79 @@ void main() {
|
||||
});
|
||||
}
|
||||
|
||||
for (final viaKeyboard in [false, true]) {
|
||||
testWidgets(
|
||||
'${viaKeyboard ? 'keyboard activation of' : 'tapping'} the version copies the whole version string',
|
||||
(tester) async {
|
||||
String? copied;
|
||||
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
SystemChannels.platform,
|
||||
(call) async {
|
||||
if (call.method == 'Clipboard.setData') {
|
||||
copied = (call.arguments as Map)['text'] as String?;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
addTearDown(
|
||||
() => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||
SystemChannels.platform,
|
||||
null,
|
||||
),
|
||||
);
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
overrides: [savedPrefsProvider.overrideWithValue(prefs)],
|
||||
child: MaterialApp(
|
||||
theme: AppTheme.light(),
|
||||
home: SettingsPage(
|
||||
profileHeader: const SizedBox.shrink(),
|
||||
identityRecoveryPageBuilder: (_) => const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final version = find.text('v0.16.0 (432)');
|
||||
await tester.scrollUntilVisible(version, 200);
|
||||
await tester.pumpAndSettle();
|
||||
final target = find.ancestor(
|
||||
of: version,
|
||||
matching: find.byType(InkWell),
|
||||
);
|
||||
expect(
|
||||
tester.getSize(target).height,
|
||||
greaterThanOrEqualTo(kMinInteractiveDimension),
|
||||
);
|
||||
|
||||
if (viaKeyboard) {
|
||||
final versionFocus = Focus.of(tester.element(version));
|
||||
var tabs = 0;
|
||||
while (!versionFocus.hasPrimaryFocus) {
|
||||
expect(
|
||||
tabs++,
|
||||
lessThan(50),
|
||||
reason: 'version is not reachable by Tab',
|
||||
);
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
|
||||
await tester.pump();
|
||||
}
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
|
||||
} else {
|
||||
await tester.tap(version);
|
||||
}
|
||||
await tester.pump();
|
||||
|
||||
expect(copied, 'v0.16.0 (432)');
|
||||
expect(find.text('Version copied'), findsOneWidget);
|
||||
await tester.pump(const Duration(seconds: 5));
|
||||
await tester.pumpAndSettle();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
for (final outcome in ['absent', 'error']) {
|
||||
testWidgets('hides notifications while capability loads and is $outcome', (
|
||||
tester,
|
||||
|
||||
Reference in New Issue
Block a user