import 'dart:collection'; import 'package:aitrainer_app/bloc/training_plan/training_plan_bloc.dart'; import 'package:aitrainer_app/model/exercise_ability.dart'; import 'package:aitrainer_app/bloc/menu/menu_bloc.dart'; import 'package:aitrainer_app/repository/training_plan_repository.dart'; import 'package:aitrainer_app/util/enums.dart'; import 'package:aitrainer_app/util/track.dart'; import 'package:aitrainer_app/widgets/menu_image.dart'; import 'package:aitrainer_app/widgets/menu_search_bar.dart'; import 'package:aitrainer_app/util/app_language.dart'; import 'package:aitrainer_app/util/app_localization.dart'; import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/model/workout_menu_tree.dart'; import 'package:aitrainer_app/service/logging.dart'; import 'package:aitrainer_app/util/trans.dart'; import 'package:aitrainer_app/widgets/dialog_common.dart'; import 'package:badges/badges.dart'; import 'package:ezanimation/ezanimation.dart'; import 'package:firebase_dynamic_links/firebase_dynamic_links.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:google_fonts/google_fonts.dart'; // ignore: must_be_immutable class MenuPageWidget extends StatefulWidget { int? parent; MenuPageWidget({this.parent}); @override _MenuPageWidgetState createState() => _MenuPageWidgetState(); } class _MenuPageWidgetState extends State with Trans, Logging { final double baseWidth = 312; final double baseHeight = 675.2; late MenuBloc menuBloc; //late TutorialBloc tutorialBloc; final scrollController = ScrollController(); final bool activeExercisePlan = Cache().activeExercisePlan != null; final EzAnimation animation = EzAnimation(35.0, 10.0, Duration(seconds: 2), reverseCurve: Curves.linear); @override void initState() { if (activeExercisePlan || Cache().myTrainingPlan != null) { animation.start(); animation.addStatusListener((status) { if (status == AnimationStatus.completed || status == AnimationStatus.dismissed) { animation.reverse(); } }); animation.addListener(() {}); } /// We require the initializers to run after the loading screen is rendered SchedulerBinding.instance.addPostFrameCallback((_) { menuBloc.add(MenuCreate()); }); super.initState(); } @override bool didUpdateWidget(MenuPageWidget oldWidget) { super.didUpdateWidget(oldWidget); scrollController.animateTo(5, duration: Duration(milliseconds: 300), curve: Curves.easeIn); runDelayedEvent(); return true; } Future runDelayedEvent() async { await Future.delayed(Duration(milliseconds: 500), () async { if (Cache().userLoggedIn != null) { await initDynamicLinks(); } }); } Future initDynamicLinks() async { FirebaseDynamicLinks.instance.onLink.listen((dynamicLink) { final Uri deepLink = dynamicLink.link; print("DeepLink: $deepLink"); final String deepLinkPath = deepLink.path.replaceFirst("/", ""); Navigator.pushNamed(context, deepLinkPath); }, onError: (e) async { print('onLinkError'); print(e.message); }); final PendingDynamicLinkData? data = await FirebaseDynamicLinks.instance.getInitialLink(); final Uri? deepLink = data?.link; print("Pending DeepLink: $deepLink"); if (deepLink != null) { // ignore: unawaited_futures final String deepLinkPath = deepLink.path.replaceFirst("/", ""); Navigator.pushNamed(context, deepLinkPath); } } @override Widget build(BuildContext context) { menuBloc = BlocProvider.of(context); setContext(context); double cWidth = MediaQuery.of(context).size.width; double cHeight = MediaQuery.of(context).size.height; if (widget.parent == null) { widget.parent = 0; } return Stack(children: [ CustomScrollView( controller: scrollController, scrollDirection: Axis.vertical, slivers: buildMenuColumn(widget.parent!, context, menuBloc, cWidth, cHeight)), ]); } List buildMenuColumn(int parent, BuildContext context, MenuBloc menuBloc, double cWidth, double cHeight) { final List slivers = []; bool isChild = menuBloc.menuTreeRepository.isChildAndGym(menuBloc.parent); if (!isChild) { slivers.add(getInfoWidget(context, menuBloc)); } else { slivers.add(getFilterWidget(parent, menuBloc)); slivers.add(getFilterElements(menuBloc)); } final List _columnChildren = []; if (menuBloc.getFilteredBranch(menuBloc.parent).isEmpty) { _columnChildren.add(Container( padding: EdgeInsets.only(top: 15.0), child: Center( child: Stack(alignment: Alignment.bottomLeft, children: [ Text(AppLocalizations.of(context)!.translate("All Exercises has been filtered out"), style: GoogleFonts.inter(color: Colors.white)), ])))); } else { menuBloc.getFilteredBranch(menuBloc.parent).forEach((treeName, value) { WorkoutMenuTree workoutTree = value; _columnChildren.add(Container( padding: EdgeInsets.only(top: 15.0, left: cWidth * 0.04, right: cWidth * 0.04), child: Badge( padding: EdgeInsets.all(8), position: BadgePosition.bottomEnd(end: 0), animationDuration: Duration(milliseconds: 500), animationType: BadgeAnimationType.slide, badgeColor: Colors.orange[800]!, showBadge: workoutTree.base, badgeContent: Text(AppLocalizations.of(context)!.translate("base"), style: TextStyle( color: Colors.white, fontSize: 12, )), child: Stack(alignment: Alignment.bottomLeft, children: [ TextButton( child: badgedIcon(workoutTree, cWidth, cHeight), onPressed: () => menuClick(workoutTree, menuBloc), ), Container( padding: EdgeInsets.only(left: 15, bottom: 8, right: 15), child: GestureDetector( onTap: () => menuClick(workoutTree, menuBloc), child: Text( workoutTree.name, maxLines: 4, style: GoogleFonts.archivoBlack(color: workoutTree.color, fontSize: workoutTree.fontSize, height: 1.1), ), ), ), ])))); }); } _columnChildren.add(SizedBox( height: 50, )); SliverList sliverList = SliverList( delegate: SliverChildListDelegate(_columnChildren), ); slivers.add(sliverList); return slivers; } SliverGrid getFilterWidget(int parent, MenuBloc menuBloc) { SliverGrid sliverList = SliverGrid( delegate: SliverChildListDelegate([ Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( AppLocalizations.of(context)!.translate("Equipment Filter"), textAlign: TextAlign.center, style: GoogleFonts.archivoBlack( fontSize: 24, 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, ), ], ), ), ]) ]), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 1, mainAxisSpacing: 5.0, crossAxisSpacing: 5.0, childAspectRatio: 9, )); return sliverList; } SliverGrid getFilterElements(MenuBloc menuBloc) { List list = []; int index = 0; menuBloc.exerciseDeviceRepository.getGymDevices().forEach((element) { String deviceName = AppLanguage().appLocal == Locale('en') ? element.name : element.nameTranslation; ChoiceChip chip = ChoiceChip( labelPadding: EdgeInsets.only(right: 3), avatar: Icon( Icons.remove_circle_outline, color: Colors.orange, size: 10, ), label: Text(deviceName), labelStyle: TextStyle(fontSize: 9, color: Colors.indigo), selectedColor: Colors.white, selected: menuBloc.selectedDevice(element.exerciseDeviceId), backgroundColor: Colors.blue[100], shadowColor: Colors.black54, onSelected: (value) => menuBloc.add(MenuFilterExerciseType(deviceId: element.exerciseDeviceId)), ); list.add(chip); if (index == 4) { list.add(Divider()); } index++; }); SliverGrid sliverList = SliverGrid( delegate: SliverChildListDelegate(list), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 4, mainAxisSpacing: 1.5, crossAxisSpacing: 1.5, childAspectRatio: 2.5, )); return sliverList; } SliverAppBar getInfoWidget(BuildContext context, MenuBloc menuBloc) { menuBloc.setContext(context); SliverAppBar sliverAppBar = SliverAppBar( automaticallyImplyLeading: false, backgroundColor: Colors.transparent, title: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ !activeExercisePlan ? SizedBox( width: 50, ) : SizedBox( width: 10, ), MenuSearchBar( listItems: menuBloc.menuTreeRepository.menuAsExercise, onFind: (value) { if (Cache().userLoggedIn == null) { ScaffoldMessenger.of(context).showSnackBar(SnackBar( backgroundColor: Colors.orange, content: Text(AppLocalizations.of(context)!.translate('Please log in'), style: TextStyle(color: Colors.white)))); } else { Track().track(TrackingEvent.search, eventValue: value!.exerciseType!.name); menuBloc.ability = ExerciseAbility.oneRepMax; Navigator.of(context).pushNamed('exerciseNewPage', arguments: value.exerciseType); } }, ), Cache().activeExercisePlan != null ? GestureDetector( onTap: () => showDialog( context: context, builder: (BuildContext context) { return DialogCommon( title: t("You have an active Test Set!"), descriptions: Cache().activeExercisePlan!.name, description2: t("Press OK to continue"), text: "OK", onTap: () => { Navigator.of(context).pop(), if (Cache().activeExercisePlan != null) { Navigator.of(context).popAndPushNamed("testSetExecute"), } }, onCancel: () => { Navigator.of(context).pop(), }, ); }), child: AnimatedBuilder( animation: animation, builder: (context, snapshot) { return Center( child: Container( width: animation.value, height: animation.value, child: Image.asset("asset/image/pict_reps_volumen_db.png"), ), ); })) : Offstage(), activeExercisePlan ? SizedBox( width: 10, ) : Offstage(), ])); return sliverAppBar; } void menuClick(WorkoutMenuTree workoutTree, MenuBloc menuBloc) { /* if (tutorialBloc.isActive) { final String checkText = workoutTree.nameEnglish; if (!tutorialBloc.checkAction(checkText)) { return; } } */ print("ability: ${menuBloc.ability} tree: $workoutTree parent: ${workoutTree.parent}"); if (workoutTree.child == false) { if (menuBloc.ability != null && ExerciseAbility.mini_test_set.equalsTo(menuBloc.ability!) && workoutTree.parent != 0) { HashMap args = HashMap(); args['templateName'] = workoutTree.nameEnglish; args['templateNameTranslation'] = workoutTree.name; Navigator.of(context).pushNamed('testSetEdit', arguments: args); menuBloc.add(MenuTreeDown(item: workoutTree, parent: workoutTree.id)); } else if (menuBloc.ability != null && ExerciseAbility.training.equalsTo(menuBloc.ability!) && workoutTree.parent != 0) { HashMap args = HashMap(); args['parentName'] = workoutTree.internalName; Navigator.of(context).pushNamed("myTrainingPlanActivate", arguments: args); menuBloc.add(MenuTreeDown(item: workoutTree, parent: workoutTree.id)); } else if (workoutTree.internalName == "training_execute") { if (Cache().myTrainingPlan != null) { final TrainingPlanBloc bloc = BlocProvider.of(context); bloc.setMyPlan(Cache().myTrainingPlan); Navigator.of(context).pushNamed("myTrainingPlanExecute"); } else { showDialog( context: context, builder: (BuildContext context) { return DialogCommon( title: t("No selected Training Plan"), descriptions: t("Based on your initial data, we will generate the personalized training plan for you."), text: "OK", onTap: () { TrainingPlanRepository trainingPlanRepository = TrainingPlanRepository(); trainingPlanRepository.generateTrainingPlan(); final TrainingPlanBloc bloc = BlocProvider.of(context); bloc.setMyPlan(Cache().myTrainingPlan); Future.delayed(Duration(milliseconds: 1000), () async { Navigator.of(context).pushNamed("myTrainingPlanExecute"); }); }, onCancel: () => { Navigator.of(context).pop(), }, ); }); } } else { menuBloc.add(MenuTreeDown(item: workoutTree, parent: workoutTree.id)); } } else { if (workoutTree.exerciseType!.name == "Custom" && Cache().userLoggedIn!.admin == 1) { Navigator.of(context).pushNamed('exerciseCustomPage', arguments: workoutTree.exerciseType); } else { Navigator.of(context).pushNamed('exerciseNewPage', arguments: workoutTree.exerciseType); } } } dynamic getShape(WorkoutMenuTree workoutTree) { bool base = workoutTree.base; dynamic returnCode = (base == true) ? RoundedRectangleBorder( side: BorderSide(width: 6, color: Colors.orangeAccent), borderRadius: BorderRadius.all(Radius.circular(24.0))) : RoundedRectangleBorder( side: BorderSide(width: 1, color: Colors.transparent), borderRadius: BorderRadius.all(Radius.circular(8.0))); return returnCode; } Widget badgedIcon(WorkoutMenuTree workoutMenuTree, double cWidth, double cHeight) { String badgeKey = workoutMenuTree.nameEnglish; bool show = Cache().getBadges()[badgeKey] != null; int counter = Cache().getBadges()[badgeKey] != null ? Cache().getBadges()[badgeKey] : 0; Widget buttonImage = MenuImage(imageName: workoutMenuTree.imageName, workoutTreeId: workoutMenuTree.id); return Badge( padding: EdgeInsets.all(8), position: BadgePosition.topEnd(top: 3, end: 3), animationDuration: Duration(milliseconds: 500), animationType: BadgeAnimationType.slide, badgeColor: Colors.red, showBadge: show, badgeContent: Text(counter.toString(), style: TextStyle( color: Colors.white, fontSize: 16, )), child: buttonImage, ); } @override void dispose() { scrollController.dispose(); super.dispose(); } }