From c42fd615a6c9af65ff32893cf606bfc63a5842fc Mon Sep 17 00:00:00 2001 From: Sn-Kinos Date: Sun, 11 Oct 2026 23:43:43 +0900 Subject: [PATCH] =?UTF-8?q?=EC=86=8C=EC=85=9C=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=A7=84=ED=96=89=20=EC=A4=91=20=EB=A1=9C=EB=94=A9?= =?UTF-8?q?=20=ED=91=9C=EC=8B=9C=20=C2=B7=20=EC=A4=91=EB=B3=B5=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/lib/features/auth/login_screen.dart | 8 +++++++- app/test/signup_terms_test.dart | 16 ++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/app/lib/features/auth/login_screen.dart b/app/lib/features/auth/login_screen.dart index 548af81..1226eb5 100644 --- a/app/lib/features/auth/login_screen.dart +++ b/app/lib/features/auth/login_screen.dart @@ -209,6 +209,7 @@ class _SimpleLoginScreenState extends State { bool _busy = false; Future _signIn(SocialProvider provider) async { + if (_busy) return; if (widget.signUp) { final agreed = await Navigator.of(context).push>( MaterialPageRoute(builder: (_) => const TermsScreen()), @@ -217,6 +218,8 @@ class _SimpleLoginScreenState extends State { // 인증 뒤 동의가 필요한 계정이면 이 동의를 서버에 기록한다(약관을 다시 묻지 않는다). widget.auth.pendingConsent = agreed; } + // 제공자 화면에서 돌아온 뒤에도 서버 토큰 교환 · 로그인에 몇 초 걸린다(Kakao · LINE). + // 끝날 때까지 화면 전체를 막고 진행 표시를 띄운다 — 다시 누르지 못하게. setState(() => _busy = true); final outcome = await widget.auth.signIn(provider); if (outcome is! AuthSucceeded) widget.auth.pendingConsent = null; @@ -348,7 +351,10 @@ class _SimpleLoginScreenState extends State { ), ), ); - return scaffold; + return PopScope( + canPop: !_busy, + child: Stack(children: [scaffold, if (_busy) const BlockingProgress()]), + ); } } diff --git a/app/test/signup_terms_test.dart b/app/test/signup_terms_test.dart index 1f0cd47..6a0fc2f 100644 --- a/app/test/signup_terms_test.dart +++ b/app/test/signup_terms_test.dart @@ -5,10 +5,12 @@ import 'package:fancake/core/firebase/firebase_boot.dart'; import 'package:fancake/features/auth/auth.dart'; import 'package:fancake/features/auth/auth_repository.dart'; import 'package:fancake/features/auth/login_screen.dart'; +import 'package:fancake/features/auth/signup_screens.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; /// 가입 약관 — 동의 기록이 없는 계정은 HOME 대신 약관으로 가고, 동의를 기록한 뒤에만 HOME 에 들어간다. +/// 소셜 로그인 진행 중에는 화면 전체를 막고 진행 표시를 띄운다. void main() { setUp(() { FirebaseBoot.debugSetMode(FirebaseMode.mock, reason: '가입 약관 위젯 테스트'); @@ -19,7 +21,9 @@ void main() { provider: AuthProviderKind.kakao, ); - testWidgets('동의 없는 새 계정은 약관 → 필수 동의 → HOME', (tester) async { + testWidgets('로그인 진행 중 로딩 · 중복 입력 막음 → 동의 없는 새 계정은 약관 → 필수 동의 → HOME', ( + tester, + ) async { final signIn = Completer(); final provider = _Provider(AuthProviderKind.kakao, () => signIn.future); Set? recorded; @@ -34,8 +38,15 @@ void main() { await tester.tap(find.widgetWithText(FilledButton, '로그인')); await tester.pumpAndSettle(); - await tester.tap(find.text(providerButtonLabel(AuthProviderKind.kakao))); + final kakao = find.text(providerButtonLabel(AuthProviderKind.kakao)); + await tester.tap(kakao); await tester.pump(); + expect(find.byType(BlockingProgress), findsOneWidget); + expect(find.byType(CircularProgressIndicator), findsOneWidget); + await tester.tap(kakao, warnIfMissed: false); + await tester.pump(); + expect(provider.calls, 1); + signIn.complete(const AuthSucceeded(newUser)); await tester.pumpAndSettle(); expect(find.byType(NavigationBar), findsNothing); @@ -67,6 +78,7 @@ void main() { expect(recorded, {'service', 'privacy', 'age'}); expect(find.byType(NavigationBar), findsOneWidget); + expect(find.byType(BlockingProgress), findsNothing); }); testWidgets('이미 동의한 계정은 약관 없이 HOME', (tester) async {