diff --git a/android/data/queries.sql b/android/data/queries.sql new file mode 100644 index 0000000..128153c --- /dev/null +++ b/android/data/queries.sql @@ -0,0 +1 @@ +SELECT * FROM `tracking` WHERE `date_add` > '2021-04-17 00:00:00' AND `date_add` < '2021-04-18 00:00:00' AND `event` LIKE 'registration' \ No newline at end of file diff --git a/i18n/en.json b/i18n/en.json index df11e5b..685de90 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -439,6 +439,8 @@ "How can serve you this result?":"How can serve you this result?", "Go":"Go", "Result":"Result", - "Name too short":"Name too short" + "Name too short":"Name too short", + "No, bring me there":"No, bring me there", + "You are about to add a new parallel test":"You are about to add a new parallel test" } \ No newline at end of file diff --git a/i18n/hu.json b/i18n/hu.json index dd673c8..cb12778 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -434,5 +434,7 @@ "Development":"Fejlődésedhez", "Go":"Érdekel", "Result":"Értékelés", - "Name too short":"A név túl rövid" + "Name too short":"A név túl rövid", + "No, bring me there":"Nem, vigyél oda", + "You are about to add a new parallel test":"Egy új gyakorlatot készülsz párhuzamosan végrehajtani" } \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index f0fd441..b3e970d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -156,14 +156,14 @@ PODS: - path_provider (0.0.1): - Flutter - PromisesObjC (1.2.12) - - Purchases (3.10.6): - - PurchasesCoreSwift (= 3.10.6) - - purchases_flutter (3.1.0): + - Purchases (3.10.7): + - PurchasesCoreSwift (= 3.10.7) + - purchases_flutter (3.2.1): - Flutter - - PurchasesHybridCommon (= 1.6.1) - - PurchasesCoreSwift (3.10.6) - - PurchasesHybridCommon (1.6.1): - - Purchases (= 3.10.6) + - PurchasesHybridCommon (= 1.6.2) + - PurchasesCoreSwift (3.10.7) + - PurchasesHybridCommon (1.6.2): + - Purchases (= 3.10.7) - shared_preferences (0.0.1): - Flutter - smartlook (0.0.5): @@ -312,10 +312,10 @@ SPEC CHECKSUMS: package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c PromisesObjC: 3113f7f76903778cf4a0586bd1ab89329a0b7b97 - Purchases: 520fdb59140fed96932a30d02a3ec04858cb541c - purchases_flutter: 05472ba84c83f05a138a3a657f1013f5f7143539 - PurchasesCoreSwift: 31c2a3d7394432abbe64d46f0933835de0b33033 - PurchasesHybridCommon: 013c8072b73e752a206779747e88c068fbf999ec + Purchases: b8b8fb6e856ac8166e217f6e014df894d821dda1 + purchases_flutter: 0130970b895c903e4e0aad793dd3a4c1b70bb434 + PurchasesCoreSwift: 8ae0f08e020f0bc97c1befa4e38a0dbc8e9732e0 + PurchasesHybridCommon: 5f5c1c245b12fc5e8760af7d11cb10f888109a9b shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d smartlook: bbc5c73a85c752a31dabf100c8930838c646342e sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index c0c663d..5dad58a 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -405,7 +405,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 1.1.12; + MARKETING_VERSION = 1.1.13; PRODUCT_BUNDLE_IDENTIFIER = com.aitrainer.app; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -548,7 +548,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 1.1.12; + MARKETING_VERSION = 1.1.13; PRODUCT_BUNDLE_IDENTIFIER = com.aitrainer.app; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -583,7 +583,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 1.1.12; + MARKETING_VERSION = 1.1.13; PRODUCT_BUNDLE_IDENTIFIER = com.aitrainer.app; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/lib/bloc/sales/sales_bloc.dart b/lib/bloc/sales/sales_bloc.dart index f046cd8..acf7a07 100644 --- a/lib/bloc/sales/sales_bloc.dart +++ b/lib/bloc/sales/sales_bloc.dart @@ -136,7 +136,7 @@ class SalesBloc extends Bloc with Logging { } product2Display.sort((a, b) { - return a.sort < b.sort ? a.sort : b.sort; + return a.sort < b.sort ? -1 : 1; }); productTest.productId = productId; diff --git a/lib/bloc/test_set_edit/test_set_edit_bloc.dart b/lib/bloc/test_set_edit/test_set_edit_bloc.dart index 0e750be..8eee1d1 100644 --- a/lib/bloc/test_set_edit/test_set_edit_bloc.dart +++ b/lib/bloc/test_set_edit/test_set_edit_bloc.dart @@ -8,6 +8,7 @@ import 'package:aitrainer_app/model/exercise_plan.dart'; import 'package:aitrainer_app/model/exercise_plan_detail.dart'; import 'package:aitrainer_app/model/exercise_plan_template.dart'; import 'package:aitrainer_app/model/exercise_type.dart'; +import 'package:aitrainer_app/model/workout_menu_tree.dart'; import 'package:aitrainer_app/repository/workout_tree_repository.dart'; import 'package:aitrainer_app/service/exercise_plan_service.dart'; import 'package:aitrainer_app/util/enums.dart'; @@ -28,6 +29,9 @@ class TestSetEditBloc extends Bloc { final List _actualExerciseTypes = []; final HashMap _exercisePlanDetails = HashMap(); + final HashMap?> _displayPlanDetails = HashMap(); + int? sliderIndex; + TestSetEditBloc( {required this.templateName, required this.templateNameTranslation, required this.workoutTreeRepository, required this.menuBloc}) : super(TestSetEditInitial()) { @@ -36,12 +40,24 @@ class TestSetEditBloc extends Bloc { final ExercisePlanTemplate template = element as ExercisePlanTemplate; if (template.name == templateName) { templateDescription = template.descriptionTranslation; + int index = 0; template.exerciseTypes.forEach((id) { final ExerciseType? exerciseType = Cache().getExerciseTypeById(id); if (exerciseType != null) { _exerciseTypes.add(exerciseType); _actualExerciseTypes.add(exerciseType); - _exercisePlanDetails[exerciseType.exerciseTypeId] = exerciseType; + _exercisePlanDetails[index] = exerciseType; + + _displayPlanDetails[index] = [exerciseType]; + final WorkoutMenuTree? workoutTree = menuBloc.menuTreeRepository.getMenuItemByExerciseTypeId(exerciseType.exerciseTypeId); + final List? alternativeMenuItems = menuBloc.menuTreeRepository.getWorkoutTreeAlternatives(workoutTree); + if (alternativeMenuItems != null) { + final List? actualList = _displayPlanDetails[index]; + alternativeMenuItems.forEach((element) { + actualList!.add(element.exerciseType!); + }); + } + index++; } }); } @@ -54,32 +70,24 @@ class TestSetEditBloc extends Bloc { try { if (event is TestSetEditChangeExerciseType) { yield TestSetEditLoading(); - final List alternatives = workoutTreeRepository.getExerciseTypeAlternatives(event.exerciseTypeId); - final ExerciseType? exerciseType = Cache().getExerciseTypeById(event.exerciseTypeId); - if (exerciseType != null) { - if (_exercisePlanDetails[event.exerciseTypeId] == null) { - /// it was skipped - _exercisePlanDetails[exerciseType.exerciseTypeId] = exerciseType; - } else { - if (event.index == 0) { - _exercisePlanDetails[exerciseType.exerciseTypeId] = exerciseType; - } else { - final changedExerciseType = alternatives[event.index - 1]; - _exercisePlanDetails[exerciseType.exerciseTypeId] = changedExerciseType; - } - } + ExerciseType? exerciseType; + if (_exercisePlanDetails[event.indexKey] == null) { + exerciseType = displayPlanDetails[event.indexKey][0]; + } else { + exerciseType = displayPlanDetails[event.indexKey][event.index]; } - - // to keep the slider accurate - refreshActualPlan(); + _exercisePlanDetails[event.indexKey] = exerciseType; + this.sliderIndex = event.index; yield TestSetEditReady(); } else if (event is TestSetEditDeleteExerciseType) { yield TestSetEditLoading(); - final ExerciseType? exerciseType = Cache().getExerciseTypeById(event.exerciseTypeId); - if (exerciseType != null) { - _exercisePlanDetails[exerciseType.exerciseTypeId] = null; - } - refreshActualPlan(); + _exercisePlanDetails[event.indexKey] = null; + + yield TestSetEditReady(); + } else if (event is TestSetEditAddExerciseType) { + yield TestSetEditLoading(); + ExerciseType exerciseType = displayPlanDetails[event.indexKey][0]; + _exercisePlanDetails[event.indexKey] = exerciseType; yield TestSetEditReady(); } else if (event is TestSetEditSubmit) { yield TestSetEditLoading(); @@ -114,6 +122,7 @@ class TestSetEditBloc extends Bloc { List get exerciseTypes => this._exerciseTypes; List get actualExerciseTypes => this._actualExerciseTypes; HashMap get exercisePlanDetails => this._exercisePlanDetails; + get displayPlanDetails => this._displayPlanDetails; void refreshActualPlan() { _actualExerciseTypes.removeRange(0, _actualExerciseTypes.length - 1); diff --git a/lib/bloc/test_set_edit/test_set_edit_event.dart b/lib/bloc/test_set_edit/test_set_edit_event.dart index 8649cae..b845cdd 100644 --- a/lib/bloc/test_set_edit/test_set_edit_event.dart +++ b/lib/bloc/test_set_edit/test_set_edit_event.dart @@ -13,19 +13,27 @@ class TestSetEditLoad extends TestSetEditEvent { class TestSetEditChangeExerciseType extends TestSetEditEvent { final int index; - final int exerciseTypeId; - const TestSetEditChangeExerciseType({required this.index, required this.exerciseTypeId}); + final int indexKey; + const TestSetEditChangeExerciseType({required this.index, required this.indexKey}); @override - List get props => [index, exerciseTypeId]; + List get props => [index, indexKey]; } class TestSetEditDeleteExerciseType extends TestSetEditEvent { - final int exerciseTypeId; - const TestSetEditDeleteExerciseType({required this.exerciseTypeId}); + final int indexKey; + const TestSetEditDeleteExerciseType({required this.indexKey}); @override - List get props => [exerciseTypeId]; + List get props => [indexKey]; +} + +class TestSetEditAddExerciseType extends TestSetEditEvent { + final int indexKey; + const TestSetEditAddExerciseType({required this.indexKey}); + + @override + List get props => [indexKey]; } class TestSetEditSkipExerciseType extends TestSetEditEvent { diff --git a/lib/bloc/test_set_execute/test_set_execute_bloc.dart b/lib/bloc/test_set_execute/test_set_execute_bloc.dart index a1b68f9..4f6e7a6 100644 --- a/lib/bloc/test_set_execute/test_set_execute_bloc.dart +++ b/lib/bloc/test_set_execute/test_set_execute_bloc.dart @@ -37,6 +37,7 @@ class TestSetExecuteBloc extends Bloc void initExercisePlan() { exercisePlan = Cache().activeExercisePlan; if (exercisePlan != null) { + print("Ability ${exercisePlan!.type!}"); testName = exercisePlan!.name; this.miniTestSet = exercisePlan!.type != null && ExerciseAbility.mini_test_set.equalsStringTo(exercisePlan!.type!); this.paralellTest = exercisePlan!.type != null && ExerciseAbility.paralell_test.equalsStringTo(exercisePlan!.type!); @@ -265,7 +266,7 @@ class TestSetExecuteBloc extends Bloc HashMap ret = HashMap(); if (exercisePlan != null && ExerciseAbility.mini_test_set.equalsStringTo(exercisePlan!.type!)) { final String message = "You have an active Test Set!"; - final String message2 = "Do you want you to override it?"; + final String message2 = "Do you want to override it?"; ret['message'] = message; ret['message2'] = message2; ret['canAdd'] = false; diff --git a/lib/library/dropdown_search.dart b/lib/library/dropdown_search/dropdown_search.dart similarity index 100% rename from lib/library/dropdown_search.dart rename to lib/library/dropdown_search/dropdown_search.dart diff --git a/lib/library/popup_menu.dart b/lib/library/dropdown_search/popup_menu.dart similarity index 100% rename from lib/library/popup_menu.dart rename to lib/library/dropdown_search/popup_menu.dart diff --git a/lib/library/select_dialog.dart b/lib/library/dropdown_search/select_dialog.dart similarity index 100% rename from lib/library/select_dialog.dart rename to lib/library/dropdown_search/select_dialog.dart diff --git a/lib/library/facebook_app_events/facebook_app_events.dart b/lib/library/facebook_app_events/facebook_app_events.dart new file mode 100644 index 0000000..1b88027 --- /dev/null +++ b/lib/library/facebook_app_events/facebook_app_events.dart @@ -0,0 +1,340 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +const channelName = 'flutter.oddbit.id/facebook_app_events'; + +class FacebookAppEvents { + static const _channel = MethodChannel(channelName); + + // See: https://github.com/facebook/facebook-android-sdk/blob/master/facebook-core/src/main/java/com/facebook/appevents/AppEventsConstants.java + static const eventNameActivatedApp = 'fb_mobile_activate_app'; + static const eventNameDeactivatedApp = 'fb_mobile_deactivate_app'; + static const eventNameCompletedRegistration = 'fb_mobile_complete_registration'; + static const eventNameViewedContent = 'fb_mobile_content_view'; + static const eventNameRated = 'fb_mobile_rate'; + static const eventNameInitiatedCheckout = 'fb_mobile_initiated_checkout'; + static const eventNameAddedToCart = 'fb_mobile_add_to_cart'; + static const eventNameAddedToWishlist = 'fb_mobile_add_to_wishlist'; + + static const _paramNameValueToSum = "_valueToSum"; + static const paramNameCurrency = "fb_currency"; + static const paramNameRegistrationMethod = "fb_registration_method"; + static const paramNamePaymentInfoAvailable = "fb_payment_info_available"; + static const paramNameNumItems = "fb_num_items"; + static const paramValueYes = "1"; + static const paramValueNo = "0"; + + /// Parameter key used to specify a generic content type/family for the logged event, e.g. + /// "music", "photo", "video". Options to use will vary depending on the nature of the app. + static const paramNameContentType = "fb_content_type"; + + /// Parameter key used to specify data for the one or more pieces of content being logged about. + /// Data should be a JSON encoded string. + /// Example: + /// "[{\"id\": \"1234\", \"quantity\": 2, \"item_price\": 5.99}, {\"id\": \"5678\", \"quantity\": 1, \"item_price\": 9.99}]" + static const paramNameContent = "fb_content"; + + /// Parameter key used to specify an ID for the specific piece of content being logged about. + /// This could be an EAN, article identifier, etc., depending on the nature of the app. + static const paramNameContentId = "fb_content_id"; + + /// Clears the current user data + Future clearUserData() { + return _channel.invokeMethod('clearUserData'); + } + + /// Clears the currently set user id. + Future clearUserID() { + return _channel.invokeMethod('clearUserID'); + } + + /// Explicitly flush any stored events to the server. + Future flush() { + return _channel.invokeMethod('flush'); + } + + /// Returns the app ID this logger was configured to log to. + Future getApplicationId() { + return _channel.invokeMethod('getApplicationId'); + } + + Future getAnonymousId() { + return _channel.invokeMethod('getAnonymousId'); + } + + /// Log an app event with the specified [name] and the supplied [parameters] value. + Future logEvent({ + required String name, + Map? parameters, + double? valueToSum, + }) { + final args = { + 'name': name, + 'parameters': parameters, + _paramNameValueToSum: valueToSum, + }; + + return _channel.invokeMethod('logEvent', _filterOutNulls(args)); + } + + /// Sets user data to associate with all app events. + /// All user data are hashed and used to match Facebook user from this + /// instance of an application. The user data will be persisted between + /// application instances. + Future setUserData({ + String? email, + String? firstName, + String? lastName, + String? phone, + String? dateOfBirth, + String? gender, + String? city, + String? state, + String? zip, + String? country, + }) { + final args = { + 'email': email, + 'firstName': firstName, + 'lastName': lastName, + 'phone': phone, + 'dateOfBirth': dateOfBirth, + 'gender': gender, + 'city': city, + 'state': state, + 'zip': zip, + 'country': country, + }; + + return _channel.invokeMethod('setUserData', args); + } + + /// Logs an app event that tracks that the application was open via Push Notification. + Future logPushNotificationOpen({ + required Map payload, + String? action, + }) { + final args = { + 'payload': payload, + 'action': action, + }; + + return _channel.invokeMethod('logPushNotificationOpen', args); + } + + /// Sets a user [id] to associate with all app events. + /// This can be used to associate your own user id with the + /// app events logged from this instance of an application. + /// The user ID will be persisted between application instances. + Future setUserID(String id) { + return _channel.invokeMethod('setUserID', id); + } + + /// Update user properties as provided by a map of [parameters] + Future updateUserProperties({ + required Map parameters, + String? applicationId, + }) { + final args = { + 'parameters': parameters, + 'applicationId': applicationId, + }; + + return _channel.invokeMethod('updateUserProperties', args); + } + + // Below are shorthand implementations of the predefined app event constants + + /// Log this event when an app is being activated. + /// + /// See: https://developers.facebook.com/docs/reference/androidsdk/current/facebook/com/facebook/appevents/appeventsconstants.html/#eventnameactivatedapp + Future logActivatedApp() { + return logEvent(name: eventNameActivatedApp); + } + + /// Log this event when an app is being deactivated. + /// + /// See: https://developers.facebook.com/docs/reference/androidsdk/current/facebook/com/facebook/appevents/appeventsconstants.html/#eventnamedeactivatedapp + Future logDeactivatedApp() { + return logEvent(name: eventNameDeactivatedApp); + } + + /// Log this event when the user has completed registration with the app. + /// Parameter [registrationMethod] is used to specify the method the user has + /// used to register for the app, e.g. "Facebook", "email", "Google", etc. + /// See: https://developers.facebook.com/docs/reference/androidsdk/current/facebook/com/facebook/appevents/appeventsconstants.html/#eventnamecompletedregistration + Future logCompletedRegistration({String? registrationMethod}) { + return logEvent( + name: eventNameCompletedRegistration, + parameters: { + paramNameRegistrationMethod: registrationMethod, + }, + ); + } + + /// Log this event when the user has rated an item in the app. + /// + /// See: https://developers.facebook.com/docs/reference/androidsdk/current/facebook/com/facebook/appevents/appeventsconstants.html/#eventnamerated + Future logRated({double? valueToSum}) { + return logEvent( + name: eventNameRated, + valueToSum: valueToSum, + ); + } + + /// Log this event when the user has viewed a form of content in the app. + /// + /// See: https://developers.facebook.com/docs/reference/androidsdk/current/facebook/com/facebook/appevents/appeventsconstants.html/#eventnameviewedcontent + Future logViewContent({ + Map? content, + String? id, + String? type, + String? currency, + double? price, + }) { + return logEvent( + name: eventNameViewedContent, + parameters: { + paramNameContent: content != null ? json.encode(content) : null, + paramNameContentId: id, + paramNameContentType: type, + paramNameCurrency: currency, + }, + valueToSum: price, + ); + } + + /// Log this event when the user has added item to cart + /// + /// See: https://developers.facebook.com/docs/reference/androidsdk/current/facebook/com/facebook/appevents/appeventsconstants.html/#eventnameaddedtocart + Future logAddToCart({ + Map? content, + @required String? id, + @required String? type, + @required String? currency, + @required double? price, + }) { + return logEvent( + name: eventNameAddedToCart, + parameters: { + paramNameContent: content != null ? json.encode(content) : null, + paramNameContentId: id, + paramNameContentType: type, + paramNameCurrency: currency, + }, + valueToSum: price, + ); + } + + /// Log this event when the user has added item to cart + /// + /// See: https://developers.facebook.com/docs/reference/androidsdk/current/facebook/com/facebook/appevents/appeventsconstants.html/#eventnameaddedtowishlist + Future logAddToWishlist({ + Map? content, + @required String? id, + @required String? type, + @required String? currency, + @required double? price, + }) { + return logEvent( + name: eventNameAddedToWishlist, + parameters: { + paramNameContent: content != null ? json.encode(content) : null, + paramNameContentId: id, + paramNameContentType: type, + paramNameCurrency: currency, + }, + valueToSum: price, + ); + } + + /// Re-enables auto logging of app events after user consent + /// if disabled for GDPR-compliance. + /// + /// See: https://developers.facebook.com/docs/app-events/gdpr-compliance + Future setAutoLogAppEventsEnabled(bool enabled) { + return _channel.invokeMethod('setAutoLogAppEventsEnabled', enabled); + } + + /// Set Data Processing Options + /// This is needed for California Consumer Privacy Act (CCPA) compliance + /// + /// See: https://developers.facebook.com/docs/marketing-apis/data-processing-options + Future setDataProcessingOptions( + List options, { + int? country, + int? state, + }) { + final args = { + 'options': options, + 'country': country, + 'state': state, + }; + + return _channel.invokeMethod('setDataProcessingOptions', args); + } + + Future logPurchase({ + required double amount, + required String currency, + Map? parameters, + }) { + final args = { + 'amount': amount, + 'currency': currency, + 'parameters': parameters, + }; + return _channel.invokeMethod('logPurchase', _filterOutNulls(args)); + } + + Future logInitiatedCheckout({ + required double totalPrice, + required String currency, + required String contentType, + required String contentId, + required int numItems, + bool paymentInfoAvailable = false, + }) { + return logEvent( + name: eventNameInitiatedCheckout, + valueToSum: totalPrice, + parameters: { + paramNameContentType: contentType, + paramNameContentId: contentId, + paramNameNumItems: numItems, + paramNameCurrency: currency, + paramNamePaymentInfoAvailable: paymentInfoAvailable ? paramValueYes : paramValueNo, + }, + ); + } + + /// Sets the Advert Tracking propeety for iOS advert tracking + /// an iOS 14+ feature, android should just return a success. + Future setAdvertiserTracking({ + required bool enabled, + }) { + final args = {'enabled': enabled}; + + return _channel.invokeMethod('setAdvertiserTracking', args); + } + + // --------------------------------------------------------------------------- + // --------------------------------------------------------------------------- + // + // PRIVATE METHODS BELOW HERE + + /// Creates a new map containing all of the key/value pairs from [parameters] + /// except those whose value is `null`. + Map _filterOutNulls(Map parameters) { + final Map filtered = {}; + parameters.forEach((String key, dynamic value) { + if (value != null) { + filtered[key] = value; + } + }); + return filtered; + } +} diff --git a/lib/library/liquid_progress_indicator/liquid_circular_progress_indicator.dart b/lib/library/liquid_progress_indicator/liquid_circular_progress_indicator.dart new file mode 100644 index 0000000..2440009 --- /dev/null +++ b/lib/library/liquid_progress_indicator/liquid_circular_progress_indicator.dart @@ -0,0 +1,119 @@ +import 'dart:math' as math; + +import 'package:flutter/material.dart'; +import 'wave.dart'; + +const double _twoPi = math.pi * 2.0; +const double _epsilon = .001; +const double _sweep = _twoPi - _epsilon; + +class LiquidCircularProgressIndicator extends ProgressIndicator { + ///The width of the border, if this is set [borderColor] must also be set. + final double borderWidth; + + ///The color of the border, if this is set [borderWidth] must also be set. + final Color borderColor; + + ///The widget to show in the center of the progress indicator. + final Widget? center; + + ///The direction the liquid travels. + final Axis direction; + + LiquidCircularProgressIndicator({ + Key? key, + double value = 0.5, + Color? backgroundColor, + Animation? valueColor, + required this.borderWidth, + required this.borderColor, + this.center, + this.direction = Axis.vertical, + }) : super( + key: key, + value: value, + backgroundColor: backgroundColor, + valueColor: valueColor, + ); + + Color _getBackgroundColor(BuildContext context) => backgroundColor ?? Theme.of(context).backgroundColor; + + Color _getValueColor(BuildContext context) => valueColor?.value ?? Theme.of(context).accentColor; + + @override + State createState() => _LiquidCircularProgressIndicatorState(); +} + +class _LiquidCircularProgressIndicatorState extends State { + @override + Widget build(BuildContext context) { + return ClipPath( + clipper: _CircleClipper(), + child: CustomPaint( + painter: _CirclePainter( + color: widget._getBackgroundColor(context), + ), + foregroundPainter: _CircleBorderPainter( + color: widget.borderColor, + width: widget.borderWidth, + ), + child: Stack( + children: [ + Wave( + value: widget.value!, + color: widget._getValueColor(context), + direction: widget.direction, + ), + if (widget.center != null) Center(child: widget.center), + ], + ), + ), + ); + } +} + +class _CirclePainter extends CustomPainter { + final Color color; + + _CirclePainter({required this.color}); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint()..color = color; + canvas.drawArc(Offset.zero & size, 0, _sweep, false, paint); + } + + @override + bool shouldRepaint(_CirclePainter oldDelegate) => color != oldDelegate.color; +} + +class _CircleBorderPainter extends CustomPainter { + final Color color; + final double width; + + _CircleBorderPainter({required this.color, required this.width}); + + @override + void paint(Canvas canvas, Size size) { + final borderPaint = Paint() + ..color = color + ..style = PaintingStyle.stroke + ..strokeWidth = width; + final newSize = Size(size.width - width, size.height - width); + canvas.drawArc(Offset(width / 2, width / 2) & newSize, 0, _sweep, false, borderPaint); + } + + @override + bool shouldRepaint(_CircleBorderPainter oldDelegate) => color != oldDelegate.color || width != oldDelegate.width; +} + +class _CircleClipper extends CustomClipper { + @override + Path getClip(Size size) { + final path = Path()..addArc(Offset.zero & size, 0, _sweep); + return path; + } + + @override + bool shouldReclip(CustomClipper oldClipper) => false; +} diff --git a/lib/library/liquid_progress_indicator/liquid_custom_progress_indicator.dart b/lib/library/liquid_progress_indicator/liquid_custom_progress_indicator.dart new file mode 100644 index 0000000..35af1bf --- /dev/null +++ b/lib/library/liquid_progress_indicator/liquid_custom_progress_indicator.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; +import 'wave.dart'; + +class LiquidCustomProgressIndicator extends ProgressIndicator { + ///The widget to show in the center of the progress indicator. + final Widget? center; + + ///The direction the liquid travels. + final Axis direction; + + ///The path used to draw the shape of the progress indicator. The size of the progress indicator is controlled by the bounds of this path. + final Path shapePath; + + LiquidCustomProgressIndicator({ + Key? key, + double value = 0.5, + Color? backgroundColor, + Animation? valueColor, + this.center, + required this.direction, + required this.shapePath, + }) : super( + key: key, + value: value, + backgroundColor: backgroundColor, + valueColor: valueColor, + ); + + Color _getBackgroundColor(BuildContext context) => backgroundColor ?? Theme.of(context).backgroundColor; + + Color _getValueColor(BuildContext context) => valueColor?.value ?? Theme.of(context).accentColor; + + @override + State createState() => _LiquidCustomProgressIndicatorState(); +} + +class _LiquidCustomProgressIndicatorState extends State { + @override + Widget build(BuildContext context) { + final pathBounds = widget.shapePath.getBounds(); + return SizedBox( + width: pathBounds.width + pathBounds.left, + height: pathBounds.height + pathBounds.top, + child: ClipPath( + clipper: _CustomPathClipper( + path: widget.shapePath, + ), + child: CustomPaint( + painter: _CustomPathPainter( + color: widget._getBackgroundColor(context), + path: widget.shapePath, + ), + child: Stack( + children: [ + Positioned.fill( + left: pathBounds.left, + top: pathBounds.top, + child: Wave( + value: widget.value!, + color: widget._getValueColor(context), + direction: widget.direction, + ), + ), + if (widget.center != null) Center(child: widget.center), + ], + ), + ), + ), + ); + } +} + +class _CustomPathPainter extends CustomPainter { + final Color color; + final Path path; + + _CustomPathPainter({required this.color, required this.path}); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint()..color = color; + canvas.drawPath(path, paint); + } + + @override + bool shouldRepaint(_CustomPathPainter oldDelegate) => color != oldDelegate.color || path != oldDelegate.path; +} + +class _CustomPathClipper extends CustomClipper { + final Path path; + + _CustomPathClipper({required this.path}); + + @override + Path getClip(Size size) { + return path; + } + + @override + bool shouldReclip(CustomClipper oldClipper) => false; +} diff --git a/lib/library/liquid_progress_indicator/liquid_linear_progress_indicator.dart b/lib/library/liquid_progress_indicator/liquid_linear_progress_indicator.dart new file mode 100644 index 0000000..9895af5 --- /dev/null +++ b/lib/library/liquid_progress_indicator/liquid_linear_progress_indicator.dart @@ -0,0 +1,152 @@ +import 'package:flutter/material.dart'; +import 'wave.dart'; + +class LiquidLinearProgressIndicator extends ProgressIndicator { + ///The width of the border, if this is set [borderColor] must also be set. + final double? borderWidth; + + ///The color of the border, if this is set [borderWidth] must also be set. + final Color? borderColor; + + ///The radius of the border. + final double? borderRadius; + + ///The widget to show in the center of the progress indicator. + final Widget? center; + + ///The direction the liquid travels. + final Axis direction; + + LiquidLinearProgressIndicator({ + Key? key, + double value = 0.5, + Color? backgroundColor, + Animation? valueColor, + this.borderWidth, + this.borderColor, + this.borderRadius, + this.center, + this.direction = Axis.horizontal, + }) : super( + key: key, + value: value, + backgroundColor: backgroundColor, + valueColor: valueColor, + ) { + if (borderWidth != null && borderColor == null || borderColor != null && borderWidth == null) { + throw ArgumentError("borderWidth and borderColor should both be set."); + } + } + + Color _getBackgroundColor(BuildContext context) => backgroundColor ?? Theme.of(context).backgroundColor; + + Color _getValueColor(BuildContext context) => valueColor?.value ?? Theme.of(context).accentColor; + + @override + State createState() => _LiquidLinearProgressIndicatorState(); +} + +class _LiquidLinearProgressIndicatorState extends State { + @override + Widget build(BuildContext context) { + return ClipPath( + clipper: _LinearClipper( + radius: widget.borderRadius!, + ), + child: CustomPaint( + painter: _LinearPainter( + color: widget._getBackgroundColor(context), + radius: widget.borderRadius!, + ), + foregroundPainter: _LinearBorderPainter( + color: widget.borderColor!, + width: widget.borderWidth!, + radius: widget.borderRadius!, + ), + child: Stack( + children: [ + Wave( + value: widget.value!, + color: widget._getValueColor(context), + direction: widget.direction, + ), + if (widget.center != null) Center(child: widget.center), + ], + ), + ), + ); + } +} + +class _LinearPainter extends CustomPainter { + final Color color; + final double radius; + + _LinearPainter({required this.color, required this.radius}); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint()..color = color; + canvas.drawRRect( + RRect.fromRectAndRadius( + Rect.fromLTWH(0, 0, size.width, size.height), + Radius.circular(radius), + ), + paint); + } + + @override + bool shouldRepaint(_LinearPainter oldDelegate) => color != oldDelegate.color; +} + +class _LinearBorderPainter extends CustomPainter { + final Color color; + final double width; + final double? radius; + + _LinearBorderPainter({ + required this.color, + required this.width, + required this.radius, + }); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = color + ..style = PaintingStyle.stroke + ..strokeWidth = width; + final alteredRadius = radius ?? 0; + canvas.drawRRect( + RRect.fromRectAndRadius( + Rect.fromLTWH(width / 2, width / 2, size.width - width, size.height - width), + Radius.circular(alteredRadius - width), + ), + paint); + } + + @override + bool shouldRepaint(_LinearBorderPainter oldDelegate) => + color != oldDelegate.color || width != oldDelegate.width || radius != oldDelegate.radius; +} + +class _LinearClipper extends CustomClipper { + final double? radius; + + _LinearClipper({required this.radius}); + + @override + Path getClip(Size size) { + final path = Path() + ..addRRect( + RRect.fromRectAndRadius( + Rect.fromLTWH(0, 0, size.width, size.height), + Radius.circular(radius ?? 0), + ), + ); + return path; + } + + @override + bool shouldReclip(CustomClipper oldClipper) => false; +} diff --git a/lib/library/liquid_progress_indicator/liquid_progress_indicator.dart b/lib/library/liquid_progress_indicator/liquid_progress_indicator.dart new file mode 100644 index 0000000..98bd91e --- /dev/null +++ b/lib/library/liquid_progress_indicator/liquid_progress_indicator.dart @@ -0,0 +1,5 @@ +library liquid_progress_indicator; + +export 'liquid_circular_progress_indicator.dart'; +//export 'liquid_linear_progress_indicator.dart'; +//export 'liquid_custom_progress_indicator.dart'; diff --git a/lib/library/liquid_progress_indicator/wave.dart b/lib/library/liquid_progress_indicator/wave.dart new file mode 100644 index 0000000..fa41c56 --- /dev/null +++ b/lib/library/liquid_progress_indicator/wave.dart @@ -0,0 +1,114 @@ +import 'dart:math' as math; + +import 'package:flutter/material.dart'; + +class Wave extends StatefulWidget { + final double value; + final Color color; + final Axis direction; + + const Wave({ + Key? key, + required this.value, + required this.color, + required this.direction, + }) : super(key: key); + + @override + _WaveState createState() => _WaveState(); +} + +class _WaveState extends State with SingleTickerProviderStateMixin { + late AnimationController _animationController; + + @override + void initState() { + super.initState(); + + _animationController = AnimationController( + vsync: this, + duration: Duration(seconds: 2), + ); + _animationController.repeat(); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOut, + ), + builder: (context, child) => ClipPath( + child: Container( + color: widget.color, + ), + clipper: _WaveClipper( + animationValue: _animationController.value, + value: widget.value, + direction: widget.direction, + ), + ), + ); + } +} + +class _WaveClipper extends CustomClipper { + final double animationValue; + final double value; + final Axis direction; + + _WaveClipper({ + required this.animationValue, + required this.value, + required this.direction, + }); + + @override + Path getClip(Size size) { + if (direction == Axis.horizontal) { + Path path = Path() + ..addPolygon(_generateHorizontalWavePath(size), false) + ..lineTo(0.0, size.height) + ..lineTo(0.0, 0.0) + ..close(); + return path; + } + + Path path = Path() + ..addPolygon(_generateVerticalWavePath(size), false) + ..lineTo(size.width, size.height) + ..lineTo(0.0, size.height) + ..close(); + return path; + } + + List _generateHorizontalWavePath(Size size) { + final waveList = []; + for (int i = -2; i <= size.height.toInt() + 2; i++) { + final waveHeight = (size.width / 20); + final dx = math.sin((animationValue * 360 - i) % 360 * (math.pi / 180)) * waveHeight + (size.width * value); + waveList.add(Offset(dx, i.toDouble())); + } + return waveList; + } + + List _generateVerticalWavePath(Size size) { + final waveList = []; + for (int i = -2; i <= size.width.toInt() + 2; i++) { + final waveHeight = (size.height / 20); + final dy = math.sin((animationValue * 360 - i) % 360 * (math.pi / 180)) * waveHeight + (size.height - (size.height * value)); + waveList.add(Offset(i.toDouble(), dy)); + } + return waveList; + } + + @override + bool shouldReclip(_WaveClipper oldClipper) => animationValue != oldClipper.animationValue; +} diff --git a/lib/main.dart b/lib/main.dart index d68b962..d6fcc8a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:aitrainer_app/bloc/test_set_execute/test_set_execute_bloc.dart'; import 'package:aitrainer_app/push_notifications.dart'; import 'package:aitrainer_app/repository/customer_repository.dart'; @@ -35,6 +36,7 @@ import 'package:aitrainer_app/view/test_set_edit.dart'; import 'package:aitrainer_app/view/test_set_execute.dart'; import 'package:aitrainer_app/view/test_set_new.dart'; import 'package:aitrainer_app/widgets/home.dart'; +import 'package:aitrainer_app/library/facebook_app_events/facebook_app_events.dart'; import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:firebase_analytics/observer.dart'; import 'package:flurry/flurry.dart'; @@ -56,6 +58,7 @@ import 'bloc/menu/menu_bloc.dart'; import 'bloc/session/session_bloc.dart'; import 'bloc/settings/settings_bloc.dart'; import 'bloc/timer/timer_bloc.dart'; +import 'model/cache.dart'; const dsn = 'https://5fac40cbfcfb4c15aa80c7a8638d7310@o418565.ingest.sentry.io/5322520'; @@ -84,11 +87,11 @@ Future _reportError(dynamic error, dynamic stackTrace) async { } print('Reporting to Sentry.io...'); - - final sentryId = await Sentry.captureException( - error, - stackTrace: stackTrace, - ); + final String customerId = Cache().userLoggedIn != null ? Cache().userLoggedIn!.customerId.toString() : "0"; + final String platform = Platform.isAndroid ? "Android" : "iOS"; + final String version = Cache().packageInfo != null ? Cache().packageInfo!.version + "+" + Cache().packageInfo!.buildNumber : ""; + final sentryId = + await Sentry.captureException(error, stackTrace: stackTrace, hint: "Platform: $platform, Version: $version, User: $customerId"); print('Capture exception result : SentryId : $sentryId'); } @@ -185,10 +188,12 @@ Future initFlurry() async { } class WorkoutTestApp extends StatelessWidget { + static final facebookAppEvents = FacebookAppEvents(); @override Widget build(BuildContext context) { SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); final FirebaseAnalytics analytics = FirebaseAnalytics(); + //facebookAppEvents.setAdvertiserTracking(enabled: true); initFlurry(); PushNotificationsManager().init(); return MaterialApp( diff --git a/lib/model/cache.dart b/lib/model/cache.dart index 1d9f775..ee029f5 100644 --- a/lib/model/cache.dart +++ b/lib/model/cache.dart @@ -191,6 +191,7 @@ class Cache with Logging { DateTime savedPlanDate; savedPlanDate = format.parse(savedPlanDateString); + print("Saved plan: $savedPlanDate"); final DateTime now = DateTime.now(); final DateTime added = savedPlanDate.add(Duration(days: 1)); diff --git a/lib/model/exercise_ability.dart b/lib/model/exercise_ability.dart index 1919109..93477b8 100644 --- a/lib/model/exercise_ability.dart +++ b/lib/model/exercise_ability.dart @@ -3,7 +3,7 @@ enum ExerciseAbility { oneRepMax, endurance, running, mini_test_set, paralell_te extension ExerciseAbilityExt on ExerciseAbility { String enumToString() => this.toString().split(".").last; bool equalsTo(ExerciseAbility ability) => this.toString() == ability.toString(); - bool equalsStringTo(String ability) => this.toString() == ability; + bool equalsStringTo(String ability) => this.enumToString() == ability; String get description { switch (this) { case ExerciseAbility.endurance: diff --git a/lib/model/exercise_plan_template.dart b/lib/model/exercise_plan_template.dart index 96ab286..553aa92 100644 --- a/lib/model/exercise_plan_template.dart +++ b/lib/model/exercise_plan_template.dart @@ -22,7 +22,14 @@ class ExercisePlanTemplate { } if (json['details'] != null && (json['details']).length > 0) { - List details = json['details']; + final List details = json['details']; + details.sort((a, b) { + if (a['sort'] == null || b['sort'] == null) { + return a['exercisePlanTemplateDetailId'] < b['exercisePlanTemplateDetailId'] ? -1 : 1; + } else { + return a['sort'] < b['sort'] ? -1 : 1; + } + }); details.forEach((element) { exerciseTypes.add(element['exerciseTypeId']); }); diff --git a/lib/util/common.dart b/lib/util/common.dart index ef08ff6..e3f7d60 100644 --- a/lib/util/common.dart +++ b/lib/util/common.dart @@ -138,4 +138,8 @@ mixin Common { ), ); } + + static normalizeDecimal(String value) { + return value.replaceFirst(",", "."); + } } diff --git a/lib/view/customer_bodytype_animation.dart b/lib/view/customer_bodytype_animation.dart index c220234..ad90ae5 100644 --- a/lib/view/customer_bodytype_animation.dart +++ b/lib/view/customer_bodytype_animation.dart @@ -11,7 +11,7 @@ import 'package:aitrainer_app/widgets/dialog_html.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:google_fonts/google_fonts.dart'; -import 'package:liquid_progress_indicator/liquid_progress_indicator.dart'; +import 'package:aitrainer_app/library/liquid_progress_indicator/liquid_custom_progress_indicator.dart'; import 'package:modal_progress_hud_nsn/modal_progress_hud_nsn.dart'; import 'package:rainbow_color/rainbow_color.dart'; @@ -598,9 +598,9 @@ class _AnimatedLiquidCustomProgressIndicatorState extends State<_AnimatedLiquidC vsync: this, duration: Duration(seconds: 3), )..addListener(() { - setState(() { + /* setState(() { print("build animation state change"); - }); + }); */ }); _animationController.forward(); @@ -608,7 +608,7 @@ class _AnimatedLiquidCustomProgressIndicatorState extends State<_AnimatedLiquidC @override void initState() { - buildAnimation(); + //buildAnimation(); super.initState(); } diff --git a/lib/view/customer_fitness_page.dart b/lib/view/customer_fitness_page.dart index 6921754..b626c7d 100644 --- a/lib/view/customer_fitness_page.dart +++ b/lib/view/customer_fitness_page.dart @@ -14,7 +14,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:google_fonts/google_fonts.dart'; import '../bloc/customer_change/customer_change_bloc.dart'; -import '../library/dropdown_search.dart'; +import '../library/dropdown_search/dropdown_search.dart'; // ignore: must_be_immutable class CustomerFitnessPage extends StatefulWidget { diff --git a/lib/view/exercise_plan_custom_detail_add_page.dart b/lib/view/exercise_plan_custom_detail_add_page.dart index dda3e2f..0a67620 100644 --- a/lib/view/exercise_plan_custom_detail_add_page.dart +++ b/lib/view/exercise_plan_custom_detail_add_page.dart @@ -136,7 +136,7 @@ class _ExercisePlanDetailAddPage extends State with T final bool weightVisible = bloc.exercisePlanRepository.getActualPlanDetail()!.exerciseType!.unitQuantityUnit != null; String summary = bloc.serie!.toStringAsFixed(0) + " x " + bloc.quantity.toStringAsFixed(0); if (bloc.quantityUnit > 0) { - summary += " x " + bloc.quantityUnit.toStringAsFixed(0) + " kg"; + summary += " x " + bloc.quantityUnit.toStringAsFixed(1) + " kg"; } final String unit = bloc.exercisePlanRepository.getActualPlanDetail()!.exerciseType!.unit; return Form( @@ -237,11 +237,15 @@ class _ExercisePlanDetailAddPage extends State with T ), ), focusNode: _nodeText3, - initialValue: bloc.quantityUnit.toStringAsFixed(0), + initialValue: bloc.quantityUnit.toStringAsFixed(1), keyboardType: TextInputType.numberWithOptions(decimal: true), style: GoogleFonts.archivoBlack(fontSize: 60, color: Colors.yellow[200]), onChanged: (value) => { - if (value.isNotEmpty) {bloc.add(ExercisePlanCustomAddChangeQuantityUnit(quantity: double.parse(value)))} + if (value.isNotEmpty) + { + value = value.replaceFirst(",", "."), + bloc.add(ExercisePlanCustomAddChangeQuantityUnit(quantity: double.parse(value))) + } }) : Offstage(), diff --git a/lib/view/test_set_edit.dart b/lib/view/test_set_edit.dart index 02720e9..70ab97f 100644 --- a/lib/view/test_set_edit.dart +++ b/lib/view/test_set_edit.dart @@ -3,6 +3,7 @@ import 'dart:collection'; import 'package:aitrainer_app/bloc/menu/menu_bloc.dart'; import 'package:aitrainer_app/bloc/test_set_edit/test_set_edit_bloc.dart'; import 'package:aitrainer_app/library/custom_icon_icons.dart'; +import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/model/exercise_type.dart'; import 'package:aitrainer_app/model/workout_menu_tree.dart'; import 'package:aitrainer_app/util/trans.dart'; @@ -10,6 +11,7 @@ import 'package:aitrainer_app/widgets/app_bar.dart'; import 'package:aitrainer_app/widgets/dialog_common.dart'; import 'package:aitrainer_app/widgets/menu_image.dart'; import 'package:carousel_slider/carousel_slider.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -25,6 +27,7 @@ class TestSetEdit extends StatelessWidget with Trans { // ignore: close_sinks final MenuBloc menuBloc = BlocProvider.of(context); late TestSetEditBloc? bloc; + final bool activeExercisePlan = Cache().activeExercisePlan != null; setContext(context); return Scaffold( @@ -65,24 +68,39 @@ class TestSetEdit extends StatelessWidget with Trans { ); }))), floatingActionButton: FloatingActionButton.extended( - onPressed: () => showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return DialogCommon( - title: "Start!", - descriptions: t("Enjoy the exercises, good luck with the testing!"), - text: "OK", - onTap: () => { - Navigator.of(context).pop(), - if (bloc != null) - { - bloc!.add(TestSetEditSubmit()), - } - }, - onCancel: () => {Navigator.of(context).pop()}, - ); - }), + onPressed: () { + if (activeExercisePlan) { + showCupertinoDialog( + useRootNavigator: true, + context: context, + builder: (_) => CupertinoAlertDialog( + title: Text(t("You have an active Test Set!") + "\n" + Cache().activeExercisePlan!.name), + content: Column(children: [ + Divider(), + Text(t("Do you want to override it?"), style: GoogleFonts.inter(color: Colors.black, fontSize: 16)), + ]), + actions: [ + TextButton( + child: Text(t("No, bring me there"), textAlign: TextAlign.center), + onPressed: () => { + Navigator.pop(context), + Navigator.pop(context), + Navigator.of(context).pushNamed("testSetExecute"), + }, + ), + TextButton( + child: Text(t("Yes")), + onPressed: () { + Navigator.pop(context); + startTrainingDialog(bloc); + }, + ) + ], + )); + } else { + startTrainingDialog(bloc); + } + }, backgroundColor: Colors.orange[800], icon: Icon(CustomIcon.clock), label: Text( @@ -93,6 +111,27 @@ class TestSetEdit extends StatelessWidget with Trans { ); } + void startTrainingDialog(TestSetEditBloc? bloc) { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return DialogCommon( + title: "Start!", + descriptions: t("Enjoy the exercises, good luck with the testing!"), + text: "OK", + onTap: () => { + Navigator.of(context).pop(), + if (bloc != null) + { + bloc.add(TestSetEditSubmit()), + } + }, + onCancel: () => {Navigator.of(context).pop()}, + ); + }); + } + Widget getTestSetWidget(TestSetEditBloc bloc, String templateName) { return Container( padding: EdgeInsets.only(left: 10, right: 10), @@ -155,97 +194,95 @@ class TestSetEdit extends StatelessWidget with Trans { ])); } - List imageSliders(List alternatives, MenuBloc menuBloc, WorkoutMenuTree workoutTree, TestSetEditBloc bloc) { + List imageSliders(int index, WorkoutMenuTree? workoutTree, TestSetEditBloc bloc) { final List list = []; - if (bloc.exercisePlanDetails[workoutTree.exerciseTypeId] == null) { - list.add(Container( - padding: EdgeInsets.only(top: 25, bottom: 25), - child: ClipRRect( - borderRadius: BorderRadius.circular(24.0), - child: Container( - color: Colors.red[600], - child: Center( - child: Text( - "X", - style: GoogleFonts.archivoBlack( - color: Colors.white, - fontSize: 80, - ), - ), - ))))); - list.add(getImageStack(workoutTree, menuBloc, bloc)); - } else { - ExerciseType exerciseType = bloc.exercisePlanDetails[workoutTree.exerciseTypeId]; + bool deleted = workoutTree == null; + bloc.displayPlanDetails[index].forEach((value) { + ExerciseType alternative = value; - final WorkoutMenuTree actualWorkoutTree = bloc.menuBloc.menuTreeRepository.getMenuItemByExerciseTypeId(exerciseType.exerciseTypeId)!; - list.add(getImageStack(actualWorkoutTree, menuBloc, bloc)); - } + final WorkoutMenuTree? workoutTreeAlternative = + bloc.menuBloc.menuTreeRepository.getMenuItemByExerciseTypeId(alternative.exerciseTypeId); - alternatives.forEach((element) { - if (workoutTree.exerciseTypeId != element.exerciseTypeId) { - list.add(getImageStack(element, menuBloc, bloc)); - } + list.add(getImageStack(workoutTreeAlternative!, bloc, index, deleted)); }); return list; } - Stack getImageStack(WorkoutMenuTree element, MenuBloc menuBloc, TestSetEditBloc bloc) { + Stack getImageStack(WorkoutMenuTree element, TestSetEditBloc bloc, int index, bool deleted) { return Stack(alignment: Alignment.topRight, children: [ Stack(alignment: Alignment.bottomLeft, children: [ - MenuImage(imageName: element.imageName, workoutTreeId: element.id), + deleted + ? Opacity(opacity: 0.2, child: MenuImage(imageName: element.imageName, workoutTreeId: element.id)) + : MenuImage(imageName: element.imageName, workoutTreeId: element.id), Container( padding: EdgeInsets.only(left: 15, bottom: 15, right: 15), child: Text( element.name, maxLines: 4, - style: GoogleFonts.archivoBlack(color: element.color, fontSize: 16, height: 1.1), + style: GoogleFonts.archivoBlack(color: deleted ? element.color.withOpacity(0.2) : element.color, fontSize: 16, height: 1.1), ), ), ]), - Container( - width: 40, - height: 40, - child: ClipRRect( - borderRadius: BorderRadius.circular(24.0), - child: GestureDetector( - onTap: () => bloc.add(TestSetEditDeleteExerciseType(exerciseTypeId: element.exerciseTypeId)), - child: Container( - color: Colors.red[600], - child: Center( - child: Text( - "X", - style: GoogleFonts.archivoBlack( - color: Colors.white, - fontSize: 28, - ), - ), - ))))) + deleted == false + ? Container( + width: 40, + height: 40, + child: ClipRRect( + borderRadius: BorderRadius.circular(24.0), + child: GestureDetector( + onTap: () => bloc.add(TestSetEditDeleteExerciseType(indexKey: index)), + child: Container( + color: Colors.red[600], + child: Center( + child: Text( + "X", + style: GoogleFonts.archivoBlack( + color: Colors.white, + fontSize: 28, + ), + ), + ))))) + : Container( + width: 40, + height: 40, + child: ClipRRect( + borderRadius: BorderRadius.circular(24.0), + child: GestureDetector( + onTap: () => bloc.add(TestSetEditAddExerciseType(indexKey: index)), + child: Container( + color: Colors.yellow[700], + child: Center( + child: Icon( + Icons.undo_sharp, + size: 40, + color: Colors.white, + )), + )))) ]); } List getExerciseTypeWidgets(TestSetEditBloc bloc) { final List widgets = []; - bloc.exerciseTypes.forEach((element) { - final WorkoutMenuTree? workoutTree = bloc.menuBloc.menuTreeRepository.getMenuItemByExerciseTypeId(element.exerciseTypeId); - - final List? alternativeMenuItems = bloc.menuBloc.menuTreeRepository.getWorkoutTreeAlternatives(workoutTree); - if (workoutTree != null && alternativeMenuItems != null) { - final Widget widget = CarouselSlider( - options: CarouselOptions( - viewportFraction: 0.80, - reverse: false, - enableInfiniteScroll: false, - autoPlay: false, - aspectRatio: 2, - enlargeCenterPage: true, - onPageChanged: (index, reason) => - bloc.add(TestSetEditChangeExerciseType(index: index, exerciseTypeId: element.exerciseTypeId)), - enlargeStrategy: CenterPageEnlargeStrategy.scale), - items: imageSliders(alternativeMenuItems, bloc.menuBloc, workoutTree, bloc), - ); - widgets.add(widget); + bloc.exercisePlanDetails.forEach((key, element) { + WorkoutMenuTree? workoutTree; + if (element != null) { + workoutTree = bloc.menuBloc.menuTreeRepository.getMenuItemByExerciseTypeId(element.exerciseTypeId); } + + final Widget widget = CarouselSlider( + options: CarouselOptions( + viewportFraction: 0.80, + reverse: false, + enableInfiniteScroll: false, + autoPlay: false, + aspectRatio: 2, + enlargeCenterPage: true, + onPageChanged: (index, reason) => bloc.add(TestSetEditChangeExerciseType(index: index, indexKey: key)), + enlargeStrategy: CenterPageEnlargeStrategy.scale), + items: imageSliders(key, workoutTree, bloc), + ); + widgets.add(widget); }); return widgets; } diff --git a/lib/view/test_set_execute.dart b/lib/view/test_set_execute.dart index db38247..cee48dd 100644 --- a/lib/view/test_set_execute.dart +++ b/lib/view/test_set_execute.dart @@ -3,7 +3,6 @@ import 'dart:collection'; import 'package:aitrainer_app/bloc/menu/menu_bloc.dart'; import 'package:aitrainer_app/bloc/test_set_execute/test_set_execute_bloc.dart'; import 'package:aitrainer_app/library/custom_icon_icons.dart'; -import 'package:aitrainer_app/library/fade_in.dart'; import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/model/exercise_plan_detail.dart'; import 'package:aitrainer_app/repository/exercise_repository.dart'; @@ -32,7 +31,7 @@ class TestSetExecute extends StatelessWidget with Trans { executeBloc.add(TestSetExecuteLoad()); setContext(context); return Scaffold( - appBar: AppBarNav(depth: 1), + appBar: AppBarNav(depth: 0), body: Container( padding: EdgeInsets.all(20), decoration: BoxDecoration( @@ -349,11 +348,7 @@ class _ExerciseTileState extends State with Trans { void initState() { animation.start(); animation.addStatusListener((status) { - if (status == AnimationStatus.completed) { - setState(() { - print("animation state completed"); - }); - } + if (status == AnimationStatus.completed) {} }); super.initState(); @@ -407,7 +402,6 @@ class _ExerciseTileState extends State with Trans { final bool done = state.equalsTo(ExercisePlanDetailState.finished); final String countSerie = widget.exercisePlanDetail.exercises == null ? "1" : (widget.exercisePlanDetail.exercises!.length).toString(); final String serie = widget.exercisePlanDetail.exerciseType!.unitQuantityUnit == null ? "/1" : "/4"; - setContext(context); return Container( color: Colors.transparent, diff --git a/lib/widgets/app_bar_progress.dart b/lib/widgets/app_bar_progress.dart index d1ce76c..03a0d41 100644 --- a/lib/widgets/app_bar_progress.dart +++ b/lib/widgets/app_bar_progress.dart @@ -3,7 +3,7 @@ import 'package:aitrainer_app/util/common.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:liquid_progress_indicator/liquid_progress_indicator.dart'; +import 'package:aitrainer_app/library/liquid_progress_indicator/liquid_linear_progress_indicator.dart'; class AppBarProgress extends StatefulWidget implements PreferredSizeWidget { final int max; diff --git a/lib/widgets/bmi_widget.dart b/lib/widgets/bmi_widget.dart index 7d5c68f..ea96a64 100644 --- a/lib/widgets/bmi_widget.dart +++ b/lib/widgets/bmi_widget.dart @@ -258,10 +258,11 @@ class _BMIState extends State with Trans { ), ), initialValue: widget.exerciseBloc.height.toStringAsFixed(0), - keyboardType: TextInputType.numberWithOptions(decimal: true), + keyboardType: TextInputType.numberWithOptions(decimal: false), textInputAction: TextInputAction.done, style: GoogleFonts.archivoBlack(fontSize: 20, color: Colors.yellow[300]), - onChanged: (value) => {widget.exerciseBloc.add(ExerciseNewHeightChange(value: double.parse(value)))}), + onChanged: (value) => + {value = value.replaceFirst(",", "."), widget.exerciseBloc.add(ExerciseNewHeightChange(value: double.parse(value)))}), ); } else { return Container(); @@ -294,11 +295,12 @@ class _BMIState extends State with Trans { borderSide: BorderSide(color: Colors.white12, width: 0.4), ), ), - initialValue: widget.exerciseBloc.weight.toStringAsFixed(0), + initialValue: widget.exerciseBloc.weight.toStringAsFixed(1), keyboardType: TextInputType.numberWithOptions(decimal: true), textInputAction: TextInputAction.done, style: GoogleFonts.archivoBlack(fontSize: 20, color: Colors.yellow[300]), - onChanged: (value) => {widget.exerciseBloc.add(ExerciseNewWeightChange(value: double.parse(value)))}, + onChanged: (value) => + {value = value.replaceFirst(",", "."), widget.exerciseBloc.add(ExerciseNewWeightChange(value: double.parse(value)))}, ), ), IconButton( diff --git a/lib/widgets/bmr_widget.dart b/lib/widgets/bmr_widget.dart index be6ca0b..bc7382a 100644 --- a/lib/widgets/bmr_widget.dart +++ b/lib/widgets/bmr_widget.dart @@ -3,7 +3,7 @@ import 'package:aitrainer_app/bloc/exercise_new/exercise_new_bloc.dart'; import 'package:aitrainer_app/util/app_localization.dart'; import 'package:aitrainer_app/model/fitness_state.dart'; import 'package:aitrainer_app/util/trans.dart'; -import 'package:aitrainer_app/library/dropdown_search.dart'; +import 'package:aitrainer_app/library/dropdown_search/dropdown_search.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; @@ -213,10 +213,11 @@ class _BMRState extends State with Trans { ), ), initialValue: widget.exerciseBloc.height.toStringAsFixed(0), - keyboardType: TextInputType.numberWithOptions(decimal: true), + keyboardType: TextInputType.numberWithOptions(decimal: false), textInputAction: TextInputAction.done, style: GoogleFonts.archivoBlack(fontSize: 20, color: Colors.yellow[300]), - onChanged: (value) => {widget.exerciseBloc.add(ExerciseNewHeightChange(value: double.parse(value)))}), + onChanged: (value) => + {value = value.replaceFirst(",", "."), widget.exerciseBloc.add(ExerciseNewHeightChange(value: double.parse(value)))}), ); } else { return Container(); @@ -361,11 +362,12 @@ class _BMRState extends State with Trans { borderSide: BorderSide(color: Colors.white12, width: 0.4), ), ), - initialValue: widget.exerciseBloc.weight.toStringAsFixed(0), + initialValue: widget.exerciseBloc.weight.toStringAsFixed(1), keyboardType: TextInputType.numberWithOptions(decimal: true), textInputAction: TextInputAction.done, style: GoogleFonts.archivoBlack(fontSize: 20, color: Colors.yellow[300]), - onChanged: (value) => {widget.exerciseBloc.add(ExerciseNewWeightChange(value: double.parse(value)))}, + onChanged: (value) => + {value = value.replaceFirst(",", "."), widget.exerciseBloc.add(ExerciseNewWeightChange(value: double.parse(value)))}, ), ), IconButton( diff --git a/lib/widgets/bottom_bar_multiple_exercises.dart b/lib/widgets/bottom_bar_multiple_exercises.dart index 7195845..184bbbf 100644 --- a/lib/widgets/bottom_bar_multiple_exercises.dart +++ b/lib/widgets/bottom_bar_multiple_exercises.dart @@ -107,6 +107,7 @@ class _BottomBarMultipleExercisesState extends State } List getChildren(TestSetExecuteBloc bloc) { + print("Isset? ${widget.isSet}"); final List list = []; if (!widget.isSet! && !bloc.hasBegun()) { list.add(GestureDetector( @@ -174,7 +175,7 @@ class _BottomBarMultipleExercisesState extends State color: Colors.transparent, ), Text( - ret['message2'], + t(ret['message2']), style: GoogleFonts.inter(color: Colors.grey[800]), ), ]))), diff --git a/lib/widgets/exercise_save.dart b/lib/widgets/exercise_save.dart index 707a371..199d389 100644 --- a/lib/widgets/exercise_save.dart +++ b/lib/widgets/exercise_save.dart @@ -227,27 +227,6 @@ class _ExerciseSaveState extends State with Trans { Divider( color: Colors.transparent, ), - /* TextButton( - onPressed: () { - widget.onSubmit(); - /* showDialog( - context: context, - builder: (BuildContext context) { - return Victory( - victory: true, - ); - }); */ - }, - child: Stack( - alignment: Alignment.center, - children: [ - Image.asset('asset/icon/gomb_orange_c.png', width: 140, height: 60), - Text( - t("Save"), - style: TextStyle(fontSize: 16, color: Colors.white), - ), - ], - )), */ ]), ))); } @@ -276,7 +255,10 @@ class _ExerciseSaveState extends State with Trans { keyboardType: TextInputType.numberWithOptions(decimal: true), textInputAction: TextInputAction.done, style: GoogleFonts.archivoBlack(fontSize: 80, color: Colors.yellow[300]), - onChanged: (value) => widget.onUnitQuantityChanged!(value)), + onChanged: (value) => { + value = value.replaceFirst(",", "."), + widget.onUnitQuantityChanged!(value), + }), ])); } return row; diff --git a/lib/widgets/input_dialog_widget.dart b/lib/widgets/input_dialog_widget.dart index 498eaa1..a1126d8 100644 --- a/lib/widgets/input_dialog_widget.dart +++ b/lib/widgets/input_dialog_widget.dart @@ -152,11 +152,11 @@ class _InputDialogState extends State> with Trans { borderSide: BorderSide(color: Colors.white12, width: 0.4), ), ), - initialValue: widget.initialValue.toStringAsFixed(0), + initialValue: widget.initialValue.toStringAsFixed(1), keyboardType: TextInputType.numberWithOptions(decimal: true), textInputAction: TextInputAction.done, style: GoogleFonts.archivoBlack(fontSize: 20, color: Colors.yellow[300]), - onChanged: (value) => this.inputValue = double.parse(value), + onChanged: (value) => {value = value.replaceFirst(",", "."), this.inputValue = double.parse(value)}, ), ), SizedBox( diff --git a/lib/widgets/menu_page_widget.dart b/lib/widgets/menu_page_widget.dart index 32f37d2..266c10e 100644 --- a/lib/widgets/menu_page_widget.dart +++ b/lib/widgets/menu_page_widget.dart @@ -279,9 +279,13 @@ class _MenuPageWidgetState extends State with Trans, Logging { automaticallyImplyLeading: false, backgroundColor: Colors.transparent, title: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - SizedBox( - width: 10, - ), + !activeExercisePlan + ? SizedBox( + width: 50, + ) + : SizedBox( + width: 10, + ), GestureDetector( onTap: () => { showDialog( @@ -318,18 +322,22 @@ class _MenuPageWidgetState extends State with Trans, Logging { } }, ), - activeExercisePlan + Cache().activeExercisePlan != null ? GestureDetector( onTap: () => showDialog( context: context, builder: (BuildContext context) { return DialogCommon( title: t("You have an active Test Set!"), - descriptions: t("Press OK to continue"), + descriptions: Cache().activeExercisePlan!.name, + description2: t("Press OK to continue"), text: "OK", onTap: () => { Navigator.of(context).pop(), - Navigator.of(context).pushNamed("testSetExecute"), + if (Cache().activeExercisePlan != null) + { + Navigator.of(context).pushNamed("testSetExecute"), + } }, onCancel: () => { Navigator.of(context).pop(), @@ -348,6 +356,11 @@ class _MenuPageWidgetState extends State with Trans, Logging { ); })) : Offstage(), + activeExercisePlan + ? SizedBox( + width: 10, + ) + : Offstage(), ])); return sliverAppBar; } diff --git a/lib/widgets/menu_search_bar.dart b/lib/widgets/menu_search_bar.dart index cf70d49..60893de 100644 --- a/lib/widgets/menu_search_bar.dart +++ b/lib/widgets/menu_search_bar.dart @@ -2,8 +2,7 @@ import 'dart:async'; import 'package:aitrainer_app/model/workout_menu_tree.dart'; import 'package:aitrainer_app/util/trans.dart'; -import 'package:aitrainer_app/library/dropdown_search.dart'; -//import 'package:aitrainer_app/library/dropdown_search.dart'; +import 'package:aitrainer_app/library/dropdown_search/dropdown_search.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; diff --git a/lib/widgets/size_widget.dart b/lib/widgets/size_widget.dart index 1cd6b20..a4d058e 100644 --- a/lib/widgets/size_widget.dart +++ b/lib/widgets/size_widget.dart @@ -83,7 +83,12 @@ class _SizeState extends State with Trans { .toDouble() - 45, child: GestureDetector( - onTap: () => onPressed(widget.exerciseBloc.customerRepository.getPropertyByName("Weight")!), + onTap: () => { + if (widget.exerciseBloc.customerRepository.getPropertyByName("Weight") != null) + { + onPressed(widget.exerciseBloc.customerRepository.getPropertyByName("Weight")!), + } + }, child: Image.asset( "asset/image/merleg.png", height: 120, @@ -181,7 +186,6 @@ class _SizeState extends State with Trans { } void onPressed(Property element) { - print(element.propertyName); showDialog( context: context, builder: (context) => InputDialog( diff --git a/pubspec.lock b/pubspec.lock index 63995c4..5f8060c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -630,13 +630,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.4.0" - liquid_progress_indicator: - dependency: "direct main" - description: - name: liquid_progress_indicator - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.2" logging: dependency: transitive description: @@ -846,7 +839,7 @@ packages: name: purchases_flutter url: "https://pub.dartlang.org" source: hosted - version: "3.1.0" + version: "3.2.1" quiver: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 85396ae..2b18ffb 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.10+65 +version: 1.1.13+66 environment: sdk: ">=2.12.0 <3.0.0" @@ -46,10 +46,8 @@ dependencies: flutter_html: ^2.0.0-nullsafety.0 wakelock: ^ 0.4.0 timeline_tile: ^2.0.0 - purchases_flutter: ^3.1.0 - package_info: ^2.0.0 - liquid_progress_indicator: ^0.3.2 - #audioplayer: ^0.8.1 + purchases_flutter: ^3.2.1 + package_info: ^2.0.0 ezanimation: ^0.5.0 confetti: ^0.6.0-nullsafety crypto: ^3.0.0 @@ -66,7 +64,7 @@ dependencies: flutter_facebook_auth: ^3.3.2 google_sign_in: ^5.0.1 apple_sign_in: ^0.1.0 - + smartlook: ^1.0.7 flurry: ^0.0.4