import 'dart:async'; import 'package:aitrainer_app/library/custom_icon_icons.dart'; import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/util/trans.dart'; import 'package:aitrainer_app/widgets/menu_image.dart'; import 'package:aitrainer_app/widgets/time_picker.dart'; import 'package:aitrainer_app/widgets/tutorial_widget.dart'; import 'package:badges/badges.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:keyboard_actions/keyboard_actions.dart'; import 'package:keyboard_actions/keyboard_actions_config.dart'; import 'package:stop_watch_timer/stop_watch_timer.dart'; import 'package:wakelock/wakelock.dart'; import 'dialog_html.dart'; class ExerciseSaveStream { static final ExerciseSaveStream _singleton = ExerciseSaveStream._internal(); final StreamController streamController = StreamController.broadcast(); int repeats = 0; double weight = 0; Stream get stream => streamController.stream; StreamController getStreamController() => streamController; factory ExerciseSaveStream() => _singleton; ExerciseSaveStream._internal(); void dispose() { streamController.close(); } } enum Explanations { intro, introBold, explanationWeight, explanationRepeats, explanationButton, explanationButtonExt } extension ExplanationsExt on Explanations { String toStr() => this.toString().split(".").last; bool equalsTo(Explanations type) => this.toString() == type.toString(); bool equalsStringTo(String type) => this.toStr() == type; } class ExplanationExt { final ActivityDone tip; final String? unitQuantityUnit; final double? weight; final int? repeats; ExplanationExt({ required this.tip, this.unitQuantityUnit, required this.weight, required this.repeats, }); String getExplanation(Explanations explanation) { String expl = ""; if (this.tip.equalsTo(ActivityDone.exerciseSaveTestTip)) { if (explanation.equalsTo(Explanations.intro)) { expl = "Please take a middle weight which you are able to do 8-20 times with."; } else if (explanation.equalsTo(Explanations.introBold)) { expl = "Execute your MAXIMUM repeats with it!"; } else if (explanation.equalsTo(Explanations.explanationWeight)) { expl = "Type here your selected weight,"; } else if (explanation.equalsTo(Explanations.explanationRepeats)) { expl = "then here, how many times could you repeat it!"; } else if (explanation.equalsTo(Explanations.explanationButton)) { expl = "After you done, click to the OK button!"; } else if (explanation.equalsTo(Explanations.explanationButtonExt)) { expl = ""; } } else if (this.tip.equalsTo(ActivityDone.exerciseSaveTrainingTip)) { if (unitQuantityUnit != null) { if (weight == -1) { if (explanation.equalsTo(Explanations.intro)) { expl = "Please take a middle weight which you are able to do 8-20 times with."; } else if (explanation.equalsTo(Explanations.introBold)) { expl = "Execute your MAXIMUM repeats with it!"; } } else if (repeats == 99) { if (explanation.equalsTo(Explanations.intro)) { expl = "It is time to exhaust your muscles"; } } else { if (explanation.equalsTo(Explanations.intro)) { expl = "For your optimal development we calculated a suitable weight and repeats"; } else if (explanation.equalsTo(Explanations.introBold)) { expl = "You can change the weight, if you could not set it in the training room"; } } if (explanation.equalsTo(Explanations.explanationButtonExt)) { if (repeats != 99) { expl = "If you could do less, then modify and click to OK"; } } else if (explanation.equalsTo(Explanations.explanationWeight)) { if (weight == -1 || weight == -2) { expl = "Type here your selected weight,"; } else { expl = "Here is your tailored weight,"; } } else if (explanation.equalsTo(Explanations.explanationRepeats)) { if (repeats == 99) { expl = "and execute it with maximum repeats!"; } else { expl = "and executed with this number of repeats!"; } } } else { if (repeats == 99) { if (explanation.equalsTo(Explanations.intro)) { expl = "Please repeat as much times as you can! MAXIMIZE it!"; } } else { if (explanation.equalsTo(Explanations.intro)) { expl = "Please try to execute this exercise with exact repeats what is suggested"; } } } if (explanation.equalsTo(Explanations.explanationButton)) { expl = "After you done, click to the OK button!"; } } else if (this.tip.equalsTo(ActivityDone.exerciseSaveTestsetTip)) {} return expl; } } // ignore: must_be_immutable class ExerciseSave extends StatefulWidget { final ValueChanged onQuantityChanged; final ValueChanged? onUnitQuantityChanged; final VoidCallback? onUnitQuantityChangeUp; final VoidCallback? onUnitQuantityChangeDown; final VoidCallback? onSubmit; final bool hasUnitQuantity; final String? unitQuantityUnit; final String unit; final String exerciseName; final String exerciseDescription; final String exerciseTask; final int exerciseTypeId; final double? weight; final int? repeats; final int? set; final int? exerciseNr; final int? originalQuantity; final MenuImage menuImage; final ActivityDone? tip; ExerciseSave( {required this.onQuantityChanged, this.onUnitQuantityChanged, this.onUnitQuantityChangeUp, this.onUnitQuantityChangeDown, this.onSubmit, required this.hasUnitQuantity, this.unitQuantityUnit, required this.unit, required this.exerciseName, required this.exerciseDescription, required this.exerciseTask, required this.exerciseTypeId, this.weight, this.repeats, this.set, this.exerciseNr, this.originalQuantity, required this.menuImage, this.tip}); @override _ExerciseSaveState createState() => _ExerciseSaveState(); } class _ExerciseSaveState extends State with Trans { final FocusNode _nodeText1 = FocusNode(); final FocusNode _nodeText2 = FocusNode(); final _controller1 = TextEditingController(); final _controller2 = TextEditingController(); final StopWatchTimer stopWatchTimer = StopWatchTimer( isLapHours: false, ); final Stream stream = ExerciseSaveStream().stream; late StreamSubscription subscription; bool changable = false; @override Widget build(BuildContext context) { setContext(context); return getExerciseWidget(); } @override initState() { super.initState(); _nodeText1.addListener(() { if (_nodeText1.hasFocus) { _controller1.selection = TextSelection(baseOffset: 0, extentOffset: _controller1.text.length); } }); _nodeText2.addListener(() { if (_nodeText2.hasFocus) { _controller2.selection = TextSelection(baseOffset: 0, extentOffset: _controller2.text.length); } }); if (widget.unit == "second") { stopWatchTimer.rawTime.listen((value) => { if (value > 0) { widget.onQuantityChanged((value / 1000)), } }); } SchedulerBinding.instance!.addPostFrameCallback((_) { subscription = stream.listen((event) { _controller1.text = ExerciseSaveStream().weight.toStringAsFixed(0); _controller2.text = ExerciseSaveStream().repeats.toStringAsFixed(0); }); print("ExerciseSave weight ${widget.weight}"); _controller1.text = widget.weight == null || widget.weight == -1 ? "TEST" : widget.weight! % widget.weight!.round() == 0 ? widget.weight!.toStringAsFixed(0) : widget.weight!.toStringAsFixed(1); _controller2.text = widget.repeats == null ? "TEST" : widget.repeats! == 99 ? "MAX" : widget.weight == -1 || widget.weight == -2 ? "TEST" : widget.repeats!.toStringAsFixed(0); changable = (_controller2.text != "TEST" && _controller1.text != "TEST"); if (widget.unitQuantityUnit != null && widget.tip != null && Cache().isActivityDone(widget.tip!) == false) { Timer( Duration(milliseconds: 2000), () => { TutorialWidget().explanation( context, ExplanationWidget( unitQuantityUnit: widget.unitQuantityUnit, unit: widget.unit, tip: widget.tip, weight: widget.weight, repeats: widget.repeats, )), }); } }); } @override dispose() { _controller1.dispose(); stopWatchTimer.dispose(); subscription.cancel(); super.dispose(); } void isChangable() { changable = !(widget.weight == -1 || widget.weight == -2 || widget.weight == null); } KeyboardActionsConfig _buildConfig(BuildContext context) { return KeyboardActionsConfig( keyboardActionsPlatform: KeyboardActionsPlatform.ALL, keyboardBarColor: Colors.grey[200], keyboardSeparatorColor: Colors.black26, nextFocus: true, actions: [ KeyboardActionsItem(focusNode: _nodeText2, toolbarButtons: [ (node) { return GestureDetector( onTap: () => node.unfocus(), child: Container( padding: EdgeInsets.all(8.0), color: Colors.orange[500], child: Text( t("Done"), style: TextStyle(color: Colors.white), ), ), ); } ]), KeyboardActionsItem( focusNode: _nodeText1, toolbarButtons: [ //button 2 (node) { return GestureDetector( onTap: () => node.unfocus(), child: Container( color: Colors.orange, padding: EdgeInsets.all(8.0), child: Text( t("Done"), style: TextStyle(color: Colors.white), ), ), ); } ], ), ], ); } String getTimeGoal(int? originalQuantity) { if (originalQuantity == null) { return ""; } else if (originalQuantity == -1) { return t("Goal") + ": Max"; } else { int mins = (originalQuantity / 60).floor(); int secs = originalQuantity % 60; String seconds = ""; if (secs > 0) { seconds = secs.toStringAsFixed(0) + " " + t("secs"); } return t("Goal") + ": " + mins.toStringAsFixed(0) + " " + t("mins") + " " + seconds; } } Widget getExerciseWidget() { ExplanationExt expl = ExplanationExt( tip: widget.tip!, weight: widget.weight, repeats: widget.repeats, unitQuantityUnit: widget.unitQuantityUnit, ); return KeyboardActions( config: _buildConfig(context), child: Container( child: SingleChildScrollView( scrollDirection: Axis.vertical, child: Column(mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.center, children: [ Stack(alignment: Alignment.bottomLeft, children: [ Badge( elevation: 0, padding: EdgeInsets.all(0), position: BadgePosition.topEnd(top: 5, end: 5), animationDuration: Duration(milliseconds: 1500), animationType: BadgeAnimationType.fade, badgeColor: Colors.transparent, showBadge: true, badgeContent: IconButton( iconSize: 30, onPressed: () => showDialog( context: context, builder: (BuildContext context) { return DialogHTML(title: widget.exerciseName, htmlData: '

' + widget.exerciseDescription + '

'); }), icon: Icon( CustomIcon.info_circle, color: Colors.white, )), child: widget.menuImage, ), Container( padding: EdgeInsets.only(left: 10, bottom: 10, right: 10), child: Text( widget.exerciseName, style: GoogleFonts.archivoBlack( fontWeight: FontWeight.bold, 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, ), ], ), overflow: TextOverflow.fade, maxLines: 4, softWrap: true, )) ]), ListTile( leading: IconButton( iconSize: 30, onPressed: () => { if (widget.unitQuantityUnit != null) { TutorialWidget().explanation( context, ExplanationWidget( unitQuantityUnit: widget.unitQuantityUnit, unit: widget.unit, tip: widget.tip, weight: widget.weight, repeats: widget.repeats, )) } }, icon: Icon( CustomIcon.info_circle, color: Colors.orange[100], )), subtitle: Text( t(expl.getExplanation(Explanations.intro)), style: GoogleFonts.inter( fontSize: 14, color: Colors.white, fontWeight: FontWeight.bold, shadows: [ Shadow( offset: Offset(2.0, 2.0), blurRadius: 6.0, color: Colors.black54, ), Shadow( offset: Offset(-3.0, 3.0), blurRadius: 12.0, color: Colors.black54, ), ], ), maxLines: 3, textAlign: TextAlign.left, overflow: TextOverflow.fade, softWrap: true, ), ), expl.getExplanation(Explanations.introBold).length > 0 ? Text( t(expl.getExplanation(Explanations.introBold)), style: GoogleFonts.inter( fontSize: 14, color: Colors.orange, fontWeight: FontWeight.bold, shadows: [ Shadow( offset: Offset(2.0, 2.0), blurRadius: 6.0, color: Colors.black54, ), Shadow( offset: Offset(-3.0, 3.0), blurRadius: 12.0, color: Colors.black54, ), ], ), maxLines: 3, textAlign: TextAlign.center, overflow: TextOverflow.fade, softWrap: true, ) : Offstage(), widget.unit == "second" ? Text( getTimeGoal(widget.originalQuantity), style: GoogleFonts.inter( fontSize: 24, color: Colors.yellow[400], fontWeight: FontWeight.bold, shadows: [ Shadow( offset: Offset(2.0, 2.0), blurRadius: 6.0, color: Colors.black54, ), Shadow( offset: Offset(-3.0, 3.0), blurRadius: 12.0, color: Colors.black54, ), ], ), textAlign: TextAlign.center, ) : Offstage(), columnQuantityUnit(), columnQuantity(), Divider( color: Colors.transparent, ), Text( t(expl.getExplanation(Explanations.explanationButton)), style: GoogleFonts.inter( fontSize: 14, color: Colors.white, fontWeight: FontWeight.bold, shadows: [ Shadow( offset: Offset(2.0, 2.0), blurRadius: 6.0, color: Colors.black54, ), Shadow( offset: Offset(-3.0, 3.0), blurRadius: 12.0, color: Colors.black54, ), ], ), maxLines: 3, textAlign: TextAlign.center, overflow: TextOverflow.fade, softWrap: true, ), Padding( padding: const EdgeInsets.only(left: 35, right: 35), child: Text( t(expl.getExplanation(Explanations.explanationButtonExt)), style: GoogleFonts.inter( fontSize: 14, color: Colors.orange, fontWeight: FontWeight.bold, shadows: [ Shadow( offset: Offset(2.0, 2.0), blurRadius: 6.0, color: Colors.black54, ), Shadow( offset: Offset(-3.0, 3.0), blurRadius: 12.0, color: Colors.black54, ), ], ), maxLines: 3, textAlign: TextAlign.center, overflow: TextOverflow.fade, softWrap: true, )), Divider( color: Colors.transparent, ), Divider( color: Colors.transparent, ), ]), ))); } Widget columnQuantityUnit() { //changable = (_controller2.text != "TEST" && _controller1.text != "TEST"); isChangable(); changable = false; //print("PlusMinus: $changable - con2: ${_controller2.text} con1: ${_controller1.text}"); Widget row = Padding(padding: const EdgeInsets.only(top: 10, left: 55, right: 55), child: Column()); if (widget.hasUnitQuantity) { row = Padding( padding: const EdgeInsets.only(top: 10, left: 55, right: 55), child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Flexible( child: TextFormField( focusNode: _nodeText1, controller: _controller1, maxLength: 5, decoration: InputDecoration( contentPadding: EdgeInsets.only(left: 25, top: 5, bottom: 5), labelText: t(widget.unitQuantityUnit!), labelStyle: GoogleFonts.inter(fontSize: 20, color: Colors.yellow[50]), fillColor: Colors.black38, filled: true, border: OutlineInputBorder( gapPadding: 8.0, borderRadius: BorderRadius.circular(12.0), borderSide: BorderSide(color: Colors.white12, width: 0.4), ), ), keyboardType: TextInputType.numberWithOptions(decimal: true), textInputAction: TextInputAction.done, style: GoogleFonts.archivoBlack(fontSize: 75, color: Colors.yellow[300]), onChanged: (value) { if (value.isNotEmpty) { value = value.replaceFirst(",", "."); value = value.replaceAll(RegExp(r'[^0-9.]'), ""); widget.onUnitQuantityChanged!(double.parse(value)); } })), changable ? Column(children: [ IconButton( onPressed: () => widget.onUnitQuantityChangeUp!(), icon: Icon(CustomIcon.plus_circle, color: Colors.orange, size: 20), ), IconButton( onPressed: () => widget.onUnitQuantityChangeDown!(), icon: Icon(CustomIcon.minus_circle, color: Colors.orange, size: 20), ), ]) : Offstage() ])); } return row; } Widget columnQuantity() { if (widget.unit == "second") { return Column(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ Padding( padding: const EdgeInsets.only(top: 10, left: 55, right: 55), child: StreamBuilder( stream: stopWatchTimer.rawTime, initialData: stopWatchTimer.rawTime.value, builder: (context, snap) { final value = snap.data; final displayTime = StopWatchTimer.getDisplayTime(value!, hours: false); return Column(children: [ Padding( padding: const EdgeInsets.all(8), child: Text( displayTime, style: const TextStyle(fontSize: 35, fontFamily: 'Helvetica', fontWeight: FontWeight.bold, color: Colors.white), ), ), ]); })), Padding( padding: const EdgeInsets.only(top: 10, left: 25, right: 25), child: Column( children: [ Padding( padding: const EdgeInsets.only(bottom: 0), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 5), child: IconButton( padding: const EdgeInsets.all(2), color: Colors.white70, onPressed: () async { stopWatchTimer.onExecute.add(StopWatchExecute.start); Wakelock.enable(); // prevent sleep the phone }, icon: Icon(CustomIcon.play_1), iconSize: 40, ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 5), child: IconButton( padding: const EdgeInsets.all(2), iconSize: 40, color: Colors.white70, onPressed: () async { stopWatchTimer.onExecute.add(StopWatchExecute.stop); Wakelock.disable(); }, icon: Icon(CustomIcon.stop), ), ), Padding( padding: const EdgeInsets.symmetric(horizontal: 5), child: IconButton( padding: const EdgeInsets.all(2), iconSize: 40, color: Colors.white70, onPressed: () async { stopWatchTimer.onExecute.add(StopWatchExecute.reset); }, icon: Icon(CustomIcon.creative_commons_zero), ), ), ], ), ), ], ), ), Divider(), Divider(), Text(t("Or type the time manually:"), style: GoogleFonts.inter(color: Colors.white)), TimePickerWidget( onChange: (value) => { widget.onQuantityChanged(value), }, ) ]); } Widget row = Container( padding: const EdgeInsets.only(top: 10, left: 55, right: 55), child: Column(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ TextFormField( focusNode: _nodeText2, controller: _controller2, decoration: InputDecoration( contentPadding: EdgeInsets.only(left: 25, top: 5, bottom: 5), labelText: t(widget.unit), labelStyle: GoogleFonts.inter(fontSize: 20, color: Colors.orange[50], decorationColor: Colors.black12), fillColor: Colors.black38, filled: true, border: OutlineInputBorder( gapPadding: 8.0, borderRadius: BorderRadius.circular(12.0), borderSide: BorderSide(color: Colors.black26, width: 0.4), ), ), keyboardType: TextInputType.number, textInputAction: TextInputAction.next, style: GoogleFonts.archivoBlack(fontSize: 75, color: Colors.orange[200]), onChanged: (value) { if (value.isNotEmpty) { value = value.replaceFirst(",", "."); value = value.replaceAll(RegExp(r'[^0-9.]'), ""); widget.onQuantityChanged(double.parse(value)); } }, ), ])); return row; } } class ExplanationWidget extends StatefulWidget { final String? unitQuantityUnit; final String unit; final ActivityDone? tip; final double? weight; final int? repeats; const ExplanationWidget({ Key? key, this.unitQuantityUnit, required this.unit, this.tip, this.weight, this.repeats, }) : super(key: key); @override _ExplanationWidgetState createState() => _ExplanationWidgetState(); } class _ExplanationWidgetState extends State with Trans { bool _selected = false; @override Widget build(BuildContext context) { ExplanationExt expl = ExplanationExt( tip: widget.tip!, weight: widget.weight, repeats: widget.repeats, unitQuantityUnit: widget.unitQuantityUnit, ); setContext(context); return Material( color: Colors.transparent, child: Column( children: [ Theme( data: ThemeData(unselectedWidgetColor: Colors.white38), child: CheckboxListTile( value: _selected, onChanged: (bool? checked) { setState(() { _selected = checked!; }); }, checkColor: Colors.white, activeColor: Colors.orange[600], controlAffinity: ListTileControlAffinity.leading, title: Text( t("Show this tip no more"), style: GoogleFonts.inter(color: Colors.grey), ))), Padding( padding: const EdgeInsets.only(top: 10, left: 10, right: 10), child: Text(t(expl.getExplanation(Explanations.intro)), maxLines: 5, textAlign: TextAlign.center, style: GoogleFonts.inter( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 16, ))), Padding( padding: const EdgeInsets.only(top: 10, left: 10, right: 10), child: Text(t(expl.getExplanation(Explanations.introBold)), maxLines: 5, textAlign: TextAlign.center, style: GoogleFonts.inter( color: Colors.orange, fontWeight: FontWeight.bold, fontSize: 16, ))), Stack(children: [ Padding( padding: const EdgeInsets.only(top: 13, left: 18, right: 18), child: Column(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ TextFormField( decoration: InputDecoration( contentPadding: EdgeInsets.only(left: 25, top: 5, bottom: 5), labelText: t(widget.unitQuantityUnit!), labelStyle: GoogleFonts.inter(fontSize: 20, color: Colors.yellow[50]), fillColor: Colors.black38, filled: true, border: OutlineInputBorder( gapPadding: 8.0, borderRadius: BorderRadius.circular(12.0), borderSide: BorderSide(color: Colors.white12, width: 0.4), ), ), initialValue: ".", keyboardType: TextInputType.numberWithOptions(decimal: true), textInputAction: TextInputAction.done, style: GoogleFonts.archivoBlack(fontSize: 80, color: Colors.transparent), onChanged: (value) {}), ])), Container( padding: EdgeInsets.only(top: 35, left: 35, right: 35), child: Text( t(expl.getExplanation(Explanations.explanationWeight)), style: GoogleFonts.archivoBlack(fontSize: 23, color: Colors.yellow[300]), )), ]), Stack(children: [ Padding( padding: const EdgeInsets.only(top: 10, left: 15, right: 15), child: Column(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ TextFormField( decoration: InputDecoration( contentPadding: EdgeInsets.only(left: 25, top: 5, bottom: 5), labelText: t(widget.unit), labelStyle: GoogleFonts.inter(fontSize: 20, color: Colors.yellow[50]), fillColor: Colors.black38, filled: true, border: OutlineInputBorder( gapPadding: 8.0, borderRadius: BorderRadius.circular(12.0), borderSide: BorderSide(color: Colors.white12, width: 0.4), ), ), initialValue: ".", keyboardType: TextInputType.numberWithOptions(decimal: true), textInputAction: TextInputAction.done, style: GoogleFonts.archivoBlack(fontSize: 80, color: Colors.transparent), onChanged: (value) {}), ])), Container( padding: EdgeInsets.only(top: 25, left: 35, right: 35), child: Text( t(expl.getExplanation(Explanations.explanationRepeats)), style: GoogleFonts.archivoBlack(fontSize: 23, color: Colors.yellow[300]), )), ]), Padding( padding: const EdgeInsets.only(top: 10, left: 5, right: 5), child: Text( t("Don't forget, the app can give you only the right values, if you execute the task regurarly and after the exact instructions."), maxLines: 5, textAlign: TextAlign.center, style: GoogleFonts.inter( color: Colors.white, fontSize: 14, ))), Divider(), GestureDetector( onTap: () => { TutorialWidget().close(), if (_selected && widget.tip != null) { Cache().setActivityDonePrefs(widget.tip!), } }, child: Stack( alignment: Alignment.center, children: [ Image.asset('asset/icon/gomb_orange_c.png', width: 100, height: 45), Text( t("Got It"), style: TextStyle(fontSize: 20, color: Colors.white), ), ], )), ], )); } }