SendTrain/lib/providers/action_timer.dart

212 lines
6.5 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_sound/public/flutter_sound_player.dart';
import 'package:flutter_sound/flutter_sound.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'package:sendtrain/database/database.dart';
import 'package:sendtrain/models/action_model.dart';
class ActionTimer with ChangeNotifier {
ActionModel? actionModel;
double _progress = 0;
int _currentTime = 0;
final List<ItemScrollController> _scrollControllers = [];
final FlutterSoundPlayer _mPlayer = FlutterSoundPlayer();
ActionTimer();
Map get state => actionModel?.state ?? _stateConstructor();
ActionStatus get status => actionModel?.status ?? ActionStatus.pending;
bool get started => status == ActionStatus.started;
bool get paused => status == ActionStatus.paused;
bool get pending => status == ActionStatus.pending;
bool get complete => status == ActionStatus.complete;
bool get available => paused | pending;
List<Set> get sets => actionModel!.sets;
List<Item> get items => actionModel!.items;
Set get currentSet => sets[state['currentSet']];
Reps get currentRep => currentSet.reps[state['currentRep']];
Item get currentAction => allActions[state['currentAction']];
int get currentTime => _currentTime;
dynamic get currentValue => currentAction.valueType == RepType.time
? currentTime
: currentAction.value;
List<Item> get allActions => actionModel?.allItems ?? [];
String get repType =>
actionModel!.action.repType == RepType.time ? 'Seconds' : 'Reps';
int? get repLength => currentRep.value;
int? get repCount => currentRep.count;
dynamic get repValue =>
actionModel!.action.repType == RepType.time ? repLength : repCount;
double get progress => _progress;
int get totalTime => actionModel!.totalTime;
Timer? _periodicTimer;
Map _stateConstructor() {
return {
'currentSet': 0,
'currentRep': 0,
'currentTime': 0,
'currentAction': 0
};
}
void setup(ActionModel actionModel, ItemScrollController scrollController,
[bool resetOnLoad = true]) async {
_scrollControllers.add(scrollController);
if (resetOnLoad) {
if (this.actionModel == actionModel) {
reset();
_scrollControllers.add(scrollController);
}
this.actionModel = actionModel;
setAction(currentAction.id);
}
}
Future pause() async =>
await actionModel?.updateStatus(ActionStatus.paused).whenComplete(() {
_periodicTimer?.cancel();
notifyListeners();
// _mPlayer.stopPlayer();
// Be careful : you must `close` the audio session when you have finished with it.
});
Future start() async {
await actionModel!.updateStatus(ActionStatus.started);
await _mPlayer.openPlayer();
Uint8List? countTone;
Uint8List? finishTone;
await rootBundle
.load('assets/audio/count_tone.wav')
.then((data) => countTone = data.buffer.asUint8List());
await rootBundle
.load('assets/audio/count_finish.mp3')
.then((data) => finishTone = data.buffer.asUint8List());
// start timer
if (_periodicTimer == null || _periodicTimer!.isActive == false) {
_periodicTimer =
Timer.periodic(const Duration(seconds: 1), (Timer timer) async {
switch (currentAction.valueType) {
case RepType.count:
break;
case RepType.time:
_currentTime--;
if (_currentTime <= 3 && _currentTime != 0) {
await _mPlayer.startPlayer(
fromDataBuffer: countTone, codec: Codec.pcm16WAV);
}
if (_currentTime == 0) {
// move to next action
await _mPlayer.startPlayer(
fromDataBuffer: finishTone, codec: Codec.mp3);
await setAction(state['currentAction'] + 1);
}
await updateProgress();
notifyListeners();
}
});
}
notifyListeners();
}
Future close() async => await actionModel!
.updateStatus(ActionStatus.complete)
.whenComplete(() async {
_periodicTimer!.cancel();
_mPlayer.closePlayer();
notifyListeners();
});
Future reset() async {
await actionModel?.updateStatus(ActionStatus.pending);
await actionModel?.updateState(json.encode(_stateConstructor()));
_periodicTimer?.cancel();
_progress = 0;
_scrollControllers.clear();
_mPlayer.closePlayer();
notifyListeners();
}
Future clear() async {
await reset();
}
double timeUsed() {
Iterable<Item> usedItems = allActions.getRange(0, state['currentAction']);
return usedItems.fold(0.0, (p, c) => p + c.value!);
}
double totalComplete() {
Iterable<Item> usedItems = allActions.getRange(0, state['currentAction']);
return usedItems.length / allActions.length;
}
updateProgress() {
double repUsed = (currentAction.value - currentTime) / currentAction.value;
_progress =
totalComplete() + ((repUsed < 0 ? 0 : repUsed) / allActions.length);
notifyListeners();
}
setAction(int actionNum, [bool isManual = false]) async {
if (actionNum < allActions.length) {
Item item = allActions[actionNum];
Map newState = state;
newState['currentAction'] = actionNum;
newState['currentSet'] = item.parentId;
newState['currentRep'] = item.id;
newState['currentTime'] = _currentTime = item.value!;
await actionModel!
.updateState(json.encode(newState))
.whenComplete(() async {
// if manual select, pause next action
if (isManual) {
await pause();
await updateProgress();
}
int index = currentAction.parentId != null
? currentAction.parentId!
: currentAction.id;
if (_scrollControllers.isNotEmpty) {
for (int i = 0; i < _scrollControllers.length; i++) {
ItemScrollController sc = _scrollControllers[i];
sc.scrollTo(
index: index,
duration: Duration(milliseconds: 500),
curve: Curves.easeInOutCubic);
}
}
// _scrollController?.scrollTo(
// index: index,
// duration: Duration(milliseconds: 500),
// curve: Curves.easeInOutCubic);
});
} else {
await actionModel?.updateStatus(ActionStatus.complete).whenComplete(() {
_periodicTimer?.cancel();
notifyListeners();
});
}
notifyListeners();
}
}