mirror of
https://github.com/block/buzz.git
synced 2026-10-28 21:11:48 +00:00
Make QR scanning the primary onboarding action, with full-width contrasting buttons inside the existing gutters. The landing offers **Scan a QR code** and **or sign in with email**. A two-finger double tap on the open welcome area reveals **Enter a community link** and **Use a pairing code**; a screen-reader action provides the same access. Action buttons remain outside double-tap detection so scanner taps respond immediately. Pending sign-in keeps its cancellation action. The Dynamic Island scanner uses 230 ms opening and closing springs. Welcome content fades and scales with the same progress, camera startup begins on the first frame, the scanner prompt fades with the camera reveal, the live preview adds a 230 ms fade when camera startup completes, and opening gives selection haptics. Native iOS status indicators request a matching 230 ms fade, returning when dismissal begins. Reduced motion is respected. Validation: full mobile suites and analysis run through local checks and pre-push hooks. The 230 ms iOS Release build was signed, installed, and launched in the separate Onboarding app on iPhone Air. The owner reviewed the simplified landing and immediate tap response; the latest 230 ms timing is installed for comparison. The latest 230 ms build, including the prompt fade, is installed and launched for comparison. Native status-bar timing has not been independently measured on-device. Product intent confirmed by Kenny: the supported visible onboarding choices are email sign-in or QR scanning. Camera permission/startup failure intentionally does not expose manual pairing or community-link recovery. Those remain hidden developer options, with no keyboard path by design. --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz>
127 lines
3.9 KiB
Dart
127 lines
3.9 KiB
Dart
import 'package:buzz/shared/hosted/hosted_environment.dart';
|
|
import 'dart:async';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:buzz/shared/community/paired_community_landing.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:buzz/app.dart';
|
|
import 'package:buzz/features/age_gate/age_signal_provider.dart';
|
|
import 'package:buzz/shared/auth/auth.dart';
|
|
import 'package:buzz/shared/theme/theme_provider.dart';
|
|
|
|
void main() {
|
|
testWidgets('pairing loading cover blocks hidden input and semantics', (
|
|
tester,
|
|
) async {
|
|
SharedPreferences.setMockInitialValues({});
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final container = ProviderContainer(
|
|
overrides: [
|
|
authProvider.overrideWith(() => _FakeAuthNotifier()),
|
|
ageSignalProvider.overrideWith(() => _AllowedAgeSignalNotifier()),
|
|
savedPrefsProvider.overrideWithValue(prefs),
|
|
],
|
|
);
|
|
addTearDown(container.dispose);
|
|
final semantics = tester.ensureSemantics();
|
|
String semanticsTree() => tester
|
|
.binding
|
|
.renderViews
|
|
.single
|
|
.owner!
|
|
.semanticsOwner!
|
|
.rootSemanticsNode!
|
|
.toStringDeep();
|
|
await tester.pumpWidget(
|
|
UncontrolledProviderScope(container: container, child: const App()),
|
|
);
|
|
await tester.pump();
|
|
var taps = 0;
|
|
final navigator = tester.state<NavigatorState>(
|
|
find.byType(Navigator).first,
|
|
);
|
|
unawaited(
|
|
navigator.push(
|
|
MaterialPageRoute<void>(
|
|
builder: (_) => Scaffold(
|
|
body: Align(
|
|
alignment: Alignment.topLeft,
|
|
child: TextButton(
|
|
onPressed: () => taps++,
|
|
child: const Text('Hidden action'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
final button = find.text('Hidden action');
|
|
final location = tester.getCenter(button);
|
|
expect(semanticsTree(), contains('Hidden action'));
|
|
container
|
|
.read(pairedCommunityLandingProvider.notifier)
|
|
.request(
|
|
Community(
|
|
id: 'test',
|
|
addedAt: DateTime(2026),
|
|
name: 'Test',
|
|
relayUrl: 'https://relay.test',
|
|
),
|
|
);
|
|
await tester.pump();
|
|
await tester.tapAt(location);
|
|
expect(taps, 0);
|
|
expect(semanticsTree(), isNot(contains('Hidden action')));
|
|
container.read(pairedCommunityLandingProvider.notifier).clear();
|
|
await tester.pump();
|
|
expect(semanticsTree(), contains('Hidden action'));
|
|
await tester.tapAt(location);
|
|
expect(taps, 1);
|
|
await tester.pumpWidget(const SizedBox.shrink());
|
|
semantics.dispose();
|
|
});
|
|
|
|
testWidgets('App renders pairing page when unauthenticated', (
|
|
WidgetTester tester,
|
|
) async {
|
|
SharedPreferences.setMockInitialValues({});
|
|
final prefs = await SharedPreferences.getInstance();
|
|
|
|
await tester.pumpWidget(
|
|
ProviderScope(
|
|
overrides: [
|
|
authProvider.overrideWith(() => _FakeAuthNotifier()),
|
|
ageSignalProvider.overrideWith(() => _AllowedAgeSignalNotifier()),
|
|
savedPrefsProvider.overrideWithValue(prefs),
|
|
],
|
|
child: const App(),
|
|
),
|
|
);
|
|
await tester.pump();
|
|
expect(find.bySemanticsLabel('Buzz'), findsOneWidget);
|
|
expect(find.text('Sign in with email').hitTestable(), findsNothing);
|
|
expect(
|
|
find.text('or sign in with email'),
|
|
HostedEnvironment.enabled ? findsOneWidget : findsNothing,
|
|
);
|
|
expect(find.text('Scan a QR code'), findsOneWidget);
|
|
});
|
|
}
|
|
|
|
class _AllowedAgeSignalNotifier extends AgeSignalNotifier {
|
|
@override
|
|
AgeSignalState build() => AgeSignalState.allowed;
|
|
|
|
@override
|
|
Future<void> request() async {}
|
|
}
|
|
|
|
class _FakeAuthNotifier extends AuthNotifier {
|
|
@override
|
|
Future<AuthState> build() async {
|
|
return const AuthState(status: AuthStatus.unauthenticated);
|
|
}
|
|
}
|