로그인 · 회원가입 흐름을 서버 계정·약관 동의와 연결

This commit is contained in:
Sn-Kinos
2026-09-29 17:06:48 +09:00
parent 3aca678072
commit de44739e63
6 changed files with 133 additions and 99 deletions
+49 -39
View File
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import '../../core/firebase/firebase_boot.dart';
import '../../core/i18n/app_locale.dart';
import '../../core/theme/tokens.dart';
import '../../core/ui/app_icons.dart';
import 'auth.dart';
@@ -62,14 +63,14 @@ class _LoginScreenState extends State<LoginScreen> {
if (!mounted) return;
setState(() {
_busy = false;
_notice = authNotice(outcome);
_notice = authNotice(outcome, context.l10n);
});
}
Future<void> _resetPassword() async {
final email = _email.text.trim();
if (email.isEmpty) {
setState(() => _notice = '재설정 메일을 받으실 이메일을 먼저 입력해 주세요.');
setState(() => _notice = context.l10n.authResetNeedEmail);
return;
}
final impl = widget.auth.qaEmailProvider;
@@ -81,8 +82,8 @@ class _LoginScreenState extends State<LoginScreen> {
_busy = false;
// 성공해도 계정 존재 여부를 알려 주지 않는다. 가입된 이메일을 훑을 수 있다.
_notice = outcome is AuthFailed
? authNotice(outcome)
: '입력하신 주소로 재설정 안내를 보냈습니다.';
? authNotice(outcome, context.l10n)
: context.l10n.authResetSent;
});
}
@@ -171,12 +172,12 @@ class _LoginScreenState extends State<LoginScreen> {
backgroundColor: AppColors.cosmicAccent,
foregroundColor: AppColors.onCosmicAccent,
),
child: const Text('로그인'),
child: Text(context.l10n.authLogin),
),
const SizedBox(height: AppSpace.s4),
AuthSwitchRow(
question: '팬우주는 처음이세요?',
action: '회원가입',
question: context.l10n.authFirstTime,
action: context.l10n.authSignup,
onTap: () => _open(signUp: true),
),
],
@@ -216,7 +217,7 @@ class _SimpleLoginScreenState extends State<SimpleLoginScreen> {
if (agreed != true || !mounted) return;
}
setState(() => _busy = true);
final outcome = await widget.auth.signIn(provider);
final outcome = await widget.auth.signIn(provider, signUp: widget.signUp);
if (!mounted) return;
setState(() => _busy = false);
// 키 대기 제공자는 안내 다이얼로그로 끊는다 — 눌렀는데 아무 일도 안 일어나면
@@ -226,7 +227,7 @@ class _SimpleLoginScreenState extends State<SimpleLoginScreen> {
return;
}
if (!mounted) return;
setState(() => _notice = authNotice(outcome));
setState(() => _notice = authNotice(outcome, context.l10n));
}
Future<void> _showNotConfigured(AuthNotConfigured outcome) async {
@@ -234,15 +235,17 @@ class _SimpleLoginScreenState extends State<SimpleLoginScreen> {
await showDialog<void>(
context: context,
builder: (context) => AlertDialog(
title: Text('$name 로그인 준비 중'),
title: Text(context.l10n.authNotConfiguredTitle(name)),
content: Text(
'$name 로그인은 발주사 키가 들어오면 열립니다.\n\n'
'필요한 항목\n· ${outcome.missing.join('\n· ')}',
context.l10n.authNotConfiguredBody(
name,
outcome.missing.join('\n· '),
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('확인'),
child: Text(context.l10n.commonConfirm),
),
],
),
@@ -294,8 +297,8 @@ class _SimpleLoginScreenState extends State<SimpleLoginScreen> {
const SizedBox(height: AppSpace.s5),
Text(
signUp
? '가장 편한 방법으로\n팬우주에 회원가입 하세요!'
: '가장 편한 방법으로\n팬우주에 로그인하세요!',
? context.l10n.authSignupHeadline
: context.l10n.authLoginHeadline,
textAlign: TextAlign.center,
style: const TextStyle(
fontFamily: AppFonts.body,
@@ -317,7 +320,11 @@ class _SimpleLoginScreenState extends State<SimpleLoginScreen> {
for (final p in providers) ...[
_ProviderButton(
provider: p.provider,
label: providerButtonLabel(p.provider, signUp: signUp),
label: providerButtonLabel(
p.provider,
signUp: signUp,
l10n: context.l10n,
),
enabled: !_busy,
onPressed: () => _signIn(p.provider),
),
@@ -329,13 +336,13 @@ class _SimpleLoginScreenState extends State<SimpleLoginScreen> {
],
signUp
? AuthSwitchRow(
question: '이미 가입하셨나요?',
action: '로그인',
question: context.l10n.authAlreadyMember,
action: context.l10n.authLogin,
onTap: _switchMode,
)
: AuthSwitchRow(
question: '팬우주는 처음이세요?',
action: '회원가입',
question: context.l10n.authFirstTime,
action: context.l10n.authSignup,
onTap: _switchMode,
),
],
@@ -357,26 +364,29 @@ const _figmaOrder = [
/// 간편 로그인 · 회원가입 버튼 문구 — 시안 「Kakao로 로그인」 · 「Kakao로 가입하기」.
/// [AuthProviderKind.label](「카카오로 시작하기」)은 설정 · 안내 문구가 쓰므로 따로 둔다.
String providerButtonLabel(AuthProviderKind kind, {bool signUp = false}) =>
'${_names[kind]} ${signUp ? '가입하기' : '로그인'}';
/// 제공자별 조사(ko 「LINE으로」)는 arb 의 select 가 맡는다. [l10n] 이 없으면 ko.
String providerButtonLabel(
AuthProviderKind kind, {
bool signUp = false,
AppLocalizations? l10n,
}) {
final t = l10n ?? koL10n;
return signUp
? t.authProviderSignup(kind.name)
: t.authProviderLogin(kind.name);
}
/// 시안 버튼 문구의 제공자 표기. LINE 은 조사가 「으로」다.
const _names = {
AuthProviderKind.apple: 'Apple로',
AuthProviderKind.google: 'Google로',
AuthProviderKind.kakao: 'Kakao로',
AuthProviderKind.line: 'LINE으로',
AuthProviderKind.email: '이메일로',
};
String? authNotice(AuthOutcome outcome) => switch (outcome) {
AuthSucceeded() => null,
AuthCancelled() => null,
AuthFailed(:final code, :final message) => '[$code] $message',
AuthNotConfigured(:final provider, :final missing) =>
'${provider.shortName} 로그인은 아직 준비 중입니다.\n'
'필요한 항목: ${missing.join(' · ')}',
};
String? authNotice(AuthOutcome outcome, [AppLocalizations? l10n]) =>
switch (outcome) {
AuthSucceeded() => null,
AuthCancelled() => null,
AuthFailed(:final code, :final message) => '[$code] $message',
AuthNotConfigured(:final provider, :final missing) =>
(l10n ?? koL10n).authNotConfiguredNotice(
provider.shortName,
missing.join(' · '),
),
};
TextStyle _abz(double size, Color color) => TextStyle(
fontFamily: AppFonts.display,