가입 약관 동의 기록 · 서비스 탈퇴 · 온보딩 6단계 · Coach Mark

This commit is contained in:
Sn-Kinos
2026-10-05 22:48:25 +09:00
parent e7010010bb
commit 55424bd4a2
36 changed files with 2884 additions and 77 deletions
+6
View File
@@ -1,3 +1,4 @@
import '../../features/auth/onboarding_data.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:async';
@@ -66,6 +67,11 @@ class Repositories {
? FirestoreInsideRepository(backend)
: const SampleInsideRepository();
/// 온보딩 6단계(약관 · 프로필 · 관심 아티스트 · 행성 가입).
late final OnboardingRepository onboarding = _live
? FirestoreOnboardingRepository(backend)
: SampleOnboardingRepository();
late final MyRepository my = _live
? FirestoreMyRepository(backend)
: SampleMyRepository();
+48 -6
View File
@@ -7,8 +7,10 @@ import '../theme/tokens.dart';
import '../ui/app_icons.dart';
import '../../features/auth/auth_repository.dart';
import '../../features/auth/login_screen.dart';
import '../../features/auth/onboarding_screens.dart';
import '../../features/auth/signup_screens.dart';
import '../../features/galaxy/galaxy_home_screen.dart';
import '../../features/home/coach_mark.dart';
import '../../features/home/home_screen.dart';
import '../../features/inside/inside_screen.dart';
import '../../features/my/my_screen.dart';
@@ -79,11 +81,15 @@ List<AppTab> visibleTabs(FeatureFlags flags) => [
const String loginPath = '/login';
const String signupCompletePath = '/welcome';
const String introPath = '/intro';
const String onboardingPath = '/onboarding';
/// 5탭 셸 라우트 + 온보딩(로그인) 라우트.
/// 5탭 셸 라우트 + 온보딩 라우트.
///
/// 온보딩 나머지 화면(S-A-INTRO · S-A-TERMS · S-A-PROFILE · S-A-ARTIST ·
/// S-A-PLANET-NEW · S-A-PERMS)은 3주차 범위라 아직 라우트가 없다.
/// 온보딩 6단계(10/3 통합회신 1항): ① 소개 3장([introPath], 첫 실행 · 로그인 전) → 로그인 →
/// ②~⑥ 약관 · 프로필 · 관심 아티스트 · 행성 가입/성운 생성 · 권한 안내([onboardingPath]) →
/// 가입 완료([signupCompletePath]) → HOME(Coach Mark). 서버에 약관 동의가 없는 계정은
/// 「로그인」 버튼으로 들어왔어도 셸로 가지 못하고 [onboardingPath] 로 간다.
GoRouter buildRouter(AuthRepository auth, {Repositories? repositories}) {
// 저장소 선택은 앱이 뜰 때 한 번만 한다. 탭을 옮길 때마다 다시 만들면
// Firestore 커서 같은 화면 상태가 매번 초기화된다.
@@ -95,11 +101,23 @@ GoRouter buildRouter(AuthRepository auth, {Repositories? repositories}) {
refreshListenable: auth,
redirect: (context, state) {
final at = state.matchedLocation;
if (!auth.isSignedIn) return at == loginPath ? null : loginPath;
if (!auth.isSignedIn) {
final entry = auth.introPending ? introPath : loginPath;
return at == entry ? null : entry;
}
if (auth.onboarding != OnboardingGate.done) {
return at == onboardingPath ? null : onboardingPath;
}
if (auth.justSignedUp)
return at == signupCompletePath ? null : signupCompletePath;
if (at == loginPath || at == signupCompletePath)
if (const [
loginPath,
signupCompletePath,
introPath,
onboardingPath,
].contains(at)) {
return appTabs.first.path;
}
return null;
},
routes: [
@@ -107,6 +125,24 @@ GoRouter buildRouter(AuthRepository auth, {Repositories? repositories}) {
path: loginPath,
builder: (_, _) => LoginScreen(auth: auth),
),
GoRoute(
path: introPath,
builder: (_, _) => IntroScreen(onDone: auth.finishIntro),
),
GoRoute(
path: onboardingPath,
builder: (_, _) => ListenableBuilder(
listenable: auth,
builder: (_, _) => auth.onboarding == OnboardingGate.checking
? const Scaffold(body: Center(child: CircularProgressIndicator()))
: OnboardingFlow(
auth: auth,
repository: repos.onboarding,
home: repos.home,
push: repos.push,
),
),
),
GoRoute(
path: signupCompletePath,
builder: (_, _) => SignupCompleteScreen(onStart: auth.finishSignup),
@@ -230,7 +266,13 @@ class AppShell extends StatelessWidget {
),
destinations: [
for (final tab in tabs)
NavigationDestination(icon: tab.icon, label: tab.label),
NavigationDestination(
// Coach Mark ② 가 MY 위치를 가리킨다(coach_mark.dart).
icon: tab.path == appTabs.last.path
? KeyedSubtree(key: coachMyTabKey, child: tab.icon)
: tab.icon,
label: tab.label,
),
],
),
),