Skip to content

Commit

Permalink
Relax requirements for TimerController
Browse files Browse the repository at this point in the history
  • Loading branch information
Pante committed Oct 27, 2023
1 parent 7f9d1a7 commit bbcd4a6
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 104 deletions.
2 changes: 1 addition & 1 deletion stevia/example/lib/timer_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class _CountdownState extends State<CountdownExample> {
children: [
ValueListenableBuilder(
valueListenable: controller,
builder: (context, microseconds, child) => Text(TimerController.seconds(microseconds)),
builder: (context, microseconds, child) => Text('$microseconds'),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
Expand Down
7 changes: 7 additions & 0 deletions stevia/lib/services.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
/// * [ColorFilters]
/// * [IntTextInputFormatter]
///
///
/// ## Timer
/// Controllers that simply the implementation of timers.
///
/// * [TimerController]
library stevia.services;

import 'package:stevia/services.dart';
Expand All @@ -13,3 +18,5 @@ export 'package:stevia/services_time.dart';

export 'src/services/color_filters.dart' hide Matrix5;
export 'src/services/text_input_formatters.dart';

export 'src/services/timer_controller.dart';
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import 'package:flutter/foundation.dart';
///
/// No accompanying timer widget is provided. It is expected that users will create their own timer widget tailored to
/// their own use-cases.
///
///
/// A basic timer can be created as follows:
/// ```dart
/// class TimerExample extends StatefulWidget {
Expand All @@ -19,83 +19,138 @@ import 'package:flutter/foundation.dart';
/// _TimerState createState() => _TimerState();
///}
///
///class _TimerState extends State<TimerExample> {
///
/// late TimerController controller;
///
/// @override
/// void initState() {
/// super.initState();
/// controller = TimerController(duration: const Duration(seconds: 30))..run();
/// }
/// class _TimerState extends State<TimerExample> {
/// late TimerController controller;
///
/// @override
/// Widget build(BuildContext context) => Column(
/// children: [
/// ValueListenableBuilder(
/// valueListenable: controller,
/// builder: (context, microseconds, child) => Text(TimerController.seconds(microseconds)),
/// ),
/// IconButton(icon: const Icon(Icons.play_arrow), onPressed: controller.run),
/// IconButton(icon: const Icon(Icons.pause), onPressed: controller.pause),
/// IconButton(icon: const Icon(Icons.replay), onPressed: controller.reset),
/// ],
/// );
/// @override
/// void initState() {
/// super.initState();
/// controller = TimerController(duration: const Duration(seconds: 30))..run();
/// }
///
/// @override
/// void dispose() {
/// controller.dispose();
/// super.dispose();
/// }
/// @override
/// Widget build(BuildContext context) => Column(
/// children: [
/// ValueListenableBuilder(
/// valueListenable: controller,
/// builder: (context, microseconds, child) => Text('$microseconds'),
/// ),
/// IconButton(icon: const Icon(Icons.play_arrow), onPressed: controller.run),
/// IconButton(icon: const Icon(Icons.pause), onPressed: controller.pause),
/// IconButton(icon: const Icon(Icons.replay), onPressed: controller.reset),
/// ],
/// );
///
///}
/// @override
/// void dispose() {
/// controller.dispose();
/// super.dispose();
/// }
/// }
/// ```
sealed class TimerController extends ValueNotifier<int> {

/// Returns a string representation of the [microseconds], rounded to the nearest second, in the format, "HH:mm:ss".
///
/// For example, 30,120,000 microseconds yields `00:00:30.12`.
static String seconds(int microseconds) {
final hours = (microseconds ~/ Duration.microsecondsPerHour).toString().padLeft(2, '0');
microseconds = microseconds.remainder(Duration.microsecondsPerHour);

final minutes = (microseconds ~/ Duration.microsecondsPerMinute).toString().padLeft(2, '0');
microseconds = microseconds.remainder(Duration.microsecondsPerMinute);

final seconds = (microseconds ~/ Duration.microsecondsPerSecond).toString().padLeft(2, '0');
microseconds = microseconds.remainder(Duration.microsecondsPerSecond);

return '$hours:$minutes:$seconds';
}

/// The timer's duration.
final Duration duration;
/// The interval at which the timer is updated.
final Duration interval;
final int _initial;
final ValueNotifier<TimerState> _state;
Timer? _timer;

/// Creates a [TimerController].
///
/// ## Contract
/// The following will lead to undefined behaviour:
/// * [duration] is negative
/// * [interval] is non-positive
/// A non-positive [interval] will lead to undefined behaviour.
factory TimerController({
required Duration duration,
Duration interval = const Duration(seconds: 1),
bool ascending = false,
}) => ascending ? _AscendingTimerController(duration, interval, 0) : _DescendingTimerController(duration, interval, duration.inMicroseconds);
}) {
if (duration.inMicroseconds <= 0) {
return _EmptyController(duration, interval);
}

if (ascending) {
return _AscendingTimerController(duration, interval, 0);

TimerController._(this.duration, this.interval, this._initial):
assert(0 <= duration.inMicroseconds, 'The duration should be non-negative, but it is $duration.'),
} else {
return _DescendingTimerController(duration, interval, duration.inMicroseconds);
}
}

TimerController._(this.duration, this.interval, this._initial, [TimerState state = TimerState.idle]):
assert(0 < interval.inMicroseconds, 'The interval should be positive, but it is $interval.'),
_state = ValueNotifier(TimerState.idle),
_state = ValueNotifier(state),
super(_initial);


/// Starts or resumes the timer if it is idle or paused. Otherwise does nothing.
void run();

/// Pauses the timer if it is running. Otherwise does nothing.
void pause();

/// Resets the timer if it is running, paused or done, and [duration] is not zero. Otherwise does nothing.
void reset();


/// The timer's current state.
ValueListenable<TimerState> get state => _state;

@override
void dispose() {
_state.dispose();
super.dispose();
}
}

/// The possible timer states.
enum TimerState {
/// Signifies that the timer has not yet started, or has been reset.
///
/// Possible transitions:
/// * [idle] -> [running]
idle,
/// Signifies that the timer is currently running.
///
/// Possible transitions:
/// * [running] -> [paused]
/// * [running] -> [done]
/// * [running] -> [idle]
running,
/// Signifies that the timer is currently paused.
///
/// Possible transitions:
/// * [paused] -> [running]
/// * [paused] -> [idle]
paused,
/// Signifies that the timer has completed running.
///
/// Possible transitions:
/// * [done] -> [idle]
done,
}


class _EmptyController extends TimerController {
_EmptyController(Duration duration, Duration interval): super._(duration, interval, 0, TimerState.done);

@override
void run() {}

@override
void pause() {}

@override
void reset() {}
}


sealed class _Controller extends TimerController {
Timer? _timer;

_Controller(super.duration, super.interval, super._initial): super._();

@override
void run() {
final state = _state.value;
if (state == TimerState.idle || state == TimerState.paused) {
Expand All @@ -107,15 +162,15 @@ sealed class TimerController extends ValueNotifier<int> {
void _periodic(Timer timer);


/// Pauses the timer if it is running. Otherwise does nothing.
@override
void pause() {
if (_state.value == TimerState.running) {
_state.value = TimerState.paused;
_timer?.cancel();
}
}

/// Resets the timer if it is running, paused or done. Otherwise does nothing.
@override
void reset() {
if (_state.value != TimerState.idle) {
value = _initial;
Expand All @@ -124,20 +179,15 @@ sealed class TimerController extends ValueNotifier<int> {
}
}

/// The timer's current state.
ValueListenable<TimerState> get state => _state;

@override
void dispose() {
_timer?.cancel();
_state.dispose();
super.dispose();
}

}

class _AscendingTimerController extends TimerController {
_AscendingTimerController(super.duration, super.interval, super._initial): super._();
class _AscendingTimerController extends _Controller {
_AscendingTimerController(super.duration, super.interval, super._initial);

@override
void _periodic(Timer timer) {
Expand All @@ -153,8 +203,8 @@ class _AscendingTimerController extends TimerController {
}
}

class _DescendingTimerController extends TimerController {
_DescendingTimerController(super.duration, super.interval, super._initial): super._();
class _DescendingTimerController extends _Controller {
_DescendingTimerController(super.duration, super.interval, super._initial);

@override
void _periodic(Timer timer) {
Expand All @@ -169,31 +219,3 @@ class _DescendingTimerController extends TimerController {
}
}
}


/// The possible timer states.
enum TimerState {
/// Signifies that the timer has not yet started, or has been reset.
///
/// Possible transitions:
/// * [idle] -> [running]
idle,
/// Signifies that the timer is currently running.
///
/// Possible transitions:
/// * [running] -> [paused]
/// * [running] -> [done]
/// * [running] -> [idle]
running,
/// Signifies that the timer is currently paused.
///
/// Possible transitions:
/// * [paused] -> [running]
/// * [paused] -> [idle]
paused,
/// Signifies that the timer has completed running.
///
/// Possible transitions:
/// * [done] -> [idle]
done,
}
7 changes: 0 additions & 7 deletions stevia/lib/widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,6 @@
/// Widgets that contain children which can be resized either horizontally or vertically.
///
/// * [ResizableBox]
///
/// ## Timer
/// Controllers that simply the implementation of timers.
///
/// * [TimerController]
library stevia.widgets;

import 'package:stevia/widgets.dart';
Expand All @@ -35,5 +30,3 @@ export 'src/widgets/async/stream_value_builder.dart';
export 'src/widgets/resizable/resizable_box.dart';
export 'src/widgets/resizable/resizable_icon.dart';
export 'src/widgets/resizable/resizable_region.dart';

export 'src/widgets/timer/timer_controller.dart';
Loading

0 comments on commit bbcd4a6

Please sign in to comment.