Skip to content

Instantly share code, notes, and snippets.

@PlugFox
Last active December 24, 2021 08:54
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save PlugFox/a4cb719747ad6120f91b3c8cf8e295ba to your computer and use it in GitHub Desktop.
Save PlugFox/a4cb719747ad6120f91b3c8cf8e295ba to your computer and use it in GitHub Desktop.
Timer BLoC
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