import 'dart:convert'; import 'dart:math'; import 'package:flustars/flustars.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:star_lock/common/safetyVerification/entity/CheckSafetyVerificationEntity.dart'; import 'package:star_lock/common/safetyVerification/entity/SafetyVerificationEntity.dart'; import 'package:star_lock/common/safetyVerification/safetyVerification_logic.dart'; import 'package:star_lock/common/safetyVerification/safetyVerification_state.dart'; import '../../app_settings/app_colors.dart'; import '../../network/api_repository.dart'; import '../../tools/titleAppBar.dart'; class SafetyVerificationPage extends StatefulWidget { const SafetyVerificationPage({Key? key}) : super(key: key); @override State createState() => _SafetyVerificationPageState(); } class _SafetyVerificationPageState extends State with TickerProviderStateMixin { final SafetyVerificationLogic logic = Get.put(SafetyVerificationLogic()); final SafetyVerificationState state = Get.find().state; String baseImageBase64 = ''; String slideImageBase64 = ''; Size baseSize = Size.zero; //底部基类图片 Size slideSize = Size.zero; //滑块图片 double sliderStartX = 0; //滑块未拖前的X坐标 bool sliderMoveFinish = false; //滑块拖动结束 bool checkResultAfterDrag = false; //拖动后的校验结果 //-------------动画------------ int _checkMilliseconds = 0; //滑动时间 bool _showTimeLine = false; //是否显示动画部件 bool _checkSuccess = false; //校验是否成功 AnimationController? controller; //高度动画 Animation? offsetAnimation; final double _bottomSliderSize = 60; //加载验证码 Future loadCaptcha() async { setState(() { _showTimeLine = false; sliderMoveFinish = false; checkResultAfterDrag = false; }); final SafetyVerificationEntity entity = await ApiRepository.to.getSliderVerifyImg(state.getData['countryCode'].toString(), state.getData['account'].toString()); if(entity.errorCode! == 0){ state.sliderXMoved.value = 0; sliderStartX = 0; checkResultAfterDrag = false; baseImageBase64 = entity.data!.bigImg!; slideImageBase64 = entity.data!.smallImg!; final Rect baseR = await WidgetUtil.getImageWH( image: Image.memory(const Base64Decoder().convert(baseImageBase64))); baseSize = baseR.size; final Rect silderR = await WidgetUtil.getImageWH( image: Image.memory(const Base64Decoder().convert(slideImageBase64))); slideSize = silderR.size; setState(() {}); } else { } } //校验验证码 Future checkCaptcha(sliderMovedX, {BuildContext? myContext}) async { setState(() { sliderMoveFinish = true; }); final CheckSafetyVerificationEntity entity = await ApiRepository.to.checkSliderVerifyImg(state.getData['countryCode'].toString(), state.getData['account'].toString(), sliderMovedX.toString()); if(entity.errorCode! == 0){ checkSuccess('captchaVerification'); }else{ state.sliderXMoved.value = 0; sliderStartX = 0; checkResultAfterDrag = false; setState(() {}); } } @override void initState() { super.initState(); initAnimation(); loadCaptcha(); } // 初始化动画 void initAnimation() { controller = AnimationController(duration: const Duration(milliseconds: 500), vsync: this); offsetAnimation = Tween(begin: 0.5, end: 0) .animate(CurvedAnimation(parent: controller!, curve: Curves.ease)) ..addListener(() { setState(() {}); }); } @override void didUpdateWidget(SafetyVerificationPage oldWidget) { super.didUpdateWidget(oldWidget); } @override Widget build(BuildContext context) { return MaxScaleTextWidget( child: buildContent(context), ); } Widget buildContent(BuildContext context) { final MediaQueryData mediaQuery = MediaQuery.of(context); double dialogWidth = 0.9 * mediaQuery.size.width; if (dialogWidth < 330) { dialogWidth = mediaQuery.size.width; } return Scaffold( backgroundColor: Colors.white, appBar: TitleAppBar( barTitle: '安全验证'.tr, haveBack: true, backgroundColor: AppColors.mainColor), body: Container( // key: _containerKey, width: 1.sw, // height: 340, color: Colors.white, child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ _topContainer(), _middleContainer(), _bottomContainer(), ], ), ), ); } //校验通过 void checkSuccess(String content) { setState(() { checkResultAfterDrag = true; _checkSuccess = true; _showTimeLine = true; }); _forwardAnimation(); //刷新验证码 Future.delayed(const Duration(milliseconds: 1000)).then((v) { _reverseAnimation().then((v) { setState(() { _showTimeLine = false; }); //关闭验证码 Navigator.pop(context, {'xWidth': state.sliderXMoved.value.toString()}); }); }); } //校验失败 void checkFail() { setState(() { _showTimeLine = true; _checkSuccess = false; checkResultAfterDrag = false; }); _forwardAnimation(); //刷新验证码 Future.delayed(const Duration(milliseconds: 1000)).then((v) { _reverseAnimation().then((v) { setState(() { _showTimeLine = false; }); loadCaptcha(); //回调 // if (widget.onFail != null) { // widget.onFail!(); // } }); }); } // 反向执行动画 _reverseAnimation() async { await controller!.reverse(); } // 正向执行动画 _forwardAnimation() async { await controller!.forward(); } ///顶部,提示+关闭 _topContainer() { return Container( height: 70.h, padding: const EdgeInsets.fromLTRB(10, 0, 10, 0), decoration: const BoxDecoration( border: Border(bottom: BorderSide(width: 1, color: Color(0xffe5e5e5))), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('拖动下方滑块完成拼图'.tr, style: TextStyle(fontSize: 26.sp),), IconButton( icon: const Icon(Icons.refresh), iconSize: 30, color: Colors.black54, onPressed: loadCaptcha), ], ), ); } _middleContainer() { ////显示验证码 return Container( margin: const EdgeInsets.symmetric(vertical: 10), child: Stack( children: [ ///底图 310*155 if (baseImageBase64.isNotEmpty) Image.memory( const Base64Decoder().convert(baseImageBase64), fit: BoxFit.fitWidth, // key: _baseImageKey, gaplessPlayback: true, ) else Container( width: 310, height: 155, alignment: Alignment.center, child: const CircularProgressIndicator(), ), ///滑块图 if (slideImageBase64.isNotEmpty) Obx(() => Container( margin: EdgeInsets.fromLTRB(state.sliderXMoved.value, 0, 0, 0), child: Image.memory(const Base64Decoder().convert(slideImageBase64), fit: BoxFit.fitHeight, // key: _slideImageKey, gaplessPlayback: true, ), )) else Container(), Positioned( bottom: 0, left: -10, right: -10, child: Offstage( offstage: !_showTimeLine, child: FractionalTranslation( translation: Offset(0, offsetAnimation!.value), child: Container( margin: const EdgeInsets.only(left: 10, right: 10), height: 40, color: _checkSuccess ? const Color(0x7F66BB6A) : const Color.fromRGBO(200, 100, 100, 0.4), alignment: Alignment.centerLeft, child: Text( _checkSuccess ? '${(_checkMilliseconds / (60.0 * 12)).toStringAsFixed(2)}s${'验证成功'.tr}' : '验证失败', style: const TextStyle(color: Colors.white), ), ), ), )), Positioned( bottom: -20, left: 0, right: 0, child: Offstage( offstage: !_showTimeLine, child: Container( margin: const EdgeInsets.only(left: 10, right: 10), height: 20, color: Colors.white, ), )) ], ), ); } ///底部,滑动区域 Widget _bottomContainer() { return baseSize.width >0 ? SizedBox( height: 70, width: baseSize.width, child: Stack( alignment: AlignmentDirectional.centerStart, children: [ Container( height: _bottomSliderSize, decoration: BoxDecoration( border: Border.all( width: 1, color: const Color(0xffe5e5e5), ), color: const Color(0xfff8f9fb), ), ), Container( alignment: Alignment.center, child: Text('向右拖动滑块填充拼图'.tr, style: const TextStyle(fontSize: 16),), ), Obx(() => Container( width: state.sliderXMoved.value, height: _bottomSliderSize-2, color: const Color(0xfff3fef1), )), GestureDetector( onPanStart: (DragStartDetails startDetails) { ///开始 _checkMilliseconds = DateTime.now().millisecondsSinceEpoch; sliderStartX = startDetails.localPosition.dx; }, onPanUpdate: (DragUpdateDetails updateDetails) { ///更新 double offset = updateDetails.localPosition.dx - sliderStartX; if(offset < 0){ offset = 0; } setState(() { state.sliderXMoved.value = offset; }); }, onPanEnd: (DragEndDetails endDetails) { //结束 checkCaptcha(state.sliderXMoved.value); final int nowTime = DateTime.now().millisecondsSinceEpoch; _checkMilliseconds = nowTime - _checkMilliseconds; }, child: Obx(() { return Container( width: _bottomSliderSize, height: _bottomSliderSize, margin: EdgeInsets.only(left: state.sliderXMoved.value > 0 ? state.sliderXMoved.value : 1), decoration: const BoxDecoration( border: Border( top: BorderSide(width: 1, color: Color(0xffe5e5e5)), right: BorderSide(width: 1, color: Color(0xffe5e5e5)), bottom: BorderSide(width: 1, color: Color(0xffe5e5e5)), ), color: Colors.white, ), child: IconButton( icon: const Icon(Icons.arrow_forward), iconSize: 30, color: Colors.black54, onPressed: () {}, ), ); }), ) ], )) : Container(); } @override void dispose() { controller!.dispose(); super.dispose(); } } class MaxScaleTextWidget extends StatelessWidget { const MaxScaleTextWidget({Key? key, this.max = 1.0, required this.child}) : super(key: key); final double max; final Widget child; @override Widget build(BuildContext context) { final MediaQueryData data = MediaQuery.of(context); final double textScaleFactor = min(max, data.textScaleFactor); return MediaQuery(data: data.copyWith(textScaleFactor: textScaleFactor), child: child); } }