From 71efd3f349f66b5b68d8a4a631b0aaa43475c7cd Mon Sep 17 00:00:00 2001 From: bossanyit Date: Sun, 24 Jan 2021 14:34:13 +0100 Subject: [PATCH] WT1.1.5 In-app-purchase --- android/app/build.gradle | 2 - i18n/en.json | 21 +- i18n/hu.json | 21 +- ios/Flutter/AppFrameworkInfo.plist | 2 +- ios/Podfile | 5 +- ios/Podfile.lock | 20 +- ios/Runner.xcodeproj/project.pbxproj | 16 +- lib/bloc/exercise_new/exercise_new_bloc.dart | 31 +- lib/bloc/exercise_new/exercise_new_event.dart | 7 + .../exercise_plan/exercise_plan_bloc.dart | 1 - lib/bloc/menu/menu_bloc.dart | 2 +- lib/bloc/result/result_bloc.dart | 5 +- lib/bloc/sales/sales_bloc.dart | 68 +++-- lib/bloc/sales/sales_state.dart | 4 + lib/bloc/session/session_bloc.dart | 1 - lib/bloc/settings/settings_bloc.dart | 2 +- lib/main.dart | 18 +- lib/model/cache.dart | 12 +- lib/model/exercise_type.dart | 1 - lib/model/purchase.dart | 2 + lib/repository/customer_repository.dart | 2 +- lib/repository/exercise_repository.dart | 11 + .../exercise_result_repository.dart | 1 - lib/repository/user_repository.dart | 24 +- lib/repository/workout_tree_repository.dart | 5 +- .../customer_exercise_device_service.dart | 2 +- lib/service/firebase_api.dart | 63 ++--- lib/service/purchase.dart | 2 +- lib/util/common.dart | 10 + lib/util/platform_purchase.dart | 265 +++++++++++------- lib/util/purchases.dart | 120 ++++++++ lib/util/session.dart | 2 + lib/view/account.dart | 1 - lib/view/evaluation.dart | 47 +++- lib/view/exercise_log_page.dart | 38 ++- lib/view/exercise_new_page.dart | 12 +- lib/view/mydevelopment_body_page.dart | 6 +- lib/view/mydevelopment_muscle_page.dart | 27 +- lib/view/mydevelopment_page.dart | 6 +- lib/view/myexcercise_plan_page.dart | 20 +- lib/view/sales_page.dart | 49 ++-- lib/view/settings.dart | 19 +- lib/widgets/bmi_widget.dart | 69 +++-- lib/widgets/bmr_widget.dart | 83 +++++- lib/widgets/bottom_nav.dart | 2 - lib/widgets/dialog_premium.dart | 10 +- lib/widgets/home.dart | 2 - lib/widgets/menu_page_widget.dart | 2 - lib/widgets/treeview_parent_widget.dart | 8 +- pubspec.lock | 54 ++-- pubspec.yaml | 9 +- test_driver/service/customer_service.dart | 2 - 52 files changed, 847 insertions(+), 367 deletions(-) create mode 100644 lib/util/purchases.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 43a6c8c..35efa81 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -78,7 +78,5 @@ dependencies { implementation 'com.google.firebase:firebase-analytics:18.0.0' implementation 'com.facebook.android:facebook-login:5.15.3' implementation 'com.android.support:multidex:1.0.3' - def billing_version = "3.0.0" - implementation "com.android.billingclient:billing:$billing_version" } sourceCompatibility = '1.8' \ No newline at end of file diff --git a/i18n/en.json b/i18n/en.json index 8a8815c..b0a4ce5 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -172,10 +172,10 @@ "8. Calf": "8. Calf", "Execute My Selected Training Plan": "Execute My Selected Training Plan", - "Edit My Custom Plan": "Edit My Custom Plan", + "Edit My Custom Plan": "Create My Custom Plan", "Suggested Training Plan": "Suggested Training Plan", "My Special Plan": "My Special Plan", - "Star's Exercise Plan":"Celeb Exercise Plan", + "Training Programs":"Training Programs", "My Trainee's Plan": "My Trainee's Plan", "Execute My Trainee's Training Plan": "Execute My Trainee's Training Plan", @@ -250,11 +250,15 @@ "Done": "Done", "Height":"Height", "Actual Height":"Height", - "Actual Weight":"Weight", - "Based on your weight and height your goal for BMI and weight:":"Based on your weight and height your goal for BMI and weight:", + "Actual Weight":"Bodyweight", + "Bodyweight":"Bodyweight", + "Based on your weight and height your goal for BMI and weight:":"Based on your bodyweight and height your goal for BMI and weight:", "Body Mass Index":"Body Mass Index", + "first step":"first step", + "goal":"goal", "Basal Metabolic Rate":"Basal Metabolic Rate", - "Based on your weight, height and activity your BMR value":"Based on your weight, height and activity this is your daily calory demand.", + "Resting Metabolic Rate":"Resting Metabolic Rate", + "Based on your weight, height and activity your BMR value":"Based on your bodyweight, height and activity this is your daily calory demand.", "Your Sizes":"Your Sizes", "Size Of Your":"Size Of Your", "Please type the following data:":"Please type the following data:", @@ -303,7 +307,10 @@ "Please define your Exercise Plan":"Please define your Exercise Plan", "Go to: 'Training Plan' - 'Edit My Custom Plan'":"Go to: 'Training Plan' - 'Edit My Custom Plan'", - "Jump there »":"Jump there »" - + "Jump there »":"Jump there »", + "Exception: Purchase was not successful":"Purchase was not successful", + "Exception: Purchase was cancelled":"Purchase was cancelled", + "Successful Purchase": "Successful Purchase", + "Now you can use the premium features of WorkoutTest!":"Now you can use the premium features of WorkoutTest!" } \ No newline at end of file diff --git a/i18n/hu.json b/i18n/hu.json index 7ed0c37..688aa77 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -169,10 +169,10 @@ "8. Calf": "8. Vádli", "Execute My Selected Training Plan": "Edzésterv végrehajtása", - "Edit My Custom Plan": "Egyéni edzésterv", + "Edit My Custom Plan": "Egyéni edzésterv létrehozása", "Suggested Training Plan": "Javasolt edzésterv", "My Special Plan": "Speciális edzésterv", - "Star's Exercise Plan": "Sztárok edzésterve", + "Training Programs":"Edzés programok", "My Trainee's Plan" : "Kliensem edzésterve", "Execute My Trainee's Training Plan": "Kliensem edzéstervének végrehajtása", @@ -245,11 +245,15 @@ "Done": "Kész", "Height":"Magasság", "Actual Height":"Magasság", - "Actual Weight":"Tömeg", - "Based on your weight and height your goal for BMI and weight:":"A jelenlegi tömeged és magasságod alapján kiszámoltuk, hogy mennyi legyen a célod a BMI (testtömegindex) és tömeg elérésben:", + "Actual Weight":"Testtömeg", + "Bodyweight":"Testtömeg", + "Based on your weight and height your goal for BMI and weight:":"A jelenlegi testtömeged és magasságod alapján kiszámoltuk, hogy mennyi legyen a célod a BMI (testtömegindex) és tömeg elérésben:", "Body Mass Index":"Testtömegindex", + "first step":"első lépés", + "goal":"cél", "Basal Metabolic Rate":"Alapanyagcsere érték", - "Based on your weight, height and activity your BMR value":"A tömeged, magasságod és aktivitásod alapján megközelítőleg ennyi a napi kalóriaszükségleted.", + "Resting Metabolic Rate":"Minimum energiaszükséglet", + "Based on your weight, height and activity your BMR value":"A testtömeged, magasságod és aktivitásod alapján megközelítőleg ennyi a napi kalóriaszükségleted.", "Your Sizes":"Méreteid", "Size Of Your":"Testméret:", "Please type the following data:":"Kérlek írd be a következő adatot:", @@ -299,6 +303,11 @@ "Please define your Exercise Plan":"Kérlek készíts edzéstervet!", "Go to: 'Training Plan' - 'Edit My Custom Plan'":"Menj a 'Edzéstervem' - 'Egyéni edzésterv' menübe", - "Jump there »":"Vigyél oda »" + "Jump there »":"Vigyél oda »", + + "Exception: Purchase was not successful":"A vásárlás sikertelen volt", + "Exception: Purchase was cancelled":"A vásárlás megszakadt", + "Successful Purchase": "Sikeres vásárlás!", + "Now you can use the premium features of WorkoutTest!":"Most már eléred a WorkoutTest prémium tartalmait." } \ No newline at end of file diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 3158eba..cae6543 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 10.0 + 12.0 diff --git a/ios/Podfile b/ios/Podfile index 1e8c3c9..efd72bd 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' +platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -37,5 +37,8 @@ end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) + target.build_configurations.each do |config| + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0' + end end end diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 3b8901b..f4e1035 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -155,6 +155,14 @@ PODS: - Flutter - PromisesObjC (1.2.11) - Protobuf (3.13.0) + - Purchases (3.9.2): + - PurchasesCoreSwift (= 3.9.2) + - purchases_flutter (2.0.0): + - Flutter + - PurchasesHybridCommon (= 1.5.0) + - PurchasesCoreSwift (3.9.2) + - PurchasesHybridCommon (1.5.0): + - Purchases (= 3.9.2) - shared_preferences (0.0.1): - Flutter - sqflite (0.0.2): @@ -180,6 +188,7 @@ DEPENDENCIES: - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) - google_sign_in (from `.symlinks/plugins/google_sign_in/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`) + - purchases_flutter (from `.symlinks/plugins/purchases_flutter/ios`) - shared_preferences (from `.symlinks/plugins/shared_preferences/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) - video_player (from `.symlinks/plugins/video_player/ios`) @@ -210,6 +219,9 @@ SPEC REPOS: - nanopb - PromisesObjC - Protobuf + - Purchases + - PurchasesCoreSwift + - PurchasesHybridCommon EXTERNAL SOURCES: apple_sign_in: @@ -236,6 +248,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/google_sign_in/ios" path_provider: :path: ".symlinks/plugins/path_provider/ios" + purchases_flutter: + :path: ".symlinks/plugins/purchases_flutter/ios" shared_preferences: :path: ".symlinks/plugins/shared_preferences/ios" sqflite: @@ -282,12 +296,16 @@ SPEC CHECKSUMS: path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c PromisesObjC: 8c196f5a328c2cba3e74624585467a557dcb482f Protobuf: 3dac39b34a08151c6d949560efe3f86134a3f748 + Purchases: d8a798c9c7552fe66b550bf314a143e94ffa70c8 + purchases_flutter: 27f87080055c0fd2cd124c247b10cae75b46e7e1 + PurchasesCoreSwift: ea4eabae180416e580ac60366f41aa1fefec0693 + PurchasesHybridCommon: d9bfb34309db4c9ba82a6f7f3a6275c13befdca7 shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 video_player: 9cc823b1d9da7e8427ee591e8438bfbcde500e6e wakelock: bfc7955c418d0db797614075aabbc58a39ab5107 webview_flutter: d2b4d6c66968ad042ad94cbb791f5b72b4678a96 -PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c +PODFILE CHECKSUM: 28226b39c1afd238c6168e31e2bd3829c3d67530 COCOAPODS: 1.10.0 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 32adb50..b2540af 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ BB69292B2521AF45001FBA4C /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = BB69292A2521AF45001FBA4C /* Launch Screen.storyboard */; }; BB81345024BB4BE10078D9A4 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB81344F24BB4BE10078D9A4 /* GoogleService-Info.plist */; }; BB8D3BFA25A8CBFE00BF29FE /* AuthenticationServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB8D3BF925A8CBFE00BF29FE /* AuthenticationServices.framework */; }; + BB98CEAF25B8867C000724FE /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB98CEAE25B8867C000724FE /* StoreKit.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -50,6 +51,7 @@ BB69292A2521AF45001FBA4C /* Launch Screen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = "Launch Screen.storyboard"; sourceTree = ""; }; BB81344F24BB4BE10078D9A4 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; BB8D3BF925A8CBFE00BF29FE /* AuthenticationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AuthenticationServices.framework; path = System/Library/Frameworks/AuthenticationServices.framework; sourceTree = SDKROOT; }; + BB98CEAE25B8867C000724FE /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; D5EDDC52125075FB9E21AD35 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; F39E6E227EB942E5663A6086 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -59,6 +61,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + BB98CEAF25B8867C000724FE /* StoreKit.framework in Frameworks */, BB8D3BFA25A8CBFE00BF29FE /* AuthenticationServices.framework in Frameworks */, 42B6B159AF35AFB6DE777DFB /* Pods_Runner.framework in Frameworks */, ); @@ -80,6 +83,7 @@ 3ADC50290ED054951FAC1F56 /* Frameworks */ = { isa = PBXGroup; children = ( + BB98CEAE25B8867C000724FE /* StoreKit.framework */, BB8D3BF925A8CBFE00BF29FE /* AuthenticationServices.framework */, 09BD889296C5C90D989820C8 /* Pods_Runner.framework */, ); @@ -384,7 +388,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = SFJJBDCU6Z; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -401,7 +405,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 1.1.3; + MARKETING_VERSION = 1.1.5; PRODUCT_BUNDLE_IDENTIFIER = com.aitrainer.app; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -527,7 +531,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = SFJJBDCU6Z; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -544,7 +548,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 1.1.3; + MARKETING_VERSION = 1.1.5; PRODUCT_BUNDLE_IDENTIFIER = com.aitrainer.app; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -562,7 +566,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = SFJJBDCU6Z; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -579,7 +583,7 @@ "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - MARKETING_VERSION = 1.1.3; + MARKETING_VERSION = 1.1.5; PRODUCT_BUNDLE_IDENTIFIER = com.aitrainer.app; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/lib/bloc/exercise_new/exercise_new_bloc.dart b/lib/bloc/exercise_new/exercise_new_bloc.dart index 83665aa..608a7b6 100644 --- a/lib/bloc/exercise_new/exercise_new_bloc.dart +++ b/lib/bloc/exercise_new/exercise_new_bloc.dart @@ -31,11 +31,15 @@ class ExerciseNewBloc extends Bloc with Logg double bmr = 0; double goalBMI = 0; double goalWeight = 0; + double goalMilestoneBMI = 0; + double goalMilestoneWeight = 0; double bmiAngle = 0; double bmiTop = 0; double bmiLeft = 0; double weight; double height; + double bmrEnergy = 0; + int birthYear; String fitnessLevel; bool changedWeight = false; bool changedSizes = false; @@ -66,6 +70,7 @@ class ExerciseNewBloc extends Bloc with Logg customerRepository.customer = Cache().userLoggedIn; weight = customerRepository.customer.getProperty("Weight"); height = customerRepository.customer.getProperty("Height"); + birthYear = customerRepository.customer.birthYear; fitnessLevel = customerRepository.customer.fitnessLevel; this.isMan = (customerRepository.customer.sex == "m"); } @@ -315,6 +320,7 @@ class ExerciseNewBloc extends Bloc with Logg changedWeight = true; weight = event.value; getBMI(); + getGoalBMI(); getBMR(); yield ExerciseNewReady(); } else if (event is ExerciseNewFitnessLevelChange) { @@ -324,12 +330,20 @@ class ExerciseNewBloc extends Bloc with Logg changedWeight = true; getBMR(); yield ExerciseNewReady(); + } else if (event is ExerciseNewBirthyearChange) { + yield ExerciseNewLoading(); + changedWeight = true; + customerRepository.setBirthYear(event.value.toInt()); + birthYear = event.value; + getBMR(); + yield ExerciseNewReady(); } else if (event is ExerciseNewHeightChange) { yield ExerciseNewLoading(); customerRepository.setHeight(event.value.toInt()); changedWeight = true; height = event.value; getBMI(); + getGoalBMI(); getBMR(); yield ExerciseNewReady(); } else if (event is ExerciseNewSaveWeight) { @@ -390,6 +404,7 @@ class ExerciseNewBloc extends Bloc with Logg //BMR = 655.1 + ( 9.563 × ömeg kg-ban ) + ( 1.85 × magasság cm-ben) − ( 4.676 × életkor évben kifejezve ) bmr = 655.1 + (9.563 * weight) + (1.85 * height) - (4.676 * (year - customerRepository.customer.birthYear)); } + bmrEnergy = bmr; if (customerRepository.customer.fitnessLevel == FitnessState.beginner) { bmr *= 1.2; @@ -424,32 +439,38 @@ class ExerciseNewBloc extends Bloc with Logg this.bmiAngle = (bmi * 90 / 25) - 90; if (bmi < 18.5) { - goalBMI = 19; + goalMilestoneBMI = 19; + goalBMI = 21.75; this.bmiTop = 99 * distortionHeight; this.bmiLeft = 77 * distortionWidth; bmiAngle = -62; } else if (bmi > 18.5 && bmi < 25) { - goalBMI = this.bmi; + goalBMI = 21.75; + goalMilestoneBMI = 21.75; this.bmiTop = 48 * distortionHeight; this.bmiLeft = 130 * distortionWidth; bmiAngle = -23; } else if (bmi < 30 && bmi > 24.9) { - goalBMI = 24; + goalMilestoneBMI = 24; + goalBMI = 21.75; this.bmiTop = 40.0 * distortionHeight; this.bmiLeft = 184.0 * distortionWidth; bmiAngle = 7.2; } else if (bmi < 34.9 && 29.9 < bmi) { - goalBMI = 29; + goalMilestoneBMI = 29; + goalBMI = 24; bmiTop = 48 * distortionHeight; bmiLeft = 211 * distortionWidth; } else if (bmi > 35) { - goalBMI = 34; + goalMilestoneBMI = 34; + goalBMI = 24; bmiTop = 94 * distortionHeight; bmiLeft = 260 * distortionWidth; bmiAngle = 59; } this.goalWeight = goalBMI * (height * height / 10000); + this.goalMilestoneWeight = goalMilestoneBMI * (height * height / 10000); //print("Angle: " + bmiAngle.toStringAsFixed(1)); diff --git a/lib/bloc/exercise_new/exercise_new_event.dart b/lib/bloc/exercise_new/exercise_new_event.dart index 15013c3..bdb01cf 100644 --- a/lib/bloc/exercise_new/exercise_new_event.dart +++ b/lib/bloc/exercise_new/exercise_new_event.dart @@ -28,6 +28,13 @@ class ExerciseNewQuantityUnitChange extends ExerciseNewEvent { List get props => [quantity]; } +class ExerciseNewBirthyearChange extends ExerciseNewEvent { + final int value; + const ExerciseNewBirthyearChange({this.value}); + @override + List get props => [value]; +} + class ExerciseNewWeightChange extends ExerciseNewEvent { final double value; const ExerciseNewWeightChange({this.value}); diff --git a/lib/bloc/exercise_plan/exercise_plan_bloc.dart b/lib/bloc/exercise_plan/exercise_plan_bloc.dart index 9e8694e..817b83b 100644 --- a/lib/bloc/exercise_plan/exercise_plan_bloc.dart +++ b/lib/bloc/exercise_plan/exercise_plan_bloc.dart @@ -7,7 +7,6 @@ import 'package:aitrainer_app/repository/workout_tree_repository.dart'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:flurry/flurry.dart'; -import 'package:flurry/flurry.dart'; import 'package:meta/meta.dart'; part 'exercise_plan_event.dart'; diff --git a/lib/bloc/menu/menu_bloc.dart b/lib/bloc/menu/menu_bloc.dart index bd17683..63f8f06 100644 --- a/lib/bloc/menu/menu_bloc.dart +++ b/lib/bloc/menu/menu_bloc.dart @@ -164,7 +164,7 @@ class MenuBloc extends Bloc with Trans, Logging { ability = ExerciseAbility.endurance; break; case "Cardio": - case "Body Compositions": + case "My Body": ability = ExerciseAbility.none; break; } diff --git a/lib/bloc/result/result_bloc.dart b/lib/bloc/result/result_bloc.dart index 913fc49..a5643d0 100644 --- a/lib/bloc/result/result_bloc.dart +++ b/lib/bloc/result/result_bloc.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/model/result.dart'; import 'package:aitrainer_app/repository/exercise_repository.dart'; import 'package:aitrainer_app/repository/exercise_result_repository.dart'; @@ -38,6 +37,10 @@ class ResultBloc extends Bloc with Logging, Trans { ResultBloc({this.resultRepository, this.exerciseRepository, this.context}) : super(ResultInitial()) { this.startTime = exerciseRepository.start; this.endTime = exerciseRepository.end; + if (this.startTime == null) { + this.startTime = exerciseRepository.exercise.dateAdd; + exerciseRepository.start = exerciseRepository.exercise.dateAdd; + } } @override diff --git a/lib/bloc/sales/sales_bloc.dart b/lib/bloc/sales/sales_bloc.dart index df069d8..9de73e3 100644 --- a/lib/bloc/sales/sales_bloc.dart +++ b/lib/bloc/sales/sales_bloc.dart @@ -1,16 +1,19 @@ import 'dart:async'; import 'dart:io'; -import 'dart:math'; +import 'dart:math' as math; import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/model/product.dart'; import 'package:aitrainer_app/model/product_test.dart'; +import 'package:aitrainer_app/model/purchase.dart'; import 'package:aitrainer_app/service/logging.dart'; -import 'package:aitrainer_app/service/product_test_service.dart'; -import 'package:aitrainer_app/util/platform_purchase.dart'; +import 'package:aitrainer_app/service/purchase.dart'; +import 'package:aitrainer_app/util/common.dart'; +import 'package:aitrainer_app/util/purchases.dart'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:flurry/flurry.dart'; +import 'package:purchases_flutter/offering_wrapper.dart'; part 'sales_event.dart'; part 'sales_state.dart'; @@ -28,15 +31,34 @@ class SalesBloc extends Bloc with Logging { try { if (event is SalesLoad) { yield SalesLoading(); - //Flurry.logEvent("SalesPageOpen"); - // await PlatformPurchaseApi().initPurchasePlatformState(); - // this.getProductSet(); + log("Load Sales"); + Common.sendMessage("Salespage Load"); + Flurry.logEvent("SalesPageOpen"); + //await PlatformPurchaseApi().initPurchasePlatform(); + await RevenueCatPurchases().getOfferings(); + this.getProductSet(); yield SalesReady(); } else if (event is SalesPurchase) { + if (Cache().hasPurchased) { + throw Exception("You have already a successfull subscription"); + } + yield SalesLoading(); final int productId = event.productId; - trace("Requesting purchase for" + productId.toString()); - //Flurry.logEvent("PurchaseRequest"); - // PlatformPurchaseApi().purchase(getSelectedProduct(productId)); + log("Requesting purchase for: " + productId.toString()); + Flurry.logEvent("PurchaseRequest"); + final Product selectedProduct = this.getSelectedProduct(productId); + log("SelectedProduct for purchase " + selectedProduct.toString()); + await RevenueCatPurchases().makePurchase(selectedProduct); + if (Cache().hasPurchased) { + Purchase purchase = Purchase(customerId: Cache().userLoggedIn.customerId, productId: productId); + purchase.dateAdd = DateTime.now(); + purchase.purchaseSum = 0; + purchase.currency = "EUR"; + await PurchaseApi().savePurchase(purchase); + Flurry.logEvent("PurchaseSuccessful"); + Common.sendMessage("Purchase: " + purchase.toJson().toString()); + } + yield SalesSuccessful(); } } on Exception catch (ex) { yield SalesError(message: ex.toString()); @@ -53,18 +75,24 @@ class SalesBloc extends Bloc with Logging { return prod; } - /* String getLocalizedPrice(String productId) { + String getLocalizedPrice(String productId) { String price = ""; - for (var product in PlatformPurchaseApi().getIAPItems()) { - if (Platform.isAndroid) { - print("PlatformProduct " + product.toString()); - if (productId == product.productId) { - price = product.localizedPrice; + Offering offering = RevenueCatPurchases().offering; + if (offering != null) { + for (var package in offering.availablePackages) { + log("PlatformProduct " + package.toString()); + if (productId == package.product.identifier) { + price = package.product.priceString; + if (price.contains(r'HUF')) { + price = price.replaceAll(RegExp(r'HUF'), 'Ft'); + price = price.replaceAll(RegExp(r',00'), ""); + } break; } } + } else { + log(" !!! No Offering"); } - return price; } @@ -73,7 +101,7 @@ class SalesBloc extends Bloc with Logging { this.tests = Cache().productTests; if (tests.isEmpty) { - var rand = Random.secure(); + var rand = math.Random.secure(); productSet = rand.nextInt(5) + 1; } else { trace("Previous ProductTest: " + tests[0].toJson().toString()); @@ -88,7 +116,9 @@ class SalesBloc extends Bloc with Logging { } ProductTest productTest = ProductTest(); - trace("ProductSet: " + productSet.toString()); + + productSet = 2; + log("ProductSet: " + productSet.toString()); for (var elem in Cache().products) { Product product = elem as Product; @@ -109,5 +139,5 @@ class SalesBloc extends Bloc with Logging { productTest.dateView = DateTime.now(); //ProductTestApi().saveProductTest(productTest); //Cache().productTests.add(productTest); - } */ + } } diff --git a/lib/bloc/sales/sales_state.dart b/lib/bloc/sales/sales_state.dart index e6391b7..3cc5c09 100644 --- a/lib/bloc/sales/sales_state.dart +++ b/lib/bloc/sales/sales_state.dart @@ -19,6 +19,10 @@ class SalesReady extends SalesState { const SalesReady(); } +class SalesSuccessful extends SalesState { + const SalesSuccessful(); +} + class SalesError extends SalesState { final String message; const SalesError({this.message}); diff --git a/lib/bloc/session/session_bloc.dart b/lib/bloc/session/session_bloc.dart index 57bac26..fca4dac 100644 --- a/lib/bloc/session/session_bloc.dart +++ b/lib/bloc/session/session_bloc.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:aitrainer_app/bloc/settings/settings_bloc.dart'; import 'package:aitrainer_app/localization/app_language.dart'; import 'package:aitrainer_app/service/logging.dart'; -import 'package:aitrainer_app/util/platform_purchase.dart'; import 'package:aitrainer_app/util/session.dart'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; diff --git a/lib/bloc/settings/settings_bloc.dart b/lib/bloc/settings/settings_bloc.dart index 7f2d6a5..3215ff6 100644 --- a/lib/bloc/settings/settings_bloc.dart +++ b/lib/bloc/settings/settings_bloc.dart @@ -74,7 +74,7 @@ class SettingsBloc extends Bloc with Logging { HealthDataType.RESTING_HEART_RATE ]; final HealthFactory health = HealthFactory(); */ - DateTime now = DateTime.now(); + //DateTime now = DateTime.now(); //List _healthDataList = await health.getHealthDataFromTypes(now.subtract(Duration(minutes: 5)), now, types); //log(_healthDataList.toString()); } diff --git a/lib/main.dart b/lib/main.dart index 43a2c73..4c9ac16 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -52,7 +52,7 @@ import 'bloc/menu/menu_bloc.dart'; import 'bloc/session/session_bloc.dart'; import 'bloc/settings/settings_bloc.dart'; -final SentryClient _sentry = new SentryClient(dsn: 'https://5fac40cbfcfb4c15aa80c7a8638d7310@o418565.ingest.sentry.io/5322520'); +const dsn = 'https://5fac40cbfcfb4c15aa80c7a8638d7310@o418565.ingest.sentry.io/5322520'; /// Whether the VM is running in debug mode. /// @@ -80,16 +80,12 @@ Future _reportError(dynamic error, dynamic stackTrace) async { print('Reporting to Sentry.io...'); - final SentryResponse response = await _sentry.captureException( - exception: error, + final sentryId = await Sentry.captureException( + error, stackTrace: stackTrace, ); - if (response.isSuccessful) { - print('Success! Event ID: ${response.eventId}'); - } else { - print('Failed to report to Sentry.io: ${response.error}'); - } + print('Capture exception result : SentryId : $sentryId'); } Future main() async { @@ -119,8 +115,10 @@ Future main() async { runZonedGuarded>(() async { final WorkoutTreeRepository menuTreeRepository = WorkoutTreeRepository(); WidgetsFlutterBinding.ensureInitialized(); + print(" -- FireBase init.."); await FirebaseApi().initializeFlutterFire(); + runApp(MultiBlocProvider( providers: [ BlocProvider( @@ -157,8 +155,6 @@ Future main() async { Future initFlurry() async { await Flurry.initialize(androidKey: "JNYCTCWBT34FM3J8TV36", iosKey: "3QBG7BSMGPDH24S8TRQP", enableLog: true); - //await Flurry.setUserId("userId"); - //await Flurry.logEvent("eventName"); } class WorkoutTestApp extends StatelessWidget { @@ -232,7 +228,7 @@ class WorkoutTestApp extends StatelessWidget { //primarySwatch: Colors.transparent, //fontFamily: 'Arial', textTheme: TextTheme( - bodyText1: GoogleFonts.openSans(textStyle: TextStyle(fontSize: 14.0)), + bodyText1: GoogleFonts.inter(textStyle: TextStyle(fontSize: 14.0)), )), navigatorObservers: [ FirebaseAnalyticsObserver(analytics: analytics), diff --git a/lib/model/cache.dart b/lib/model/cache.dart index 6987ce3..0ae3570 100644 --- a/lib/model/cache.dart +++ b/lib/model/cache.dart @@ -5,7 +5,7 @@ import 'package:aitrainer_app/model/exercise_plan_detail.dart'; import 'package:aitrainer_app/model/exercise_tree.dart'; import 'package:aitrainer_app/model/exercise.dart'; import 'package:aitrainer_app/model/model_change.dart'; -import 'package:aitrainer_app/model/product.dart'; +import 'package:aitrainer_app/model/product.dart' as wt_product; import 'package:aitrainer_app/model/product_test.dart'; import 'package:aitrainer_app/model/property.dart'; import 'package:aitrainer_app/model/purchase.dart'; @@ -84,7 +84,7 @@ class Cache with Logging { List _exercises; ExercisePlan _myExercisePlan; List _properties; - List _products; + List _products; List _purchases = List(); List _productTests; @@ -405,18 +405,18 @@ class Cache with Logging { setBadge("Sizes", true); setBadge("BMI", true); setBadge("BMR", true); - setBadgeNr("Body Compositions", 3); + setBadgeNr("My Body", 3); setBadgeNr("home", 3); } else if (customerRepository.getWeight() == 0) { setBadge("BMI", true); setBadge("BMR", true); - setBadge("Body Compositions", true); + setBadge("My Body", true); setBadgeNr("home", 1); } if (customerRepository.getHeight() == 0) { setBadge("BMI", true); setBadge("BMR", true); - setBadge("Body Compositions", true); + setBadge("My Body", true); setBadgeNr("home", 1); } } @@ -450,7 +450,7 @@ class Cache with Logging { await customerRepository.getPurchase(); await customerRepository.getProductTests(); - this.hasPurchased = this._purchases.isNotEmpty; + //this.hasPurchased = this._purchases.isNotEmpty; Cache().startPage = "home"; } diff --git a/lib/model/exercise_type.dart b/lib/model/exercise_type.dart index 5af9122..0176011 100644 --- a/lib/model/exercise_type.dart +++ b/lib/model/exercise_type.dart @@ -55,7 +55,6 @@ class ExerciseType { this.parents.add(parent['exerciseTreeId']); }); } - ; } Map toJson() => { diff --git a/lib/model/purchase.dart b/lib/model/purchase.dart index d68cbbe..8307d72 100644 --- a/lib/model/purchase.dart +++ b/lib/model/purchase.dart @@ -9,6 +9,8 @@ class Purchase { double purchaseSum; String currency; + Purchase({this.customerId, this.productId}); + Purchase.fromJson(Map json) { this.purchaseId = json['purchaseId']; this.customerId = json['customerId']; diff --git a/lib/repository/customer_repository.dart b/lib/repository/customer_repository.dart index 87349f7..3df20b9 100644 --- a/lib/repository/customer_repository.dart +++ b/lib/repository/customer_repository.dart @@ -270,7 +270,7 @@ class CustomerRepository with Logging { try { int customerId = Cache().userLoggedIn.customerId; tests = await ProductTestApi().getProductTestByCustomer(customerId); - } on NotFoundException catch (ex) { + } on NotFoundException catch (_) { log("Product Tests not found"); Cache().productTests = tests; } on Exception catch (ex) { diff --git a/lib/repository/exercise_repository.dart b/lib/repository/exercise_repository.dart index 1c88557..38e2359 100644 --- a/lib/repository/exercise_repository.dart +++ b/lib/repository/exercise_repository.dart @@ -259,6 +259,17 @@ class ExerciseRepository { return actualExerciseType; } + void getSameExercise(int exerciseTypeId, String day) { + this.actualExerciseList = List(); + for (int i = 0; i < this.exerciseList.length; i++) { + Exercise exercise = exerciseList[i]; + String exerciseDate = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd); + if (exerciseTypeId == exercise.exerciseTypeId && exerciseDate == day) { + this.actualExerciseList.add(exercise); + } + } + } + void sortByDate() { if (exerciseList.isEmpty) { return; diff --git a/lib/repository/exercise_result_repository.dart b/lib/repository/exercise_result_repository.dart index 2b5af13..648189b 100644 --- a/lib/repository/exercise_result_repository.dart +++ b/lib/repository/exercise_result_repository.dart @@ -1,7 +1,6 @@ import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/model/exercise_result.dart'; import 'package:aitrainer_app/model/result.dart'; -import 'package:aitrainer_app/service/exercise_result_service.dart'; enum ResultType { running, man, woman, none } diff --git a/lib/repository/user_repository.dart b/lib/repository/user_repository.dart index c54381a..1506f19 100644 --- a/lib/repository/user_repository.dart +++ b/lib/repository/user_repository.dart @@ -59,6 +59,11 @@ class UserRepository with Logging { if (e.code == 'email-already-in-use') { log('The account already exists for that email.'); throw Exception("The email address has been registered already"); + } else if (e.code == 'weak-password') { + log('The password provided is too weak.'); + throw Exception("Password too short"); + } else { + throw Exception(e); } } on WorkoutTestException catch (ex) { if (ex.code == WorkoutTestException.CUSTOMER_EXISTS) { @@ -66,8 +71,8 @@ class UserRepository with Logging { throw Exception("The email address has been registered already"); } } on Exception catch (ex) { - log("Google exception: " + ex.toString()); - throw Exception("Google Sign In failed"); + log("FB exception: " + ex.toString()); + throw Exception("Facebook Sign In failed"); } } @@ -90,6 +95,8 @@ class UserRepository with Logging { if (e.code == 'email-already-in-use') { log('The account already exists for that email.'); throw Exception("The email address has been registered already"); + } else { + throw Exception(e); } } on WorkoutTestException catch (ex) { if (ex.code == WorkoutTestException.CUSTOMER_EXISTS) { @@ -167,17 +174,18 @@ class UserRepository with Logging { Future getUserByGoogle() async { final User modelUser = this.user; - Map userData = await FirebaseApi().signInWithGoogle(); - if (userData == null || userData['email'] == null) { - throw new Exception("Google login was not successful"); - } - modelUser.email = userData['email']; try { + Map userData = await FirebaseApi().signInWithGoogle(); + if (userData == null || userData['email'] == null) { + throw new Exception("Google login was not successful"); + } + modelUser.email = userData['email']; + await CustomerApi().getUserByEmail(modelUser.email); await Cache().afterFirebaseLogin(); } on Exception catch (ex) { log("Google exception: " + ex.toString()); - throw Exception("Customer does not exist or the password is wrong"); + throw Exception(ex); } } diff --git a/lib/repository/workout_tree_repository.dart b/lib/repository/workout_tree_repository.dart index 28c2e0a..36b1c17 100644 --- a/lib/repository/workout_tree_repository.dart +++ b/lib/repository/workout_tree_repository.dart @@ -10,7 +10,6 @@ import 'package:aitrainer_app/service/exercise_tree_service.dart'; import 'package:aitrainer_app/service/exercisetype_service.dart'; import 'package:aitrainer_app/service/logging.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; class Antagonist { static String chest = "Chest"; @@ -48,7 +47,7 @@ class WorkoutTreeRepository with Logging { Antagonist.calf: Antagonist.calfNr }; - Future _buildImage(String imageUrl) async { + /* Future _buildImage(String imageUrl) async { String assetImage = 'asset/menu/' + imageUrl.substring(7); //print("Loading image " + assetImage); return rootBundle.load(assetImage).then((value) { @@ -59,7 +58,7 @@ class WorkoutTreeRepository with Logging { //print("Exception: " + assetImage + " will be loaded from the network " + url); return url; }); - } + } */ Future createTree() async { isEnglish = AppLanguage().appLocal == Locale('en'); diff --git a/lib/service/customer_exercise_device_service.dart b/lib/service/customer_exercise_device_service.dart index 71022be..8e9f61b 100644 --- a/lib/service/customer_exercise_device_service.dart +++ b/lib/service/customer_exercise_device_service.dart @@ -15,7 +15,7 @@ class CustomerExerciseDeviceApi with Logging { final body = await _client.get("customer_exercise_device/customer/" + customerId.toString(), ""); final Iterable json = jsonDecode(body); devices = json.map((device) => CustomerExerciseDevice.fromJson(device)).toList(); - } on NotFoundException catch (e) { + } on NotFoundException catch (_) { log("No devices found"); } return devices; diff --git a/lib/service/firebase_api.dart b/lib/service/firebase_api.dart index 42f7466..53b6243 100644 --- a/lib/service/firebase_api.dart +++ b/lib/service/firebase_api.dart @@ -165,41 +165,37 @@ class FirebaseApi with logging.Logging { Future> signInWithGoogle() async { Map userData = Map(); - try { - // Trigger the authentication flow - GoogleSignIn _googleSignIn = GoogleSignIn( - scopes: [ - 'email', - 'https://www.googleapis.com/auth/contacts.readonly', - ], - ); - final GoogleSignInAccount googleUser = await _googleSignIn.signIn(); + // Trigger the authentication flow + GoogleSignIn _googleSignIn = GoogleSignIn( + scopes: [ + 'email', + 'https://www.googleapis.com/auth/contacts.readonly', + ], + ); + final GoogleSignInAccount googleUser = await _googleSignIn.signIn(); - if (googleUser == null) { - throw Exception("Google Sign In failed"); - } - - // Obtain the auth details from the request - final GoogleSignInAuthentication googleAuth = await googleUser.authentication; - - // Create a new credential - final GoogleAuthCredential credential = GoogleAuthProvider.credential( - accessToken: googleAuth.accessToken, - idToken: googleAuth.idToken, - ); - - await FirebaseAuth.instance.signInWithCredential(credential); - - if (googleUser != null) { - log("GoogleUser: " + googleUser.toString()); - userData['email'] = googleUser.email; - userData['id'] = googleUser.id; - userData['name'] = googleUser.displayName; - } - } on Exception catch (ex) { - log("Google exception: " + ex.toString()); + if (googleUser == null) { throw Exception("Google Sign In failed"); } + + // Obtain the auth details from the request + final GoogleSignInAuthentication googleAuth = await googleUser.authentication; + + // Create a new credential + final GoogleAuthCredential credential = GoogleAuthProvider.credential( + accessToken: googleAuth.accessToken, + idToken: googleAuth.idToken, + ); + + await FirebaseAuth.instance.signInWithCredential(credential); + + if (googleUser != null) { + log("GoogleUser: " + googleUser.toString()); + userData['email'] = googleUser.email; + userData['id'] = googleUser.id; + userData['name'] = googleUser.displayName; + } + return userData; } @@ -266,15 +262,16 @@ class FirebaseApi with logging.Logging { Cache().accessTokenFacebook = accessToken; // get the user data userData = await FacebookAuth.instance.getUserData(); + log("FB user data: " + userData.toString()); // Create a credential from the access token final FacebookAuthCredential facebookAuthCredential = FacebookAuthProvider.credential(accessToken.token); // Once signed in, return the UserCredential final userCredential = await FirebaseAuth.instance.signInWithCredential(facebookAuthCredential); + log("Email by FB: " + userData['email'] + " FB credential: " + userCredential.toString()); Cache().firebaseUid = userCredential.user.uid; - log(userData.toString()); } else { throw Exception("Facebook login was not successful"); } diff --git a/lib/service/purchase.dart b/lib/service/purchase.dart index 594947a..ecb1953 100644 --- a/lib/service/purchase.dart +++ b/lib/service/purchase.dart @@ -15,7 +15,7 @@ class PurchaseApi with Logging { final Iterable json = jsonDecode(body); final List purchases = json.map((purchase) => Purchase.fromJson(purchase)).toList(); Cache().setPurchases(purchases); - } on NotFoundException catch (e) { + } on NotFoundException catch (_) { log("No purchases found"); } return purchases; diff --git a/lib/util/common.dart b/lib/util/common.dart index 6ffbd21..1d72988 100644 --- a/lib/util/common.dart +++ b/lib/util/common.dart @@ -8,6 +8,7 @@ import 'package:badges/badges.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'package:sentry/sentry.dart'; class DateRate { static String daily = "daily"; @@ -130,4 +131,13 @@ mixin Common { ), ); } + + static Future sendMessage(String message) async { + // Sends a full Sentry event payload to show the different parts of the UI. + await Sentry.captureMessage( + message, + level: SentryLevel.info, + template: 'Message: %s, customerId: ' + Cache().userLoggedIn.customerId.toString(), + ); + } } diff --git a/lib/util/platform_purchase.dart b/lib/util/platform_purchase.dart index a0f7df8..a183e98 100644 --- a/lib/util/platform_purchase.dart +++ b/lib/util/platform_purchase.dart @@ -1,28 +1,29 @@ -import 'dart:async'; -import 'dart:io'; /* import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/model/product.dart'; import 'package:aitrainer_app/service/logging.dart'; -import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart'; +import 'package:in_app_purchase/in_app_purchase.dart'; class PlatformPurchaseApi with Logging { static final PlatformPurchaseApi _singleton = PlatformPurchaseApi._internal(); - StreamSubscription _purchaseUpdatedSubscription; - StreamSubscription _purchaseErrorSubscription; - StreamSubscription _conectionSubscription; - String _platformVersion = 'Unknown'; - List _items = []; - List getIAPItems() => _items; - List _purchases = []; + bool _kAutoConsume = true; + final String _kConsumableId = 'consumable'; + final InAppPurchaseConnection _connection = InAppPurchaseConnection.instance; + bool _isAvailable = false; + List _products = []; + List _purchases = []; + List _notFoundIds = []; + List _consumables = []; + bool _purchasePending = false; + String _queryProductError; + + StreamSubscription> _subscription; final List _productList = List(); List getProductList() => _productList; - - factory PlatformPurchaseApi() { return _singleton; } @@ -30,55 +31,152 @@ class PlatformPurchaseApi with Logging { PlatformPurchaseApi._internal(); Future close() async { - if (_conectionSubscription != null) { - _conectionSubscription.cancel(); - _conectionSubscription = null; + _subscription.cancel(); + } + + Future initStoreInfo() async { + final bool isAvailable = await _connection.isAvailable(); + if (!isAvailable) { + _isAvailable = isAvailable; + _products = []; + _purchases = []; + _notFoundIds = []; + _consumables = []; + _purchasePending = false; + log("Payment processor not available"); + return; } - await FlutterInappPurchase.instance.endConnection; + + getSubscriptions(); + ProductDetailsResponse productDetailResponse = await _connection.queryProductDetails(_productList.toSet()); + log("ProductDetailsResponse " + productDetailResponse.toString()); + if (productDetailResponse.error != null) { + _queryProductError = productDetailResponse.error.message; + _isAvailable = isAvailable; + _products = productDetailResponse.productDetails; + _purchases = []; + _notFoundIds = productDetailResponse.notFoundIDs; + _consumables = []; + _purchasePending = false; + log("Payment processor not available"); + return; + } + + if (productDetailResponse.productDetails.isEmpty) { + _queryProductError = null; + _isAvailable = isAvailable; + _products = productDetailResponse.productDetails; + _purchases = []; + _notFoundIds = productDetailResponse.notFoundIDs; + _consumables = []; + _purchasePending = false; + + return; + } + + _products = productDetailResponse.productDetails; + _products.forEach((element) { + ProductDetails product = element as ProductDetails; + log("Product " + product.id + " " + product.price + " " + product.skuDetail.toString()); + }); + + final QueryPurchaseDetailsResponse purchaseResponse = await _connection.queryPastPurchases(); + if (purchaseResponse.error != null) { + // handle query past purchase error.. + } + + final List verifiedPurchases = []; + for (PurchaseDetails purchase in purchaseResponse.pastPurchases) { + if (await _verifyPurchase(purchase)) { + verifiedPurchases.add(purchase); + } + } + + //TODO List consumables = await ConsumableStore.load(); + _isAvailable = isAvailable; + _products = productDetailResponse.productDetails; + _purchases = verifiedPurchases; + _notFoundIds = productDetailResponse.notFoundIDs; + //_consumables = consumables; + _purchasePending = false; } // Platform messages are asynchronous, so we initialize in an async method. - Future initPurchasePlatformState() async { + Future initPurchasePlatform() async { if (_productList.length > 0) { return; } + log(' --- InappPurchase connection: $_connection'); log(" --- Init PurchasePlatform"); - String platformVersion; - // Platform messages may fail, so we use a try/catch PlatformException. - try { - platformVersion = await FlutterInappPurchase.instance.platformVersion; - } on Exception { - platformVersion = 'Failed to get platform version for InAppPurchase.'; - log("PlatformVersion" + platformVersion); + await this.initStoreInfo(); + + Stream purchaseUpdated = InAppPurchaseConnection.instance.purchaseUpdatedStream; + _subscription = purchaseUpdated.listen((purchaseDetailsList) { + _listenToPurchaseUpdated(purchaseDetailsList); + }, onDone: () { + _subscription.cancel(); + }, onError: (error) { + // handle error here. + log("Error listen purchase updated " + error.toString()); + }); + } + + void deliverProduct(PurchaseDetails purchaseDetails) async { + log("DeliverProduct"); + // IMPORTANT!! Always verify a purchase purchase details before delivering the product. + if (purchaseDetails.productID == _kConsumableId) { + //TODO + //await ConsumableStore.save(purchaseDetails.purchaseID); + //List consumables = await ConsumableStore.load(); + + _purchasePending = false; + //_consumables = consumables; + } else { + _purchases.add(purchaseDetails); + _purchasePending = false; } + } - // prepare - var result = await FlutterInappPurchase.instance.initConnection; - log(' FlutterInappPurchase init result: $result'); + Future _verifyPurchase(PurchaseDetails purchaseDetails) { + log("verifyPurchase"); + // IMPORTANT!! Always verify a purchase before delivering the product. + // For the purpose of an example, we directly return true. + log("Verifying Purchase" + purchaseDetails.toString()); + return Future.value(true); + } - _platformVersion = platformVersion; + void _handleInvalidPurchase(PurchaseDetails purchaseDetails) { + // handle invalid purchase here if _verifyPurchase` failed. + log("Invalid Purchase" + purchaseDetails.toString()); + } - // refresh items for android - try { - String msg = await FlutterInappPurchase.instance.consumeAllItems; - log('consumeAllItems: $msg'); - } catch (err) { - log('consumeAllItems error: $err'); - } - - _conectionSubscription = FlutterInappPurchase.connectionUpdated.listen((connected) { - log('connected: $connected'); + void _listenToPurchaseUpdated(List purchaseDetailsList) { + purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async { + if (purchaseDetails.status == PurchaseStatus.pending) { + //showPendingUI(); + log("Purchase pending"); + } else { + if (purchaseDetails.status == PurchaseStatus.error) { + //handleError(purchaseDetails.error); + } else if (purchaseDetails.status == PurchaseStatus.purchased) { + bool valid = await _verifyPurchase(purchaseDetails); + if (valid) { + deliverProduct(purchaseDetails); + } else { + _handleInvalidPurchase(purchaseDetails); + return; + } + } + if (Platform.isAndroid) { + if (!_kAutoConsume && purchaseDetails.productID == _kConsumableId) { + await InAppPurchaseConnection.instance.consumePurchase(purchaseDetails); + } + } + if (purchaseDetails.pendingCompletePurchase) { + await InAppPurchaseConnection.instance.completePurchase(purchaseDetails); + } + } }); - - _purchaseUpdatedSubscription = FlutterInappPurchase.purchaseUpdated.listen((productItem) { - log('purchase-updated: $productItem'); - }); - - _purchaseErrorSubscription = FlutterInappPurchase.purchaseError.listen((purchaseError) { - log('purchase-error: $purchaseError'); - }); - - getSubscriptions(); } void getSubscriptions() { @@ -98,7 +196,6 @@ class PlatformPurchaseApi with Logging { _productList.forEach((element) { print(element); }); - _getSubscription(); } void purchase(Product product) { @@ -106,57 +203,29 @@ class PlatformPurchaseApi with Logging { throw Exception("No product to purchase"); } String productId = Platform.isAndroid ? product.productIdAndroid : product.productIdIos; - IAPItem selected; - for (var item in _items) { - if (item.productId == productId) { - selected = item; - log("Item to purchase: " + item.toString()); + ProductDetails productDetails; + _products.forEach((element) { + if (element.id == productId) { + productDetails = element; + log("product to purchase: " + + productDetails.id + + " title " + + productDetails.title + + " price " + + productDetails.price + + " period " + + productDetails.skuDetail.subscriptionPeriod); } - } + }); - if (selected != null) { - requestPurchase(selected); - } else { - throw Exception("product " + productId + " not defined in the PlatformStore"); - } - } - - void requestPurchase(IAPItem item) { - //FlutterInappPurchase.instance.requestPurchase(item.productId); - FlutterInappPurchase.instance.requestPurchase(item.productId); - } - - Future _getSubscription() async { - List items = await FlutterInappPurchase.instance.getSubscriptions(_productList); - for (var item in items) { - log('${item.toString()}'); - this._items.add(item); - } - - this._items = items; - this._purchases = []; - } - - Future _getPurchases() async { - List items = await FlutterInappPurchase.instance.getAvailablePurchases(); - for (var item in items) { - log('${item.toString()}'); - this._purchases.add(item); - } - - this._items = []; - this._purchases = items; - } - - Future _getPurchaseHistory() async { - List items = await FlutterInappPurchase.instance.getPurchaseHistory(); - for (var item in items) { - log('${item.toString()}'); - this._purchases.add(item); - } - - this._items = []; - this._purchases = items; + final PurchaseParam purchaseParam = PurchaseParam(productDetails: productDetails); +/* if (_isConsumable(productDetails)) { + InAppPurchaseConnection.instance.buyConsumable(purchaseParam: purchaseParam); + } else { */ + InAppPurchaseConnection.instance.buyNonConsumable(purchaseParam: purchaseParam); + //} +// From here the purchase flow will be handled by the underlying storefront. +// Updates will be delivered to the `InAppPurchaseConnection.instance.purchaseUpdatedStream`. } } */ diff --git a/lib/util/purchases.dart b/lib/util/purchases.dart new file mode 100644 index 0000000..7cc41ed --- /dev/null +++ b/lib/util/purchases.dart @@ -0,0 +1,120 @@ +import 'dart:io'; + +import 'package:aitrainer_app/model/cache.dart'; +import 'package:aitrainer_app/service/logging.dart'; +import 'package:aitrainer_app/util/common.dart'; +import 'package:flutter/services.dart'; +import 'package:purchases_flutter/purchases_flutter.dart'; +import 'package:aitrainer_app/model/product.dart' as wtproduct; + +class RevenueCatPurchases with Logging { + static final RevenueCatPurchases _singleton = RevenueCatPurchases._internal(); + Offering _offering; + String appUserId; + + factory RevenueCatPurchases() { + return _singleton; + } + + RevenueCatPurchases._internal(); + + Offering get offering => _offering; + + Future initPlatform() async { + if (Cache().userLoggedIn != null) { + await Purchases.setDebugLogsEnabled(true); + await Purchases.setup("yLxGFWaeRtThLImqznvBnUEKjgArSsZE", appUserId: Cache().userLoggedIn.customerId.toString()); + appUserId = await Purchases.appUserID; + log("AppUserId: " + appUserId); + await this.restore(); + } + } + + Future restore() async { + if (appUserId != null) { + try { + PurchaserInfo purchaserInfo = await Purchases.restoreTransactions(); + if (purchaserInfo != null && + purchaserInfo.entitlements.all["wt_subscription"] != null && + purchaserInfo.entitlements.all["wt_subscription"].isActive) { + Cache().hasPurchased = true; + log(" ******************************************** "); + log(" Purchase active ! "); + } else { + log(" ** No subscription active"); + } + } on PlatformException catch (e) { + log("Purchaserinfo not reachable " + e.toString()); + } + } + } + + Future getOfferings() async { + if (appUserId == null) { + await initPlatform(); + } + log(" .. acessing offerings..."); + try { + Offerings offerings = await Purchases.getOfferings(); + if (offerings.current != null && offerings.current.availablePackages.isNotEmpty) { + // Display packages for sale + _offering = offerings.current; + } else { + log("No current offerings"); + Common.sendMessage("No Current offerings"); + } + } on PlatformException catch (e) { + // optional error handling + log("!!!! Getting offerings error " + e.toString()); + } + return _offering; + } + + Future makePurchase(wtproduct.Product product) async { + try { + if (_offering == null) { + _offering = await getOfferings(); + } + if (_offering != null) { + String productId = Platform.isAndroid ? product.productIdAndroid : product.productIdIos; + Package selectedPackage; + log("Nr of packages: " + _offering.availablePackages.length.toString() + " ProductId: " + productId); + for (var package in _offering.availablePackages) { + log("package to check " + package.product.identifier.toString()); + if (package.product.identifier == productId) { + selectedPackage = package; + log("**** Selected package to purchase" + package.product.identifier); + break; + } + } + if (selectedPackage != null) { + PurchaserInfo purchaserInfo = await Purchases.purchasePackage(selectedPackage); + if (purchaserInfo.entitlements.all["wt_subscription"].isActive) { + Cache().hasPurchased = true; + } + } else { + log("!!!! No Selected package to purchase"); + Common.sendMessage("No Selected package to purchase"); + throw Exception("Purchase was not successful"); + } + } else { + log("!!!! No active offering"); + } + } on PlatformException catch (e) { + var errorCode = PurchasesErrorHelper.getErrorCode(e); + if (errorCode == PurchasesErrorCode.invalidReceiptError) { + log("iOS Sandbox invalid receipt"); + Cache().hasPurchased = true; + return; + } + log(e.toString()); + if (errorCode == PurchasesErrorCode.purchaseCancelledError) { + Common.sendMessage("Purchase was cancelled"); + throw Exception("Purchase was cancelled"); + } else { + Common.sendMessage("Purchase was not successful"); + throw Exception("Purchase was not successful"); + } + } + } +} diff --git a/lib/util/session.dart b/lib/util/session.dart index fcf6abc..663e5cf 100644 --- a/lib/util/session.dart +++ b/lib/util/session.dart @@ -6,6 +6,7 @@ import 'package:aitrainer_app/service/api.dart'; import 'package:aitrainer_app/service/logging.dart'; import 'package:aitrainer_app/service/product_service.dart'; import 'package:aitrainer_app/service/property_service.dart'; +import 'package:aitrainer_app/util/purchases.dart'; import 'package:devicelocale/devicelocale.dart'; import 'package:flutter/services.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -28,6 +29,7 @@ class Session with Logging { Cache().setServerAddress(_sharedPreferences); Cache().getHardware(_sharedPreferences); await _fetchToken(_sharedPreferences); + await RevenueCatPurchases().initPlatform(); //initDeviceLocale(); // Create the initialization Future outside of `build`: diff --git a/lib/view/account.dart b/lib/view/account.dart index 3128562..463897f 100644 --- a/lib/view/account.dart +++ b/lib/view/account.dart @@ -5,7 +5,6 @@ import 'package:aitrainer_app/model/customer.dart'; import 'package:aitrainer_app/util/common.dart'; import 'package:aitrainer_app/util/trans.dart'; import 'package:aitrainer_app/widgets/app_bar_min.dart'; -import 'package:badges/badges.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:aitrainer_app/widgets/bottom_nav.dart'; import 'package:flutter/material.dart'; diff --git a/lib/view/evaluation.dart b/lib/view/evaluation.dart index 514d069..d6d3218 100644 --- a/lib/view/evaluation.dart +++ b/lib/view/evaluation.dart @@ -4,7 +4,9 @@ import 'dart:ui'; import 'package:aitrainer_app/bloc/result/result_bloc.dart'; import 'package:aitrainer_app/localization/app_language.dart'; import 'package:aitrainer_app/model/cache.dart'; +import 'package:aitrainer_app/model/exercise.dart'; import 'package:aitrainer_app/model/exercise_ability.dart'; +import 'package:aitrainer_app/model/exercise_type.dart'; import 'package:aitrainer_app/repository/exercise_repository.dart'; import 'package:aitrainer_app/repository/exercise_result_repository.dart'; import 'package:aitrainer_app/util/trans.dart'; @@ -15,6 +17,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_bloc/flutter_form_bloc.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:aitrainer_app/model/result.dart'; +import 'package:modal_progress_hud/modal_progress_hud.dart'; // ignore: must_be_immutable class EvaluationPage extends StatelessWidget with Trans { @@ -22,7 +25,7 @@ class EvaluationPage extends StatelessWidget with Trans { Widget build(BuildContext context) { LinkedHashMap arguments = ModalRoute.of(context).settings.arguments; ExerciseRepository exerciseRepository; - // ignore: close_sinks + if (arguments != null) { exerciseRepository = arguments['exerciseRepository']; } else { @@ -37,10 +40,22 @@ class EvaluationPage extends StatelessWidget with Trans { resultType = ResultType.man; imageUrl = 'asset/image/WT_Results_for_female.png'; } + + if (arguments['past'] != null && arguments['past'] == true) { + Exercise exercise = arguments['exercise']; + if (exercise != null) { + ExerciseType exerciseType = exerciseRepository.getExerciseTypeById(exercise.exerciseTypeId); + exerciseRepository.exerciseType = exerciseType; + exerciseRepository.exercise = exercise; + String exerciseDate = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd); + exerciseRepository.getSameExercise(exercise.exerciseTypeId, exerciseDate); + } + } if (exerciseRepository.exerciseType.getAbility().equalsTo(ExerciseAbility.running)) { resultType = ResultType.running; imageUrl = 'asset/image/WT_Results_for_runners.png'; } + setContext(context); return Scaffold( appBar: AppBarMin( @@ -61,7 +76,7 @@ class EvaluationPage extends StatelessWidget with Trans { create: (context) => ResultBloc( resultRepository: ExerciseResultRepository(resultType: resultType), exerciseRepository: exerciseRepository, - context: context), //..add(ResultLoad()) + context: context), child: BlocConsumer(listener: (context, state) { if (state is ResultError) { Scaffold.of(context).showSnackBar( @@ -74,7 +89,13 @@ class EvaluationPage extends StatelessWidget with Trans { } }, builder: (context, state) { final resultBloc = BlocProvider.of(context); - return getEvaluationWidgets(resultBloc); + return ModalProgressHUD( + child: getEvaluationWidgets(resultBloc), + inAsyncCall: state is ResultLoading, + opacity: 0.5, + color: Colors.black54, + progressIndicator: CircularProgressIndicator(), + ); }))), bottomNavigationBar: BottomNavigator(bottomNavIndex: 0)); } @@ -89,13 +110,15 @@ class EvaluationPage extends StatelessWidget with Trans { SliverAppBar( pinned: true, backgroundColor: Colors.transparent, - expandedHeight: 50.0, + expandedHeight: 120.0, + collapsedHeight: 80, + toolbarHeight: 30, automaticallyImplyLeading: false, flexibleSpace: FlexibleSpaceBar( title: Text(exerciseName, textAlign: TextAlign.center, maxLines: 3, - softWrap: true, + //softWrap: true, style: GoogleFonts.archivoBlack( fontSize: 24, color: Colors.white, @@ -382,14 +405,12 @@ class EvaluationPage extends StatelessWidget with Trans { Widget getSummary(ResultBloc bloc) { int index = 0; List resultList = List(); - /* for (int i = 0; i < bloc.exerciseRepository.actualExerciseList.length; i++) { - print("Q " + - bloc.exerciseRepository.actualExerciseList[i].quantity.toString() + - " Qu: " + - bloc.exerciseRepository.actualExerciseList[i].unitQuantity.toString()); - } */ + bloc.exerciseRepository.actualExerciseList.forEach((actual) { - final String unit = t(bloc.exerciseRepository.exerciseType.unit); + //final String unit = t(bloc.exerciseRepository.exerciseType.unit); + final String unit = bloc.exerciseRepository.exerciseType.unitQuantityUnit != null + ? bloc.exerciseRepository.exerciseType.unitQuantityUnit + : bloc.exerciseRepository.exerciseType.unit; String exerciseElement = ""; String exerciseRepeats = actual.quantity.toStringAsFixed(0); if (bloc.exerciseRepository.exerciseType.unit == "second") { @@ -407,7 +428,7 @@ class EvaluationPage extends StatelessWidget with Trans { } index++; resultList.add( - Text(exerciseElement + exerciseRepeats + exerciseUnitQuantity + " " + unit, + Text(exerciseElement + exerciseRepeats + exerciseUnitQuantity + " " + t(unit), textAlign: TextAlign.center, maxLines: 2, softWrap: true, diff --git a/lib/view/exercise_log_page.dart b/lib/view/exercise_log_page.dart index 4f5cfdc..deca86c 100644 --- a/lib/view/exercise_log_page.dart +++ b/lib/view/exercise_log_page.dart @@ -200,7 +200,7 @@ class _ExerciseLogPage extends State with Trans, Common { iconSize: 36, icon: Icon(CustomIcon.heart_1, color: Colors.blue[800]), onPressed: () { - evaluation(); + evaluation(exerciseRepository, exercise); }, ), Cache().hasPurchased @@ -212,7 +212,7 @@ class _ExerciseLogPage extends State with Trans, Common { width: 25, ), onTap: () { - evaluation(); + evaluation(exerciseRepository, exercise); }), ], ), @@ -230,19 +230,27 @@ class _ExerciseLogPage extends State with Trans, Common { return list; } - void evaluation() { - showDialog( - context: context, - builder: (BuildContext context) { - return DialogPremium( - unlocked: true, - unlockRound: 1, - unlockedText: t("Enjoy also this premium fetaure to show all old evaluation data of your successful exercises."), - function: "My Exercise Logs", - onTap: () => {Navigator.of(context).pop()}, - onCancel: () => {Navigator.of(context).pop()}, - ); - }); + void evaluation(ExerciseRepository exerciseRepository, Exercise exercise) { + if (!Cache().hasPurchased) { + showDialog( + context: context, + builder: (BuildContext context) { + return DialogPremium( + unlocked: Cache().hasPurchased, + unlockRound: 1, + unlockedText: t("Enjoy also this premium fetaure to show all old evaluation data of your successful exercises."), + function: "My Exercise Logs", + onTap: () => {Navigator.of(context).pop()}, + onCancel: () => {Navigator.of(context).pop()}, + ); + }); + } else { + LinkedHashMap args = LinkedHashMap(); + args['exerciseRepository'] = exerciseRepository; + args['exercise'] = exercise; + args['past'] = true; + Navigator.of(context).pushNamed('evaluationPage', arguments: args); + } } void confirmationDialog(ExerciseLogBloc bloc, Exercise exercise) { diff --git a/lib/view/exercise_new_page.dart b/lib/view/exercise_new_page.dart index da9f039..627b1fa 100644 --- a/lib/view/exercise_new_page.dart +++ b/lib/view/exercise_new_page.dart @@ -22,6 +22,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_form_bloc/flutter_form_bloc.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:keyboard_actions/keyboard_actions.dart'; +import 'package:modal_progress_hud/modal_progress_hud.dart'; import 'package:stop_watch_timer/stop_watch_timer.dart'; import 'package:wakelock/wakelock.dart'; @@ -101,11 +102,14 @@ class _ExerciseNewPageState extends State with Trans, Logging { } }, builder: (context, state) { - if (state is ExerciseNewReady) { - //LoadingDialog.hide(context); - } final exerciseBloc = BlocProvider.of(context); - return getExerciseWidget(exerciseBloc, exerciseType, menuBloc); + return ModalProgressHUD( + child: getExerciseWidget(exerciseBloc, exerciseType, menuBloc), + inAsyncCall: state is ExerciseNewLoading, + opacity: 0.5, + color: Colors.black54, + progressIndicator: CircularProgressIndicator(), + ); }, )); } diff --git a/lib/view/mydevelopment_body_page.dart b/lib/view/mydevelopment_body_page.dart index f896fcf..8bdc2cf 100644 --- a/lib/view/mydevelopment_body_page.dart +++ b/lib/view/mydevelopment_body_page.dart @@ -28,7 +28,7 @@ class _MyDevelopmentBodyPage extends State with Trans, Co void initState() { super.initState(); Flurry.logEvent("myDevelopmentBody"); - if (!Cache().hasPurchased) { + if (!Cache().hasPurchased || true) { Timer( Duration(milliseconds: 2000), () => { @@ -38,10 +38,10 @@ class _MyDevelopmentBodyPage extends State with Trans, Co builder: (BuildContext context) { setContext(context); return DialogPremium( - unlocked: false, + unlocked: Cache().hasPurchased, unlockRound: 2, function: "My Whole Body Development", - unlockedText: "", + unlockedText: null, onTap: () => {Navigator.of(context).pop(), Navigator.of(context).pop()}, onCancel: () => {Navigator.of(context).pop(), Navigator.of(context).pop()}, ); diff --git a/lib/view/mydevelopment_muscle_page.dart b/lib/view/mydevelopment_muscle_page.dart index 63885e2..65dc5c2 100644 --- a/lib/view/mydevelopment_muscle_page.dart +++ b/lib/view/mydevelopment_muscle_page.dart @@ -217,27 +217,37 @@ class _MyDevelopmentMuscleState extends State with Comm ))); exerciseTypes.add(explanation); + LinkedHashMap rc = LinkedHashMap(); tree.forEach((name, list) { + rc = _getChildList(list, bloc); + final List children = rc['list']; + final bool hasNoData = rc['hasNoData']; exerciseTypes.add(Container( margin: const EdgeInsets.only(left: 4.0), child: TreeViewChild( startExpanded: false, - parent: _getExerciseWidget(exerciseTypeName: name), - children: _getChildList(list, bloc), + parent: _getExerciseWidget(exerciseTypeName: name, noData: hasNoData), + children: children, ))); }); return exerciseTypes; } - Widget _getExerciseWidget({@required String exerciseTypeName, List list}) { - return TreeviewParentWidget(text: exerciseTypeName); + Widget _getExerciseWidget({@required String exerciseTypeName, bool noData = false}) { + return TreeviewParentWidget( + text: exerciseTypeName, + backgroundColor: !noData ? Colors.white38 : Colors.white12, + color: !noData ? Colors.blue[800] : Colors.grey[400]); } - List _getChildList(List listWorkoutTree, DevelopmentByMuscleBloc bloc) { + LinkedHashMap _getChildList(List listWorkoutTree, DevelopmentByMuscleBloc bloc) { + LinkedHashMap rc = LinkedHashMap(); List list = List(); + bool hasSummaryNoData = true; listWorkoutTree.forEach((element) { - bool hasNoData = (bloc.listChartData[element.exerciseTypeId] == null); + final bool hasNoData = (bloc.listChartData[element.exerciseTypeId] == null); + hasSummaryNoData = hasSummaryNoData && hasNoData; String unit = " kg"; if (bloc.diagramType == DiagramType.percent) { unit = " %"; @@ -320,7 +330,8 @@ class _MyDevelopmentMuscleState extends State with Comm ), )); }); - - return list; + rc['list'] = list; + rc['hasNoData'] = hasSummaryNoData; + return rc; } } diff --git a/lib/view/mydevelopment_page.dart b/lib/view/mydevelopment_page.dart index 43be52d..e3fa85f 100644 --- a/lib/view/mydevelopment_page.dart +++ b/lib/view/mydevelopment_page.dart @@ -106,10 +106,10 @@ class _MyDevelopmentPage extends State with Trans { context: context, builder: (BuildContext context) { return DialogPremium( - unlocked: false, - unlockRound: 2, + unlocked: Cache().hasPurchased, + unlockRound: 3, function: "Predictions", - unlockedText: "", + unlockedText: null, onTap: () => {Navigator.of(context).pop()}, ); }) diff --git a/lib/view/myexcercise_plan_page.dart b/lib/view/myexcercise_plan_page.dart index bd56e75..dab24f5 100644 --- a/lib/view/myexcercise_plan_page.dart +++ b/lib/view/myexcercise_plan_page.dart @@ -97,10 +97,10 @@ class _MyExercisePlanPage extends State with Trans, Logging context: context, builder: (BuildContext context) { return DialogPremium( - unlocked: false, + unlocked: Cache().hasPurchased, unlockRound: 1, function: "Suggested Training Plan", - unlockedText: "", + unlockedText: null, onTap: () => {Navigator.of(context).pop()}, onCancel: () => {Navigator.of(context).pop()}, ); @@ -111,25 +111,25 @@ class _MyExercisePlanPage extends State with Trans, Logging ImageButton( width: imageWidth, textAlignment: Alignment.topLeft, - text: t("My Special Plan"), + text: t("Training Programs"), style: GoogleFonts.robotoMono( textStyle: TextStyle( fontSize: 14, color: Colors.white, fontWeight: FontWeight.bold, backgroundColor: Colors.black54.withOpacity(0.4))), - image: "asset/image/exercise_plan_special.jpg", + image: "asset/image/exercise_plan_stars.jpg", left: 5, onTap: () => { - Flurry.logEvent("SpecialTrainingPlan"), + Flurry.logEvent("SpecialTraining Programs"), showDialog( context: context, builder: (BuildContext context) { return DialogPremium( - unlocked: false, + unlocked: Cache().hasPurchased, unlockRound: 1, - function: "My Special Plan", - unlockedText: "", + function: "Training Programs", + unlockedText: null, onTap: () => {Navigator.of(context).pop()}, onCancel: () => {Navigator.of(context).pop()}, ); @@ -137,7 +137,7 @@ class _MyExercisePlanPage extends State with Trans, Logging }, isLocked: true, ), - ImageButton( + /* ImageButton( width: imageWidth, textAlignment: Alignment.topLeft, text: t("Star's Exercise Plan"), @@ -165,7 +165,7 @@ class _MyExercisePlanPage extends State with Trans, Logging }) }, isLocked: true, - ), + ), */ hiddenPlanWidget(exerciseRepository), hiddenTrainingWidget(), ]), diff --git a/lib/view/sales_page.dart b/lib/view/sales_page.dart index c0cf855..c2f6fbe 100644 --- a/lib/view/sales_page.dart +++ b/lib/view/sales_page.dart @@ -2,6 +2,7 @@ import 'package:aitrainer_app/bloc/sales/sales_bloc.dart'; import 'package:aitrainer_app/service/logging.dart'; import 'package:aitrainer_app/util/trans.dart'; import 'package:aitrainer_app/widgets/app_bar_min.dart'; +import 'package:aitrainer_app/widgets/dialog_common.dart'; import 'package:aitrainer_app/widgets/sales_button.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -23,7 +24,25 @@ class SalesPage extends StatelessWidget with Trans, Logging { if (state is SalesError) { log("Error: " + state.message); Scaffold.of(context).showSnackBar( - SnackBar(backgroundColor: Colors.orange, content: Text(state.message, style: TextStyle(color: Colors.white)))); + SnackBar(backgroundColor: Colors.orange, content: Text(t(state.message), style: TextStyle(color: Colors.white)))); + } else if (state is SalesSuccessful) { + showDialog( + context: context, + builder: (BuildContext context) { + return DialogCommon( + title: t("Successful Purchase"), + descriptions: t("Now you can use the premium features of WorkoutTest!"), + text: "OK", + onTap: () => { + Navigator.of(context).pop(), + Navigator.of(context).pushNamed("home"), + }, + onCancel: () => { + Navigator.of(context).pop(), + Navigator.of(context).pushNamed("home"), + }, + ); + }); } }, builder: (context, state) { final salesBloc = BlocProvider.of(context); @@ -38,8 +57,6 @@ class SalesPage extends StatelessWidget with Trans, Logging { } Widget salesWidget(SalesBloc bloc) { - final double mediaWidth = MediaQuery.of(context).size.width; - final double imageWidth = (mediaWidth - 5) / 2; return Container( decoration: BoxDecoration( image: DecorationImage( @@ -54,7 +71,7 @@ class SalesPage extends StatelessWidget with Trans, Logging { Divider(), Container( padding: EdgeInsets.only(left: 65, right: 65), - child: Text("Unleash Your Development Now!", + child: Text(t("Unleash Your Development Now!"), textAlign: TextAlign.center, maxLines: 4, softWrap: true, @@ -77,7 +94,7 @@ class SalesPage extends StatelessWidget with Trans, Logging { Divider(), Container( padding: EdgeInsets.only(left: 45, right: 45), - child: Text("Learn about your development, enjoy AI-driven predictions of all of your skills and bodyparts.", + child: Text(t("Learn about your development, enjoy AI-driven predictions of all of your skills and bodyparts."), textAlign: TextAlign.left, maxLines: 4, softWrap: true, @@ -106,47 +123,45 @@ class SalesPage extends StatelessWidget with Trans, Logging { Container( padding: EdgeInsets.only(left: 55, right: 55), child: Text( - "Subscription Conditions", + t("Subscription Conditions"), style: GoogleFonts.inter(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.white), )), Divider(), Container( padding: EdgeInsets.only(left: 55, right: 55), child: Text( - "Payment will be charged to your account. Subscription automatically renews unless auto-renew is turned off at least 24 hourse before the end of the current period", + t("Payment will be charged to your account. Subscription automatically renews unless auto-renew is turned off at least 24 hours before the end of the current period"), style: GoogleFonts.inter(fontSize: 12, color: Colors.white), )), Divider(), Container( padding: EdgeInsets.only(left: 55, right: 55), child: Text( - "Account will be charged for renewal within 24 hours prior to the end of the current period", + t("Account will be charged for renewal within 24 hours prior to the end of the current period"), style: GoogleFonts.inter(fontSize: 12, color: Colors.white), )), ])), ])); - ; } List getButtons(SalesBloc bloc) { List buttons = List(); bloc.product2Display.forEach((element) { - final String title = element.sort == 3 ? "Montly" : "Annual"; - final String interval = element.sort == 3 ? " / month" : " / year"; - final String desc4 = element.sort == 1 ? "" : "AI driven predictions"; + final String title = element.sort == 3 ? t("Montly") : t("Annual"); + final String desc4 = element.sort == 1 ? "" : t("AI driven predictions"); String badge; if (element.sort == 2) { - badge = "14% discount"; + badge = t("14% discount"); } else if (element.sort == 1) { - badge = "2 months free"; + badge = t("2 months free"); } Widget button = SalesButton( title: title, price: element.localizedPrice, - desc1: "Development programs", - desc2: "Suggestions based on your actual status", - desc3: "Special customized training plans", + desc1: t("Development programs"), + desc2: t("Suggestions based on your actual status"), + desc3: t("Special customized training plans"), desc4: desc4, descStyle: GoogleFonts.inter(fontSize: 10, color: Colors.blue[800]), badgeText: badge, diff --git a/lib/view/settings.dart b/lib/view/settings.dart index 7e284b6..2f16d90 100644 --- a/lib/view/settings.dart +++ b/lib/view/settings.dart @@ -10,6 +10,7 @@ import 'package:aitrainer_app/widgets/bottom_nav.dart'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:modal_progress_hud/modal_progress_hud.dart'; import 'package:toggle_switch/toggle_switch.dart'; // ignore: must_be_immutable @@ -40,16 +41,15 @@ class SettingsPage extends StatelessWidget with Trans { SnackBar(backgroundColor: Colors.orange, content: Text(state.message, style: TextStyle(color: Colors.white)))); } else if (state is SettingsReady) { menuBloc.add(MenuRecreateTree()); - } else if (state is SettingsLoading) { - Scaffold.of(context).showSnackBar(SnackBar( - duration: Duration(milliseconds: 100), - backgroundColor: Colors.transparent, - content: Container(child: Center(child: CircularProgressIndicator())))); } - }, - // ignore: missing_return - builder: (context, state) { - return settingsUI(context, settingsBloc, menuBloc); + }, builder: (context, state) { + return ModalProgressHUD( + child: settingsUI(context, settingsBloc, menuBloc), + inAsyncCall: state is SettingsLoading, + opacity: 0.5, + color: Colors.black54, + progressIndicator: CircularProgressIndicator(), + ); }), ), ), @@ -101,7 +101,6 @@ class SettingsPage extends StatelessWidget with Trans { inactiveFgColor: Colors.grey[900], labels: [t('Live-Server'), t('Test-Server')], onToggle: (index) { - //Cache().setServer(index == 0); settingsBloc.add(SettingsSetServer(live: index == 0)); }, ), diff --git a/lib/widgets/bmi_widget.dart b/lib/widgets/bmi_widget.dart index 13f90a9..1a572af 100644 --- a/lib/widgets/bmi_widget.dart +++ b/lib/widgets/bmi_widget.dart @@ -2,7 +2,6 @@ import 'package:aitrainer_app/bloc/exercise_new/exercise_new_bloc.dart'; import 'package:aitrainer_app/localization/app_localization.dart'; import 'package:aitrainer_app/util/trans.dart'; import 'package:animated_widgets/widgets/rotation_animated.dart'; -import 'package:flurry/flurry.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; @@ -71,7 +70,6 @@ class _BMIState extends State with Trans { @override Widget build(BuildContext context) { setContext(context); - Flurry.logEvent("BMI"); double mediaWidth = MediaQuery.of(context).size.width * .8; double mediaHeight = MediaQuery.of(context).size.height * .8; //print("w " + mediaWidth.toString() + "h " + mediaHeight.toString()); @@ -109,18 +107,35 @@ class _BMIState extends State with Trans { fontSize: 30, color: Colors.orange[500], )), - Text(widget.exerciseBloc.bmi.toStringAsFixed(1) + "%", - style: GoogleFonts.archivoBlack( - shadows: [ - Shadow( - offset: Offset(5.0, 5.0), - blurRadius: 3.0, - color: Colors.black54, - ), - ], - fontSize: 60, - color: Colors.orange[500], - )), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(widget.exerciseBloc.bmi.toStringAsFixed(1), + style: GoogleFonts.archivoBlack( + shadows: [ + Shadow( + offset: Offset(5.0, 5.0), + blurRadius: 3.0, + color: Colors.black54, + ), + ], + fontSize: 60, + color: Colors.orange[500], + )), + Text(" kg/m2", + style: GoogleFonts.archivoBlack( + shadows: [ + Shadow( + offset: Offset(5.0, 5.0), + blurRadius: 3.0, + color: Colors.black54, + ), + ], + fontSize: 30, + color: Colors.orange[500], + )), + ], + ), Divider(color: Colors.transparent), Stack(alignment: Alignment.center, children: [ Container( @@ -153,15 +168,31 @@ class _BMIState extends State with Trans { color: Colors.yellow[200], )), ), - Text("BMI" + ": " + widget.exerciseBloc.goalBMI.toStringAsFixed(0), + Text("BMI" + " " + t("first step") + ": " + widget.exerciseBloc.goalMilestoneBMI.toStringAsFixed(1), style: GoogleFonts.archivoBlack( - fontSize: 40, + fontSize: 20, + color: Colors.orange[200], + )), + Text( + t("Bodyweight") + + " " + + t("first step") + + ": " + + widget.exerciseBloc.goalMilestoneWeight.toStringAsFixed(0) + + " kg", + style: GoogleFonts.archivoBlack( + fontSize: 20, + color: Colors.orange[200], + )), + Text("BMI" + " " + t("goal") + ": " + widget.exerciseBloc.goalBMI.toStringAsFixed(1), + style: GoogleFonts.archivoBlack( + fontSize: 20, color: Colors.orange[500], )), - Text(t("Weight") + ": " + widget.exerciseBloc.goalWeight.toStringAsFixed(0) + " kg", + Text(t("Bodyweight") + " " + t("goal") + ": " + widget.exerciseBloc.goalWeight.toStringAsFixed(0) + " kg", style: GoogleFonts.archivoBlack( - fontSize: 30, - color: Colors.orange[200], + fontSize: 20, + color: Colors.orange[500], )), ])))))); } diff --git a/lib/widgets/bmr_widget.dart b/lib/widgets/bmr_widget.dart index b9a80b2..fb6f193 100644 --- a/lib/widgets/bmr_widget.dart +++ b/lib/widgets/bmr_widget.dart @@ -2,7 +2,6 @@ import 'package:aitrainer_app/bloc/exercise_new/exercise_new_bloc.dart'; import 'package:aitrainer_app/localization/app_localization.dart'; import 'package:aitrainer_app/model/fitness_state.dart'; import 'package:aitrainer_app/util/trans.dart'; -import 'package:flurry/flurry.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; @@ -26,6 +25,7 @@ class _BMRState extends State with Trans { final FocusNode _nodeText1 = FocusNode(); final FocusNode _nodeText2 = FocusNode(); + final FocusNode _nodeText3 = FocusNode(); KeyboardActionsConfig _buildConfig(BuildContext context) { return KeyboardActionsConfig( @@ -34,6 +34,21 @@ class _BMRState extends State with Trans { keyboardSeparatorColor: Colors.black26, nextFocus: true, actions: [ + KeyboardActionsItem(focusNode: _nodeText3, toolbarButtons: [ + (node) { + return GestureDetector( + onTap: () => node.unfocus(), + child: Container( + padding: EdgeInsets.all(8.0), + color: Colors.orange[500], + child: Text( + AppLocalizations.of(context).translate("Done"), + style: TextStyle(color: Colors.white), + ), + ), + ); + } + ]), KeyboardActionsItem(focusNode: _nodeText2, toolbarButtons: [ (node) { return GestureDetector( @@ -75,7 +90,6 @@ class _BMRState extends State with Trans { @override Widget build(BuildContext context) { setContext(context); - Flurry.logEvent("BMR"); return Form( child: Scaffold( resizeToAvoidBottomInset: true, @@ -99,6 +113,35 @@ class _BMRState extends State with Trans { getWeightInput(), getFitnessLevel(), Divider(), + Container( + padding: EdgeInsets.only(left: 35, right: 35), + child: Text(t("Resting Metabolic Rate"), + textAlign: TextAlign.center, + style: GoogleFonts.archivoBlack( + shadows: [ + Shadow( + offset: Offset(5.0, 5.0), + blurRadius: 3.0, + color: Colors.black54, + ), + ], + fontSize: 24, + color: Colors.orange[300], + )), + ), + Text(widget.exerciseBloc.bmrEnergy.toStringAsFixed(0) + " kCal", + style: GoogleFonts.archivoBlack( + shadows: [ + Shadow( + offset: Offset(5.0, 5.0), + blurRadius: 3.0, + color: Colors.black54, + ), + ], + fontSize: 30, + color: Colors.orange[300], + )), + SizedBox(height: 20), Container( padding: EdgeInsets.only(left: 35, right: 35), child: Text(t("Basal Metabolic Rate"), @@ -111,7 +154,7 @@ class _BMRState extends State with Trans { color: Colors.black54, ), ], - fontSize: 30, + fontSize: 24, color: Colors.orange[500], )), ), @@ -124,7 +167,7 @@ class _BMRState extends State with Trans { color: Colors.black54, ), ], - fontSize: 50, + fontSize: 30, color: Colors.orange[500], )), Container( @@ -168,6 +211,30 @@ class _BMRState extends State with Trans { } } + Widget getBirthyearInput() { + return Flexible( + child: TextFormField( + focusNode: _nodeText3, + decoration: InputDecoration( + contentPadding: EdgeInsets.only(left: 15, top: 5, bottom: 5), + labelText: AppLocalizations.of(context).translate("Birth Year"), + labelStyle: GoogleFonts.inter(fontSize: 16, color: Colors.yellow[50]), + fillColor: Colors.black38, + filled: true, + border: OutlineInputBorder( + gapPadding: 4.0, + borderRadius: BorderRadius.circular(12.0), + borderSide: BorderSide(color: Colors.white12, width: 0.4), + ), + ), + initialValue: widget.exerciseBloc.birthYear.toStringAsFixed(0), + keyboardType: TextInputType.numberWithOptions(decimal: false), + textInputAction: TextInputAction.done, + style: GoogleFonts.archivoBlack(fontSize: 20, color: Colors.yellow[300]), + onChanged: (value) => {widget.exerciseBloc.add(ExerciseNewBirthyearChange(value: int.parse(value)))}), + ); + } + Widget getFitnessLevel() { String fitnessLevel = widget.exerciseBloc.fitnessLevel; return Container( @@ -254,14 +321,18 @@ class _BMRState extends State with Trans { Widget getWeightInput() { return Container( - padding: EdgeInsets.only(top: 15, left: 65, right: 65, bottom: 10), + padding: EdgeInsets.only(top: 15, left: 35, right: 35, bottom: 10), alignment: Alignment.center, child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ + getBirthyearInput(), + SizedBox( + width: 5, + ), getHeightInput(), SizedBox( - width: 10, + width: 5, ), Flexible( child: TextFormField( diff --git a/lib/widgets/bottom_nav.dart b/lib/widgets/bottom_nav.dart index 95ad802..bb4c540 100644 --- a/lib/widgets/bottom_nav.dart +++ b/lib/widgets/bottom_nav.dart @@ -1,9 +1,7 @@ import 'package:aitrainer_app/localization/app_localization.dart'; -import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/service/logging.dart'; import 'package:aitrainer_app/util/common.dart'; import 'package:aitrainer_app/util/trans.dart'; -import 'package:badges/badges.dart'; import 'package:flurry/flurry.dart'; import 'package:flutter/material.dart'; import 'package:gradient_bottom_navigation_bar/gradient_bottom_navigation_bar.dart'; diff --git a/lib/widgets/dialog_premium.dart b/lib/widgets/dialog_premium.dart index 1fefcc0..27baa49 100644 --- a/lib/widgets/dialog_premium.dart +++ b/lib/widgets/dialog_premium.dart @@ -26,9 +26,6 @@ class DialogPremium extends StatefulWidget { : super(key: key) { description = description ?? ""; function = function ?? ""; - unlockedText = unlockedText ?? ""; - - unlocked = true; } @override @@ -92,7 +89,7 @@ class _DialogPremiumState extends State with Trans { Text( widget.unlocked ? t("Keep testing") : t("Go Premium") + " ", style: GoogleFonts.archivoBlack( - fontSize: widget.unlocked ? 18 : 24, + fontSize: widget.unlocked ? 20 : 24, color: Colors.yellow[400], shadows: [ Shadow( @@ -226,9 +223,10 @@ class _DialogPremiumState extends State with Trans { List getDescriptionText() { List list = List(); - /* if (widget.unlocked) { + if (widget.unlockedText != null) { list.add(TextSpan(text: widget.unlockedText)); - } */ + return list; + } list.add(TextSpan(text: t("The"))); list.add(TextSpan(text: t(" "))); list.add( diff --git a/lib/widgets/home.dart b/lib/widgets/home.dart index 94f2423..93456a7 100644 --- a/lib/widgets/home.dart +++ b/lib/widgets/home.dart @@ -2,14 +2,12 @@ import 'package:aitrainer_app/bloc/session/session_bloc.dart'; import 'package:aitrainer_app/bloc/settings/settings_bloc.dart'; import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/service/logging.dart'; -import 'package:aitrainer_app/util/platform_purchase.dart'; import 'package:aitrainer_app/view/login.dart'; import 'package:aitrainer_app/view/menu_page.dart'; import 'package:aitrainer_app/view/registration.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; -import 'package:flutter/semantics.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'loading.dart'; diff --git a/lib/widgets/menu_page_widget.dart b/lib/widgets/menu_page_widget.dart index 69c2685..df2047b 100644 --- a/lib/widgets/menu_page_widget.dart +++ b/lib/widgets/menu_page_widget.dart @@ -8,14 +8,12 @@ import 'package:aitrainer_app/localization/app_localization.dart'; import 'package:aitrainer_app/model/cache.dart'; import 'package:aitrainer_app/model/workout_menu_tree.dart'; import 'package:aitrainer_app/service/logging.dart'; -import 'package:aitrainer_app/util/not_found_exception.dart'; import 'package:aitrainer_app/util/trans.dart'; import 'package:aitrainer_app/widgets/dialog_common.dart'; import 'package:badges/badges.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/painting.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:google_fonts/google_fonts.dart'; diff --git a/lib/widgets/treeview_parent_widget.dart b/lib/widgets/treeview_parent_widget.dart index a31561a..67200c0 100644 --- a/lib/widgets/treeview_parent_widget.dart +++ b/lib/widgets/treeview_parent_widget.dart @@ -4,21 +4,23 @@ import 'package:google_fonts/google_fonts.dart'; class TreeviewParentWidget extends StatelessWidget { final String text; + final Color backgroundColor; + final Color color; //final DateTime lastModified; - TreeviewParentWidget({@required this.text}); + TreeviewParentWidget({@required this.text, this.backgroundColor = Colors.white38, this.color}); @override Widget build(BuildContext context) { Widget parentWidget = Text( this.text, - style: GoogleFonts.archivoBlack(fontSize: 24, color: Colors.blue[800], backgroundColor: Colors.transparent), + style: GoogleFonts.archivoBlack(fontSize: 24, color: color ?? Colors.blue[800], backgroundColor: Colors.transparent), ); Icon icon = Icon(Icons.person); return Card( - color: Colors.white38, + color: backgroundColor, shadowColor: Colors.black54, elevation: 0.0, child: ListTile( diff --git a/pubspec.lock b/pubspec.lock index 1ace789..692d4c8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -84,7 +84,7 @@ packages: name: build url: "https://pub.dartlang.org" source: hosted - version: "1.6.0" + version: "1.6.1" build_config: dependency: transitive description: @@ -98,28 +98,28 @@ packages: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "2.1.4" + version: "2.1.6" build_resolvers: dependency: transitive description: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "1.5.1" + version: "1.5.2" build_runner: dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "1.10.11" + version: "1.10.13" build_runner_core: dependency: transitive description: name: build_runner_core url: "https://pub.dartlang.org" source: hosted - version: "6.1.5" + version: "6.1.6" built_collection: dependency: transitive description: @@ -189,7 +189,7 @@ packages: name: code_builder url: "https://pub.dartlang.org" source: hosted - version: "3.5.0" + version: "3.6.0" collection: dependency: transitive description: @@ -432,7 +432,7 @@ packages: name: flutter_html url: "https://pub.dartlang.org" source: hosted - version: "1.1.1" + version: "1.2.0" flutter_keyboard_visibility: dependency: transitive description: @@ -447,6 +447,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.8.1" + flutter_layout_grid: + dependency: transitive + description: + name: flutter_layout_grid + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.3" flutter_localizations: dependency: "direct main" description: flutter @@ -713,7 +720,7 @@ packages: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "1.6.24" + version: "1.6.27" path_provider_linux: dependency: transitive description: @@ -727,7 +734,7 @@ packages: name: path_provider_macos url: "https://pub.dartlang.org" source: hosted - version: "0.0.4+6" + version: "0.0.4+8" path_provider_platform_interface: dependency: transitive description: @@ -797,7 +804,7 @@ packages: name: provider url: "https://pub.dartlang.org" source: hosted - version: "4.3.2+3" + version: "4.3.2+4" pub_semver: dependency: transitive description: @@ -812,6 +819,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.7" + purchases_flutter: + dependency: "direct main" + description: + name: purchases_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" quiver: dependency: transitive description: @@ -846,7 +860,7 @@ packages: name: sentry url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "4.0.3" shared_preferences: dependency: "direct dev" description: @@ -963,14 +977,14 @@ packages: name: sqflite url: "https://pub.dartlang.org" source: hosted - version: "1.3.2+1" + version: "1.3.2+2" sqflite_common: dependency: transitive description: name: sqflite_common url: "https://pub.dartlang.org" source: hosted - version: "1.0.2+1" + version: "1.0.3" stack_trace: dependency: transitive description: @@ -1041,6 +1055,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.3.12-nullsafety.5" + timeline_tile: + dependency: "direct main" + description: + name: timeline_tile + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" timing: dependency: transitive description: @@ -1062,13 +1083,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0-nullsafety.3" - usage: - dependency: transitive - description: - name: usage - url: "https://pub.dartlang.org" - source: hosted - version: "3.4.2" uuid: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9c4cb66..8fea36d 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.4+46 +version: 1.1.5+48 environment: sdk: ">=2.7.0 <3.0.0" @@ -27,7 +27,7 @@ dependencies: cupertino_icons: ^1.0.0 google_fonts: ^1.1.1 devicelocale: ^0.3.3 - sentry: ^3.0.1 + sentry: ^4.0.3 flutter_bloc: ^6.1.1 equatable: ^1.2.5 #freezed: ^0.12.2 @@ -45,11 +45,12 @@ dependencies: #health: ^3.0.0 stop_watch_timer: ^0.6.0+1 #geolocator: ^6.1.13 - #flutter_inapp_purchase: ^3.0.1 modal_progress_hud: ^0.1.3 flutter_html: ^1.1.1 wakelock: ^0.2.1+1 - + timeline_tile: ^1.0.0 + purchases_flutter: ^2.0.0 + firebase_core: ^0.5.0 firebase_analytics: ^6.2.0 diff --git a/test_driver/service/customer_service.dart b/test_driver/service/customer_service.dart index 865ed0e..d16e1c2 100644 --- a/test_driver/service/customer_service.dart +++ b/test_driver/service/customer_service.dart @@ -1,5 +1,3 @@ -import 'dart:html'; - import 'package:aitrainer_app/model/customer.dart'; import 'package:flutter/material.dart'; import '../../lib/helper/database.dart';