-
-
Save dened/6296036edb8cae5c87bd1ff0cadfde9a to your computer and use it in GitHub Desktop.
Timer BLoC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:async'; | |
import 'package:bloc/bloc.dart'; | |
import 'package:bloc_concurrency/bloc_concurrency.dart'; | |
import 'package:freezed_annotation/freezed_annotation.dart'; | |
part 'timer_bloc.freezed.dart'; | |
@freezed | |
class TimerEvent with _$TimerEvent { | |
const TimerEvent._(); | |
@Implements<_CountdownStarter>() | |
@With<_StartEmitter>() | |
@With<_TickEmitter>() | |
const factory TimerEvent.restart(final int countdown) = _RestartTimerEvent; | |
@With<_ResumeEmitter>() | |
@With<_TickEmitter>() | |
const factory TimerEvent.resume() = _ResumeTimerEvent; | |
@With<_PauseEmitter>() | |
const factory TimerEvent.pause() = _PauseTimerEvent; | |
} | |
@immutable | |
abstract class TimerState { | |
const TimerState._(this.countdown); | |
const factory TimerState.inProgress( | |
final int countdown, | |
) = _InProgressTimerState; | |
const factory TimerState.paused( | |
final int countdown, | |
) = _PausedTimerState; | |
bool get completed => countdown == 0; | |
bool get isPaused; | |
final int countdown; | |
T when<T extends Object?>( | |
T Function() inProgress, | |
T Function() paused, | |
); | |
} | |
class _InProgressTimerState extends TimerState { | |
const _InProgressTimerState( | |
final int countdown, | |
) : super._(countdown); | |
@override | |
bool get isPaused => false; | |
@override | |
T when<T extends Object?>( | |
T Function() inProgress, | |
T Function() paused, | |
) => | |
inProgress(); | |
} | |
class _PausedTimerState extends TimerState { | |
const _PausedTimerState(final int countdown) : super._(countdown); | |
@override | |
bool get isPaused => true; | |
@override | |
T when<T extends Object?>( | |
T Function() inProgress, | |
T Function() paused, | |
) => | |
paused(); | |
} | |
class TimerBLoC extends Bloc<TimerEvent, TimerState> { | |
TimerBLoC() : super(const TimerState.paused(0)) { | |
on<TimerEvent>( | |
(event, emit) => event.map<void>( | |
restart: (event) => _restart(event, emit), | |
resume: (event) => _resume(event, emit), | |
pause: (event) => _pause(event, emit), | |
), | |
transformer: restartable(), | |
); | |
} | |
Future<void> _restart(_RestartTimerEvent event, Emitter<TimerState> emit) async { | |
emit(event.restart(state: state)); | |
while (state.countdown > 0) { | |
/// TODO: альтернативный алгоритм ожидания, | |
/// для большей точности выравнивать время ожидания по времени устройства | |
await Future<void>.delayed(const Duration(seconds: 1)); | |
if (state.isPaused) return; | |
emit(event.tick(state: state)); | |
} | |
emit(const TimerState.paused(0)); | |
} | |
Future<void> _resume(_ResumeTimerEvent event, Emitter<TimerState> emit) async { | |
emit(event.resume(state: state)); | |
while (state.countdown > 0) { | |
/// TODO: альтернативный алгоритм ожидания, | |
/// для большей точности выравнивать время ожидания по времени устройства | |
await Future<void>.delayed(const Duration(seconds: 1)); | |
if (state.isPaused) return; | |
emit(event.tick(state: state)); | |
} | |
emit(const TimerState.paused(0)); | |
} | |
Future<void> _pause(_PauseTimerEvent event, Emitter<TimerState> emit) => Future<void>.sync( | |
() => emit( | |
event.pause(state: state), | |
), | |
); | |
} | |
@immutable | |
abstract class _CountdownStarter { | |
int get countdown; | |
} | |
mixin _StartEmitter on TimerEvent implements _CountdownStarter { | |
TimerState restart({required final TimerState state}) { | |
assert( | |
countdown > 0, | |
'Таймер обратного отсчета должен быть больше нуля', | |
); | |
return TimerState.inProgress(countdown); | |
} | |
} | |
mixin _ResumeEmitter on TimerEvent { | |
TimerState resume({required final TimerState state}) { | |
assert( | |
state is _PausedTimerState, | |
'Ожидается, что событие продолжения будет вызываться только после состояния паузы', | |
); | |
assert( | |
!state.completed, | |
'Ожидается, что событие продолжения не будет вызываться после того, как таймер достиг обратного отсчета', | |
); | |
return TimerState.inProgress(state.countdown); | |
} | |
} | |
mixin _TickEmitter on TimerEvent { | |
TimerState tick({required final TimerState state}) { | |
assert( | |
!state.completed, | |
'Ожидается, что последующий тик не будет генерироваться если таймер поставлен на паузу', | |
); | |
return TimerState.inProgress(state.countdown - 1); | |
} | |
} | |
mixin _PauseEmitter on TimerEvent { | |
TimerState pause({ | |
required final TimerState state, | |
}) { | |
assert( | |
state is _InProgressTimerState, | |
'Ожидается, что событие продолжения будет вызываться только после состояния обратного отсчета', | |
); | |
return TimerState.paused(state.countdown); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment