From 897dff950d5147821fe6ee2e5aae55c5930f0bd5 Mon Sep 17 00:00:00 2001 From: Sn-Kinos Date: Sun, 11 Oct 2026 00:29:44 +0900 Subject: [PATCH] =?UTF-8?q?MY=20=ED=99=94=EB=A9=B4=EC=97=90=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=95=84=EC=9B=83(=ED=99=95=EC=9D=B8=20=ED=9B=84=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=99=94=EB=A9=B4=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=B5=EA=B7=80)=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/lib/core/router/app_router.dart | 2 +- app/lib/core/ui/tab_placeholder.dart | 8 ++++++ app/lib/features/my/my_screen.dart | 37 ++++++++++++++++++++++++++-- app/test/my_logout_test.dart | 29 ++++++++++++++++++++++ 4 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 app/test/my_logout_test.dart diff --git a/app/lib/core/router/app_router.dart b/app/lib/core/router/app_router.dart index 21aa657..5ca939e 100644 --- a/app/lib/core/router/app_router.dart +++ b/app/lib/core/router/app_router.dart @@ -136,7 +136,7 @@ GoRouter buildRouter(AuthRepository auth, {FeatureFlags? flags}) { routes: [ GoRoute( path: appTabs[4].path, - builder: (_, _) => const MyScreen(), + builder: (_, _) => MyScreen(auth: auth), ), ], ), diff --git a/app/lib/core/ui/tab_placeholder.dart b/app/lib/core/ui/tab_placeholder.dart index 92b0570..dba69c6 100644 --- a/app/lib/core/ui/tab_placeholder.dart +++ b/app/lib/core/ui/tab_placeholder.dart @@ -13,6 +13,7 @@ class TabPlaceholder extends StatelessWidget { required this.tab, required this.screenId, required this.screenName, + this.footer, }); /// 계약 고정 탭 라벨(견적서 v2.1 5탭). @@ -20,6 +21,9 @@ class TabPlaceholder extends StatelessWidget { final String screenId; final String screenName; + /// 자리표시 아래에 붙는 동작(예: MY 의 로그아웃). 없으면 그리지 않는다. + final Widget? footer; + @override Widget build(BuildContext context) { final text = Theme.of(context).textTheme; @@ -55,6 +59,10 @@ class TabPlaceholder extends StatelessWidget { ], ), ), + if (footer != null) ...[ + const SizedBox(height: AppSpace.s5), + footer!, + ], ], ), ), diff --git a/app/lib/features/my/my_screen.dart b/app/lib/features/my/my_screen.dart index 1623342..4e406d9 100644 --- a/app/lib/features/my/my_screen.dart +++ b/app/lib/features/my/my_screen.dart @@ -1,15 +1,48 @@ import 'package:flutter/material.dart'; import '../../core/ui/tab_placeholder.dart'; +import '../auth/auth_repository.dart'; /// MY 탭 루트 — S-M-HOME 마이 페이지. +/// +/// 화면 구현 전이라도 로그아웃은 여기서 할 수 있어야 한다 — 다른 계정 · 다른 로그인 +/// 방법으로 다시 들어가 볼 길이 없으면 소셜 로그인 4종을 한 기기에서 확인할 수 없다. +/// 로그아웃하면 인증 상태가 바뀌고 라우터가 로그인 화면으로 보낸다. class MyScreen extends StatelessWidget { - const MyScreen({super.key}); + const MyScreen({super.key, required this.auth}); + + final AuthRepository auth; + + Future _logout(BuildContext context) async { + final ok = await showDialog( + context: context, + builder: (context) => AlertDialog( + content: const Text('로그아웃 하시겠습니까?'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: const Text('취소'), + ), + FilledButton( + onPressed: () => Navigator.of(context).pop(true), + child: const Text('로그아웃'), + ), + ], + ), + ); + if (ok == true) await auth.signOut(); + } @override - Widget build(BuildContext context) => const TabPlaceholder( + Widget build(BuildContext context) => TabPlaceholder( tab: 'MY', screenId: 'S-M-HOME', screenName: '마이 페이지', + footer: Builder( + builder: (context) => OutlinedButton( + onPressed: () => _logout(context), + child: const Text('로그아웃'), + ), + ), ); } diff --git a/app/test/my_logout_test.dart b/app/test/my_logout_test.dart new file mode 100644 index 0000000..f3744f4 --- /dev/null +++ b/app/test/my_logout_test.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:fancake/app.dart'; +import 'package:fancake/features/auth/auth_repository.dart'; + +/// MY 에서 로그아웃하면 로그인 화면으로 돌아간다 — 한 기기에서 로그인 방법을 바꿔 확인할 수 있어야 한다. +void main() { + testWidgets('MY → 로그아웃 → 확인 → 로그인 화면, 취소면 그대로', (tester) async { + final auth = AuthRepository(releaseMode: false)..signInWithMockSession(); + await tester.pumpWidget(FancakeApp(auth: auth)); + await tester.pumpAndSettle(); + await tester.tap(find.text('MY')); + await tester.pumpAndSettle(); + + await tester.tap(find.widgetWithText(OutlinedButton, '로그아웃')); + await tester.pumpAndSettle(); + await tester.tap(find.text('취소')); + await tester.pumpAndSettle(); + expect(auth.isSignedIn, isTrue); + + await tester.tap(find.widgetWithText(OutlinedButton, '로그아웃')); + await tester.pumpAndSettle(); + await tester.tap(find.widgetWithText(FilledButton, '로그아웃')); + await tester.pumpAndSettle(); + expect(auth.isSignedIn, isFalse); + expect(find.byType(NavigationBar), findsNothing); + }); +}