Files
feature-next-arch/lib/features/auth/presentation/pages/login_page.dart

315 lines
12 KiB
Dart
Raw Normal View History

2026-01-11 19:42:22 +08:00
import 'package:cc_ui_kit/cc_ui_kit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
2026-03-05 10:24:38 +08:00
import 'package:maibu_satabot_v2/core/app/app_user_cubit.dart';
import 'package:maibu_satabot_v2/core/di/injection.dart';
import 'package:maibu_satabot_v2/features/devices/presentation/bloc/devices_cubit.dart';
2026-01-11 19:42:22 +08:00
import '../../../../core/router/route_paths.dart';
import '../bloc/login_cubit.dart';
import '../bloc/login_state.dart';
2026-01-18 20:21:14 +08:00
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
2026-01-11 19:42:22 +08:00
final _userCtrl = TextEditingController();
final _pwdCtrl = TextEditingController();
2026-01-18 20:21:14 +08:00
bool _isAgreed = false; // 协议勾选状态
bool _obscurePwd = true; // 密码可见性
2026-01-11 19:42:22 +08:00
2026-01-18 20:21:14 +08:00
@override
void dispose() {
_userCtrl.dispose();
_pwdCtrl.dispose();
super.dispose();
}
2026-01-11 19:42:22 +08:00
@override
Widget build(BuildContext context) {
return Scaffold(
2026-01-18 20:21:14 +08:00
backgroundColor: Colors.white,
// 保持 AppBar 简洁或直接去掉
appBar: AppBar(backgroundColor: Colors.white, elevation: 0),
2026-01-11 19:42:22 +08:00
body: BlocListener<LoginCubit, LoginState>(
listener: (context, state) {
if (state is LoginSuccess) {
2026-05-13 10:34:07 +08:00
context.go(RoutePaths.home);
2026-01-18 20:21:14 +08:00
} else if (state is LoginFailure) {
// 可以在这里弹出简洁的黑白提示框
2026-03-05 10:24:38 +08:00
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('登录失败,${state.message}')));
2026-01-11 19:42:22 +08:00
}
},
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 顶部区域:图片 + 认证信息 + 标题(压缩空间)
const SizedBox(height: 10),
// 顶部居中图片和认证信息
Center(
child: Column(
children: [
Image.asset(
'assets/images/huawei.png',
width: 60,
height: 60,
fit: BoxFit.contain,
),
const SizedBox(height: 6),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Text(
"通过华为云生态技术认证,请放心使用,证书编号C202605301",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 10,
color: Colors.grey.shade600,
height: 1.3,
),
),
),
],
2026-05-19 08:34:35 +08:00
),
),
const SizedBox(height: 16),
const Text(
"账号登录",
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.black),
),
const SizedBox(height: 8),
const Text("请填写以下信息以验证身份", style: TextStyle(fontSize: 15, color: Colors.grey)),
const SizedBox(height: 24),
// 表单区域
// 账号输入框
_buildInputLabel("账号"),
TextField(
controller: _userCtrl,
cursorColor: Colors.black,
decoration: _inputDecoration(hint: "请输入用户名"),
),
2026-01-18 20:21:14 +08:00
const SizedBox(height: 20),
// 密码输入框
_buildInputLabel("密码"),
TextField(
controller: _pwdCtrl,
obscureText: _obscurePwd,
cursorColor: Colors.black,
decoration: _inputDecoration(
hint: "请输入密码",
suffixIcon: IconButton(
icon: Icon(_obscurePwd ? Icons.visibility_off_outlined : Icons.visibility_outlined, color: Colors.grey, size: 20),
onPressed: () => setState(() => _obscurePwd = !_obscurePwd),
),
),
),
2026-01-18 20:21:14 +08:00
const SizedBox(height: 16),
// 协议勾选区域
_buildProtocolSection(),
const SizedBox(height: 30),
// 底部区域:登录按钮 + 注册链接
SizedBox(
width: double.infinity,
child: BlocBuilder<LoginCubit, LoginState>(
builder: (context, state) {
bool isLoading = state is LoginLoading;
return CCPrimaryButton(
onPressed: isLoading
? null
: () {
if (!_isAgreed) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('请先阅读并同意用户协议'),
duration: Duration(seconds: 1),
),
);
return;
}
_handleLogin();
},
text: isLoading ? '登录中...' : '登 录',
);
},
),
),
const SizedBox(height: 16),
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
"没有账号吗?",
style: TextStyle(fontSize: 14, color: Colors.grey),
),
InkWell(
onTap: () {
debugPrint(' 点击了立即注册');
debugPrint('👉 跳转路径: ${RoutePaths.register}');
context.go(RoutePaths.register);
},
borderRadius: BorderRadius.circular(4),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Text(
"立即注册",
style: TextStyle(
fontSize: 14,
color: Colors.black,
fontWeight: FontWeight.bold,
2026-04-20 08:54:27 +08:00
),
),
),
),
],
2026-05-13 10:34:07 +08:00
),
),
const SizedBox(height: 20),
],
),
2026-05-13 10:34:07 +08:00
),
),
);
},
2026-01-18 20:21:14 +08:00
),
),
);
}
// 执行登录逻辑
void _handleLogin() {
context.read<LoginCubit>().login(_userCtrl.text, _pwdCtrl.text, 2);
2026-03-05 10:24:38 +08:00
//final user = sl<AppUserCubit>().state.user;
//final devicesCubit = sl<DevicesCubit>();
//if (user != null) {
// devicesCubit.fetchAllDevices(user.username);
//}
2026-01-18 20:21:14 +08:00
}
// 辅助组件:输入框标签
Widget _buildInputLabel(String label) {
return Padding(
padding: const EdgeInsets.only(bottom: 8.0),
2026-03-05 10:24:38 +08:00
child: Text(label, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
2026-01-18 20:21:14 +08:00
);
}
// 辅助组件:输入框装饰
InputDecoration _inputDecoration({required String hint, Widget? suffixIcon}) {
return InputDecoration(
hintText: hint,
hintStyle: TextStyle(color: Colors.grey.shade400, fontSize: 14),
filled: true,
fillColor: Colors.grey.shade50,
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: Colors.grey.shade200),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Colors.black, width: 1),
),
suffixIcon: suffixIcon,
);
}
// 辅助组件:协议勾选
Widget _buildProtocolSection() {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
height: 24,
width: 24,
child: Checkbox(
value: _isAgreed,
activeColor: Colors.black,
onChanged: (val) => setState(() => _isAgreed = val!),
2026-03-05 10:24:38 +08:00
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
2026-01-18 20:21:14 +08:00
),
),
const SizedBox(width: 8),
Expanded(
child: Wrap(
children: [
2026-03-05 10:24:38 +08:00
const Text("我已阅读并同意 ", style: TextStyle(fontSize: 13, color: Colors.grey)),
2026-01-18 20:21:14 +08:00
_protocolText("《用户协议》", () => _showProtocolDetail("用户协议")),
2026-03-05 10:24:38 +08:00
const Text(" 和 ", style: TextStyle(fontSize: 13, color: Colors.grey)),
2026-01-18 20:21:14 +08:00
_protocolText("《隐私政策》", () => _showProtocolDetail("隐私政策")),
],
),
),
],
);
}
Widget _protocolText(String text, VoidCallback onTap) {
return GestureDetector(
onTap: onTap,
child: Text(
text,
2026-03-05 10:24:38 +08:00
style: const TextStyle(fontSize: 13, color: Colors.black, fontWeight: FontWeight.bold),
2026-01-18 20:21:14 +08:00
),
);
}
void _showProtocolDetail(String title) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.white,
2026-03-05 10:24:38 +08:00
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))),
2026-01-18 20:21:14 +08:00
builder: (context) => Container(
padding: const EdgeInsets.all(24),
height: MediaQuery.of(context).size.height * 0.7,
2026-01-11 19:42:22 +08:00
child: Column(
children: [
2026-03-05 10:24:38 +08:00
Text(title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
2026-01-18 20:21:14 +08:00
const Divider(height: 32),
Expanded(
child: SingleChildScrollView(
2026-03-05 10:24:38 +08:00
child: Text("此处放置您的${title}详细内容...\n" * 20, style: const TextStyle(color: Colors.black87, height: 1.5)),
2026-01-18 20:21:14 +08:00
),
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: BlocBuilder<LoginCubit, LoginState>(
builder: (context, state) {
2026-03-05 10:24:38 +08:00
return CCPrimaryButton(onPressed: () => Navigator.pop(context), text: "我已了解");
2026-01-18 20:21:14 +08:00
},
),
2026-01-11 19:42:22 +08:00
),
],
),
),
);
}
}