import 'package:flutter/material.dart'; import '../../core/firebase/firebase_boot.dart'; import '../../core/theme/tokens.dart'; import 'auth.dart'; import 'auth_repository.dart'; /// S-A-LOGIN 로그인. /// /// 시안: `시안/screens/S-A-LOGIN.html` · 명세: `wireframe/planning/screen-spec/onboard/S-A-LOGIN.md` /// 골격 범위라 4개 버튼과 결과 안내까지만 구현했다. 약관 동의(S-A-TERMS) 이후 동선은 3주차다. class LoginScreen extends StatefulWidget { const LoginScreen({super.key, required this.auth}); final AuthRepository auth; @override State createState() => _LoginScreenState(); } class _LoginScreenState extends State { String? _notice; bool _busy = false; Future _signIn(SocialProvider provider) async { setState(() => _busy = true); final outcome = await widget.auth.signIn(provider); if (!mounted) return; setState(() { _busy = false; _notice = switch (outcome) { AuthSucceeded() => null, AuthCancelled() => null, AuthFailed(:final code, :final message) => '[$code] $message', AuthNotConfigured(:final provider, :final missing) => '${provider.label.replaceAll('로 시작하기', '')} 로그인은 아직 준비 중입니다.\n' '필요한 항목: ${missing.join(' · ')}', }; }); } @override Widget build(BuildContext context) { final text = Theme.of(context).textTheme; return Scaffold( body: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.all(AppSpace.s5), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: AppSpace.s6), // 로고 원본은 미확정(Q6)이라 워드마크를 텍스트로 둔다. 확정되면 이미지로 교체. Text('Fancake', style: text.displayMedium), const SizedBox(height: AppSpace.s4), Text('당신의 팬 활동을\n하나의 행성으로', style: text.displayLarge), const SizedBox(height: AppSpace.s4), Text( '로그인하면 이용약관과 개인정보 처리방침에 동의하는 절차로 이동합니다.', style: text.bodyMedium?.copyWith(color: AppColors.onCosmicVariant), ), const SizedBox(height: AppSpace.s7), Text('계정으로 시작하기', style: text.titleLarge), const SizedBox(height: AppSpace.s2), Text( '사용 중인 계정 하나를 선택해 주세요.', style: text.bodySmall, ), const SizedBox(height: AppSpace.s4), for (final p in widget.auth.providers) ...[ _ProviderButton( provider: p.provider, enabled: !_busy, onPressed: () => _signIn(p.provider), ), const SizedBox(height: AppSpace.s3), ], if (_notice != null) ...[ const SizedBox(height: AppSpace.s2), _Notice(_notice!), ], if (widget.auth.isMockSessionAvailable) ...[ const SizedBox(height: AppSpace.s5), const Divider(), const SizedBox(height: AppSpace.s4), _MockBanner(onEnter: widget.auth.signInWithMockSession), ], const SizedBox(height: AppSpace.s6), ], ), ), ), ); } } /// 소셜 버튼. 색과 라벨은 각 사 브랜드 가이드 고정값이라 테마를 타지 않는다. class _ProviderButton extends StatelessWidget { const _ProviderButton({ required this.provider, required this.enabled, required this.onPressed, }); final SocialProvider provider; final bool enabled; final VoidCallback onPressed; @override Widget build(BuildContext context) { final (fill, label, stroke) = switch (provider) { SocialProvider.kakao => (BrandColors.kakao, BrandColors.kakaoLabel, null), SocialProvider.naver => (BrandColors.naver, BrandColors.naverLabel, null), SocialProvider.apple => ( BrandColors.apple, BrandColors.appleLabel, BrandColors.appleOnlight, ), SocialProvider.google => ( BrandColors.googleFill, BrandColors.googleLabel, BrandColors.googleStroke, ), }; return FilledButton( onPressed: enabled ? onPressed : null, style: FilledButton.styleFrom( backgroundColor: fill, foregroundColor: label, disabledBackgroundColor: fill.withValues(alpha: 0.4), side: stroke == null ? null : BorderSide(color: stroke), ), child: Text(provider.label), ); } } class _Notice extends StatelessWidget { const _Notice(this.message); final String message; @override Widget build(BuildContext context) => Container( padding: const EdgeInsets.all(AppSpace.s4), decoration: BoxDecoration( color: AppColors.cosmicSurfaceContainer, border: Border.all(color: AppColors.cosmicOutline), borderRadius: BorderRadius.circular(AppRadius.md), ), child: Text(message, style: Theme.of(context).textTheme.bodySmall), ); } /// Firebase 미설정 안내 + 탭 이동 시연 입구. class _MockBanner extends StatelessWidget { const _MockBanner({required this.onEnter}); final VoidCallback onEnter; @override Widget build(BuildContext context) { final text = Theme.of(context).textTheme; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text('목(mock) 모드', style: text.labelMedium), const SizedBox(height: AppSpace.s2), Text(FirebaseBoot.reason, style: text.bodySmall), const SizedBox(height: AppSpace.s3), OutlinedButton( onPressed: onEnter, child: const Text('목 세션으로 탭 둘러보기'), ), ], ); } }