WT1.1.5 In-app-purchase

This commit is contained in:
bossanyit 2021-01-24 14:34:13 +01:00
parent d44fefe9af
commit 71efd3f349
52 changed files with 847 additions and 367 deletions

View File

@ -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'

View File

@ -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!"
}

View File

@ -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."
}

View File

@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>10.0</string>
<string>12.0</string>
</dict>
</plist>

View File

@ -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

View File

@ -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

View File

@ -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 = "<group>"; };
BB81344F24BB4BE10078D9A4 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
/* 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";

View File

@ -31,11 +31,15 @@ class ExerciseNewBloc extends Bloc<ExerciseNewEvent, ExerciseNewState> 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<ExerciseNewEvent, ExerciseNewState> 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<ExerciseNewEvent, ExerciseNewState> 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<ExerciseNewEvent, ExerciseNewState> 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<ExerciseNewEvent, ExerciseNewState> 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<ExerciseNewEvent, ExerciseNewState> 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));

View File

@ -28,6 +28,13 @@ class ExerciseNewQuantityUnitChange extends ExerciseNewEvent {
List<Object> get props => [quantity];
}
class ExerciseNewBirthyearChange extends ExerciseNewEvent {
final int value;
const ExerciseNewBirthyearChange({this.value});
@override
List<Object> get props => [value];
}
class ExerciseNewWeightChange extends ExerciseNewEvent {
final double value;
const ExerciseNewWeightChange({this.value});

View File

@ -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';

View File

@ -164,7 +164,7 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> with Trans, Logging {
ability = ExerciseAbility.endurance;
break;
case "Cardio":
case "Body Compositions":
case "My Body":
ability = ExerciseAbility.none;
break;
}

View File

@ -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<ResultEvent, ResultState> 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

View File

@ -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<SalesEvent, SalesState> 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<SalesEvent, SalesState> 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<SalesEvent, SalesState> 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<SalesEvent, SalesState> 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<SalesEvent, SalesState> with Logging {
productTest.dateView = DateTime.now();
//ProductTestApi().saveProductTest(productTest);
//Cache().productTests.add(productTest);
} */
}
}

View File

@ -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});

View File

@ -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';

View File

@ -74,7 +74,7 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> with Logging {
HealthDataType.RESTING_HEART_RATE
];
final HealthFactory health = HealthFactory(); */
DateTime now = DateTime.now();
//DateTime now = DateTime.now();
//List<HealthDataPoint> _healthDataList = await health.getHealthDataFromTypes(now.subtract(Duration(minutes: 5)), now, types);
//log(_healthDataList.toString());
}

View File

@ -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<Null> _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<Null> main() async {
@ -119,8 +115,10 @@ Future<Null> main() async {
runZonedGuarded<Future<Null>>(() async {
final WorkoutTreeRepository menuTreeRepository = WorkoutTreeRepository();
WidgetsFlutterBinding.ensureInitialized();
print(" -- FireBase init..");
await FirebaseApi().initializeFlutterFire();
runApp(MultiBlocProvider(
providers: [
BlocProvider<SessionBloc>(
@ -157,8 +155,6 @@ Future<Null> main() async {
Future<void> 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),

View File

@ -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<Exercise> _exercises;
ExercisePlan _myExercisePlan;
List<Property> _properties;
List<Product> _products;
List<wt_product.Product> _products;
List<Purchase> _purchases = List();
List<ProductTest> _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";
}

View File

@ -55,7 +55,6 @@ class ExerciseType {
this.parents.add(parent['exerciseTreeId']);
});
}
;
}
Map<String, dynamic> toJson() => {

View File

@ -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'];

View File

@ -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) {

View File

@ -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;

View File

@ -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 }

View File

@ -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<void> getUserByGoogle() async {
final User modelUser = this.user;
Map<String, dynamic> userData = await FirebaseApi().signInWithGoogle();
if (userData == null || userData['email'] == null) {
throw new Exception("Google login was not successful");
}
modelUser.email = userData['email'];
try {
Map<String, dynamic> 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);
}
}

View File

@ -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<String> _buildImage(String imageUrl) async {
/* Future<String> _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<void> createTree() async {
isEnglish = AppLanguage().appLocal == Locale('en');

View File

@ -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;

View File

@ -165,41 +165,37 @@ class FirebaseApi with logging.Logging {
Future<Map<String, dynamic>> signInWithGoogle() async {
Map<String, dynamic> 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");
}

View File

@ -15,7 +15,7 @@ class PurchaseApi with Logging {
final Iterable json = jsonDecode(body);
final List<Purchase> purchases = json.map((purchase) => Purchase.fromJson(purchase)).toList();
Cache().setPurchases(purchases);
} on NotFoundException catch (e) {
} on NotFoundException catch (_) {
log("No purchases found");
}
return purchases;

View File

@ -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<void> 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(),
);
}
}

View File

@ -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<IAPItem> _items = [];
List getIAPItems() => _items;
List<PurchasedItem> _purchases = [];
bool _kAutoConsume = true;
final String _kConsumableId = 'consumable';
final InAppPurchaseConnection _connection = InAppPurchaseConnection.instance;
bool _isAvailable = false;
List<ProductDetails> _products = [];
List<PurchaseDetails> _purchases = [];
List<String> _notFoundIds = [];
List<String> _consumables = [];
bool _purchasePending = false;
String _queryProductError;
StreamSubscription<List<PurchaseDetails>> _subscription;
final List<String> _productList = List();
List getProductList() => _productList;
factory PlatformPurchaseApi() {
return _singleton;
}
@ -30,55 +31,152 @@ class PlatformPurchaseApi with Logging {
PlatformPurchaseApi._internal();
Future<void> close() async {
if (_conectionSubscription != null) {
_conectionSubscription.cancel();
_conectionSubscription = null;
_subscription.cancel();
}
Future<void> 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<PurchaseDetails> verifiedPurchases = [];
for (PurchaseDetails purchase in purchaseResponse.pastPurchases) {
if (await _verifyPurchase(purchase)) {
verifiedPurchases.add(purchase);
}
}
//TODO List<String> 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<void> initPurchasePlatformState() async {
Future<void> 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<String> 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<bool> _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<bool>.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<PurchaseDetails> 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<IAPItem> 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<PurchasedItem> 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<PurchasedItem> 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`.
}
}
*/

120
lib/util/purchases.dart Normal file
View File

@ -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<void> 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<void> 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<Offering> 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<void> 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");
}
}
}
}

View File

@ -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`:

View File

@ -5,7 +5,6 @@ import 'package:aitrainer_app/model/customer.dart';
import 'package:aitrainer_app/util/common.dart';
import 'package:aitrainer_app/util/trans.dart';
import 'package:aitrainer_app/widgets/app_bar_min.dart';
import 'package:badges/badges.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:aitrainer_app/widgets/bottom_nav.dart';
import 'package:flutter/material.dart';

View File

@ -4,7 +4,9 @@ import 'dart:ui';
import 'package:aitrainer_app/bloc/result/result_bloc.dart';
import 'package:aitrainer_app/localization/app_language.dart';
import 'package:aitrainer_app/model/cache.dart';
import 'package:aitrainer_app/model/exercise.dart';
import 'package:aitrainer_app/model/exercise_ability.dart';
import 'package:aitrainer_app/model/exercise_type.dart';
import 'package:aitrainer_app/repository/exercise_repository.dart';
import 'package:aitrainer_app/repository/exercise_result_repository.dart';
import 'package:aitrainer_app/util/trans.dart';
@ -15,6 +17,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_bloc/flutter_form_bloc.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:aitrainer_app/model/result.dart';
import 'package:modal_progress_hud/modal_progress_hud.dart';
// ignore: must_be_immutable
class EvaluationPage extends StatelessWidget with Trans {
@ -22,7 +25,7 @@ class EvaluationPage extends StatelessWidget with Trans {
Widget build(BuildContext context) {
LinkedHashMap arguments = ModalRoute.of(context).settings.arguments;
ExerciseRepository exerciseRepository;
// ignore: close_sinks
if (arguments != null) {
exerciseRepository = arguments['exerciseRepository'];
} else {
@ -37,10 +40,22 @@ class EvaluationPage extends StatelessWidget with Trans {
resultType = ResultType.man;
imageUrl = 'asset/image/WT_Results_for_female.png';
}
if (arguments['past'] != null && arguments['past'] == true) {
Exercise exercise = arguments['exercise'];
if (exercise != null) {
ExerciseType exerciseType = exerciseRepository.getExerciseTypeById(exercise.exerciseTypeId);
exerciseRepository.exerciseType = exerciseType;
exerciseRepository.exercise = exercise;
String exerciseDate = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd);
exerciseRepository.getSameExercise(exercise.exerciseTypeId, exerciseDate);
}
}
if (exerciseRepository.exerciseType.getAbility().equalsTo(ExerciseAbility.running)) {
resultType = ResultType.running;
imageUrl = 'asset/image/WT_Results_for_runners.png';
}
setContext(context);
return Scaffold(
appBar: AppBarMin(
@ -61,7 +76,7 @@ class EvaluationPage extends StatelessWidget with Trans {
create: (context) => ResultBloc(
resultRepository: ExerciseResultRepository(resultType: resultType),
exerciseRepository: exerciseRepository,
context: context), //..add(ResultLoad())
context: context),
child: BlocConsumer<ResultBloc, ResultState>(listener: (context, state) {
if (state is ResultError) {
Scaffold.of(context).showSnackBar(
@ -74,7 +89,13 @@ class EvaluationPage extends StatelessWidget with Trans {
}
}, builder: (context, state) {
final resultBloc = BlocProvider.of<ResultBloc>(context);
return getEvaluationWidgets(resultBloc);
return ModalProgressHUD(
child: getEvaluationWidgets(resultBloc),
inAsyncCall: state is ResultLoading,
opacity: 0.5,
color: Colors.black54,
progressIndicator: CircularProgressIndicator(),
);
}))),
bottomNavigationBar: BottomNavigator(bottomNavIndex: 0));
}
@ -89,13 +110,15 @@ class EvaluationPage extends StatelessWidget with Trans {
SliverAppBar(
pinned: true,
backgroundColor: Colors.transparent,
expandedHeight: 50.0,
expandedHeight: 120.0,
collapsedHeight: 80,
toolbarHeight: 30,
automaticallyImplyLeading: false,
flexibleSpace: FlexibleSpaceBar(
title: Text(exerciseName,
textAlign: TextAlign.center,
maxLines: 3,
softWrap: true,
//softWrap: true,
style: GoogleFonts.archivoBlack(
fontSize: 24,
color: Colors.white,
@ -382,14 +405,12 @@ class EvaluationPage extends StatelessWidget with Trans {
Widget getSummary(ResultBloc bloc) {
int index = 0;
List<Text> resultList = List();
/* for (int i = 0; i < bloc.exerciseRepository.actualExerciseList.length; i++) {
print("Q " +
bloc.exerciseRepository.actualExerciseList[i].quantity.toString() +
" Qu: " +
bloc.exerciseRepository.actualExerciseList[i].unitQuantity.toString());
} */
bloc.exerciseRepository.actualExerciseList.forEach((actual) {
final String unit = t(bloc.exerciseRepository.exerciseType.unit);
//final String unit = t(bloc.exerciseRepository.exerciseType.unit);
final String unit = bloc.exerciseRepository.exerciseType.unitQuantityUnit != null
? bloc.exerciseRepository.exerciseType.unitQuantityUnit
: bloc.exerciseRepository.exerciseType.unit;
String exerciseElement = "";
String exerciseRepeats = actual.quantity.toStringAsFixed(0);
if (bloc.exerciseRepository.exerciseType.unit == "second") {
@ -407,7 +428,7 @@ class EvaluationPage extends StatelessWidget with Trans {
}
index++;
resultList.add(
Text(exerciseElement + exerciseRepeats + exerciseUnitQuantity + " " + unit,
Text(exerciseElement + exerciseRepeats + exerciseUnitQuantity + " " + t(unit),
textAlign: TextAlign.center,
maxLines: 2,
softWrap: true,

View File

@ -200,7 +200,7 @@ class _ExerciseLogPage extends State<ExerciseLogPage> with Trans, Common {
iconSize: 36,
icon: Icon(CustomIcon.heart_1, color: Colors.blue[800]),
onPressed: () {
evaluation();
evaluation(exerciseRepository, exercise);
},
),
Cache().hasPurchased
@ -212,7 +212,7 @@ class _ExerciseLogPage extends State<ExerciseLogPage> with Trans, Common {
width: 25,
),
onTap: () {
evaluation();
evaluation(exerciseRepository, exercise);
}),
],
),
@ -230,19 +230,27 @@ class _ExerciseLogPage extends State<ExerciseLogPage> with Trans, Common {
return list;
}
void evaluation() {
showDialog(
context: context,
builder: (BuildContext context) {
return DialogPremium(
unlocked: true,
unlockRound: 1,
unlockedText: t("Enjoy also this premium fetaure to show all old evaluation data of your successful exercises."),
function: "My Exercise Logs",
onTap: () => {Navigator.of(context).pop()},
onCancel: () => {Navigator.of(context).pop()},
);
});
void evaluation(ExerciseRepository exerciseRepository, Exercise exercise) {
if (!Cache().hasPurchased) {
showDialog(
context: context,
builder: (BuildContext context) {
return DialogPremium(
unlocked: Cache().hasPurchased,
unlockRound: 1,
unlockedText: t("Enjoy also this premium fetaure to show all old evaluation data of your successful exercises."),
function: "My Exercise Logs",
onTap: () => {Navigator.of(context).pop()},
onCancel: () => {Navigator.of(context).pop()},
);
});
} else {
LinkedHashMap args = LinkedHashMap();
args['exerciseRepository'] = exerciseRepository;
args['exercise'] = exercise;
args['past'] = true;
Navigator.of(context).pushNamed('evaluationPage', arguments: args);
}
}
void confirmationDialog(ExerciseLogBloc bloc, Exercise exercise) {

View File

@ -22,6 +22,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_form_bloc/flutter_form_bloc.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:modal_progress_hud/modal_progress_hud.dart';
import 'package:stop_watch_timer/stop_watch_timer.dart';
import 'package:wakelock/wakelock.dart';
@ -101,11 +102,14 @@ class _ExerciseNewPageState extends State<ExerciseNewPage> with Trans, Logging {
}
},
builder: (context, state) {
if (state is ExerciseNewReady) {
//LoadingDialog.hide(context);
}
final exerciseBloc = BlocProvider.of<ExerciseNewBloc>(context);
return getExerciseWidget(exerciseBloc, exerciseType, menuBloc);
return ModalProgressHUD(
child: getExerciseWidget(exerciseBloc, exerciseType, menuBloc),
inAsyncCall: state is ExerciseNewLoading,
opacity: 0.5,
color: Colors.black54,
progressIndicator: CircularProgressIndicator(),
);
},
));
}

View File

@ -28,7 +28,7 @@ class _MyDevelopmentBodyPage extends State<MyDevelopmentBodyPage> with Trans, Co
void initState() {
super.initState();
Flurry.logEvent("myDevelopmentBody");
if (!Cache().hasPurchased) {
if (!Cache().hasPurchased || true) {
Timer(
Duration(milliseconds: 2000),
() => {
@ -38,10 +38,10 @@ class _MyDevelopmentBodyPage extends State<MyDevelopmentBodyPage> with Trans, Co
builder: (BuildContext context) {
setContext(context);
return DialogPremium(
unlocked: false,
unlocked: Cache().hasPurchased,
unlockRound: 2,
function: "My Whole Body Development",
unlockedText: "",
unlockedText: null,
onTap: () => {Navigator.of(context).pop(), Navigator.of(context).pop()},
onCancel: () => {Navigator.of(context).pop(), Navigator.of(context).pop()},
);

View File

@ -217,27 +217,37 @@ class _MyDevelopmentMuscleState extends State<MyDevelopmentMusclePage> with Comm
)));
exerciseTypes.add(explanation);
LinkedHashMap<String, dynamic> rc = LinkedHashMap();
tree.forEach((name, list) {
rc = _getChildList(list, bloc);
final List<Widget> children = rc['list'];
final bool hasNoData = rc['hasNoData'];
exerciseTypes.add(Container(
margin: const EdgeInsets.only(left: 4.0),
child: TreeViewChild(
startExpanded: false,
parent: _getExerciseWidget(exerciseTypeName: name),
children: _getChildList(list, bloc),
parent: _getExerciseWidget(exerciseTypeName: name, noData: hasNoData),
children: children,
)));
});
return exerciseTypes;
}
Widget _getExerciseWidget({@required String exerciseTypeName, List<WorkoutMenuTree> list}) {
return TreeviewParentWidget(text: exerciseTypeName);
Widget _getExerciseWidget({@required String exerciseTypeName, bool noData = false}) {
return TreeviewParentWidget(
text: exerciseTypeName,
backgroundColor: !noData ? Colors.white38 : Colors.white12,
color: !noData ? Colors.blue[800] : Colors.grey[400]);
}
List<Widget> _getChildList(List<WorkoutMenuTree> listWorkoutTree, DevelopmentByMuscleBloc bloc) {
LinkedHashMap<String, dynamic> _getChildList(List<WorkoutMenuTree> listWorkoutTree, DevelopmentByMuscleBloc bloc) {
LinkedHashMap<String, dynamic> rc = LinkedHashMap();
List<Widget> list = List();
bool hasSummaryNoData = true;
listWorkoutTree.forEach((element) {
bool hasNoData = (bloc.listChartData[element.exerciseTypeId] == null);
final bool hasNoData = (bloc.listChartData[element.exerciseTypeId] == null);
hasSummaryNoData = hasSummaryNoData && hasNoData;
String unit = " kg";
if (bloc.diagramType == DiagramType.percent) {
unit = " %";
@ -320,7 +330,8 @@ class _MyDevelopmentMuscleState extends State<MyDevelopmentMusclePage> with Comm
),
));
});
return list;
rc['list'] = list;
rc['hasNoData'] = hasSummaryNoData;
return rc;
}
}

View File

@ -106,10 +106,10 @@ class _MyDevelopmentPage extends State<MyDevelopmentPage> with Trans {
context: context,
builder: (BuildContext context) {
return DialogPremium(
unlocked: false,
unlockRound: 2,
unlocked: Cache().hasPurchased,
unlockRound: 3,
function: "Predictions",
unlockedText: "",
unlockedText: null,
onTap: () => {Navigator.of(context).pop()},
);
})

View File

@ -97,10 +97,10 @@ class _MyExercisePlanPage extends State<MyExercisePlanPage> with Trans, Logging
context: context,
builder: (BuildContext context) {
return DialogPremium(
unlocked: false,
unlocked: Cache().hasPurchased,
unlockRound: 1,
function: "Suggested Training Plan",
unlockedText: "",
unlockedText: null,
onTap: () => {Navigator.of(context).pop()},
onCancel: () => {Navigator.of(context).pop()},
);
@ -111,25 +111,25 @@ class _MyExercisePlanPage extends State<MyExercisePlanPage> with Trans, Logging
ImageButton(
width: imageWidth,
textAlignment: Alignment.topLeft,
text: t("My Special Plan"),
text: t("Training Programs"),
style: GoogleFonts.robotoMono(
textStyle: TextStyle(
fontSize: 14,
color: Colors.white,
fontWeight: FontWeight.bold,
backgroundColor: Colors.black54.withOpacity(0.4))),
image: "asset/image/exercise_plan_special.jpg",
image: "asset/image/exercise_plan_stars.jpg",
left: 5,
onTap: () => {
Flurry.logEvent("SpecialTrainingPlan"),
Flurry.logEvent("SpecialTraining Programs"),
showDialog(
context: context,
builder: (BuildContext context) {
return DialogPremium(
unlocked: false,
unlocked: Cache().hasPurchased,
unlockRound: 1,
function: "My Special Plan",
unlockedText: "",
function: "Training Programs",
unlockedText: null,
onTap: () => {Navigator.of(context).pop()},
onCancel: () => {Navigator.of(context).pop()},
);
@ -137,7 +137,7 @@ class _MyExercisePlanPage extends State<MyExercisePlanPage> with Trans, Logging
},
isLocked: true,
),
ImageButton(
/* ImageButton(
width: imageWidth,
textAlignment: Alignment.topLeft,
text: t("Star's Exercise Plan"),
@ -165,7 +165,7 @@ class _MyExercisePlanPage extends State<MyExercisePlanPage> with Trans, Logging
})
},
isLocked: true,
),
), */
hiddenPlanWidget(exerciseRepository),
hiddenTrainingWidget(),
]),

View File

@ -2,6 +2,7 @@ import 'package:aitrainer_app/bloc/sales/sales_bloc.dart';
import 'package:aitrainer_app/service/logging.dart';
import 'package:aitrainer_app/util/trans.dart';
import 'package:aitrainer_app/widgets/app_bar_min.dart';
import 'package:aitrainer_app/widgets/dialog_common.dart';
import 'package:aitrainer_app/widgets/sales_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -23,7 +24,25 @@ class SalesPage extends StatelessWidget with Trans, Logging {
if (state is SalesError) {
log("Error: " + state.message);
Scaffold.of(context).showSnackBar(
SnackBar(backgroundColor: Colors.orange, content: Text(state.message, style: TextStyle(color: Colors.white))));
SnackBar(backgroundColor: Colors.orange, content: Text(t(state.message), style: TextStyle(color: Colors.white))));
} else if (state is SalesSuccessful) {
showDialog(
context: context,
builder: (BuildContext context) {
return DialogCommon(
title: t("Successful Purchase"),
descriptions: t("Now you can use the premium features of WorkoutTest!"),
text: "OK",
onTap: () => {
Navigator.of(context).pop(),
Navigator.of(context).pushNamed("home"),
},
onCancel: () => {
Navigator.of(context).pop(),
Navigator.of(context).pushNamed("home"),
},
);
});
}
}, builder: (context, state) {
final salesBloc = BlocProvider.of<SalesBloc>(context);
@ -38,8 +57,6 @@ class SalesPage extends StatelessWidget with Trans, Logging {
}
Widget salesWidget(SalesBloc bloc) {
final double mediaWidth = MediaQuery.of(context).size.width;
final double imageWidth = (mediaWidth - 5) / 2;
return Container(
decoration: BoxDecoration(
image: DecorationImage(
@ -54,7 +71,7 @@ class SalesPage extends StatelessWidget with Trans, Logging {
Divider(),
Container(
padding: EdgeInsets.only(left: 65, right: 65),
child: Text("Unleash Your Development Now!",
child: Text(t("Unleash Your Development Now!"),
textAlign: TextAlign.center,
maxLines: 4,
softWrap: true,
@ -77,7 +94,7 @@ class SalesPage extends StatelessWidget with Trans, Logging {
Divider(),
Container(
padding: EdgeInsets.only(left: 45, right: 45),
child: Text("Learn about your development, enjoy AI-driven predictions of all of your skills and bodyparts.",
child: Text(t("Learn about your development, enjoy AI-driven predictions of all of your skills and bodyparts."),
textAlign: TextAlign.left,
maxLines: 4,
softWrap: true,
@ -106,47 +123,45 @@ class SalesPage extends StatelessWidget with Trans, Logging {
Container(
padding: EdgeInsets.only(left: 55, right: 55),
child: Text(
"Subscription Conditions",
t("Subscription Conditions"),
style: GoogleFonts.inter(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.white),
)),
Divider(),
Container(
padding: EdgeInsets.only(left: 55, right: 55),
child: Text(
"Payment will be charged to your account. Subscription automatically renews unless auto-renew is turned off at least 24 hourse before the end of the current period",
t("Payment will be charged to your account. Subscription automatically renews unless auto-renew is turned off at least 24 hours before the end of the current period"),
style: GoogleFonts.inter(fontSize: 12, color: Colors.white),
)),
Divider(),
Container(
padding: EdgeInsets.only(left: 55, right: 55),
child: Text(
"Account will be charged for renewal within 24 hours prior to the end of the current period",
t("Account will be charged for renewal within 24 hours prior to the end of the current period"),
style: GoogleFonts.inter(fontSize: 12, color: Colors.white),
)),
])),
]));
;
}
List<Widget> getButtons(SalesBloc bloc) {
List<Widget> buttons = List();
bloc.product2Display.forEach((element) {
final String title = element.sort == 3 ? "Montly" : "Annual";
final String interval = element.sort == 3 ? " / month" : " / year";
final String desc4 = element.sort == 1 ? "" : "AI driven predictions";
final String title = element.sort == 3 ? t("Montly") : t("Annual");
final String desc4 = element.sort == 1 ? "" : t("AI driven predictions");
String badge;
if (element.sort == 2) {
badge = "14% discount";
badge = t("14% discount");
} else if (element.sort == 1) {
badge = "2 months free";
badge = t("2 months free");
}
Widget button = SalesButton(
title: title,
price: element.localizedPrice,
desc1: "Development programs",
desc2: "Suggestions based on your actual status",
desc3: "Special customized training plans",
desc1: t("Development programs"),
desc2: t("Suggestions based on your actual status"),
desc3: t("Special customized training plans"),
desc4: desc4,
descStyle: GoogleFonts.inter(fontSize: 10, color: Colors.blue[800]),
badgeText: badge,

View File

@ -10,6 +10,7 @@ import 'package:aitrainer_app/widgets/bottom_nav.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:modal_progress_hud/modal_progress_hud.dart';
import 'package:toggle_switch/toggle_switch.dart';
// ignore: must_be_immutable
@ -40,16 +41,15 @@ class SettingsPage extends StatelessWidget with Trans {
SnackBar(backgroundColor: Colors.orange, content: Text(state.message, style: TextStyle(color: Colors.white))));
} else if (state is SettingsReady) {
menuBloc.add(MenuRecreateTree());
} else if (state is SettingsLoading) {
Scaffold.of(context).showSnackBar(SnackBar(
duration: Duration(milliseconds: 100),
backgroundColor: Colors.transparent,
content: Container(child: Center(child: CircularProgressIndicator()))));
}
},
// ignore: missing_return
builder: (context, state) {
return settingsUI(context, settingsBloc, menuBloc);
}, builder: (context, state) {
return ModalProgressHUD(
child: settingsUI(context, settingsBloc, menuBloc),
inAsyncCall: state is SettingsLoading,
opacity: 0.5,
color: Colors.black54,
progressIndicator: CircularProgressIndicator(),
);
}),
),
),
@ -101,7 +101,6 @@ class SettingsPage extends StatelessWidget with Trans {
inactiveFgColor: Colors.grey[900],
labels: [t('Live-Server'), t('Test-Server')],
onToggle: (index) {
//Cache().setServer(index == 0);
settingsBloc.add(SettingsSetServer(live: index == 0));
},
),

View File

@ -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<BMI> 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<BMI> with Trans {
fontSize: 30,
color: Colors.orange[500],
)),
Text(widget.exerciseBloc.bmi.toStringAsFixed(1) + "%",
style: GoogleFonts.archivoBlack(
shadows: <Shadow>[
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>[
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>[
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<BMI> 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],
)),
]))))));
}

View File

@ -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<BMR> 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<BMR> 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<BMR> 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<BMR> 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>[
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>[
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<BMR> with Trans {
color: Colors.black54,
),
],
fontSize: 30,
fontSize: 24,
color: Colors.orange[500],
)),
),
@ -124,7 +167,7 @@ class _BMRState extends State<BMR> with Trans {
color: Colors.black54,
),
],
fontSize: 50,
fontSize: 30,
color: Colors.orange[500],
)),
Container(
@ -168,6 +211,30 @@ class _BMRState extends State<BMR> 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<BMR> 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(

View File

@ -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';

View File

@ -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<DialogPremium> 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>[
Shadow(
@ -226,9 +223,10 @@ class _DialogPremiumState extends State<DialogPremium> with Trans {
List<TextSpan> getDescriptionText() {
List<TextSpan> 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(

View File

@ -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';

View File

@ -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';

View File

@ -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(

View File

@ -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:

View File

@ -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

View File

@ -1,5 +1,3 @@
import 'dart:html';
import 'package:aitrainer_app/model/customer.dart';
import 'package:flutter/material.dart';
import '../../lib/helper/database.dart';