Commit 1ccaf81d by tanghuan

Merge branch 'feature-2604-menu-login' into feature-2604-menu

2 parents 8ef84934 cde8d380
Showing 46 changed files with 1315 additions and 6 deletions
......@@ -4,9 +4,9 @@ import 'package:appframe/bloc/web_cubit.dart';
import 'package:appframe/ui/pages/adv_page.dart';
import 'package:appframe/ui/pages/im_page.dart';
import 'package:appframe/ui/pages/link_page.dart';
import 'package:appframe/ui/pages/login_main_page.dart';
import 'package:appframe/ui/pages/login_phone_page.dart';
import 'package:appframe/ui/pages/login_qr_page.dart';
import 'package:appframe/ui/pages/login_v3/login_main_page_v3.dart';
import 'package:appframe/ui/pages/login_v3/login_phone_page_v3.dart';
import 'package:appframe/ui/pages/login_v3/login_qr_page_v3.dart';
import 'package:appframe/ui/pages/media/preview_media_page.dart';
import 'package:appframe/ui/pages/reload_page.dart';
import 'package:appframe/ui/pages/scan_code_page.dart';
......@@ -45,19 +45,22 @@ final GoRouter router = GoRouter(
GoRoute(
path: '/loginMain',
builder: (BuildContext context, GoRouterState state) {
return const LoginMainPage();
// return const LoginMainPage();
return const LoginMainPageV3();
},
),
GoRoute(
path: '/loginPhone',
builder: (BuildContext context, GoRouterState state) {
return const LoginPhonePage();
// return const LoginPhonePage();
return const LoginPhonePageV3();
},
),
GoRoute(
path: '/loginQr',
builder: (BuildContext context, GoRouterState state) {
return const LoginQrPage();
// return const LoginQrPage();
return const LoginQrPageV3();
},
),
GoRoute(
......
import 'dart:io';
import 'package:appframe/bloc/login_main_cubit.dart';
import 'package:appframe/config/routes.dart';
import 'package:appframe/ui/widgets/login/login_page_agreed_widget.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class LoginMainPageV3 extends StatelessWidget {
const LoginMainPageV3({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => LoginMainCubit(LoginMainState()),
child: BlocConsumer<LoginMainCubit, LoginMainState>(
builder: (context, state) {
final loginMainCubit = context.read<LoginMainCubit>();
final wechatLoginBtn = (!Platform.isIOS || state.wechatInstalled)
? [
const SizedBox(height: 45),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 42.5),
child: SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: () async {
if (state.wechatInstalled) {
loginMainCubit.wechatAuth();
} else {
if (!context.mounted) {
return;
}
_showWechatNotInstallDialog(context);
}
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF7691FA),
foregroundColor: Colors.white,
textStyle: const TextStyle(fontSize: 19),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(27),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset('assets/images/login_v3/wechat_btn_icon.png'),
const SizedBox(width: 4),
const Text(
'微信登录',
style: TextStyle(
fontSize: 18,
color: Color(0xFFFFFFFF),
),
),
],
),
),
),
)
]
: [
const SizedBox(height: 30),
];
final appleLoginBtn = Platform.isIOS
? [
const SizedBox(height: 15),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 42.5),
child: SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: () async {
loginMainCubit.appleAuth();
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF000000),
foregroundColor: Colors.white,
textStyle: const TextStyle(fontSize: 19),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(27),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset('assets/images/login_v3/apple_btn_icon.png'),
const SizedBox(width: 4),
const Text(
'通过Apple登录',
style: TextStyle(
fontSize: 18,
color: Color(0xFFFFFFFF),
),
),
],
),
),
),
),
]
: [];
final scaffold = Scaffold(
extendBodyBehindAppBar: true,
extendBody: true,
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 顶部图片 + 底部35dp白色重合条
Stack(
children: [
Image.asset(
'assets/images/login_v3/login_banner.png',
width: double.infinity,
fit: BoxFit.fitWidth,
),
Positioned(
left: 0,
right: 0,
bottom: 0,
height: 35,
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(25),
topRight: Radius.circular(25),
),
),
),
),
],
),
// 剩余区域白色铺底,一直延伸到屏幕底部
Expanded(
child: Container(
color: Colors.white,
child: Column(
children: [
...wechatLoginBtn,
...appleLoginBtn,
const SizedBox(height: 33.5),
_buildAgreement(context, loginMainCubit, state.agreed),
Expanded(
child: Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'其他方式登录',
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 14,
height: 1.0,
color: Color(0xFF999999),
),
),
const SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
InkWell(
onTap: () {
context.read<LoginMainCubit>().goLoginQr();
},
child: Image.asset(
'assets/images/login_v3/wechat_qrcode.png',
),
),
const SizedBox(width: 25),
InkWell(
onTap: () {
context.read<LoginMainCubit>().goLoginPhone();
},
child: Image.asset('assets/images/login_v3/phone.png'),
),
],
),
const SizedBox(height: 40),
Text(
'提示:本应用为学生端使用,教师请前往班小二小程序操作',
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 12,
height: 20 / 12,
color: Color(0xFF999999),
),
),
],
),
),
),
),
],
),
),
),
],
),
);
return Stack(
children: [
scaffold,
state.loading
? Container(
color: Colors.black54,
width: MediaQuery.of(context).size.width,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(
color: Color(0xFF7691FA),
),
],
),
),
)
: SizedBox(),
],
);
},
listener: (context, state) {
if (state.showAgreed) {
_showAgreementDialog(context, context.read<LoginMainCubit>());
} else if (state.showNeedWechatForApple) {
_showNeedWechatDialogForApple(context, context.read<LoginMainCubit>());
} else if (state.showPrivacyFirstTime) {
_showPrivacyFirstTimeDialog(context, context.read<LoginMainCubit>());
}
},
),
);
}
Widget _buildAgreement(BuildContext context, LoginMainCubit loginMainCubit, bool agreed) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Theme(
data: Theme.of(context).copyWith(
checkboxTheme: CheckboxThemeData(
shape: CircleBorder(
side: BorderSide(width: 0.5, color: Color(0xFF999999)),
),
fillColor: WidgetStateProperty.resolveWith<Color>(
(Set<WidgetState> states) {
if (states.contains(WidgetState.selected)) {
return Color(0xFF7691FA); // 选中时的颜色
}
return Colors.white; // 未选中时的颜色
},
),
checkColor: WidgetStateProperty.all<Color>(Colors.white),
side: BorderSide(width: 0.5, color: Color(0xFF999999)),
),
cardColor: Colors.white,
unselectedWidgetColor: Color(0xFF999999),
),
child: SizedBox(
height: 19, // 设置高度为19
child: Checkbox(
value: agreed,
onChanged: (bool? value) {
loginMainCubit.toggleAgreed(value!);
},
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
),
),
LoginPageAgreedWidget(),
],
);
}
Future<void> _showAgreementDialog(BuildContext context, LoginMainCubit loginMainCubit) async {
final result = await showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(5),
),
),
title: Text(
'个人信息保护指引',
style: TextStyle(
fontSize: 17,
color: Color(0xFF000000),
// fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
content: Text.rich(
TextSpan(
children: [
TextSpan(
text: '感谢使用班小二APP,为保护您的个人权益,请仔细阅读并充分理解',
style: TextStyle(color: Color(0xFF666666), fontSize: 14),
),
TextSpan(
text: '《班小二数据安全和隐私政策》',
style: TextStyle(color: Color(0xFF7691FA), fontSize: 14),
recognizer: TapGestureRecognizer()
..onTap = () {
router.push(
'/link',
extra: {'url': 'https://bxr.banxiaoer.net/apps/privacysettings.html', 'title': '隐私保障'},
);
},
),
TextSpan(
text: '与',
style: TextStyle(color: Color(0xFF666666), fontSize: 14),
),
TextSpan(
text: '《用户协议》',
style: TextStyle(color: Color(0xFF7691FA), fontSize: 14),
recognizer: TapGestureRecognizer()
..onTap = () {
router.push(
'/link',
extra: {'url': 'https://bxr.banxiaoer.net/apps/useragreement.html', 'title': '用户协议'},
);
},
),
TextSpan(
text: '。如您同意上述文件的全部内容,请点击"同意"以继续。',
style: TextStyle(color: Color(0xFF666666), fontSize: 14),
),
],
),
),
actions: [
Table(
children: [
TableRow(
children: [
TableCell(
child: TextButton(
onPressed: () {
Navigator.of(context).pop();
},
style: TextButton.styleFrom(
foregroundColor: Color(0xFF666666),
textStyle: TextStyle(fontSize: 17),
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
),
),
child: Text('放弃登录'),
),
),
TableCell(
child: TextButton(
onPressed: () {
Navigator.of(context).pop('OK');
},
style: TextButton.styleFrom(
foregroundColor: Color(0xFF7691FA),
textStyle: TextStyle(fontSize: 17),
minimumSize: Size.fromHeight(40),
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
),
),
child: Text('同意'),
),
),
],
),
],
),
],
);
},
);
if (result != null) {
loginMainCubit.confirmAgreed();
} else {
loginMainCubit.cancelAgreed();
}
}
Future<void> _showWechatNotInstallDialog(BuildContext context) async {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(5),
),
),
title: const Text(
'提示',
style: TextStyle(
fontSize: 17,
color: Color(0xFF000000),
),
textAlign: TextAlign.center,
),
content: const Text.rich(
TextSpan(
text: '此设备未安装微信App,请选择其他方式登录。',
style: TextStyle(color: Color(0xFF666666), fontSize: 14),
),
),
actions: [
Table(
children: [
TableRow(
children: [
TableCell(
child: TextButton(
onPressed: () {
Navigator.of(context).pop('OK');
},
style: TextButton.styleFrom(
foregroundColor: const Color(0xFF7691FA),
textStyle: const TextStyle(fontSize: 17),
minimumSize: const Size.fromHeight(40),
padding: EdgeInsets.zero,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
),
),
child: const Text('确定'),
),
),
],
),
],
),
],
);
},
);
}
void _showNeedWechatDialogForApple(BuildContext context, LoginMainCubit loginMainCubit) {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext ctx) {
return PopScope(
canPop: false,
child: AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(5),
),
),
title: Text(
'温馨提示',
style: TextStyle(
fontSize: 17,
color: Color(0xFF000000),
// fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
content: Text.rich(
TextSpan(
text: '为了避免您之前在微信小程序的使用数据不丢失,必须绑定微信才可以继续!',
style: TextStyle(color: Color(0xFF666666), fontSize: 14),
),
),
actions: [
Center(
child: TextButton(
onPressed: () {
Navigator.of(ctx).pop('OK');
loginMainCubit.wechatAuthForApple();
},
style: TextButton.styleFrom(
foregroundColor: Color(0xFF7691FA),
textStyle: TextStyle(fontSize: 17),
minimumSize: Size.fromHeight(40),
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
),
),
child: Text('绑定微信'),
),
),
],
),
);
},
);
}
/// 首次打开显示个人信息收集提示弹窗
/// 值针对iOS,Android用原生界面显示
Future<void> _showPrivacyFirstTimeDialog(BuildContext context, LoginMainCubit loginMainCubit) async {
await showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(5),
),
),
title: Text(
'重要提示',
style: TextStyle(
fontSize: 17,
color: Color(0xFF000000),
),
textAlign: TextAlign.center,
),
content: SingleChildScrollView(
child: Text.rich(
TextSpan(
children: [
TextSpan(
text: '尊敬的用户,欢迎使用班小二APP!\n\n',
style: TextStyle(color: Color(0xFF666666), fontSize: 14),
),
// TextSpan(
// text: '为了向您提供更好的服务,我们需要收集以下个人信息:\n\n',
// style: TextStyle(color: Color(0xFF666666), fontSize: 14),
// ),
// TextSpan(
// text: '• 收集目的:用于用户身份识别、登录验证、提供教育相关服务\n',
// style: TextStyle(color: Color(0xFF666666), fontSize: 14),
// ),
// TextSpan(
// text: '• 收集方式:通过微信授权、手机号验证等方式获取\n',
// style: TextStyle(color: Color(0xFF666666), fontSize: 14),
// ),
// TextSpan(
// text: '• 信息范围:包括您的微信昵称、头像、手机号、学生信息等\n\n',
// style: TextStyle(color: Color(0xFF666666), fontSize: 14),
// ),
TextSpan(
text: '我们将严格遵守',
style: TextStyle(color: Color(0xFF666666), fontSize: 14),
),
TextSpan(
text: '《班小二数据安全和隐私政策》',
style: TextStyle(color: Color(0xFF7691FA), fontSize: 14),
recognizer: TapGestureRecognizer()
..onTap = () {
router.push(
'/link',
extra: {'url': 'https://bxr.banxiaoer.net/apps/privacysettings.html', 'title': '隐私保障'},
);
},
),
TextSpan(
text: '与',
style: TextStyle(color: Color(0xFF666666), fontSize: 14),
),
TextSpan(
text: '《用户协议》',
style: TextStyle(color: Color(0xFF7691FA), fontSize: 14),
recognizer: TapGestureRecognizer()
..onTap = () {
router.push(
'/link',
extra: {'url': 'https://bxr.banxiaoer.net/apps/useragreement.html', 'title': '用户协议'},
);
},
),
TextSpan(
text: '保护您的个人信息安全。',
style: TextStyle(color: Color(0xFF666666), fontSize: 14),
),
],
),
),
),
actions: [
Center(
child: TextButton(
onPressed: () {
Navigator.of(context).pop();
loginMainCubit.confirmPrivacyFirstTime();
},
style: TextButton.styleFrom(
foregroundColor: Color(0xFF7691FA),
textStyle: TextStyle(fontSize: 17),
minimumSize: Size.fromHeight(40),
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
),
),
child: Text('我知道了'),
),
),
],
);
},
);
}
}
import 'package:appframe/bloc/login_phone_cubit.dart';
import 'package:appframe/config/routes.dart';
import 'package:appframe/ui/widgets/login/login_page_agreed_widget.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class LoginPhonePageV3 extends StatelessWidget {
const LoginPhonePageV3({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => LoginPhoneCubit(LoginPhoneState()),
child: BlocConsumer<LoginPhoneCubit, LoginPhoneState>(
builder: (context, state) {
var loginPhoneCubit = context.read<LoginPhoneCubit>();
return Scaffold(
extendBodyBehindAppBar: true,
extendBody: true,
resizeToAvoidBottomInset: false,
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 顶部图片 + 底部35dp白色重合条
Stack(
children: [
Image.asset(
'assets/images/login_v3/login_banner.png',
width: double.infinity,
fit: BoxFit.fitWidth,
),
Positioned(
left: 0,
right: 0,
bottom: 0,
height: 35,
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(25),
topRight: Radius.circular(25),
),
),
),
),
],
),
// 剩余区域白色铺底,一直延伸到屏幕底部
Expanded(
child: Container(
color: Colors.white,
child: Column(
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 30),
child: Column(children: [
_buildInputFields(loginPhoneCubit, state),
SizedBox(height: 15),
_buildLoginButton(context),
]),
),
SizedBox(height: 15),
_buildAgreement(context, loginPhoneCubit, state.agreed),
Expanded(
child: Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'其他方式登录',
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 14,
height: 1.0,
color: Color(0xFF999999),
),
),
const SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
InkWell(
onTap: () {
context.read<LoginPhoneCubit>().goLoginMain();
},
child: Image.asset(
'assets/images/login_v3/wechat.png',
),
),
SizedBox(width: 25),
InkWell(
onTap: () {
context.read<LoginPhoneCubit>().goLoginQr();
},
child: Image.asset(
'assets/images/login_v3/wechat_qrcode.png',
),
)
],
),
const SizedBox(height: 40),
Text(
'提示:本应用为学生端使用,教师请前往班小二小程序操作',
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 12,
height: 20 / 12,
color: Color(0xFF999999),
),
),
],
),
),
),
),
],
),
),
),
],
),
);
},
listener: (context, state) {
if (state.showAgreed) {
_showAgreementDialog(context, context.read<LoginPhoneCubit>());
}
},
));
}
Widget _buildInputFields(LoginPhoneCubit loginPhoneCubit, LoginPhoneState state) {
return Column(
children: [
// 手机号输入框
Container(
height: 60,
decoration: BoxDecoration(
color: Color(0xFFF7F9FF),
borderRadius: BorderRadius.circular(10),
),
child: TextField(
controller: loginPhoneCubit.phoneController,
keyboardType: TextInputType.phone,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(11), // 使用这个来限制长度
FilteringTextInputFormatter.allow(RegExp(r'^1[0-9]{0,10}$')),
],
decoration: InputDecoration(
hintText: '请输入手机号',
hintStyle: TextStyle(
fontSize: 18,
height: 1.0,
color: Color(0xFFCCCCCC),
),
border: InputBorder.none,
contentPadding: EdgeInsets.fromLTRB(0, 22, 0, 22),
// 左右内边距,垂直居中
prefixIcon: Container(
margin: EdgeInsets.only(left: 15), // 控制左边距
child: Image.asset(
'assets/images/login_v3/phone_icon.png',
width: 25.5,
height: 25.5,
fit: BoxFit.contain,
),
),
),
style: TextStyle(
fontSize: 18,
color: Color(0xFF000000),
),
),
),
// 手机号输入框和发送按钮之间的间隔
SizedBox(height: 15),
// 验证码输入框和发送按钮
Container(
height: 60,
decoration: BoxDecoration(
color: Color(0xFFF7F9FF),
borderRadius: BorderRadius.circular(10),
),
child: Stack(
children: [
TextField(
controller: loginPhoneCubit.codeController,
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(4), // 使用这个来限制长度
],
decoration: InputDecoration(
hintText: '请输入验证码',
hintStyle: TextStyle(
fontSize: 18,
height: 1.0,
color: Color(0xFFCCCCCC),
),
border: InputBorder.none,
contentPadding: EdgeInsets.fromLTRB(0, 22, 0, 22),
// 左右内边距,垂直居中
prefixIcon: Container(
margin: EdgeInsets.only(left: 15), // 控制左边距
child: Image.asset(
'assets/images/login_v3/shield_icon.png',
width: 25.5,
height: 25.5,
fit: BoxFit.contain,
),
),
),
style: TextStyle(
fontSize: 18,
color: Color(0xFF000000),
),
),
Positioned(
right: 0,
top: 0,
bottom: 0,
child: Container(
width: 100,
decoration: BoxDecoration(
color: Colors.transparent,
),
child: TextButton(
onPressed: () {
if (state.allowSend) {
loginPhoneCubit.sendVerificationCode();
}
},
style: TextButton.styleFrom(
backgroundColor: Colors.transparent,
// foregroundColor: Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
padding: EdgeInsets.zero,
),
child: Text(
state.allowSend ? '发送验证码' : '${state.seconds}s后可重发',
style: TextStyle(
fontSize: 16,
height: 1.0,
color: Color(0xFF7691FA),
fontWeight: FontWeight.normal,
),
),
),
),
),
],
),
),
],
);
}
Widget _buildLoginButton(BuildContext context) {
return SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: () {
context.read<LoginPhoneCubit>().phoneAuth();
},
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF7691FA),
foregroundColor: Colors.white,
textStyle: TextStyle(fontSize: 19),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25),
),
),
child: Text(
'手机号登录',
style: TextStyle(
fontSize: 18,
height: 1.0,
fontWeight: FontWeight.normal,
color: Color(0xFFFFFFFF),
),
),
),
);
}
Widget _buildAgreement(BuildContext context, LoginPhoneCubit loginPhoneCubit, bool agreed) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Theme(
data: Theme.of(context).copyWith(
checkboxTheme: CheckboxThemeData(
shape: CircleBorder(
side: BorderSide(width: 0.5, color: Color(0xFF999999)),
),
fillColor: WidgetStateProperty.resolveWith<Color>(
(Set<WidgetState> states) {
if (states.contains(WidgetState.selected)) {
return Color(0xFF7691FA); // 选中时的颜色
}
return Colors.white; // 未选中时的颜色
},
),
checkColor: WidgetStateProperty.all<Color>(Colors.white),
side: BorderSide(width: 0.5, color: Color(0xFF999999)),
),
cardColor: Colors.white,
unselectedWidgetColor: Color(0xFF999999),
),
child: SizedBox(
height: 19,
child: Checkbox(
value: agreed,
onChanged: (bool? value) {
loginPhoneCubit.toggleAgreed(value!);
},
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, // 缩小点击区域,减小间隔
),
),
),
LoginPageAgreedWidget(),
],
);
}
Future<void> _showAgreementDialog(BuildContext context, LoginPhoneCubit loginPhoneCubit) async {
final result = await showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(5),
),
),
title: Text(
'个人信息保护指引',
style: TextStyle(
fontSize: 17,
color: Color(0xFF000000),
// fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
content: Text.rich(
TextSpan(
children: [
TextSpan(
text: '感谢使用班小二APP,为保护您的个人权益,请仔细阅读并充分理解',
style: TextStyle(color: Color(0xFF666666), fontSize: 14),
),
TextSpan(
text: '《班小二数据据安全和隐私政策》',
style: TextStyle(color: Color(0xFF7691FA), fontSize: 14),
recognizer: TapGestureRecognizer()
..onTap = () {
router.push(
'/link',
extra: {'url': 'https://bxr.banxiaoer.net/apps/privacysettings.html', 'title': '隐私保障'},
);
},
),
TextSpan(
text: '与',
style: TextStyle(color: Color(0xFF666666), fontSize: 14),
),
TextSpan(
text: '《用户协议》',
style: TextStyle(color: Color(0xFF7691FA), fontSize: 14),
recognizer: TapGestureRecognizer()
..onTap = () {
router.push(
'/link',
extra: {'url': 'https://bxr.banxiaoer.net/apps/useragreement.html', 'title': '用户协议'},
);
},
),
TextSpan(
text: '。如您同意上述文件的全部内容,请点击“同意”以继续。',
style: TextStyle(color: Color(0xFF666666), fontSize: 14),
),
],
),
),
actions: [
Table(
children: [
TableRow(
children: [
TableCell(
child: TextButton(
onPressed: () {
Navigator.of(context).pop();
},
style: TextButton.styleFrom(
foregroundColor: Color(0xFF666666),
textStyle: TextStyle(fontSize: 17),
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
),
),
child: Text('放弃登录'),
),
),
TableCell(
child: TextButton(
onPressed: () {
Navigator.of(context).pop('OK');
},
style: TextButton.styleFrom(
foregroundColor: Color(0xFF7691FA),
textStyle: TextStyle(fontSize: 17),
minimumSize: Size.fromHeight(40),
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
),
),
child: Text('同意'),
),
),
],
),
],
),
],
);
},
);
if (result != null) {
loginPhoneCubit.confirmAgreed();
} else {
loginPhoneCubit.cancelAgreed();
}
}
}
import 'package:appframe/bloc/login_qr_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class LoginQrPageV3 extends StatelessWidget {
const LoginQrPageV3({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => LoginQrCubit(LoginQrState()),
child: BlocConsumer<LoginQrCubit, LoginQrState>(
builder: (context, state) {
var loginQrCubit = context.read<LoginQrCubit>();
return Scaffold(
extendBodyBehindAppBar: true,
extendBody: true,
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// 顶部图片 + 底部35dp白色重合条
Stack(
children: [
Image.asset(
'assets/images/login_v3/login_banner.png',
width: double.infinity,
fit: BoxFit.fitWidth,
),
Positioned(
left: 0,
right: 0,
bottom: 0,
height: 35,
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(25),
topRight: Radius.circular(25),
),
),
),
),
],
),
// 剩余区域白色铺底,一直延伸到屏幕底部
Expanded(
child: Container(
color: Colors.white,
child: Column(
children: [
_buildQrCode(loginQrCubit, state) ?? const SizedBox.shrink(),
Expanded(
child: Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'其他方式登录',
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 14,
height: 1.0,
color: Color(0xFF999999),
),
),
const SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
InkWell(
onTap: () {
context.read<LoginQrCubit>().goLoginMain();
},
child: Image.asset(
'assets/images/login_v3/wechat.png',
),
),
],
),
const SizedBox(height: 40),
Text(
'提示:本应用为学生端使用,教师请前往班小二小程序操作',
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 12,
height: 20 / 12,
color: Color(0xFF999999),
),
),
],
),
),
),
),
],
),
),
),
],
),
);
},
listener: (context, state) {},
));
}
Column? _buildQrCode(LoginQrCubit loginQrCubit, LoginQrState state) {
if (state.status == 0) {
// 等待二维码数据
return Column(
children: [
Column(
children: [
SizedBox(height: 80),
Image.asset(
'assets/images/login_v3/loading.gif',
width: 72,
height: 72,
),
SizedBox(height: 15),
Text(
'正在生成二维码...',
style: TextStyle(
fontSize: 14,
height: 1.0,
color: Color(0xFF333333),
),
),
SizedBox(height: 8),
Text(
state.tip,
style: TextStyle(
fontSize: 14,
height: 1.0,
color: Color(0xFF333333),
),
),
],
),
],
);
} else if (state.status == 1) {
// 等待扫码
return Column(
children: [
Container(
width: 230,
height: 230,
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
border: Border.all(
color: Color(0x29265ECF),
width: 0.5,
),
borderRadius: BorderRadius.circular(20),
),
child: Image.memory(
state.image!,
width: 190,
height: 190,
),
),
SizedBox(height: 15),
Text(
state.tip,
style: TextStyle(
fontSize: 14,
height: 1.0,
color: Color(0xFF333333),
),
),
],
);
} else if (state.status == 2) {
// 已扫码,等待确认
return Column(
children: [
Center(
child: Image.asset(
"assets/images/login_v3/allowed.png",
),
),
SizedBox(height: 15.5),
Text(
"已扫码",
style: TextStyle(
fontSize: 16,
height: 1.0,
fontWeight: FontWeight.bold,
fontFamily: '黑体',
color: Color(0xFF333333),
),
),
SizedBox(height: 8),
Text(
state.tip,
style: TextStyle(
fontSize: 14,
height: 1.0,
color: Color(0xFF333333),
),
),
],
);
} else if (state.status == 3) {
// 拒绝
return Column(
children: [
Center(
child: Image.asset(
"assets/images/login_v3/refused.png",
),
),
SizedBox(height: 15.5),
Text(
"拒绝登录",
style: TextStyle(
fontSize: 16,
height: 1.0,
fontWeight: FontWeight.bold,
fontFamily: '黑体',
color: Color(0xFF333333),
),
),
SizedBox(height: 8),
Text(
state.tip,
style: TextStyle(
fontSize: 14,
height: 1.0,
color: Color(0xFF333333),
),
),
],
);
}
return null;
}
}
......@@ -112,3 +112,4 @@ flutter:
# - assets/dist.zip <-- 确认 zip 文件是否必须在运行时解压,这会增加包体积
- assets/images/login/
- assets/images/login_v2/
- assets/images/login_v3/
\ No newline at end of file
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!