diff --git a/circle_app/assets/images/circle/open_vip_bg.png b/circle_app/assets/images/circle/open_vip_bg.png new file mode 100644 index 0000000..01c9105 Binary files /dev/null and b/circle_app/assets/images/circle/open_vip_bg.png differ diff --git a/circle_app/assets/images/circle/selected.png b/circle_app/assets/images/circle/selected.png new file mode 100644 index 0000000..4fdfaee Binary files /dev/null and b/circle_app/assets/images/circle/selected.png differ diff --git a/circle_app/assets/images/circle/vip_func.png b/circle_app/assets/images/circle/vip_func.png new file mode 100644 index 0000000..4b5c57a Binary files /dev/null and b/circle_app/assets/images/circle/vip_func.png differ diff --git a/circle_app/lib/app/circle/view.dart b/circle_app/lib/app/circle/view.dart index 92a1cf6..dcf65b1 100644 --- a/circle_app/lib/app/circle/view.dart +++ b/circle_app/lib/app/circle/view.dart @@ -1,3 +1,4 @@ +import 'package:circle_app/common/Widgets/open_vip_tip/view.dart'; import 'package:circle_app/router/app_routers.dart'; import 'package:circle_app/util/util.dart'; import 'package:flutter/material.dart'; @@ -305,8 +306,10 @@ class CirclePage extends StatelessWidget { )), GestureDetector( onTap: () { - Get.bottomSheet(tipWdiget(), - isScrollControlled: true, enableDrag: false); + Get.toNamed(AppRoutes.Login); + // Get.bottomSheet(tipWdiget(), + // Get.bottomSheet(Open_vip_tipPage(), + // isScrollControlled: true, enableDrag: false); }, child: Image.asset( getCircleImage('add'), diff --git a/circle_app/lib/app/login/complete_material/logic.dart b/circle_app/lib/app/login/complete_material/logic.dart index cf0b8d5..c62ab85 100644 --- a/circle_app/lib/app/login/complete_material/logic.dart +++ b/circle_app/lib/app/login/complete_material/logic.dart @@ -1,5 +1,6 @@ import 'package:circle_app/router/app_routers.dart'; import 'package:circle_app/util/util.dart'; +import 'package:flutter_pickers/time_picker/model/pduration.dart'; import 'package:get/get.dart'; import 'package:image_picker/image_picker.dart'; @@ -47,4 +48,27 @@ class Complete_materialLogic extends GetxController { pushHome() { Get.toNamed(AppRoutes.Home); } + + //时间戳转年龄 + String getAge(PDuration brt){ + int age = 0; + DateTime dateTime = DateTime.now(); + + int yearNow = dateTime.year; //当前年份 + int monthNow = dateTime.month; //当前月份 + int dayOfMonthNow = dateTime.day; //当前日期 + + int yearBirth = brt.year!; + int monthBirth = brt.month!; + int dayOfMonthBirth = brt.day!; + age = yearNow - yearBirth; //计算整岁数 + if (monthNow <= monthBirth) { + if (monthNow == monthBirth) { + if (dayOfMonthNow < dayOfMonthBirth) age--;//当前日期在生日之前,年龄减一 + } else { + age--;//当前月份在生日之前,年龄减一 + } + } + return age.toString(); + } } diff --git a/circle_app/lib/app/login/complete_material/state.dart b/circle_app/lib/app/login/complete_material/state.dart index 520c46d..8f8370b 100644 --- a/circle_app/lib/app/login/complete_material/state.dart +++ b/circle_app/lib/app/login/complete_material/state.dart @@ -6,6 +6,7 @@ class Complete_materialState { String nickName = ''; String sex = ''; String age = ''; + String brithday = ''; String role = ''; String orientation = ''; List interest = []; diff --git a/circle_app/lib/app/login/complete_material/view.dart b/circle_app/lib/app/login/complete_material/view.dart index 646dbfa..359a010 100644 --- a/circle_app/lib/app/login/complete_material/view.dart +++ b/circle_app/lib/app/login/complete_material/view.dart @@ -1,6 +1,12 @@ import 'package:circle_app/components/my_app_bar.dart'; import 'package:circle_app/util/util.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_pickers/pickers.dart'; +import 'package:flutter_pickers/style/default_style.dart'; +import 'package:flutter_pickers/style/picker_style.dart'; +import 'package:flutter_pickers/time_picker/model/date_mode.dart'; +import 'package:flutter_pickers/time_picker/model/pduration.dart'; +import 'package:flutter_pickers/time_picker/model/suffix.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; @@ -8,20 +14,21 @@ import 'logic.dart'; class Complete_materialPage extends StatelessWidget { Complete_materialPage({Key? key}) : super(key: key); - + final logic = Get.put(Complete_materialLogic()); + // PickerStyle pickerStyle = PickerStyle(); @override Widget build(BuildContext context) { return GetBuilder(builder: (Complete_materialLogic controller) { return Stack( children: [ - Container( - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height, - child: Image.asset( - getBaseImage('bg'), - fit: BoxFit.fill, - ), + Container( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height, + child: Image.asset( + getBaseImage('bg'), + fit: BoxFit.fill, ), + ), Scaffold( appBar: MyAppBar( centerTitle: '完善您的个人形象', @@ -77,7 +84,7 @@ class Complete_materialPage extends StatelessWidget { fontWeight: FontWeight.w500, fontSize: 14.sp), border: InputBorder.none, - contentPadding: EdgeInsets.only(left: 20.sp)), + ), ), () {}, img: 'edit'), @@ -89,38 +96,45 @@ class Complete_materialPage extends StatelessWidget { color: Colors.white, fontSize: 14.sp, fontWeight: FontWeight.w500), - ), - () {}), + ), () { + showSexPiker(context, controller); + }), funcWidget( '年龄', Text( - controller.state.sex, + controller.state.age, style: TextStyle( color: Colors.white, fontSize: 14.sp, fontWeight: FontWeight.w500), ), - () {}), + () { + showAgePiker(context,controller,); + }), funcWidget( '角色', Text( - controller.state.sex, + controller.state.role, style: TextStyle( color: Colors.white, fontSize: 14.sp, fontWeight: FontWeight.w500), ), - () {}), + () { + showRolePiker(context, controller); + }), funcWidget( '取向', Text( - controller.state.sex, + controller.state.orientation, style: TextStyle( color: Colors.white, fontSize: 14.sp, fontWeight: FontWeight.w500), ), - () {}), + () { + showOrientationPiker(context, controller); + }), funcWidget( '兴趣', Row( @@ -164,7 +178,7 @@ class Complete_materialPage extends StatelessWidget { ), ), GestureDetector( - onTap:() { + onTap: () { controller.pushHome(); // controller.checkInfo(); }, @@ -209,6 +223,7 @@ class Complete_materialPage extends StatelessWidget { children: [ Expanded( child: GestureDetector( + behavior: HitTestBehavior.opaque, onTap: callback, child: Row( mainAxisAlignment: MainAxisAlignment.center, @@ -221,7 +236,10 @@ class Complete_materialPage extends StatelessWidget { shadows: [ Shadow(color: Color(0xffF657FF), blurRadius: 4.0.sp) ])), - Expanded(child: tipWidget), + Expanded(child: Padding( + padding: EdgeInsets.only(left: 20.sp), + child: tipWidget, + )), Image.asset( getLoginImage(img), width: img == 'down_arr' ? 24.sp : 18.sp, @@ -263,4 +281,66 @@ class Complete_materialPage extends StatelessWidget { ), ); } + + showSexPiker(context, Complete_materialLogic controller) { + Pickers.showSinglePicker(context, + pickerStyle:DefaultPickerStyle.dark(), + data: '男、女、MTF、FTM、CD、酷儿'.split('、'), + selectData: controller.state.sex.isNotEmpty ? controller.state.sex : '男', onConfirm: (p, position) { + controller.state.sex = p; + controller.update(); + }); + } + + showAgePiker(context, Complete_materialLogic controller) { + var date = PDuration.now(); + PDuration currentDur = PDuration(year: date.year! - 18, month: date.month, day: date.day); + if (controller.state.brithday.isNotEmpty) { + + } + Pickers.showDatePicker( + context, + pickerStyle:DefaultPickerStyle.dark(), + // context, + // 模式,详见下方 + mode: DateMode.YMD, + // 后缀 默认Suffix.normal(),为空的话Suffix() + suffix: Suffix(years: ' 年', month: ' 月', days: ' 日'), + // 样式 详见下方样式 + // pickerStyle: pickerStyle, + // 默认选中 + selectDate: currentDur, + maxDate: PDuration(year: date.year! - 18, month: date.month, day: date.day), + minDate: PDuration(year: date.year! - 100, month: date.month, day: date.day), + onConfirm: (p) { + String month = p.month! < 10 ? '0${p.month!}' : p.month!.toString(); + String day = p.day! < 10 ? '0${p.day!}' : p.day!.toString(); + var time ='${p.year}-${month}-${day}'; + controller.state.age = controller.getAge(p); + controller.state.brithday = time; + controller.update(); + }, + // onChanged: (p) => print(p), + ); + } + + showRolePiker(context, Complete_materialLogic controller) { + Pickers.showSinglePicker(context, + pickerStyle:DefaultPickerStyle.dark(), + data: 'Sado、Maso、Dom、Sub、Switch'.split('、'), + selectData: controller.state.role.isNotEmpty ? controller.state.role : 'Sado', onConfirm: (p, position) { + controller.state.role = p; + controller.update(); + }); + } + // + showOrientationPiker(context, Complete_materialLogic controller) { + Pickers.showSinglePicker(context, + pickerStyle:DefaultPickerStyle.dark(), + data: '异性恋、同性恋、双性恋、泛性恋、无性恋、智性恋、性单恋'.split('、'), + selectData: controller.state.orientation.isNotEmpty ? controller.state.orientation : '异性恋', onConfirm: (p, position) { + controller.state.orientation = p; + controller.update(); + }); + } } diff --git a/circle_app/lib/app/login/login/logic.dart b/circle_app/lib/app/login/login/logic.dart new file mode 100644 index 0000000..0e64ae2 --- /dev/null +++ b/circle_app/lib/app/login/login/logic.dart @@ -0,0 +1,48 @@ +import 'dart:async'; + +import 'package:circle_app/router/app_routers.dart'; +import 'package:circle_app/util/util.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:get/get.dart'; + +import 'state.dart'; + +class LoginLogic extends GetxController { + final LoginState state = LoginState(); + TextEditingController phoneEditingController = TextEditingController(); + TextEditingController codeEditingController = TextEditingController(); + + bool sendCodeBtn = false; //判断发送短信按钮是否点击过标志 + int seconds = 60; + Timer? t; + + //倒计时 + starDownTimer() { + if (sendCodeBtn == false && seconds == 60) { + sendCodeBtn = true; + t = Timer.periodic(Duration(milliseconds: 1000), (timer) { + seconds--; + if (seconds == 0) { + t?.cancel(); //清除 + sendCodeBtn = false; + seconds = 60; + } + update(); + }); + } + } + + @override + void dispose() { + // TODO: implement dispose + super.dispose(); + t?.cancel(); + } + + void login() { + Get.toNamed(AppRoutes.Complete_materialPage); + // if (!GetUtils.isPhoneNumber(phoneEditingController.text)) { + // showToast('请输入正确的手机号'); + // } + } +} diff --git a/circle_app/lib/app/login/login/state.dart b/circle_app/lib/app/login/login/state.dart new file mode 100644 index 0000000..6325be6 --- /dev/null +++ b/circle_app/lib/app/login/login/state.dart @@ -0,0 +1,5 @@ +class LoginState { + LoginState() { + ///Initialize variables + } +} diff --git a/circle_app/lib/app/login/login/view.dart b/circle_app/lib/app/login/login/view.dart new file mode 100644 index 0000000..ed4d303 --- /dev/null +++ b/circle_app/lib/app/login/login/view.dart @@ -0,0 +1,220 @@ +import 'package:circle_app/util/util.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; + +import 'logic.dart'; + +class LoginPage extends StatelessWidget { + LoginPage({Key? key}) : super(key: key); + + final logic = Get.put(LoginLogic()); + final state = Get + .find() + .state; + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + body: GestureDetector( + onTap: () { + FocusManager.instance.primaryFocus?.unfocus(); + }, + child: GetBuilder(builder: (logic) { + return Stack( + alignment: Alignment.center, + children: [ + Image.asset( + getBaseImage('bg'), + fit: BoxFit.fill, + width: Get.width, + height: Get.height, + ), + Container( + width: Get.width, + height: Get.height, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + // margin: EdgeInsets.only( + // top: MediaQuery.of(context).padding.top + 100.sp), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + ' +86', + style: TextStyle( + color: Colors.white, fontSize: 16.sp), + ), + Stack( + children: [ + Container( + width: 200.sp, + child: TextField( + controller: logic.phoneEditingController, + maxLength: 11, + style: TextStyle( + color: Colors.white, fontSize: 16.sp), + decoration: InputDecoration( + hintText: '请输入手机号', + hintStyle: TextStyle( + color: Colors.white70, + fontSize: 16.sp), + border: InputBorder.none, + counterText: '', + contentPadding: EdgeInsets.only( + left: 16.sp)), + ), + ), + Positioned( + bottom: 0, + right: 0, + child: Container(width: 184.sp, + height: 0.5.sp, + color: Colors.white60,)) + ], + ) + ], + ), + ), + Container( + margin: EdgeInsets.only(top: 20.sp), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '验证码', + style: TextStyle( + color: Colors.white, fontSize: 16.sp), + ), + Stack( + alignment: Alignment.center, + children: [ + Container( + width: 200.sp, + child: TextField( + controller: logic.codeEditingController, + maxLength: 8, + style: TextStyle( + color: Colors.white, fontSize: 16.sp), + decoration: InputDecoration( + hintText: '请输入验证码', + hintStyle: TextStyle( + color: Colors.white70, + fontSize: 16.sp), + border: InputBorder.none, + counterText: '', + contentPadding: EdgeInsets.only( + left: 16.sp)), + ), + ), + Positioned( + right: 0, + child: GestureDetector( + onTap: () { + logic.starDownTimer(); + }, + child: Container( + alignment: Alignment.center, + width: 76.sp, + height: 29.sp, + decoration: BoxDecoration( + color: Colors.white30, + borderRadius: BorderRadius.circular( + 29.sp / 2) + ), + child: Text(logic.sendCodeBtn ? '${logic.seconds}s' : '获取验证码', style: TextStyle( + color: Colors.white, fontSize: 12.sp),), + ), + )), + Positioned( + bottom: 0, + right: 0, + child: Container(width: 184.sp, + height: 0.5.sp, + color: Colors.white60,)) + ], + ) + ], + ), + ), + + ], + ), + ), + Positioned( + bottom: 182.sp, + child: GestureDetector( + onTap: () { + logic.login(); + }, + child: Container( + width: 168.sp, + height: 42.sp, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(21.sp), + gradient: const LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [Color(0xff0AFCFF), Color(0xffD739EA)])), + child: Text( + '登录', + style: TextStyle(color: Colors.white, fontSize: 16.sp), + ), + ), + ), + ), + Positioned( + bottom: 17.sp + MediaQuery.of(context).padding.bottom, + child: Container( + child: Row( + children: [ + Text( + '点击登录即表示同意', + style: TextStyle( + color: Colors.white, + fontSize: 11.sp, + fontWeight: FontWeight.w600), + ), + SizedBox( + width: 2.sp, + ), + Text( + '《用户协议》', + style: TextStyle( + color: Color(0xff00FFF4), + fontSize: 11.sp, + fontWeight: FontWeight.w400, + ), + ), + Text( + '和', + style: TextStyle( + color: Colors.white, + fontSize: 11.sp, + fontWeight: FontWeight.w600), + ), + SizedBox( + width: 2.sp, + ), + Text( + '《隐私协议》', + style: TextStyle( + color: Color(0xff00FFF4), + fontSize: 11.sp, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + )) + + ], + ); + }), + )); + } +} diff --git a/circle_app/lib/common/Widgets/open_vip_tip/logic.dart b/circle_app/lib/common/Widgets/open_vip_tip/logic.dart new file mode 100644 index 0000000..c15a926 --- /dev/null +++ b/circle_app/lib/common/Widgets/open_vip_tip/logic.dart @@ -0,0 +1,8 @@ +import 'package:get/get.dart'; + +import 'state.dart'; + +class Open_vip_tipLogic extends GetxController { + int index = 0; + final Open_vip_tipState state = Open_vip_tipState(); +} diff --git a/circle_app/lib/common/Widgets/open_vip_tip/state.dart b/circle_app/lib/common/Widgets/open_vip_tip/state.dart new file mode 100644 index 0000000..afe545e --- /dev/null +++ b/circle_app/lib/common/Widgets/open_vip_tip/state.dart @@ -0,0 +1,5 @@ +class Open_vip_tipState { + Open_vip_tipState() { + ///Initialize variables + } +} diff --git a/circle_app/lib/common/Widgets/open_vip_tip/view.dart b/circle_app/lib/common/Widgets/open_vip_tip/view.dart new file mode 100644 index 0000000..d3d1133 --- /dev/null +++ b/circle_app/lib/common/Widgets/open_vip_tip/view.dart @@ -0,0 +1,164 @@ +import 'package:circle_app/util/util.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; + +import 'logic.dart'; + +class Open_vip_tipPage extends StatelessWidget { + Open_vip_tipPage({Key? key}) : super(key: key); + + final logic = Get.put(Open_vip_tipLogic()); + final state = Get.find().state; + + @override + Widget build(BuildContext context) { + return GetBuilder(builder: (Open_vip_tipLogic controller) { + return Container( + width: Get.width, + height: Get.height, + child: Center( + child: Container( + width: 339.sp, + height: 453.sp, + decoration: BoxDecoration( + image: DecorationImage( + fit: BoxFit.fill, + image: AssetImage(getCircleImage('open_vip_bg')))), + child: Stack( + alignment: Alignment.center, + children: [ + Positioned( + top: 5.sp, + right: 12.sp, + child: GestureDetector( + onTap: () { + Get.back(); + }, + child: Image.asset( + getCircleImage('close'), + width: 24.sp, + ), + )), + Positioned( + top: 24.sp, + child: Text( + '兽迷会员十大特权', + style: TextStyle(color: Colors.white, fontSize: 16.sp), + )), + Positioned( + left: 17.sp, + top: 64.sp, + child: Text( + '为什么要解锁圈子?', + style: TextStyle(color: Colors.white, fontSize: 16.sp), + )), + Positioned( + top: 227.sp, + child: Container( + width: 339.sp, + padding: EdgeInsets.only(left: 17.sp, right: 17.sp), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + typeItem(controller,controller.index == 0,0), + + typeItem(controller,controller.index == 1,1), + typeItem(controller,controller.index == 2,2), + ], + ))), + Positioned( + bottom: 48.sp, + child: Container( + width: 168.sp, + height: 42.sp, + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(21.sp), + gradient: const LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [Color(0xff0AFCFF), Color(0xffD739EA)])), + child: Text( + '立即解锁', + style: TextStyle(color: Colors.white, fontSize: 16.sp), + ), + )), + Positioned( + bottom: 17.sp, + child: Container( + child: Row( + children: [ + Text( + '点击加入即表示同意', + style: TextStyle( + color: Colors.white, + fontSize: 11.sp, + fontWeight: FontWeight.w600), + ), + SizedBox( + width: 2.sp, + ), + Text( + '《合伙人协议》', + style: TextStyle( + color: Color(0xff00FFF4), + fontSize: 11.sp, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + )) + ], + ), + ), + ), + ); + }); + + } + + typeItem(Open_vip_tipLogic controller,bool isSelected,int index) { + return GestureDetector( + onTap: () { + controller.index = index; + controller.update(); + }, + child: Stack( + alignment: Alignment.center, + children: [ + isSelected ? Image.asset( + getCircleImage('selected'), + width: 98.sp, + height: 110.sp, + fit: BoxFit.fill, + ) : Container(), + Container( + width: isSelected ? 96.sp : 98.sp, + height: isSelected ? 108.sp : 110.sp, + decoration: BoxDecoration( + color: Color.fromRGBO(255, 255, 255, 0.1), + borderRadius: BorderRadius.circular(10.sp), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text('3个月',style: TextStyle(color: Colors.white,fontSize: 14.sp),), + Text('0.67元/天',style: TextStyle(color: Colors.white,fontSize: 14.sp),), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('58元',style: TextStyle(color: Colors.white,fontSize: 14.sp),), + SizedBox(width: 4.sp,), + Text('388元',style: TextStyle(color: Colors.white70,fontSize: 10.sp, decoration: TextDecoration.lineThrough, + decorationColor: Colors.white70,),), + ], + ) + ], + ), + ), + ], + )); + } +} diff --git a/circle_app/lib/router/app_pages.dart b/circle_app/lib/router/app_pages.dart index ccf25a0..737f765 100644 --- a/circle_app/lib/router/app_pages.dart +++ b/circle_app/lib/router/app_pages.dart @@ -11,6 +11,7 @@ import 'package:circle_app/app/help/binding.dart'; import 'package:circle_app/app/help/view.dart'; import 'package:circle_app/app/home/binding.dart'; import 'package:circle_app/app/home/view.dart'; +import 'package:circle_app/app/login/login/view.dart'; import 'package:circle_app/app/minefragment/binding.dart'; import 'package:circle_app/app/minefragment/view.dart'; import 'package:circle_app/app/photoinfo/binding.dart'; @@ -90,5 +91,6 @@ class AppPages { page: () => Call_outPage(), binding: Call_outBinding(), ), + GetPage(name: AppRoutes.Login, page: () => LoginPage()) ]; } diff --git a/circle_app/lib/router/app_routers.dart b/circle_app/lib/router/app_routers.dart index 6a169c5..7a639ba 100644 --- a/circle_app/lib/router/app_routers.dart +++ b/circle_app/lib/router/app_routers.dart @@ -2,6 +2,7 @@ abstract class AppRoutes { static const Home = '/home'; static const Complete_materialPage = '/Complete_materialPage'; static const Call_out = '/Call_out'; + static const Login = '/Login'; static const MineFragment = '/home/minefragment'; static const SetUpActivity = '/user/SetUpActivity'; static const AccountActivity = '/user/AccountActivity'; diff --git a/circle_app/pubspec.yaml b/circle_app/pubspec.yaml index 8e7f4dc..c71df89 100644 --- a/circle_app/pubspec.yaml +++ b/circle_app/pubspec.yaml @@ -36,8 +36,11 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 get: ^4.5.0 + # 屏幕适配 flutter_screenutil: ^5.6.0 + # 相册库 image_picker: ^0.8.6 + # Toast弹窗 fluttertoast: ^8.1.0 # 清除缓存 flutter_cache_manager: ^3.2.0 @@ -45,7 +48,10 @@ dependencies: url_launcher: ^6.0.0 # 下拉刷新 pull_to_refresh: ^2.0.0 + # 轮播 flutter_swiper: ^1.1.6 + # 选择器弹窗 + flutter_pickers: ^2.1.9 dev_dependencies: flutter_test: