113 lines
4.3 KiB
Dart
113 lines
4.3 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import '../theme.dart';
|
|
|
|
/// 처음 시작: 이름과 번호만 받는다. 키는 이 기기 안에서 조용히 만들고, 복구 코드는 없다.
|
|
///
|
|
/// [onStart] 가 문제를 돌려주면(번호·이름이 잘못됨) 아래에 보여 준다.
|
|
class OnboardingScreen extends StatefulWidget {
|
|
const OnboardingScreen({super.key, required this.onStart});
|
|
|
|
final Future<String?> Function(String phone, String name) onStart;
|
|
|
|
@override
|
|
State<OnboardingScreen> createState() => _OnboardingScreenState();
|
|
}
|
|
|
|
class _OnboardingScreenState extends State<OnboardingScreen> {
|
|
final _phone = TextEditingController();
|
|
final _name = TextEditingController();
|
|
String? _error;
|
|
bool _busy = false;
|
|
|
|
@override
|
|
void dispose() {
|
|
_phone.dispose();
|
|
_name.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
bool get _ready => _phone.text.trim().length >= 9 && _name.text.trim().isNotEmpty;
|
|
|
|
Future<void> _start() async {
|
|
FocusScope.of(context).unfocus();
|
|
setState(() {
|
|
_busy = true;
|
|
_error = null;
|
|
});
|
|
final err = await widget.onStart(_phone.text, _name.text);
|
|
if (mounted && err != null) {
|
|
setState(() {
|
|
_busy = false;
|
|
_error = err;
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
body: SafeArea(
|
|
child: LayoutBuilder(
|
|
builder: (context, box) => SingleChildScrollView(
|
|
padding: const EdgeInsets.fromLTRB(24, 0, 24, 24),
|
|
child: ConstrainedBox(
|
|
constraints: BoxConstraints(minHeight: box.maxHeight - 24),
|
|
child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
|
const SizedBox(height: 72),
|
|
Align(
|
|
alignment: Alignment.centerLeft,
|
|
child: Container(
|
|
width: 64,
|
|
height: 64,
|
|
decoration: BoxDecoration(color: Pal.accent, borderRadius: BorderRadius.circular(18)),
|
|
child: const Icon(Icons.hub, color: Colors.black, size: 36),
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
const Text('Friendly Map', style: TextStyle(fontSize: 28, fontWeight: FontWeight.w800)),
|
|
const SizedBox(height: 8),
|
|
const Text('대화하다 알게 된 관계를\n가족 · 학교 · 회사로 이어 두세요.', style: TextStyle(fontSize: 16, height: 1.5, color: Pal.muted)),
|
|
const SizedBox(height: 40),
|
|
TextField(
|
|
controller: _name,
|
|
textInputAction: TextInputAction.next,
|
|
onChanged: (_) => setState(() {}),
|
|
decoration: const InputDecoration(labelText: '이름'),
|
|
),
|
|
const SizedBox(height: 12),
|
|
TextField(
|
|
controller: _phone,
|
|
keyboardType: TextInputType.phone,
|
|
textInputAction: TextInputAction.done,
|
|
onChanged: (_) => setState(() {}),
|
|
onSubmitted: (_) => _ready && !_busy ? _start() : null,
|
|
decoration: const InputDecoration(labelText: '내 휴대폰 번호', hintText: '010-1234-5678'),
|
|
),
|
|
if (_error != null)
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 10),
|
|
child: Text(_error!, style: const TextStyle(color: Pal.danger, fontSize: 13)),
|
|
),
|
|
const SizedBox(height: 32),
|
|
const Text('모든 정보는 이 폰 안에 암호화되어 저장되고, 내가 보낸 것만 상대에게 가요.',
|
|
style: TextStyle(fontSize: 12, height: 1.5, color: Pal.muted)),
|
|
const SizedBox(height: 12),
|
|
SizedBox(
|
|
height: 52,
|
|
child: FilledButton(
|
|
onPressed: _ready && !_busy ? _start : null,
|
|
child: _busy
|
|
? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.black))
|
|
: const Text('시작하기', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700)),
|
|
),
|
|
),
|
|
]),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|