|
|
@@ -0,0 +1,415 @@
|
|
|
+import 'package:common_pub/screen.dart';
|
|
|
+import 'package:common_pub/utils.dart';
|
|
|
+import 'package:flutter/gestures.dart';
|
|
|
+import 'package:rive/rive.dart';
|
|
|
+import 'package:track_common/widget/prelude.dart';
|
|
|
+
|
|
|
+import 'common.dart';
|
|
|
+import 'login_controller.dart';
|
|
|
+
|
|
|
+class LoginView extends GetView<LoginController> {
|
|
|
+ static const name = '/LoginView';
|
|
|
+
|
|
|
+ const LoginView({super.key});
|
|
|
+
|
|
|
+ static Future<bool> to({
|
|
|
+ bool canBack = true,
|
|
|
+ VoidCallback? thenToPageCall,
|
|
|
+ }) async {
|
|
|
+ Future<dynamic>? r;
|
|
|
+ if (canBack) {
|
|
|
+ r = Get.to(LoginView.name, arguments: thenToPageCall);
|
|
|
+ } else {
|
|
|
+ r = Get.offAll(() => LoginView.name, arguments: thenToPageCall);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (r != null) {
|
|
|
+ final r2 = await r;
|
|
|
+ if (r2 is bool) {
|
|
|
+ return r2;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ return Scaffold(
|
|
|
+ // appBar: AppTopBar(title: '登录账号', hasBackText: true, height: context.height*0.3),
|
|
|
+ backgroundColor: Colors.white,
|
|
|
+ body: Container(
|
|
|
+ height: double.infinity,
|
|
|
+ decoration: const BoxDecoration(color: Colors.white),
|
|
|
+ alignment: Alignment.center,
|
|
|
+ clipBehavior: Clip.hardEdge,
|
|
|
+ child: Row(
|
|
|
+ children: [
|
|
|
+ const Expanded(
|
|
|
+ flex: 2,
|
|
|
+ child: AnimationPage(),
|
|
|
+ ),
|
|
|
+ Expanded(
|
|
|
+ flex: 3,
|
|
|
+ child: wPageRight(context),
|
|
|
+ )
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget wPageRight(BuildContext context) {
|
|
|
+ var inputHeight = context.height * 0.45;
|
|
|
+ const inputMinHeight = 260.0;
|
|
|
+ if (inputHeight < inputMinHeight) {
|
|
|
+ inputHeight = inputMinHeight;
|
|
|
+ }
|
|
|
+
|
|
|
+ return Container(
|
|
|
+ // margin: EdgeInsets.fromLTRB(context.width*(6), context.width*5.6), context.width*(6), context.width*(2)),
|
|
|
+ padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
|
|
|
+ decoration: const BoxDecoration(
|
|
|
+ // color: Color(0xffffcb00),
|
|
|
+ image: DecorationImage(
|
|
|
+ image: AssetImage(Assets.imagesBkLoginRight, package: package),
|
|
|
+ alignment: Alignment.topCenter,
|
|
|
+ fit: BoxFit.fitWidth),
|
|
|
+ ),
|
|
|
+ child: Column(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.center,
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
+ children: [
|
|
|
+ const Expanded(
|
|
|
+ child: Text(
|
|
|
+ '定向运动活动现场监控系统',
|
|
|
+ textAlign: TextAlign.center,
|
|
|
+ style: TextStyle(fontSize: 22.0, fontWeight: FontWeight.bold),
|
|
|
+ )),
|
|
|
+ // SizedBox(height: inputHeight),
|
|
|
+ // Expanded(child: wInput(context)),
|
|
|
+ SizedBox(height: inputHeight, child: wInput(context)),
|
|
|
+ Expanded(
|
|
|
+ child: Image.asset(Assets.imagesIcLoginLogo, package: package)),
|
|
|
+ // Expanded(flex: 10,child: wExtra()),
|
|
|
+ ],
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
+ InputDecoration wInputDecoration(hintText) {
|
|
|
+ return InputDecoration(
|
|
|
+ // isCollapsed: true,
|
|
|
+ contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
|
|
+ hintText: hintText,
|
|
|
+ // hintStyle: TextStyle(fontSize: 12.0.rpx),
|
|
|
+ focusedBorder: OutlineInputBorder(
|
|
|
+ borderRadius: BorderRadius.circular(4.0),
|
|
|
+ borderSide: const BorderSide(
|
|
|
+ color: Colors.blue,
|
|
|
+ width: 1.0,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ enabledBorder: OutlineInputBorder(
|
|
|
+ borderRadius: BorderRadius.circular(4.0),
|
|
|
+ borderSide: const BorderSide(
|
|
|
+ color: Color(0xffaaaaaa),
|
|
|
+ width: 1.0,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget wInput(BuildContext context) {
|
|
|
+ return Container(
|
|
|
+ margin: EdgeInsets.fromLTRB(
|
|
|
+ context.wp(6), context.wp(3), context.wp(6), context.wp(2)),
|
|
|
+ padding: EdgeInsets.fromLTRB(
|
|
|
+ context.wp(6), context.wp(3.6), context.wp(6), context.wp(2)),
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: const Color(0xffffffff),
|
|
|
+ borderRadius: BorderRadius.circular(context.wp(1.0)),
|
|
|
+ boxShadow: const [
|
|
|
+ BoxShadow(
|
|
|
+ color: Color(0x38000000),
|
|
|
+ offset: Offset(0.0, 3.0),
|
|
|
+ blurRadius: 6)
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ child: Column(
|
|
|
+ children: [
|
|
|
+ // const Spacer(flex: 20),
|
|
|
+ TextFormField(
|
|
|
+ decoration: wInputDecoration('请输入手机号'),
|
|
|
+ validator: (String? value) {
|
|
|
+ if (value == null || !value.isPhoneNumber) {
|
|
|
+ return '请输入正确的手机号';
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ },
|
|
|
+ onChanged: (value) {
|
|
|
+ controller.phone.value = value;
|
|
|
+ },
|
|
|
+ keyboardType: TextInputType.phone),
|
|
|
+ const Spacer(flex: 50),
|
|
|
+ Stack(
|
|
|
+ alignment: Alignment.centerRight,
|
|
|
+ children: [
|
|
|
+ TextFormField(
|
|
|
+ decoration: wInputDecoration('请输入验证码'),
|
|
|
+ onChanged: (value) {
|
|
|
+ controller.password.value = value;
|
|
|
+ },
|
|
|
+ keyboardType: TextInputType.number,
|
|
|
+ ),
|
|
|
+ Padding(
|
|
|
+ padding: const EdgeInsets.only(right: 4),
|
|
|
+ child: SizedBox(
|
|
|
+ width: 84,
|
|
|
+ height: 37,
|
|
|
+ child: Obx(() => GetCodeButton(
|
|
|
+ codeRetryLeft: controller.codeRetryLeft.value,
|
|
|
+ onPressed: () => controller.onGetCode(),
|
|
|
+ )),
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ // _TextContract(),
|
|
|
+ const Spacer(flex: 80),
|
|
|
+ button(
|
|
|
+ context,
|
|
|
+ '登 录',
|
|
|
+ () => controller.onSignIn(),
|
|
|
+ ),
|
|
|
+ const Spacer(flex: 10),
|
|
|
+ ],
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget wExtra() {
|
|
|
+ return Padding(
|
|
|
+ padding: const EdgeInsets.only(left: 41.67, right: 41.67),
|
|
|
+ child: Column(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
+ children: [
|
|
|
+ Row(
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
+ children: [
|
|
|
+ divider(),
|
|
|
+ const Padding(
|
|
|
+ padding: EdgeInsets.only(left: 22.9, right: 22.9),
|
|
|
+ child: Text(
|
|
|
+ '其他登录方式',
|
|
|
+ style: TextStyle(fontSize: 25.0),
|
|
|
+ )),
|
|
|
+ divider(),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ const SizedBox(height: 76.5),
|
|
|
+ const Row(),
|
|
|
+ const Spacer(),
|
|
|
+ Text.rich(TextSpan(
|
|
|
+ text: '还不是我们的会员?',
|
|
|
+ style: const TextStyle(fontSize: 33.33, color: Colors.black),
|
|
|
+ children: [
|
|
|
+ TextSpan(
|
|
|
+ text: '点击注册',
|
|
|
+ style: const TextStyle(color: Color(0xffffb40b)),
|
|
|
+ recognizer: TapGestureRecognizer()
|
|
|
+ ..onTap = () {
|
|
|
+ // SignUpView.show();
|
|
|
+ },
|
|
|
+ ),
|
|
|
+ ])),
|
|
|
+ const SizedBox(height: 76.5),
|
|
|
+ ],
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget divider() {
|
|
|
+ return Expanded(
|
|
|
+ child: Container(
|
|
|
+ height: 2.0,
|
|
|
+ color: Colors.black,
|
|
|
+ ));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+final _duration = 2.seconds;
|
|
|
+
|
|
|
+class AnimationPage extends StatefulWidget {
|
|
|
+ const AnimationPage({super.key});
|
|
|
+
|
|
|
+ @override
|
|
|
+ State<StatefulWidget> createState() {
|
|
|
+ return _AnimationPageState();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+class _AnimationPageState extends State<AnimationPage>
|
|
|
+ with SingleTickerProviderStateMixin {
|
|
|
+ late Animation<double> animation;
|
|
|
+ late AnimationController animationController;
|
|
|
+
|
|
|
+ @override
|
|
|
+ void initState() {
|
|
|
+ super.initState();
|
|
|
+ animationController = AnimationController(duration: _duration, vsync: this);
|
|
|
+ animation = Tween<double>(begin: 0, end: 1).animate(animationController);
|
|
|
+ animationController.forward();
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ void dispose() {
|
|
|
+ animationController.dispose();
|
|
|
+ super.dispose();
|
|
|
+ }
|
|
|
+
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ return wPageLeft(context);
|
|
|
+ }
|
|
|
+
|
|
|
+ Widget wPageLeft(BuildContext context) {
|
|
|
+ return Container(
|
|
|
+ padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
|
|
|
+ decoration: const BoxDecoration(
|
|
|
+ image: DecorationImage(
|
|
|
+ image: AssetImage(Assets.imagesBkCommonPage, package: package),
|
|
|
+ fit: BoxFit.cover)),
|
|
|
+ child: Column(
|
|
|
+ children: [
|
|
|
+ Expanded(
|
|
|
+ flex: 1,
|
|
|
+ child: Container(
|
|
|
+ // color: const Color(0xffffcb00),
|
|
|
+ padding: const EdgeInsets.only(bottom: 32),
|
|
|
+ alignment: Alignment.bottomCenter,
|
|
|
+ child: Stack(
|
|
|
+ alignment: Alignment.center,
|
|
|
+ children: [
|
|
|
+ Image.asset(
|
|
|
+ Assets.imagesImCompassNoMap,
|
|
|
+ height: context.wp(15.0),
|
|
|
+ fit: BoxFit.fitHeight,
|
|
|
+ package: package,
|
|
|
+ ),
|
|
|
+ SizedBox(
|
|
|
+ height: context.wp(10.0),
|
|
|
+ child: const RiveAnimation.asset(
|
|
|
+ 'packages/$package/${Assets.imagesAmAppStartArrow}',
|
|
|
+ fit: BoxFit.fitHeight,
|
|
|
+ ))
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ )),
|
|
|
+ Expanded(
|
|
|
+ flex: 1,
|
|
|
+ child: Column(
|
|
|
+ // mainAxisAlignment: MainAxisAlignment.start,
|
|
|
+ children: [
|
|
|
+ AnimatedText(animation: animation),
|
|
|
+ Expanded(
|
|
|
+ child: Stack(
|
|
|
+ alignment: Alignment.center,
|
|
|
+ children: [
|
|
|
+ Center(
|
|
|
+ child: AnimatedColors(
|
|
|
+ animation: animation,
|
|
|
+ ),
|
|
|
+ ),
|
|
|
+ // Positioned(
|
|
|
+ // bottom: 2.6.width*,
|
|
|
+ // child:
|
|
|
+ // Image.asset(Assets.imagesIcLogo, height: 5.0.width*))
|
|
|
+ ],
|
|
|
+ ))
|
|
|
+ ],
|
|
|
+ )),
|
|
|
+ ],
|
|
|
+ ),
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+class AnimatedText extends AnimatedWidget {
|
|
|
+ const AnimatedText({super.key, required Animation<double> animation})
|
|
|
+ : super(listenable: animation);
|
|
|
+
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ final animation = listenable as Animation<double>;
|
|
|
+ var style = (context.textTheme.titleLarge ?? const TextStyle()).copyWith(
|
|
|
+ height: context.wp(0.14),
|
|
|
+ color: Colors.white,
|
|
|
+ fontSize: context.wp(2.2));
|
|
|
+
|
|
|
+ return Opacity(
|
|
|
+ opacity: animation.value,
|
|
|
+ child: Column(
|
|
|
+ mainAxisSize: MainAxisSize.min,
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
+ children: [
|
|
|
+ SizedBox(
|
|
|
+ height: context.wp(0.9),
|
|
|
+ ),
|
|
|
+ Text('确定方向 ', style: style),
|
|
|
+ Text(' 发现你自己的路!', style: style),
|
|
|
+ Text('Orienting, Discover Your Own Way!',
|
|
|
+ style: style.copyWith(
|
|
|
+ color: const Color(0xffade0ff),
|
|
|
+ fontWeight: FontWeight.bold,
|
|
|
+ fontSize: context.wp(1.4))),
|
|
|
+ ],
|
|
|
+ ));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+class AnimatedColors extends AnimatedWidget {
|
|
|
+ const AnimatedColors({super.key, required Animation<double> animation})
|
|
|
+ : super(listenable: animation);
|
|
|
+
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ final animation = listenable as Animation<double>;
|
|
|
+ var style = (context.textTheme.titleLarge ?? const TextStyle())
|
|
|
+ .copyWith(color: Colors.white, fontSize: context.wp(8.33));
|
|
|
+
|
|
|
+ var children = <Widget>[];
|
|
|
+
|
|
|
+ for (var i = 0; i < hrPColors.length; i++) {
|
|
|
+ final color = hrPColors[i];
|
|
|
+ var opacity = 0.0;
|
|
|
+ if (i < animation.value * hrPColors.length) {
|
|
|
+ opacity = 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ children.add(Opacity(
|
|
|
+ opacity: opacity,
|
|
|
+ child: Container(
|
|
|
+ width: context.wp(1.02),
|
|
|
+ decoration: BoxDecoration(
|
|
|
+ color: color,
|
|
|
+ borderRadius: BorderRadius.circular(context.wp(2.0)),
|
|
|
+ border: Border.all(color: Colors.white.withAlpha(100))),
|
|
|
+ )));
|
|
|
+ }
|
|
|
+
|
|
|
+ return Column(
|
|
|
+ mainAxisSize: MainAxisSize.min,
|
|
|
+ crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
+ children: [
|
|
|
+ SizedBox(height: context.wp(5.36)),
|
|
|
+ SizedBox(
|
|
|
+ width: context.wp(7.6),
|
|
|
+ height: context.wp(2.5),
|
|
|
+ child: Row(
|
|
|
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
+ children: children,
|
|
|
+ )),
|
|
|
+ // Text('支持心率带检测身体数据',
|
|
|
+ // style: style.copyWith(
|
|
|
+ // color: const Color(0xffffcb00), fontSize: 3.3.width*)),
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|