로그아웃 SDK 대기 제거 · 온보딩 국가 시트 · 프로필 머리줄
This commit is contained in:
@@ -241,13 +241,19 @@ class AuthRepository extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
// SDK 로그아웃(Google · Kakao 등)은 기다리지 않는다. 로그인 상태를 정하는 것은 Firebase 세션이고,
|
||||
// SDK 쪽은 다음 로그인의 자동 선택을 지우는 정리일 뿐이다. Android Google 은 Credential Manager
|
||||
// `clearCredentialStateAsync` 가 응답 없이 멈춰 3초 시간 제한을 다 쓰고, 그 TimeoutException 이
|
||||
// 처리되지 않은 채 로그에 남았다(10/6 실기기). 실패는 로그로만 남긴다.
|
||||
if (provider != null) {
|
||||
unawaited(
|
||||
provider.signOut().catchError((Object e) {
|
||||
debugPrint('[Auth] provider sign-out skipped: $e');
|
||||
}),
|
||||
);
|
||||
}
|
||||
try {
|
||||
await Future.wait([
|
||||
// SDK 로그아웃(Google · Kakao 등)이 응답 없이 멈춰도 로컬 로그아웃은 한다.
|
||||
if (provider != null)
|
||||
provider.signOut().timeout(const Duration(seconds: 3)),
|
||||
if (_session != null) _session.signOut(),
|
||||
], eagerError: false);
|
||||
await _session?.signOut();
|
||||
} finally {
|
||||
// 원격 SDK 정리가 실패해도 로컬 라우터는 로그인 상태에 고착되지 않는다.
|
||||
_replaceAccount(null);
|
||||
|
||||
@@ -381,6 +381,8 @@ class _ProfileStepState extends State<_ProfileStep> {
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: const Color(0xFF101010),
|
||||
// 사본 `178:23489` 상단 손잡이.
|
||||
showDragHandle: true,
|
||||
builder: (_) => _CountrySheet(current: _country),
|
||||
);
|
||||
if (picked != null && mounted) setState(() => _country = picked);
|
||||
@@ -390,9 +392,12 @@ class _ProfileStepState extends State<_ProfileStep> {
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final current = _locale ?? LocaleScope.maybeOf(context)?.locale;
|
||||
// Figma `511:86825` — 머리줄 「<」 좌측 · 타이틀 중앙(사본 `178:23489` 「회원가입」), 본문 큰 제목 없음.
|
||||
// 입력란은 사본처럼 테두리 상자(`511:86809`).
|
||||
return _StepScaffold(
|
||||
title: l10n.onbProfileTitle,
|
||||
barTitle: l10n.authSignup,
|
||||
onBack: widget.onBack,
|
||||
boxedInputs: true,
|
||||
action: _PrimaryButton(
|
||||
label: l10n.commonNext,
|
||||
onPressed: _valid && !_busy ? _save : null,
|
||||
@@ -475,10 +480,12 @@ class _CountrySheetState extends State<_CountrySheet> {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 4, 0),
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 4, 0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: Text(context.l10n.onbCountry, style: _title)),
|
||||
Expanded(
|
||||
child: Text(context.l10n.onbCountrySheetTitle, style: _title),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: context.l10n.commonClose,
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
@@ -490,7 +497,7 @@ class _CountrySheetState extends State<_CountrySheet> {
|
||||
Expanded(
|
||||
child: ListView(
|
||||
children: [
|
||||
for (final c in isoCountries)
|
||||
for (final c in sortedCountries())
|
||||
ListTile(
|
||||
onTap: () => setState(() => _picked = c),
|
||||
leading: Text(
|
||||
@@ -739,14 +746,18 @@ class _PermissionStepState extends State<_PermissionStep> {
|
||||
/// 단계 화면 틀 — 사본 가입 화면과 같은 코스믹 다크 · 하단 고정 버튼.
|
||||
class _StepScaffold extends StatelessWidget {
|
||||
const _StepScaffold({
|
||||
required this.title,
|
||||
this.title,
|
||||
required this.action,
|
||||
required this.children,
|
||||
this.subtitle,
|
||||
this.onBack,
|
||||
this.barTitle,
|
||||
this.boxedInputs = false,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String? title;
|
||||
final String? barTitle;
|
||||
final bool boxedInputs;
|
||||
final String? subtitle;
|
||||
final Widget action;
|
||||
final List<Widget> children;
|
||||
@@ -757,6 +768,8 @@ class _StepScaffold extends StatelessWidget {
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
leading: onBack == null ? null : BackButton(onPressed: onBack),
|
||||
title: barTitle == null ? null : Text(barTitle!),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
@@ -764,17 +777,22 @@ class _StepScaffold extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(AppSpace.s4),
|
||||
children: [
|
||||
Text(title, style: _title),
|
||||
if (subtitle != null) ...[
|
||||
const SizedBox(height: AppSpace.s2),
|
||||
Text(subtitle!, style: _body),
|
||||
child: _boxed(
|
||||
context,
|
||||
ListView(
|
||||
padding: const EdgeInsets.all(AppSpace.s4),
|
||||
children: [
|
||||
if (title != null) ...[
|
||||
Text(title!, style: _title),
|
||||
if (subtitle != null) ...[
|
||||
const SizedBox(height: AppSpace.s2),
|
||||
Text(subtitle!, style: _body),
|
||||
],
|
||||
const SizedBox(height: AppSpace.s5),
|
||||
],
|
||||
...children,
|
||||
],
|
||||
const SizedBox(height: AppSpace.s5),
|
||||
...children,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(padding: const EdgeInsets.all(AppSpace.s4), child: action),
|
||||
@@ -782,6 +800,28 @@ class _StepScaffold extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
/// 사본 가입 화면 입력란 — 회색 테두리 상자(약관 상자와 같은 선 · 모서리 8).
|
||||
Widget _boxed(BuildContext context, Widget child) {
|
||||
if (!boxedInputs) return child;
|
||||
OutlineInputBorder line(Color c, [double w = 1]) => OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.sm),
|
||||
borderSide: BorderSide(color: c, width: w),
|
||||
);
|
||||
final theme = Theme.of(context);
|
||||
return Theme(
|
||||
data: theme.copyWith(
|
||||
inputDecorationTheme: theme.inputDecorationTheme.copyWith(
|
||||
border: line(_stroke),
|
||||
enabledBorder: line(_stroke),
|
||||
focusedBorder: line(AppColors.cosmicAccent, 2),
|
||||
errorBorder: line(AppColors.error),
|
||||
focusedErrorBorder: line(AppColors.error, 2),
|
||||
),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PrimaryButton extends StatelessWidget {
|
||||
@@ -803,6 +843,9 @@ class _PrimaryButton extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
/// 사본 입력란 테두리 — 약관 상자(`signup_screens.dart`)와 같은 값.
|
||||
const _stroke = Color(0xFF545454);
|
||||
|
||||
const _title = TextStyle(
|
||||
fontFamily: AppFonts.body,
|
||||
fontSize: 20,
|
||||
@@ -822,6 +865,19 @@ const _body = TextStyle(
|
||||
String countryFlag(String code) =>
|
||||
String.fromCharCodes(code.codeUnits.map((c) => c - 0x41 + 0x1F1E6));
|
||||
|
||||
/// 국가 시트 순서 — 기기 OS 정렬([platformCountryNames] 순서)을 따르고, OS 가 모르는 코드는 표시명 순으로 뒤에 둔다.
|
||||
/// 사본 `178:23489` 목록은 (첫 줄 독일을 빼면) 국가명 가나다순이고 검색 칸이 없다. 범위는 Q-E10 회신 대기.
|
||||
List<String> sortedCountries() {
|
||||
final rest = isoCountries.toSet();
|
||||
final ordered = [
|
||||
for (final c in platformCountryNames.keys)
|
||||
if (rest.remove(c)) c,
|
||||
];
|
||||
return ordered..addAll(
|
||||
rest.toList()..sort((a, b) => countryName(a).compareTo(countryName(b))),
|
||||
);
|
||||
}
|
||||
|
||||
/// ISO 3166-1 alpha-2 전체. 랭킹 집계 · Admin 사용자 일람의 「거주 국가/지역」 값이다.
|
||||
final List<String> isoCountries =
|
||||
('AD AE AF AG AI AL AM AO AQ AR AS AT AU AW AX AZ BA BB BD BE BF BG BH BI BJ BL BM BN BO BQ '
|
||||
|
||||
@@ -126,7 +126,15 @@ class _TermsScreenState extends State<TermsScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(context.l10n.termsTitle)),
|
||||
// Figma `179:24230` 머리줄 좌측 「<」 — 「이전」과 같은 동작(온보딩 안에서는 로그아웃 후 로그인 화면).
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
leading: BackButton(
|
||||
onPressed: widget.onBack ?? () => Navigator.of(context).pop(),
|
||||
),
|
||||
title: Text(context.l10n.termsTitle),
|
||||
centerTitle: true,
|
||||
),
|
||||
// Figma `179:24230` — 「이전 · 완료」는 목록 끝이 아니라 화면 하단 고정(56 높이 · 간격 10 · 아래 16).
|
||||
// 시스템 내비게이션 바 위로 올린다(SafeArea) — 10/6 실기기에서 첫 탭이 바에 먹혔다.
|
||||
bottomNavigationBar: SafeArea(
|
||||
|
||||
Reference in New Issue
Block a user