generated from ttipo/project-base
fix(flutter): 로그인 전에는 앱을 열 때마다 시작하기 화면을 보여 줌
"시작하기"를 한 번 누르면 기기에 저장해 이후 실행에서 인트로를 건너뛰었다. 그래서 시작하기를 누른 뒤 로그인하지 않고 앱을 다시 열면 스플래시 다음 바로 로그인 화면이 나왔다. 시작하기 여부는 이번 실행 동안만 기억한다. - 로그아웃은 시안대로 곧바로 로그인 화면으로 간다 - 예전 버전이 저장한 entry.introSeen 값은 더 이상 읽지 않는다 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,21 +2,17 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:tma_app/core/storage/preferences_provider.dart';
|
||||
|
||||
const String _kIntroSeenKey = 'entry.introSeen';
|
||||
const String _kOnboardingSeenKey = 'entry.onboardingSeen';
|
||||
|
||||
/// 설치 후 1회만 보여 주는 화면(인트로·온보딩)의 확인 여부를 기기에 저장한다.
|
||||
/// 설치 후 1회만 보여 주는 화면(온보딩)의 확인 여부를 기기에 저장한다.
|
||||
/// (인트로는 로그인 전이면 실행할 때마다 보여 주므로 저장하지 않는다)
|
||||
class EntryRepository {
|
||||
EntryRepository(this._prefs);
|
||||
|
||||
final SharedPreferences _prefs;
|
||||
|
||||
bool get introSeen => _prefs.getBool(_kIntroSeenKey) ?? false;
|
||||
|
||||
bool get onboardingSeen => _prefs.getBool(_kOnboardingSeenKey) ?? false;
|
||||
|
||||
Future<void> markIntroSeen() => _prefs.setBool(_kIntroSeenKey, true);
|
||||
|
||||
Future<void> markOnboardingSeen() => _prefs.setBool(_kOnboardingSeenKey, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
/// 진입 단계 — 설치 후 첫 진입: 스플래시 → 인트로 → 로그인 → 온보딩 → 앱
|
||||
/// 진입 단계 — 로그인 전: 스플래시 → 인트로 → 로그인 → (첫 로그인이면) 온보딩 → 앱
|
||||
enum EntryStage { intro, login, onboarding, app }
|
||||
|
||||
/// 진입 흐름 상태. 세 플래그로 지금 보여 줄 단계가 정해진다.
|
||||
class EntryState {
|
||||
const EntryState({
|
||||
required this.introSeen,
|
||||
required this.introPassed,
|
||||
required this.signedIn,
|
||||
required this.onboardingSeen,
|
||||
});
|
||||
|
||||
final bool introSeen;
|
||||
/// 이번 실행에서 인트로의 "시작하기"를 눌렀는지 — 저장하지 않는다.
|
||||
/// 로그인하지 않은 채 앱을 다시 열면 인트로부터 다시 보여 준다.
|
||||
final bool introPassed;
|
||||
final bool signedIn;
|
||||
final bool onboardingSeen;
|
||||
|
||||
EntryStage get stage {
|
||||
if (!signedIn) return introSeen ? EntryStage.login : EntryStage.intro;
|
||||
if (!signedIn) return introPassed ? EntryStage.login : EntryStage.intro;
|
||||
if (!onboardingSeen) return EntryStage.onboarding;
|
||||
return EntryStage.app;
|
||||
}
|
||||
|
||||
EntryState copyWith({bool? introSeen, bool? signedIn, bool? onboardingSeen}) {
|
||||
EntryState copyWith({bool? introPassed, bool? signedIn, bool? onboardingSeen}) {
|
||||
return EntryState(
|
||||
introSeen: introSeen ?? this.introSeen,
|
||||
introPassed: introPassed ?? this.introPassed,
|
||||
signedIn: signedIn ?? this.signedIn,
|
||||
onboardingSeen: onboardingSeen ?? this.onboardingSeen,
|
||||
);
|
||||
|
||||
@@ -12,17 +12,14 @@ class EntryNotifier extends Notifier<EntryState> {
|
||||
@override
|
||||
EntryState build() {
|
||||
return EntryState(
|
||||
introSeen: ref.watch(entryRepositoryProvider).introSeen,
|
||||
introPassed: false,
|
||||
signedIn: ref.watch(authRepositoryProvider).hasSession,
|
||||
onboardingSeen: ref.watch(entryRepositoryProvider).onboardingSeen,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> completeIntro() async {
|
||||
await _entry.markIntroSeen();
|
||||
if (!ref.mounted) return;
|
||||
state = state.copyWith(introSeen: true);
|
||||
}
|
||||
/// 인트로의 "시작하기" — 이번 실행 동안만 기억한다
|
||||
void completeIntro() => state = state.copyWith(introPassed: true);
|
||||
|
||||
Future<void> signIn(SocialProvider provider) async {
|
||||
await _auth.signIn(provider);
|
||||
@@ -36,11 +33,12 @@ class EntryNotifier extends Notifier<EntryState> {
|
||||
state = state.copyWith(onboardingSeen: true);
|
||||
}
|
||||
|
||||
/// 로그아웃하면 로그인 화면으로 돌아간다 (인트로·온보딩은 다시 보이지 않는다)
|
||||
/// 로그아웃하면 곧바로 로그인 화면으로 돌아간다 (시안 — 인트로를 거치지 않는다).
|
||||
/// 이후 앱을 새로 열면 로그인 전이므로 인트로부터 보인다.
|
||||
Future<void> signOut() async {
|
||||
await _auth.signOut();
|
||||
if (!ref.mounted) return;
|
||||
state = state.copyWith(signedIn: false);
|
||||
state = state.copyWith(signedIn: false, introPassed: true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -23,12 +22,12 @@ const Alignment _kArtAlignment = Alignment(.12, .02);
|
||||
/// 시안: CTA 는 화면 아래에서 80 (홈 인디케이터 34 포함)
|
||||
const double _kCtaBottomGap = 46;
|
||||
|
||||
/// 인트로 — 설치 후 첫 진입(스플래시 다음)에 한 번 보여 준다.
|
||||
/// 인트로 — 로그인하지 않은 상태로 앱을 열면 스플래시 다음에 보여 준다.
|
||||
class IntroPage extends ConsumerWidget {
|
||||
const IntroPage({super.key});
|
||||
|
||||
void _start(BuildContext context, WidgetRef ref) {
|
||||
unawaited(ref.read(entryProvider.notifier).completeIntro());
|
||||
ref.read(entryProvider.notifier).completeIntro();
|
||||
context.go(AppRoutes.login);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user