WT1.1.5 In-app-purchase

This commit is contained in:
bossanyit
2021-01-24 14:34:13 +01:00
parent d44fefe9af
commit 71efd3f349
52 changed files with 847 additions and 367 deletions
-1
View File
@@ -5,7 +5,6 @@ import 'package:aitrainer_app/model/customer.dart';
import 'package:aitrainer_app/util/common.dart';
import 'package:aitrainer_app/util/trans.dart';
import 'package:aitrainer_app/widgets/app_bar_min.dart';
import 'package:badges/badges.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:aitrainer_app/widgets/bottom_nav.dart';
import 'package:flutter/material.dart';
+34 -13
View File
@@ -4,7 +4,9 @@ import 'dart:ui';
import 'package:aitrainer_app/bloc/result/result_bloc.dart';
import 'package:aitrainer_app/localization/app_language.dart';
import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/model/exercise.dart';
import 'package:aitrainer_app/model/exercise_ability.dart';
import 'package:aitrainer_app/model/exercise_type.dart';
import 'package:aitrainer_app/repository/exercise_repository.dart';
import 'package:aitrainer_app/repository/exercise_result_repository.dart';
import 'package:aitrainer_app/util/trans.dart';
@@ -15,6 +17,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_bloc/flutter_form_bloc.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:aitrainer_app/model/result.dart';
import 'package:modal_progress_hud/modal_progress_hud.dart';
// ignore: must_be_immutable
class EvaluationPage extends StatelessWidget with Trans {
@@ -22,7 +25,7 @@ class EvaluationPage extends StatelessWidget with Trans {
Widget build(BuildContext context) {
LinkedHashMap arguments = ModalRoute.of(context).settings.arguments;
ExerciseRepository exerciseRepository;
// ignore: close_sinks
if (arguments != null) {
exerciseRepository = arguments['exerciseRepository'];
} else {
@@ -37,10 +40,22 @@ class EvaluationPage extends StatelessWidget with Trans {
resultType = ResultType.man;
imageUrl = 'asset/image/WT_Results_for_female.png';
}
if (arguments['past'] != null && arguments['past'] == true) {
Exercise exercise = arguments['exercise'];
if (exercise != null) {
ExerciseType exerciseType = exerciseRepository.getExerciseTypeById(exercise.exerciseTypeId);
exerciseRepository.exerciseType = exerciseType;
exerciseRepository.exercise = exercise;
String exerciseDate = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd);
exerciseRepository.getSameExercise(exercise.exerciseTypeId, exerciseDate);
}
}
if (exerciseRepository.exerciseType.getAbility().equalsTo(ExerciseAbility.running)) {
resultType = ResultType.running;
imageUrl = 'asset/image/WT_Results_for_runners.png';
}
setContext(context);
return Scaffold(
appBar: AppBarMin(
@@ -61,7 +76,7 @@ class EvaluationPage extends StatelessWidget with Trans {
create: (context) => ResultBloc(
resultRepository: ExerciseResultRepository(resultType: resultType),
exerciseRepository: exerciseRepository,
context: context), //..add(ResultLoad())
context: context),
child: BlocConsumer<ResultBloc, ResultState>(listener: (context, state) {
if (state is ResultError) {
Scaffold.of(context).showSnackBar(
@@ -74,7 +89,13 @@ class EvaluationPage extends StatelessWidget with Trans {
}
}, builder: (context, state) {
final resultBloc = BlocProvider.of<ResultBloc>(context);
return getEvaluationWidgets(resultBloc);
return ModalProgressHUD(
child: getEvaluationWidgets(resultBloc),
inAsyncCall: state is ResultLoading,
opacity: 0.5,
color: Colors.black54,
progressIndicator: CircularProgressIndicator(),
);
}))),
bottomNavigationBar: BottomNavigator(bottomNavIndex: 0));
}
@@ -89,13 +110,15 @@ class EvaluationPage extends StatelessWidget with Trans {
SliverAppBar(
pinned: true,
backgroundColor: Colors.transparent,
expandedHeight: 50.0,
expandedHeight: 120.0,
collapsedHeight: 80,
toolbarHeight: 30,
automaticallyImplyLeading: false,
flexibleSpace: FlexibleSpaceBar(
title: Text(exerciseName,
textAlign: TextAlign.center,
maxLines: 3,
softWrap: true,
//softWrap: true,
style: GoogleFonts.archivoBlack(
fontSize: 24,
color: Colors.white,
@@ -382,14 +405,12 @@ class EvaluationPage extends StatelessWidget with Trans {
Widget getSummary(ResultBloc bloc) {
int index = 0;
List<Text> resultList = List();
/* for (int i = 0; i < bloc.exerciseRepository.actualExerciseList.length; i++) {
print("Q " +
bloc.exerciseRepository.actualExerciseList[i].quantity.toString() +
" Qu: " +
bloc.exerciseRepository.actualExerciseList[i].unitQuantity.toString());
} */
bloc.exerciseRepository.actualExerciseList.forEach((actual) {
final String unit = t(bloc.exerciseRepository.exerciseType.unit);
//final String unit = t(bloc.exerciseRepository.exerciseType.unit);
final String unit = bloc.exerciseRepository.exerciseType.unitQuantityUnit != null
? bloc.exerciseRepository.exerciseType.unitQuantityUnit
: bloc.exerciseRepository.exerciseType.unit;
String exerciseElement = "";
String exerciseRepeats = actual.quantity.toStringAsFixed(0);
if (bloc.exerciseRepository.exerciseType.unit == "second") {
@@ -407,7 +428,7 @@ class EvaluationPage extends StatelessWidget with Trans {
}
index++;
resultList.add(
Text(exerciseElement + exerciseRepeats + exerciseUnitQuantity + " " + unit,
Text(exerciseElement + exerciseRepeats + exerciseUnitQuantity + " " + t(unit),
textAlign: TextAlign.center,
maxLines: 2,
softWrap: true,
+23 -15
View File
@@ -200,7 +200,7 @@ class _ExerciseLogPage extends State<ExerciseLogPage> with Trans, Common {
iconSize: 36,
icon: Icon(CustomIcon.heart_1, color: Colors.blue[800]),
onPressed: () {
evaluation();
evaluation(exerciseRepository, exercise);
},
),
Cache().hasPurchased
@@ -212,7 +212,7 @@ class _ExerciseLogPage extends State<ExerciseLogPage> with Trans, Common {
width: 25,
),
onTap: () {
evaluation();
evaluation(exerciseRepository, exercise);
}),
],
),
@@ -230,19 +230,27 @@ class _ExerciseLogPage extends State<ExerciseLogPage> with Trans, Common {
return list;
}
void evaluation() {
showDialog(
context: context,
builder: (BuildContext context) {
return DialogPremium(
unlocked: true,
unlockRound: 1,
unlockedText: t("Enjoy also this premium fetaure to show all old evaluation data of your successful exercises."),
function: "My Exercise Logs",
onTap: () => {Navigator.of(context).pop()},
onCancel: () => {Navigator.of(context).pop()},
);
});
void evaluation(ExerciseRepository exerciseRepository, Exercise exercise) {
if (!Cache().hasPurchased) {
showDialog(
context: context,
builder: (BuildContext context) {
return DialogPremium(
unlocked: Cache().hasPurchased,
unlockRound: 1,
unlockedText: t("Enjoy also this premium fetaure to show all old evaluation data of your successful exercises."),
function: "My Exercise Logs",
onTap: () => {Navigator.of(context).pop()},
onCancel: () => {Navigator.of(context).pop()},
);
});
} else {
LinkedHashMap args = LinkedHashMap();
args['exerciseRepository'] = exerciseRepository;
args['exercise'] = exercise;
args['past'] = true;
Navigator.of(context).pushNamed('evaluationPage', arguments: args);
}
}
void confirmationDialog(ExerciseLogBloc bloc, Exercise exercise) {
+8 -4
View File
@@ -22,6 +22,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_form_bloc/flutter_form_bloc.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:modal_progress_hud/modal_progress_hud.dart';
import 'package:stop_watch_timer/stop_watch_timer.dart';
import 'package:wakelock/wakelock.dart';
@@ -101,11 +102,14 @@ class _ExerciseNewPageState extends State<ExerciseNewPage> with Trans, Logging {
}
},
builder: (context, state) {
if (state is ExerciseNewReady) {
//LoadingDialog.hide(context);
}
final exerciseBloc = BlocProvider.of<ExerciseNewBloc>(context);
return getExerciseWidget(exerciseBloc, exerciseType, menuBloc);
return ModalProgressHUD(
child: getExerciseWidget(exerciseBloc, exerciseType, menuBloc),
inAsyncCall: state is ExerciseNewLoading,
opacity: 0.5,
color: Colors.black54,
progressIndicator: CircularProgressIndicator(),
);
},
));
}
+3 -3
View File
@@ -28,7 +28,7 @@ class _MyDevelopmentBodyPage extends State<MyDevelopmentBodyPage> with Trans, Co
void initState() {
super.initState();
Flurry.logEvent("myDevelopmentBody");
if (!Cache().hasPurchased) {
if (!Cache().hasPurchased || true) {
Timer(
Duration(milliseconds: 2000),
() => {
@@ -38,10 +38,10 @@ class _MyDevelopmentBodyPage extends State<MyDevelopmentBodyPage> with Trans, Co
builder: (BuildContext context) {
setContext(context);
return DialogPremium(
unlocked: false,
unlocked: Cache().hasPurchased,
unlockRound: 2,
function: "My Whole Body Development",
unlockedText: "",
unlockedText: null,
onTap: () => {Navigator.of(context).pop(), Navigator.of(context).pop()},
onCancel: () => {Navigator.of(context).pop(), Navigator.of(context).pop()},
);
+19 -8
View File
@@ -217,27 +217,37 @@ class _MyDevelopmentMuscleState extends State<MyDevelopmentMusclePage> with Comm
)));
exerciseTypes.add(explanation);
LinkedHashMap<String, dynamic> rc = LinkedHashMap();
tree.forEach((name, list) {
rc = _getChildList(list, bloc);
final List<Widget> children = rc['list'];
final bool hasNoData = rc['hasNoData'];
exerciseTypes.add(Container(
margin: const EdgeInsets.only(left: 4.0),
child: TreeViewChild(
startExpanded: false,
parent: _getExerciseWidget(exerciseTypeName: name),
children: _getChildList(list, bloc),
parent: _getExerciseWidget(exerciseTypeName: name, noData: hasNoData),
children: children,
)));
});
return exerciseTypes;
}
Widget _getExerciseWidget({@required String exerciseTypeName, List<WorkoutMenuTree> list}) {
return TreeviewParentWidget(text: exerciseTypeName);
Widget _getExerciseWidget({@required String exerciseTypeName, bool noData = false}) {
return TreeviewParentWidget(
text: exerciseTypeName,
backgroundColor: !noData ? Colors.white38 : Colors.white12,
color: !noData ? Colors.blue[800] : Colors.grey[400]);
}
List<Widget> _getChildList(List<WorkoutMenuTree> listWorkoutTree, DevelopmentByMuscleBloc bloc) {
LinkedHashMap<String, dynamic> _getChildList(List<WorkoutMenuTree> listWorkoutTree, DevelopmentByMuscleBloc bloc) {
LinkedHashMap<String, dynamic> rc = LinkedHashMap();
List<Widget> list = List();
bool hasSummaryNoData = true;
listWorkoutTree.forEach((element) {
bool hasNoData = (bloc.listChartData[element.exerciseTypeId] == null);
final bool hasNoData = (bloc.listChartData[element.exerciseTypeId] == null);
hasSummaryNoData = hasSummaryNoData && hasNoData;
String unit = " kg";
if (bloc.diagramType == DiagramType.percent) {
unit = " %";
@@ -320,7 +330,8 @@ class _MyDevelopmentMuscleState extends State<MyDevelopmentMusclePage> with Comm
),
));
});
return list;
rc['list'] = list;
rc['hasNoData'] = hasSummaryNoData;
return rc;
}
}
+3 -3
View File
@@ -106,10 +106,10 @@ class _MyDevelopmentPage extends State<MyDevelopmentPage> with Trans {
context: context,
builder: (BuildContext context) {
return DialogPremium(
unlocked: false,
unlockRound: 2,
unlocked: Cache().hasPurchased,
unlockRound: 3,
function: "Predictions",
unlockedText: "",
unlockedText: null,
onTap: () => {Navigator.of(context).pop()},
);
})
+10 -10
View File
@@ -97,10 +97,10 @@ class _MyExercisePlanPage extends State<MyExercisePlanPage> with Trans, Logging
context: context,
builder: (BuildContext context) {
return DialogPremium(
unlocked: false,
unlocked: Cache().hasPurchased,
unlockRound: 1,
function: "Suggested Training Plan",
unlockedText: "",
unlockedText: null,
onTap: () => {Navigator.of(context).pop()},
onCancel: () => {Navigator.of(context).pop()},
);
@@ -111,25 +111,25 @@ class _MyExercisePlanPage extends State<MyExercisePlanPage> with Trans, Logging
ImageButton(
width: imageWidth,
textAlignment: Alignment.topLeft,
text: t("My Special Plan"),
text: t("Training Programs"),
style: GoogleFonts.robotoMono(
textStyle: TextStyle(
fontSize: 14,
color: Colors.white,
fontWeight: FontWeight.bold,
backgroundColor: Colors.black54.withOpacity(0.4))),
image: "asset/image/exercise_plan_special.jpg",
image: "asset/image/exercise_plan_stars.jpg",
left: 5,
onTap: () => {
Flurry.logEvent("SpecialTrainingPlan"),
Flurry.logEvent("SpecialTraining Programs"),
showDialog(
context: context,
builder: (BuildContext context) {
return DialogPremium(
unlocked: false,
unlocked: Cache().hasPurchased,
unlockRound: 1,
function: "My Special Plan",
unlockedText: "",
function: "Training Programs",
unlockedText: null,
onTap: () => {Navigator.of(context).pop()},
onCancel: () => {Navigator.of(context).pop()},
);
@@ -137,7 +137,7 @@ class _MyExercisePlanPage extends State<MyExercisePlanPage> with Trans, Logging
},
isLocked: true,
),
ImageButton(
/* ImageButton(
width: imageWidth,
textAlignment: Alignment.topLeft,
text: t("Star's Exercise Plan"),
@@ -165,7 +165,7 @@ class _MyExercisePlanPage extends State<MyExercisePlanPage> with Trans, Logging
})
},
isLocked: true,
),
), */
hiddenPlanWidget(exerciseRepository),
hiddenTrainingWidget(),
]),
+32 -17
View File
@@ -2,6 +2,7 @@ import 'package:aitrainer_app/bloc/sales/sales_bloc.dart';
import 'package:aitrainer_app/service/logging.dart';
import 'package:aitrainer_app/util/trans.dart';
import 'package:aitrainer_app/widgets/app_bar_min.dart';
import 'package:aitrainer_app/widgets/dialog_common.dart';
import 'package:aitrainer_app/widgets/sales_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -23,7 +24,25 @@ class SalesPage extends StatelessWidget with Trans, Logging {
if (state is SalesError) {
log("Error: " + state.message);
Scaffold.of(context).showSnackBar(
SnackBar(backgroundColor: Colors.orange, content: Text(state.message, style: TextStyle(color: Colors.white))));
SnackBar(backgroundColor: Colors.orange, content: Text(t(state.message), style: TextStyle(color: Colors.white))));
} else if (state is SalesSuccessful) {
showDialog(
context: context,
builder: (BuildContext context) {
return DialogCommon(
title: t("Successful Purchase"),
descriptions: t("Now you can use the premium features of WorkoutTest!"),
text: "OK",
onTap: () => {
Navigator.of(context).pop(),
Navigator.of(context).pushNamed("home"),
},
onCancel: () => {
Navigator.of(context).pop(),
Navigator.of(context).pushNamed("home"),
},
);
});
}
}, builder: (context, state) {
final salesBloc = BlocProvider.of<SalesBloc>(context);
@@ -38,8 +57,6 @@ class SalesPage extends StatelessWidget with Trans, Logging {
}
Widget salesWidget(SalesBloc bloc) {
final double mediaWidth = MediaQuery.of(context).size.width;
final double imageWidth = (mediaWidth - 5) / 2;
return Container(
decoration: BoxDecoration(
image: DecorationImage(
@@ -54,7 +71,7 @@ class SalesPage extends StatelessWidget with Trans, Logging {
Divider(),
Container(
padding: EdgeInsets.only(left: 65, right: 65),
child: Text("Unleash Your Development Now!",
child: Text(t("Unleash Your Development Now!"),
textAlign: TextAlign.center,
maxLines: 4,
softWrap: true,
@@ -77,7 +94,7 @@ class SalesPage extends StatelessWidget with Trans, Logging {
Divider(),
Container(
padding: EdgeInsets.only(left: 45, right: 45),
child: Text("Learn about your development, enjoy AI-driven predictions of all of your skills and bodyparts.",
child: Text(t("Learn about your development, enjoy AI-driven predictions of all of your skills and bodyparts."),
textAlign: TextAlign.left,
maxLines: 4,
softWrap: true,
@@ -106,47 +123,45 @@ class SalesPage extends StatelessWidget with Trans, Logging {
Container(
padding: EdgeInsets.only(left: 55, right: 55),
child: Text(
"Subscription Conditions",
t("Subscription Conditions"),
style: GoogleFonts.inter(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.white),
)),
Divider(),
Container(
padding: EdgeInsets.only(left: 55, right: 55),
child: Text(
"Payment will be charged to your account. Subscription automatically renews unless auto-renew is turned off at least 24 hourse before the end of the current period",
t("Payment will be charged to your account. Subscription automatically renews unless auto-renew is turned off at least 24 hours before the end of the current period"),
style: GoogleFonts.inter(fontSize: 12, color: Colors.white),
)),
Divider(),
Container(
padding: EdgeInsets.only(left: 55, right: 55),
child: Text(
"Account will be charged for renewal within 24 hours prior to the end of the current period",
t("Account will be charged for renewal within 24 hours prior to the end of the current period"),
style: GoogleFonts.inter(fontSize: 12, color: Colors.white),
)),
])),
]));
;
}
List<Widget> getButtons(SalesBloc bloc) {
List<Widget> buttons = List();
bloc.product2Display.forEach((element) {
final String title = element.sort == 3 ? "Montly" : "Annual";
final String interval = element.sort == 3 ? " / month" : " / year";
final String desc4 = element.sort == 1 ? "" : "AI driven predictions";
final String title = element.sort == 3 ? t("Montly") : t("Annual");
final String desc4 = element.sort == 1 ? "" : t("AI driven predictions");
String badge;
if (element.sort == 2) {
badge = "14% discount";
badge = t("14% discount");
} else if (element.sort == 1) {
badge = "2 months free";
badge = t("2 months free");
}
Widget button = SalesButton(
title: title,
price: element.localizedPrice,
desc1: "Development programs",
desc2: "Suggestions based on your actual status",
desc3: "Special customized training plans",
desc1: t("Development programs"),
desc2: t("Suggestions based on your actual status"),
desc3: t("Special customized training plans"),
desc4: desc4,
descStyle: GoogleFonts.inter(fontSize: 10, color: Colors.blue[800]),
badgeText: badge,
+9 -10
View File
@@ -10,6 +10,7 @@ import 'package:aitrainer_app/widgets/bottom_nav.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:modal_progress_hud/modal_progress_hud.dart';
import 'package:toggle_switch/toggle_switch.dart';
// ignore: must_be_immutable
@@ -40,16 +41,15 @@ class SettingsPage extends StatelessWidget with Trans {
SnackBar(backgroundColor: Colors.orange, content: Text(state.message, style: TextStyle(color: Colors.white))));
} else if (state is SettingsReady) {
menuBloc.add(MenuRecreateTree());
} else if (state is SettingsLoading) {
Scaffold.of(context).showSnackBar(SnackBar(
duration: Duration(milliseconds: 100),
backgroundColor: Colors.transparent,
content: Container(child: Center(child: CircularProgressIndicator()))));
}
},
// ignore: missing_return
builder: (context, state) {
return settingsUI(context, settingsBloc, menuBloc);
}, builder: (context, state) {
return ModalProgressHUD(
child: settingsUI(context, settingsBloc, menuBloc),
inAsyncCall: state is SettingsLoading,
opacity: 0.5,
color: Colors.black54,
progressIndicator: CircularProgressIndicator(),
);
}),
),
),
@@ -101,7 +101,6 @@ class SettingsPage extends StatelessWidget with Trans {
inactiveFgColor: Colors.grey[900],
labels: [t('Live-Server'), t('Test-Server')],
onToggle: (index) {
//Cache().setServer(index == 0);
settingsBloc.add(SettingsSetServer(live: index == 0));
},
),