login_view.dart 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. import 'package:common_pub/screen.dart';
  2. import 'package:common_pub/utils.dart';
  3. import 'package:flutter/gestures.dart';
  4. import 'package:pretty_qr_code/pretty_qr_code.dart';
  5. import 'package:rive/rive.dart';
  6. import 'package:track_common/widget/prelude.dart';
  7. import 'common.dart';
  8. import 'login_controller.dart';
  9. class LoginView extends GetView<LoginController> {
  10. static const name = '/LoginView';
  11. const LoginView({super.key});
  12. static Future<bool> to({
  13. bool canBack = true,
  14. VoidCallback? thenToPageCall,
  15. }) async {
  16. Future<dynamic>? r;
  17. if (canBack) {
  18. r = Get.toNamed(LoginView.name, arguments: thenToPageCall);
  19. } else {
  20. r = Get.offAllNamed(LoginView.name, arguments: thenToPageCall);
  21. }
  22. if (r != null) {
  23. final r2 = await r;
  24. if (r2 is bool) {
  25. return r2;
  26. }
  27. }
  28. return false;
  29. }
  30. @override
  31. Widget build(BuildContext context) {
  32. return Scaffold(
  33. // appBar: AppTopBar(title: '登录账号', hasBackText: true, height: context.height*0.3),
  34. backgroundColor: Colors.white,
  35. body: Container(
  36. height: double.infinity,
  37. decoration: const BoxDecoration(color: Colors.white),
  38. alignment: Alignment.center,
  39. clipBehavior: Clip.hardEdge,
  40. child: Row(
  41. children: [
  42. const Expanded(
  43. flex: 2,
  44. child: AnimationPage(),
  45. ),
  46. Expanded(
  47. flex: 3,
  48. child: wPageRight(context),
  49. )
  50. ],
  51. ),
  52. ),
  53. );
  54. }
  55. Widget wPageRight(BuildContext context) {
  56. var inputHeight = context.height * 0.45;
  57. const inputMinHeight = 260.0;
  58. if (inputHeight < inputMinHeight) {
  59. inputHeight = inputMinHeight;
  60. }
  61. return Container(
  62. padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
  63. decoration: const BoxDecoration(
  64. // color: Color(0xffffcb00),
  65. image: DecorationImage(
  66. image: AssetImage(Assets.imagesBkLoginRight, package: package),
  67. alignment: Alignment.topCenter,
  68. fit: BoxFit.fitWidth),
  69. ),
  70. child: Column(
  71. mainAxisAlignment: MainAxisAlignment.center,
  72. crossAxisAlignment: CrossAxisAlignment.center,
  73. children: [
  74. const Spacer(),
  75. const Text(
  76. '定向运动活动现场监控系统',
  77. textAlign: TextAlign.center,
  78. style: TextStyle(fontSize: 29.87, fontWeight: FontWeight.w500),
  79. ),
  80. SizedBox(height: inputHeight, child: wInput(context)),
  81. Image.asset(Assets.imagesIcLoginLogo, height: 24, package: package),
  82. const Spacer()
  83. // Expanded(flex: 10,child: wExtra()),
  84. ],
  85. ));
  86. }
  87. Widget wInput(BuildContext context) {
  88. return Obx(() => Container(
  89. width: 432.8,
  90. height: 424,
  91. margin: const EdgeInsets.only(top: 22.04, bottom: 21),
  92. decoration: BoxDecoration(
  93. color: const Color(0xffffffff),
  94. borderRadius: BorderRadius.circular(context.wp(1.0)),
  95. boxShadow: const [
  96. BoxShadow(
  97. color: Color(0x38000000),
  98. offset: Offset(0.0, 3.0),
  99. blurRadius: 6)
  100. ],
  101. ),
  102. child: controller.isPageQrCode.value ? _InputQrCode() : _InputPhone()));
  103. }
  104. Widget wExtra() {
  105. return Padding(
  106. padding: const EdgeInsets.only(left: 41.67, right: 41.67),
  107. child: Column(
  108. crossAxisAlignment: CrossAxisAlignment.center,
  109. children: [
  110. Row(
  111. crossAxisAlignment: CrossAxisAlignment.center,
  112. children: [
  113. divider(),
  114. const Padding(
  115. padding: EdgeInsets.only(left: 22.9, right: 22.9),
  116. child: Text(
  117. '其他登录方式',
  118. style: TextStyle(fontSize: 25.0),
  119. )),
  120. divider(),
  121. ],
  122. ),
  123. const SizedBox(height: 76.5),
  124. const Row(),
  125. const Spacer(),
  126. Text.rich(TextSpan(
  127. text: '还不是我们的会员?',
  128. style: const TextStyle(fontSize: 33.33, color: Colors.black),
  129. children: [
  130. TextSpan(
  131. text: '点击注册',
  132. style: const TextStyle(color: Color(0xffffb40b)),
  133. recognizer: TapGestureRecognizer()
  134. ..onTap = () {
  135. // SignUpView.show();
  136. },
  137. ),
  138. ])),
  139. const SizedBox(height: 76.5),
  140. ],
  141. ));
  142. }
  143. Widget divider() {
  144. return Expanded(
  145. child: Container(
  146. height: 2.0,
  147. color: Colors.black,
  148. ));
  149. }
  150. }
  151. final _duration = 2.seconds;
  152. class AnimationPage extends StatefulWidget {
  153. const AnimationPage({super.key});
  154. @override
  155. State<StatefulWidget> createState() {
  156. return _AnimationPageState();
  157. }
  158. }
  159. class _AnimationPageState extends State<AnimationPage>
  160. with SingleTickerProviderStateMixin {
  161. late Animation<double> animation;
  162. late AnimationController animationController;
  163. @override
  164. void initState() {
  165. super.initState();
  166. animationController = AnimationController(duration: _duration, vsync: this);
  167. animation = Tween<double>(begin: 0, end: 1).animate(animationController);
  168. animationController.forward();
  169. }
  170. @override
  171. void dispose() {
  172. animationController.dispose();
  173. super.dispose();
  174. }
  175. @override
  176. Widget build(BuildContext context) {
  177. return wPageLeft(context);
  178. }
  179. Widget wPageLeft(BuildContext context) {
  180. return Container(
  181. padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
  182. decoration: const BoxDecoration(
  183. image: DecorationImage(
  184. image: AssetImage(Assets.imagesBkCommonPage, package: package),
  185. fit: BoxFit.cover)),
  186. child: Column(
  187. children: [
  188. Expanded(
  189. flex: 1,
  190. child: Container(
  191. // color: const Color(0xffffcb00),
  192. padding: const EdgeInsets.only(bottom: 32),
  193. alignment: Alignment.bottomCenter,
  194. child: Stack(
  195. alignment: Alignment.center,
  196. children: [
  197. Image.asset(
  198. Assets.imagesImCompassNoMap,
  199. height: context.wp(15.0),
  200. fit: BoxFit.fitHeight,
  201. package: package,
  202. ),
  203. SizedBox(
  204. height: context.wp(10.0),
  205. child: const RiveAnimation.asset(
  206. 'packages/$package/${Assets.imagesAmAppStartArrow}',
  207. fit: BoxFit.fitHeight,
  208. ))
  209. ],
  210. ),
  211. )),
  212. Expanded(
  213. flex: 1,
  214. child: Column(
  215. // mainAxisAlignment: MainAxisAlignment.start,
  216. children: [
  217. AnimatedText(animation: animation),
  218. Expanded(
  219. child: Stack(
  220. alignment: Alignment.center,
  221. children: [
  222. Center(
  223. child: AnimatedColors(
  224. animation: animation,
  225. ),
  226. ),
  227. // Positioned(
  228. // bottom: 2.6.width*,
  229. // child:
  230. // Image.asset(Assets.imagesIcLogo, height: 5.0.width*))
  231. ],
  232. ))
  233. ],
  234. )),
  235. ],
  236. ),
  237. );
  238. }
  239. }
  240. class AnimatedText extends AnimatedWidget {
  241. const AnimatedText({super.key, required Animation<double> animation})
  242. : super(listenable: animation);
  243. @override
  244. Widget build(BuildContext context) {
  245. final animation = listenable as Animation<double>;
  246. var style = (context.textTheme.titleLarge ?? const TextStyle()).copyWith(
  247. height: context.wp(0.14),
  248. color: Colors.white,
  249. fontSize: context.wp(2.2));
  250. return Opacity(
  251. opacity: animation.value,
  252. child: Column(
  253. mainAxisSize: MainAxisSize.min,
  254. crossAxisAlignment: CrossAxisAlignment.center,
  255. children: [
  256. SizedBox(
  257. height: context.wp(0.9),
  258. ),
  259. Text('确定方向 ', style: style),
  260. Text(' 发现你自己的路!', style: style),
  261. Text('Orienting, Discover Your Own Way!',
  262. style: style.copyWith(
  263. color: const Color(0xffade0ff),
  264. fontWeight: FontWeight.bold,
  265. fontSize: context.wp(1.4))),
  266. ],
  267. ));
  268. }
  269. }
  270. class AnimatedColors extends AnimatedWidget {
  271. const AnimatedColors({super.key, required Animation<double> animation})
  272. : super(listenable: animation);
  273. @override
  274. Widget build(BuildContext context) {
  275. final animation = listenable as Animation<double>;
  276. var children = <Widget>[];
  277. for (var i = 0; i < hrPColors.length; i++) {
  278. final color = hrPColors[i];
  279. var opacity = 0.0;
  280. if (i < animation.value * hrPColors.length) {
  281. opacity = 1;
  282. }
  283. children.add(Opacity(
  284. opacity: opacity,
  285. child: Container(
  286. width: context.wp(1.02),
  287. decoration: BoxDecoration(
  288. color: color,
  289. borderRadius: BorderRadius.circular(context.wp(2.0)),
  290. border: Border.all(color: Colors.white.withAlpha(100))),
  291. )));
  292. }
  293. return Column(
  294. mainAxisSize: MainAxisSize.min,
  295. crossAxisAlignment: CrossAxisAlignment.center,
  296. children: [
  297. SizedBox(height: context.wp(5.36)),
  298. SizedBox(
  299. width: context.wp(7.6),
  300. height: context.wp(2.5),
  301. child: Row(
  302. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  303. children: children,
  304. )),
  305. // Text('支持心率带检测身体数据',
  306. // style: style.copyWith(
  307. // color: const Color(0xffffcb00), fontSize: 3.3.width*)),
  308. ],
  309. );
  310. }
  311. }
  312. class _InputPhone extends GetView<LoginController> {
  313. @override
  314. Widget build(BuildContext context) {
  315. return Stack(
  316. children: [
  317. Align(
  318. alignment: Alignment.topRight,
  319. child: GestureDetector(
  320. onTap: () => controller.toQrcode(),
  321. child: Image.asset(Assets.imagesIcLoginQrcode,
  322. package: package, width: 58.5, height: 58.5),
  323. )),
  324. Padding(
  325. padding: EdgeInsets.fromLTRB(
  326. context.wp(6), context.wp(3.6), context.wp(6), context.wp(2)),
  327. child: Column(
  328. children: [
  329. // const Spacer(flex: 20),
  330. TextFormField(
  331. decoration: wInputDecoration('请输入手机号'),
  332. validator: (String? value) {
  333. if (value == null || !value.isPhoneNumber) {
  334. return '请输入正确的手机号';
  335. }
  336. return null;
  337. },
  338. onChanged: (value) {
  339. controller.phone.value = value;
  340. },
  341. keyboardType: TextInputType.phone),
  342. const Spacer(flex: 50),
  343. Stack(
  344. alignment: Alignment.centerRight,
  345. children: [
  346. TextFormField(
  347. decoration: wInputDecoration('请输入验证码'),
  348. onChanged: (value) {
  349. controller.password.value = value;
  350. },
  351. keyboardType: TextInputType.number,
  352. ),
  353. Padding(
  354. padding: const EdgeInsets.only(right: 4),
  355. child: SizedBox(
  356. width: 84,
  357. height: 37,
  358. child: Obx(() => GetCodeButton(
  359. codeRetryLeft: controller.codeRetryLeft.value,
  360. onPressed: () => controller.onGetCode(),
  361. )),
  362. ),
  363. ),
  364. ],
  365. ),
  366. // _TextContract(),
  367. const Spacer(flex: 80),
  368. button(
  369. context,
  370. '登 录',
  371. () => controller.onSignIn(),
  372. ),
  373. const Spacer(flex: 10),
  374. ],
  375. ))
  376. ],
  377. );
  378. }
  379. InputDecoration wInputDecoration(hintText) {
  380. return InputDecoration(
  381. // isCollapsed: true,
  382. contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
  383. hintText: hintText,
  384. // hintStyle: TextStyle(fontSize: 12.0.rpx),
  385. focusedBorder: OutlineInputBorder(
  386. borderRadius: BorderRadius.circular(4.0),
  387. borderSide: const BorderSide(
  388. color: Colors.blue,
  389. width: 1.0,
  390. ),
  391. ),
  392. enabledBorder: OutlineInputBorder(
  393. borderRadius: BorderRadius.circular(4.0),
  394. borderSide: const BorderSide(
  395. color: Color(0xffaaaaaa),
  396. width: 1.0,
  397. ),
  398. ),
  399. );
  400. }
  401. }
  402. class _InputQrCode extends GetView<LoginController> {
  403. @override
  404. Widget build(BuildContext context) {
  405. return Obx(() => Column(
  406. crossAxisAlignment: CrossAxisAlignment.center,
  407. children: [
  408. Row(
  409. children: [
  410. const Spacer(),
  411. TextButton(
  412. onPressed: () => controller.isPageQrCode.value = false,
  413. child: const Text('使用短信登录',
  414. style: TextStyle(color: Color(0xff004670))))
  415. ],
  416. ),
  417. Stack(children: [
  418. GestureDetector(
  419. onTap: () => controller.flushQrCode(),
  420. child: Obx(() => SizedBox.square(
  421. dimension: 104,
  422. child:
  423. PrettyQrView.data(data: controller.qrCode.value)))),
  424. if (controller.codeErr.value.isNotEmpty)
  425. Container(
  426. height: 104,
  427. width: 104,
  428. color: const Color(0xe6ffffff),
  429. alignment: Alignment.center,
  430. child: Column(
  431. children: [
  432. const Expanded(
  433. child: Center(
  434. child: Icon(
  435. Icons.cancel,
  436. color: Colors.red,
  437. size: 53.3,
  438. ),
  439. )),
  440. Text(controller.codeErr.value,
  441. style: const TextStyle(
  442. fontSize: 14.2, fontWeight: FontWeight.w500)),
  443. const SizedBox(height: 5)
  444. ],
  445. ),
  446. )
  447. ]),
  448. Expanded(
  449. child: Center(
  450. child: controller.codeErr.value.isEmpty
  451. ? const Text('请用彩图奔跑APP 扫一扫登录')
  452. : TextButton(
  453. onPressed: () => controller.flushQrCode(),
  454. child: const Text(
  455. '重新扫码',
  456. style: TextStyle(color: Color(0xff004670)),
  457. )),
  458. ))
  459. ],
  460. ));
  461. }
  462. }