Flutter 앱 골격 · 5탭 네비게이션 · 로그인 화면 · Firestore 보안 규칙·인덱스

This commit is contained in:
Sn-Kinos
2026-08-10 01:02:03 +09:00
parent fbba4405c3
commit 233ef99837
113 changed files with 8549 additions and 2 deletions
@@ -0,0 +1,80 @@
import 'package:flutter/foundation.dart';
import '../../core/firebase/firebase_boot.dart';
import 'auth.dart';
import 'social_providers.dart';
/// 로그인 상태 보관소. 라우터가 이걸 듣고 로그인/셸을 가른다.
class AuthRepository extends ChangeNotifier {
AuthRepository({List<SocialAuthProvider>? providers, bool? releaseMode})
: providers = providers ?? _defaultProviders(),
// 기본값은 실제 빌드 모드. 테스트에서만 주입해 릴리스 동작을 재현한다
// (kReleaseMode 는 컴파일 상수라 테스트에서 뒤집을 수 없다).
_releaseMode = releaseMode ?? kReleaseMode;
final bool _releaseMode;
/// 로그인 화면에 그릴 제공자 목록. 순서는 시안 S-A-LOGIN 버튼 순서다.
final List<SocialAuthProvider> providers;
AuthAccount? _account;
AuthAccount? get account => _account;
bool get isSignedIn => _account != null;
static List<SocialAuthProvider> _defaultProviders() => const [
KeylessSocialProvider(SocialProvider.kakao, [
'카카오 네이티브 앱 키',
'카카오 개발자 콘솔 앱 등록(사업자 정보 필요)',
]),
KeylessSocialProvider(SocialProvider.naver, [
'네이버 client ID / client secret',
'네이버 개발자센터 앱 등록(사업자 정보 필요)',
]),
AppleSignInProvider(),
GoogleSignInProvider(),
// 이메일 가입 구현체는 여기 없다. CD-18 미확정(Q2) — 회신이 오면 이 목록에 추가한다.
// 자세한 사유는 auth.dart 상단 「이메일 가입에 대하여」 참고.
];
Future<AuthOutcome> signIn(SocialProvider provider) async {
final impl = providers.firstWhere((p) => p.provider == provider);
final outcome = await impl.signIn();
if (outcome is AuthSucceeded) {
_account = outcome.account;
notifyListeners();
}
return outcome;
}
/// Firebase 미설정 상태에서 탭 이동을 시연하기 위한 임시 세션.
///
/// 별첨2 2주차 발주사 확인 항목에 「앱 설치·로그인·탭 이동 확인」이 있는데,
/// 설정 파일이 없으면 실 로그인이 물리적으로 불가능하다. 그래서 목 모드에서만
/// 이 경로를 열어 둔다.
///
/// **가드가 두 겹인 이유** — `!FirebaseBoot.isLive` 하나로는 부족하다.
/// [FirebaseBoot.init] 은 모든 예외를 삼키고 목 모드로 내려가므로, 설정 파일이
/// 정상 투입된 릴리스 빌드라도 초기화가 어떤 이유로든 실패하면(플랫폼 초기화 실패,
/// 리소스 패키징 오류, 기기 환경) 이 우회 입구가 배포 앱에 노출된다.
/// 그래서 빌드 모드로 한 겹 더 막는다 — **릴리스 빌드에서는 어떤 경우에도 열리지 않는다.**
bool get isMockSessionAvailable => !FirebaseBoot.isLive && !_releaseMode;
void signInWithMockSession() {
if (!isMockSessionAvailable) return;
_account = const AuthAccount(
uid: 'mock-local',
provider: null,
displayName: '목 세션',
isMock: true,
);
notifyListeners();
}
Future<void> signOut() async {
for (final p in providers) {
await p.signOut();
}
_account = null;
notifyListeners();
}
}