fix(mobile): make the Join button readable in dark mode (#8969)

## Summary

After scanning or pasting a community invite in dark mode, the native
Join button can display white text on a white background. This fix
passes the theme’s foreground color to native confirmation and applies
it alongside the button’s background color.

### Related issue

Related invite handoff: https://github.com/block/buzz/pull/8601.

### Testing

A widget regression checks the foreground and background colors sent to
native confirmation. The isolated PR build shows readable black Join
text on the white native button in dark mode.

### Screenshots

Before (consent-only build `3d566de`):

![Join label invisible in dark
mode](https://raw.githubusercontent.com/block/buzz/e12a750a31badc9e2a95a18c277b33968c7ec1f5/pr-8969--DARK_JOIN_BEFORE.png)

After (this PR):

![Readable Join label in dark
mode](https://raw.githubusercontent.com/block/buzz/e12a750a31badc9e2a95a18c277b33968c7ec1f5/pr-8969--DARK_JOIN_AFTER.png)

Signed-off-by: John Tennant <jtennant@block.xyz>
Signed-off-by: John Tennant <jtennant@squareup.com>
Co-authored-by: Zack <04f47b290b155b3b8b782b11501cdb15f1e21e82dfceefd01ff0ed405459d927@buzz.block.builderlab.xyz>
Co-authored-by: Aaron <f425b86c3b780d43f8d9cf1802429d04dd985bf92eac44fca9468b5ad3887c5e@buzz.block.builderlab.xyz>
This commit is contained in:
John Matthew Tennant
2026-11-02 21:46:36 -05:00
committed by GitHub
parent 83bd8b1cae
commit 6684f7e2f0
5 changed files with 42 additions and 1 deletions

View File

@@ -260,7 +260,12 @@ private final class NativeCommunityEntryController: UIViewController, UITextFiel
@discardableResult
private func addButton(_ title: String, primary: Bool = false, action: @escaping () -> Void) -> UIButton {
let config = onboardingButtonConfiguration(title, primary: primary)
var config = onboardingButtonConfiguration(title, primary: primary)
if primary, let argb = (configuration["onPrimaryArgb"] as? NSNumber)?.uint32Value {
config.baseBackgroundColor = view.tintColor
config.baseForegroundColor = UIColor(red: CGFloat((argb >> 16) & 255) / 255,
green: CGFloat((argb >> 8) & 255) / 255, blue: CGFloat(argb & 255) / 255, alpha: 1)
}
let button = UIButton(configuration: config, primaryAction: UIAction { _ in action() })
button.titleLabel?.numberOfLines = 0
button.titleLabel?.adjustsFontForContentSizeCategory = true

View File

@@ -20,6 +20,7 @@ Future<bool?> showInviteJoinSheet(BuildContext context) async {
name: state.host ?? 'Unknown community',
dark: Theme.of(context).brightness == Brightness.dark,
primaryArgb: Theme.of(context).colorScheme.primary.toARGB32(),
onPrimaryArgb: Theme.of(context).colorScheme.onPrimary.toARGB32(),
choices: const [],
connect: (_, _, _, _) async {},
confirmInvite: () async {

View File

@@ -62,6 +62,7 @@ Future<bool> showSelfHostedEntry(
),
dark: dark,
primaryArgb: primary,
onPrimaryArgb: context.colors.onPrimary.toARGB32(),
choices: choices,
connect: (pubkey, nsec, create, current) async {
final community = await service.prepare(

View File

@@ -16,6 +16,7 @@ abstract final class NativeCommunityEntry {
required String name,
required bool dark,
required int primaryArgb,
int? onPrimaryArgb,
required List<DeviceIdentityChoice> choices,
Future<Map<String, dynamic>> Function()? confirmInvite,
required Future<void> Function(
@@ -101,6 +102,7 @@ abstract final class NativeCommunityEntry {
'name': name,
'dark': dark,
'primaryArgb': primaryArgb,
'onPrimaryArgb': ?onPrimaryArgb,
'invite': confirmInvite != null,
'choices': [
for (final choice in choices)

View File

@@ -1,3 +1,4 @@
import 'package:buzz/shared/theme/theme.dart';
import 'package:buzz/features/invites/invite_join_provider.dart';
import 'package:buzz/shared/deeplink/deep_link.dart';
import 'package:buzz/features/invites/invite_join_sheet.dart';
@@ -93,6 +94,37 @@ void main() {
expect(find.textContaining('Could not open the browser'), findsOneWidget);
expect(tester.widget<FilledButton>(button).onPressed, isNotNull);
});
testWidgets('native dark Join uses the theme foreground on its accent fill', (
tester,
) async {
const channel = MethodChannel('buzz/community_entry');
final messenger =
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
Map? config;
messenger.setMockMethodCallHandler(channel, (call) async {
config = call.arguments as Map;
return false;
});
addTearDown(() => messenger.setMockMethodCallHandler(channel, null));
final theme = AppTheme.dark();
await tester.pumpWidget(
WidgetHelpers.testable(
child: Theme(data: theme, child: const _InviteJoinSheetLauncher()),
overrides: [
inviteJoinProvider.overrideWith(
() => _StaticInviteJoinNotifier(
const InviteJoinState(status: InviteJoinStatus.confirming),
),
),
],
),
);
await tester.tap(find.text('Open invite'));
await tester.pumpAndSettle();
expect(config?['primaryArgb'], theme.colorScheme.primary.toARGB32());
expect(config?['onPrimaryArgb'], theme.colorScheme.onPrimary.toARGB32());
}, variant: TargetPlatformVariant.only(TargetPlatform.iOS));
testWidgets('recovery error is scrollable and exposes retry setup', (
tester,
) async {