610 lines
24 KiB
Dart
610 lines
24 KiB
Dart
import 'dart:collection';
|
|
|
|
import 'package:aitrainer_app/bloc/menu/menu_bloc.dart';
|
|
import 'package:aitrainer_app/bloc/training_plan/training_plan_bloc.dart';
|
|
import 'package:aitrainer_app/library/custom_icon_icons.dart';
|
|
import 'package:aitrainer_app/library/tree_view.dart';
|
|
import 'package:aitrainer_app/model/cache.dart';
|
|
import 'package:aitrainer_app/model/training_plan.dart';
|
|
import 'package:aitrainer_app/model/workout_menu_tree.dart';
|
|
import 'package:aitrainer_app/util/app_language.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_premium.dart';
|
|
import 'package:aitrainer_app/widgets/menu_image.dart';
|
|
import 'package:aitrainer_app/widgets/treeview_parent_widget.dart';
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:flutter_html/flutter_html.dart';
|
|
import 'package:flutter_html/style.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'package:modal_progress_hud_nsn/modal_progress_hud_nsn.dart';
|
|
import 'package:syncfusion_flutter_datagrid/datagrid.dart';
|
|
|
|
// ignore: must_be_immutable
|
|
class TrainingPlanActivatePage extends StatelessWidget with Trans {
|
|
late String parentName;
|
|
TrainingPlanActivatePage();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
setContext(context);
|
|
final HashMap<String, dynamic> args = ModalRoute.of(context)!.settings.arguments as HashMap<String, dynamic>;
|
|
parentName = args['parentName'];
|
|
|
|
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<TrainingPlanBloc, TrainingPlanState>(
|
|
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).pushNamed("login"),
|
|
onCancel: () => {
|
|
Navigator.of(context).pop(),
|
|
},
|
|
);
|
|
});
|
|
} else if (state is TrainingPlanFinished) {
|
|
Navigator.of(context).pop();
|
|
Navigator.of(context).pushNamed("myTrainingPlanExecute");
|
|
}
|
|
},
|
|
builder: (context, state) {
|
|
final TrainingPlanBloc bloc = BlocProvider.of<TrainingPlanBloc>(context);
|
|
return ModalProgressHUD(
|
|
child: getPlans(bloc),
|
|
inAsyncCall: state is TrainingPlanLoading,
|
|
opacity: 0.5,
|
|
color: Colors.black54,
|
|
progressIndicator: CircularProgressIndicator(),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget getPlans(TrainingPlanBloc bloc) {
|
|
return TreeView(
|
|
startExpanded: false,
|
|
children: _getTreeChildren(bloc),
|
|
);
|
|
}
|
|
|
|
List<Widget> _getTreeChildren(TrainingPlanBloc bloc) {
|
|
final List<TrainingPlan> plans = bloc.trainingPlanRepository.getPlansByParent(parentName);
|
|
final String parentTitle =
|
|
bloc.trainingPlanRepository.parentTree != null ? bloc.trainingPlanRepository.parentTree!.nameTranslation : "";
|
|
final String parentDescription =
|
|
bloc.trainingPlanRepository.parentTree != null && bloc.trainingPlanRepository.parentTree!.descriptionTranslation != null
|
|
? bloc.trainingPlanRepository.parentTree!.descriptionTranslation!
|
|
: "";
|
|
List<Widget> listWidget = [];
|
|
|
|
Card explanation = Card(
|
|
color: Colors.white60,
|
|
child: Container(
|
|
padding: EdgeInsets.only(left: 10, right: 5, top: 12, bottom: 8),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(
|
|
Icons.info,
|
|
color: Colors.orangeAccent,
|
|
),
|
|
Text(" "),
|
|
Flexible(
|
|
child: Text(
|
|
parentTitle,
|
|
maxLines: 2,
|
|
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
|
)),
|
|
],
|
|
),
|
|
Divider(
|
|
color: Colors.transparent,
|
|
),
|
|
Html(
|
|
data: parentDescription,
|
|
//Optional parameters:
|
|
style: {
|
|
"p": Style(
|
|
color: Colors.black,
|
|
fontSize: FontSize(12),
|
|
padding: const EdgeInsets.only(left: 20, right: 8, bottom: 4),
|
|
),
|
|
"strong": Style(
|
|
color: Colors.indigo,
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: FontSize(14),
|
|
),
|
|
"h3": Style(
|
|
color: Colors.yellow[600],
|
|
fontSize: FontSize(16),
|
|
textAlign: TextAlign.center,
|
|
padding: const EdgeInsets.all(12),
|
|
),
|
|
"li": Style(
|
|
color: Colors.white,
|
|
fontSize: FontSize(14),
|
|
padding: const EdgeInsets.only(left: 20, bottom: 10, right: 8),
|
|
//before: "*",
|
|
display: Display.LIST_ITEM),
|
|
"h2": Style(
|
|
color: Colors.yellow[600],
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: FontSize(24),
|
|
textAlign: TextAlign.center,
|
|
//padding: const EdgeInsets.all(4),
|
|
),
|
|
"h1": Style(
|
|
color: Colors.yellow[400],
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: FontSize.larger,
|
|
alignment: Alignment.center,
|
|
padding: const EdgeInsets.all(4),
|
|
),
|
|
},
|
|
), //
|
|
],
|
|
)));
|
|
listWidget.add(explanation);
|
|
|
|
plans.forEach((element) {
|
|
listWidget.add(Container(
|
|
margin: const EdgeInsets.only(left: 4.0),
|
|
child: TreeViewChild(
|
|
startExpanded: false,
|
|
parent: TreeviewParentWidget(
|
|
text: element.nameTranslations[AppLanguage().appLocal.toString()] != null
|
|
? element.nameTranslations[AppLanguage().appLocal.toString()]!
|
|
: element.name,
|
|
fontSize: 18,
|
|
icon: Icon(Icons.list_sharp),
|
|
color: Colors.blue[800],
|
|
),
|
|
children: _getChildList(element, bloc),
|
|
)));
|
|
});
|
|
|
|
return listWidget;
|
|
}
|
|
|
|
List<Widget> _getChildList(TrainingPlan plan, TrainingPlanBloc bloc) {
|
|
List<Widget> list = [];
|
|
|
|
bool restricted = (!plan.free && !Cache().hasPurchased);
|
|
|
|
list.add(Card(
|
|
margin: EdgeInsets.only(left: 10, top: 5),
|
|
color: Colors.white60,
|
|
child: Container(
|
|
padding: EdgeInsets.only(left: 10, right: 10),
|
|
child: Column(children: [
|
|
Html(
|
|
data: plan.descriptionTranslations[AppLanguage().appLocal.toString()] != null
|
|
? plan.descriptionTranslations[AppLanguage().appLocal.toString()]!
|
|
: plan.description,
|
|
//Optional parameters:
|
|
style: {
|
|
"p": Style(
|
|
color: Colors.black,
|
|
padding: const EdgeInsets.all(4),
|
|
),
|
|
"li": Style(
|
|
color: Colors.white,
|
|
//padding: const EdgeInsets.all(4),
|
|
),
|
|
"h2": Style(
|
|
color: Colors.black,
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: FontSize.larger,
|
|
|
|
//padding: const EdgeInsets.all(4),
|
|
),
|
|
"h1": Style(
|
|
color: Colors.black,
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: FontSize.larger,
|
|
alignment: Alignment.center,
|
|
padding: const EdgeInsets.all(4),
|
|
),
|
|
},
|
|
),
|
|
plan.details == null || plan.details!.isEmpty
|
|
? Container(
|
|
padding: EdgeInsets.only(bottom: 8),
|
|
child: Text(
|
|
"Soon! Check back later for the plan details",
|
|
style: GoogleFonts.inter(
|
|
color: Colors.orange[800],
|
|
shadows: <Shadow>[
|
|
Shadow(
|
|
offset: Offset(2.0, 2.0),
|
|
blurRadius: 3.0,
|
|
color: Colors.black54,
|
|
),
|
|
],
|
|
),
|
|
))
|
|
: Offstage(),
|
|
getPlanDetails(plan, bloc),
|
|
ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
onPrimary: Colors.white,
|
|
primary: restricted ? Colors.grey[600] : Colors.orange,
|
|
),
|
|
child: Text(t("Start")),
|
|
onPressed: () {
|
|
if (Cache().userLoggedIn == null) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return DialogCommon(
|
|
warning: true,
|
|
title: t("Warning"),
|
|
descriptions: t("Please log in"),
|
|
description2: t("because only that way can we generated the training plan for you."),
|
|
text: "OK",
|
|
onTap: () => Navigator.of(context).popAndPushNamed("login"),
|
|
onCancel: () => {
|
|
Navigator.of(context).pop(),
|
|
},
|
|
);
|
|
});
|
|
} else {
|
|
if (restricted) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return DialogPremium(
|
|
unlocked: Cache().hasPurchased,
|
|
unlockRound: 1,
|
|
unlockedText: t("Enjoy also this premium feature") + " " + t("to activate all available training programs."),
|
|
function: "Training Programs",
|
|
onTap: () => {Navigator.of(context).pop()},
|
|
onCancel: () => {Navigator.of(context).pop()},
|
|
);
|
|
});
|
|
} else {
|
|
activate(plan, bloc);
|
|
}
|
|
}
|
|
},
|
|
),
|
|
/* restricted
|
|
? Container(
|
|
padding: EdgeInsets.only(bottom: 8),
|
|
child: Text(
|
|
t("This is a premium function"),
|
|
style: GoogleFonts.inter(
|
|
color: Colors.deepOrange[800],
|
|
fontWeight: FontWeight.bold,
|
|
shadows: <Shadow>[
|
|
Shadow(
|
|
offset: Offset(2.0, 2.0),
|
|
blurRadius: 4.0,
|
|
color: Colors.black54,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
)
|
|
: Offstage(), */
|
|
]),
|
|
)));
|
|
|
|
return list;
|
|
}
|
|
|
|
void activate(TrainingPlan plan, TrainingPlanBloc bloc) {
|
|
if (plan.details == null || plan.details!.isEmpty) {
|
|
return;
|
|
}
|
|
if (Cache().myTrainingPlan != null) {
|
|
showCupertinoDialog(
|
|
useRootNavigator: true,
|
|
context: context,
|
|
builder: (_) => CupertinoAlertDialog(
|
|
title: Text(t("You have an active Training Plan")),
|
|
content: Column(children: [
|
|
Divider(),
|
|
Text(
|
|
t("Do you want to override it with"),
|
|
style: (TextStyle(color: Colors.blue)),
|
|
),
|
|
Text(
|
|
plan.nameTranslations[AppLanguage().appLocal.toString()]! + "?",
|
|
style: (TextStyle(color: Colors.blue[800], fontWeight: FontWeight.bold)),
|
|
),
|
|
]),
|
|
actions: [
|
|
TextButton(
|
|
child: Text(t("No")),
|
|
onPressed: () => Navigator.pop(context),
|
|
),
|
|
TextButton(
|
|
child: Text(t("Yes")),
|
|
onPressed: () {
|
|
Navigator.pop(context);
|
|
bloc.add(TrainingPlanActivate(trainingPlanId: plan.trainingPlanId));
|
|
},
|
|
)
|
|
],
|
|
));
|
|
} else {
|
|
bloc.add(TrainingPlanActivate(trainingPlanId: plan.trainingPlanId));
|
|
}
|
|
}
|
|
|
|
Widget getPlanDetails(TrainingPlan plan, TrainingPlanBloc bloc) {
|
|
return SfDataGrid(
|
|
headerRowHeight: 30,
|
|
rowHeight: 60,
|
|
source: TrainingPlanDetailSource(
|
|
plan: plan,
|
|
menuBloc: bloc.menuBloc,
|
|
onWeightTap: () => {
|
|
showDialog(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return DialogCommon(
|
|
title: t("Calculated Weight"),
|
|
descriptions: t("The weight is based on your previuos tests - if they exist."),
|
|
description2: t("If it does not exist, your very first exercise will be a test."),
|
|
text: "OK",
|
|
onTap: () => {
|
|
Navigator.of(context).pop(),
|
|
},
|
|
onCancel: () => {
|
|
Navigator.of(context).pop(),
|
|
},
|
|
);
|
|
})
|
|
},
|
|
onRepeatTap: () => {
|
|
showDialog(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return DialogCommon(
|
|
title: t("Maximum repeats"),
|
|
descriptions: t("During the exercise you should try your best:"),
|
|
description2: t("do it with the possible maximum repeats."),
|
|
text: "OK",
|
|
onTap: () => {
|
|
Navigator.of(context).pop(),
|
|
},
|
|
onCancel: () => {
|
|
Navigator.of(context).pop(),
|
|
},
|
|
);
|
|
}),
|
|
},
|
|
onDropsetTap: () => {
|
|
showDialog(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return DialogCommon(
|
|
title: t("Dropset"),
|
|
descriptions: t("A drop set is an advanced resistance training technique "),
|
|
description2:
|
|
t(" in which you focus on completing a set until failure - or the inability to do another repetition."),
|
|
text: "OK",
|
|
onTap: () => {
|
|
Navigator.of(context).pop(),
|
|
},
|
|
onCancel: () => {
|
|
Navigator.of(context).pop(),
|
|
},
|
|
);
|
|
})
|
|
}),
|
|
headerGridLinesVisibility: GridLinesVisibility.both,
|
|
gridLinesVisibility: GridLinesVisibility.both,
|
|
columns: [
|
|
GridTextColumn(
|
|
columnWidthMode: ColumnWidthMode.lastColumnFill,
|
|
maximumWidth: 160,
|
|
columnName: 'exerciseImage',
|
|
label: Container(
|
|
color: Colors.green[50],
|
|
padding: EdgeInsets.only(left: 8.0),
|
|
alignment: Alignment.centerLeft,
|
|
child: Text(
|
|
t('Exercise'),
|
|
textAlign: TextAlign.start,
|
|
overflow: TextOverflow.ellipsis,
|
|
))),
|
|
GridTextColumn(
|
|
visible: false,
|
|
columnName: 'exerciseName',
|
|
label: Container(
|
|
color: Colors.green[50],
|
|
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
|
alignment: Alignment.centerLeft,
|
|
child: Text(
|
|
t('Exercise'),
|
|
textAlign: TextAlign.start,
|
|
overflow: TextOverflow.ellipsis,
|
|
))),
|
|
GridTextColumn(
|
|
maximumWidth: 40,
|
|
columnName: 'set',
|
|
label: Container(
|
|
color: Colors.green[50],
|
|
padding: EdgeInsets.symmetric(horizontal: 2.0),
|
|
alignment: Alignment.centerLeft,
|
|
child: Text(
|
|
t('Set'),
|
|
overflow: TextOverflow.ellipsis,
|
|
))),
|
|
GridTextColumn(
|
|
maximumWidth: 50,
|
|
columnName: 'repeats',
|
|
label: Container(
|
|
color: Colors.green[50],
|
|
padding: EdgeInsets.symmetric(horizontal: 2.0),
|
|
alignment: Alignment.centerLeft,
|
|
child: Text(
|
|
t('Reps'),
|
|
overflow: TextOverflow.ellipsis,
|
|
))),
|
|
GridTextColumn(
|
|
maximumWidth: 60,
|
|
columnName: 'weight',
|
|
label: Container(
|
|
color: Colors.green[50],
|
|
padding: EdgeInsets.symmetric(horizontal: 2.0),
|
|
alignment: Alignment.centerLeft,
|
|
child: Text(
|
|
t('Weight'),
|
|
overflow: TextOverflow.ellipsis,
|
|
))),
|
|
GridTextColumn(
|
|
maximumWidth: 50,
|
|
columnName: 'day',
|
|
label: Container(
|
|
color: Colors.green[50],
|
|
padding: EdgeInsets.symmetric(horizontal: 8.0),
|
|
alignment: Alignment.centerLeft,
|
|
child: Text(
|
|
t('Day'),
|
|
overflow: TextOverflow.ellipsis,
|
|
))),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class TrainingPlanDetailSource extends DataGridSource {
|
|
final TrainingPlan plan;
|
|
final MenuBloc menuBloc;
|
|
final VoidCallback onDropsetTap;
|
|
final VoidCallback onWeightTap;
|
|
final VoidCallback onRepeatTap;
|
|
TrainingPlanDetailSource({
|
|
required this.plan,
|
|
required this.menuBloc,
|
|
required this.onDropsetTap,
|
|
required this.onWeightTap,
|
|
required this.onRepeatTap,
|
|
}) {
|
|
if (plan.details != null) {
|
|
dataGridRows = plan.details!.map((dataGridRow) {
|
|
WorkoutMenuTree? menuTree = menuBloc.menuTreeRepository.getMenuItemByExerciseTypeId(dataGridRow.exerciseTypeId);
|
|
if (menuTree == null) {
|
|
return DataGridRow(cells: []);
|
|
}
|
|
return DataGridRow(cells: [
|
|
DataGridCell<Widget>(
|
|
columnName: 'exerciseImage',
|
|
value: MenuImage(
|
|
imageName: menuTree.imageName,
|
|
workoutTreeId: menuTree.id,
|
|
radius: 8,
|
|
)),
|
|
DataGridCell<String>(columnName: 'exerciseName', value: menuTree.name),
|
|
DataGridCell<int>(columnName: 'set', value: dataGridRow.set),
|
|
DataGridCell<int>(columnName: 'reps', value: dataGridRow.repeats),
|
|
DataGridCell<double>(columnName: 'weight', value: dataGridRow.weight),
|
|
DataGridCell<String>(columnName: 'day', value: dataGridRow.day),
|
|
]);
|
|
}).toList();
|
|
}
|
|
}
|
|
|
|
List<DataGridRow> dataGridRows = [];
|
|
|
|
@override
|
|
List<DataGridRow> get rows => dataGridRows;
|
|
|
|
@override
|
|
DataGridRowAdapter? buildRow(DataGridRow row) {
|
|
if (row.getCells().isEmpty) {
|
|
return null;
|
|
}
|
|
String name = row.getCells()[1].value;
|
|
return DataGridRowAdapter(
|
|
color: Colors.white60,
|
|
cells: row.getCells().map<Widget>((dataGridCell) {
|
|
return Container(
|
|
alignment: dataGridCell.columnName == "exerciseImage" ? Alignment.centerLeft : Alignment.centerLeft,
|
|
padding: EdgeInsets.only(top: 2, bottom: 2, left: 4, right: 4),
|
|
child: dataGridCell.columnName == "exerciseImage"
|
|
? Stack(alignment: AlignmentDirectional.bottomStart, children: [
|
|
dataGridCell.value,
|
|
Container(
|
|
padding: EdgeInsets.only(left: 4, right: 8, bottom: 3),
|
|
child: Text(
|
|
name,
|
|
maxLines: 3,
|
|
overflow: TextOverflow.ellipsis,
|
|
style:
|
|
GoogleFonts.inter(fontSize: 10, color: Colors.yellow[600], fontWeight: FontWeight.bold, shadows: <Shadow>[
|
|
Shadow(
|
|
offset: Offset(2.0, 2.0),
|
|
blurRadius: 6.0,
|
|
color: Colors.black,
|
|
),
|
|
]),
|
|
))
|
|
])
|
|
: dataGridCell.columnName == "weight" && (dataGridCell.value == -1 || dataGridCell.value == -2)
|
|
? GestureDetector(
|
|
onTap: () {
|
|
onWeightTap();
|
|
},
|
|
child: Icon(
|
|
CustomIcon.question_circle,
|
|
color: Colors.indigo[300],
|
|
))
|
|
: dataGridCell.columnName == "weight" && dataGridCell.value == -3
|
|
? GestureDetector(
|
|
onTap: () {
|
|
onDropsetTap();
|
|
},
|
|
child: Icon(
|
|
CustomIcon.question_circle,
|
|
color: Colors.orange[400],
|
|
))
|
|
: dataGridCell.columnName == "reps" && dataGridCell.value == -1
|
|
? GestureDetector(
|
|
onTap: () {
|
|
onRepeatTap();
|
|
},
|
|
child: Icon(
|
|
CustomIcon.question_circle,
|
|
color: Colors.indigo[600],
|
|
))
|
|
: Text(dataGridCell.value.toString(),
|
|
style: GoogleFonts.inter(
|
|
fontSize: 14,
|
|
color: Colors.indigo,
|
|
fontWeight: FontWeight.bold,
|
|
)));
|
|
}).toList());
|
|
}
|
|
}
|