I'm working on a Flutter Workout App and I'm having some issues implementing the Workout Sequence and Timer.
I was able to implement the auto countdown and workout sequence however, the countdown skips numbers between and the workout sequence runs only for the first 2 sets.
For example, if an exercise has 4 sets, the cycle only runs for the first two and it's glitchy. I need help with optimized code to help me achieve the workout sequence
JSON DATA
{
"data": {
"day1": [
{
"id": 1,
"title": "Reclining Triceps Press",
"equipment": "Pull Up Bar",
"level": "Beginner",
"reps": "15",
"rest": "45 Seconds",
"sets": "4",
"image": "https://wicombit.com/demo/fitpro/images/exercise_1519941887.jpg",
"video": null,
},
{
"id": 10,
"title": "Plank with Arm Raise",
"equipment": "Pull Up Bar",
"level": "Intermediate",
"reps": "12",
"rest": "30 Seconds",
"sets": "3",
"image": "https://wicombit.com/demo/fitpro/images/exercise_1519938568.jpg",
"video": null,
},
{
"id": 3,
"title": "90-degree Static Hold",
"equipment": "Pull Up Bar",
"level": "Beginner",
"reps": "12",
"rest": "45 Seconds",
"sets": "3",
"image": "https://wicombit.com/demo/fitpro/images/exercise_1519940878.jpg",
"video": null,
},
{
"id": 5,
"title": "Single-arm Medicine Ball Pushup",
"equipment": "Kettlebells",
"level": "Elite",
"reps": "8",
"rest": "45 Seconds",
"sets": "3",
"image": "https://wicombit.com/demo/fitpro/images/exercise_1519940316.jpg",
"video": null,
"status": "draft"
}
],
}
}
I have tried the below implementation but not getting the results I want.
Dart Implementation
class WorkouStartSequenceScreen extends StatefulWidget {
final exercise;
WorkouStartSequenceScreen({super.key, required this.exercise});
@override
State<WorkouStartSequenceScreen> createState() =>
_WorkouStartSequenceScreenState();
}
class _WorkouStartSequenceScreenState extends State<WorkouStartSequenceScreen> {
late Timer _startTimer;
late Timer _restTimer;
late int totalNoOfExercisesInDay;
int currentExerciseIndex = 0;
late int totalNoOfSets;
late int totalNoOfReps;
late int totalRestTime;
int currentSetNo = 0;
int currentRepNo = 0;
int currentRestTime = 0;
bool dayWorkoutComplete = false;
int startUpCountdown = 10;
StreamController<int> _startEvents = BehaviorSubject();
StreamController<int> _restEvents = BehaviorSubject();
@override
void initState() {
totalNoOfExercisesInDay = widget.exercise.length;
totalNoOfReps = int.parse(widget.exercise[currentExerciseIndex].reps);
totalNoOfSets = int.parse(widget.exercise[currentExerciseIndex].sets);
totalRestTime = int.parse(widget.exercise[currentExerciseIndex].rest
.replaceAll(new RegExp(r'[^0-9]'), ''));
currentRestTime = totalRestTime;
_startEvents.add(startUpCountdown);
super.initState();
}
@override
void dispose() {
_startTimer.cancel();
_restTimer.cancel();
// _startEvents.close();
// _restEvents.close();
super.dispose();
}
void _showStartupDialog() async {
_startTimer = Timer.periodic(Duration(seconds: 1), (timer) {
(startUpCountdown > 0) ? startUpCountdown-- : _startTimer.cancel();
_startEvents.add(startUpCountdown);
});
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext builderContext) {
_startTimer = Timer(Duration(seconds: 10), () {
Navigator.of(context).pop();
});
return AlertDialog(
backgroundColor: AppStyles.primaryColor,
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Your workout starts in',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white, fontSize: SizeConfig.font20)),
SizedBox(height: SizeConfig.height20),
StreamBuilder<int>(
stream: _startEvents.stream,
builder:
(BuildContext context, AsyncSnapshot<int> snapshot) {
return Text('${snapshot.data.toString()}',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: SizeConfig.font50));
}),
],
),
);
}).then((val) {
if (_startTimer.isActive) {
_startTimer.cancel();
}
});
}
void startRestTimer() async {
_restTimer = Timer.periodic(Duration(seconds: 1), (timer) {
(currentRestTime > 0) ? currentRestTime-- : _restTimer.cancel();
_restEvents.add(currentRestTime);
});
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext builderContext) {
_restTimer = Timer(Duration(seconds: totalRestTime), () {
Navigator.of(context).pop();
});
return AlertDialog(
backgroundColor: AppStyles.primaryColor,
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('REST',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white, fontSize: SizeConfig.font20)),
SizedBox(height: SizeConfig.height20),
StreamBuilder<int>(
stream: _restEvents.stream,
builder:
(BuildContext context, AsyncSnapshot<int> snapshot) {
return Text('${snapshot.data.toString()}',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontSize: SizeConfig.font50));
}),
],
),
);
}).then((_) {
print("Next Set Starting");
setState(() {
currentRepNo = 0;
currentRestTime = int.parse(widget.exercise[currentExerciseIndex].rest
.replaceAll(new RegExp(r'[^0-9]'), ''));
startExercise();
});
_restTimer.cancel();
});
}
void startExercise() {
const oneSec = const Duration(seconds: 2);
new Timer.periodic(
oneSec,
(Timer timer) {
if (currentRepNo == totalNoOfReps) {
setState(() {
timer.cancel();
startRestTimer();
currentSetNo++;
if (currentSetNo == totalNoOfSets) {
currentExerciseIndex++;
}
});
} else {
setState(() {
currentRepNo++;
});
}
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blueGrey[100],
appBar: AppBar(
backgroundColor: AppStyles.primaryDark,
leading: GestureDetector(
onTap: () => Get.back(),
child: Icon(FontAwesomeIcons.xmark,
color: AppStyles.appSecondaryColor)),
),
body: Column(children: [
Image.network(widget.exercise[currentExerciseIndex].image),
SizedBox(height: SizeConfig.height20),
TitleTextWidget(
titleText: widget.exercise[currentExerciseIndex].title,
titleSize: SizeConfig.font25,
titleTextMaxLines: 3),
SizedBox(height: SizeConfig.height50),
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
Column(children: [
Row(children: [
TitleTextWidget(
titleText: ("$currentRepNo"),
titleSize: SizeConfig.font80,
),
Text("/", style: TextStyle(fontSize: SizeConfig.font80)),
DescriptionTextWidget(
descriptionText: widget.exercise[currentExerciseIndex].reps,
descriptionSize: SizeConfig.font80,
fontFamily: 'Raleway'),
]),
SizedBox(height: SizeConfig.height20),
DescriptionTextWidget(
descriptionText: 'Reps',
fontFamily: 'Raleway',
descriptionSize: SizeConfig.font30)
]),
Column(children: [
Row(children: [
TitleTextWidget(
titleText: currentSetNo.toString(),
titleSize: SizeConfig.font80,
),
Text("/", style: TextStyle(fontSize: SizeConfig.font80)),
DescriptionTextWidget(
descriptionText: widget.exercise[currentExerciseIndex].sets,
descriptionSize: SizeConfig.font80,
fontFamily: 'Raleway'),
]),
SizedBox(height: SizeConfig.height20),
DescriptionTextWidget(
descriptionText: 'Sets',
fontFamily: 'Raleway',
descriptionSize: SizeConfig.font30)
])
]),
SizedBox(height: SizeConfig.height30),
GestureDetector(
onTap: () {
_showStartupDialog();
Future.delayed(const Duration(seconds: 10), () {
startExercise();
});
},
child: Container(
height: SizeConfig.height70,
width: double.maxFinite,
margin: EdgeInsets.symmetric(
horizontal: SizeConfig.width20, vertical: SizeConfig.height5),
decoration: BoxDecoration(
color: AppStyles.primaryColor,
borderRadius: BorderRadius.circular(
SizeConfig.radius15,
)),
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: SizeConfig.width40,
vertical: SizeConfig.height10),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(FontAwesomeIcons.play, color: Colors.white),
SizedBox(width: SizeConfig.width10),
Text(
"Start",
style: TextStyle(
color: Colors.white,
fontSize: SizeConfig.font25,
fontWeight: FontWeight.w500),
),
],
),
),
),
)
]),
);
}
}