Commit 17a806898123aa4215fd32331797f156ef980868
1 parent
f138a761
feat:倒计时功能开发
Showing
7 changed files
with
231 additions
and
38 deletions
lib/common/blocs/timerbloc/timer_bloc.dart
0 → 100644
| 1 | +import 'dart:async'; | ||
| 2 | + | ||
| 3 | +import 'package:flutter/cupertino.dart'; | ||
| 4 | +import 'package:equatable/equatable.dart'; | ||
| 5 | +import 'package:flutter_bloc/flutter_bloc.dart'; | ||
| 6 | +import 'package:wow_english/common/widgets/timer_ticker.dart'; | ||
| 7 | + | ||
| 8 | +part 'timer_event.dart'; | ||
| 9 | +part 'timer_state.dart'; | ||
| 10 | + | ||
| 11 | +class TimerBloc extends Bloc<TimerEvent, TimerState> { | ||
| 12 | + /// 定时器时间 | ||
| 13 | + static const int _duration = 60; | ||
| 14 | + /// 定时器数据流 | ||
| 15 | + final TimerTicker _ticker; | ||
| 16 | + // 流订阅 | ||
| 17 | + StreamSubscription<int>? _tickerSubscription; | ||
| 18 | + TimerBloc({required TimerTicker ticker}) | ||
| 19 | + :_ticker = ticker, | ||
| 20 | + super(const TimerInitial(_duration)) { | ||
| 21 | + on<StartEvent>(_onStarted); | ||
| 22 | + on<TickEvent>(_onTicked); | ||
| 23 | + on<PausedEvent>(_onPaused); | ||
| 24 | + on<ResumedEvent>(_onResumed); | ||
| 25 | + on<ResetEvent>(_onReset); | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + @override | ||
| 29 | + Future<void> close() { | ||
| 30 | + _tickerSubscription?.cancel(); | ||
| 31 | + return super.close(); | ||
| 32 | + } | ||
| 33 | + | ||
| 34 | + /// 开始计时 | ||
| 35 | + void _onStarted(StartEvent event,Emitter<TimerState> emitter) async { | ||
| 36 | + emitter(RunningState(event.duration)); | ||
| 37 | + _tickerSubscription?.cancel(); | ||
| 38 | + _tickerSubscription = _ticker | ||
| 39 | + .tick(ticks: event.duration) | ||
| 40 | + .listen((event) => add(TickEvent(duration: event))); | ||
| 41 | + } | ||
| 42 | + | ||
| 43 | + /// 计时中,判断是否剩余时间 | ||
| 44 | + void _onTicked(TickEvent event,Emitter<TimerState> emitter) async { | ||
| 45 | + emitter(event.duration>0?RunningState(event.duration):const FinishedState()); | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + ///暂停,判断当时是否state为PausedState类型,之后暂停订阅,发送暂停state | ||
| 49 | + void _onPaused(PausedEvent event, Emitter<TimerState> emit) async{ | ||
| 50 | + if( state is RunningState) { | ||
| 51 | + _tickerSubscription?.pause(); | ||
| 52 | + emit(PausedState(state.duration)); | ||
| 53 | + } | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + /// 恢复,我们判断当前state为暂停状态后,进行恢复 | ||
| 57 | + void _onResumed(ResumedEvent event, Emitter<TimerState> emit) async{ | ||
| 58 | + if( state is PausedState) { | ||
| 59 | + _tickerSubscription?.resume(); | ||
| 60 | + emit(RunningState(state.duration)); | ||
| 61 | + } | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + /// 重置,初始化 | ||
| 65 | + void _onReset(ResetEvent event ,Emitter<TimerState> emit) async{ | ||
| 66 | + _tickerSubscription?.cancel(); | ||
| 67 | + emit(const TimerInitial(_duration)); | ||
| 68 | + } | ||
| 69 | +} |
lib/common/blocs/timerbloc/timer_event.dart
0 → 100644
| 1 | +part of 'timer_bloc.dart'; | ||
| 2 | + | ||
| 3 | +@immutable | ||
| 4 | +abstract class TimerEvent extends Equatable { | ||
| 5 | + const TimerEvent(); | ||
| 6 | + | ||
| 7 | + @override | ||
| 8 | + List<Object> get props => []; | ||
| 9 | +} | ||
| 10 | + | ||
| 11 | +/// 开始事件 | ||
| 12 | +class StartEvent extends TimerEvent { | ||
| 13 | + /// 定时器时间 | ||
| 14 | + final int duration; | ||
| 15 | + | ||
| 16 | + const StartEvent({required this.duration}); | ||
| 17 | +} | ||
| 18 | + | ||
| 19 | +/// 定时器事件 | ||
| 20 | +class TickEvent extends TimerEvent { | ||
| 21 | + const TickEvent({required this.duration}); | ||
| 22 | + /// 当前时间 | ||
| 23 | + final int duration; | ||
| 24 | + | ||
| 25 | + @override | ||
| 26 | + List<Object> get props => [duration]; | ||
| 27 | +} | ||
| 28 | + | ||
| 29 | +/// 暂停事件 | ||
| 30 | +class PausedEvent extends TimerEvent {} | ||
| 31 | + | ||
| 32 | +/// 恢复状态 | ||
| 33 | +class ResumedEvent extends TimerEvent {} | ||
| 34 | + | ||
| 35 | +/// 重置状态 | ||
| 36 | +class ResetEvent extends TimerEvent {} | ||
| 0 | \ No newline at end of file | 37 | \ No newline at end of file |
lib/common/blocs/timerbloc/timer_state.dart
0 → 100644
| 1 | +part of 'timer_bloc.dart'; | ||
| 2 | + | ||
| 3 | +@immutable | ||
| 4 | +abstract class TimerState extends Equatable { | ||
| 5 | + /// 时间 | ||
| 6 | + final int duration; | ||
| 7 | + /// 构造方法 | ||
| 8 | + const TimerState(this.duration); | ||
| 9 | + @override | ||
| 10 | + List<Object> get props => [duration]; | ||
| 11 | +} | ||
| 12 | + | ||
| 13 | +class TimerInitial extends TimerState { | ||
| 14 | + const TimerInitial(super.duration); | ||
| 15 | +} | ||
| 16 | + | ||
| 17 | +/// 暂停状态 | ||
| 18 | +class PausedState extends TimerState { | ||
| 19 | + const PausedState(super.duration); | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | +/// 运行状态 | ||
| 23 | +class RunningState extends TimerState { | ||
| 24 | + const RunningState(super.duration); | ||
| 25 | +} | ||
| 26 | + | ||
| 27 | +/// 完成状态 | ||
| 28 | +class FinishedState extends TimerState{ | ||
| 29 | + const FinishedState() : super(0); | ||
| 30 | +} | ||
| 31 | + | ||
| 32 | +class BtnState extends TimerState { | ||
| 33 | + const BtnState(super.duration); | ||
| 34 | +} | ||
| 0 | \ No newline at end of file | 35 | \ No newline at end of file |
lib/common/widgets/timer_ticker.dart
0 → 100644
lib/login/loginpage/bloc/login_bloc.dart
| 1 | -import 'dart:async'; | ||
| 2 | - | ||
| 3 | import 'package:flutter/cupertino.dart'; | 1 | import 'package:flutter/cupertino.dart'; |
| 4 | -import 'package:flutter/foundation.dart'; | ||
| 5 | import 'package:flutter_bloc/flutter_bloc.dart'; | 2 | import 'package:flutter_bloc/flutter_bloc.dart'; |
| 6 | 3 | ||
| 7 | part 'login_event.dart'; | 4 | part 'login_event.dart'; |
| @@ -35,7 +32,6 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> { | @@ -35,7 +32,6 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> { | ||
| 35 | bool get agreement => _agreement; | 32 | bool get agreement => _agreement; |
| 36 | LoginType get loginType => _loginType; | 33 | LoginType get loginType => _loginType; |
| 37 | bool get canSendSms => _canSendSms; | 34 | bool get canSendSms => _canSendSms; |
| 38 | - int get countDown => _countDown; | ||
| 39 | bool get sendSmsIng => _sendSmsIng; | 35 | bool get sendSmsIng => _sendSmsIng; |
| 40 | 36 | ||
| 41 | LoginBloc() : super(LoginInitial()) { | 37 | LoginBloc() : super(LoginInitial()) { |
| @@ -45,8 +41,6 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> { | @@ -45,8 +41,6 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> { | ||
| 45 | on<AgreementChangeEvent>(_agreementTypeChange); | 41 | on<AgreementChangeEvent>(_agreementTypeChange); |
| 46 | on<ForgetPasswordEvent>(_forgetPassword); | 42 | on<ForgetPasswordEvent>(_forgetPassword); |
| 47 | on<CheckFieldChangeEvent>(_checkFiledChange); | 43 | on<CheckFieldChangeEvent>(_checkFiledChange); |
| 48 | - on<CountDownEvent>(_countDownChange); | ||
| 49 | - on<CancelCountDownEvent>(_cancelTimer); | ||
| 50 | } | 44 | } |
| 51 | 45 | ||
| 52 | 46 | ||
| @@ -111,16 +105,6 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> { | @@ -111,16 +105,6 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> { | ||
| 111 | 105 | ||
| 112 | } | 106 | } |
| 113 | 107 | ||
| 114 | - ///倒计时 | ||
| 115 | - void _countDownChange(CountDownEvent event, Emitter<LoginState> emitter) { | ||
| 116 | - _sendSmsIng = !_sendSmsIng; | ||
| 117 | - emitter(CountDownNumChangeState()); | ||
| 118 | - } | ||
| 119 | - | ||
| 120 | - void _cancelTimer(CancelCountDownEvent event,Emitter<LoginState> emitter) { | ||
| 121 | - | ||
| 122 | - } | ||
| 123 | - | ||
| 124 | ///登陆状态判断 | 108 | ///登陆状态判断 |
| 125 | bool _loginStateChange() { | 109 | bool _loginStateChange() { |
| 126 | if (_agreement) { | 110 | if (_agreement) { |
lib/login/loginpage/login_page.dart
| @@ -5,6 +5,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; | @@ -5,6 +5,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; | ||
| 5 | import 'package:wow_english/common/extension/string_extension.dart'; | 5 | import 'package:wow_english/common/extension/string_extension.dart'; |
| 6 | import 'package:wow_english/common/widgets/textfield_customer_widget.dart'; | 6 | import 'package:wow_english/common/widgets/textfield_customer_widget.dart'; |
| 7 | import 'package:wow_english/login/loginpage/bloc/login_bloc.dart'; | 7 | import 'package:wow_english/login/loginpage/bloc/login_bloc.dart'; |
| 8 | +import 'package:wow_english/login/loginpage/time_widget.dart'; | ||
| 8 | import 'package:wow_english/route/route.dart'; | 9 | import 'package:wow_english/route/route.dart'; |
| 9 | 10 | ||
| 10 | class LoginPage extends StatelessWidget { | 11 | class LoginPage extends StatelessWidget { |
| @@ -163,7 +164,7 @@ class LoginPage extends StatelessWidget { | @@ -163,7 +164,7 @@ class LoginPage extends StatelessWidget { | ||
| 163 | ); | 164 | ); |
| 164 | 165 | ||
| 165 | Widget _buildSmsViewWidget()=> BlocBuilder<LoginBloc,LoginState>( | 166 | Widget _buildSmsViewWidget()=> BlocBuilder<LoginBloc,LoginState>( |
| 166 | - builder: (context,state){ | 167 | + builder: (context,state) { |
| 167 | final bloc = BlocProvider.of<LoginBloc>(context); | 168 | final bloc = BlocProvider.of<LoginBloc>(context); |
| 168 | return Padding( | 169 | return Padding( |
| 169 | padding: EdgeInsets.symmetric(horizontal: 135.w), | 170 | padding: EdgeInsets.symmetric(horizontal: 135.w), |
| @@ -198,27 +199,8 @@ class LoginPage extends StatelessWidget { | @@ -198,27 +199,8 @@ class LoginPage extends StatelessWidget { | ||
| 198 | controller: bloc.checkNumController, | 199 | controller: bloc.checkNumController, |
| 199 | ) | 200 | ) |
| 200 | ), | 201 | ), |
| 201 | - GestureDetector( | ||
| 202 | - onTap: () { | ||
| 203 | - if (bloc.canSendSms) { | ||
| 204 | - bloc.add(CountDownEvent()); | ||
| 205 | - } | ||
| 206 | - }, | ||
| 207 | - child: Container( | ||
| 208 | - decoration: BoxDecoration( | ||
| 209 | - image: DecorationImage( | ||
| 210 | - image: AssetImage( | ||
| 211 | - bloc.canSendSms?'securitycode'.assetPng:'securitycode_dis'.assetPng | ||
| 212 | - ), | ||
| 213 | - fit: BoxFit.fill | ||
| 214 | - ), | ||
| 215 | - ), | ||
| 216 | - padding: const EdgeInsets.symmetric(horizontal:12.0,vertical: 15.0), | ||
| 217 | - child: Text( | ||
| 218 | - !bloc.sendSmsIng ? '获取验证码':'${bloc.countDown}s后在次获取' | ||
| 219 | - ), | ||
| 220 | - ), | ||
| 221 | - ) | 202 | + TimerWidget( |
| 203 | + canSendSms: bloc.canSendSms) | ||
| 222 | ], | 204 | ], |
| 223 | ) | 205 | ) |
| 224 | ], | 206 | ], |
lib/login/loginpage/time_widget.dart
0 → 100644
| 1 | +import 'package:flutter/cupertino.dart'; | ||
| 2 | +import 'package:flutter_bloc/flutter_bloc.dart'; | ||
| 3 | +import 'package:wow_english/common/blocs/timerbloc/timer_bloc.dart'; | ||
| 4 | +import 'package:wow_english/common/extension/string_extension.dart'; | ||
| 5 | +import 'package:wow_english/common/widgets/timer_ticker.dart'; | ||
| 6 | + | ||
| 7 | +class TimerWidget extends StatefulWidget { | ||
| 8 | + const TimerWidget({super.key, required this.canSendSms}); | ||
| 9 | + | ||
| 10 | + final bool canSendSms; | ||
| 11 | + | ||
| 12 | + @override | ||
| 13 | + State<StatefulWidget> createState() { | ||
| 14 | + return _TimerWidgetState(); | ||
| 15 | + } | ||
| 16 | +} | ||
| 17 | + | ||
| 18 | +class _TimerWidgetState extends State<TimerWidget> { | ||
| 19 | + late bool sendSmsIng; | ||
| 20 | + | ||
| 21 | + @override | ||
| 22 | + void initState() { | ||
| 23 | + super.initState(); | ||
| 24 | + sendSmsIng = false; | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + @override | ||
| 28 | + Widget build(BuildContext context) { | ||
| 29 | + return BlocProvider( | ||
| 30 | + create: (_) => TimerBloc(ticker: const TimerTicker()), | ||
| 31 | + child: _buildCountdownWidget(), | ||
| 32 | + ); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + Widget _buildCountdownWidget() => BlocBuilder<TimerBloc,TimerState>( | ||
| 36 | + buildWhen: (prev, state) => prev.runtimeType != state.runtimeType, | ||
| 37 | + builder: (context,state) { | ||
| 38 | + final bloc = BlocProvider.of<TimerBloc>(context); | ||
| 39 | + final duration = bloc.state.duration; | ||
| 40 | + final secondsStr = (duration % 60).floor().toString().padLeft(2, '0'); | ||
| 41 | + if (state is TimerInitial) { | ||
| 42 | + sendSmsIng = true; | ||
| 43 | + } else { | ||
| 44 | + sendSmsIng = false; | ||
| 45 | + } | ||
| 46 | + return GestureDetector( | ||
| 47 | + onTap: () { | ||
| 48 | + if (widget.canSendSms && !sendSmsIng) { | ||
| 49 | + bloc.add(ResetEvent()); | ||
| 50 | + bloc.add(StartEvent(duration: bloc.state.duration)); | ||
| 51 | + } | ||
| 52 | + }, | ||
| 53 | + child: Container( | ||
| 54 | + decoration: BoxDecoration( | ||
| 55 | + image: DecorationImage( | ||
| 56 | + image: AssetImage( | ||
| 57 | + widget.canSendSms?'securitycode'.assetPng:'securitycode_dis'.assetPng | ||
| 58 | + ), | ||
| 59 | + fit: BoxFit.fill | ||
| 60 | + ), | ||
| 61 | + ), | ||
| 62 | + padding: const EdgeInsets.symmetric(horizontal:12.0,vertical: 15.0), | ||
| 63 | + child: Row( | ||
| 64 | + children: [ | ||
| 65 | + if (state is TimerInitial)...[ | ||
| 66 | + const Text('获取验证码') | ||
| 67 | + ], | ||
| 68 | + if (state is RunningState)...[ | ||
| 69 | + Text('${secondsStr}s倒计时') | ||
| 70 | + ], | ||
| 71 | + if (state is FinishedState) ...[ | ||
| 72 | + const Text('获取验证码') | ||
| 73 | + ] | ||
| 74 | + ], | ||
| 75 | + ), | ||
| 76 | + ), | ||
| 77 | + ); | ||
| 78 | + }, | ||
| 79 | + ); | ||
| 80 | +} | ||
| 0 | \ No newline at end of file | 81 | \ No newline at end of file |