import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; import '../../shared/l10n/l10n.dart'; import '../../shared/theme/theme.dart'; import '../../shared/widgets/buzz_loading_indicator.dart'; import '../../shared/widgets/modal_presentation.dart'; import '../pairing/pairing_page.dart'; import 'invite_join_provider.dart'; Future showInviteJoinSheet(BuildContext context, WidgetRef ref) { return showBuzzModalBottomSheet( context: context, isScrollControlled: true, showDragHandle: true, builder: (_) => const InviteJoinSheet(), ); } class InviteJoinSheet extends ConsumerWidget { const InviteJoinSheet({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final state = ref.watch(inviteJoinProvider); final isClaiming = state.status == InviteJoinStatus.claiming; final host = state.host ?? context.l10n.unknownHost; final derivedName = state.communityName; if (state.status == InviteJoinStatus.success) { return _InviteJoinSuccess(host: host, communityName: derivedName); } return SafeArea( child: Padding( padding: const EdgeInsets.fromLTRB(Grid.sm, 0, Grid.sm, Grid.sm), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Icon(LucideIcons.userPlus, size: 40, color: context.colors.primary), const SizedBox(height: Grid.sm), Text( context.l10n.inviteJoinTitle, style: context.textTheme.titleLarge, ), const SizedBox(height: Grid.xxs), Text( context.l10n.inviteJoinHostPrompt, style: context.textTheme.bodyMedium?.copyWith( color: context.colors.onSurfaceVariant, ), ), const SizedBox(height: Grid.xs), Container( padding: const EdgeInsets.all(Grid.twelve), decoration: BoxDecoration( color: context.colors.surfaceContainerHighest.withValues( alpha: 0.7, ), borderRadius: BorderRadius.circular(12), border: Border.all(color: context.colors.outlineVariant), ), child: Text( host, style: context.textTheme.titleMedium?.copyWith( fontFamily: 'GeistMono', fontWeight: FontWeight.w700, ), ), ), if (derivedName != null && derivedName != host) ...[ const SizedBox(height: Grid.xxs), Text( context.l10n.inviteDisplayName(derivedName), style: context.textTheme.bodySmall?.copyWith( color: context.colors.onSurfaceVariant, ), ), ], const SizedBox(height: Grid.sm), Text( context.l10n.inviteIdentityWarning, style: context.textTheme.bodyMedium?.copyWith( color: context.colors.onSurfaceVariant, ), ), if (state.status == InviteJoinStatus.error && state.errorMessage != null) ...[ const SizedBox(height: Grid.sm), Text( _localizedInviteError(context, state.errorMessage!), style: context.textTheme.bodySmall?.copyWith( color: context.colors.error, ), ), ], const SizedBox(height: Grid.lg), Row( children: [ Expanded( child: OutlinedButton( onPressed: isClaiming ? null : () => Navigator.of(context).pop(), child: Text(context.l10n.cancel), ), ), const SizedBox(width: Grid.sm), Expanded( child: FilledButton.icon( onPressed: isClaiming || state.requiresFreshInvite ? null : () => ref .read(inviteJoinProvider.notifier) .confirmJoin(), icon: isClaiming ? SizedBox( width: 16, height: 16, child: BuzzLoadingIndicator( size: 16, semanticLabel: context.l10n.inviteJoiningCommunity, ), ) : const Icon(LucideIcons.check), label: Text( isClaiming ? context.l10n.joining : context.l10n.join, ), ), ), ], ), ], ), ), ); } } class _InviteJoinSuccess extends StatelessWidget { final String host; final String? communityName; const _InviteJoinSuccess({required this.host, this.communityName}); @override Widget build(BuildContext context) { return SafeArea( child: Padding( padding: const EdgeInsets.fromLTRB(Grid.sm, 0, Grid.sm, Grid.sm), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Icon( LucideIcons.circleCheck, size: 40, color: context.colors.primary, ), const SizedBox(height: Grid.sm), Text( context.l10n.inviteJoined(communityName ?? host), style: context.textTheme.titleLarge, ), const SizedBox(height: Grid.xs), Text( context.l10n.inviteIdentityWarning, style: context.textTheme.bodyMedium?.copyWith( color: context.colors.onSurfaceVariant, ), ), const SizedBox(height: Grid.lg), FilledButton.icon( onPressed: () { Navigator.of(context).pop(); Navigator.of(context).push( MaterialPageRoute( builder: (_) => const PairingPage(addingCommunity: true), ), ); }, icon: const Icon(LucideIcons.scanLine), label: Text(context.l10n.inviteBackupNow), ), const SizedBox(height: Grid.xs), TextButton( onPressed: () => Navigator.of(context).pop(), child: Text(context.l10n.inviteNotNow), ), ], ), ), ); } } String _localizedInviteError(BuildContext context, String error) { if (Localizations.localeOf(context).languageCode == 'en') return error; if (error.startsWith('This invite has expired')) { return context.l10n.inviteExpired; } if (error.startsWith('This invite has reached its use limit')) { return context.l10n.inviteExhausted; } if (error.startsWith('This invite is not valid')) { return context.l10n.inviteInvalid; } if (error.startsWith('This invite approval has expired')) { return context.l10n.inviteApprovalExpired; } if (error.startsWith('Could not reach the relay')) { return context.l10n.inviteRelayUnreachable; } return context.l10n.inviteJoinFailed; }