import 'dart:collection'; import 'dart:math' show pi; 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/app_localization.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/dialog_html.dart'; import 'package:aitrainer_app/widgets/menu_image.dart'; import 'package:badges/badges.dart'; import 'package:carousel_slider/carousel_slider.dart'; import 'package:extended_tabs/extended_tabs.dart'; import 'package:flutter/cupertino.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'; class TrainingPlanExecute extends StatefulWidget { const TrainingPlanExecute({Key? key}) : super(key: key); @override _TrainingPlanExecuteState createState() => _TrainingPlanExecuteState(); } class _TrainingPlanExecuteState extends State with Trans { @override Widget build(BuildContext context) { final TrainingPlanBloc bloc = BlocProvider.of(context); bloc.activateDays(); setContext(context); return Scaffold( appBar: AppBarNav( depth: 0, ), body: Container( padding: EdgeInsets.all(0), 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) { showDialog( context: context, builder: (BuildContext context) { return DialogCommon( warning: true, title: t("Warning"), descriptions: t(state.message), text: "OK", onTap: () => Navigator.of(context).pop(), onCancel: () => { Navigator.of(context).pop(), }, ); }); } else if (state is TrainingPlanDayFinished) { bloc.celebrating = false; final HashMap args = HashMap(); args["bloc"] = bloc; args["day"] = bloc.dayNames[bloc.activeDayIndex]; Navigator.of(context).pushNamed('myTrainingEvaluation', arguments: args); } else if (state is TrainingPlanDayReadyToRestart) { if (!bloc.celebrating) { showCupertinoDialog( useRootNavigator: true, context: context, builder: (_) => CupertinoAlertDialog( title: Text(t("The training is finished")), content: Column(children: [Divider(), Text(t("Do you want to restart, or select a new Training Plan?"))]), actions: [ TextButton( child: Text(t("New Training Plan"), textAlign: TextAlign.center), onPressed: () => { Navigator.pop(context), Navigator.of(context).popAndPushNamed('myTrainingPlans'), bloc.restarting = false, }), TextButton( child: Text(t("Restart")), onPressed: () { bloc.restart(); Navigator.pop(context); Navigator.of(context).popAndPushNamed('home'); }, ) ], )); } } }, builder: (context, state) { return ModalProgressHUD( child: ExerciseTabs(bloc: bloc), inAsyncCall: state is TrainingPlanLoading, opacity: 0.5, color: Colors.black54, progressIndicator: CircularProgressIndicator(), ); }), ), floatingActionButton: FloatingActionButton.extended( onPressed: () { final HashMap args = HashMap(); args["bloc"] = bloc; args["day"] = bloc.dayNames[bloc.activeDayIndex]; bloc.getNext() != null ? _ExerciseListState.executeExercise(bloc, bloc.getNext()!, context) : Navigator.of(context).pushNamed('myTrainingEvaluation', arguments: args); }, backgroundColor: Colors.orange[600], //Color(0xffb4f500), icon: Icon( CustomIcon.weight_hanging, color: Colors.black, ), label: Text( t("Next Exercise"), style: GoogleFonts.inter(color: Colors.black, fontWeight: FontWeight.bold, fontSize: 16), ), ), ); } } class ExerciseTabs extends StatefulWidget { final TrainingPlanBloc bloc; ExerciseTabs({required this.bloc}); @override _ExerciseTabs createState() => _ExerciseTabs(); } class _ExerciseTabs extends State with TickerProviderStateMixin { late TabController tabController; @override void initState() { super.initState(); tabController = TabController(length: widget.bloc.dayNames.length, vsync: this); tabController.animateTo(widget.bloc.activeDayIndex, duration: Duration(milliseconds: 300)); } @override void dispose() { tabController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return getTabs(widget.bloc); } Widget getTabs(TrainingPlanBloc bloc) { final String tabName = bloc.getMyPlan() != null && bloc.getMyPlan()!.name != null ? bloc.getMyPlan()!.name! : ""; return Column(children: [ Text( tabName, style: GoogleFonts.archivoBlack( fontSize: 14, color: Colors.white, shadows: [ Shadow( offset: Offset(2.0, 2.0), blurRadius: 6.0, color: Colors.black87, ), Shadow( offset: Offset(2.0, 2.0), blurRadius: 6.0, color: Colors.black87, ), ], ), ), ExtendedTabBar( indicator: BoxDecoration( color: Colors.black87, border: Border( bottom: BorderSide(width: 4.0, color: Color(0xffb4f500)), // top: BorderSide(width: 4.0, color: Colors.blue), )), labelPadding: EdgeInsets.only(left: 0, right: 0), tabs: getTabNames(), controller: tabController, onTap: (index) => bloc.activeDayIndex = index, ), Expanded( child: ExtendedTabBarView( children: getExerciseLists(), controller: tabController, /// if link is true and current tabbarview over scroll, /// it will check and scroll ancestor or child tabbarView. link: true, /// cache page count /// default is 0. /// if cacheExtent is 1, it has two pages in cache /// null is infinity, it will cache all pages cacheExtent: 0, )), ]); } List getTabNames() { List tabs = []; final int tabCount = widget.bloc.dayNames.length; double cWidth = MediaQuery.of(context).size.width; widget.bloc.dayNames.forEach((element) { final Widget widget = Container( //height: 40, padding: EdgeInsets.only(top: 3, left: 10, right: 10, bottom: 3), width: (cWidth / tabCount), color: Colors.white10, child: RichText( textScaleFactor: 0.8, text: TextSpan( style: GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.white, ), children: [ TextSpan( text: AppLocalizations.of(context)!.translate("Training Day") + ": \n", style: GoogleFonts.inter( fontSize: 14, color: Colors.white, 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: element, style: GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.bold, color: Color(0xffb4f500), 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, ), ], )), ]))); tabs.add(Tab(child: widget)); }); return tabs; } List getExerciseLists() { List list = []; widget.bloc.dayNames.forEach((element) { list.add(ExerciseList(bloc: widget.bloc, dayName: element)); }); return list; } } class ExerciseList extends StatefulWidget { final TrainingPlanBloc bloc; final String dayName; ExerciseList({required this.bloc, required this.dayName}); @override _ExerciseListState createState() => _ExerciseListState(); } class _ExerciseListState extends State with Trans { final scrollController = ScrollController(); double offset = 5; @override void initState() { WidgetsBinding.instance.addPostFrameCallback((_) { animate(); }); super.initState(); } @override void didUpdateWidget(ExerciseList page) { super.didUpdateWidget(page); WidgetsBinding.instance.addPostFrameCallback((_) { animate(); }); } void animate() { offset = widget.bloc.getOffset(); if (scrollController.hasClients) { scrollController.animateTo(offset, duration: Duration(milliseconds: 300), curve: Curves.easeIn); } } @override void dispose() { scrollController.dispose(); super.dispose(); } static void executeExercise(TrainingPlanBloc bloc, CustomerTrainingPlanDetails detail, BuildContext context) { CustomerTrainingPlanDetails? next = bloc.getNext(); if (next != null) { String title = ""; String description = ""; String description2 = ""; if (next.exerciseTypeId != detail.exerciseTypeId) { title = AppLocalizations.of(context)!.translate("Stop!"); description = AppLocalizations.of(context)!.translate("Please continue with the next exercise in the queue:") + next.exerciseType!.nameTranslation; } else { final HashMap args = HashMap(); args['exerciseType'] = next.exerciseType; args['customerTrainingPlanDetails'] = detail; bloc.backupDetail(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'); } } @override Widget build(BuildContext context) { setContext(context); return CustomScrollView(controller: scrollController, slivers: [ SliverList(delegate: SliverChildListDelegate(getTiles(widget.bloc))), ]); } List getTiles(TrainingPlanBloc bloc) { List tiles = []; tiles.addAll(getExerciseTiles(bloc, context)); return tiles; } List getExerciseTiles(TrainingPlanBloc bloc, BuildContext context) { List tiles = []; CustomerTrainingPlanDetails? prev; if (bloc.getMyPlan() != null && bloc.getMyPlan()!.details.isNotEmpty && bloc.getMyPlan()!.days[widget.dayName] != null && bloc.getMyPlan()!.days[widget.dayName]!.isNotEmpty) { bloc.getMyPlan()!.days[widget.dayName]!.forEach((element) { if (prev == null || (prev != null && prev!.exerciseTypeId != element.exerciseTypeId)) { tiles.add(GestureDetector( onTap: () => bloc.getNext() != null ? executeExercise(bloc, bloc.getNext()!, context) : Navigator.of(context).pushNamed('home'), child: ExerciseTile( bloc: bloc, detail: element, ))); } prev = element; }); } return tiles; } } // ignore: must_be_immutable class ExerciseTile extends StatelessWidget with Trans { final TrainingPlanBloc bloc; final CustomerTrainingPlanDetails detail; ExerciseTile({required this.bloc, required this.detail}); Widget getExerciseQuantities(CustomerTrainingPlanDetails detail, int step, bool noFilter, int highlightStep) { bool skipped = detail.state == ExercisePlanDetailState.skipped; String quantities = ""; List spans = []; if (detail.exerciseType!.name == "Warming Up") { quantities = t("Min. 10 minutes"); spans.add( TextSpan(text: quantities), ); } else if (detail.exerciseType!.name == "Stretching") { quantities = t("Recommended"); spans.add( TextSpan(text: quantities), ); } else { List details = bloc.getAllDetailsSameExercise(detail); int index = 0; bool isWeight = true; details.forEach((element) { quantities = ""; if (index > 0) { spans.add( TextSpan(text: ", "), ); } //print("STEP $step highlight: $highlightStep index: $index"); if (element.set! > 1) { spans.add( TextSpan( text: (element.exercises.length + 1).toString(), style: GoogleFonts.archivoBlack( color: highlightStep == index && noFilter ? Colors.orange[600] : Colors.white, )), ); spans.add( TextSpan(text: "/${element.set} x "), ); } if (element.repeats == -1) { quantities += " MAX "; } else { quantities += "${element.repeats}"; } if (element.exerciseType!.unitQuantityUnit != null && element.weight != null) { quantities += "x"; if (element.weight == -1 || element.weight == -2 || element.weight == -3) { quantities += "? kg"; } else { num weight = element.weight! % element.weight!.round() == 0 ? element.weight!.round() : element.weight!; quantities += "$weight kg"; } } else { isWeight = false; } if (highlightStep == index && noFilter) { spans.add( TextSpan( text: quantities, style: GoogleFonts.archivoBlack( color: Colors.orange[600], ), ), ); } else { spans.add( TextSpan(text: quantities), ); } index++; }); if (isWeight) { quantities += " kg"; } } return RichText( text: TextSpan( style: GoogleFonts.archivoBlack( fontSize: 20, color: skipped ? Colors.grey : Colors.white, shadows: [ Shadow( offset: Offset(2.0, 2.0), blurRadius: 6.0, color: Colors.black87, ), Shadow( offset: Offset(2.0, 2.0), blurRadius: 6.0, color: Colors.black87, ), ], ), children: spans), ); } @override Widget build(BuildContext context) { setContext(context); //print("Display Exercise: ${detail.exerciseTypeId}"); return CarouselSlider( options: CarouselOptions( viewportFraction: 1, reverse: false, enableInfiniteScroll: false, autoPlay: false, aspectRatio: 1.28, enlargeCenterPage: false, onPageChanged: (index, reason) { if (detail.exercises.length == 0) { int alternateIndex = index; if (alternateIndex >= bloc.alternatives[detail.customerTrainingPlanDetailsId].length) { alternateIndex = 0; } CustomerTrainingPlanDetails? alternate = bloc.alternatives[detail.customerTrainingPlanDetailsId][alternateIndex]; if (alternate != null) { bloc.add(TrainingPlanAlternateChange(detail: alternate, index: alternateIndex)); } } else { bloc.add(TrainingPlanCreateException(message: t("This exericise has already been begun"))); } }, ), items: getExerciseTiles(detail), ); } List getExerciseTiles(CustomerTrainingPlanDetails detail) { final List list = []; if (bloc.alternatives[detail.customerTrainingPlanDetailsId] != null && bloc.alternatives[detail.customerTrainingPlanDetailsId].length > 0) { int index = 0; for (CustomerTrainingPlanDetails alternative in bloc.alternatives[detail.customerTrainingPlanDetailsId]) { final Widget widget = getTile(alternative, index); list.add(widget); index++; } } return list; } Widget getTile(CustomerTrainingPlanDetails detail, int index) { final CustomerTrainingPlanDetails? next = bloc.getNext(); final bool noFilter = next != null && next.exerciseTypeId == detail.exerciseTypeId; final bool done = bloc.isAllDetailsSameExerciseFinished(detail); final bool buddyWarning = detail.exerciseType == null ? false : detail.exerciseType!.buddyWarning; final int step = bloc.getStep(detail); final int highlightStep = bloc.getHighlightStep(detail); final bool hasLeftAlternative = detail.alternatives.length > 0 && index > 0; final bool hasRightAlternative = detail.alternatives.length > 0 && index + 1 < bloc.alternatives[detail.customerTrainingPlanDetailsId].length; return Container( child: Stack(alignment: Alignment.centerRight, children: [ Stack(alignment: Alignment.centerLeft, children: [ Stack(alignment: Alignment.bottomLeft, children: [ Badge( elevation: 0, padding: EdgeInsets.all(0), position: BadgePosition.topEnd(top: 5, end: 5), animationDuration: Duration(milliseconds: 1500), animationType: BadgeAnimationType.fade, badgeColor: Colors.transparent, showBadge: noFilter || done, badgeContent: IconButton( iconSize: 36, onPressed: () => !done ? skip() : {}, icon: Icon( done ? CustomIcon.ok_circled : Icons.cancel, color: done ? Colors.green[600] : Colors.red[600], )), child: Badge( elevation: 0, padding: EdgeInsets.all(0), position: BadgePosition.topStart(top: 5, start: 5), animationDuration: Duration(milliseconds: 1000), animationType: BadgeAnimationType.scale, badgeColor: Colors.transparent, showBadge: true, badgeContent: IconButton( iconSize: 36, onPressed: () => showDialog( context: context, builder: (BuildContext context) { return DialogHTML( title: detail.exerciseType!.nameTranslation, htmlData: '

' + detail.exerciseType!.descriptionTranslation + '

'); }), icon: Icon( Icons.info_outline, color: Colors.yellow[200], )), child: Badge( elevation: 0, padding: EdgeInsets.all(0), position: BadgePosition.topEnd(top: 5, end: 60), animationDuration: Duration(milliseconds: 500), animationType: BadgeAnimationType.fade, badgeColor: Colors.transparent, showBadge: buddyWarning, badgeContent: IconButton( iconSize: 36, onPressed: () => showDialog( context: context, builder: (BuildContext context) { return DialogCommon( warning: true, text: "Warning", descriptions: t("Attention!"), description2: t("The safe and exact execution of this exercise you need a training buddy or a trainer"), description3: t("Execution at your own risk!"), onTap: () => Navigator.of(context).pop(), onCancel: () => Navigator.of(context).pop(), title: t('Training Buddy'), ); }), icon: Icon( CustomIcon.exclamation_circle, color: Colors.red[800], )), child: Column(children: [ MenuImage( imageName: bloc.getActualImageName(detail.exerciseType!.exerciseTypeId), workoutTreeId: bloc.getActualWorkoutTreeId(detail.exerciseType!.exerciseTypeId)!, radius: 0, filter: !noFilter, ), Container( padding: EdgeInsets.only(left: 20, top: 10, bottom: 0), decoration: BoxDecoration( image: DecorationImage( image: AssetImage('asset/image/WT_zold.jpg'), fit: BoxFit.cover, alignment: Alignment.center, ), ), foregroundDecoration: !noFilter ? BoxDecoration( color: Colors.black38, backgroundBlendMode: BlendMode.darken, ) : null, height: 80, width: double.infinity, child: getExerciseQuantities(detail, step, noFilter, highlightStep), ) ])))), Container( padding: EdgeInsets.only(left: 15, bottom: 80, right: 15), width: double.infinity, color: Colors.transparent, child: Text( detail.exerciseType!.nameTranslation, maxLines: 3, style: GoogleFonts.archivoBlack( color: noFilter ? Colors.white : Colors.grey, fontSize: 36, height: 1.1, shadows: [ Shadow( offset: Offset(8.0, 8.0), blurRadius: 8.0, color: Colors.black54, ), Shadow( offset: Offset(8.0, 8.0), blurRadius: 8.0, color: Colors.black54, ), ], ), ), ), ]), hasLeftAlternative ? Positioned( top: 50, child: Image.asset( 'asset/image/alternatives_arrow.png', height: 100, width: 100, color: noFilter ? Color(0xffb4f500) : Colors.grey, )) : Offstage(), ]), hasRightAlternative ? Positioned( top: 50, child: Transform.rotate( angle: pi, alignment: Alignment.center, child: Image.asset( 'asset/image/alternatives_arrow.png', height: 100, width: 100, color: noFilter ? Color(0xffb4f500) : Colors.grey, )), ) : Offstage(), ])); } void skip() { showCupertinoDialog( useRootNavigator: true, context: context, builder: (_) => CupertinoAlertDialog( title: Text(t("You want to skip really the entire exercise?")), content: Column(children: [ Divider(), ]), actions: [ TextButton( child: Text(t("No")), onPressed: () => { Navigator.pop(context), }), TextButton( child: Text(t("Yes")), onPressed: () { Navigator.pop(context); bloc.add(TrainingPlanSkipEntireExercise(detail: detail)); }, ) ], )); } }