Files
flutterApp/lib/features/auth/presentation/pages/login_page.dart

315 lines
12 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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';
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';
import '../../../../core/router/route_paths.dart';
import '../bloc/login_cubit.dart';
import '../bloc/login_state.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final _userCtrl = TextEditingController();
final _pwdCtrl = TextEditingController();
bool _isAgreed = false; // 协议勾选状态
bool _obscurePwd = true; // 密码可见性
@override
void dispose() {
_userCtrl.dispose();
_pwdCtrl.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
// 保持 AppBar 简洁或直接去掉
appBar: AppBar(backgroundColor: Colors.white, elevation: 0),
body: BlocListener<LoginCubit, LoginState>(
listener: (context, state) {
if (state is LoginSuccess) {
context.go(RoutePaths.home);
} else if (state is LoginFailure) {
// 可以在这里弹出简洁的黑白提示框
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('登录失败,${state.message}')));
}
},
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,
),
),
),
],
),
),
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: "请输入用户名"),
),
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),
),
),
),
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,
),
),
),
),
],
),
),
const SizedBox(height: 20),
],
),
),
),
);
},
),
),
);
}
// 执行登录逻辑
void _handleLogin() {
context.read<LoginCubit>().login(_userCtrl.text, _pwdCtrl.text, 2);
//final user = sl<AppUserCubit>().state.user;
//final devicesCubit = sl<DevicesCubit>();
//if (user != null) {
// devicesCubit.fetchAllDevices(user.username);
//}
}
// 辅助组件:输入框标签
Widget _buildInputLabel(String label) {
return Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(label, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600)),
);
}
// 辅助组件:输入框装饰
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!),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)),
),
),
const SizedBox(width: 8),
Expanded(
child: Wrap(
children: [
const Text("我已阅读并同意 ", style: TextStyle(fontSize: 13, color: Colors.grey)),
_protocolText("《用户协议》", () => _showProtocolDetail("用户协议")),
const Text(" 和 ", style: TextStyle(fontSize: 13, color: Colors.grey)),
_protocolText("《隐私政策》", () => _showProtocolDetail("隐私政策")),
],
),
),
],
);
}
Widget _protocolText(String text, VoidCallback onTap) {
return GestureDetector(
onTap: onTap,
child: Text(
text,
style: const TextStyle(fontSize: 13, color: Colors.black, fontWeight: FontWeight.bold),
),
);
}
void _showProtocolDetail(String title) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.white,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(16))),
builder: (context) => Container(
padding: const EdgeInsets.all(24),
height: MediaQuery.of(context).size.height * 0.7,
child: Column(
children: [
Text(title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const Divider(height: 32),
Expanded(
child: SingleChildScrollView(
child: Text("此处放置您的${title}详细内容...\n" * 20, style: const TextStyle(color: Colors.black87, height: 1.5)),
),
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: BlocBuilder<LoginCubit, LoginState>(
builder: (context, state) {
return CCPrimaryButton(onPressed: () => Navigator.pop(context), text: "我已了解");
},
),
),
],
),
),
);
}
}