login_page.dart 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. import 'package:auto_route/auto_route.dart';
  2. import 'package:eitc_erm_dental_flutter/app_router.gr.dart';
  3. import 'package:eitc_erm_dental_flutter/funcs.dart';
  4. import 'package:eitc_erm_dental_flutter/pages/login/vm/login_view_model.dart';
  5. import 'package:eitc_erm_dental_flutter/pages/patient/vm/patient_view_model.dart';
  6. import 'package:eitc_erm_dental_flutter/vm/global_view_model.dart';
  7. import 'package:eitc_erm_dental_flutter/widget/counting_button.dart';
  8. import 'package:eitc_erm_dental_flutter/widget/main_button.dart';
  9. import 'package:flutter/foundation.dart';
  10. import 'package:flutter/gestures.dart';
  11. import 'package:flutter/material.dart';
  12. import 'package:flutter/services.dart';
  13. import 'package:flutter_riverpod/flutter_riverpod.dart';
  14. import 'package:flutter_screenutil/flutter_screenutil.dart';
  15. import '../../global.dart';
  16. @RoutePage(name: "loginRoute")
  17. class LoginPage extends ConsumerStatefulWidget {
  18. final bool cancelable;
  19. const LoginPage({super.key, required this.cancelable});
  20. @override
  21. ConsumerState createState() => _LoginPageState();
  22. }
  23. class _LoginPageState extends ConsumerState<LoginPage> {
  24. final TextEditingController _mobileTextController = TextEditingController();
  25. final TextEditingController _captchaTextController = TextEditingController();
  26. final CountingButtonController _countingButtonController =
  27. CountingButtonController();
  28. bool _isAgreementChecked = false;
  29. @override
  30. void initState() {
  31. super.initState();
  32. WidgetsBinding.instance.addPostFrameCallback((_) {
  33. //刷新本地就诊人列表数据
  34. ref.invalidate(localPatientListProvider);
  35. if (!widget.cancelable) {
  36. //停止检查连接
  37. ref
  38. .read(deviceConnectStatusProvider(videoChannel).notifier)
  39. .stopCheck();
  40. }
  41. });
  42. }
  43. @override
  44. Widget build(BuildContext context) {
  45. return Scaffold(
  46. appBar: AppBar(
  47. automaticallyImplyLeading: widget.cancelable,
  48. forceMaterialTransparency: true,
  49. ),
  50. body: SafeArea(
  51. child: SingleChildScrollView(
  52. child: Padding(
  53. padding: EdgeInsets.fromLTRB(15.w, 50.h, 15.w, 0.0),
  54. child: Column(children: [
  55. Text(
  56. getS().registerHint,
  57. textAlign: TextAlign.center,
  58. style: Theme.of(context).textTheme.titleMedium,
  59. ),
  60. SizedBox(
  61. height: 80.h,
  62. ),
  63. _getInputs(),
  64. SizedBox(
  65. height: 10.h,
  66. ),
  67. _getAgreements(),
  68. SizedBox(
  69. height: 80.h,
  70. ),
  71. SizedBox(
  72. width: double.infinity,
  73. child: MainButton(text: getS().login, onPressed: _onLogin),
  74. ),
  75. ]),
  76. ),
  77. )),
  78. );
  79. }
  80. ///输入框
  81. Widget _getInputs() {
  82. //保证内部元素高度一致
  83. return IntrinsicHeight(
  84. child: Row(
  85. children: [
  86. Column(
  87. mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  88. children: [
  89. Text(getS().mobileColon),
  90. SizedBox(
  91. height: 30.h,
  92. ),
  93. Text(getS().captchaColon)
  94. ],
  95. ),
  96. SizedBox(
  97. width: 10.w,
  98. ),
  99. Expanded(
  100. child: Column(
  101. children: [
  102. _getMobileInput(),
  103. SizedBox(
  104. height: 30.h,
  105. ),
  106. Row(
  107. children: [
  108. Expanded(child: _getCaptchInput()),
  109. SizedBox(
  110. width: 10.w,
  111. ),
  112. CountingButton(
  113. controller: _countingButtonController,
  114. onGetChild: (t, isCounting) {
  115. String str = getS().send;
  116. if (isCounting) {
  117. str = "$str(${60 - t})";
  118. }
  119. return Text(
  120. str,
  121. style: TextStyle(
  122. color: Theme.of(context).colorScheme.onPrimary,
  123. fontWeight: FontWeight.bold),
  124. );
  125. },
  126. onPressed: _onSendCaptcha,
  127. )
  128. ],
  129. ),
  130. ],
  131. ))
  132. ],
  133. ),
  134. );
  135. }
  136. ///手机号输入框
  137. Widget _getMobileInput() {
  138. return _getTextInput(_mobileTextController, 11, TextInputType.phone,
  139. getS().mobileInputHint, [FilteringTextInputFormatter.digitsOnly]);
  140. }
  141. ///验证码输入框
  142. Widget _getCaptchInput() {
  143. return _getTextInput(_captchaTextController, 6, TextInputType.number,
  144. getS().captchaInputHint, [FilteringTextInputFormatter.digitsOnly]);
  145. }
  146. ///文本输入框
  147. Widget _getTextInput(
  148. TextEditingController controller,
  149. int? maxLength,
  150. TextInputType? keyboardType,
  151. String hint,
  152. List<TextInputFormatter>? inputFormatters) {
  153. return Container(
  154. padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 10.h),
  155. decoration: BoxDecoration(
  156. color: Theme.of(context).colorScheme.surfaceContainerHigh,
  157. borderRadius: BorderRadius.circular(8.r)),
  158. child: TextField(
  159. controller: controller,
  160. maxLines: 1,
  161. maxLength: maxLength,
  162. keyboardType: keyboardType,
  163. decoration: InputDecoration(
  164. isDense: true,
  165. contentPadding: EdgeInsets.zero,
  166. border: const OutlineInputBorder(borderSide: BorderSide.none),
  167. counterText: "",
  168. hintText: hint,
  169. hintStyle: const TextStyle(color: Color(0xFF999999)),
  170. ),
  171. inputFormatters: inputFormatters,
  172. ),
  173. );
  174. }
  175. ///用户协议及隐私协议
  176. Widget _getAgreements() {
  177. return Row(
  178. children: [
  179. Checkbox(
  180. value: _isAgreementChecked,
  181. onChanged: (bo) {
  182. setState(() {
  183. _isAgreementChecked = bo ?? false;
  184. });
  185. }),
  186. Expanded(
  187. child: Text.rich(
  188. softWrap: true,
  189. TextSpan(text: getS().readAndAgree, children: [
  190. getClickTextSpan(getS().userAgreementBookTitle,
  191. () => gotoUserAgreement(context, checkWifi: false)),
  192. TextSpan(text: "、"),
  193. getClickTextSpan(getS().privacyPolicyBookTitle,
  194. () => gotoPrivacyPolicy(context, checkWifi: false)),
  195. ]),
  196. )),
  197. ],
  198. );
  199. }
  200. TextSpan getClickTextSpan(String text, GestureTapCallback onTap) {
  201. return TextSpan(
  202. text: text,
  203. style: TextStyle(color: Theme.of(context).colorScheme.primary),
  204. recognizer: TapGestureRecognizer()..onTap = onTap);
  205. }
  206. ///发送验证码
  207. void _onSendCaptcha() async {
  208. if (!validMobile(_mobileTextController.text)) {
  209. showToast(text: getS().mobileFormatError);
  210. return;
  211. }
  212. _countingButtonController.start(60);
  213. bool bo = await ref
  214. .read(captchaProvider.notifier)
  215. .sendCaptcha(_mobileTextController.text);
  216. showToast(text: bo ? getS().captchaHasSend : getS().captchaSendError);
  217. }
  218. ///验证输入
  219. Future<bool> _validInput() async {
  220. ///手机号
  221. if (_mobileTextController.text.isEmpty) {
  222. showToast(text: getS().mobileInputHint);
  223. return SynchronousFuture(false);
  224. }
  225. ///手机号格式
  226. if (!validMobile(_mobileTextController.text)) {
  227. showToast(text: getS().mobileFormatError);
  228. return SynchronousFuture(false);
  229. }
  230. ///验证码
  231. if (_captchaTextController.text.isEmpty) {
  232. showToast(text: getS().captchaInputHint);
  233. return SynchronousFuture(false);
  234. }
  235. ///用户协议及隐私协议
  236. if (!_isAgreementChecked) {
  237. showToast(text: getS().needReadAndAgree);
  238. return SynchronousFuture(false);
  239. }
  240. //调用接口验证验证码
  241. logd("验证验证码");
  242. if (!await ref.read(captchaProvider.notifier).checkCaptcha(
  243. _captchaTextController.text, _mobileTextController.text)) {
  244. showToast(text: getS().captchaError);
  245. return SynchronousFuture(false);
  246. }
  247. return SynchronousFuture(true);
  248. }
  249. void _onLogin() async {
  250. if (!await _validInput()) {
  251. return;
  252. }
  253. logd("登录");
  254. bool bo = await ref
  255. .read(loginProvider.notifier)
  256. .login(_mobileTextController.text);
  257. if (bo) {
  258. showToast(text: getS().loginSuccess);
  259. if (mounted) {
  260. popAllRoutes(context);
  261. context.pushRoute(const MainRoute());
  262. //刷新本地就诊人列表数据
  263. ref.invalidate(localPatientListProvider);
  264. }
  265. } else {
  266. showToast(text: getS().loginFailed);
  267. }
  268. }
  269. }