mirror of
https://github.com/block/buzz.git
synced 2026-10-31 06:21:34 +00:00
## Summary Use consistent fullscreen screens for identity setup, community selection, joining, and Add Community. Restore all memberships and open the selected community; share the community grid with the switcher. Add QR-first choices, animated link entry with 12px keyboard clearance, a two-finger double-tap pairing toggle, and the centered reversing bee loader. Preserve cancellation, Back, and reduced-motion behavior. Add intentionally omits community creation and Builderlab sign-in: this route follows sign-in and the existing-community picker. ### Related issue Related: #8628 (native profile setup). No duplicate fullscreen onboarding PR found. ### Testing - Full standard and hosted-authentication mobile suites, static analysis, and required push checks passed. - iPhone preview iterated with Kenny; final simulator walkthrough below uses synthetic data and does not authenticate or save account changes. - Independent review completed; regressions cover cancellation during loading, Add recovery after failed/conflicting memberships, retrying skipped communities, and explicit picker stages. Walkthrough recorded before the copy and recovery followups: https://github.com/user-attachments/assets/bfea3ad3-1dde-40c4-8748-44c84226c292 --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Co-authored-by: Terp <5856f6900e8e0a4e0ea1f4f8d2ee33e4753d2119bc122f94ecafc660b46c0511@buzz.block.builderlab.xyz>
189 lines
6.0 KiB
Dart
189 lines
6.0 KiB
Dart
import 'dart:async';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
|
|
import 'features/pairing/pairing_page.dart';
|
|
import 'features/pairing/pairing_provider.dart';
|
|
import 'shared/hosted/hosted_flow_provider.dart';
|
|
import 'shared/hosted/hosted_flow.dart';
|
|
import 'shared/hosted/hosted_environment.dart';
|
|
import 'shared/hosted/hosted_community_creation.dart';
|
|
import 'shared/hosted/hosted_flow_problem_message.dart';
|
|
|
|
/// Navigation over the existing branded welcome surface.
|
|
class OnboardingAppEntry extends HookConsumerWidget {
|
|
const OnboardingAppEntry({
|
|
super.key,
|
|
this.addingCommunity = false,
|
|
this.restoreAccount = true,
|
|
this.onExit,
|
|
});
|
|
final bool addingCommunity;
|
|
final bool restoreAccount;
|
|
final VoidCallback? onExit;
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final existingUser = useState(false);
|
|
final creating = useState(false);
|
|
final enteringCommunityLink = useState(false);
|
|
final flow = HostedEnvironment.enabled
|
|
? ref.watch(hostedFlowProvider)
|
|
: null;
|
|
useListenable(flow);
|
|
useEffect(() {
|
|
if (flow == null || !restoreAccount) return null;
|
|
Future<void> restore() async {
|
|
await flow.initialize();
|
|
if (context.mounted) await flow.signInAccount(openBrowser: false);
|
|
}
|
|
|
|
unawaited(restore());
|
|
return null;
|
|
}, [flow]);
|
|
void finishEntry() {
|
|
if (!addingCommunity) return;
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (!context.mounted) return;
|
|
final route = ModalRoute.of(context);
|
|
if (route != null && route.isActive) {
|
|
route.navigator?.removeRoute(route);
|
|
}
|
|
});
|
|
}
|
|
|
|
ref.listen(communityEntryCompletionProvider, (_, _) => finishEntry());
|
|
useEffect(() {
|
|
final presentation = ref.read(hostedInlineEntryProvider.notifier);
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
if (context.mounted) presentation.setActive(true);
|
|
});
|
|
void completed() {
|
|
if (flow?.stage == HostedFlowStage.complete) finishEntry();
|
|
}
|
|
|
|
flow?.addListener(completed);
|
|
return () {
|
|
flow?.removeListener(completed);
|
|
scheduleMicrotask(() {
|
|
presentation.setActive(false);
|
|
if (addingCommunity &&
|
|
onExit == null &&
|
|
flow != null &&
|
|
flow.stage != HostedFlowStage.complete) {
|
|
unawaited(flow.leave());
|
|
}
|
|
});
|
|
};
|
|
}, [flow]);
|
|
final signingIn = flow?.stage == HostedFlowStage.signIn;
|
|
final busy =
|
|
flow != null &&
|
|
flow.accountOnly &&
|
|
(flow.stage == HostedFlowStage.working ||
|
|
flow.stage == HostedFlowStage.signIn);
|
|
final connected = flow?.account.account != null;
|
|
final problem = flow != null && flow.accountOnly ? flow.problem : null;
|
|
final status = flow == null
|
|
? null
|
|
: problem != null
|
|
? hostedFlowProblemMessage(problem)
|
|
: busy
|
|
? null
|
|
: flow.noCommunitiesToAdd
|
|
? 'No communities available to add. Scan a QR code or use an invite link.'
|
|
: connected
|
|
? 'Connected to Builderlab.'
|
|
: null;
|
|
final handlingBack = useRef(false);
|
|
void back() {
|
|
// Nested page and entry PopScopes receive the same system Back event.
|
|
if (handlingBack.value) return;
|
|
handlingBack.value = true;
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
handlingBack.value = false;
|
|
});
|
|
if (!existingUser.value &&
|
|
!enteringCommunityLink.value &&
|
|
!creating.value &&
|
|
(onExit != null || addingCommunity)) {
|
|
if (onExit != null) {
|
|
onExit!();
|
|
} else {
|
|
Navigator.of(context).pop();
|
|
}
|
|
return;
|
|
}
|
|
unawaited(flow?.leave());
|
|
existingUser.value = false;
|
|
creating.value = false;
|
|
enteringCommunityLink.value = false;
|
|
FocusManager.instance.primaryFocus?.unfocus();
|
|
ref.read(pairingProvider.notifier).reset();
|
|
}
|
|
|
|
return PopScope(
|
|
canPop:
|
|
onExit == null &&
|
|
!existingUser.value &&
|
|
!enteringCommunityLink.value &&
|
|
!creating.value,
|
|
onPopInvokedWithResult: (didPop, _) {
|
|
if (!didPop &&
|
|
onExit != null &&
|
|
!existingUser.value &&
|
|
!enteringCommunityLink.value &&
|
|
!creating.value) {
|
|
back();
|
|
return;
|
|
}
|
|
if (!didPop &&
|
|
(existingUser.value ||
|
|
creating.value ||
|
|
enteringCommunityLink.value)) {
|
|
back();
|
|
}
|
|
},
|
|
child: PairingPage(
|
|
connectionLanding: true,
|
|
showConnectionOptions:
|
|
!HostedEnvironment.enabled || addingCommunity || connected,
|
|
addingCommunity: addingCommunity,
|
|
existingUser: existingUser.value,
|
|
enteringCommunityLink: enteringCommunityLink.value,
|
|
creatingCommunity: creating.value,
|
|
onSubmitCommunity: flow == null
|
|
? null
|
|
: (name) {
|
|
creating.value = false;
|
|
unawaited(flow.start(HostedCreateCommunity(name)));
|
|
},
|
|
onCommunityLink: () => enteringCommunityLink.value = true,
|
|
onCreateCommunity: flow == null || busy
|
|
? null
|
|
: () => creating.value = true,
|
|
onBuilderlab: flow == null
|
|
? null
|
|
: signingIn
|
|
? () => unawaited(flow.leave())
|
|
: busy
|
|
? null
|
|
: () => unawaited(flow.signInAccount()),
|
|
accountStatus: status,
|
|
builderlabBusy: busy,
|
|
signInPending: signingIn || busy,
|
|
builderlabLabel: signingIn
|
|
? 'Cancel sign-in'
|
|
: busy
|
|
? 'Connecting…'
|
|
: connected
|
|
? 'Continue with Builderlab'
|
|
: 'Sign in with email',
|
|
onWelcomeBack: back,
|
|
onAlreadyUseBuzz: () => existingUser.value = true,
|
|
),
|
|
);
|
|
}
|
|
}
|