import 'dart:collection'; import 'package:aitrainer_app/bloc/training_plan/training_plan_bloc.dart'; import 'package:aitrainer_app/library/custom_icon_icons.dart'; import 'package:aitrainer_app/model/customer_training_plan_details.dart'; import 'package:aitrainer_app/model/exercise_plan_detail.dart'; import 'package:aitrainer_app/util/trans.dart'; import 'package:aitrainer_app/widgets/app_bar.dart'; import 'package:aitrainer_app/widgets/dialog_common.dart'; import 'package:aitrainer_app/widgets/menu_image.dart'; import 'package:ezanimation/ezanimation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:modal_progress_hud_nsn/modal_progress_hud_nsn.dart'; import 'package:timeline_tile/timeline_tile.dart'; // ignore: must_be_immutable class TrainingPlanExecutePage extends StatelessWidget with Trans { @override Widget build(BuildContext context) { final TrainingPlanBloc bloc = BlocProvider.of(context); setContext(context); return Scaffold( appBar: AppBarNav(depth: 0), body: Container( padding: EdgeInsets.all(20), decoration: BoxDecoration( image: DecorationImage( image: AssetImage('asset/image/WT_black_background.jpg'), fit: BoxFit.cover, alignment: Alignment.center, ), ), child: BlocConsumer(listener: (context, state) { if (state is TrainingPlanError) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(backgroundColor: Colors.orange, content: Text(state.message, style: TextStyle(color: Colors.white)))); } else if (state is TrainingPlanFinished) {} }, builder: (context, state) { return ModalProgressHUD( child: getExercises(bloc), inAsyncCall: state is TrainingPlanLoading, opacity: 0.5, color: Colors.black54, progressIndicator: CircularProgressIndicator(), ); }), ), floatingActionButton: FloatingActionButton.extended( onPressed: () => bloc.getNext() != null ? executeExercise(bloc, bloc.getNext()!) : Navigator.of(context).pushNamed('home'), backgroundColor: Colors.orange[800], icon: Icon(CustomIcon.weight_hanging), label: Text( t("Training!"), style: GoogleFonts.inter(fontWeight: FontWeight.bold, fontSize: 16), ), ), ); } Widget getExercises(TrainingPlanBloc bloc) { return CustomScrollView(slivers: [ SliverList(delegate: SliverChildListDelegate(getTiles(bloc))), ]); } List getTiles(TrainingPlanBloc bloc) { List tiles = []; tiles.add(getStartTile(bloc)); tiles.addAll(getExerciseTiles(bloc, context)); if (bloc.myPlan != null) tiles.add(getEndTile()); return tiles; } Widget getStartTile(TrainingPlanBloc bloc) { String startText = ""; String explainingText = ""; if (null == bloc.getMyPlan()) { startText = "No Active Training Plan"; explainingText = "Please select one in the Training menu, or create your custom plan"; } else { startText = bloc.isStarted() ? "Continue your training" : "Start your training"; explainingText = bloc.getMyPlan()!.name != null ? bloc.getMyPlan()!.name! : ""; } return TimelineTile( alignment: TimelineAlign.manual, lineXY: 0.1, isFirst: true, afterLineStyle: const LineStyle( color: Colors.orange, thickness: 6, ), indicatorStyle: IndicatorStyle( width: 40, color: Colors.orange, padding: const EdgeInsets.all(8), iconStyle: IconStyle( color: Colors.white, iconData: Icons.emoji_flags_rounded, ), ), endChild: Container( padding: EdgeInsets.only(top: 30), constraints: const BoxConstraints( minHeight: 120, ), color: Colors.transparent, child: RichText( text: TextSpan( style: GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.white, ), children: [ TextSpan( text: startText, style: GoogleFonts.inter( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.yellow[400], shadows: [ Shadow( offset: Offset(5.0, 5.0), blurRadius: 12.0, color: Colors.black54, ), Shadow( offset: Offset(-3.0, 3.0), blurRadius: 12.0, color: Colors.black54, ), ], )), TextSpan( text: "\n", style: GoogleFonts.inter( fontSize: 16, color: Colors.white, )), TextSpan( text: explainingText, style: GoogleFonts.inter( fontSize: 16, color: Colors.white, )), ])), ), ); } Widget getEndTile() { return Container( color: Colors.transparent, child: TimelineTile( alignment: TimelineAlign.manual, lineXY: 0.1, isLast: true, beforeLineStyle: const LineStyle( color: Colors.orange, thickness: 6, ), indicatorStyle: IndicatorStyle( width: 40, color: Colors.orange, padding: const EdgeInsets.all(8), iconStyle: IconStyle( color: Colors.white, iconData: Icons.thumb_up, ), ), endChild: Container( padding: EdgeInsets.only(top: 50), constraints: const BoxConstraints( minHeight: 120, ), color: Colors.transparent, child: RichText( text: TextSpan( style: GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.white, ), children: [ TextSpan( text: "Finish!", style: GoogleFonts.inter( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.yellow[400], shadows: [ Shadow( offset: Offset(5.0, 5.0), blurRadius: 12.0, color: Colors.black54, ), Shadow( offset: Offset(-3.0, 3.0), blurRadius: 12.0, color: Colors.black54, ), ], )), ])), ), ), ); } List getExerciseTiles(TrainingPlanBloc bloc, BuildContext context) { List tiles = []; if (bloc.myPlan != null && bloc.myPlan!.details.isNotEmpty) { bloc.myPlan!.details.forEach((element) { tiles.add(GestureDetector( onTap: () => {}, child: ExerciseTile( bloc: bloc, detail: element, ))); }); } return tiles; } void executeExercise(TrainingPlanBloc bloc, CustomerTrainingPlanDetails detail) { CustomerTrainingPlanDetails? next = bloc.getNext(); if (next != null) { String title = ""; String description = ""; String description2 = ""; if (next.exerciseTypeId != detail.exerciseTypeId) { title = t("Stop!"); description = t("Please continue with the next exercise in the queue:") + next.exerciseType!.nameTranslation; description2 = t("Or, you can redifine this exercise queue in the Compact Test menu"); } else { final HashMap args = HashMap(); args['exerciseType'] = next.exerciseType; args['customerTrainingPlanDetails'] = detail; Navigator.of(context).pushNamed('myTrainingPlanExercise', arguments: args); return; } showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return DialogCommon( title: title, descriptions: description, description2: description2, text: "OK", onTap: () => {Navigator.of(context).pop()}, onCancel: () => {Navigator.of(context).pop()}, ); }); } else { Navigator.of(context).pushNamed('home'); } } } // ignore: must_be_immutable class ExerciseTile extends StatefulWidget with Trans { final TrainingPlanBloc bloc; final CustomerTrainingPlanDetails detail; ExerciseTile({required this.bloc, required this.detail}); @override _ExerciseTileState createState() => _ExerciseTileState(); } class _ExerciseTileState extends State with Trans { final EzAnimation animation = EzAnimation(1.0, 30.0, Duration(seconds: 3), reverseCurve: Curves.easeIn); @override void initState() { animation.start(); animation.addStatusListener((status) { if (status == AnimationStatus.completed) {} }); super.initState(); } @override bool didUpdateWidget(ExerciseTile oldWidget) { super.didUpdateWidget(oldWidget); Future.delayed(Duration(milliseconds: 400)).then((value) => animation.start()); return true; } Widget getIndicator(ExercisePlanDetailState state) { CustomerTrainingPlanDetails? next = widget.bloc.getNext(); bool actual = false; if (next != null) { if (next.exerciseTypeId == widget.detail.exerciseTypeId) { actual = true; } } if (state.equalsTo(ExercisePlanDetailState.inProgress)) { return ClipRRect( borderRadius: BorderRadius.circular(24.0), child: Container( color: actual ? Colors.green : Colors.orange, child: Icon( CustomIcon.calendar_2, size: 28, color: Colors.white, ))); } else if (state.equalsTo(ExercisePlanDetailState.finished)) { return ClipRRect( borderRadius: BorderRadius.circular(24.0), child: Container( color: Colors.white, child: Icon( CustomIcon.ok_circled, size: 40, color: Colors.green, ))); } else { return Image.asset( "asset/image/pict_reps_volumen_db.png", ); } } @override Widget build(BuildContext context) { setContext(context); final ExercisePlanDetailState state = widget.detail.state; final bool done = state.equalsTo(ExercisePlanDetailState.finished); final String countSerie = widget.detail.set.toString(); final String step = (widget.detail.exercises.length).toString(); String weight = widget.detail.weight!.toStringAsFixed(1); String restingTime = widget.detail.restingTime == null ? "" : widget.detail.restingTime!.toStringAsFixed(0); bool isTest = false; if (widget.detail.weight! == -1) { weight = t("TEST"); isTest = true; } setContext(context); return Container( color: Colors.transparent, child: TimelineTile( alignment: TimelineAlign.manual, lineXY: 0.1, beforeLineStyle: const LineStyle( color: Color(0xffb4f500), thickness: 6, ), afterLineStyle: const LineStyle( color: Color(0xffb4f500), thickness: 6, ), indicatorStyle: IndicatorStyle( width: 40, height: 40, indicator: getIndicator(state), ), endChild: Container( padding: EdgeInsets.only(left: 10), child: Row(children: [ Container( width: 120, height: 80, child: MenuImage( imageName: widget.bloc.getActualImageName(widget.detail.exerciseType!.exerciseTypeId), workoutTreeId: widget.bloc.getActualWorkoutTreeId(widget.detail.exerciseType!.exerciseTypeId)!, )), SizedBox( width: 10, ), Expanded( child: RichText( text: TextSpan( style: GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.bold, color: done ? Colors.grey[400] : Colors.white, ), children: [ TextSpan( text: widget.detail.exerciseType!.nameTranslation, style: GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.bold, color: done ? Colors.grey[400] : Colors.orange[500], shadows: [ Shadow( offset: Offset(5.0, 5.0), blurRadius: 12.0, color: Colors.black54, ), Shadow( offset: Offset(-3.0, 3.0), blurRadius: 12.0, color: Colors.black54, ), ], )), widget.detail.exerciseType!.unitQuantityUnit != null ? TextSpan( text: "\n", ) : TextSpan(), widget.detail.exerciseType!.unitQuantityUnit != null ? TextSpan( text: t(widget.detail.exerciseType!.unitQuantityUnit!) + ": ", style: GoogleFonts.inter( fontSize: 12, color: done ? Colors.grey[100] : Colors.yellow[400], fontWeight: FontWeight.bold)) : TextSpan(), widget.detail.exerciseType!.unitQuantityUnit != null ? TextSpan( text: weight, style: GoogleFonts.inter( fontSize: 12, )) : TextSpan(), TextSpan( text: "\n", ), TextSpan( text: t(widget.detail.exerciseType!.unit) + ": ", style: GoogleFonts.inter( fontSize: 12, color: done ? Colors.grey[100] : Colors.yellow[400], fontWeight: FontWeight.bold)), TextSpan( text: widget.detail.repeats!.toString(), style: GoogleFonts.inter( fontSize: 12, )), TextSpan( text: "\n", ), TextSpan( text: t("Set") + ": ", style: GoogleFonts.inter( fontSize: 12, color: done ? Colors.grey[100] : Colors.yellow[400], fontWeight: FontWeight.bold)), TextSpan( text: step + "/" + countSerie, style: GoogleFonts.inter( fontSize: 12, )), TextSpan( text: "\n", ), TextSpan( text: t("Resting time") + ": ", style: GoogleFonts.inter( fontSize: 12, color: done ? Colors.grey[100] : Colors.yellow[400], fontWeight: FontWeight.bold)), TextSpan( text: restingTime + " " + t("min(s)"), style: GoogleFonts.inter(fontSize: 12, color: done ? Colors.grey[100] : Colors.white, fontWeight: FontWeight.bold)), ]), )), done ? AnimatedBuilder( animation: animation, builder: (context, snapshot) { return Column(mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( "asset/image/kupa.png", width: animation.value, ), Text("Result", style: GoogleFonts.inter(fontSize: 10, color: Colors.white)), ]); }) : Offstage(), isTest ? AnimatedBuilder( animation: animation, builder: (context, snapshot) { return Column(mainAxisAlignment: MainAxisAlignment.center, children: [ GestureDetector( onTap: () => showDialog( context: context, builder: (BuildContext context) { return DialogCommon( warning: false, title: t("Why Test?"), descriptions: t("This is your first exercise after at least 3 weeks."), description2: t("The first exercise will be a test. The following sets will be recalculated base on your test."), description3: t("This is the most optimal way for your development"), text: "OK", onTap: () => Navigator.of(context).pop(), onCancel: () => { Navigator.of(context).pop(), }, ); }), child: Icon( CustomIcon.question_circle, color: Colors.yellowAccent[700], size: 16, )), ]); }) : Offstage() ]), ), ), ); } }