From 4d22d98ae06d5ed05a861135c798699a6922e4a3 Mon Sep 17 00:00:00 2001 From: bossanyit Date: Sun, 21 Nov 2021 15:11:03 +0100 Subject: [PATCH] WT 1.1.25+1 training log --- lib/bloc/training_log/training_log_bloc.dart | 112 +++++++++++ lib/bloc/training_log/training_log_event.dart | 32 ++++ lib/bloc/training_log/training_log_state.dart | 28 +++ lib/library/super_tooltip.dart | 3 +- lib/main.dart | 4 + lib/model/training_result.dart | 24 +++ lib/view/exercise_log_page.dart | 1 - lib/view/mydevelopment_log.dart | 177 ++++++++++++++++++ lib/view/mydevelopment_page.dart | 5 +- lib/view/training_plan_execute.dart | 3 +- pubspec.lock | 28 ++- pubspec.yaml | 9 +- 12 files changed, 409 insertions(+), 17 deletions(-) create mode 100644 lib/bloc/training_log/training_log_bloc.dart create mode 100644 lib/bloc/training_log/training_log_event.dart create mode 100644 lib/bloc/training_log/training_log_state.dart create mode 100644 lib/model/training_result.dart create mode 100644 lib/view/mydevelopment_log.dart diff --git a/lib/bloc/training_log/training_log_bloc.dart b/lib/bloc/training_log/training_log_bloc.dart new file mode 100644 index 0000000..f3374be --- /dev/null +++ b/lib/bloc/training_log/training_log_bloc.dart @@ -0,0 +1,112 @@ +import 'package:aitrainer_app/model/cache.dart'; +import 'package:aitrainer_app/model/exercise.dart'; +import 'package:aitrainer_app/model/exercise_type.dart'; +import 'package:aitrainer_app/model/training_result.dart'; +import 'package:aitrainer_app/repository/exercise_repository.dart'; +import 'package:aitrainer_app/util/app_language.dart'; +import 'package:aitrainer_app/util/enums.dart'; +import 'package:aitrainer_app/util/track.dart'; +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_calendar/calendar.dart'; + +part 'training_log_event.dart'; +part 'training_log_state.dart'; + +class TrainingLogBloc extends Bloc { + final ExerciseRepository exerciseRepository = ExerciseRepository(); + TrainingLogBloc() : super(TrainingLogInitial()) { + on(_onLoad); + on(_onDelete); + on(_onResult); + } + + @override + Future close() { + return super.close(); + } + + void _onLoad(TrainingLogLoad event, Emitter emit) async { + await Cache().setActivityDonePrefs(ActivityDone.isExerciseLogSeen); + Track().track(TrackingEvent.exercise_log_open); + emit(TrainingLogReady()); + } + + void _onDelete(TrainingLogDelete event, Emitter emit) async { + exerciseRepository.exerciseList!.remove(event.exercise); + await exerciseRepository.deleteExercise(event.exercise); + Track().track(TrackingEvent.exercise_log_delete); + emit(TrainingLogReady()); + } + + void _onResult(TrainingResultEvent event, Emitter emit) async { + Track().track(TrackingEvent.exercise_log_result); + emit(TrainingLogReady()); + } + + List getTrainingResults() { + exerciseRepository.getExerciseList(); + exerciseRepository.sortByDate(); + + final List trainings = []; + if (exerciseRepository.exerciseLogList == null) { + return trainings; + } + bool isEnglish = AppLanguage().appLocal == Locale('en'); + exerciseRepository.exerciseLogList!.forEach((exercise) { + final ExerciseType? exerciseType = exerciseRepository.getExerciseTypeById(exercise.exerciseTypeId!); + final String exerciseName = isEnglish ? exerciseType!.name : exerciseType!.nameTranslation; + final DateTime start = DateTime( + exercise.dateAdd!.year, exercise.dateAdd!.month, exercise.dateAdd!.day, exercise.dateAdd!.hour, exercise.dateAdd!.minute, 0); + final DateTime end = exercise.trainingPlanDetailsId == null + ? DateTime(exercise.dateAdd!.year, exercise.dateAdd!.month, exercise.dateAdd!.day, exercise.dateAdd!.hour, + exercise.dateAdd!.minute + 2, 0) + : DateTime(exercise.dateAdd!.year, exercise.dateAdd!.month, exercise.dateAdd!.day, exercise.dateAdd!.hour + 2, + exercise.dateAdd!.minute, 0); + Color color = exercise.trainingPlanDetailsId == null ? Colors.blue : Colors.orange; + trainings.add(TrainingResult( + exercise: exercise, + eventName: exerciseName, + from: start, + to: end, + background: color, + isAllDay: false, + isTest: exercise.trainingPlanDetailsId == null ? true : false, + summary: exercise.summary, + )); + }); + return trainings; + } +} + +class TrainingDataSource extends CalendarDataSource { + TrainingDataSource(List source) { + appointments = source; + } + + @override + DateTime getStartTime(int index) { + return appointments![index].from; + } + + @override + DateTime getEndTime(int index) { + return appointments![index].to; + } + + @override + String getSubject(int index) { + return appointments![index].eventName; + } + + @override + Color getColor(int index) { + return appointments![index].background; + } + + @override + bool isAllDay(int index) { + return appointments![index].isAllDay; + } +} diff --git a/lib/bloc/training_log/training_log_event.dart b/lib/bloc/training_log/training_log_event.dart new file mode 100644 index 0000000..bc44a58 --- /dev/null +++ b/lib/bloc/training_log/training_log_event.dart @@ -0,0 +1,32 @@ +part of 'training_log_bloc.dart'; + +abstract class TrainingLogEvent extends Equatable { + const TrainingLogEvent(); + + @override + List get props => []; +} + +class TrainingLogLoad extends TrainingLogEvent { + const TrainingLogLoad(); +} + +class TrainingLogDelete extends TrainingLogEvent { + final Exercise exercise; + const TrainingLogDelete({required this.exercise}); + + @override + List get props => [exercise]; +} + +class TrainingResultEvent extends TrainingLogEvent { + final Exercise exercise; + const TrainingResultEvent({required this.exercise}); + + @override + List get props => [exercise]; +} + +class TrainingLogChange extends TrainingLogEvent { + const TrainingLogChange(); +} diff --git a/lib/bloc/training_log/training_log_state.dart b/lib/bloc/training_log/training_log_state.dart new file mode 100644 index 0000000..cf19e5b --- /dev/null +++ b/lib/bloc/training_log/training_log_state.dart @@ -0,0 +1,28 @@ +part of 'training_log_bloc.dart'; + +abstract class TrainingLogState extends Equatable { + const TrainingLogState(); + + @override + List get props => []; +} + +class TrainingLogInitial extends TrainingLogState { + const TrainingLogInitial(); +} + +class TrainingLogLoading extends TrainingLogState { + const TrainingLogLoading(); +} + +class TrainingLogReady extends TrainingLogState { + const TrainingLogReady(); +} + +class TrainingLogError extends TrainingLogState { + final String message; + const TrainingLogError({required this.message}); + + @override + List get props => [message]; +} diff --git a/lib/library/super_tooltip.dart b/lib/library/super_tooltip.dart index d6b08ca..1265837 100644 --- a/lib/library/super_tooltip.dart +++ b/lib/library/super_tooltip.dart @@ -199,7 +199,7 @@ class SuperTooltip { onClose!(); } - _ballonOverlay!.remove(); + _ballonOverlay?.remove(); _backGroundOverlay?.remove(); isOpen = false; } @@ -308,6 +308,7 @@ class SuperTooltip { } void showBox(BuildContext targetContext) { + if (targetContext == null || targetContext.findRenderObject() == null) return; final renderBox = targetContext.findRenderObject() as RenderBox; var size = renderBox.size; print("Size $size"); diff --git a/lib/main.dart b/lib/main.dart index 0343f2f..fc6b646 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -24,6 +24,7 @@ import 'package:aitrainer_app/view/exercise_log_page.dart'; import 'package:aitrainer_app/view/faq_page.dart'; import 'package:aitrainer_app/view/login.dart'; import 'package:aitrainer_app/view/exercise_new_page.dart'; +import 'package:aitrainer_app/view/mydevelopment_log.dart'; import 'package:aitrainer_app/view/training_plan_custom.dart'; import 'package:aitrainer_app/view/training_plan_custom_add.dart'; import 'package:aitrainer_app/view/training_plan_execute.dart'; @@ -68,6 +69,7 @@ import 'bloc/settings/settings_bloc.dart'; import 'bloc/timer/timer_bloc.dart'; import 'model/cache.dart'; import 'view/training_evaluation_page.dart'; +import 'package:syncfusion_localizations/syncfusion_localizations.dart'; const dsn = 'https://0f635b7225564abc9089f8106f25eb5c@sentry.aitrainer.app/1'; @@ -231,6 +233,7 @@ class WorkoutTestApp extends StatelessWidget { AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + SfGlobalLocalizations.delegate ], supportedLocales: [ const Locale('en', "US"), // English @@ -272,6 +275,7 @@ class WorkoutTestApp extends StatelessWidget { 'settings': (context) => SettingsPage(), 'myDevelopment': (context) => MyDevelopmentPage(), 'exerciseLogPage': (context) => ExerciseLogPage(), + 'mydevelopmentLog': (context) => MyDevelopmentLog(), 'mydevelopmentMusclePage': (context) => MyDevelopmentMusclePage(), 'mydevelopmentBodyPage': (context) => MyDevelopmentBodyPage(), 'mydevelopmentSizesPage': (context) => SizesDevelopmentPage(), diff --git a/lib/model/training_result.dart b/lib/model/training_result.dart new file mode 100644 index 0000000..771cc84 --- /dev/null +++ b/lib/model/training_result.dart @@ -0,0 +1,24 @@ +import 'package:aitrainer_app/model/exercise.dart'; +import 'package:flutter/material.dart'; + +class TrainingResult { + final Exercise exercise; + final String eventName; + final DateTime from; + final DateTime to; + final Color background; + final bool isAllDay; + final bool isTest; + String? summary; + + TrainingResult({ + required this.eventName, + required this.from, + required this.to, + required this.background, + required this.isAllDay, + required this.exercise, + required this.isTest, + this.summary, + }); +} diff --git a/lib/view/exercise_log_page.dart b/lib/view/exercise_log_page.dart index 857f1d3..574ff05 100644 --- a/lib/view/exercise_log_page.dart +++ b/lib/view/exercise_log_page.dart @@ -2,7 +2,6 @@ import 'dart:collection'; import 'package:aitrainer_app/bloc/exercise_log/exercise_log_bloc.dart'; import 'package:aitrainer_app/widgets/app_bar.dart'; import 'package:aitrainer_app/widgets/bottom_nav.dart'; -import 'package:aitrainer_app/widgets/dialog_common.dart'; import 'package:aitrainer_app/widgets/dialog_premium.dart'; import 'package:badges/badges.dart'; import 'package:flutter/cupertino.dart'; diff --git a/lib/view/mydevelopment_log.dart b/lib/view/mydevelopment_log.dart new file mode 100644 index 0000000..3b2c964 --- /dev/null +++ b/lib/view/mydevelopment_log.dart @@ -0,0 +1,177 @@ +import 'package:aitrainer_app/bloc/training_log/training_log_bloc.dart'; +import 'package:aitrainer_app/model/training_result.dart'; +import 'package:aitrainer_app/util/common.dart'; +import 'package:aitrainer_app/util/trans.dart'; +import 'package:aitrainer_app/widgets/app_bar.dart'; +import 'package:aitrainer_app/widgets/bottom_nav.dart'; +import 'package:aitrainer_app/widgets/menu_search_bar.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:modal_progress_hud_nsn/modal_progress_hud_nsn.dart'; +import 'package:syncfusion_flutter_calendar/calendar.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +// ignore: must_be_immutable +class MyDevelopmentLog extends StatelessWidget with Trans, Common { + MyDevelopmentLog({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + setContext(context); + return BlocProvider( + create: (context) => TrainingLogBloc()..add(TrainingLogLoad()), + child: BlocConsumer(listener: (context, state) { + if (state is TrainingLogError) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(backgroundColor: Colors.orange, content: Text(state.message, style: TextStyle(color: Colors.white)))); + } + }, builder: (context, state) { + final exerciseBloc = BlocProvider.of(context); + return ModalProgressHUD( + child: getTrainingLog(exerciseBloc), + inAsyncCall: state is TrainingLogLoading, + opacity: 0.5, + color: Colors.black54, + progressIndicator: CircularProgressIndicator(), + ); + })); + } + + Widget getTrainingLog(TrainingLogBloc bloc) { + return Scaffold( + appBar: AppBarNav(depth: 1), + body: Container( + height: double.infinity, + width: double.infinity, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('asset/image/WT_plainblack_background.jpg'), + fit: BoxFit.cover, + alignment: Alignment.center, + ), + ), + child: Container( + padding: EdgeInsets.only(left: 10, right: 10), + child: Column(children: [ + getHeader(), + getCalendar(bloc), + ]), + )), + bottomNavigationBar: BottomNavigator(bottomNavIndex: 1), + ); + } + + Widget getSearchBar() { + return MenuSearchBar( + listItems: [], + onFind: (value) {}, + ); + } + + Widget getCalendar(TrainingLogBloc bloc) { + return Expanded( + child: SfCalendarTheme( + data: SfCalendarThemeData(brightness: Brightness.dark, backgroundColor: Colors.transparent), + child: SfCalendar( + dataSource: TrainingDataSource(bloc.getTrainingResults()), + view: CalendarView.month, + allowedViews: [ + CalendarView.day, + CalendarView.week, + CalendarView.month, + ], + monthViewSettings: MonthViewSettings( + showAgenda: true, + appointmentDisplayMode: MonthAppointmentDisplayMode.appointment, + showTrailingAndLeadingDates: true, + ), + appointmentTimeTextFormat: 'HH:mm', + headerDateFormat: "y MMMM", + firstDayOfWeek: 1, // Monday + selectionDecoration: BoxDecoration( + color: Colors.transparent, + border: Border.all(color: Colors.red, width: 2), + borderRadius: const BorderRadius.all(Radius.circular(4)), + shape: BoxShape.rectangle, + ), + todayHighlightColor: Color(0xffb4f500), + showNavigationArrow: true, + showDatePickerButton: true, + allowViewNavigation: true, + onTap: (detail) => { + print("${detail.appointments}"), + }, + appointmentBuilder: (BuildContext context, CalendarAppointmentDetails details) { + final TrainingResult result = details.appointments.first; + return Container( + padding: EdgeInsets.only(left: 8, top: 5, right: 5, bottom: 5), + height: 70, + color: result.isTest ? Colors.blue : Colors.orange, + child: Row(mainAxisAlignment: MainAxisAlignment.start, children: [ + Flexible( + fit: FlexFit.tight, + flex: 30, + child: Text(result.eventName, style: GoogleFonts.inter(fontSize: 14, color: Colors.white, fontWeight: FontWeight.bold)), + ), + Flexible( + fit: FlexFit.tight, + flex: 25, + child: Text( + result.summary == null ? "" : result.summary!, + style: TextStyle(fontSize: 12, color: Colors.white), + )), + IconButton( + padding: EdgeInsets.zero, + icon: Icon(Icons.info_outline, color: Color(0xffb4f500)), + onPressed: () {}, + ), + IconButton( + padding: EdgeInsets.zero, + icon: Icon(Icons.delete, color: Colors.black12), + onPressed: () {}, + ) + ])); + }), + )); + } + + Widget getHeader() { + return Card( + color: Colors.white60, + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('asset/image/WT_plainblack_background.jpg'), + fit: BoxFit.cover, + alignment: Alignment.center, + ), + ), + padding: EdgeInsets.only(left: 10, right: 5, top: 12, bottom: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Icon( + Icons.info, + color: Colors.orangeAccent, + ), + Text(" "), + Text( + t("My Exercise Logs"), + style: GoogleFonts.inter( + fontSize: 16, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + Divider(), + Flexible( + fit: FlexFit.tight, + flex: 5, + child: getSearchBar(), + ) + ], + ), + )); + } +} diff --git a/lib/view/mydevelopment_page.dart b/lib/view/mydevelopment_page.dart index 895269b..33e21bf 100644 --- a/lib/view/mydevelopment_page.dart +++ b/lib/view/mydevelopment_page.dart @@ -6,7 +6,6 @@ import 'package:aitrainer_app/repository/exercise_repository.dart'; import 'package:aitrainer_app/util/enums.dart'; import 'package:aitrainer_app/util/track.dart'; import 'package:aitrainer_app/widgets/dialog_common.dart'; -import 'package:aitrainer_app/widgets/dialog_html.dart'; import 'package:aitrainer_app/widgets/dialog_premium.dart'; import 'package:badges/badges.dart'; import 'package:flutter/services.dart'; @@ -67,7 +66,7 @@ class _MyDevelopmentPage extends State with Trans { child: ImageButton( width: imageWidth, textAlignment: Alignment.topCenter, - text: t("My Exercise Logs"), + text: t("My Training Logs"), style: GoogleFonts.robotoMono( textStyle: TextStyle( fontSize: 14, @@ -76,7 +75,7 @@ class _MyDevelopmentPage extends State with Trans { backgroundColor: Colors.black54.withOpacity(0.4))), image: "asset/image/edzesnaplom400400.jpg", left: 5, - onTap: () => this.callBackExerciseLog(exerciseRepository, customerRepository), + onTap: () => Navigator.of(context).pushNamed('mydevelopmentLog', arguments: args), isLocked: false, )), Badge( diff --git a/lib/view/training_plan_execute.dart b/lib/view/training_plan_execute.dart index 52dec59..131c528 100644 --- a/lib/view/training_plan_execute.dart +++ b/lib/view/training_plan_execute.dart @@ -158,9 +158,10 @@ class _ExerciseTabs extends State with TickerProviderStateMixin { } Widget getTabs(TrainingPlanBloc bloc) { + final String tabName = bloc.getMyPlan() != null && bloc.getMyPlan()!.name != null ? bloc.getMyPlan()!.name! : ""; return Column(children: [ Text( - bloc.getMyPlan()!.name!, + tabName, style: GoogleFonts.archivoBlack( fontSize: 14, color: Colors.white, diff --git a/pubspec.lock b/pubspec.lock index 81e350c..e328493 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -63,7 +63,7 @@ packages: name: bloc_test url: "https://pub.dartlang.org" source: hosted - version: "8.2.0" + version: "8.5.0" boolean_selector: dependency: transitive description: @@ -281,6 +281,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.1" + diff_match_patch: + dependency: transitive + description: + name: diff_match_patch + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.1" equatable: dependency: "direct main" description: @@ -474,7 +481,7 @@ packages: name: flutter_bloc url: "https://pub.dartlang.org" source: hosted - version: "7.3.0" + version: "7.3.3" flutter_facebook_auth: dependency: "direct main" description: @@ -748,7 +755,7 @@ packages: name: mocktail url: "https://pub.dartlang.org" source: hosted - version: "0.1.1" + version: "0.2.0" modal_progress_hud_nsn: dependency: "direct main" description: @@ -1201,7 +1208,7 @@ packages: name: syncfusion_flutter_calendar url: "https://pub.dartlang.org" source: hosted - version: "19.2.60" + version: "19.3.53" syncfusion_flutter_charts: dependency: "direct main" description: @@ -1215,7 +1222,7 @@ packages: name: syncfusion_flutter_core url: "https://pub.dartlang.org" source: hosted - version: "19.3.48" + version: "19.3.54" syncfusion_flutter_datagrid: dependency: "direct main" description: @@ -1229,7 +1236,7 @@ packages: name: syncfusion_flutter_datepicker url: "https://pub.dartlang.org" source: hosted - version: "19.2.60" + version: "19.3.53" syncfusion_flutter_gauges: dependency: "direct main" description: @@ -1237,6 +1244,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "19.3.48" + syncfusion_localizations: + dependency: "direct main" + description: + name: syncfusion_localizations + url: "https://pub.dartlang.org" + source: hosted + version: "19.3.54" synchronized: dependency: transitive description: @@ -1285,7 +1299,7 @@ packages: name: timezone url: "https://pub.dartlang.org" source: hosted - version: "0.7.0" + version: "0.8.0" timing: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0eaf615..375e830 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.1.24+102 +version: 1.1.24+103 environment: sdk: ">=2.12.0 <3.0.0" @@ -28,7 +28,7 @@ dependencies: google_fonts: ^2.1.0 devicelocale: ^0.4.1 sentry_flutter: ^5.1.0-beta.1 - flutter_bloc: ^7.3.0 + flutter_bloc: ^7.3.3 equatable: ^2.0.3 spider_chart: ^0.1.5 @@ -72,7 +72,8 @@ dependencies: syncfusion_flutter_gauges: ^19.3.48 syncfusion_flutter_datagrid: ^19.1.63 syncfusion_flutter_charts: ^19.2.60 - syncfusion_flutter_calendar: ^19.2.60 + syncfusion_flutter_calendar: ^19.3.53 + syncfusion_localizations: ^19.3.54 flutter_facebook_auth: ^3.5.1 google_sign_in: ^5.0.3 @@ -99,7 +100,7 @@ dev_dependencies: test: '>=1.0.0 <2.0.0' flutter_test: sdk: flutter - bloc_test: ^8.2.0 + bloc_test: ^8.5.0 build_runner: