新住戶流程UI設計和切版
This commit is contained in:
parent
bdb81665aa
commit
4ec947bfc3
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'home_page.dart'; // 要跳轉的頁面
|
||||
import 'new_resident_step.dart';
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
const LoginPage({super.key});
|
||||
@ -64,6 +65,15 @@ class _LoginPageState extends State<LoginPage> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
'社區通',
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.indigo,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
CircleAvatar(
|
||||
radius: 64,
|
||||
backgroundImage: AssetImage('assets/images/login.png'),
|
||||
@ -113,15 +123,43 @@ class _LoginPageState extends State<LoginPage> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextButton(
|
||||
onPressed: () {},
|
||||
child: const Text(
|
||||
'忘記密碼?',
|
||||
style: TextStyle(
|
||||
color: Colors.indigo,
|
||||
decoration: TextDecoration.underline,
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const NewResidentStepPage(),
|
||||
),
|
||||
);
|
||||
},
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.indigo,
|
||||
side: const BorderSide(color: Colors.indigo),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 12,
|
||||
),
|
||||
),
|
||||
child: const Text('新住戶'),
|
||||
),
|
||||
),
|
||||
OutlinedButton(
|
||||
onPressed: () {
|
||||
// TODO: 忘記密碼事件
|
||||
},
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.indigo,
|
||||
side: const BorderSide(color: Colors.indigo),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 12,
|
||||
),
|
||||
),
|
||||
child: const Text('忘記密碼'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
424
lib/new_resident_step.dart
Normal file
424
lib/new_resident_step.dart
Normal file
@ -0,0 +1,424 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NewResidentStepPage extends StatefulWidget {
|
||||
const NewResidentStepPage({super.key});
|
||||
|
||||
@override
|
||||
State<NewResidentStepPage> createState() => _NewResidentStepPageState();
|
||||
}
|
||||
|
||||
class _NewResidentStepPageState extends State<NewResidentStepPage> {
|
||||
int currentStep = 1;
|
||||
final TextEditingController _emailController = TextEditingController();
|
||||
final List<TextEditingController> _codeControllers = List.generate(
|
||||
6,
|
||||
(_) => TextEditingController(),
|
||||
);
|
||||
|
||||
String? statusMessage;
|
||||
bool isSuccess = false;
|
||||
|
||||
void sendVerification() {
|
||||
final email = _emailController.text.trim();
|
||||
if (email.contains("@")) {
|
||||
setState(() {
|
||||
isSuccess = true;
|
||||
statusMessage = '📧 驗證碼已寄出至 $email';
|
||||
currentStep = 2;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
isSuccess = false;
|
||||
statusMessage = '❌ 請輸入有效的 Email';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void verifyCode() {
|
||||
final code = _codeControllers.map((c) => c.text).join();
|
||||
if (RegExp(r'^\d{6}$').hasMatch(code)) {
|
||||
setState(() {
|
||||
isSuccess = true;
|
||||
statusMessage = '✅ 驗證成功!';
|
||||
currentStep = 3;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
isSuccess = false;
|
||||
statusMessage = '❌ 請輸入正確的 6 位數驗證碼';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildStep1() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Text(
|
||||
'步驟 1 之 3',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const Text(
|
||||
'輸入電子郵件',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
TextField(
|
||||
controller: _emailController,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
decoration: const InputDecoration(
|
||||
labelText: '電子郵件',
|
||||
border: OutlineInputBorder(),
|
||||
hintText: 'example@mail.com',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: sendVerification,
|
||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.indigo),
|
||||
child: const Text('發送驗證碼', style: TextStyle(color: Colors.white)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildStep2() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Text(
|
||||
'步驟 2 之 3',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const Text(
|
||||
'請輸入驗證碼',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const Text(
|
||||
'我們已將 6 位數驗證碼寄至您的 Email',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: List.generate(6, (i) {
|
||||
return Container(
|
||||
width: 40,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: TextField(
|
||||
controller: _codeControllers[i],
|
||||
maxLength: 1,
|
||||
textAlign: TextAlign.center,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: const InputDecoration(counterText: ''),
|
||||
onChanged: (val) {
|
||||
if (val.length == 1 && i < 5) {
|
||||
FocusScope.of(context).nextFocus();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: verifyCode,
|
||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.indigo),
|
||||
child: const Text('驗證並繼續', style: TextStyle(color: Colors.white)),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
statusMessage = '📨 驗證碼已重新寄出,請再次查收 Email。';
|
||||
});
|
||||
},
|
||||
child: const Text('重新發送驗證碼'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// 在 State 裡新增這些 controller 和變數:
|
||||
final nameController = TextEditingController();
|
||||
final birthdayController = TextEditingController();
|
||||
final phoneController = TextEditingController();
|
||||
final roomController = TextEditingController();
|
||||
final plateController = TextEditingController();
|
||||
final passwordController = TextEditingController();
|
||||
final confirmPasswordController = TextEditingController();
|
||||
String? selectedGender;
|
||||
|
||||
// 表單驗證 function
|
||||
bool validateStep3Fields() {
|
||||
return true;
|
||||
/*
|
||||
nameController.text.isNotEmpty &&
|
||||
selectedGender != null &&
|
||||
birthdayController.text.isNotEmpty &&
|
||||
phoneController.text.isNotEmpty &&
|
||||
roomController.text.isNotEmpty &&
|
||||
plateController.text.isNotEmpty &&
|
||||
passwordController.text.isNotEmpty &&
|
||||
confirmPasswordController.text.isNotEmpty &&
|
||||
passwordController.text == confirmPasswordController.text;
|
||||
*/
|
||||
}
|
||||
|
||||
// buildStep3 widget
|
||||
Widget buildStep3() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Text(
|
||||
'步驟 3 之 3',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 14, color: Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const Text(
|
||||
'填寫基本資料',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
const Text(
|
||||
'請輸入您的基本資訊以完成註冊',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
|
||||
TextField(
|
||||
controller: nameController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: '姓名',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
DropdownButtonFormField<String>(
|
||||
value: selectedGender,
|
||||
items: const [
|
||||
DropdownMenuItem(value: '男', child: Text('男')),
|
||||
DropdownMenuItem(value: '女', child: Text('女')),
|
||||
DropdownMenuItem(value: '其他', child: Text('其他')),
|
||||
],
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
selectedGender = value;
|
||||
});
|
||||
},
|
||||
decoration: const InputDecoration(
|
||||
labelText: '性別',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: birthdayController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: '生日',
|
||||
hintText: 'YYYY-MM-DD',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: TextInputType.datetime,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: phoneController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: '手機號碼',
|
||||
hintText: '例如:0912345678',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: TextInputType.phone,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: roomController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: '房號 / 室別',
|
||||
hintText: '例如:A棟 5F-2',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: plateController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: '車牌號碼',
|
||||
hintText: '例如:ABC-1234',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: passwordController,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(
|
||||
labelText: '密碼',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: confirmPasswordController,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(
|
||||
labelText: '確認密碼',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
if (validateStep3Fields()) {
|
||||
setState(() {
|
||||
currentStep = 4;
|
||||
statusMessage = null;
|
||||
isSuccess = true;
|
||||
});
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('✅ 註冊完成!歡迎加入社區!')));
|
||||
} else {
|
||||
setState(() {
|
||||
isSuccess = false;
|
||||
statusMessage = '❌ 請完整填寫所有欄位,並確認密碼一致';
|
||||
});
|
||||
}
|
||||
},
|
||||
style: ElevatedButton.styleFrom(backgroundColor: Colors.green),
|
||||
child: const Text('完成註冊', style: TextStyle(color: Colors.white)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildStep4() {
|
||||
return Column(
|
||||
children: [
|
||||
// Body content
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min, // 讓 Column 的高度根據內容來調整
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Text(
|
||||
'親愛的用戶您好',
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF333333),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
const Text(
|
||||
'您的新用戶申請已送至管理室,請您拿手機至管理室由管理員掃描開通。',
|
||||
style: TextStyle(fontSize: 16, color: Color(0xFF666666)),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: () {
|
||||
// 跳轉到掃描開通畫面
|
||||
Navigator.pushNamed(context, '/scan_activate');
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: const Color(0xFF4CAF50),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
),
|
||||
child: const Text(
|
||||
'掃描開通',
|
||||
style: TextStyle(fontSize: 16, color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: const Color(0xFFF7F8FA),
|
||||
appBar: AppBar(
|
||||
backgroundColor: const Color(0xFF9EAF9F),
|
||||
foregroundColor: Colors.white,
|
||||
title: const Text('新住戶註冊'),
|
||||
leading:
|
||||
currentStep > 1
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
currentStep--;
|
||||
});
|
||||
},
|
||||
)
|
||||
: null,
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 3)],
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
buildStepContent(),
|
||||
const SizedBox(height: 20),
|
||||
if (statusMessage != null)
|
||||
Text(
|
||||
statusMessage!,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: isSuccess ? Colors.green : Colors.red,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildStepContent() {
|
||||
switch (currentStep) {
|
||||
case 1:
|
||||
return buildStep1();
|
||||
case 2:
|
||||
return buildStep2();
|
||||
case 3:
|
||||
return buildStep3();
|
||||
case 4:
|
||||
return buildStep4();
|
||||
default:
|
||||
return const Text('未知步驟');
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user