513 lines
20 KiB
Dart
513 lines
20 KiB
Dart
import 'dart:collection';
|
|
import 'dart:ui';
|
|
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/dialog_trial.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/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';
|
|
|
|
// 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 || 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: 3000), () async {
|
|
if (Cache().userLoggedIn != null) {
|
|
await initDynamicLinks();
|
|
}
|
|
if (Cache().canTrial()) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return DialogTrialWidget(
|
|
title: t("10 days Premium for free"),
|
|
description: t("Would you like to try all premium functions for 10 days, without any subscription or bank card data?"),
|
|
widget: Column(children: [
|
|
Text(
|
|
t("If you click to 'Yes', all premium functions will be available right now."),
|
|
style: GoogleFonts.inter(color: Colors.white),
|
|
),
|
|
Divider(),
|
|
Text(
|
|
t("If you click to 'No', you can use all basic functions, and you will loose the oppurtunity to try the premium functions for free."),
|
|
style: GoogleFonts.inter(color: Colors.white),
|
|
),
|
|
]),
|
|
onCancel: () => {
|
|
Navigator.of(context).pop(),
|
|
menuBloc.add(MenuStartTrial(start: DateTime.parse("1900-01-01 00:00:00"))),
|
|
},
|
|
onTap: () => {Navigator.of(context).pop(), menuBloc.add(MenuStartTrial(start: DateTime.now()))},
|
|
);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
Future<void> initDynamicLinks() async {
|
|
FirebaseDynamicLinks.instance.onLink(onSuccess: (PendingDynamicLinkData? dynamicLink) async {
|
|
final Uri? deepLink = dynamicLink?.link;
|
|
print("DeepLink: $deepLink");
|
|
if (deepLink != null) {
|
|
// ignore: unawaited_futures
|
|
final String deepLinkPath = deepLink.path.replaceFirst("/", "");
|
|
Navigator.pushNamed(context, deepLinkPath);
|
|
}
|
|
}, onError: (OnLinkErrorException 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<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: 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>[
|
|
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().myTrainingPlan != null
|
|
? GestureDetector(
|
|
onTap: () => showDialog(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return DialogCommon(
|
|
title: t("You have an active Training Plan"),
|
|
descriptions: Cache().myTrainingPlan!.name != null ? Cache().myTrainingPlan!.name! : "",
|
|
description2: t("Press OK to continue"),
|
|
text: "OK",
|
|
onTap: () {
|
|
Navigator.of(context).pop();
|
|
if (Cache().myTrainingPlan != null) {
|
|
final TrainingPlanBloc bloc = BlocProvider.of<TrainingPlanBloc>(context);
|
|
bloc.setMyPlan(Cache().myTrainingPlan);
|
|
Navigator.of(context).popAndPushNamed("myTrainingPlanExecute");
|
|
}
|
|
},
|
|
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/continue.png"),
|
|
),
|
|
);
|
|
}))
|
|
: Offstage(), */
|
|
/* activeExercisePlan
|
|
? SizedBox(
|
|
width: 10,
|
|
)
|
|
: Offstage(), */
|
|
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<String, dynamic> 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") {
|
|
/* Cache().myTrainingPlan = null;
|
|
Cache().deleteMyTrainingPlan(); */
|
|
if (Cache().myTrainingPlan != null) {
|
|
final TrainingPlanBloc bloc = BlocProvider.of<TrainingPlanBloc>(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<TrainingPlanBloc>(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 {
|
|
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();
|
|
}
|
|
}
|