423 lines
16 KiB
Dart
423 lines
16 KiB
Dart
import 'dart:collection';
|
|
import 'dart:ui';
|
|
import 'package:aitrainer_app/bloc/tutorial/tutorial_bloc.dart';
|
|
import 'package:aitrainer_app/model/exercise_ability.dart';
|
|
import 'package:aitrainer_app/bloc/menu/menu_bloc.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:aitrainer_app/widgets/tutorial_widget.dart';
|
|
import 'package:badges/badges.dart';
|
|
import 'package:ezanimation/ezanimation.dart';
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/painting.dart';
|
|
import 'package:flutter/scheduler.dart';
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
|
|
import 'dialog_html.dart';
|
|
|
|
// ignore: must_be_immutable
|
|
class MenuPageWidget extends StatefulWidget {
|
|
int? parent;
|
|
|
|
MenuPageWidget({this.parent});
|
|
|
|
@override
|
|
_MenuPageWidgetState createState() => _MenuPageWidgetState();
|
|
}
|
|
|
|
class _MenuPageWidgetState extends State<MenuPageWidget> 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) {
|
|
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());
|
|
//runDelayedEvent();
|
|
});
|
|
|
|
super.initState();
|
|
}
|
|
|
|
Future runDelayedEvent() async {
|
|
bool isFirst = false;
|
|
await Future.delayed(Duration(milliseconds: 600), () async {
|
|
if (Cache().userLoggedIn != null) {
|
|
if (Cache().userLoggedIn!.sex == "m") {
|
|
tutorialBloc.tutorialName = ActivityDone.tutorialBasicChestPress.toStr();
|
|
} else {
|
|
tutorialBloc.tutorialName = ActivityDone.tutorialBasicLegPress.toStr();
|
|
}
|
|
}
|
|
if (!tutorialBloc.isTutorialDone()) {
|
|
if (tutorialBloc.isActive == false && tutorialBloc.canActivate) {
|
|
print("Activate tutorial");
|
|
tutorialBloc.canActivate = true;
|
|
tutorialBloc.isActive = true;
|
|
tutorialBloc.menuBloc = menuBloc;
|
|
tutorialBloc.add(TutorialLoad());
|
|
tutorialBloc.init();
|
|
isFirst = true;
|
|
}
|
|
}
|
|
});
|
|
final bool canActivate = tutorialBloc.activateTutorial();
|
|
if (canActivate) {
|
|
if (!isFirst) {
|
|
TutorialWidget().tip(context);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
bool didUpdateWidget(MenuPageWidget oldWidget) {
|
|
super.didUpdateWidget(oldWidget);
|
|
scrollController.animateTo(5, duration: Duration(milliseconds: 300), curve: Curves.easeIn);
|
|
runDelayedEvent();
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
menuBloc = BlocProvider.of<MenuBloc>(context);
|
|
tutorialBloc = BlocProvider.of<TutorialBloc>(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<Widget> buildMenuColumn(int parent, BuildContext context, MenuBloc menuBloc, double cWidth, double cHeight) {
|
|
final List<Widget> 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<Widget> _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),
|
|
//height: (cHeight / 3) - cWidth * 0.16,
|
|
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: Badge(
|
|
showBadge: workoutTree.nameEnglish == "One Rep Max" || workoutTree.nameEnglish == "Endurance",
|
|
animationDuration: Duration(milliseconds: 800),
|
|
animationType: BadgeAnimationType.fade,
|
|
badgeColor: Colors.blue[100]!,
|
|
badgeContent: GestureDetector(
|
|
onTap: () => {
|
|
showDialog(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return DialogHTML(
|
|
title: workoutTree.name,
|
|
htmlData: workoutTree.nameEnglish == "Endurance"
|
|
? AppLocalizations.of(context)!.translate("Endurance_desc")
|
|
: AppLocalizations.of(context)!.translate("OneRepMax_desc"),
|
|
);
|
|
})
|
|
},
|
|
child: Icon(Icons.info_outline_rounded)),
|
|
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),
|
|
),
|
|
),
|
|
),
|
|
])))));
|
|
});
|
|
}
|
|
|
|
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>[
|
|
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<Widget> 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).pushNamed("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;
|
|
print("Click: tutorial is active $checkText");
|
|
if (!tutorialBloc.checkAction(checkText)) {
|
|
return;
|
|
}
|
|
}
|
|
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 {
|
|
menuBloc.add(MenuClickExercise(exerciseTypeId: workoutTree.id));
|
|
|
|
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();
|
|
}
|
|
}
|