WT 1.1.11+3 Evaluation, null-safe fixes
This commit is contained in:
parent
01148c6e39
commit
10c8a0a2d8
@ -9,6 +9,7 @@
|
||||
<application
|
||||
android:name="io.flutter.app.FlutterApplication"
|
||||
android:label="WorkoutTest"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:icon="@mipmap/launcher_icon">
|
||||
<activity
|
||||
android:name="com.aitrainer.aitrainer_app.MainActivity"
|
||||
|
BIN
asset/image/kupa.png
Normal file
BIN
asset/image/kupa.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
BIN
asset/menu/FG_1_test.jpg
Normal file
BIN
asset/menu/FG_1_test.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 100 KiB |
BIN
asset/menu/FG_1_training.jpg
Normal file
BIN
asset/menu/FG_1_training.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 99 KiB |
29
i18n/en.json
29
i18n/en.json
@ -32,7 +32,9 @@
|
||||
"Exception: Customer does not exist or the password is wrong": "The email does not exist or the password is wrong",
|
||||
"Exception: Facebook signup was not successful. Please try another method": "Facebook signup was not successful. Please try another method",
|
||||
"Exception: Customer exists": "The email address has been registered already",
|
||||
"Exception: Exception: Facebook login was not successful": "Facebook signin was not successful",
|
||||
"Exception: The email address has been registered already":"The email address has been registered already",
|
||||
"Exception: Exception: The email address has been registered already":"The email address has been registered already",
|
||||
"Exception: Please type an email address": "Please type an email address",
|
||||
"Exception: Password too short": "Password too short (at least 9 characters)",
|
||||
"Exception: Google Sign In failed":"Google Sign In failed",
|
||||
@ -83,6 +85,7 @@
|
||||
"No": "No",
|
||||
"with": "with",
|
||||
"Do you save this exercise with these parameters?":"Do you save this exercise with these parameters?",
|
||||
"Did you try the MAXIMUM what you can do? Are you sure we save the exercise with ONLY 12 repeats?":"Did you try the MAXIMUM what you can do? Are you sure we save the exercise with ONLY 12 repeats?",
|
||||
"The number of the exercise": "The number of the exercise",
|
||||
"The number of the exercise done with": "The number of the exercise done with",
|
||||
"Please repeat with": "Please repeat with",
|
||||
@ -226,6 +229,7 @@
|
||||
"Almost!": "Almost done!",
|
||||
"You have only 1-2 exercise left to finish!": "You have only 1-2 exercise left to finish!",
|
||||
"exercise!": "exercise!",
|
||||
"exercise": "exercise",
|
||||
"Chest": "Chest",
|
||||
"Back": "Back",
|
||||
"Thigh": "Thigh",
|
||||
@ -409,6 +413,29 @@
|
||||
"Compact Test":"Compact Test",
|
||||
"Custom Test":"Custom Test",
|
||||
"Set": "Set",
|
||||
"Add this exercise to execute it paralell":"Add this exercise to execute it paralell"
|
||||
"Add this exercise to execute it paralell":"Add this exercise to execute it paralell",
|
||||
|
||||
"very_poor": "Very Poor",
|
||||
"poor": "Poor",
|
||||
"below_average": "Below Average",
|
||||
"average": "Average",
|
||||
"above_verage": "Above Average",
|
||||
"good": "Good",
|
||||
"excellent": "Excellent",
|
||||
"elite": "Elite",
|
||||
|
||||
"Training":"Training",
|
||||
"Growth":"Growth",
|
||||
"Compared with...":"Compared with...",
|
||||
"your best":"your best",
|
||||
"your last":"your last",
|
||||
"best":"best",
|
||||
"last":"last",
|
||||
"volumen":"volumen",
|
||||
"Get the Fastlane to your":"Get the Fastlane to your",
|
||||
"Development":"Development",
|
||||
"How can serve you this result?":"How can serve you this result?",
|
||||
"Go":"Go",
|
||||
"Result":"Result"
|
||||
|
||||
}
|
29
i18n/hu.json
29
i18n/hu.json
@ -33,6 +33,7 @@
|
||||
"Customer does not exist or the password is wrong": "A felhasználó nem létezik vagy a jelszó rossz.",
|
||||
"The email does not exist or the password is wrong": "A felhasználó nem létezik vagy a jelszó rossz.",
|
||||
"Exception: Facebook login was not successful": "Facebook bejelentkezés sikertelen",
|
||||
"Exception: Exception: Facebook login was not successful": "Facebook bejelentkezés sikertelen",
|
||||
"Exception: Facebook signup was not successful. Please try another method": "Facebook regisztráció sikertelen. Kérlek próbálj mással regisztrálni",
|
||||
"Exception: Please type an email address": "Kérlek írj be egy email címet",
|
||||
"Exception: Password too short": "A jelszó min. 9 karakterből álljon",
|
||||
@ -40,6 +41,7 @@
|
||||
"Exception: Facebook login cancelled":"Facebook bejelentkezés megszakítva ",
|
||||
"Exception: Facebook login failed":"Facebook bejelentkezés sikertelen",
|
||||
"Exception: The email address has been registered already":"Ezzel email címmel már regisztráltak",
|
||||
"Exception: Exception: The email address has been registered already":"Ezzel email címmel már regisztráltak",
|
||||
"Exception: Google Sign In failed":"Google bejelentkezés sikertelen",
|
||||
"Exception: Apple Sign-In failed":"Apple bejelentkezés sikertelen",
|
||||
"Exception: Apple Sign-In cancelled":"Apple bejelentkezés megszakítva",
|
||||
@ -94,6 +96,7 @@
|
||||
"No": "Nem",
|
||||
"with": "",
|
||||
"Do you save this exercise with these parameters?":"Elmented a gyakorlatot?",
|
||||
"Did you try the MAXIMUM what you can do? Are you sure we save the exercise with ONLY 12 repeats?":"Mindent beleadtál, ez a MAXIMUM, amit végre tudtál hajtani? Biztos vagy benne, hogy CSAK 12 ismétléssel mentsük el a gyakorlatot?",
|
||||
|
||||
|
||||
"repeat": "ismétlés",
|
||||
@ -224,6 +227,7 @@
|
||||
"Almost!": "Majdnem a végén vagy!",
|
||||
"You have only 1-2 exercise left to finish!": "Már csak 1-2 gyakorlat van a hátra!",
|
||||
"exercise!": "gyakorlattal!",
|
||||
"exercise": "gyakorlat",
|
||||
"Chest": "mell",
|
||||
"Back": "hát",
|
||||
"Thigh": "comb",
|
||||
@ -270,7 +274,7 @@
|
||||
"Available Equipments":"Elérhető eszközök",
|
||||
"select your places by tapping":"kattints az edzéshelyszínre",
|
||||
"Available Training Places":"Elérhető edzéshelyszínek",
|
||||
"Please take a relative bigger weight and repeat 12-20 times":"Válassz egy relatív nagyobb súlyt, amivel maximum 12-20 közötti ismétlésre vagy képes",
|
||||
"Please take a relative bigger weight and repeat 12-20 times and do your best! MAXIMIZE it!":"Válassz egy relatív nagyobb súlyt, amivel 12-20 közötti ismétlésre vagy képes, és adj bele mindent! MAXOLD KI!",
|
||||
"Please take a medium weight and repeat 20-30 times": "Válassz egy közepes súlyt, amivel maximum 20-30 közötti ismétlésre vagy képes",
|
||||
"Equipment Filter":"Eszköz szűrő",
|
||||
"Live-Server":"Live-Server",
|
||||
@ -405,7 +409,28 @@
|
||||
"Compact Test":"Kompakt teszt",
|
||||
"Custom Test":"Egyedi teszt",
|
||||
"Set": "Széria",
|
||||
"Add this exercise to execute it paralell":"Adj hozzá egy gyakorlatot a párhuzamos végrehajtáshoz"
|
||||
"Add this exercise to execute it paralell":"Adj hozzá egy gyakorlatot a párhuzamos végrehajtáshoz",
|
||||
|
||||
"very_poor": "Nagyon gyenge",
|
||||
"poor": "Gyenge",
|
||||
"below_average": "Átlag alatti",
|
||||
"average": "Átlagos",
|
||||
"above_verage": "Átlag feletti",
|
||||
"good": "Jó",
|
||||
"excellent": "Kitűnő",
|
||||
"elite": "Elit kategória",
|
||||
|
||||
"Training":"Tréning",
|
||||
"Growth":"Fejlődésem",
|
||||
"Compared with...":"Összehasonlítva...",
|
||||
"your best":"a legjobb",
|
||||
"your last":"az utolsó",
|
||||
"best":"a legjobb",
|
||||
"last":"az utolsó",
|
||||
"volumen":"volumennel",
|
||||
"How can serve you this result?":"Hogyan segít ez az eredmény téged?",
|
||||
"Get the Fastlane to your":"Kapd el a gyorsítósávot a optimális",
|
||||
"Development":"Fejlődésedhez",
|
||||
"Go":"Érdekel",
|
||||
"Result":"Értékelés"
|
||||
}
|
@ -31,18 +31,18 @@ PODS:
|
||||
- Firebase/Messaging (7.3.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseMessaging (~> 7.3.0)
|
||||
- firebase_analytics (8.0.0-dev.0):
|
||||
- firebase_analytics (8.0.0-dev.2):
|
||||
- Firebase/Analytics (= 7.3.0)
|
||||
- firebase_core
|
||||
- Flutter
|
||||
- firebase_auth (1.0.1):
|
||||
- firebase_auth (1.0.3):
|
||||
- Firebase/Auth (= 7.3.0)
|
||||
- firebase_core
|
||||
- Flutter
|
||||
- firebase_core (1.0.2):
|
||||
- firebase_core (1.0.3):
|
||||
- Firebase/CoreOnly (= 7.3.0)
|
||||
- Flutter
|
||||
- firebase_messaging (9.1.0):
|
||||
- firebase_messaging (9.1.1):
|
||||
- Firebase/Messaging (= 7.3.0)
|
||||
- firebase_core
|
||||
- Flutter
|
||||
@ -86,6 +86,10 @@ PODS:
|
||||
- GoogleUtilities/Environment (~> 7.0)
|
||||
- GoogleUtilities/Reachability (~> 7.0)
|
||||
- GoogleUtilities/UserDefaults (~> 7.0)
|
||||
- flurry (0.0.4):
|
||||
- Flurry-iOS-SDK/FlurrySDK
|
||||
- Flutter
|
||||
- Flurry-iOS-SDK/FlurrySDK (11.2.0)
|
||||
- Flutter (1.0.0)
|
||||
- flutter_facebook_auth (2.0.0):
|
||||
- FBSDKCoreKit (~> 9.1.0)
|
||||
@ -181,6 +185,7 @@ DEPENDENCIES:
|
||||
- firebase_auth (from `.symlinks/plugins/firebase_auth/ios`)
|
||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
||||
- flurry (from `.symlinks/plugins/flurry/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_facebook_auth (from `.symlinks/plugins/flutter_facebook_auth/ios`)
|
||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||
@ -210,6 +215,7 @@ SPEC REPOS:
|
||||
- FirebaseInstallations
|
||||
- FirebaseInstanceID
|
||||
- FirebaseMessaging
|
||||
- Flurry-iOS-SDK
|
||||
- FMDB
|
||||
- GoogleAppMeasurement
|
||||
- GoogleDataTransport
|
||||
@ -236,6 +242,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/firebase_core/ios"
|
||||
firebase_messaging:
|
||||
:path: ".symlinks/plugins/firebase_messaging/ios"
|
||||
flurry:
|
||||
:path: ".symlinks/plugins/flurry/ios"
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
flutter_facebook_auth:
|
||||
@ -274,10 +282,10 @@ SPEC CHECKSUMS:
|
||||
FBSDKCoreKit: a00fe2efd780c195a5e09201bf51c56106245b40
|
||||
FBSDKLoginKit: d98498c598ec09de657385a9349a1f21119b7f86
|
||||
Firebase: 26223c695fe322633274198cb19dca8cb7e54416
|
||||
firebase_analytics: c9b8ddc8e864e45cd70761c5d972bd11c83574ab
|
||||
firebase_auth: 9f6491ea8e44570323361ae713a2ae3175b3f21a
|
||||
firebase_core: e6cbb0d1f7091edfcae31559e58224bfc1e455dc
|
||||
firebase_messaging: 9c746d6c52bb05764e73bbe745d0d698e5afb695
|
||||
firebase_analytics: 4c032e04324c47ee6dd7a28bfff831e0ac0d09b6
|
||||
firebase_auth: c2c27e1081671b02f90981e70dad54722198491f
|
||||
firebase_core: b5d81dfd4fb2d6f700e67de34d9a633ae325c4e9
|
||||
firebase_messaging: 7547c99f31466371f9cfcb733d5a1bf32a819872
|
||||
FirebaseAnalytics: 2580c2d62535ae7b644143d48941fcc239ea897a
|
||||
FirebaseAuth: c224a0cf1afa0949bd5c7bfcf154b4f5ce8ddef2
|
||||
FirebaseCore: 4d3c72622ce0e2106aaa07bb4b2935ba2c370972
|
||||
@ -285,6 +293,8 @@ SPEC CHECKSUMS:
|
||||
FirebaseInstallations: 5e777e6640fa060405cc7632447b6c5ca5af4742
|
||||
FirebaseInstanceID: 53140c03b9f6136f890d7901399f85a4c90ab2d0
|
||||
FirebaseMessaging: 68d1bcb14880189558a8ae57167abe0b7e417232
|
||||
flurry: 15b01f664ab1367c62b50291541ea7f78ca85aad
|
||||
Flurry-iOS-SDK: 6636d30c30f12010e7c7c71d84b443416a168efc
|
||||
Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
|
||||
flutter_facebook_auth: 4b170c07b7fce791497093fcc3f134fb215f3f07
|
||||
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
|
||||
@ -307,7 +317,7 @@ SPEC CHECKSUMS:
|
||||
PurchasesCoreSwift: 31c2a3d7394432abbe64d46f0933835de0b33033
|
||||
PurchasesHybridCommon: 013c8072b73e752a206779747e88c068fbf999ec
|
||||
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
|
||||
smartlook: bda0b1561935a02ef0fea5448258d5ac75027859
|
||||
smartlook: bbc5c73a85c752a31dabf100c8930838c646342e
|
||||
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
|
||||
video_player: 9cc823b1d9da7e8427ee591e8438bfbcde500e6e
|
||||
wakelock: b0843b2479edbf6504d8d262c2959446f35373aa
|
||||
|
@ -388,7 +388,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 6;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = SFJJBDCU6Z;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@ -405,7 +405,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
MARKETING_VERSION = 1.1.10;
|
||||
MARKETING_VERSION = 1.1.11;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.aitrainer.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@ -531,7 +531,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 6;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = SFJJBDCU6Z;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@ -548,7 +548,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
MARKETING_VERSION = 1.1.10;
|
||||
MARKETING_VERSION = 1.1.11;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.aitrainer.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@ -566,7 +566,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 6;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = SFJJBDCU6Z;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@ -583,7 +583,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
MARKETING_VERSION = 1.1.10;
|
||||
MARKETING_VERSION = 1.1.11;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.aitrainer.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
|
@ -74,7 +74,7 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
|
||||
yield AccountLoggedIn();
|
||||
} else if (event is AccountLogout) {
|
||||
await Cache().logout();
|
||||
//customerRepository.customer = null;
|
||||
customerRepository.customer = null;
|
||||
customerRepository.emptyTrainees();
|
||||
loggedIn = false;
|
||||
yield AccountLoggedOut();
|
||||
|
@ -9,6 +9,8 @@ import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../../model/fitness_state.dart';
|
||||
|
||||
part 'customer_change_event.dart';
|
||||
part 'customer_change_state.dart';
|
||||
|
||||
@ -19,13 +21,39 @@ class CustomerChangeBloc extends Bloc<CustomerChangeEvent, CustomerChangeState>
|
||||
double weight = 60;
|
||||
double height = 170;
|
||||
CustomerChangeBloc({required this.customerRepository}) : super(CustomerChangeInitial()) {
|
||||
year = this.customerRepository.customer.birthYear;
|
||||
if (this.customerRepository.customer == null) {
|
||||
this.customerRepository.createNew();
|
||||
}
|
||||
year = this.customerRepository.customer!.birthYear;
|
||||
if (year == null || year == 0) {
|
||||
year = 1990;
|
||||
}
|
||||
weight = this.customerRepository.getWeight() == 0 ? 60 : this.customerRepository.getWeight();
|
||||
height = this.customerRepository.getHeight() == 0 ? 170 : this.customerRepository.getHeight();
|
||||
|
||||
print("fitnesslevel " + customerRepository.customer!.fitnessLevel.toString());
|
||||
if (customerRepository.customer!.fitnessLevel != null) {
|
||||
if (!FitnessItem().elements.contains(customerRepository.customer!.fitnessLevel)) {
|
||||
Sport.values.forEach((element) {
|
||||
print(" .. ${element.toStr()}");
|
||||
if (element.equalsStringTo(customerRepository.customer!.fitnessLevel!)) {
|
||||
selectedSport = element;
|
||||
selectedFitnessItem = FitnessState.professional;
|
||||
}
|
||||
});
|
||||
if (selectedSport == null) {
|
||||
selectedFitnessItem = customerRepository.customer!.fitnessLevel;
|
||||
}
|
||||
} else {
|
||||
selectedFitnessItem = customerRepository.customer!.fitnessLevel;
|
||||
}
|
||||
}
|
||||
|
||||
print("selected: $selectedFitnessItem sport: $selectedSport " + customerRepository.customer!.fitnessLevel.toString());
|
||||
}
|
||||
|
||||
Sport? selectedSport;
|
||||
String? selectedFitnessItem;
|
||||
|
||||
@override
|
||||
Stream<CustomerChangeState> mapEventToState(
|
||||
@ -42,7 +70,8 @@ class CustomerChangeBloc extends Bloc<CustomerChangeEvent, CustomerChangeState>
|
||||
visiblePassword = !visiblePassword;
|
||||
yield CustomerDataChanged();
|
||||
} else if (event is CustomerFitnessChange) {
|
||||
customerRepository.setFitnessLevel(event.fitness);
|
||||
//customerRepository.setFitnessLevel(event.fitness);
|
||||
selectedFitnessItem = event.fitness;
|
||||
yield CustomerDataChanged();
|
||||
} else if (event is CustomerBirthYearChange) {
|
||||
yield CustomerChangeLoading();
|
||||
@ -77,9 +106,25 @@ class CustomerChangeBloc extends Bloc<CustomerChangeEvent, CustomerChangeState>
|
||||
} else if (event is CustomerGenderChange) {
|
||||
customerRepository.setSex(event.gender == 0 ? "m" : "w");
|
||||
yield CustomerDataChanged();
|
||||
} else if (event is CustomerSportChange) {
|
||||
yield CustomerChangeLoading();
|
||||
selectedSport = event.sport;
|
||||
//customerRepository.setFitnessLevel(event.sport.toStr());
|
||||
yield CustomerDataChanged();
|
||||
} else if (event is CustomerSave) {
|
||||
yield CustomerSaving();
|
||||
if (validation()) {
|
||||
if (selectedFitnessItem != null) {
|
||||
if (selectedFitnessItem == FitnessState.professional) {
|
||||
if (selectedSport != null) {
|
||||
customerRepository.setFitnessLevel(selectedSport!.toStr());
|
||||
} else {
|
||||
customerRepository.setFitnessLevel(FitnessState.professional);
|
||||
}
|
||||
} else {
|
||||
customerRepository.setFitnessLevel(selectedFitnessItem!);
|
||||
}
|
||||
}
|
||||
await customerRepository.saveCustomer();
|
||||
Cache().initBadges();
|
||||
yield CustomerSaveSuccess();
|
||||
@ -123,4 +168,7 @@ class CustomerChangeBloc extends Bloc<CustomerChangeEvent, CustomerChangeState>
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Sport? get getSelectedSport => this.selectedSport;
|
||||
set setSelectedSport(Sport? selectedSport) => this.selectedSport = selectedSport;
|
||||
}
|
||||
|
@ -95,6 +95,14 @@ class CustomerPasswordChange extends CustomerChangeEvent {
|
||||
List<Object> get props => [password];
|
||||
}
|
||||
|
||||
class CustomerSportChange extends CustomerChangeEvent {
|
||||
final Sport sport;
|
||||
const CustomerSportChange({required this.sport});
|
||||
|
||||
@override
|
||||
List<Object> get props => [sport];
|
||||
}
|
||||
|
||||
class CustomerChangePasswordObscure extends CustomerChangeEvent {
|
||||
const CustomerChangePasswordObscure();
|
||||
}
|
||||
|
@ -26,8 +26,8 @@ class ExerciseExecutePlanAddBloc extends Bloc<ExerciseExecutePlanAddEvent, Exerc
|
||||
int step = 1;
|
||||
int countSteps = 1;
|
||||
|
||||
late double quantity;
|
||||
late double unitQuantity;
|
||||
double? quantity;
|
||||
double? unitQuantity;
|
||||
|
||||
double scrollOffset = 0;
|
||||
|
||||
@ -38,7 +38,9 @@ class ExerciseExecutePlanAddBloc extends Bloc<ExerciseExecutePlanAddEvent, Exerc
|
||||
required this.customerId,
|
||||
required this.workoutTree,
|
||||
required this.planBloc})
|
||||
: super(ExerciseExecutePlanAddInitial()) {
|
||||
: super(ExerciseExecutePlanAddInitial());
|
||||
|
||||
void init() {
|
||||
exerciseRepository.exerciseType = workoutTree.exerciseType;
|
||||
if (Cache().userLoggedIn!.customerId == customerId) {
|
||||
customer = Cache().userLoggedIn!;
|
||||
@ -48,12 +50,15 @@ class ExerciseExecutePlanAddBloc extends Bloc<ExerciseExecutePlanAddEvent, Exerc
|
||||
exercisePlanRepository.setActualPlanDetailByExerciseType(workoutTree.exerciseType!);
|
||||
exerciseRepository.customer = customer;
|
||||
countSteps = exercisePlanRepository.getActualPlanDetail()!.serie!;
|
||||
|
||||
if (exercisePlanRepository.getActualPlanDetail()!.weightEquation == null) {
|
||||
unitQuantity = 0.0;
|
||||
} else {
|
||||
unitQuantity = double.parse(exercisePlanRepository.getActualPlanDetail()!.weightEquation!);
|
||||
}
|
||||
quantity = exercisePlanRepository.getActualPlanDetail()!.repeats!.toDouble();
|
||||
|
||||
exerciseRepository.setQuantity(quantity);
|
||||
exerciseRepository.setUnitQuantity(unitQuantity);
|
||||
exerciseRepository.setQuantity(quantity!);
|
||||
exerciseRepository.setUnitQuantity(unitQuantity!);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -61,17 +66,18 @@ class ExerciseExecutePlanAddBloc extends Bloc<ExerciseExecutePlanAddEvent, Exerc
|
||||
try {
|
||||
if (event is ExerciseExecutePlanAddLoad) {
|
||||
yield ExerciseExecutePlanAddLoading();
|
||||
init();
|
||||
Track().track(TrackingEvent.my_exercise_plan_execute_open);
|
||||
yield ExerciseExecutePlanAddReady();
|
||||
} else if (event is ExerciseExecutePlanAddChangeQuantity) {
|
||||
yield ExerciseExecutePlanAddLoading();
|
||||
quantity = event.quantity;
|
||||
exerciseRepository.setQuantity(quantity);
|
||||
exerciseRepository.setQuantity(quantity!);
|
||||
yield ExerciseExecutePlanAddReady();
|
||||
} else if (event is ExerciseExecutePlanAddChangeUnitQuantity) {
|
||||
yield ExerciseExecutePlanAddLoading();
|
||||
unitQuantity = event.quantity;
|
||||
exerciseRepository.setUnitQuantity(unitQuantity);
|
||||
exerciseRepository.setUnitQuantity(unitQuantity!);
|
||||
yield ExerciseExecutePlanAddReady();
|
||||
} else if (event is ExerciseExecutePlanAddSubmit) {
|
||||
yield ExerciseExecutePlanAddLoading();
|
||||
|
@ -68,10 +68,10 @@ class ExerciseNewBloc extends Bloc<ExerciseNewEvent, ExerciseNewState> with Logg
|
||||
exerciseRepository.start = DateTime.now();
|
||||
if (Cache().userLoggedIn != null) {
|
||||
customerRepository.customer = Cache().userLoggedIn!;
|
||||
weight = customerRepository.customer.getProperty("Weight");
|
||||
height = customerRepository.customer.getProperty("Height");
|
||||
birthYear = customerRepository.customer.birthYear!;
|
||||
fitnessLevel = customerRepository.customer.fitnessLevel!;
|
||||
weight = customerRepository.customer!.getProperty("Weight");
|
||||
height = customerRepository.customer!.getProperty("Height");
|
||||
birthYear = customerRepository.customer!.birthYear!;
|
||||
fitnessLevel = customerRepository.customer!.fitnessLevel!;
|
||||
}
|
||||
if (exerciseType.unit == "second") {
|
||||
stopWatchTimer.rawTime.listen((value) => {timerValue = value, this.setQuantity((value / 1000).toDouble())});
|
||||
@ -183,22 +183,22 @@ class ExerciseNewBloc extends Bloc<ExerciseNewEvent, ExerciseNewState> with Logg
|
||||
|
||||
int year = int.parse(DateFormat(DateFormat.YEAR).format(date));
|
||||
|
||||
if (customerRepository.customer.sex == "m") {
|
||||
if (customerRepository.customer!.sex == "m") {
|
||||
//66.47 + ( 13.75 × tömeg kg-ban ) + ( 5.003 × magasság cm-ben ) − ( 6.755 × életkor évben kifejezve )
|
||||
bmr = 66.47 + (13.75 * weight) + (5.003 * height) - (6.755 * (year - customerRepository.customer.birthYear!));
|
||||
bmr = 66.47 + (13.75 * weight) + (5.003 * height) - (6.755 * (year - customerRepository.customer!.birthYear!));
|
||||
} else {
|
||||
//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!));
|
||||
bmr = 655.1 + (9.563 * weight) + (1.85 * height) - (4.676 * (year - customerRepository.customer!.birthYear!));
|
||||
}
|
||||
bmrEnergy = bmr;
|
||||
|
||||
if (customerRepository.customer.fitnessLevel == FitnessState.beginner) {
|
||||
if (customerRepository.customer!.fitnessLevel == FitnessState.beginner) {
|
||||
bmr *= 1.2;
|
||||
} else if (customerRepository.customer.fitnessLevel == FitnessState.intermediate) {
|
||||
} else if (customerRepository.customer!.fitnessLevel == FitnessState.intermediate) {
|
||||
bmr *= 1.375;
|
||||
} else if (customerRepository.customer.fitnessLevel == FitnessState.advanced) {
|
||||
} else if (customerRepository.customer!.fitnessLevel == FitnessState.advanced) {
|
||||
bmr *= 1.55;
|
||||
} else if (customerRepository.customer.fitnessLevel == FitnessState.professional) {
|
||||
} else if (customerRepository.customer!.fitnessLevel == FitnessState.professional) {
|
||||
bmr *= 1.9;
|
||||
}
|
||||
return bmr;
|
||||
|
@ -75,12 +75,17 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> with Trans {
|
||||
if (!this.dataPolicyAllowed) {
|
||||
throw Exception("Please accept our data policy");
|
||||
}
|
||||
final String? validationError = validate();
|
||||
if (validationError != null) {
|
||||
yield LoginError(message: validationError);
|
||||
} else {
|
||||
await userRepository.addUser();
|
||||
accountBloc.add(AccountLogInFinished(customer: Cache().userLoggedIn!));
|
||||
await saveCustomer();
|
||||
Track().track(TrackingEvent.registration, eventValue: "email");
|
||||
Cache().setLoginType(LoginType.email);
|
||||
yield LoginSuccess();
|
||||
}
|
||||
} else if (event is RegistrationFB) {
|
||||
yield LoginLoading();
|
||||
if (!this.dataPolicyAllowed) {
|
||||
@ -131,20 +136,36 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> with Trans {
|
||||
|
||||
Future<void> saveCustomer() async {
|
||||
customerRepository.customer = Cache().userLoggedIn!;
|
||||
customerRepository.customer.dataPolicyAllowed = 1;
|
||||
customerRepository.customer!.dataPolicyAllowed = 1;
|
||||
await customerRepository.saveCustomer();
|
||||
}
|
||||
|
||||
String? emailValidation(String email) {
|
||||
String? emailValidation(String? email) {
|
||||
String? message = Common.emailValidation(email);
|
||||
return message;
|
||||
}
|
||||
|
||||
String? passwordValidation(String value) {
|
||||
String? passwordValidation(String? value) {
|
||||
String? message = Common.passwordValidation(value);
|
||||
if (message != null) {
|
||||
message = t(message);
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
String? validate() {
|
||||
String? error;
|
||||
|
||||
error = emailValidation(userRepository.user.email);
|
||||
if (error != null) {
|
||||
return error;
|
||||
}
|
||||
|
||||
error = passwordValidation(userRepository.user.password);
|
||||
if (error != null) {
|
||||
return error;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,9 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> with Trans, Logging {
|
||||
//await menuTreeRepository.createTree();
|
||||
//menuTreeRepository.getBranch(this.parent);
|
||||
//setMenuInfo();
|
||||
if (Cache().getDevices() != null) {
|
||||
exerciseDeviceRepository.setDevices(Cache().getDevices()!);
|
||||
}
|
||||
yield MenuReady();
|
||||
} else if (event is MenuRecreateTree) {
|
||||
yield MenuLoading();
|
||||
|
@ -25,7 +25,7 @@ class PasswordResetBloc extends Bloc<PasswordResetEvent, PasswordResetState> {
|
||||
} else if (event is PasswordResetSubmit) {
|
||||
yield PasswordResetLoading();
|
||||
await userRepository.resetPassword();
|
||||
yield PasswordResetReady();
|
||||
yield PasswordResetFinished();
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
yield PasswordResetError(message: e.toString());
|
||||
|
@ -26,3 +26,7 @@ class PasswordResetError extends PasswordResetState {
|
||||
class PasswordResetReady extends PasswordResetState {
|
||||
const PasswordResetReady();
|
||||
}
|
||||
|
||||
class PasswordResetFinished extends PasswordResetState {
|
||||
const PasswordResetFinished();
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:aitrainer_app/model/result.dart';
|
||||
import 'package:aitrainer_app/repository/evaluation_repository.dart';
|
||||
import 'package:aitrainer_app/repository/exercise_repository.dart';
|
||||
import 'package:aitrainer_app/repository/exercise_result_repository.dart';
|
||||
import 'package:aitrainer_app/service/logging.dart';
|
||||
@ -17,6 +18,7 @@ part 'result_state.dart';
|
||||
class ResultBloc extends Bloc<ResultEvent, ResultState> with Logging, Trans {
|
||||
final ExerciseResultRepository resultRepository;
|
||||
final ExerciseRepository exerciseRepository;
|
||||
final EvaluationRepository evaluationRepository = EvaluationRepository();
|
||||
final BuildContext context;
|
||||
//List<HealthDataPoint> _healthDataList = [];
|
||||
DateTime? startTime;
|
||||
@ -53,7 +55,7 @@ class ResultBloc extends Bloc<ResultEvent, ResultState> with Logging, Trans {
|
||||
|
||||
//await _fetchHealthData();
|
||||
_matchExerciseData();
|
||||
await resultRepository.saveExerciseResults();
|
||||
//await resultRepository.saveExerciseResults();
|
||||
yield ResultReady();
|
||||
}
|
||||
} on Exception catch (ex) {
|
||||
|
@ -31,7 +31,7 @@ class TestSetControlBloc extends Bloc<TestSetControlEvent, TestSetControlState>
|
||||
}
|
||||
|
||||
void initBloc() {
|
||||
oneRepMax = executeBloc.calculate1RM(exercisePlanDetail.exercises!.last.unitQuantity, exercisePlanDetail.exercises!.last.quantity);
|
||||
oneRepMax = executeBloc.calculate1RM(exercisePlanDetail.exercises!.last.unitQuantity!, exercisePlanDetail.exercises!.last.quantity!);
|
||||
initQuantity = 12;
|
||||
quantity = initQuantity;
|
||||
initUnitQuantity = oneRepMax * 0.75;
|
||||
|
@ -136,7 +136,9 @@ class TestSetExecuteBloc extends Bloc<TestSetExecuteEvent, TestSetExecuteState>
|
||||
|
||||
if (!this.existsInPlanDetails(event.exerciseTypeId)) {
|
||||
ExercisePlanDetail exercisePlanDetail = ExercisePlanDetail(event.exerciseTypeId);
|
||||
if (exercisePlan!.exercisePlanId != null) {
|
||||
exercisePlanDetail.exercisePlanId = exercisePlan!.exercisePlanId!;
|
||||
}
|
||||
final ExerciseType exerciseType = Cache().getExerciseTypeById(event.exerciseTypeId)!;
|
||||
|
||||
exercisePlanDetail.serie = exerciseType.unitQuantityUnit == null ? 1 : 4;
|
||||
@ -316,8 +318,9 @@ class TestSetExecuteBloc extends Bloc<TestSetExecuteEvent, TestSetExecuteState>
|
||||
|
||||
ExercisePlanDetail? getNext() {
|
||||
ExercisePlanDetail? nextExercisePlanDetail;
|
||||
|
||||
int minStep = 99;
|
||||
if (this.exercisePlanDetails == null) {
|
||||
if (this.exercisePlanDetails != null) {
|
||||
for (final detail in this.exercisePlanDetails!) {
|
||||
if (!detail.state.equalsTo(ExercisePlanDetailState.finished)) {
|
||||
if (detail.exercises == null) {
|
||||
@ -369,9 +372,9 @@ class TestSetExecuteBloc extends Bloc<TestSetExecuteEvent, TestSetExecuteState>
|
||||
if (!hasBegun() || exercisePlanDetail.exercises!.length < 2) {
|
||||
return text;
|
||||
}
|
||||
final double unitQuantity = exercisePlanDetail.exercises!.last.unitQuantity;
|
||||
final double quantity = exercisePlanDetail.exercises!.last.quantity;
|
||||
double oneRepMax = this.calculate1RM(quantity, unitQuantity);
|
||||
final double? unitQuantity = exercisePlanDetail.exercises!.last.unitQuantity!;
|
||||
final double? quantity = exercisePlanDetail.exercises!.last.quantity!;
|
||||
double oneRepMax = this.calculate1RM(quantity!, unitQuantity!);
|
||||
text = (oneRepMax * 0.75).round().toStringAsFixed(0) + " " + exercisePlanDetail.exerciseType!.unitQuantityUnit!;
|
||||
return text;
|
||||
}
|
||||
|
541
lib/library/dropdown_search.dart
Normal file
541
lib/library/dropdown_search.dart
Normal file
@ -0,0 +1,541 @@
|
||||
library dropdown_search;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
import 'popup_menu.dart';
|
||||
import 'select_dialog.dart';
|
||||
|
||||
typedef Future<List<T>> DropdownSearchOnFind<T>(String text);
|
||||
typedef String DropdownSearchItemAsString<T>(T item);
|
||||
typedef bool DropdownSearchFilterFn<T>(T item, String filter);
|
||||
typedef bool DropdownSearchCompareFn<T>(T item, T selectedItem);
|
||||
typedef Widget DropdownSearchBuilder<T>(BuildContext context, T selectedItem, String itemAsString);
|
||||
typedef Widget DropdownSearchPopupItemBuilder<T>(
|
||||
BuildContext context,
|
||||
T item,
|
||||
bool isSelected,
|
||||
);
|
||||
typedef bool DropdownSearchPopupItemEnabled<T>(T item);
|
||||
typedef Widget ErrorBuilder<T>(BuildContext context, String? searchEntry, dynamic exception);
|
||||
typedef Widget EmptyBuilder<T>(BuildContext context, String? searchEntry);
|
||||
typedef Widget LoadingBuilder<T>(BuildContext context, String? searchEntry);
|
||||
typedef Widget IconButtonBuilder(BuildContext context);
|
||||
typedef Future<bool> BeforeChange<T>(T prevItem, T nextItem);
|
||||
|
||||
typedef Widget FavoriteItemsBuilder<T>(BuildContext context, T item);
|
||||
|
||||
///[items] are the original item from [items] or/and [onFind]
|
||||
typedef List<T> FavoriteItems<T>(List<T> items);
|
||||
|
||||
enum Mode { DIALOG, BOTTOM_SHEET, MENU }
|
||||
|
||||
class DropdownSearch<T> extends StatefulWidget {
|
||||
///DropDownSearch label
|
||||
final String? label;
|
||||
|
||||
///DropDownSearch hint
|
||||
final String? hint;
|
||||
|
||||
///show/hide the search box
|
||||
final bool showSearchBox;
|
||||
|
||||
///true if the filter on items is applied onlie (via API)
|
||||
final bool isFilteredOnline;
|
||||
|
||||
///show/hide clear selected item
|
||||
final bool showClearButton;
|
||||
|
||||
///offline items list
|
||||
final List<T>? items;
|
||||
|
||||
///selected item
|
||||
final T? selectedItem;
|
||||
|
||||
///function that returns item from API
|
||||
final DropdownSearchOnFind<T>? onFind;
|
||||
|
||||
///called when a new item is selected
|
||||
final ValueChanged<T>? onChanged;
|
||||
|
||||
///to customize list of items UI
|
||||
final DropdownSearchBuilder<T>? dropdownBuilder;
|
||||
|
||||
///to customize selected item
|
||||
final DropdownSearchPopupItemBuilder<T>? popupItemBuilder;
|
||||
|
||||
///decoration for search box
|
||||
final InputDecoration? searchBoxDecoration;
|
||||
|
||||
///the title for dialog/menu/bottomSheet
|
||||
final Color? popupBackgroundColor;
|
||||
|
||||
///custom widget for the popup title
|
||||
final Widget? popupTitle;
|
||||
|
||||
///customize the fields the be shown
|
||||
final DropdownSearchItemAsString<T>? itemAsString;
|
||||
|
||||
/// custom filter function
|
||||
final DropdownSearchFilterFn<T>? filterFn;
|
||||
|
||||
///enable/disable dropdownSearch
|
||||
final bool enabled;
|
||||
|
||||
///MENU / DIALOG/ BOTTOM_SHEET
|
||||
final Mode mode;
|
||||
|
||||
///the max height for dialog/bottomSheet/Menu
|
||||
final double? maxHeight;
|
||||
|
||||
///the max width for the dialog
|
||||
final double? dialogMaxWidth;
|
||||
|
||||
///select the selected item in the menu/dialog/bottomSheet of items
|
||||
final bool showSelectedItem;
|
||||
|
||||
///function that compares two object with the same type to detected if it's the selected item or not
|
||||
final DropdownSearchCompareFn<T>? compareFn;
|
||||
|
||||
///dropdownSearch input decoration
|
||||
final InputDecoration? dropdownSearchDecoration;
|
||||
|
||||
///custom layout for empty results
|
||||
final EmptyBuilder? emptyBuilder;
|
||||
|
||||
///custom layout for loading items
|
||||
final LoadingBuilder? loadingBuilder;
|
||||
|
||||
///custom layout for error
|
||||
final ErrorBuilder? errorBuilder;
|
||||
|
||||
///the search box will be focused if true
|
||||
final bool autoFocusSearchBox;
|
||||
|
||||
///custom shape for the popup
|
||||
final ShapeBorder? popupShape;
|
||||
|
||||
final AutovalidateMode autoValidateMode;
|
||||
|
||||
/// An optional method to call with the final value when the form is saved via
|
||||
final FormFieldSetter<T>? onSaved;
|
||||
|
||||
/// An optional method that validates an input. Returns an error string to
|
||||
/// display if the input is invalid, or null otherwise.
|
||||
final FormFieldValidator<T>? validator;
|
||||
|
||||
///custom dropdown clear button icon widget
|
||||
final Widget? clearButton;
|
||||
|
||||
///custom clear button widget builder
|
||||
final IconButtonBuilder? clearButtonBuilder;
|
||||
|
||||
///custom dropdown icon button widget
|
||||
final Widget? dropDownButton;
|
||||
|
||||
///custom dropdown button widget builder
|
||||
final IconButtonBuilder? dropdownButtonBuilder;
|
||||
|
||||
///whether to manage the clear and dropdown icons via InputDecoration suffixIcon
|
||||
final bool showAsSuffixIcons;
|
||||
|
||||
///If true, the dropdownBuilder will continue the uses of material behavior
|
||||
///This will be useful if you want to handle a custom UI only if the item !=null
|
||||
final bool dropdownBuilderSupportsNullItem;
|
||||
|
||||
///defines if an item of the popup is enabled or not, if the item is disabled,
|
||||
///it cannot be clicked
|
||||
final DropdownSearchPopupItemEnabled<T>? popupItemDisabled;
|
||||
|
||||
///set a custom color for the popup barrier
|
||||
final Color? popupBarrierColor;
|
||||
|
||||
///text controller to set default search word for example
|
||||
final TextEditingController? searchBoxController;
|
||||
|
||||
///called when popup is dismissed
|
||||
final VoidCallback? onPopupDismissed;
|
||||
|
||||
/// callback executed before applying value change
|
||||
///delay before searching, change it to Duration(milliseconds: 0)
|
||||
///if you do not use online search
|
||||
final Duration? searchDelay;
|
||||
|
||||
/// callback executed before applying value change
|
||||
final BeforeChange<T>? onBeforeChange;
|
||||
|
||||
///show or hide favorites items
|
||||
final bool showFavoriteItems;
|
||||
|
||||
///to customize favorites chips
|
||||
final FavoriteItemsBuilder<T>? favoriteItemBuilder;
|
||||
|
||||
///favorites items list
|
||||
final FavoriteItems<T>? favoriteItems;
|
||||
|
||||
///favorite items alignment
|
||||
final MainAxisAlignment? favoriteItemsAlignment;
|
||||
|
||||
DropdownSearch({
|
||||
Key? key,
|
||||
this.onSaved,
|
||||
this.validator,
|
||||
this.autoValidateMode = AutovalidateMode.disabled,
|
||||
this.onChanged,
|
||||
this.mode = Mode.DIALOG,
|
||||
this.label,
|
||||
this.hint,
|
||||
this.isFilteredOnline = false,
|
||||
this.popupTitle,
|
||||
this.items,
|
||||
this.selectedItem,
|
||||
this.onFind,
|
||||
this.dropdownBuilder,
|
||||
this.popupItemBuilder,
|
||||
this.showSearchBox = false,
|
||||
this.showClearButton = false,
|
||||
this.searchBoxDecoration,
|
||||
this.popupBackgroundColor,
|
||||
this.enabled = true,
|
||||
this.maxHeight,
|
||||
this.filterFn,
|
||||
this.itemAsString,
|
||||
this.showSelectedItem = false,
|
||||
this.compareFn,
|
||||
this.dropdownSearchDecoration,
|
||||
this.emptyBuilder,
|
||||
this.loadingBuilder,
|
||||
this.errorBuilder,
|
||||
this.autoFocusSearchBox = false,
|
||||
this.dialogMaxWidth,
|
||||
this.clearButton,
|
||||
this.clearButtonBuilder,
|
||||
this.dropDownButton,
|
||||
this.dropdownButtonBuilder,
|
||||
this.showAsSuffixIcons = false,
|
||||
this.dropdownBuilderSupportsNullItem = false,
|
||||
this.popupShape,
|
||||
this.popupItemDisabled,
|
||||
this.popupBarrierColor,
|
||||
this.onPopupDismissed,
|
||||
this.searchBoxController,
|
||||
this.searchDelay,
|
||||
this.onBeforeChange,
|
||||
this.favoriteItemBuilder,
|
||||
this.favoriteItems,
|
||||
this.showFavoriteItems = false,
|
||||
this.favoriteItemsAlignment = MainAxisAlignment.start,
|
||||
}) : assert(!showSelectedItem || T == String || compareFn != null),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
DropdownSearchState<T> createState() => DropdownSearchState<T>();
|
||||
}
|
||||
|
||||
class DropdownSearchState<T> extends State<DropdownSearch<T?>> {
|
||||
final ValueNotifier<T?> _selectedItemNotifier = ValueNotifier(null);
|
||||
final ValueNotifier<bool> _isFocused = ValueNotifier(false);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_selectedItemNotifier.value = widget.selectedItem;
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(DropdownSearch<T?> oldWidget) {
|
||||
final oldSelectedItem = oldWidget.selectedItem;
|
||||
final newSelectedItem = widget.selectedItem;
|
||||
if (oldSelectedItem != newSelectedItem) {
|
||||
_selectedItemNotifier.value = newSelectedItem;
|
||||
}
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ValueListenableBuilder<T?>(
|
||||
valueListenable: _selectedItemNotifier,
|
||||
builder: (context, T? data, wt) {
|
||||
return IgnorePointer(
|
||||
ignoring: !widget.enabled,
|
||||
child: GestureDetector(
|
||||
onTap: () => _selectSearchMode(data),
|
||||
child: _formField(data),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _defaultSelectItemWidget(T? data) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: widget.dropdownBuilder != null
|
||||
? widget.dropdownBuilder!(
|
||||
context,
|
||||
data,
|
||||
_selectedItemAsString(data),
|
||||
)
|
||||
: Text(_selectedItemAsString(data), style: Theme.of(context).textTheme.subtitle1),
|
||||
),
|
||||
if (!widget.showAsSuffixIcons) _manageTrailingIcons(data),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _formField(T? value) {
|
||||
return FormField(
|
||||
enabled: widget.enabled,
|
||||
onSaved: widget.onSaved,
|
||||
validator: widget.validator,
|
||||
autovalidateMode: widget.autoValidateMode,
|
||||
initialValue: widget.selectedItem,
|
||||
builder: (FormFieldState<T> state) {
|
||||
if (state.value != value) {
|
||||
WidgetsBinding.instance!.addPostFrameCallback((_) {
|
||||
state.didChange(value);
|
||||
});
|
||||
}
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: _isFocused,
|
||||
builder: (context, bool isFocused, w) {
|
||||
return InputDecorator(
|
||||
isEmpty: value == null && (widget.dropdownBuilder == null || widget.dropdownBuilderSupportsNullItem),
|
||||
isFocused: isFocused,
|
||||
decoration: _manageDropdownDecoration(state, value),
|
||||
child: _defaultSelectItemWidget(value),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
///manage dropdownSearch field decoration
|
||||
InputDecoration _manageDropdownDecoration(FormFieldState state, T? data) {
|
||||
return (widget.dropdownSearchDecoration ??
|
||||
InputDecoration(contentPadding: EdgeInsets.fromLTRB(12, 12, 0, 0), border: OutlineInputBorder()))
|
||||
.applyDefaults(Theme.of(state.context).inputDecorationTheme)
|
||||
.copyWith(
|
||||
enabled: widget.enabled,
|
||||
labelText: widget.label,
|
||||
hintText: widget.hint,
|
||||
suffixIcon: widget.showAsSuffixIcons ? _manageTrailingIcons(data) : null,
|
||||
errorText: state.errorText);
|
||||
}
|
||||
|
||||
///function that return the String value of an object
|
||||
String _selectedItemAsString(T? data) {
|
||||
if (data == null) {
|
||||
return "";
|
||||
} else if (widget.itemAsString != null) {
|
||||
return widget.itemAsString!(data);
|
||||
} else {
|
||||
return data.toString();
|
||||
}
|
||||
}
|
||||
|
||||
///function that manage Trailing icons(close, dropDown)
|
||||
Widget _manageTrailingIcons(T? data) {
|
||||
final clearButtonPressed = () => _handleOnChangeSelectedItem(null);
|
||||
final dropdownButtonPressed = () => _selectSearchMode(data);
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
if (data != null && widget.showClearButton == true)
|
||||
widget.clearButtonBuilder != null
|
||||
? GestureDetector(
|
||||
onTap: clearButtonPressed,
|
||||
child: widget.clearButtonBuilder!(context),
|
||||
)
|
||||
: IconButton(
|
||||
icon: widget.clearButton ?? const Icon(Icons.clear, size: 24),
|
||||
onPressed: clearButtonPressed,
|
||||
),
|
||||
widget.dropdownButtonBuilder != null
|
||||
? GestureDetector(
|
||||
onTap: dropdownButtonPressed,
|
||||
child: widget.dropdownButtonBuilder!(context),
|
||||
)
|
||||
: IconButton(
|
||||
icon: widget.dropDownButton ?? const Icon(Icons.arrow_drop_down, size: 24),
|
||||
onPressed: dropdownButtonPressed,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
///open dialog
|
||||
Future<T?> _openSelectDialog(T? data) {
|
||||
return showGeneralDialog(
|
||||
barrierDismissible: true,
|
||||
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
||||
transitionDuration: const Duration(milliseconds: 400),
|
||||
barrierColor: widget.popupBarrierColor ?? const Color(0x80000000),
|
||||
context: context,
|
||||
pageBuilder: (context, animation, secondaryAnimation) {
|
||||
return AlertDialog(
|
||||
contentPadding: EdgeInsets.all(0),
|
||||
shape: widget.popupShape,
|
||||
backgroundColor: widget.popupBackgroundColor,
|
||||
content: _selectDialogInstance(data),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
///open BottomSheet (Dialog mode)
|
||||
Future<T?> _openBottomSheet(T? data) {
|
||||
return showModalBottomSheet<T>(
|
||||
barrierColor: widget.popupBarrierColor,
|
||||
backgroundColor: widget.popupBackgroundColor,
|
||||
isScrollControlled: true,
|
||||
shape: widget.popupShape,
|
||||
context: context,
|
||||
builder: (ctx) {
|
||||
return _selectDialogInstance(data, defaultHeight: 350);
|
||||
});
|
||||
}
|
||||
|
||||
///openMenu
|
||||
Future<T?> _openMenu(T? data) {
|
||||
// Here we get the render object of our physical button, later to get its size & position
|
||||
final RenderBox popupButtonObject = context.findRenderObject() as RenderBox;
|
||||
// Get the render object of the overlay used in `Navigator` / `MaterialApp`, i.e. screen size reference
|
||||
final RenderBox overlay = Overlay.of(context)!.context.findRenderObject() as RenderBox;
|
||||
// Calculate the show-up area for the dropdown using button's size & position based on the `overlay` used as the coordinate space.
|
||||
final RelativeRect position = RelativeRect.fromSize(
|
||||
Rect.fromPoints(
|
||||
popupButtonObject.localToGlobal(popupButtonObject.size.bottomLeft(Offset.zero), ancestor: overlay),
|
||||
popupButtonObject.localToGlobal(popupButtonObject.size.bottomRight(Offset.zero), ancestor: overlay),
|
||||
),
|
||||
Size(overlay.size.width, overlay.size.height),
|
||||
);
|
||||
return customShowMenu<T>(
|
||||
barrierColor: widget.popupBarrierColor,
|
||||
shape: widget.popupShape,
|
||||
color: widget.popupBackgroundColor,
|
||||
context: context,
|
||||
position: position,
|
||||
elevation: 8,
|
||||
items: [
|
||||
CustomPopupMenuItem(
|
||||
enabled: false,
|
||||
child: Container(
|
||||
width: popupButtonObject.size.width,
|
||||
child: _selectDialogInstance(data, defaultHeight: 224),
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
SelectDialog<T?> _selectDialogInstance(T? data, {double? defaultHeight}) {
|
||||
return SelectDialog<T?>(
|
||||
popupTitle: widget.popupTitle,
|
||||
maxHeight: widget.maxHeight ?? defaultHeight,
|
||||
isFilteredOnline: widget.isFilteredOnline,
|
||||
itemAsString: widget.itemAsString,
|
||||
filterFn: widget.filterFn,
|
||||
items: widget.items,
|
||||
onFind: widget.onFind,
|
||||
showSearchBox: widget.showSearchBox,
|
||||
itemBuilder: widget.popupItemBuilder,
|
||||
selectedValue: data,
|
||||
searchBoxDecoration: widget.searchBoxDecoration,
|
||||
onChanged: _handleOnChangeSelectedItem,
|
||||
showSelectedItem: widget.showSelectedItem,
|
||||
compareFn: widget.compareFn,
|
||||
emptyBuilder: widget.emptyBuilder,
|
||||
loadingBuilder: widget.loadingBuilder,
|
||||
errorBuilder: widget.errorBuilder,
|
||||
autoFocusSearchBox: widget.autoFocusSearchBox,
|
||||
dialogMaxWidth: widget.dialogMaxWidth,
|
||||
itemDisabled: widget.popupItemDisabled,
|
||||
searchBoxController: widget.searchBoxController ?? TextEditingController(),
|
||||
searchDelay: widget.searchDelay,
|
||||
showFavoriteItems: widget.showFavoriteItems,
|
||||
favoriteItems: widget.favoriteItems,
|
||||
favoriteItemBuilder: widget.favoriteItemBuilder,
|
||||
favoriteItemsAlignment: widget.favoriteItemsAlignment,
|
||||
);
|
||||
}
|
||||
|
||||
///Function that manage focus listener
|
||||
///set true only if the widget already not focused to prevent unnecessary build
|
||||
///same thing for clear focus,
|
||||
void _handleFocus(bool isFocused) {
|
||||
if (isFocused && !_isFocused.value) {
|
||||
FocusScope.of(context).unfocus();
|
||||
_isFocused.value = true;
|
||||
} else if (!isFocused && _isFocused.value) _isFocused.value = false;
|
||||
}
|
||||
|
||||
///handle on change value , if the validation is active , we validate the new selected item
|
||||
void _handleOnChangeSelectedItem(T? selectedItem) {
|
||||
final changeItem = () {
|
||||
_selectedItemNotifier.value = selectedItem;
|
||||
if (widget.onChanged != null) widget.onChanged!(selectedItem);
|
||||
};
|
||||
|
||||
if (widget.onBeforeChange != null) {
|
||||
widget.onBeforeChange!(_selectedItemNotifier.value, selectedItem).then((value) {
|
||||
if (value == true) {
|
||||
changeItem();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
changeItem();
|
||||
}
|
||||
|
||||
_handleFocus(false);
|
||||
}
|
||||
|
||||
///Function that return then UI based on searchMode
|
||||
///[data] selected item to be passed to the UI
|
||||
///If we close the popup , or maybe we just selected
|
||||
///another widget we should clear the focus
|
||||
Future<T?> _selectSearchMode(T? data) async {
|
||||
_handleFocus(true);
|
||||
T? selectedItem;
|
||||
if (widget.mode == Mode.MENU) {
|
||||
selectedItem = await _openMenu(data);
|
||||
} else if (widget.mode == Mode.BOTTOM_SHEET) {
|
||||
selectedItem = await _openBottomSheet(data);
|
||||
} else {
|
||||
selectedItem = await _openSelectDialog(data);
|
||||
}
|
||||
_handleFocus(false);
|
||||
widget.onPopupDismissed?.call();
|
||||
|
||||
return selectedItem;
|
||||
}
|
||||
|
||||
///Public Function that return then UI based on searchMode
|
||||
///[data] selected item to be passed to the UI
|
||||
///If we close the popup , or maybe we just selected
|
||||
///another widget we should clear the focus
|
||||
///THIS USED FOR OPEN DROPDOWN_SEARCH PROGRAMMATICALLY,
|
||||
///otherwise you can you [_selectSearchMode]
|
||||
Future<T?> openDropDownSearch() => _selectSearchMode(_selectedItemNotifier.value);
|
||||
|
||||
///Change selected Value; this function is public USED to change the selected
|
||||
///value PROGRAMMATICALLY, Otherwise you can use [_handleOnChangeSelectedItem]
|
||||
void changeSelectedItem(T selectedItem) => _handleOnChangeSelectedItem(selectedItem);
|
||||
|
||||
///Change selected Value; this function is public USED to clear selected
|
||||
///value PROGRAMMATICALLY, Otherwise you can use [_handleOnChangeSelectedItem]
|
||||
void clear() => _handleOnChangeSelectedItem(null);
|
||||
|
||||
///get selected value programmatically
|
||||
T? get getSelectedItem => _selectedItemNotifier.value;
|
||||
|
||||
///check if the dropdownSearch is focused
|
||||
bool get isFocused => _isFocused.value;
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class Flurry {
|
||||
static const MethodChannel _channel = const MethodChannel('flurry');
|
||||
|
||||
static Future<String> get platformVersion async {
|
||||
final String version = await _channel.invokeMethod('getPlatformVersion');
|
||||
return version;
|
||||
}
|
||||
|
||||
static Future<Null> initialize({String androidKey = "", String iosKey = "", bool enableLog = true}) async {
|
||||
Map<String, dynamic> args = <String, dynamic>{};
|
||||
args.putIfAbsent("api_key_android", () => androidKey);
|
||||
args.putIfAbsent("api_key_ios", () => iosKey);
|
||||
args.putIfAbsent("is_log_enabled", () => enableLog);
|
||||
|
||||
await _channel.invokeMethod('initialize', args);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static Future<Null> logEvent(String message) async {
|
||||
Map<String, dynamic> args = <String, dynamic>{};
|
||||
args.putIfAbsent("message", () => message);
|
||||
|
||||
await _channel.invokeMethod('logEvent', args);
|
||||
return null;
|
||||
}
|
||||
|
||||
static Future<Null> setUserId(String userId) async {
|
||||
Map<String, dynamic> args = <String, dynamic>{};
|
||||
args.putIfAbsent("userId", () => userId);
|
||||
|
||||
await _channel.invokeMethod('userId', args);
|
||||
return null;
|
||||
}
|
||||
}
|
589
lib/library/popup_menu.dart
Normal file
589
lib/library/popup_menu.dart
Normal file
@ -0,0 +1,589 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
const Duration _kMenuDuration = Duration(milliseconds: 300);
|
||||
const double _kMenuCloseIntervalEnd = 2.0 / 3.0;
|
||||
const double _kMenuHorizontalPadding = 0.0;
|
||||
const double _kMenuMinWidth = 2.0 * _kMenuWidthStep;
|
||||
const double _kMenuVerticalPadding = 0.0;
|
||||
const double _kMenuWidthStep = 1.0;
|
||||
const double _kMenuScreenPadding = 0.0;
|
||||
|
||||
// This widget only exists to enable _PopupMenuRoute to save the sizes of
|
||||
// each menu item. The sizes are used by _PopupMenuRouteLayout to compute the
|
||||
// y coordinate of the menu's origin so that the center of selected menu
|
||||
// item lines up with the center of its PopupMenuButton.
|
||||
class _MenuItem extends SingleChildRenderObjectWidget {
|
||||
const _MenuItem({
|
||||
Key? key,
|
||||
required this.onLayout,
|
||||
Widget? child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
final ValueChanged<Size> onLayout;
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
return _RenderMenuItem(onLayout);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, covariant _RenderMenuItem renderObject) {
|
||||
renderObject.onLayout = onLayout;
|
||||
}
|
||||
}
|
||||
|
||||
class _RenderMenuItem extends RenderShiftedBox {
|
||||
_RenderMenuItem(this.onLayout, [RenderBox? child]) : super(child);
|
||||
|
||||
ValueChanged<Size> onLayout;
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
if (child == null) {
|
||||
size = Size.zero;
|
||||
} else {
|
||||
child!.layout(constraints, parentUsesSize: true);
|
||||
size = constraints.constrain(child!.size);
|
||||
}
|
||||
final BoxParentData childParentData = child!.parentData as BoxParentData;
|
||||
childParentData.offset = Offset.zero;
|
||||
onLayout(size);
|
||||
}
|
||||
}
|
||||
|
||||
/// An item in a material design popup menu.
|
||||
///
|
||||
/// To show a popup menu, use the [customShowMenu] function. To create a button that
|
||||
/// shows a popup menu, consider using [PopupMenuButton].
|
||||
///
|
||||
/// To show a checkmark next to a popup menu item, consider using
|
||||
/// [CheckedPopupMenuItem].
|
||||
///
|
||||
/// Typically the [child] of a [CustomPopupMenuItem] is a [Text] widget. More
|
||||
/// elaborate menus with icons can use a [ListTile]. By default, a
|
||||
/// [CustomPopupMenuItem] is kMinInteractiveDimension pixels high. If you use a widget
|
||||
/// with a different height, it must be specified in the [height] property.
|
||||
///
|
||||
/// {@tool sample}
|
||||
///
|
||||
/// Here, a [Text] widget is used with a popup menu item. The `WhyFarther` type
|
||||
/// is an enum, not shown here.
|
||||
///
|
||||
/// ```dart
|
||||
/// const CustomPopupMenuItem<WhyFarther>(
|
||||
/// value: WhyFarther.harder,
|
||||
/// child: Text('Working a lot harder'),
|
||||
/// )
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// See the example at [PopupMenuButton] for how this example could be used in a
|
||||
/// complete menu, and see the example at [CheckedPopupMenuItem] for one way to
|
||||
/// keep the text of [CustomPopupMenuItem]s that use [Text] widgets in their [child]
|
||||
/// slot aligned with the text of [CheckedPopupMenuItem]s or of [CustomPopupMenuItem]
|
||||
/// that use a [ListTile] in their [child] slot.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [PopupMenuDivider], which can be used to divide items from each other.
|
||||
/// * [CheckedPopupMenuItem], a variant of [CustomPopupMenuItem] with a checkmark.
|
||||
/// * [customShowMenu], a method to dynamically show a popup menu at a given location.
|
||||
/// * [PopupMenuButton], an [IconButton] that automatically shows a menu when
|
||||
/// it is tapped.
|
||||
class CustomPopupMenuItem<T> extends PopupMenuEntry<T> {
|
||||
/// Creates an item for a popup menu.
|
||||
///
|
||||
/// By default, the item is [enabled].
|
||||
///
|
||||
/// The `enabled` and `height` arguments must not be null.
|
||||
const CustomPopupMenuItem({
|
||||
Key? key,
|
||||
this.value,
|
||||
this.enabled = true,
|
||||
this.height = kMinInteractiveDimension,
|
||||
this.textStyle,
|
||||
required this.child,
|
||||
}) : super(key: key);
|
||||
|
||||
/// The value that will be returned by [customShowMenu] if this entry is selected.
|
||||
final T? value;
|
||||
|
||||
/// Whether the user is permitted to select this item.
|
||||
///
|
||||
/// Defaults to true. If this is false, then the item will not react to
|
||||
/// touches.
|
||||
final bool enabled;
|
||||
|
||||
/// The minimum height height of the menu item.
|
||||
///
|
||||
/// Defaults to [kMinInteractiveDimension] pixels.
|
||||
@override
|
||||
final double height;
|
||||
|
||||
/// The text style of the popup menu item.
|
||||
///
|
||||
/// If this property is null, then [PopupMenuThemeData.textStyle] is used.
|
||||
/// If [PopupMenuThemeData.textStyle] is also null, then [ThemeData.textTheme.subhead] is used.
|
||||
final TextStyle? textStyle;
|
||||
|
||||
/// The widget below this widget in the tree.
|
||||
///
|
||||
/// Typically a single-line [ListTile] (for menus with icons) or a [Text]. An
|
||||
/// appropriate [DefaultTextStyle] is put in scope for the child. In either
|
||||
/// case, the text should be short enough that it won't wrap.
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
bool represents(T? value) => value == this.value;
|
||||
|
||||
@override
|
||||
PopupMenuItemState<T, CustomPopupMenuItem<T>> createState() => PopupMenuItemState<T, CustomPopupMenuItem<T>>();
|
||||
}
|
||||
|
||||
/// The [State] for [CustomPopupMenuItem] subclasses.
|
||||
///
|
||||
/// By default this implements the basic styling and layout of Material Design
|
||||
/// popup menu items.
|
||||
///
|
||||
/// The [buildChild] method can be overridden to adjust exactly what gets placed
|
||||
/// in the menu. By default it returns [CustomPopupMenuItem.child].
|
||||
///
|
||||
/// The [handleTap] method can be overridden to adjust exactly what happens when
|
||||
/// the item is tapped. By default, it uses [Navigator.pop] to return the
|
||||
/// [CustomPopupMenuItem.value] from the menu route.
|
||||
///
|
||||
/// This class takes two type arguments. The second, `W`, is the exact type of
|
||||
/// the [Widget] that is using this [State]. It must be a subclass of
|
||||
/// [CustomPopupMenuItem]. The first, `T`, must match the type argument of that widget
|
||||
/// class, and is the type of values returned from this menu.
|
||||
class PopupMenuItemState<T, W extends CustomPopupMenuItem<T>> extends State<W> {
|
||||
/// The menu item contents.
|
||||
///
|
||||
/// Used by the [build] method.
|
||||
///
|
||||
/// By default, this returns [CustomPopupMenuItem.child]. Override this to put
|
||||
/// something else in the menu entry.
|
||||
@protected
|
||||
Widget buildChild() => widget.child;
|
||||
|
||||
/// The handler for when the user selects the menu item.
|
||||
///
|
||||
/// Used by the [InkWell] inserted by the [build] method.
|
||||
///
|
||||
/// By default, uses [Navigator.pop] to return the [CustomPopupMenuItem.value] from
|
||||
/// the menu route.
|
||||
@protected
|
||||
void handleTap() {
|
||||
Navigator.pop<T>(context, widget.value);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
|
||||
TextStyle? style = widget.textStyle ?? popupMenuTheme.textStyle ?? theme.textTheme.subtitle1;
|
||||
|
||||
if (!widget.enabled) style = style!.copyWith(color: theme.disabledColor);
|
||||
|
||||
Widget item = AnimatedDefaultTextStyle(
|
||||
style: style!,
|
||||
duration: kThemeChangeDuration,
|
||||
child: Container(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
constraints: BoxConstraints(minHeight: widget.height),
|
||||
padding: const EdgeInsets.symmetric(horizontal: _kMenuHorizontalPadding),
|
||||
child: buildChild(),
|
||||
),
|
||||
);
|
||||
|
||||
if (!widget.enabled) {
|
||||
final bool isDark = theme.brightness == Brightness.dark;
|
||||
item = IconTheme.merge(
|
||||
data: IconThemeData(opacity: isDark ? 0.5 : 0.38),
|
||||
child: item,
|
||||
);
|
||||
}
|
||||
|
||||
return InkWell(
|
||||
onTap: widget.enabled ? handleTap : null,
|
||||
canRequestFocus: widget.enabled,
|
||||
child: item,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PopupMenu<T> extends StatelessWidget {
|
||||
const _PopupMenu({
|
||||
Key? key,
|
||||
this.route,
|
||||
this.semanticLabel,
|
||||
}) : super(key: key);
|
||||
|
||||
final _PopupMenuRoute<T>? route;
|
||||
final String? semanticLabel;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double unit = 1.0 / (route!.items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade.
|
||||
final List<Widget> children = <Widget>[];
|
||||
final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
|
||||
|
||||
for (int i = 0; i < route!.items.length; i += 1) {
|
||||
final double start = (i + 1) * unit;
|
||||
final double end = (start + 1.5 * unit).clamp(0.0, 1.0);
|
||||
final CurvedAnimation opacity = CurvedAnimation(
|
||||
parent: route!.animation!,
|
||||
curve: Interval(start, end),
|
||||
);
|
||||
Widget item = route!.items[i];
|
||||
if (route!.initialValue != null && route!.items[i].represents(route!.initialValue)) {
|
||||
item = Container(
|
||||
color: Theme.of(context).highlightColor,
|
||||
child: item,
|
||||
);
|
||||
}
|
||||
children.add(
|
||||
_MenuItem(
|
||||
onLayout: (Size size) {
|
||||
route!.itemSizes[i] = size;
|
||||
},
|
||||
child: FadeTransition(
|
||||
opacity: opacity,
|
||||
child: item,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final CurveTween opacity = CurveTween(curve: const Interval(0.0, 1.0 / 3.0));
|
||||
final CurveTween width = CurveTween(curve: Interval(0.0, unit));
|
||||
final CurveTween height = CurveTween(curve: Interval(0.0, unit * route!.items.length));
|
||||
|
||||
final Widget child = ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: _kMenuMinWidth),
|
||||
child: IntrinsicWidth(
|
||||
stepWidth: _kMenuWidthStep,
|
||||
child: Semantics(
|
||||
scopesRoute: true,
|
||||
namesRoute: true,
|
||||
explicitChildNodes: true,
|
||||
label: semanticLabel,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(vertical: _kMenuVerticalPadding),
|
||||
child: ListBody(children: children),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return AnimatedBuilder(
|
||||
animation: route!.animation!,
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
return Opacity(
|
||||
opacity: opacity.evaluate(route!.animation!),
|
||||
child: Material(
|
||||
shape: route!.shape ?? popupMenuTheme.shape,
|
||||
color: route!.color ?? popupMenuTheme.color,
|
||||
type: MaterialType.card,
|
||||
elevation: route!.elevation ?? popupMenuTheme.elevation ?? 8.0,
|
||||
child: Align(
|
||||
alignment: AlignmentDirectional.topEnd,
|
||||
widthFactor: width.evaluate(route!.animation!),
|
||||
heightFactor: height.evaluate(route!.animation!),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Positioning of the menu on the screen.
|
||||
class _PopupMenuRouteLayout extends SingleChildLayoutDelegate {
|
||||
_PopupMenuRouteLayout(this.position, this.itemSizes, this.selectedItemIndex, this.textDirection);
|
||||
|
||||
// Rectangle of underlying button, relative to the overlay's dimensions.
|
||||
final RelativeRect? position;
|
||||
|
||||
// The sizes of each item are computed when the menu is laid out, and before
|
||||
// the route is laid out.
|
||||
List<Size?> itemSizes;
|
||||
|
||||
// The index of the selected item, or null if PopupMenuButton.initialValue
|
||||
// was not specified.
|
||||
final int? selectedItemIndex;
|
||||
|
||||
// Whether to prefer going to the left or to the right.
|
||||
final TextDirection textDirection;
|
||||
|
||||
// We put the child wherever position specifies, so long as it will fit within
|
||||
// the specified parent size padded (inset) by 8. If necessary, we adjust the
|
||||
// child's position so that it fits.
|
||||
|
||||
@override
|
||||
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
|
||||
// The menu can be at most the size of the overlay minus 8.0 pixels in each
|
||||
// direction.
|
||||
return BoxConstraints.loose(constraints.biggest - const Offset(_kMenuScreenPadding * 2.0, _kMenuScreenPadding * 2.0) as Size);
|
||||
}
|
||||
|
||||
@override
|
||||
Offset getPositionForChild(Size size, Size childSize) {
|
||||
// size: The size of the overlay.
|
||||
// childSize: The size of the menu, when fully open, as determined by
|
||||
// getConstraintsForChild.
|
||||
|
||||
// Find the ideal vertical position.
|
||||
double y = position!.top;
|
||||
if (selectedItemIndex != null) {
|
||||
double selectedItemOffset = _kMenuVerticalPadding;
|
||||
for (int index = 0; index < selectedItemIndex!; index += 1) selectedItemOffset += itemSizes[index]!.height;
|
||||
selectedItemOffset += itemSizes[selectedItemIndex!]!.height / 2;
|
||||
y = position!.top + (size.height - position!.top - position!.bottom) / 2.0 - selectedItemOffset;
|
||||
}
|
||||
|
||||
// Find the ideal horizontal position.
|
||||
late double x;
|
||||
if (position!.left > position!.right) {
|
||||
// Menu button is closer to the right edge, so grow to the left, aligned to the right edge.
|
||||
x = size.width - position!.right - childSize.width;
|
||||
} else if (position!.left < position!.right) {
|
||||
// Menu button is closer to the left edge, so grow to the right, aligned to the left edge.
|
||||
x = position!.left;
|
||||
} else {
|
||||
// Menu button is equidistant from both edges, so grow in reading direction.
|
||||
switch (textDirection) {
|
||||
case TextDirection.rtl:
|
||||
x = size.width - position!.right - childSize.width;
|
||||
break;
|
||||
case TextDirection.ltr:
|
||||
x = position!.left;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid going outside an area defined as the rectangle 8.0 pixels from the
|
||||
// edge of the screen in every direction.
|
||||
if (x < _kMenuScreenPadding)
|
||||
x = _kMenuScreenPadding;
|
||||
else if (x + childSize.width > size.width - _kMenuScreenPadding) x = size.width - childSize.width - _kMenuScreenPadding;
|
||||
if (y < _kMenuScreenPadding)
|
||||
y = _kMenuScreenPadding;
|
||||
else if (y + childSize.height > size.height - _kMenuScreenPadding) y = size.height - childSize.height - _kMenuScreenPadding;
|
||||
return Offset(x, y);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRelayout(_PopupMenuRouteLayout oldDelegate) {
|
||||
// If called when the old and new itemSizes have been initialized then
|
||||
// we expect them to have the same length because there's no practical
|
||||
// way to change length of the items list once the menu has been shown.
|
||||
assert(itemSizes.length == oldDelegate.itemSizes.length);
|
||||
|
||||
return position != oldDelegate.position ||
|
||||
selectedItemIndex != oldDelegate.selectedItemIndex ||
|
||||
textDirection != oldDelegate.textDirection ||
|
||||
!listEquals(itemSizes, oldDelegate.itemSizes);
|
||||
}
|
||||
}
|
||||
|
||||
class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
_PopupMenuRoute({
|
||||
this.position,
|
||||
required this.items,
|
||||
this.initialValue,
|
||||
this.elevation,
|
||||
this.theme,
|
||||
this.popupMenuTheme,
|
||||
this.barrierLabel,
|
||||
this.semanticLabel,
|
||||
this.shape,
|
||||
this.color,
|
||||
this.showMenuContext,
|
||||
this.captureInheritedThemes,
|
||||
this.barrierColor,
|
||||
}) : itemSizes = List<Size?>.filled(items.length, null, growable: false);
|
||||
|
||||
final RelativeRect? position;
|
||||
final List<PopupMenuEntry<T>> items;
|
||||
final List<Size?> itemSizes;
|
||||
final dynamic initialValue;
|
||||
final double? elevation;
|
||||
final ThemeData? theme;
|
||||
final String? semanticLabel;
|
||||
final ShapeBorder? shape;
|
||||
final Color? color;
|
||||
final PopupMenuThemeData? popupMenuTheme;
|
||||
final BuildContext? showMenuContext;
|
||||
final bool? captureInheritedThemes;
|
||||
final Color? barrierColor;
|
||||
|
||||
@override
|
||||
Animation<double> createAnimation() {
|
||||
return CurvedAnimation(
|
||||
parent: super.createAnimation(),
|
||||
curve: Curves.linear,
|
||||
reverseCurve: const Interval(0.0, _kMenuCloseIntervalEnd),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Duration get transitionDuration => _kMenuDuration;
|
||||
|
||||
@override
|
||||
bool get barrierDismissible => true;
|
||||
|
||||
@override
|
||||
final String? barrierLabel;
|
||||
|
||||
@override
|
||||
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
|
||||
int? selectedItemIndex;
|
||||
if (initialValue != null) {
|
||||
for (int index = 0; selectedItemIndex == null && index < items.length; index += 1) {
|
||||
if (items[index].represents(initialValue)) selectedItemIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
Widget menu = _PopupMenu<T>(route: this, semanticLabel: semanticLabel);
|
||||
if (captureInheritedThemes!) {
|
||||
menu = InheritedTheme.captureAll(showMenuContext!, menu);
|
||||
} else {
|
||||
// For the sake of backwards compatibility. An (unlikely) app that relied
|
||||
// on having menus only inherit from the material Theme could set
|
||||
// captureInheritedThemes to false and get the original behavior.
|
||||
if (theme != null) menu = Theme(data: theme!, child: menu);
|
||||
}
|
||||
|
||||
return MediaQuery.removePadding(
|
||||
context: context,
|
||||
removeTop: true,
|
||||
removeBottom: true,
|
||||
removeLeft: true,
|
||||
removeRight: true,
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
return CustomSingleChildLayout(
|
||||
delegate: _PopupMenuRouteLayout(
|
||||
position,
|
||||
itemSizes,
|
||||
selectedItemIndex,
|
||||
Directionality.of(context),
|
||||
),
|
||||
child: menu,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Show a popup menu that contains the `items` at `position`.
|
||||
///
|
||||
/// `items` should be non-null and not empty.
|
||||
///
|
||||
/// If `initialValue` is specified then the first item with a matching value
|
||||
/// will be highlighted and the value of `position` gives the rectangle whose
|
||||
/// vertical center will be aligned with the vertical center of the highlighted
|
||||
/// item (when possible).
|
||||
///
|
||||
/// If `initialValue` is not specified then the top of the menu will be aligned
|
||||
/// with the top of the `position` rectangle.
|
||||
///
|
||||
/// In both cases, the menu position will be adjusted if necessary to fit on the
|
||||
/// screen.
|
||||
///
|
||||
/// Horizontally, the menu is positioned so that it grows in the direction that
|
||||
/// has the most room. For example, if the `position` describes a rectangle on
|
||||
/// the left edge of the screen, then the left edge of the menu is aligned with
|
||||
/// the left edge of the `position`, and the menu grows to the right. If both
|
||||
/// edges of the `position` are equidistant from the opposite edge of the
|
||||
/// screen, then the ambient [Directionality] is used as a tie-breaker,
|
||||
/// preferring to grow in the reading direction.
|
||||
///
|
||||
/// The positioning of the `initialValue` at the `position` is implemented by
|
||||
/// iterating over the `items` to find the first whose
|
||||
/// [CustomPopupMenuEntry.represents] method returns true for `initialValue`, and then
|
||||
/// summing the values of [CustomPopupMenuEntry.height] for all the preceding widgets
|
||||
/// in the list.
|
||||
///
|
||||
/// The `elevation` argument specifies the z-coordinate at which to place the
|
||||
/// menu. The elevation defaults to 8, the appropriate elevation for popup
|
||||
/// menus.
|
||||
///
|
||||
/// The `context` argument is used to look up the [Navigator] and [Theme] for
|
||||
/// the menu. It is only used when the method is called. Its corresponding
|
||||
/// widget can be safely removed from the tree before the popup menu is closed.
|
||||
///
|
||||
/// The `useRootNavigator` argument is used to determine whether to push the
|
||||
/// menu to the [Navigator] furthest from or nearest to the given `context`. It
|
||||
/// is `false` by default.
|
||||
///
|
||||
/// The `semanticLabel` argument is used by accessibility frameworks to
|
||||
/// announce screen transitions when the menu is opened and closed. If this
|
||||
/// label is not provided, it will default to
|
||||
/// [MaterialLocalizations.popupMenuLabel].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [CustomPopupMenuItem], a popup menu entry for a single value.
|
||||
/// * [PopupMenuDivider], a popup menu entry that is just a horizontal line.
|
||||
/// * [CheckedPopupMenuItem], a popup menu item with a checkmark.
|
||||
/// * [PopupMenuButton], which provides an [IconButton] that shows a menu by
|
||||
/// calling this method automatically.
|
||||
/// * [SemanticsConfiguration.namesRoute], for a description of edge triggered
|
||||
/// semantics.
|
||||
Future<T?> customShowMenu<T>({
|
||||
required BuildContext context,
|
||||
required RelativeRect position,
|
||||
required List<PopupMenuEntry<T>> items,
|
||||
T? initialValue,
|
||||
double? elevation,
|
||||
String? semanticLabel,
|
||||
Color? barrierColor,
|
||||
ShapeBorder? shape,
|
||||
Color? color,
|
||||
bool captureInheritedThemes = true,
|
||||
bool useRootNavigator = false,
|
||||
}) {
|
||||
assert(items.isNotEmpty);
|
||||
assert(debugCheckHasMaterialLocalizations(context));
|
||||
|
||||
String? label = semanticLabel;
|
||||
switch (Theme.of(context).platform) {
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.macOS:
|
||||
label = semanticLabel;
|
||||
break;
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.linux:
|
||||
case TargetPlatform.windows:
|
||||
label = semanticLabel ?? MaterialLocalizations.of(context).popupMenuLabel;
|
||||
}
|
||||
|
||||
return Navigator.of(context, rootNavigator: useRootNavigator).push(
|
||||
_PopupMenuRoute<T>(
|
||||
position: position,
|
||||
items: items,
|
||||
initialValue: initialValue,
|
||||
elevation: elevation,
|
||||
semanticLabel: label,
|
||||
theme: Theme.of(context),
|
||||
popupMenuTheme: PopupMenuTheme.of(context),
|
||||
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
||||
barrierColor: barrierColor,
|
||||
shape: shape,
|
||||
color: color,
|
||||
showMenuContext: context,
|
||||
captureInheritedThemes: captureInheritedThemes,
|
||||
),
|
||||
);
|
||||
}
|
489
lib/library/select_dialog.dart
Normal file
489
lib/library/select_dialog.dart
Normal file
@ -0,0 +1,489 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'dropdown_search.dart';
|
||||
|
||||
class SelectDialog<T> extends StatefulWidget {
|
||||
final T? selectedValue;
|
||||
final List<T>? items;
|
||||
final bool showSearchBox;
|
||||
final bool isFilteredOnline;
|
||||
final ValueChanged<T>? onChanged;
|
||||
final DropdownSearchOnFind<T>? onFind;
|
||||
final DropdownSearchPopupItemBuilder<T>? itemBuilder;
|
||||
final InputDecoration? searchBoxDecoration;
|
||||
final DropdownSearchItemAsString<T>? itemAsString;
|
||||
final DropdownSearchFilterFn<T>? filterFn;
|
||||
final String? hintText;
|
||||
final double? maxHeight;
|
||||
final double? dialogMaxWidth;
|
||||
final Widget? popupTitle;
|
||||
final bool showSelectedItem;
|
||||
final DropdownSearchCompareFn<T>? compareFn;
|
||||
final DropdownSearchPopupItemEnabled<T>? itemDisabled;
|
||||
|
||||
///custom layout for empty results
|
||||
final EmptyBuilder? emptyBuilder;
|
||||
|
||||
///custom layout for loading items
|
||||
final LoadingBuilder? loadingBuilder;
|
||||
|
||||
///custom layout for error
|
||||
final ErrorBuilder? errorBuilder;
|
||||
|
||||
///the search box will be focused if true
|
||||
final bool autoFocusSearchBox;
|
||||
|
||||
///text controller to set default search word for example
|
||||
final TextEditingController? searchBoxController;
|
||||
|
||||
///delay before searching
|
||||
final Duration? searchDelay;
|
||||
|
||||
///show or hide favorites items
|
||||
final bool showFavoriteItems;
|
||||
|
||||
///build favorites chips
|
||||
final FavoriteItemsBuilder<T>? favoriteItemBuilder;
|
||||
|
||||
///favorite items alignment
|
||||
final MainAxisAlignment? favoriteItemsAlignment;
|
||||
|
||||
///favorites item
|
||||
final FavoriteItems<T>? favoriteItems;
|
||||
|
||||
const SelectDialog({
|
||||
Key? key,
|
||||
this.popupTitle,
|
||||
this.items,
|
||||
this.maxHeight,
|
||||
this.showSearchBox = false,
|
||||
this.isFilteredOnline = false,
|
||||
this.onChanged,
|
||||
this.selectedValue,
|
||||
this.onFind,
|
||||
this.itemBuilder,
|
||||
this.searchBoxDecoration,
|
||||
this.hintText,
|
||||
this.itemAsString,
|
||||
this.filterFn,
|
||||
this.showSelectedItem = false,
|
||||
this.compareFn,
|
||||
this.emptyBuilder,
|
||||
this.loadingBuilder,
|
||||
this.errorBuilder,
|
||||
this.autoFocusSearchBox = false,
|
||||
this.dialogMaxWidth,
|
||||
this.itemDisabled,
|
||||
this.searchBoxController,
|
||||
this.searchDelay,
|
||||
this.favoriteItemBuilder,
|
||||
this.favoriteItems,
|
||||
this.showFavoriteItems = false,
|
||||
this.favoriteItemsAlignment = MainAxisAlignment.start,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_SelectDialogState<T> createState() => _SelectDialogState<T>();
|
||||
}
|
||||
|
||||
class _SelectDialogState<T> extends State<SelectDialog<T?>> {
|
||||
final FocusNode focusNode = new FocusNode();
|
||||
final StreamController<List<T?>> _itemsStream = StreamController<List<T?>>.broadcast();
|
||||
final ValueNotifier<bool> _loadingNotifier = ValueNotifier(false);
|
||||
final List<T?> _items = <T>[];
|
||||
late Debouncer _debouncer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_debouncer = Debouncer(delay: widget.searchDelay);
|
||||
|
||||
Future.delayed(
|
||||
Duration.zero,
|
||||
() => manageItemsByFilter(widget.searchBoxController?.text ?? '', isFistLoad: true),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
if (widget.autoFocusSearchBox) FocusScope.of(context).requestFocus(focusNode);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_itemsStream.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Size deviceSize = MediaQuery.of(context).size;
|
||||
bool isTablet = deviceSize.width > deviceSize.height;
|
||||
double maxHeight = deviceSize.height * (isTablet ? .8 : .6);
|
||||
double maxWidth = deviceSize.width * (isTablet ? .7 : .9);
|
||||
|
||||
return Container(
|
||||
width: widget.dialogMaxWidth ?? maxWidth,
|
||||
constraints: BoxConstraints(maxHeight: widget.maxHeight ?? maxHeight),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
_searchField(),
|
||||
if (widget.showFavoriteItems == true) _favoriteItemsWidget(),
|
||||
Expanded(
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
StreamBuilder<List<T?>>(
|
||||
stream: _itemsStream.stream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) {
|
||||
return _errorWidget(snapshot.error);
|
||||
} else if (!snapshot.hasData) {
|
||||
return _loadingWidget();
|
||||
} else if (snapshot.data!.isEmpty) {
|
||||
if (widget.emptyBuilder != null)
|
||||
return widget.emptyBuilder!(context, widget.searchBoxController?.text);
|
||||
else
|
||||
return const Center(
|
||||
child: const Text("No data found"),
|
||||
);
|
||||
}
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
padding: EdgeInsets.symmetric(vertical: 0),
|
||||
itemCount: snapshot.data!.length,
|
||||
itemBuilder: (context, index) {
|
||||
var item = snapshot.data![index];
|
||||
return _itemWidget(item);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
_loadingWidget()
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showErrorDialog(dynamic error) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text("Error while getting online items"),
|
||||
content: _errorWidget(error),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: new Text("OK"),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _errorWidget(dynamic error) {
|
||||
if (widget.errorBuilder != null)
|
||||
return widget.errorBuilder!(context, widget.searchBoxController?.text, error);
|
||||
else
|
||||
return Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: Text(
|
||||
error?.toString() ?? 'Error',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _loadingWidget() {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: _loadingNotifier,
|
||||
builder: (context, bool isLoading, wid) {
|
||||
if (isLoading) {
|
||||
if (widget.loadingBuilder != null)
|
||||
return widget.loadingBuilder!(context, widget.searchBoxController?.text);
|
||||
else
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: const Center(
|
||||
child: const CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Container();
|
||||
});
|
||||
}
|
||||
|
||||
void _onTextChanged(String filter) async {
|
||||
manageItemsByFilter(filter);
|
||||
}
|
||||
|
||||
///Function that filter item (online and offline) base on user filter
|
||||
///[filter] is the filter keyword
|
||||
///[isFirstLoad] true if it's the first time we load data from online, false other wises
|
||||
void manageItemsByFilter(String filter, {bool isFistLoad = false}) async {
|
||||
_loadingNotifier.value = true;
|
||||
|
||||
String encoded(String item) {
|
||||
String encodedItem = "";
|
||||
for (int i = 0; i < item.length; i++) {
|
||||
var char = item[i];
|
||||
switch (char) {
|
||||
case 'Á':
|
||||
case 'á':
|
||||
case 'ą':
|
||||
case 'ä':
|
||||
char = 'a';
|
||||
break;
|
||||
case 'é':
|
||||
case 'É':
|
||||
char = 'e';
|
||||
break;
|
||||
case 'ú':
|
||||
case 'ű':
|
||||
case 'ü':
|
||||
case 'Ú':
|
||||
case 'Ű':
|
||||
case 'Ü':
|
||||
char = 'u';
|
||||
break;
|
||||
case 'ö':
|
||||
case 'ő':
|
||||
case 'ó':
|
||||
case 'Ö':
|
||||
case 'Ő':
|
||||
case 'Ó':
|
||||
char = 'o';
|
||||
break;
|
||||
case 'í':
|
||||
case 'Í':
|
||||
char = 'i';
|
||||
break;
|
||||
}
|
||||
encodedItem += char;
|
||||
}
|
||||
return encodedItem;
|
||||
}
|
||||
|
||||
List<T?> applyFilter(String filter) {
|
||||
return _items.where((i) {
|
||||
if (widget.filterFn != null)
|
||||
return (widget.filterFn!(i, filter));
|
||||
else if (i.toString().toLowerCase().contains(filter.toLowerCase()) ||
|
||||
encoded(i.toString()).toLowerCase().contains(encoded(filter.toLowerCase()))) {
|
||||
return true;
|
||||
} else if (widget.itemAsString != null) {
|
||||
bool found = (widget.itemAsString!(i)).toLowerCase().contains(filter.toLowerCase());
|
||||
if (!found) {
|
||||
found = (encoded(widget.itemAsString!(i))).toLowerCase().contains(encoded(filter.toLowerCase()));
|
||||
}
|
||||
return found;
|
||||
}
|
||||
return false;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
//load offline data for the first time
|
||||
if (isFistLoad && widget.items != null) _items.addAll(widget.items!);
|
||||
|
||||
//manage offline items
|
||||
if (widget.onFind != null && (widget.isFilteredOnline || isFistLoad)) {
|
||||
try {
|
||||
final List<T?> onlineItems = [];
|
||||
onlineItems.addAll(await widget.onFind!(filter));
|
||||
|
||||
//Remove all old data
|
||||
_items.clear();
|
||||
//add offline items
|
||||
if (widget.items != null) {
|
||||
_items.addAll(widget.items!);
|
||||
//if filter online we filter only local list based on entered keyword (filter)
|
||||
if (widget.isFilteredOnline == true) {
|
||||
var filteredLocalList = applyFilter(filter);
|
||||
_items.clear();
|
||||
_items.addAll(filteredLocalList);
|
||||
}
|
||||
}
|
||||
//add new online items to list
|
||||
_items.addAll(onlineItems);
|
||||
|
||||
//don't filter data , they are already filtred online and local data are already filtered
|
||||
if (widget.isFilteredOnline == true)
|
||||
_addDataToStream(_items);
|
||||
else
|
||||
_addDataToStream(applyFilter(filter));
|
||||
} catch (e) {
|
||||
_addErrorToStream(e);
|
||||
//if offline items count > 0 , the error will be not visible for the user
|
||||
//As solution we show it in dialog
|
||||
if (widget.items != null && widget.items!.isNotEmpty) {
|
||||
_showErrorDialog(e);
|
||||
_addDataToStream(applyFilter(filter));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_addDataToStream(applyFilter(filter));
|
||||
}
|
||||
|
||||
_loadingNotifier.value = false;
|
||||
}
|
||||
|
||||
void _addDataToStream(List<T?> data) {
|
||||
if (_itemsStream.isClosed) return;
|
||||
_itemsStream.add(data);
|
||||
}
|
||||
|
||||
void _addErrorToStream(Object error, [StackTrace? stackTrace]) {
|
||||
if (_itemsStream.isClosed) return;
|
||||
_itemsStream.addError(error, stackTrace);
|
||||
}
|
||||
|
||||
Widget _itemWidget(T? item) {
|
||||
if (widget.itemBuilder != null)
|
||||
return InkWell(
|
||||
child: widget.itemBuilder!(
|
||||
context,
|
||||
item,
|
||||
_manageSelectedItemVisibility(item),
|
||||
),
|
||||
onTap: widget.itemDisabled != null && (widget.itemDisabled!(item)) == true ? null : () => _handleSelectItem(item),
|
||||
);
|
||||
else
|
||||
return ListTile(
|
||||
title: Text(_selectedItemAsString(item)),
|
||||
selected: _manageSelectedItemVisibility(item),
|
||||
onTap: widget.itemDisabled != null && (widget.itemDisabled!(item)) == true ? null : () => _handleSelectItem(item),
|
||||
);
|
||||
}
|
||||
|
||||
/// selected item will be highlighted only when [widget.showSelectedItem] is true,
|
||||
/// if our object is String [widget.compareFn] is not required , other wises it's required
|
||||
bool _manageSelectedItemVisibility(T? item) {
|
||||
if (!widget.showSelectedItem) return false;
|
||||
|
||||
if (item is String?) {
|
||||
return item == widget.selectedValue;
|
||||
} else {
|
||||
return widget.compareFn!(item, widget.selectedValue);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _searchField() {
|
||||
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: <Widget>[
|
||||
widget.popupTitle ?? const SizedBox.shrink(),
|
||||
if (widget.showSearchBox)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: TextField(
|
||||
controller: widget.searchBoxController,
|
||||
focusNode: focusNode,
|
||||
onChanged: (f) => _debouncer(() {
|
||||
_onTextChanged(f);
|
||||
}),
|
||||
decoration: widget.searchBoxDecoration ??
|
||||
InputDecoration(
|
||||
hintText: widget.hintText,
|
||||
border: const OutlineInputBorder(),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
),
|
||||
),
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
Widget _favoriteItemsWidget() {
|
||||
return StreamBuilder<List<T?>>(
|
||||
stream: _itemsStream.stream,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return _buildFavoriteItems(widget.favoriteItems!(snapshot.data!));
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildFavoriteItems(List<T?>? favoriteItems) {
|
||||
if (favoriteItems != null) {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
child: LayoutBuilder(builder: (context, constraints) {
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minWidth: constraints.maxWidth),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: widget.favoriteItemsAlignment ?? MainAxisAlignment.start,
|
||||
children: favoriteItems
|
||||
.map(
|
||||
(f) => GestureDetector(
|
||||
onTap: () => _handleSelectItem(f),
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(right: 4),
|
||||
child: widget.favoriteItemBuilder != null
|
||||
? widget.favoriteItemBuilder!(context, f)
|
||||
: _favoriteItemDefaultWidget(f),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList()),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
|
||||
void _handleSelectItem(T? selectedItem) {
|
||||
Navigator.pop(context, selectedItem);
|
||||
if (widget.onChanged != null) widget.onChanged!(selectedItem);
|
||||
}
|
||||
|
||||
Widget _favoriteItemDefaultWidget(T? item) {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), color: Theme.of(context).primaryColorLight),
|
||||
child: Text(
|
||||
_selectedItemAsString(item),
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.subtitle1,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
///function that return the String value of an object
|
||||
String _selectedItemAsString(T? data) {
|
||||
if (data == null) {
|
||||
return "";
|
||||
} else if (widget.itemAsString != null) {
|
||||
return widget.itemAsString!(data);
|
||||
} else {
|
||||
return data.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Debouncer {
|
||||
final Duration? delay;
|
||||
Timer? _timer;
|
||||
|
||||
Debouncer({this.delay});
|
||||
|
||||
call(Function action) {
|
||||
_timer?.cancel();
|
||||
_timer = Timer(delay ?? const Duration(milliseconds: 500), action as void Function());
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ import 'package:aitrainer_app/view/customer_fitness_page.dart';
|
||||
import 'package:aitrainer_app/view/customer_goal_page.dart';
|
||||
import 'package:aitrainer_app/view/customer_modify_page.dart';
|
||||
import 'package:aitrainer_app/view/customer_welcome_page.dart';
|
||||
import 'package:aitrainer_app/view/evaluation.dart';
|
||||
import 'package:aitrainer_app/view/evaluation_page.dart';
|
||||
import 'package:aitrainer_app/view/exercise_control_page.dart';
|
||||
import 'package:aitrainer_app/view/exercise_execute_page.dart';
|
||||
import 'package:aitrainer_app/view/exercise_execute_plan_add_page.dart';
|
||||
@ -37,7 +37,7 @@ import 'package:aitrainer_app/view/test_set_new.dart';
|
||||
import 'package:aitrainer_app/widgets/home.dart';
|
||||
import 'package:firebase_analytics/firebase_analytics.dart';
|
||||
import 'package:firebase_analytics/observer.dart';
|
||||
import 'package:aitrainer_app/library/flurry.dart';
|
||||
import 'package:flurry/flurry.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@ -118,6 +118,11 @@ Future<Null> main() async {
|
||||
// - https://api.dartlang.org/stable/1.24.2/dart-async/Zone-class.html
|
||||
// - https://www.dartlang.org/articles/libraries/zones
|
||||
runZonedGuarded<Future<Null>>(() async {
|
||||
await Sentry.init(
|
||||
(options) {
|
||||
options.dsn = dsn;
|
||||
},
|
||||
);
|
||||
final WorkoutTreeRepository menuTreeRepository = WorkoutTreeRepository();
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
import 'package:aitrainer_app/model/customer.dart';
|
||||
import 'package:aitrainer_app/model/evaluation.dart';
|
||||
import 'package:aitrainer_app/model/exercise_plan.dart';
|
||||
import 'package:aitrainer_app/model/exercise_plan_detail.dart';
|
||||
import 'package:aitrainer_app/model/exercise_plan_template.dart';
|
||||
@ -21,7 +22,7 @@ import 'package:aitrainer_app/main.dart';
|
||||
import 'package:aitrainer_app/util/enums.dart';
|
||||
import 'package:aitrainer_app/util/env.dart';
|
||||
import 'package:aitrainer_app/util/track.dart';
|
||||
import 'package:aitrainer_app/library/flurry.dart';
|
||||
import 'package:flurry/flurry.dart';
|
||||
import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
|
||||
import 'package:package_info/package_info.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
@ -84,8 +85,8 @@ class Cache with Logging {
|
||||
AccessToken? accessTokenFacebook;
|
||||
Customer? userLoggedIn;
|
||||
String? firebaseUid;
|
||||
late LoginType loginType;
|
||||
late PackageInfo packageInfo;
|
||||
LoginType? loginType;
|
||||
PackageInfo? packageInfo;
|
||||
|
||||
bool hasPurchased = false;
|
||||
|
||||
@ -93,6 +94,7 @@ class Cache with Logging {
|
||||
|
||||
List<ExerciseType>? _exerciseTypes;
|
||||
List<ExerciseTree>? _exerciseTree;
|
||||
List<Evaluation>? _evaluations;
|
||||
|
||||
List<Exercise>? _exercises;
|
||||
ExercisePlan? _myExercisePlan;
|
||||
@ -204,7 +206,6 @@ class Cache with Logging {
|
||||
|
||||
String? detailsJson = sharedPreferences.getString(Cache.activeExercisePlanDetailsKey);
|
||||
if (detailsJson != null) {
|
||||
print("Details $detailsJson");
|
||||
Iterable json = jsonDecode(detailsJson);
|
||||
this.activeExercisePlanDetails = json.map((details) => ExercisePlanDetail.fromJsonWithExerciseList(details)).toList();
|
||||
}
|
||||
@ -641,4 +642,7 @@ class Cache with Logging {
|
||||
}
|
||||
return isMuscleDevelopmentSeen!;
|
||||
}
|
||||
|
||||
List<Evaluation>? get evaluations => this._evaluations;
|
||||
set evaluations(List<Evaluation>? value) => this._evaluations = value;
|
||||
}
|
||||
|
28
lib/model/evaluation.dart
Normal file
28
lib/model/evaluation.dart
Normal file
@ -0,0 +1,28 @@
|
||||
import 'package:aitrainer_app/model/evaluation_attribute.dart';
|
||||
|
||||
class Evaluation {
|
||||
late int evaluationId;
|
||||
late String name;
|
||||
late int exerciseTypeId;
|
||||
late String unit;
|
||||
late List attributes;
|
||||
|
||||
Evaluation.fromJson(Map json) {
|
||||
evaluationId = json['evaluationId'];
|
||||
name = json['name'];
|
||||
exerciseTypeId = json['exerciseTypeId'];
|
||||
unit = json['unit'];
|
||||
this.attributes = json['attributes'].map((attr) => EvaluationAttribute.fromJson(attr)).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
Map<String, dynamic> json = {
|
||||
'evaluationId': this.evaluationId,
|
||||
'name': this.name,
|
||||
'exerciseTypeId': this.exerciseTypeId,
|
||||
'unit': this.unit
|
||||
};
|
||||
return json.toString();
|
||||
}
|
||||
}
|
41
lib/model/evaluation_attribute.dart
Normal file
41
lib/model/evaluation_attribute.dart
Normal file
@ -0,0 +1,41 @@
|
||||
class EvaluationAttribute {
|
||||
late int evaluationAttrId;
|
||||
late int evaluationId;
|
||||
late String name;
|
||||
late String sex;
|
||||
late int ageMin;
|
||||
late int ageMax;
|
||||
late double valueMin;
|
||||
late double valueMax;
|
||||
late String evaluationText;
|
||||
String? suggestion;
|
||||
|
||||
EvaluationAttribute.fromJson(Map json) {
|
||||
evaluationAttrId = json['evaluationAttrId'];
|
||||
evaluationId = json['evaluationId'];
|
||||
name = json['name'];
|
||||
sex = json['sex'];
|
||||
ageMin = json['ageMin'];
|
||||
ageMax = json['ageMax'];
|
||||
valueMin = json['valueMin'];
|
||||
valueMax = json['valueMax'];
|
||||
evaluationText = json['evaluation_text'];
|
||||
suggestion = json['suggestion'];
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
Map<String, dynamic> json = {
|
||||
'evaluationAttrId': this.evaluationAttrId,
|
||||
'evaluationId': this.evaluationId,
|
||||
'name': this.name,
|
||||
'sex': this.sex,
|
||||
'ageMin': this.ageMin,
|
||||
'ageMax': this.ageMax,
|
||||
'valueMin': this.valueMin,
|
||||
'valueMax': this.valueMax,
|
||||
'evaluation_text': this.evaluationText,
|
||||
};
|
||||
return json.toString();
|
||||
}
|
||||
}
|
@ -61,4 +61,10 @@ class ExercisePlan {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
Map<String, dynamic> json = toJson();
|
||||
return json.toString();
|
||||
}
|
||||
}
|
||||
|
@ -12,13 +12,16 @@ extension ExericisePlanDetailStateExt on ExercisePlanDetailState {
|
||||
|
||||
class ExercisePlanDetail {
|
||||
int? exercisePlanDetailId;
|
||||
late int exercisePlanId;
|
||||
int? exercisePlanId;
|
||||
late int exerciseTypeId;
|
||||
int? serie;
|
||||
int? repeats;
|
||||
String? weightEquation;
|
||||
|
||||
List? exercises;
|
||||
/// List<Exercise>
|
||||
List<Exercise>? exercises;
|
||||
|
||||
/// bool finished
|
||||
bool? finished;
|
||||
ExercisePlanDetailState state = ExercisePlanDetailState.start;
|
||||
|
||||
@ -53,7 +56,7 @@ class ExercisePlanDetail {
|
||||
|
||||
jsonExercises = jsonExercises.replaceAll(r'\"null\"', 'null');
|
||||
|
||||
print("Exercises $jsonExercises");
|
||||
//print("Exercises $jsonExercises");
|
||||
Iterable iterable = jsonDecode(jsonExercises);
|
||||
this.exercises = iterable.map((exercise) => Exercise.fromJson(exercise)).toList();
|
||||
} on Exception catch (e) {
|
||||
@ -62,7 +65,7 @@ class ExercisePlanDetail {
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"exercisePlanId": exercisePlanId,
|
||||
"exercisePlanId": exercisePlanId == null ? 0 : exercisePlanId,
|
||||
"exerciseTypeId": exerciseTypeId,
|
||||
"serie": serie,
|
||||
"repeats": repeats,
|
||||
@ -76,6 +79,12 @@ class ExercisePlanDetail {
|
||||
"serie": serie,
|
||||
"repeats": repeats,
|
||||
"weightEquation": weightEquation,
|
||||
'exercises': exercises!.map((exercise) => exercise.toJson()).toList().toString(),
|
||||
'exercises': exercises == null ? [].toString() : exercises!.map((exercise) => exercise.toJson()).toList().toString(),
|
||||
};
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
Map<String, dynamic> json = toJsonWithExerciseList();
|
||||
return json.toString();
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,23 @@
|
||||
enum Sport { football, fitness, footgolf }
|
||||
|
||||
extension SportExt on Sport {
|
||||
String toStr() => this.toString().split(".").last;
|
||||
bool equalsTo(Sport sport) => this.toString() == sport.toString();
|
||||
bool equalsStringTo(String sport) => this.toStr() == sport;
|
||||
|
||||
String description(Sport sport) {
|
||||
if (Sport.football.equalsTo(sport)) {
|
||||
return "Football";
|
||||
} else if (Sport.fitness.equalsTo(sport)) {
|
||||
return "Fitness / Body Building";
|
||||
} else if (Sport.footgolf.equalsTo(sport)) {
|
||||
return "Footgolf";
|
||||
} else {
|
||||
return "Sport";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FitnessState {
|
||||
late final String value;
|
||||
late final String stateText;
|
||||
|
@ -7,7 +7,7 @@ class Tracking {
|
||||
late int customerId;
|
||||
late DateTime dateAdd;
|
||||
late String event;
|
||||
late String eventValue;
|
||||
String? eventValue;
|
||||
late String area;
|
||||
late String platform;
|
||||
late String version;
|
||||
@ -19,6 +19,6 @@ class Tracking {
|
||||
"eventValue": eventValue,
|
||||
"area": Platform.localeName,
|
||||
"platform": Platform.isAndroid ? "Android" : "iOS",
|
||||
"version": Cache().packageInfo.version + "+" + Cache().packageInfo.buildNumber
|
||||
"version": Cache().packageInfo != null ? Cache().packageInfo!.version + "+" + Cache().packageInfo!.buildNumber : ""
|
||||
};
|
||||
}
|
||||
|
@ -21,8 +21,8 @@ class GenderItem {
|
||||
}
|
||||
|
||||
class CustomerRepository with Logging {
|
||||
late Customer customer;
|
||||
late Customer? _trainee;
|
||||
Customer? customer;
|
||||
Customer? _trainee;
|
||||
List<Customer>? _trainees;
|
||||
List<CustomerProperty>? _allProperties;
|
||||
final PropertyRepository propertyRepository = PropertyRepository();
|
||||
@ -68,51 +68,61 @@ class CustomerRepository with Logging {
|
||||
}
|
||||
|
||||
String? get name {
|
||||
return this.customer.name != null ? this.customer.name : "";
|
||||
return this.customer != null && this.customer!.name != null ? this.customer!.name : "";
|
||||
}
|
||||
|
||||
String? get firstName {
|
||||
return this.customer.firstname != null ? this.customer.firstname : "";
|
||||
return this.customer != null && this.customer!.firstname != null ? this.customer!.firstname : "";
|
||||
}
|
||||
|
||||
String get sex {
|
||||
return this.customer.sex == "m" ? "Man" : "Woman";
|
||||
if (this.customer == null) throw Exception("Initialize the customer object");
|
||||
return this.customer!.sex == "m" ? "Man" : "Woman";
|
||||
}
|
||||
|
||||
int? get birthYear {
|
||||
return this.customer.birthYear;
|
||||
if (this.customer == null) throw Exception("Initialize the customer object");
|
||||
return this.customer!.birthYear;
|
||||
}
|
||||
|
||||
String? get goal {
|
||||
return this.customer.goal;
|
||||
if (this.customer == null) throw Exception("Initialize the customer object");
|
||||
return this.customer!.goal;
|
||||
}
|
||||
|
||||
String? get fitnessLevel {
|
||||
return this.customer.fitnessLevel;
|
||||
if (this.customer == null) throw Exception("Initialize the customer object");
|
||||
return this.customer!.fitnessLevel;
|
||||
}
|
||||
|
||||
String? get bodyType {
|
||||
return this.customer.bodyType;
|
||||
if (this.customer == null) throw Exception("Initialize the customer object");
|
||||
return this.customer!.bodyType;
|
||||
}
|
||||
|
||||
setName(String name) {
|
||||
this.customer.name = name;
|
||||
if (this.customer == null) return;
|
||||
this.customer!.name = name;
|
||||
}
|
||||
|
||||
setFirstName(String firstName) {
|
||||
this.customer.firstname = firstName;
|
||||
if (this.customer == null) return;
|
||||
this.customer!.firstname = firstName;
|
||||
}
|
||||
|
||||
setPassword(String password) {
|
||||
this.customer.password = password;
|
||||
if (this.customer == null) return;
|
||||
this.customer!.password = password;
|
||||
}
|
||||
|
||||
setEmail(String email) {
|
||||
this.customer.email = email;
|
||||
if (this.customer == null) return;
|
||||
this.customer!.email = email;
|
||||
}
|
||||
|
||||
setSex(String sex) {
|
||||
this.customer.sex = sex;
|
||||
if (this.customer == null) return;
|
||||
this.customer!.sex = sex;
|
||||
}
|
||||
|
||||
setWeight(int weight) {
|
||||
@ -126,19 +136,20 @@ class CustomerRepository with Logging {
|
||||
}
|
||||
|
||||
setCustomerProperty(String propertyName, double value, {id = 0}) {
|
||||
if (this.customer.properties[propertyName] == null) {
|
||||
this.customer.properties[propertyName] = CustomerProperty(
|
||||
if (this.customer == null) throw Exception("Initialize the customer object");
|
||||
if (this.customer!.properties[propertyName] == null) {
|
||||
this.customer!.properties[propertyName] = CustomerProperty(
|
||||
propertyId: propertyRepository.getPropertyByName("Height")!.propertyId,
|
||||
customerId: this.customer.customerId!,
|
||||
customerId: this.customer!.customerId!,
|
||||
propertyValue: value,
|
||||
dateAdd: DateTime.now());
|
||||
} else {
|
||||
this.customer.properties[propertyName]!.propertyValue = value;
|
||||
this.customer!.properties[propertyName]!.propertyValue = value;
|
||||
}
|
||||
this.customer.properties[propertyName]!.dateAdd = DateTime.now();
|
||||
this.customer.properties[propertyName]!.newData = true;
|
||||
this.customer!.properties[propertyName]!.dateAdd = DateTime.now();
|
||||
this.customer!.properties[propertyName]!.newData = true;
|
||||
if (id > 0) {
|
||||
this.customer.properties[propertyName]!.customerPropertyId = id;
|
||||
this.customer!.properties[propertyName]!.customerPropertyId = id;
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,31 +162,36 @@ class CustomerRepository with Logging {
|
||||
}
|
||||
|
||||
double getCustomerPropertyValue(String propertyName) {
|
||||
if (this.customer.properties[propertyName] == null) {
|
||||
if (this.customer == null || this.customer!.properties[propertyName] == null) {
|
||||
return 0.0;
|
||||
} else {
|
||||
return this.customer.properties[propertyName]!.propertyValue;
|
||||
return this.customer!.properties[propertyName]!.propertyValue;
|
||||
}
|
||||
}
|
||||
|
||||
CustomerProperty? getCustomerProperty(String propertyName) {
|
||||
return this.customer.properties[propertyName];
|
||||
if (this.customer == null) throw Exception("Initialize the customer object");
|
||||
return this.customer!.properties[propertyName];
|
||||
}
|
||||
|
||||
setBirthYear(int birthYear) {
|
||||
this.customer.birthYear = birthYear;
|
||||
if (this.customer == null) throw Exception("Initialize the customer object");
|
||||
this.customer!.birthYear = birthYear;
|
||||
}
|
||||
|
||||
setFitnessLevel(String level) {
|
||||
this.customer.fitnessLevel = level;
|
||||
if (this.customer == null) throw Exception("Initialize the customer object");
|
||||
this.customer!.fitnessLevel = level;
|
||||
}
|
||||
|
||||
setGoal(String goal) {
|
||||
this.customer.goal = goal;
|
||||
if (this.customer == null) throw Exception("Initialize the customer object");
|
||||
this.customer!.goal = goal;
|
||||
}
|
||||
|
||||
setBodyType(String bodyType) {
|
||||
this.customer.bodyType = bodyType;
|
||||
if (this.customer == null) throw Exception("Initialize the customer object");
|
||||
this.customer!.bodyType = bodyType;
|
||||
}
|
||||
|
||||
createNew() {
|
||||
@ -183,7 +199,8 @@ class CustomerRepository with Logging {
|
||||
}
|
||||
|
||||
Customer getCustomer() {
|
||||
return this.customer;
|
||||
if (this.customer == null) throw Exception("Initialize the customer object");
|
||||
return this.customer!;
|
||||
}
|
||||
|
||||
void setCustomer(Customer customer) {
|
||||
@ -191,12 +208,14 @@ class CustomerRepository with Logging {
|
||||
}
|
||||
|
||||
Future<void> addCustomer() async {
|
||||
final Customer modelCustomer = customer;
|
||||
if (this.customer == null) throw Exception("Initialize the customer object");
|
||||
final Customer modelCustomer = customer!;
|
||||
await CustomerApi().addCustomer(modelCustomer);
|
||||
}
|
||||
|
||||
Future<void> saveCustomer() async {
|
||||
final Customer modelCustomer = customer;
|
||||
if (this.customer == null) throw Exception("Initialize the customer object");
|
||||
final Customer modelCustomer = customer!;
|
||||
await CustomerApi().saveCustomer(modelCustomer);
|
||||
await this.saveProperties(modelCustomer.properties);
|
||||
}
|
||||
@ -317,6 +336,7 @@ class CustomerRepository with Logging {
|
||||
}
|
||||
|
||||
void addSizes(String sex) {
|
||||
if (this.customer == null) throw Exception("Initialize the customer object");
|
||||
List<Property>? properties = Cache().getProperties();
|
||||
if (properties == null) {
|
||||
return;
|
||||
@ -328,62 +348,62 @@ class CustomerRepository with Logging {
|
||||
if (element.propertyName == "Shoulder") {
|
||||
element.top = (122 * distortionHeight).toInt();
|
||||
element.left = (130 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Shoulder");
|
||||
element.value = this.customer!.getProperty("Shoulder");
|
||||
manSizes.add(element);
|
||||
} else if (element.propertyName == "Neck") {
|
||||
element.top = (68 * distortionHeight).toInt();
|
||||
element.left = (130 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Neck");
|
||||
element.value = this.customer!.getProperty("Neck");
|
||||
manSizes.add(element);
|
||||
} else if (element.propertyName == "Biceps") {
|
||||
element.top = (178 * distortionHeight).toInt();
|
||||
element.left = (208 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Biceps");
|
||||
element.value = this.customer!.getProperty("Biceps");
|
||||
manSizes.add(element);
|
||||
} else if (element.propertyName == "Chest") {
|
||||
element.top = (154 * distortionHeight).toInt();
|
||||
element.left = (130 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Chest");
|
||||
element.value = this.customer!.getProperty("Chest");
|
||||
manSizes.add(element);
|
||||
} else if (element.propertyName == "Belly") {
|
||||
element.top = (244 * distortionHeight).toInt();
|
||||
element.left = (130 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Belly");
|
||||
element.value = this.customer!.getProperty("Belly");
|
||||
manSizes.add(element);
|
||||
} else if (element.propertyName == "Hip") {
|
||||
element.top = (308 * distortionHeight).toInt();
|
||||
element.left = (130 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Hip");
|
||||
element.value = this.customer!.getProperty("Hip");
|
||||
manSizes.add(element);
|
||||
} else if (element.propertyName == "Thigh Top") {
|
||||
element.top = (332 * distortionHeight).toInt();
|
||||
element.left = (165 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Thigh Top");
|
||||
element.value = this.customer!.getProperty("Thigh Top");
|
||||
manSizes.add(element);
|
||||
} else if (element.propertyName == "Thigh Middle") {
|
||||
element.top = (382 * distortionHeight).toInt();
|
||||
element.left = (100 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Thigh Middle");
|
||||
element.value = this.customer!.getProperty("Thigh Middle");
|
||||
manSizes.add(element);
|
||||
} else if (element.propertyName == "Knee") {
|
||||
element.top = (464 * distortionHeight).toInt();
|
||||
element.left = (97 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Knee");
|
||||
element.value = this.customer!.getProperty("Knee");
|
||||
manSizes.add(element);
|
||||
} else if (element.propertyName == "Calf") {
|
||||
element.top = (520 * distortionHeight).toInt();
|
||||
element.left = (97 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Calf");
|
||||
element.value = this.customer!.getProperty("Calf");
|
||||
manSizes.add(element);
|
||||
} else if (element.propertyName == "Ankle") {
|
||||
element.top = (620 * distortionHeight).toInt();
|
||||
element.left = (150 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Ankle");
|
||||
element.value = this.customer!.getProperty("Ankle");
|
||||
manSizes.add(element);
|
||||
} else if (element.propertyName == "Weight") {
|
||||
element.top = (402 * distortionHeight).toInt();
|
||||
element.left = (240 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Weight");
|
||||
element.value = this.customer!.getProperty("Weight");
|
||||
manSizes.add(element);
|
||||
}
|
||||
});
|
||||
@ -392,62 +412,62 @@ class CustomerRepository with Logging {
|
||||
if (element.propertyName == "Shoulder") {
|
||||
element.top = (122 * distortionHeight).toInt();
|
||||
element.left = (151 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Shoulder");
|
||||
element.value = this.customer!.getProperty("Shoulder");
|
||||
manSizes.add(element);
|
||||
} else if (element.propertyName == "Neck") {
|
||||
element.top = (78 * distortionHeight).toInt();
|
||||
element.left = (151 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Neck");
|
||||
element.value = this.customer!.getProperty("Neck");
|
||||
manSizes.add(element);
|
||||
} else if (element.propertyName == "Biceps") {
|
||||
element.top = (178 * distortionHeight).toInt();
|
||||
element.left = (212 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Biceps");
|
||||
element.value = this.customer!.getProperty("Biceps");
|
||||
manSizes.add(element);
|
||||
} else if (element.propertyName == "Chest") {
|
||||
element.top = (154 * distortionHeight).toInt();
|
||||
element.left = (151 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Chest");
|
||||
element.value = this.customer!.getProperty("Chest");
|
||||
manSizes.add(element);
|
||||
} else if (element.propertyName == "Belly") {
|
||||
element.top = (230 * distortionHeight).toInt();
|
||||
element.left = (151 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Belly");
|
||||
element.value = this.customer!.getProperty("Belly");
|
||||
manSizes.add(element);
|
||||
} else if (element.propertyName == "Hip") {
|
||||
element.top = (294 * distortionHeight).toInt();
|
||||
element.left = (151 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Hip");
|
||||
element.value = this.customer!.getProperty("Hip");
|
||||
manSizes.add(element);
|
||||
} else if (element.propertyName == "Thigh Top") {
|
||||
element.top = (335 * distortionHeight).toInt();
|
||||
element.left = (185 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Thigh Top");
|
||||
element.value = this.customer!.getProperty("Thigh Top");
|
||||
manSizes.add(element);
|
||||
} else if (element.propertyName == "Thigh Middle") {
|
||||
element.top = (377 * distortionHeight).toInt();
|
||||
element.left = (125 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Thigh Middle");
|
||||
element.value = this.customer!.getProperty("Thigh Middle");
|
||||
manSizes.add(element);
|
||||
} else if (element.propertyName == "Knee") {
|
||||
element.top = (468 * distortionHeight).toInt();
|
||||
element.left = (129 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Knee");
|
||||
element.value = this.customer!.getProperty("Knee");
|
||||
manSizes.add(element);
|
||||
} else if (element.propertyName == "Calf") {
|
||||
element.top = (525 * distortionHeight).toInt();
|
||||
element.left = (129 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Calf");
|
||||
element.value = this.customer!.getProperty("Calf");
|
||||
manSizes.add(element);
|
||||
} else if (element.propertyName == "Ankle") {
|
||||
element.top = (620 * distortionHeight).toInt();
|
||||
element.left = (162 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Ankle");
|
||||
element.value = this.customer!.getProperty("Ankle");
|
||||
manSizes.add(element);
|
||||
} else if (element.propertyName == "Weight") {
|
||||
element.top = (402 * distortionHeight).toInt();
|
||||
element.left = (240 * distortionWidth).toInt();
|
||||
element.value = this.customer.getProperty("Weight");
|
||||
element.value = this.customer!.getProperty("Weight");
|
||||
manSizes.add(element);
|
||||
}
|
||||
});
|
||||
|
77
lib/repository/evaluation_repository.dart
Normal file
77
lib/repository/evaluation_repository.dart
Normal file
@ -0,0 +1,77 @@
|
||||
import 'package:aitrainer_app/model/cache.dart';
|
||||
import 'package:aitrainer_app/model/customer.dart';
|
||||
import 'package:aitrainer_app/model/evaluation.dart';
|
||||
import 'package:aitrainer_app/model/evaluation_attribute.dart';
|
||||
import 'package:aitrainer_app/util/enums.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class EvaluationRepository {
|
||||
List<Evaluation>? evaluations;
|
||||
|
||||
EvaluationRepository() {
|
||||
evaluations = Cache().evaluations;
|
||||
}
|
||||
|
||||
String getEvaluationTextByExerciseType(int exerciseTypeId, double value) {
|
||||
String? eval;
|
||||
|
||||
Evaluation? evaluation = this.getEvaluationByExerciseTypeId(exerciseTypeId);
|
||||
Customer? customer = Cache().userLoggedIn;
|
||||
|
||||
if (evaluation != null && customer != null) {
|
||||
int? age = customer.age;
|
||||
if (age != null) {
|
||||
for (var attribute in evaluation.attributes) {
|
||||
EvaluationAttribute attr = attribute as EvaluationAttribute;
|
||||
if (age >= attr.ageMin && age <= attr.ageMax && value >= attr.valueMin && value <= attr.valueMax) {
|
||||
eval = attr.evaluationText;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (eval == null) {
|
||||
eval = EvaluationText.fair.toStr();
|
||||
}
|
||||
return eval;
|
||||
}
|
||||
|
||||
Evaluation? getEvaluationByExerciseTypeId(int exerciseTypeId) {
|
||||
Evaluation? eval;
|
||||
if (evaluations != null) {
|
||||
for (var evaluation in evaluations!) {
|
||||
if (evaluation.exerciseTypeId == exerciseTypeId) {
|
||||
eval = evaluation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return eval;
|
||||
}
|
||||
|
||||
Color getEvaluationColor(String eval) {
|
||||
Color color = Colors.yellow[100]!;
|
||||
|
||||
if (EvaluationText.very_poor.equalsStringTo(eval)) {
|
||||
return Colors.red[800]!;
|
||||
} else if (EvaluationText.poor.equalsStringTo(eval)) {
|
||||
return Colors.red[400]!;
|
||||
} else if (EvaluationText.below_average.equalsStringTo(eval)) {
|
||||
return Colors.orange[600]!;
|
||||
} else if (EvaluationText.average.equalsStringTo(eval)) {
|
||||
return Colors.yellow[600]!;
|
||||
} else if (EvaluationText.above_average.equalsStringTo(eval)) {
|
||||
return Colors.greenAccent;
|
||||
} else if (EvaluationText.good.equalsStringTo(eval)) {
|
||||
return Colors.green[400]!;
|
||||
} else if (EvaluationText.excellent.equalsStringTo(eval)) {
|
||||
return Colors.green[600]!;
|
||||
} else if (EvaluationText.elite.equalsStringTo(eval)) {
|
||||
return Colors.green[800]!;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
}
|
@ -37,6 +37,7 @@ class ExerciseDeviceRepository {
|
||||
}
|
||||
|
||||
List<ExerciseDevice> getGymDevices() {
|
||||
if (Cache().getDevices() == null) return [];
|
||||
final List<ExerciseDevice> gymDevices = [];
|
||||
if (_devices.isEmpty) {
|
||||
_devices = Cache().getDevices()!;
|
||||
|
@ -27,8 +27,13 @@ class ExercisePlanRepository {
|
||||
ExercisePlan? getExercisePlan() => exercisePlan;
|
||||
|
||||
void addDetailToPlan() {
|
||||
if (exercisePlan == null) {
|
||||
this.createNewPlan();
|
||||
}
|
||||
if (exercisePlan != null && actualPlanDetail != null) {
|
||||
if (exercisePlan!.exercisePlanId != null) {
|
||||
actualPlanDetail!.exercisePlanId = exercisePlan!.exercisePlanId!;
|
||||
}
|
||||
exercisePlanDetails[actualPlanDetail!.exerciseTypeId] = actualPlanDetail!;
|
||||
Cache().addToMyExercisePlanDetails(actualPlanDetail!);
|
||||
}
|
||||
@ -85,24 +90,14 @@ class ExercisePlanRepository {
|
||||
}
|
||||
|
||||
void removeExerciseTypeFromPlanByExerciseTypeId(int exerciseTypeId) {
|
||||
if (exercisePlanDetails[exerciseTypeId] == null) return;
|
||||
exercisePlanDetails[exerciseTypeId]!.change = ModelChange.delete;
|
||||
Cache().deleteMyExercisePlanDetailByExerciseTypeId(exerciseTypeId);
|
||||
}
|
||||
|
||||
Future<void> saveExercisePlan() async {
|
||||
if (exercisePlan == null) {
|
||||
if (Cache().userLoggedIn == null) {
|
||||
throw Exception("please log in");
|
||||
}
|
||||
|
||||
String exercisePlanName;
|
||||
if (this.customerId == Cache().userLoggedIn!.customerId) {
|
||||
exercisePlanName = Cache().userLoggedIn!.name! + " private";
|
||||
} else {
|
||||
exercisePlanName = Cache().getTrainee()!.name! + " " + Cache().getTrainee()!.firstname! + " private";
|
||||
}
|
||||
|
||||
exercisePlan = ExercisePlan(exercisePlanName, this.customerId);
|
||||
this.createNewPlan();
|
||||
}
|
||||
if (newPlan) {
|
||||
exercisePlan!.dateAdd = DateTime.now();
|
||||
@ -139,6 +134,22 @@ class ExercisePlanRepository {
|
||||
}
|
||||
}
|
||||
|
||||
void createNewPlan() {
|
||||
if (Cache().userLoggedIn == null) {
|
||||
throw Exception("please log in");
|
||||
}
|
||||
|
||||
String exercisePlanName;
|
||||
if (this.customerId == Cache().userLoggedIn!.customerId) {
|
||||
exercisePlanName = Cache().userLoggedIn!.name! + " private";
|
||||
} else {
|
||||
exercisePlanName = Cache().getTrainee()!.name! + " " + Cache().getTrainee()!.firstname! + " private";
|
||||
}
|
||||
|
||||
exercisePlan = ExercisePlan(exercisePlanName, this.customerId);
|
||||
newPlan = true;
|
||||
}
|
||||
|
||||
Future<ExercisePlan?> getLastExercisePlan() async {
|
||||
if (customerId == 0) {
|
||||
return null;
|
||||
@ -150,7 +161,12 @@ class ExercisePlanRepository {
|
||||
}
|
||||
|
||||
exercisePlan = await ExercisePlanApi().getLastExercisePlan(customerId);
|
||||
|
||||
newPlan = (exercisePlan == null);
|
||||
if (exercisePlan == null) {
|
||||
this.createNewPlan();
|
||||
}
|
||||
;
|
||||
Cache().setMyExercisePlan(exercisePlan!);
|
||||
return exercisePlan;
|
||||
}
|
||||
@ -170,16 +186,19 @@ class ExercisePlanRepository {
|
||||
exercisePlanDetails = listCache;
|
||||
return;
|
||||
} else {
|
||||
if (exercisePlan!.exercisePlanId != null) {
|
||||
list = await ExercisePlanApi().getExercisePlanDetail(exercisePlan!.exercisePlanId!);
|
||||
}
|
||||
}
|
||||
|
||||
exercisePlanDetails = LinkedHashMap<int, ExercisePlanDetail>();
|
||||
|
||||
if (list.isNotEmpty) {
|
||||
list.forEach((element) {
|
||||
newPlan = false;
|
||||
ExercisePlanDetail detail = element;
|
||||
exercisePlanDetails[detail.exerciseTypeId] = detail;
|
||||
});
|
||||
}
|
||||
Cache().setMyExercisePlanDetails(exercisePlanDetails);
|
||||
|
||||
return;
|
||||
|
@ -270,16 +270,167 @@ class ExerciseRepository {
|
||||
void getSameExercise(int exerciseTypeId, String day) {
|
||||
this.actualExerciseList = [];
|
||||
if (exerciseList != null) {
|
||||
int index = 0;
|
||||
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) {
|
||||
final String exerciseDate = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd!);
|
||||
if (exerciseTypeId == exercise.exerciseTypeId && exerciseDate == day && index < 4) {
|
||||
this.actualExerciseList!.add(exercise);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double calculate1RM(Exercise exercise) {
|
||||
double weight = exercise.unitQuantity!;
|
||||
double repeat = exercise.quantity!;
|
||||
if (weight == 0 || repeat == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
double rmWendler = weight * repeat * 0.0333 + weight;
|
||||
double rmOconner = weight * (1 + repeat / 40);
|
||||
double average = (rmWendler + rmOconner) / 2;
|
||||
|
||||
return average;
|
||||
}
|
||||
|
||||
double getBest1RMPercent(Exercise exercise) {
|
||||
double result = 0;
|
||||
if (this.exerciseList == null || this.exerciseList!.isEmpty) {
|
||||
this.exerciseList = Cache().getExercises();
|
||||
}
|
||||
|
||||
final int exerciseTypeId = exercise.exerciseTypeId!;
|
||||
double toCompare = this.calculate1RM(exercise);
|
||||
|
||||
final String today = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(DateTime.now());
|
||||
List<Exercise> oldExercises = [];
|
||||
this.exerciseList!.forEach((exercise) {
|
||||
final String exerciseDate = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd!);
|
||||
if (exercise.exerciseTypeId == exerciseTypeId && exerciseDate.compareTo(today) < 0) {
|
||||
oldExercises.add(exercise);
|
||||
}
|
||||
});
|
||||
|
||||
if (oldExercises.isNotEmpty) {
|
||||
oldExercises.sort((a, b) {
|
||||
double sumA = 0;
|
||||
double sumB = 0;
|
||||
if (a.unitQuantity != null && b.unitQuantity != null) {
|
||||
sumA = a.quantity! * a.unitQuantity!;
|
||||
sumB = b.quantity! * b.unitQuantity!;
|
||||
} else {
|
||||
sumA = a.quantity!;
|
||||
sumB = b.quantity!;
|
||||
}
|
||||
return sumA >= sumB ? 1 : -1;
|
||||
});
|
||||
|
||||
double withCompare = this.calculate1RM(oldExercises.last);
|
||||
|
||||
result = toCompare >= withCompare ? (1 - toCompare / withCompare) * 100 : (1 - toCompare / withCompare) * -100;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
double getLast1RMPercent(Exercise exercise) {
|
||||
double result = 0;
|
||||
if (this.exerciseList == null || this.exerciseList!.isEmpty) {
|
||||
this.exerciseList = Cache().getExercises();
|
||||
}
|
||||
|
||||
final int exerciseTypeId = exercise.exerciseTypeId!;
|
||||
double toCompare = this.calculate1RM(exercise);
|
||||
|
||||
final String today = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd!);
|
||||
List<Exercise> oldExercises = [];
|
||||
this.exerciseList!.forEach((exercise) {
|
||||
final String exerciseDate = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd!);
|
||||
if (exercise.exerciseTypeId == exerciseTypeId && exerciseDate.compareTo(today) < 0) {
|
||||
oldExercises.add(exercise);
|
||||
}
|
||||
});
|
||||
|
||||
if (oldExercises.isNotEmpty) {
|
||||
double withCompare = this.calculate1RM(oldExercises.first);
|
||||
result = toCompare >= withCompare ? (toCompare / withCompare) * 100 : (1 - toCompare / withCompare) * -100;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
double getBestExercisePercent(Exercise exercise) {
|
||||
double result = 0;
|
||||
if (this.exerciseList == null || this.exerciseList!.isEmpty) {
|
||||
this.exerciseList = Cache().getExercises();
|
||||
}
|
||||
|
||||
final int exerciseTypeId = exercise.exerciseTypeId!;
|
||||
double toCompare = exercise.unitQuantity != null ? exercise.quantity! * exercise.unitQuantity! : exercise.quantity!;
|
||||
|
||||
final String today = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(DateTime.now());
|
||||
List<Exercise> oldExercises = [];
|
||||
this.exerciseList!.forEach((exercise) {
|
||||
final String exerciseDate = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd!);
|
||||
if (exercise.exerciseTypeId == exerciseTypeId && exerciseDate.compareTo(today) < 0) {
|
||||
oldExercises.add(exercise);
|
||||
}
|
||||
});
|
||||
|
||||
if (oldExercises.isNotEmpty) {
|
||||
oldExercises.sort((a, b) {
|
||||
double sumA = 0;
|
||||
double sumB = 0;
|
||||
if (a.unitQuantity != null && b.unitQuantity != null) {
|
||||
sumA = a.quantity! * a.unitQuantity!;
|
||||
sumB = b.quantity! * b.unitQuantity!;
|
||||
} else {
|
||||
sumA = a.quantity!;
|
||||
sumB = b.quantity!;
|
||||
}
|
||||
return sumA >= sumB ? 1 : -1;
|
||||
});
|
||||
|
||||
double withCompare = oldExercises.last.unitQuantity != null
|
||||
? oldExercises.last.quantity! * oldExercises.last.unitQuantity!
|
||||
: oldExercises.last.quantity!;
|
||||
|
||||
result = toCompare >= withCompare ? (1 - toCompare / withCompare) * 100 : (1 - toCompare / withCompare) * -100;
|
||||
print("Last Best: ${oldExercises.last} - result: $result");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
double getLastExercisePercent(Exercise exercise) {
|
||||
double result = 0;
|
||||
if (this.exerciseList == null || this.exerciseList!.isEmpty) {
|
||||
this.exerciseList = Cache().getExercises();
|
||||
}
|
||||
|
||||
final int exerciseTypeId = exercise.exerciseTypeId!;
|
||||
double toCompare = exercise.unitQuantity != null ? exercise.quantity! * exercise.unitQuantity! : exercise.quantity!;
|
||||
|
||||
final String today = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd!);
|
||||
List<Exercise> oldExercises = [];
|
||||
this.exerciseList!.forEach((exercise) {
|
||||
final String exerciseDate = DateFormat("yyyy-MM-dd", AppLanguage().appLocal.toString()).format(exercise.dateAdd!);
|
||||
if (exercise.exerciseTypeId == exerciseTypeId && exerciseDate.compareTo(today) < 0) {
|
||||
oldExercises.add(exercise);
|
||||
}
|
||||
});
|
||||
|
||||
if (oldExercises.isNotEmpty) {
|
||||
double withCompare = oldExercises.first.unitQuantity != null
|
||||
? oldExercises.first.quantity! * oldExercises.first.unitQuantity!
|
||||
: oldExercises.first.quantity!;
|
||||
|
||||
result = toCompare >= withCompare ? (toCompare / withCompare) * 100 : (1 - toCompare / withCompare) * -100;
|
||||
print("Last Last: ${oldExercises.first} vs. $exercise - - result: $result");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void sortByDate() {
|
||||
if (exerciseList == null || exerciseList!.isEmpty) {
|
||||
return;
|
||||
|
@ -3,7 +3,7 @@ import 'package:aitrainer_app/model/property.dart';
|
||||
import 'package:aitrainer_app/service/property_service.dart';
|
||||
|
||||
class PropertyRepository {
|
||||
late List<Property>? _properties;
|
||||
List<Property>? _properties;
|
||||
|
||||
Future<List<Property>?> getDBProperties() async {
|
||||
this._properties = await PropertyApi().getProperties();
|
||||
|
@ -112,7 +112,9 @@ class WorkoutTreeRepository with Logging {
|
||||
parent != null ? parent.nameEnglish : "",
|
||||
0);
|
||||
this.tree[exerciseType.name] = menuItem;
|
||||
if (isRunning || is1RM) {
|
||||
menuAsExercise.add(menuItem);
|
||||
}
|
||||
//log("ExerciseType in Menu item ${exerciseType.toJson()} is1RM: $is1RM");
|
||||
});
|
||||
} else {
|
||||
|
@ -8,6 +8,7 @@ import 'package:aitrainer_app/model/cache.dart';
|
||||
class APIClient with Common, Logging {
|
||||
Future<String> get(String endPoint, String param) async {
|
||||
final url = Cache.getBaseUrl() + endPoint + param;
|
||||
|
||||
trace("-------- API get " + url);
|
||||
String authToken = Cache().getAuthToken();
|
||||
if (authToken.length == 0) {
|
||||
@ -57,6 +58,7 @@ class APIClient with Common, Logging {
|
||||
final response = await http.post(uri, headers: {'Authorization': '1', 'Content-Type': 'application/json'}, body: body);
|
||||
final responseCode = response.statusCode;
|
||||
if (responseCode != 200) {
|
||||
trace("authentication response: $responseCode");
|
||||
return {
|
||||
"error": "Authentication error, total failure",
|
||||
};
|
||||
@ -65,6 +67,7 @@ class APIClient with Common, Logging {
|
||||
final responseJson = json.decode(response.body);
|
||||
return responseJson;
|
||||
} catch (exception) {
|
||||
print(exception.toString());
|
||||
return {"error": "Network error, try again later " + exception.toString()};
|
||||
}
|
||||
}
|
||||
|
@ -91,9 +91,10 @@ class ExercisePlanApi with Logging {
|
||||
final String responseBody = await _client.get("exercise_plan/last/" + customerId.toString(), body);
|
||||
exercisePlan = ExercisePlan.fromJson(jsonDecode(responseBody));
|
||||
} on Exception catch (e) {
|
||||
print(e.toString());
|
||||
if (e is NotFoundException) {
|
||||
log("ExercisePlan not found for " + customerId.toString());
|
||||
return exercisePlan;
|
||||
return null;
|
||||
} else {
|
||||
throw new Exception(e.toString());
|
||||
}
|
||||
@ -104,7 +105,7 @@ class ExercisePlanApi with Logging {
|
||||
Future<List<ExercisePlanDetail>> getExercisePlanDetail(int exercisePlanId) async {
|
||||
String body = "";
|
||||
log(" ===== get exercisePlanDetail $exercisePlanId");
|
||||
List<ExercisePlanDetail> listExercisePlanDetail;
|
||||
List<ExercisePlanDetail> listExercisePlanDetail = [];
|
||||
try {
|
||||
final String responseBody = await _client.get("exercise_plan_detail/" + exercisePlanId.toString(), body);
|
||||
log("response body:" + responseBody);
|
||||
|
@ -109,7 +109,7 @@ class FirebaseApi with logging.Logging {
|
||||
// Sign in the user with Firebase. If the nonce we generated earlier does
|
||||
// not match the nonce in `appleCredential.identityToken`, sign in will fail.
|
||||
UserCredential userCredential = await FirebaseAuth.instance.signInWithCredential(oauthCredential);
|
||||
|
||||
Cache().firebaseUid = userCredential.user!.uid;
|
||||
log("userCredential: " + userCredential.toString());
|
||||
|
||||
log("Apple Credentials: " +
|
||||
@ -181,7 +181,8 @@ class FirebaseApi with logging.Logging {
|
||||
idToken: googleAuth.idToken,
|
||||
);
|
||||
|
||||
await FirebaseAuth.instance.signInWithCredential(credential);
|
||||
UserCredential userCredential = await FirebaseAuth.instance.signInWithCredential(credential);
|
||||
Cache().firebaseUid = userCredential.user!.uid;
|
||||
|
||||
log("GoogleUser: " + googleUser.toString());
|
||||
userData['email'] = googleUser.email;
|
||||
@ -230,8 +231,9 @@ class FirebaseApi with logging.Logging {
|
||||
Map<String, dynamic> userData;
|
||||
|
||||
// by default the login method has the next permissions ['email','public_profile']
|
||||
AccessToken? accessToken = await FacebookAuth.instance.accessToken;
|
||||
if (accessToken != null) {
|
||||
final LoginResult result = await FacebookAuth.instance.login();
|
||||
if (result.status == LoginStatus.success) {
|
||||
final AccessToken accessToken = result.accessToken!;
|
||||
log(accessToken.toJson().toString());
|
||||
Cache().accessTokenFacebook = accessToken;
|
||||
// get the user data
|
||||
@ -249,8 +251,9 @@ class FirebaseApi with logging.Logging {
|
||||
Map<String, dynamic> userData;
|
||||
|
||||
// by default the login method has the next permissions ['email','public_profile']
|
||||
AccessToken? accessToken = await FacebookAuth.instance.accessToken;
|
||||
if (accessToken != null) {
|
||||
final LoginResult result = await FacebookAuth.instance.login();
|
||||
if (result.status == LoginStatus.success) {
|
||||
final AccessToken accessToken = result.accessToken!;
|
||||
Cache().accessTokenFacebook = accessToken;
|
||||
// get the user data
|
||||
userData = await FacebookAuth.instance.getUserData();
|
||||
|
@ -4,6 +4,7 @@ import 'package:aitrainer_app/model/cache.dart';
|
||||
import 'package:aitrainer_app/model/customer.dart';
|
||||
import 'package:aitrainer_app/model/customer_exercise_device.dart';
|
||||
import 'package:aitrainer_app/model/customer_property.dart';
|
||||
import 'package:aitrainer_app/model/evaluation.dart';
|
||||
import 'package:aitrainer_app/model/exercise.dart';
|
||||
import 'package:aitrainer_app/model/exercise_device.dart';
|
||||
import 'package:aitrainer_app/model/exercise_plan_template.dart';
|
||||
@ -59,6 +60,9 @@ class PackageApi {
|
||||
Cache().setExercisePlanTemplates(exercisePlanTemplates);
|
||||
} else if (headRecord[0] == "ExerciseTreeParents") {
|
||||
exerciseTreeParents = json.map((exerciseTreeParent) => ExerciseTreeParents.fromJson(exerciseTreeParent)).toList();
|
||||
} else if (headRecord[0] == "Evaluation") {
|
||||
final List<Evaluation> evaluations = json.map((evaluation) => Evaluation.fromJson(evaluation)).toList();
|
||||
Cache().evaluations = evaluations;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -98,17 +98,22 @@ mixin Common {
|
||||
return datePart;
|
||||
}
|
||||
|
||||
static String? emailValidation(String email) {
|
||||
static String? emailValidation(String? email) {
|
||||
final String error = "Please type an email address";
|
||||
if (email == null) {
|
||||
return error;
|
||||
}
|
||||
bool emailValid = RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+").hasMatch(email);
|
||||
return emailValid ? null : "Please type an email address";
|
||||
return emailValid ? null : error;
|
||||
}
|
||||
|
||||
static String? passwordValidation(String? value) {
|
||||
final String error = "Password too short";
|
||||
if (value == null || value.length == 0) {
|
||||
return null;
|
||||
return error;
|
||||
}
|
||||
bool valid = 8 < value.length;
|
||||
return valid ? null : "Password too short";
|
||||
return valid ? null : error;
|
||||
}
|
||||
|
||||
static Widget badgedIcon(Color color, IconData icon, String badgeKey) {
|
||||
|
@ -73,3 +73,11 @@ extension SizesExt on SizesEnum {
|
||||
bool equalsTo(SizesEnum event) => this.toString() == event.toString();
|
||||
bool equalsStringTo(String event) => this.toString() == event;
|
||||
}
|
||||
|
||||
enum EvaluationText { very_poor, poor, fair, below_average, average, above_average, good, excellent, elite }
|
||||
|
||||
extension EvaluationTextExt on EvaluationText {
|
||||
String toStr() => this.toString().split(".").last;
|
||||
bool equalsTo(EvaluationText eval) => this.toString() == eval.toString();
|
||||
bool equalsStringTo(String eval) => this.toStr() == eval;
|
||||
}
|
||||
|
@ -3,8 +3,8 @@ import 'package:aitrainer_app/model/cache.dart';
|
||||
import 'package:aitrainer_app/service/logging.dart';
|
||||
import 'package:aitrainer_app/service/tracking_service.dart';
|
||||
import 'package:aitrainer_app/util/enums.dart';
|
||||
import 'package:aitrainer_app/library/flurry.dart';
|
||||
import 'package:aitrainer_app/model/tracking.dart' as model;
|
||||
import 'package:flurry/flurry.dart';
|
||||
import 'package:smartlook/smartlook.dart';
|
||||
|
||||
class Track with Logging {
|
||||
|
@ -8,6 +8,9 @@ mixin Trans {
|
||||
}
|
||||
|
||||
String t(String text) {
|
||||
if (context == null) {
|
||||
throw Exception("Translation: initialize the context");
|
||||
}
|
||||
return AppLocalizations.of(context)!.translate(text);
|
||||
}
|
||||
}
|
||||
|
@ -52,9 +52,11 @@ class AccountPage extends StatelessWidget with Trans {
|
||||
|
||||
customerName = accountBloc.customerRepository.firstName! + " " + accountBloc.customerRepository.name!;
|
||||
customerName = customerName.length < 3 ? t("Personal data") : customerName;
|
||||
goal = accountBloc.customerRepository.customer.goal != null ? t(accountBloc.customerRepository.customer.goal!) : goal;
|
||||
fitnessLevel = accountBloc.customerRepository.customer.fitnessLevel != null
|
||||
? t(capitalize(accountBloc.customerRepository.customer.fitnessLevel!))
|
||||
goal = accountBloc.customerRepository.customer != null && accountBloc.customerRepository.customer!.goal != null
|
||||
? t(accountBloc.customerRepository.customer!.goal!)
|
||||
: goal;
|
||||
fitnessLevel = accountBloc.customerRepository.customer != null && accountBloc.customerRepository.customer!.fitnessLevel != null
|
||||
? t(capitalize(accountBloc.customerRepository.customer!.fitnessLevel!))
|
||||
: fitnessLevel;
|
||||
bodyType = accountBloc.getAccurateBodyType();
|
||||
|
||||
@ -166,8 +168,6 @@ class AccountPage extends StatelessWidget with Trans {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [Text(t("Available Devices"), style: TextStyle(color: Colors.orange)), Icon(Icons.arrow_forward_ios)]),
|
||||
//textColor: Colors.orange,
|
||||
//color: Colors.white,
|
||||
onPressed: () => {
|
||||
if (Cache().userLoggedIn != null)
|
||||
{
|
||||
@ -185,7 +185,7 @@ class AccountPage extends StatelessWidget with Trans {
|
||||
String text = "Logout";
|
||||
Color buttonColor = Colors.orange;
|
||||
|
||||
if (accountBloc.customerRepository.customer.email == null) {
|
||||
if (accountBloc.customerRepository.customer == null || accountBloc.customerRepository.customer!.email == null) {
|
||||
text = "Login";
|
||||
buttonColor = Colors.blue;
|
||||
}
|
||||
@ -220,7 +220,7 @@ class AccountPage extends StatelessWidget with Trans {
|
||||
}
|
||||
|
||||
Widget getMyTrainees(BuildContext context, AccountBloc accountBloc) {
|
||||
if (accountBloc.customerRepository.customer.trainer == 0) {
|
||||
if (accountBloc.customerRepository.customer == null || accountBloc.customerRepository.customer!.trainer == 0) {
|
||||
return ListTile(
|
||||
title: Container(),
|
||||
);
|
||||
|
@ -11,6 +11,10 @@ import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
import '../bloc/customer_change/customer_change_bloc.dart';
|
||||
import '../library/dropdown_search.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class CustomerFitnessPage extends StatefulWidget {
|
||||
@ -22,10 +26,8 @@ class CustomerFitnessPage extends StatefulWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// dropbox for professional sport
|
||||
|
||||
class _CustomerFitnessPageState extends State<CustomerFitnessPage> with Trans {
|
||||
late String selected;
|
||||
String? selected;
|
||||
bool fulldata = false;
|
||||
|
||||
@override
|
||||
@ -41,8 +43,6 @@ class _CustomerFitnessPageState extends State<CustomerFitnessPage> with Trans {
|
||||
customerRepository = ModalRoute.of(context)!.settings.arguments as CustomerRepository;
|
||||
}
|
||||
|
||||
selected = customerRepository.customer.fitnessLevel!;
|
||||
|
||||
PreferredSizeWidget _bar = AppBarMin(
|
||||
back: true,
|
||||
);
|
||||
@ -56,6 +56,10 @@ class _CustomerFitnessPageState extends State<CustomerFitnessPage> with Trans {
|
||||
child: Builder(builder: (context) {
|
||||
// ignore: close_sinks
|
||||
CustomerChangeBloc changeBloc = BlocProvider.of<CustomerChangeBloc>(context);
|
||||
selected = changeBloc.selectedFitnessItem;
|
||||
if (selected == null) {
|
||||
selected = FitnessState.beginner;
|
||||
}
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: Container(
|
||||
@ -85,7 +89,7 @@ class _CustomerFitnessPageState extends State<CustomerFitnessPage> with Trans {
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
shape: getShape(customerRepository, FitnessState.beginner),
|
||||
shape: getShape(changeBloc, FitnessState.beginner),
|
||||
),
|
||||
child: Container(
|
||||
width: cWidth,
|
||||
@ -103,14 +107,14 @@ class _CustomerFitnessPageState extends State<CustomerFitnessPage> with Trans {
|
||||
onPressed: () => {
|
||||
setState(() {
|
||||
selected = FitnessState.beginner;
|
||||
changeBloc.add(CustomerFitnessChange(fitness: selected));
|
||||
changeBloc.add(CustomerFitnessChange(fitness: selected!));
|
||||
}),
|
||||
}),
|
||||
Divider(),
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
shape: getShape(customerRepository, FitnessState.intermediate),
|
||||
shape: getShape(changeBloc, FitnessState.intermediate),
|
||||
),
|
||||
child: Container(
|
||||
width: cWidth,
|
||||
@ -136,7 +140,7 @@ class _CustomerFitnessPageState extends State<CustomerFitnessPage> with Trans {
|
||||
onPressed: () => {
|
||||
setState(() {
|
||||
selected = FitnessState.intermediate;
|
||||
changeBloc.add(CustomerFitnessChange(fitness: selected));
|
||||
changeBloc.add(CustomerFitnessChange(fitness: selected!));
|
||||
print(selected);
|
||||
}),
|
||||
}),
|
||||
@ -144,7 +148,7 @@ class _CustomerFitnessPageState extends State<CustomerFitnessPage> with Trans {
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
shape: getShape(customerRepository, FitnessState.advanced),
|
||||
shape: getShape(changeBloc, FitnessState.advanced),
|
||||
),
|
||||
child: Container(
|
||||
width: cWidth,
|
||||
@ -170,7 +174,7 @@ class _CustomerFitnessPageState extends State<CustomerFitnessPage> with Trans {
|
||||
onPressed: () => {
|
||||
setState(() {
|
||||
selected = FitnessState.advanced;
|
||||
changeBloc.add(CustomerFitnessChange(fitness: selected));
|
||||
changeBloc.add(CustomerFitnessChange(fitness: selected!));
|
||||
print(selected);
|
||||
}),
|
||||
}),
|
||||
@ -178,7 +182,7 @@ class _CustomerFitnessPageState extends State<CustomerFitnessPage> with Trans {
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
shape: getShape(customerRepository, FitnessState.professional),
|
||||
shape: getShape(changeBloc, FitnessState.professional),
|
||||
),
|
||||
child: Container(
|
||||
width: cWidth,
|
||||
@ -204,11 +208,13 @@ class _CustomerFitnessPageState extends State<CustomerFitnessPage> with Trans {
|
||||
onPressed: () => {
|
||||
setState(() {
|
||||
selected = FitnessState.professional;
|
||||
changeBloc.add(CustomerFitnessChange(fitness: selected));
|
||||
changeBloc.add(CustomerFitnessChange(fitness: selected!));
|
||||
print(selected);
|
||||
}),
|
||||
}),
|
||||
Divider(),
|
||||
selected == FitnessState.professional ? getSport(changeBloc) : Offstage(),
|
||||
Divider(),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
onPrimary: Colors.white,
|
||||
@ -228,8 +234,8 @@ class _CustomerFitnessPageState extends State<CustomerFitnessPage> with Trans {
|
||||
})));
|
||||
}
|
||||
|
||||
dynamic getShape(CustomerRepository customerRepository, String fitnessLevel) {
|
||||
String selected = customerRepository.fitnessLevel!;
|
||||
dynamic getShape(CustomerChangeBloc changeBloc, String fitnessLevel) {
|
||||
String? selected = changeBloc.selectedFitnessItem;
|
||||
dynamic returnCode = (selected == fitnessLevel)
|
||||
? RoundedRectangleBorder(
|
||||
side: BorderSide(width: 4, color: Colors.orange),
|
||||
@ -240,4 +246,90 @@ class _CustomerFitnessPageState extends State<CustomerFitnessPage> with Trans {
|
||||
//return
|
||||
return returnCode;
|
||||
}
|
||||
|
||||
Widget getSport(CustomerChangeBloc bloc) {
|
||||
Sport? selected = bloc.getSelectedSport;
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 65, right: 65),
|
||||
child: DropdownSearch<Sport>(
|
||||
dropdownSearchDecoration: InputDecoration(
|
||||
contentPadding: EdgeInsets.only(left: 15, top: 5, bottom: 5),
|
||||
labelText: t("Sport"),
|
||||
labelStyle: GoogleFonts.inter(fontSize: 16, color: Colors.indigo),
|
||||
//fillColor: Colors.black38,
|
||||
filled: false,
|
||||
border: OutlineInputBorder(
|
||||
gapPadding: 2.0,
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderSide: BorderSide(color: Colors.blue, width: 0.4),
|
||||
),
|
||||
),
|
||||
mode: Mode.MENU,
|
||||
compareFn: (Sport i, Sport s) => i.equalsTo(s),
|
||||
showSelectedItem: true,
|
||||
selectedItem: selected,
|
||||
itemAsString: (data) => t(data.toStr()),
|
||||
onChanged: (data) {
|
||||
bloc.add(CustomerSportChange(sport: data));
|
||||
},
|
||||
dropdownBuilder: _customDropDownItem,
|
||||
popupItemBuilder: _customMenuBuilder,
|
||||
popupBarrierColor: Colors.white10,
|
||||
//popupBackgroundColor: Colors.yellow,
|
||||
items: Sport.values,
|
||||
dropDownButton: Icon(
|
||||
Icons.arrow_drop_down,
|
||||
color: Colors.indigo,
|
||||
),
|
||||
));
|
||||
//items: FitnessItem().toList()));
|
||||
}
|
||||
|
||||
Widget _customMenuBuilder(BuildContext context, Sport sport, bool isSelected) {
|
||||
//bool selected = bloc.getSelectedSport;
|
||||
return Container(
|
||||
decoration: !isSelected
|
||||
? BoxDecoration(color: Colors.grey[300])
|
||||
: BoxDecoration(
|
||||
border: Border.all(color: Colors.blue),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: Colors.grey[100],
|
||||
),
|
||||
child: ListTile(
|
||||
selected: isSelected,
|
||||
title: Text(
|
||||
t(sport.toStr()),
|
||||
style: GoogleFonts.archivoBlack(fontSize: 20, color: Colors.blue[600]),
|
||||
),
|
||||
subtitle: Text(
|
||||
t(sport.description(sport)),
|
||||
style: GoogleFonts.inter(fontSize: 12, color: Colors.blue[600]),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _customDropDownItem(BuildContext context, Sport? item, String itemDesignation) {
|
||||
return Container(
|
||||
child: (item == null)
|
||||
? ListTile(
|
||||
contentPadding: EdgeInsets.all(0),
|
||||
title: Text(
|
||||
t("No item selected"),
|
||||
style: GoogleFonts.inter(fontSize: 14, color: Colors.blue[600]),
|
||||
),
|
||||
)
|
||||
: ListTile(
|
||||
contentPadding: EdgeInsets.all(0),
|
||||
title: Text(
|
||||
t(item.toStr()),
|
||||
style: GoogleFonts.archivoBlack(fontSize: 20, color: Colors.blue[600]),
|
||||
),
|
||||
subtitle: Text(
|
||||
t(item.description(item)),
|
||||
style: GoogleFonts.inter(fontSize: 12, color: Colors.blue[600]),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import 'package:aitrainer_app/widgets/app_bar_progress.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class GoalsItem {
|
||||
static String muscle = "gain_muscle";
|
||||
@ -70,7 +71,8 @@ class _CustomerGoalPage extends State<CustomerGoalPage> with Trans {
|
||||
InkWell(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.translate("Set Your Goals"),
|
||||
style: TextStyle(color: Colors.orange, fontSize: 50, fontFamily: 'Arial', fontWeight: FontWeight.w900),
|
||||
style:
|
||||
GoogleFonts.archivoBlack(color: Colors.orange, fontSize: 30, fontWeight: FontWeight.w900),
|
||||
),
|
||||
highlightColor: Colors.white,
|
||||
),
|
||||
@ -146,6 +148,7 @@ class _CustomerGoalPage extends State<CustomerGoalPage> with Trans {
|
||||
}
|
||||
|
||||
dynamic getShape(CustomerChangeBloc customerBloc, String goal) {
|
||||
if (customerBloc.customerRepository.goal == null) return null;
|
||||
String selectedGoal = customerBloc.customerRepository.goal!;
|
||||
dynamic returnCode = (selectedGoal == goal)
|
||||
? RoundedRectangleBorder(
|
||||
|
@ -26,12 +26,12 @@ class CustomerModifyPage extends StatelessWidget with Trans {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
setContext(context);
|
||||
dynamic arguments = ModalRoute.of(context)!.settings.arguments;
|
||||
if (arguments is HashMap && arguments['personal_data'] != null) {
|
||||
fulldata = arguments['personal_data'];
|
||||
}
|
||||
|
||||
setContext(context);
|
||||
// ignore: close_sinks
|
||||
final accountBloc = BlocProvider.of<AccountBloc>(context);
|
||||
|
||||
@ -88,7 +88,6 @@ class CustomerModifyPage extends StatelessWidget with Trans {
|
||||
Widget loadForm(CustomerChangeBloc customerBloc) {
|
||||
return Form(
|
||||
key: _scaffoldKey,
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
padding: EdgeInsets.only(top: 40, left: 25, right: 25, bottom: 250),
|
||||
@ -96,7 +95,7 @@ class CustomerModifyPage extends StatelessWidget with Trans {
|
||||
alignment: Alignment.center,
|
||||
child: Column(
|
||||
children: [
|
||||
Text(t("Edit Profile"), style: GoogleFonts.inter(color: Colors.indigo, fontSize: 16), textAlign: TextAlign.center),
|
||||
Text(t("Edit Profile"), style: GoogleFonts.archivoBlack(color: Colors.indigo, fontSize: 20), textAlign: TextAlign.center),
|
||||
Divider(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
@ -114,7 +113,7 @@ class CustomerModifyPage extends StatelessWidget with Trans {
|
||||
borderSide: BorderSide(color: Colors.green[50]!, width: 0.4),
|
||||
),
|
||||
),
|
||||
initialValue: customerBloc.customerRepository.customer.email,
|
||||
initialValue: customerBloc.customerRepository.customer!.email,
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
validator: (val) {
|
||||
String? validator = customerBloc.emailValidation(val!);
|
||||
@ -147,8 +146,8 @@ class CustomerModifyPage extends StatelessWidget with Trans {
|
||||
borderSide: BorderSide(color: Colors.green[50]!, width: 0.4),
|
||||
),
|
||||
),
|
||||
initialValue: customerBloc.customerRepository.customer.password,
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
initialValue: customerBloc.customerRepository.customer!.password,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
validator: (val) {
|
||||
String? validator = customerBloc.passwordValidation(val!);
|
||||
return validator == null ? null : t(validator);
|
||||
@ -173,8 +172,8 @@ class CustomerModifyPage extends StatelessWidget with Trans {
|
||||
borderSide: BorderSide(color: Colors.green[50]!, width: 0.4),
|
||||
),
|
||||
),
|
||||
initialValue: customerBloc.customerRepository.customer.name,
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
initialValue: customerBloc.customerRepository.customer!.name,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
validator: (val) {
|
||||
return customerBloc.nameValidation(val);
|
||||
},
|
||||
@ -198,8 +197,8 @@ class CustomerModifyPage extends StatelessWidget with Trans {
|
||||
borderSide: BorderSide(color: Colors.green[50]!, width: 0.4),
|
||||
),
|
||||
),
|
||||
initialValue: customerBloc.customerRepository.customer.firstname,
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
initialValue: customerBloc.customerRepository.customer!.firstname,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
validator: (val) {
|
||||
return customerBloc.nameValidation(val);
|
||||
},
|
||||
@ -210,25 +209,42 @@ class CustomerModifyPage extends StatelessWidget with Trans {
|
||||
Divider(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
Row(
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 15),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white24,
|
||||
border: Border.all(color: Colors.black, width: 0.4),
|
||||
borderRadius: BorderRadius.all(Radius.circular(12))),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Text(t("Birth Year"), style: TextStyle(fontWeight: FontWeight.normal, fontSize: 18)),
|
||||
),
|
||||
flex: 2,
|
||||
child: Text(t("Birth Year"),
|
||||
style: GoogleFonts.inter(
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 18,
|
||||
))),
|
||||
NumberPickerWidget(
|
||||
minValue: 1930,
|
||||
maxValue: 2100,
|
||||
maxValue: DateTime.now().year,
|
||||
initalValue: customerBloc.year!.toInt(),
|
||||
unit: " ",
|
||||
color: Colors.indigo,
|
||||
onChange: (value) => {customerBloc.add(CustomerBirthYearChange(year: value.toInt()))}),
|
||||
SizedBox(width: 80),
|
||||
SizedBox(width: 30),
|
||||
],
|
||||
)),
|
||||
Divider(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
Divider(),
|
||||
Row(
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 15),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white24,
|
||||
border: Border.all(color: Colors.black, width: 0.4),
|
||||
borderRadius: BorderRadius.all(Radius.circular(12))),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Expanded(
|
||||
@ -242,11 +258,19 @@ class CustomerModifyPage extends StatelessWidget with Trans {
|
||||
unit: " ",
|
||||
color: Colors.indigo,
|
||||
onChange: (value) => {customerBloc.add(CustomerWeightChange(weight: value.toInt()))}),
|
||||
SizedBox(width: 80),
|
||||
SizedBox(width: 30),
|
||||
],
|
||||
)),
|
||||
Divider(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
Divider(),
|
||||
Row(
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 15),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white24,
|
||||
border: Border.all(color: Colors.black, width: 0.4),
|
||||
borderRadius: BorderRadius.all(Radius.circular(12))),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Expanded(
|
||||
@ -260,15 +284,17 @@ class CustomerModifyPage extends StatelessWidget with Trans {
|
||||
unit: " ",
|
||||
color: Colors.indigo[300]!,
|
||||
onChange: (value) => {customerBloc.add(CustomerHeightChange(height: value.toInt()))}),
|
||||
SizedBox(width: 80),
|
||||
SizedBox(width: 30),
|
||||
],
|
||||
)),
|
||||
Divider(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
Divider(),
|
||||
ToggleSwitch(
|
||||
minWidth: 80.0,
|
||||
minHeight: 50.0,
|
||||
fontSize: 14.0,
|
||||
initialLabelIndex: customerBloc.customerRepository.customer.sex == "m" ? 0 : 1,
|
||||
initialLabelIndex: customerBloc.customerRepository.customer!.sex == "m" ? 0 : 1,
|
||||
activeBgColor: Colors.indigo,
|
||||
activeFgColor: Colors.white,
|
||||
inactiveBgColor: Colors.white30,
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'dart:collection';
|
||||
import 'dart:ui';
|
||||
import 'package:aitrainer_app/util/enums.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:aitrainer_app/bloc/result/result_bloc.dart';
|
||||
import 'package:aitrainer_app/util/app_language.dart';
|
||||
@ -281,6 +282,7 @@ class EvaluationPage extends StatelessWidget with Trans {
|
||||
}
|
||||
|
||||
Widget getSuggestionWidget(ResultBloc resultBloc, String title, String picture, String repeats, double percent, String restTime) {
|
||||
double _opacity = 1;
|
||||
String unitQuantityUnit = resultBloc.exerciseRepository.exerciseType!.unitQuantityUnit == null
|
||||
? ""
|
||||
: resultBloc.exerciseRepository.exerciseType!.unitQuantityUnit!;
|
||||
@ -325,7 +327,7 @@ class EvaluationPage extends StatelessWidget with Trans {
|
||||
softWrap: true,
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontSize: 18,
|
||||
color: Colors.orange,
|
||||
color: Colors.orange.withOpacity(_opacity),
|
||||
shadows: <Shadow>[
|
||||
Shadow(
|
||||
offset: Offset(5.0, 5.0),
|
||||
@ -398,7 +400,7 @@ class EvaluationPage extends StatelessWidget with Trans {
|
||||
|
||||
Widget getSummary(ResultBloc bloc) {
|
||||
int index = 0;
|
||||
List<Text> resultList = [];
|
||||
List<Widget> resultList = [];
|
||||
|
||||
bloc.exerciseRepository.actualExerciseList!.forEach((actual) {
|
||||
//final String unit = t(bloc.exerciseRepository.exerciseType.unit);
|
||||
@ -421,36 +423,248 @@ class EvaluationPage extends StatelessWidget with Trans {
|
||||
exerciseElement = t("3rd Control") + ": ";
|
||||
}
|
||||
index++;
|
||||
resultList.add(
|
||||
Text(exerciseElement + exerciseRepeats + exerciseUnitQuantity + " " + t(unit),
|
||||
textAlign: TextAlign.center,
|
||||
maxLines: 2,
|
||||
softWrap: true,
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 18,
|
||||
fontSize: 20,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: exerciseElement),
|
||||
TextSpan(
|
||||
text: exerciseRepeats + exerciseUnitQuantity + " " + t(unit),
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.yellow[300]!,
|
||||
),
|
||||
),
|
||||
]),
|
||||
));
|
||||
});
|
||||
|
||||
return Column(children: resultList);
|
||||
}
|
||||
|
||||
Widget getEvaluationWidget(ResultBloc bloc) {
|
||||
List<Widget> resultList = [];
|
||||
print("Act ${bloc.exerciseRepository.actualExerciseList}");
|
||||
if (bloc.exerciseRepository.actualExerciseList == null || bloc.exerciseRepository.actualExerciseList!.isEmpty) {
|
||||
return Offstage();
|
||||
}
|
||||
final int exerciseTypeId = bloc.exerciseRepository.actualExerciseList![0].exerciseTypeId!;
|
||||
final double quantity = bloc.exerciseRepository.actualExerciseList![0].quantity!;
|
||||
String eval = bloc.evaluationRepository.getEvaluationTextByExerciseType(exerciseTypeId, quantity);
|
||||
Color color = bloc.evaluationRepository.getEvaluationColor(eval);
|
||||
double compareBest = bloc.exerciseRepository.getBestExercisePercent(bloc.exerciseRepository.actualExerciseList![0]);
|
||||
double compareLast = bloc.exerciseRepository.getLastExercisePercent(bloc.exerciseRepository.actualExerciseList![0]);
|
||||
bool has1RM = bloc.exerciseRepository.actualExerciseList![0].unitQuantity != null;
|
||||
double? bestCompared1RM;
|
||||
double? lastCompared1RM;
|
||||
if (has1RM) {
|
||||
lastCompared1RM = bloc.exerciseRepository.getLast1RMPercent(bloc.exerciseRepository.actualExerciseList![0]);
|
||||
bestCompared1RM = bloc.exerciseRepository.getBest1RMPercent(bloc.exerciseRepository.actualExerciseList![0]);
|
||||
}
|
||||
|
||||
if (!EvaluationText.fair.equalsStringTo(eval)) {
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 30,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: t("Your result is: ")),
|
||||
TextSpan(
|
||||
text: eval,
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
]),
|
||||
));
|
||||
|
||||
resultList.add(Divider(color: Colors.transparent));
|
||||
}
|
||||
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 20,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: t("Compared with...")),
|
||||
]),
|
||||
));
|
||||
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 20,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: t("your best")),
|
||||
TextSpan(text: " "),
|
||||
TextSpan(text: has1RM ? t("volumen") : t("exercise")),
|
||||
TextSpan(text: ": "),
|
||||
TextSpan(
|
||||
text: compareBest.toStringAsFixed(1) + "%",
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: compareBest >= 0 ? Colors.green : Colors.red[600],
|
||||
),
|
||||
),
|
||||
]),
|
||||
));
|
||||
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 20,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: t("your last")),
|
||||
TextSpan(text: " "),
|
||||
TextSpan(text: has1RM ? t("volumen") : t("exercise")),
|
||||
TextSpan(text: ": "),
|
||||
TextSpan(
|
||||
text: compareLast.toStringAsFixed(1) + "%",
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: compareLast >= 0 ? Colors.green : Colors.red[600],
|
||||
),
|
||||
),
|
||||
]),
|
||||
));
|
||||
resultList.add(Divider(color: Colors.transparent));
|
||||
|
||||
if (has1RM) {
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 20,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: t("best")),
|
||||
TextSpan(text: " "),
|
||||
TextSpan(text: t("1RM")),
|
||||
TextSpan(text: ": "),
|
||||
TextSpan(
|
||||
text: bestCompared1RM!.toStringAsFixed(1) + "%",
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: bestCompared1RM >= 0 ? Colors.green : Colors.red[600],
|
||||
),
|
||||
),
|
||||
]),
|
||||
));
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 20,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(text: t("last")),
|
||||
TextSpan(text: " "),
|
||||
TextSpan(text: t("1RM")),
|
||||
TextSpan(text: ": "),
|
||||
TextSpan(
|
||||
text: lastCompared1RM!.toStringAsFixed(1) + "%",
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: lastCompared1RM >= 0 ? Colors.green : Colors.red[600],
|
||||
),
|
||||
),
|
||||
]),
|
||||
));
|
||||
}
|
||||
|
||||
if (!Cache().hasPurchased) {
|
||||
resultList.add(Divider());
|
||||
resultList.add(Divider());
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: t('How can serve you this result?'),
|
||||
style: GoogleFonts.inter(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
]),
|
||||
));
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: t('Get the Fastlane to your'),
|
||||
style: GoogleFonts.inter(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
]),
|
||||
));
|
||||
resultList.add(RichText(
|
||||
text: TextSpan(
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.white,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: t('Development'),
|
||||
style: GoogleFonts.inter(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.yellow[300],
|
||||
),
|
||||
)
|
||||
])));
|
||||
|
||||
resultList.add(TextButton(
|
||||
onPressed: () => {Navigator.of(context).pushNamed("salesPage")},
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Image.asset('asset/icon/gomb_orange_a.png', width: 140, height: 60),
|
||||
Text(
|
||||
t("Go"),
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
shadows: <Shadow>[
|
||||
Shadow(
|
||||
offset: Offset(5.0, 5.0),
|
||||
blurRadius: 12.0,
|
||||
color: Colors.black54,
|
||||
),
|
||||
Shadow(
|
||||
offset: Offset(-3.0, 3.0),
|
||||
blurRadius: 12.0,
|
||||
color: Colors.black54,
|
||||
),
|
||||
],
|
||||
)),
|
||||
);
|
||||
});
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
return Column(children: resultList);
|
||||
}
|
||||
|
||||
Widget getResultSummary(ResultBloc resultBloc) {
|
||||
return SliverList(
|
||||
delegate: SliverChildListDelegate(
|
||||
[Divider(color: Colors.transparent), getSummary(resultBloc)],
|
||||
[Divider(color: Colors.transparent), getSummary(resultBloc), Divider(color: Colors.transparent), getEvaluationWidget(resultBloc)],
|
||||
),
|
||||
);
|
||||
}
|
@ -45,7 +45,8 @@ class _ExerciseExecuteAddPage extends State<ExerciseExecutePlanAddPage> with Tra
|
||||
exercisePlanRepository: planBloc.exercisePlanRepository,
|
||||
customerId: customerId,
|
||||
workoutTree: workoutTree,
|
||||
planBloc: planBloc),
|
||||
planBloc: planBloc)
|
||||
..add(ExerciseExecutePlanAddLoad()),
|
||||
child: BlocConsumer<ExerciseExecutePlanAddBloc, ExerciseExecutePlanAddState>(listener: (context, state) {
|
||||
if (state is ExerciseExecutePlanAddError) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
@ -54,7 +55,7 @@ class _ExerciseExecuteAddPage extends State<ExerciseExecutePlanAddPage> with Tra
|
||||
}, builder: (context, state) {
|
||||
// ignore: close_sinks
|
||||
final exerciseBloc = BlocProvider.of<ExerciseExecutePlanAddBloc>(context);
|
||||
if (state is ExerciseExecutePlanAddReady) {
|
||||
if (state is ExerciseExecutePlanAddReady && _controller.hasClients) {
|
||||
_controller.animateTo(exerciseBloc.scrollOffset, duration: Duration(milliseconds: 300), curve: Curves.easeIn);
|
||||
}
|
||||
return ModalProgressHUD(
|
||||
@ -67,7 +68,10 @@ class _ExerciseExecuteAddPage extends State<ExerciseExecutePlanAddPage> with Tra
|
||||
}));
|
||||
}
|
||||
|
||||
Form getControlForm(ExerciseExecutePlanAddBloc exerciseBloc) {
|
||||
Widget getControlForm(ExerciseExecutePlanAddBloc exerciseBloc) {
|
||||
if (exerciseBloc.exerciseRepository.exerciseType == null || exerciseBloc.quantity == null) {
|
||||
return Offstage();
|
||||
}
|
||||
String exerciseName = AppLanguage().appLocal == Locale("en")
|
||||
? exerciseBloc.exerciseRepository.exerciseType!.name
|
||||
: exerciseBloc.exerciseRepository.exerciseType!.nameTranslation;
|
||||
@ -186,7 +190,7 @@ class _ExerciseExecuteAddPage extends State<ExerciseExecutePlanAddPage> with Tra
|
||||
minValue: 0,
|
||||
maxValue: 1000,
|
||||
fontSize: 16,
|
||||
initalValue: exerciseBloc.unitQuantity.toInt(),
|
||||
initalValue: exerciseBloc.unitQuantity!.toInt(),
|
||||
unit: t(exerciseBloc.exerciseRepository.exerciseType!.unitQuantityUnit!),
|
||||
color: Colors.yellow[50]!,
|
||||
onChange: (value) => {exerciseBloc.add(ExerciseExecutePlanAddChangeUnitQuantity(quantity: value.toDouble()))}),
|
||||
@ -194,7 +198,7 @@ class _ExerciseExecuteAddPage extends State<ExerciseExecutePlanAddPage> with Tra
|
||||
minValue: 0,
|
||||
maxValue: 200,
|
||||
fontSize: 16,
|
||||
initalValue: exerciseBloc.quantity.toInt(),
|
||||
initalValue: exerciseBloc.quantity!.toInt(),
|
||||
unit: t(exerciseBloc.exerciseRepository.exerciseType!.unit), //t("repeat"),
|
||||
color: Colors.yellow[50]!,
|
||||
onChange: (value) => {exerciseBloc.add(ExerciseExecutePlanAddChangeQuantity(quantity: value.toDouble()))}),
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'dart:collection';
|
||||
import 'package:aitrainer_app/bloc/exercise_log/exercise_log_bloc.dart';
|
||||
import 'package:aitrainer_app/library/custom_icon_icons.dart';
|
||||
import 'package:aitrainer_app/widgets/app_bar.dart';
|
||||
import 'package:aitrainer_app/widgets/bottom_nav.dart';
|
||||
import 'package:aitrainer_app/widgets/dialog_premium.dart';
|
||||
@ -196,13 +195,12 @@ class _ExerciseLogPage extends State<ExerciseLogPage> with Trans, Common {
|
||||
exercise.summary == null ? "" : exercise.summary!,
|
||||
style: TextStyle(fontSize: 12, color: Colors.blue[800]),
|
||||
)),
|
||||
IconButton(
|
||||
iconSize: 36,
|
||||
icon: Icon(CustomIcon.heart_1, color: Colors.orange[800]),
|
||||
onPressed: () {
|
||||
evaluation(exerciseRepository, exercise);
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () => evaluation(exerciseRepository, exercise),
|
||||
child: Image.asset(
|
||||
"asset/image/kupa.png",
|
||||
width: 35,
|
||||
)),
|
||||
IconButton(
|
||||
icon: Icon(Icons.delete, color: Colors.black12),
|
||||
onPressed: () {
|
||||
|
@ -107,7 +107,9 @@ class _ExerciseNewPageState extends State<ExerciseNewPage> with Trans, Logging {
|
||||
child: ExerciseSave(
|
||||
exerciseName: exerciseBloc.exerciseRepository.exerciseType!.nameTranslation,
|
||||
exerciseDescription: exerciseBloc.exerciseRepository.exerciseType!.descriptionTranslation,
|
||||
exerciseTask: t("Please take a relative bigger weight and repeat 12-20 times"),
|
||||
exerciseTask: exerciseBloc.exerciseRepository.exerciseType!.unitQuantityUnit != null
|
||||
? t("Please take a relative bigger weight and repeat 12-20 times and do your best! MAXIMIZE it!")
|
||||
: t("Please repeat as much times as you can! MAXIMIZE it!"),
|
||||
unit: exerciseBloc.exerciseRepository.exerciseType!.unit,
|
||||
unitQuantityUnit: exerciseBloc.exerciseRepository.exerciseType!.unitQuantityUnit,
|
||||
hasUnitQuantity: exerciseBloc.exerciseRepository.exerciseType!.unitQuantityUnit != null,
|
||||
@ -121,10 +123,13 @@ class _ExerciseNewPageState extends State<ExerciseNewPage> with Trans, Logging {
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () => save(exerciseBloc, menuBloc),
|
||||
backgroundColor: Colors.orange[800],
|
||||
icon: Icon(CustomIcon.save),
|
||||
icon: Icon(
|
||||
CustomIcon.save,
|
||||
size: 20,
|
||||
),
|
||||
label: Text(
|
||||
t("Save"),
|
||||
style: GoogleFonts.inter(fontWeight: FontWeight.bold, fontSize: 16),
|
||||
style: GoogleFonts.inter(fontWeight: FontWeight.bold, fontSize: 12),
|
||||
),
|
||||
),
|
||||
bottomNavigationBar: BottomBarMultipleExercises(
|
||||
@ -164,11 +169,15 @@ class _ExerciseNewPageState extends State<ExerciseNewPage> with Trans, Logging {
|
||||
// ignore: close_sinks
|
||||
final TestSetExecuteBloc executeBloc = BlocProvider.of<TestSetExecuteBloc>(context);
|
||||
|
||||
final question = bloc.exerciseRepository.exercise!.quantity! == 12.0
|
||||
? "Did you try the MAXIMUM what you can do? Are you sure we save the exercise with ONLY 12 repeats?"
|
||||
: "Do you save this exercise with these parameters?";
|
||||
|
||||
showCupertinoDialog(
|
||||
useRootNavigator: true,
|
||||
context: context,
|
||||
builder: (_) => CupertinoAlertDialog(
|
||||
title: Text(t("Do you save this exercise with these parameters?")),
|
||||
title: Text(t(question)),
|
||||
content: Column(children: [
|
||||
Divider(),
|
||||
Text(
|
||||
|
@ -120,7 +120,7 @@ class LoginPage extends StatelessWidget with Trans {
|
||||
initialValue: loginBloc.userRepository.user.email,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
validator: (val) {
|
||||
String? validationText = loginBloc.emailValidation(val!);
|
||||
String? validationText = loginBloc.emailValidation(val);
|
||||
return validationText == null ? null : t(validationText);
|
||||
},
|
||||
onChanged: (value) => loginBloc.add(LoginEmailChange(email: value)),
|
||||
@ -150,9 +150,9 @@ class LoginPage extends StatelessWidget with Trans {
|
||||
),
|
||||
),
|
||||
initialValue: loginBloc.userRepository.user.password,
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
validator: (val) {
|
||||
String? validationText = loginBloc.passwordValidation(val!);
|
||||
String? validationText = loginBloc.passwordValidation(val);
|
||||
return validationText == null ? null : t(validationText);
|
||||
},
|
||||
onChanged: (value) => loginBloc.add(LoginPasswordChange(password: value)),
|
||||
|
@ -163,7 +163,7 @@ class RegistrationPage extends StatelessWidget with Trans {
|
||||
),
|
||||
),
|
||||
initialValue: loginBloc.userRepository.user.password,
|
||||
autovalidateMode: AutovalidateMode.always,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
validator: (val) {
|
||||
final String? validator = loginBloc.passwordValidation(val!);
|
||||
return validator == null ? null : t(validator);
|
||||
|
@ -31,7 +31,7 @@ class ResetPasswordPage extends StatelessWidget with Trans {
|
||||
if (state is PasswordResetError) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(backgroundColor: Colors.orange, content: Text(t(state.message), style: TextStyle(color: Colors.white))));
|
||||
} else if (state is PasswordResetReady) {
|
||||
} else if (state is PasswordResetFinished) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
|
@ -150,7 +150,7 @@ class SalesPage extends StatelessWidget with Trans, Logging {
|
||||
bloc.product2Display.forEach((element) {
|
||||
final String title = element.sort == 3 ? t("Montly") : t("Annual");
|
||||
final String desc4 = element.sort == 1 ? "" : t("Predictions with Artificial Intelligence");
|
||||
late String badge;
|
||||
String? badge;
|
||||
if (element.sort == 2) {
|
||||
badge = t("14% discount");
|
||||
} else if (element.sort == 1) {
|
||||
|
@ -3,12 +3,16 @@ import 'dart:collection';
|
||||
import 'package:aitrainer_app/bloc/menu/menu_bloc.dart';
|
||||
import 'package:aitrainer_app/bloc/test_set_execute/test_set_execute_bloc.dart';
|
||||
import 'package:aitrainer_app/library/custom_icon_icons.dart';
|
||||
import 'package:aitrainer_app/library/fade_in.dart';
|
||||
import 'package:aitrainer_app/model/cache.dart';
|
||||
import 'package:aitrainer_app/model/exercise_plan_detail.dart';
|
||||
import 'package:aitrainer_app/repository/exercise_repository.dart';
|
||||
import 'package:aitrainer_app/util/trans.dart';
|
||||
import 'package:aitrainer_app/widgets/app_bar.dart';
|
||||
import 'package:aitrainer_app/widgets/dialog_common.dart';
|
||||
import 'package:aitrainer_app/widgets/menu_image.dart';
|
||||
import 'package:aitrainer_app/widgets/victory_widget.dart';
|
||||
import 'package:ezanimation/ezanimation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
@ -64,7 +68,9 @@ class TestSetExecute extends StatelessWidget with Trans {
|
||||
}),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () => executeExercise(executeBloc, executeBloc.getNext()!, context),
|
||||
onPressed: () => executeBloc.getNext() != null
|
||||
? executeExercise(executeBloc, executeBloc.getNext()!, context)
|
||||
: Navigator.of(context).pushNamed('home'),
|
||||
backgroundColor: Colors.orange[800],
|
||||
icon: Icon(CustomIcon.weight_hanging),
|
||||
label: Text(
|
||||
@ -86,11 +92,6 @@ class TestSetExecute extends StatelessWidget with Trans {
|
||||
tiles.add(getStartTile(bloc));
|
||||
tiles.addAll(getExerciseTiles(bloc, context));
|
||||
tiles.add(getEndTile());
|
||||
/* if (bloc.isDone100Percent()) {
|
||||
tiles.add(Victory(
|
||||
victory: true,
|
||||
));
|
||||
} */
|
||||
return tiles;
|
||||
}
|
||||
|
||||
@ -254,7 +255,9 @@ class TestSetExecute extends StatelessWidget with Trans {
|
||||
//if (element != null && element.exerciseTypeId != null) {
|
||||
tiles.add(GestureDetector(
|
||||
onDoubleTap: () => print("Execute ${element.exerciseType!.nameTranslation}"),
|
||||
onTap: () => executeExercise(bloc, element, context),
|
||||
onTap: () => element.state.equalsTo(ExercisePlanDetailState.finished)
|
||||
? evaluation(bloc, element)
|
||||
: executeExercise(bloc, element, context),
|
||||
child: ExerciseTile(
|
||||
bloc: bloc,
|
||||
exercisePlanDetail: element,
|
||||
@ -266,12 +269,27 @@ class TestSetExecute extends StatelessWidget with Trans {
|
||||
return tiles;
|
||||
}
|
||||
|
||||
void evaluation(TestSetExecuteBloc bloc, ExercisePlanDetail planDetail) {
|
||||
if (planDetail.exercises != null && planDetail.exercises!.isNotEmpty) {
|
||||
ExerciseRepository exerciseRepository = ExerciseRepository();
|
||||
exerciseRepository.start = planDetail.exercises![0].dateAdd;
|
||||
exerciseRepository.exerciseList = Cache().getExercises();
|
||||
exerciseRepository.actualExerciseList = planDetail.exercises!;
|
||||
print("actualEx ${exerciseRepository.actualExerciseList}");
|
||||
LinkedHashMap args = LinkedHashMap();
|
||||
args['exerciseRepository'] = exerciseRepository;
|
||||
args['exercise'] = planDetail.exercises![0];
|
||||
args['past'] = true;
|
||||
Navigator.of(context).pushNamed('evaluationPage', arguments: args);
|
||||
}
|
||||
}
|
||||
|
||||
void executeExercise(TestSetExecuteBloc bloc, ExercisePlanDetail exercisePlanDetail, BuildContext context) {
|
||||
ExercisePlanDetail? next = bloc.getNext();
|
||||
|
||||
if (next != null) {
|
||||
final HashMap args = HashMap();
|
||||
args['exerciseType'] = exercisePlanDetail.exerciseType;
|
||||
args['exerciseType'] = next.exerciseType;
|
||||
args['exercisePlanDetailId'] = exercisePlanDetail.exercisePlanDetailId;
|
||||
args['testSetExecuteBloc'] = bloc;
|
||||
String title = "";
|
||||
@ -314,17 +332,43 @@ class TestSetExecute extends StatelessWidget with Trans {
|
||||
}
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class ExerciseTile extends StatelessWidget with Trans {
|
||||
class ExerciseTile extends StatefulWidget {
|
||||
final TestSetExecuteBloc bloc;
|
||||
final ExercisePlanDetail exercisePlanDetail;
|
||||
|
||||
ExerciseTile({required this.bloc, required this.exercisePlanDetail});
|
||||
|
||||
@override
|
||||
_ExerciseTileState createState() => _ExerciseTileState();
|
||||
}
|
||||
|
||||
class _ExerciseTileState extends State<ExerciseTile> with Trans {
|
||||
final EzAnimation animation = EzAnimation(1.0, 30.0, Duration(seconds: 3), reverseCurve: Curves.easeIn);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
animation.start();
|
||||
animation.addStatusListener((status) {
|
||||
if (status == AnimationStatus.completed) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
bool didUpdateWidget(ExerciseTile oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
animation.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
Widget getIndicator(ExercisePlanDetailState state) {
|
||||
ExercisePlanDetail? next = bloc.getNext();
|
||||
ExercisePlanDetail? next = widget.bloc.getNext();
|
||||
bool actual = false;
|
||||
if (next != null) {
|
||||
if (next.exerciseTypeId == exercisePlanDetail.exerciseTypeId) {
|
||||
if (next.exerciseTypeId == widget.exercisePlanDetail.exerciseTypeId) {
|
||||
actual = true;
|
||||
}
|
||||
}
|
||||
@ -357,10 +401,11 @@ class ExerciseTile extends StatelessWidget with Trans {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ExercisePlanDetailState state = exercisePlanDetail.state;
|
||||
final ExercisePlanDetailState state = widget.exercisePlanDetail.state;
|
||||
final bool done = state.equalsTo(ExercisePlanDetailState.finished);
|
||||
final String countSerie = exercisePlanDetail.exercises == null ? "1" : (exercisePlanDetail.exercises!.length).toString();
|
||||
final String serie = exercisePlanDetail.exerciseType!.unitQuantityUnit == null ? "/1" : "/4";
|
||||
final String countSerie = widget.exercisePlanDetail.exercises == null ? "1" : (widget.exercisePlanDetail.exercises!.length).toString();
|
||||
final String serie = widget.exercisePlanDetail.exerciseType!.unitQuantityUnit == null ? "/1" : "/4";
|
||||
|
||||
setContext(context);
|
||||
return Container(
|
||||
color: Colors.transparent,
|
||||
@ -387,8 +432,8 @@ class ExerciseTile extends StatelessWidget with Trans {
|
||||
width: 120,
|
||||
height: 80,
|
||||
child: MenuImage(
|
||||
imageName: bloc.getActualImageName(exercisePlanDetail.exerciseType!.exerciseTypeId),
|
||||
workoutTreeId: bloc.getActualWorkoutTreeId(exercisePlanDetail.exerciseType!.exerciseTypeId)!,
|
||||
imageName: widget.bloc.getActualImageName(widget.exercisePlanDetail.exerciseType!.exerciseTypeId),
|
||||
workoutTreeId: widget.bloc.getActualWorkoutTreeId(widget.exercisePlanDetail.exerciseType!.exerciseTypeId)!,
|
||||
)),
|
||||
SizedBox(
|
||||
width: 10,
|
||||
@ -403,7 +448,7 @@ class ExerciseTile extends StatelessWidget with Trans {
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: exercisePlanDetail.exerciseType!.nameTranslation,
|
||||
text: widget.exercisePlanDetail.exerciseType!.nameTranslation,
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
@ -421,20 +466,20 @@ class ExerciseTile extends StatelessWidget with Trans {
|
||||
),
|
||||
],
|
||||
)),
|
||||
exercisePlanDetail.exerciseType!.unitQuantityUnit != null
|
||||
widget.exercisePlanDetail.exerciseType!.unitQuantityUnit != null
|
||||
? TextSpan(
|
||||
text: "\n",
|
||||
)
|
||||
: TextSpan(),
|
||||
exercisePlanDetail.exerciseType!.unitQuantityUnit != null
|
||||
widget.exercisePlanDetail.exerciseType!.unitQuantityUnit != null
|
||||
? TextSpan(
|
||||
text: t(exercisePlanDetail.exerciseType!.unitQuantityUnit!) + ": ",
|
||||
text: t(widget.exercisePlanDetail.exerciseType!.unitQuantityUnit!) + ": ",
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12, color: done ? Colors.grey[100] : Colors.yellow[400], fontWeight: FontWeight.bold))
|
||||
: TextSpan(),
|
||||
exercisePlanDetail.exerciseType!.unitQuantityUnit != null
|
||||
widget.exercisePlanDetail.exerciseType!.unitQuantityUnit != null
|
||||
? TextSpan(
|
||||
text: t(bloc.getExerciseWeight(exercisePlanDetail)),
|
||||
text: t(widget.bloc.getExerciseWeight(widget.exercisePlanDetail)),
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12,
|
||||
))
|
||||
@ -443,11 +488,11 @@ class ExerciseTile extends StatelessWidget with Trans {
|
||||
text: "\n",
|
||||
),
|
||||
TextSpan(
|
||||
text: t(exercisePlanDetail.exerciseType!.unit) + ": ",
|
||||
text: t(widget.exercisePlanDetail.exerciseType!.unit) + ": ",
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12, color: done ? Colors.grey[100] : Colors.yellow[400], fontWeight: FontWeight.bold)),
|
||||
TextSpan(
|
||||
text: bloc.repeatTimesText(exercisePlanDetail),
|
||||
text: widget.bloc.repeatTimesText(widget.exercisePlanDetail),
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 12,
|
||||
)),
|
||||
@ -465,6 +510,19 @@ class ExerciseTile extends StatelessWidget with Trans {
|
||||
)),
|
||||
]),
|
||||
)),
|
||||
done
|
||||
? AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (context, snapshot) {
|
||||
return Column(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
Image.asset(
|
||||
"asset/image/kupa.png",
|
||||
width: animation.value,
|
||||
),
|
||||
Text("Result", style: GoogleFonts.inter(fontSize: 10, color: Colors.white)),
|
||||
]);
|
||||
})
|
||||
: Offstage()
|
||||
]),
|
||||
),
|
||||
),
|
||||
|
@ -92,9 +92,11 @@ class TestSetNew extends StatelessWidget with Trans {
|
||||
return ExerciseSave(
|
||||
exerciseName: bloc.exerciseType.nameTranslation,
|
||||
exerciseDescription: bloc.exerciseType.descriptionTranslation,
|
||||
exerciseTask: t("Please take a relative bigger weight and repeat 12-20 times"),
|
||||
exerciseTask: bloc.exerciseType.unitQuantityUnit != null
|
||||
? t("Please take a relative bigger weight and repeat 12-20 times and do your best! MAXIMIZE it!")
|
||||
: t("Please repeat as much times as you can! MAXIMIZE it!"),
|
||||
unit: bloc.exerciseType.unit,
|
||||
unitQuantityUnit: bloc.exerciseType.unitQuantityUnit!,
|
||||
unitQuantityUnit: bloc.exerciseType.unitQuantityUnit,
|
||||
hasUnitQuantity: bloc.exerciseType.unitQuantityUnit != null,
|
||||
onQuantityChanged: (value) {
|
||||
bloc.add(TestSetNewChangeQuantity(quantity: double.parse(value)));
|
||||
|
@ -241,7 +241,7 @@ class _BMIState extends State<BMI> with Trans {
|
||||
}
|
||||
|
||||
Widget getHeightInput() {
|
||||
if (widget.exerciseBloc.customerRepository.customer.birthYear! < 2003) {
|
||||
if (widget.exerciseBloc.customerRepository.customer!.birthYear! < 2003) {
|
||||
return Flexible(
|
||||
child: TextFormField(
|
||||
focusNode: _nodeText2,
|
||||
|
@ -3,7 +3,7 @@ import 'package:aitrainer_app/bloc/exercise_new/exercise_new_bloc.dart';
|
||||
import 'package:aitrainer_app/util/app_localization.dart';
|
||||
import 'package:aitrainer_app/model/fitness_state.dart';
|
||||
import 'package:aitrainer_app/util/trans.dart';
|
||||
import 'package:dropdown_search/dropdown_search.dart';
|
||||
import 'package:aitrainer_app/library/dropdown_search.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@ -196,7 +196,7 @@ class _BMRState extends State<BMR> with Trans {
|
||||
}
|
||||
|
||||
Widget getHeightInput() {
|
||||
if (widget.exerciseBloc.customerRepository.customer.birthYear! < 2003) {
|
||||
if (widget.exerciseBloc.customerRepository.customer!.birthYear! < 2003) {
|
||||
return Flexible(
|
||||
child: TextFormField(
|
||||
focusNode: _nodeText2,
|
||||
|
@ -71,7 +71,7 @@ class _BottomBarMultipleExercisesState extends State<BottomBarMultipleExercises>
|
||||
],
|
||||
),
|
||||
),
|
||||
height: 90,
|
||||
height: 70,
|
||||
child: BlocConsumer<TestSetExecuteBloc, TestSetExecuteState>(listener: (context, state) {
|
||||
if (state is TestSetExecuteError) {
|
||||
ScaffoldMessenger.of(context)
|
||||
|
@ -1,10 +1,12 @@
|
||||
import 'package:aitrainer_app/library/gradient_bottom_navigation_bar.dart';
|
||||
import 'dart:ui';
|
||||
|
||||
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/enums.dart';
|
||||
import 'package:aitrainer_app/util/track.dart';
|
||||
import 'package:aitrainer_app/util/trans.dart';
|
||||
import 'package:convex_bottom_bar/convex_bottom_bar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
@ -34,55 +36,41 @@ class _NawDrawerWidget extends State<BottomNavigator> with Trans, Logging {
|
||||
Widget build(BuildContext context) {
|
||||
final Color bgrColor = Color(0xffb4f500);
|
||||
final Color bgrColorEnd = Colors.blue;
|
||||
final Color active = Colors.black;
|
||||
final Color inactive = Colors.black26;
|
||||
final Color active = Colors.white;
|
||||
final Color inactive = Colors.black38;
|
||||
setContext(context);
|
||||
|
||||
return GradientBottomNavigationBar(
|
||||
currentIndex: widget.bottomNavIndex, // this will be set when a new tab is tapped
|
||||
backgroundColorStart: bgrColorEnd,
|
||||
backgroundColorEnd: bgrColor,
|
||||
fixedColor: active,
|
||||
return StyleProvider(
|
||||
style: Style(),
|
||||
child: ConvexAppBar(
|
||||
initialActiveIndex: widget.bottomNavIndex,
|
||||
curve: Curves.easeIn,
|
||||
style: TabStyle.react,
|
||||
color: inactive,
|
||||
height: 55,
|
||||
gradient: LinearGradient(colors: [bgrColorEnd, bgrColor], stops: [0.1, .75]),
|
||||
items: [
|
||||
BottomNavigationBarItem(
|
||||
backgroundColor: bgrColor,
|
||||
TabItem(
|
||||
isIconBlend: false,
|
||||
title: t("Home"),
|
||||
icon: Common.badgedIcon(inactive, Icons.home, "home"),
|
||||
activeIcon: Common.badgedIcon(active, Icons.home, "home"),
|
||||
title: Text(t("Home"), style: TextStyle(fontSize: 12))),
|
||||
BottomNavigationBarItem(
|
||||
backgroundColor: bgrColor,
|
||||
),
|
||||
TabItem(
|
||||
title: t("Growth"),
|
||||
icon: Common.badgedIcon(inactive, Icons.trending_up, "development"),
|
||||
activeIcon: Common.badgedIcon(active, Icons.trending_up, "development"),
|
||||
title: Text(
|
||||
t("My Development"),
|
||||
style: TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
backgroundColor: bgrColor,
|
||||
TabItem(
|
||||
title: t("Training"),
|
||||
icon: Icon(Icons.featured_play_list, color: inactive),
|
||||
activeIcon: Icon(
|
||||
Icons.featured_play_list,
|
||||
color: active,
|
||||
),
|
||||
title: Text(
|
||||
t("My Training Plan"),
|
||||
style: TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
backgroundColor: bgrColor,
|
||||
activeIcon: Icon(Icons.featured_play_list, color: active)),
|
||||
TabItem(
|
||||
title: t("Account"),
|
||||
icon: Common.badgedIcon(inactive, Icons.person, "account"),
|
||||
activeIcon: Common.badgedIcon(active, Icons.person, "account"),
|
||||
title: Text(
|
||||
t("Account"),
|
||||
style: TextStyle(fontSize: 12),
|
||||
)),
|
||||
BottomNavigationBarItem(
|
||||
backgroundColor: bgrColor,
|
||||
icon: Icon(Icons.settings, color: inactive),
|
||||
activeIcon: Icon(Icons.settings, color: active),
|
||||
title: Text(t("Settings"), style: TextStyle(fontSize: 12))),
|
||||
),
|
||||
TabItem(title: t("Settings"), icon: Icon(Icons.settings, color: inactive), activeIcon: Icon(Icons.settings, color: active)),
|
||||
],
|
||||
onTap: (index) {
|
||||
setState(() {
|
||||
@ -119,6 +107,22 @@ class _NawDrawerWidget extends State<BottomNavigator> with Trans, Logging {
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class Style extends StyleHook {
|
||||
@override
|
||||
double get activeIconSize => 50;
|
||||
|
||||
@override
|
||||
double get activeIconMargin => 10;
|
||||
|
||||
@override
|
||||
double get iconSize => 20;
|
||||
|
||||
@override
|
||||
TextStyle textStyle(Color color) {
|
||||
return TextStyle(fontSize: 12, color: color);
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ class _DialogPremiumState extends State<DialogCommon> with Trans {
|
||||
),
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
child: contentBox(context),
|
||||
child: SingleChildScrollView(padding: EdgeInsets.only(bottom: 30), child: contentBox(context)),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -129,13 +129,9 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans {
|
||||
return KeyboardActions(
|
||||
config: _buildConfig(context),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 10, left: 55, right: 55),
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[
|
||||
Text(
|
||||
widget.exerciseName,
|
||||
style: GoogleFonts.archivoBlack(
|
||||
@ -163,13 +159,15 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans {
|
||||
SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
Text(
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10, left: 55, right: 55),
|
||||
child: Text(
|
||||
widget.exerciseDescription,
|
||||
style: GoogleFonts.inter(fontSize: 12, color: Colors.yellow[300]),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: true,
|
||||
),
|
||||
)),
|
||||
InkWell(
|
||||
child: Text(
|
||||
t("More »"),
|
||||
@ -186,8 +184,7 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans {
|
||||
Divider(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
widget.hasUnitQuantity
|
||||
? Text(
|
||||
Text(
|
||||
t(widget.exerciseTask),
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 14,
|
||||
@ -198,8 +195,7 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans {
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: true,
|
||||
)
|
||||
: Offstage(),
|
||||
),
|
||||
Divider(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
@ -256,10 +252,12 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans {
|
||||
)));
|
||||
}
|
||||
|
||||
Column columnQuantityUnit() {
|
||||
Column row = Column();
|
||||
Widget columnQuantityUnit() {
|
||||
Widget row = Padding(padding: const EdgeInsets.only(top: 10, left: 55, right: 55), child: Column());
|
||||
if (widget.hasUnitQuantity) {
|
||||
row = Column(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
|
||||
row = Padding(
|
||||
padding: const EdgeInsets.only(top: 10, left: 55, right: 55),
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
|
||||
TextFormField(
|
||||
focusNode: _nodeText1,
|
||||
controller: _controller1,
|
||||
@ -279,16 +277,17 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans {
|
||||
textInputAction: TextInputAction.done,
|
||||
style: GoogleFonts.archivoBlack(fontSize: 80, color: Colors.yellow[300]),
|
||||
onChanged: (value) => widget.onUnitQuantityChanged!(value)),
|
||||
]);
|
||||
]));
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
Column columnQuantity() {
|
||||
Widget columnQuantity() {
|
||||
if (widget.unit == "second") {
|
||||
return Column(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 0),
|
||||
padding: const EdgeInsets.only(top: 10, left: 55, right: 55),
|
||||
//padding: const EdgeInsets.only(bottom: 0),
|
||||
child: StreamBuilder<int>(
|
||||
stream: stopWatchTimer.rawTime,
|
||||
initialData: stopWatchTimer.rawTime.valueWrapper?.value,
|
||||
@ -300,13 +299,14 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans {
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Text(
|
||||
displayTime,
|
||||
style: const TextStyle(fontSize: 40, fontFamily: 'Helvetica', fontWeight: FontWeight.bold, color: Colors.white),
|
||||
style: const TextStyle(fontSize: 35, fontFamily: 'Helvetica', fontWeight: FontWeight.bold, color: Colors.white),
|
||||
),
|
||||
),
|
||||
]);
|
||||
})),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(2),
|
||||
//padding: const EdgeInsets.all(2),
|
||||
padding: const EdgeInsets.only(top: 10, left: 25, right: 25),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
@ -315,7 +315,7 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||
child: IconButton(
|
||||
padding: const EdgeInsets.all(2),
|
||||
color: Colors.white70,
|
||||
@ -328,7 +328,7 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans {
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||
child: IconButton(
|
||||
padding: const EdgeInsets.all(2),
|
||||
iconSize: 40,
|
||||
@ -341,7 +341,7 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans {
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5),
|
||||
child: IconButton(
|
||||
padding: const EdgeInsets.all(2),
|
||||
iconSize: 40,
|
||||
@ -364,7 +364,9 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans {
|
||||
TimePickerWidget(onChange: (value) => widget.onQuantityChanged((value).toString()))
|
||||
]);
|
||||
}
|
||||
Column row = Column(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
|
||||
Widget row = Container(
|
||||
padding: const EdgeInsets.only(top: 10, left: 55, right: 55),
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
|
||||
TextFormField(
|
||||
focusNode: _nodeText2,
|
||||
controller: _controller2,
|
||||
@ -387,7 +389,7 @@ class _ExerciseSaveState extends State<ExerciseSave> with Trans {
|
||||
widget.onQuantityChanged(value);
|
||||
},
|
||||
),
|
||||
]);
|
||||
]));
|
||||
|
||||
return row;
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ class _HomePageState extends State<AitrainerHome> with Logging {
|
||||
}
|
||||
|
||||
Future runDelayedEvent() async {
|
||||
await Future.delayed(Duration(seconds: 2), () async {
|
||||
await Future.delayed(Duration(milliseconds: 500), () async {
|
||||
// ignore: close_sinks
|
||||
SessionBloc sessionBloc = BlocProvider.of<SessionBloc>(context);
|
||||
if (sessionBloc.state != SessionReady()) {
|
||||
|
@ -68,7 +68,7 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
@override
|
||||
bool didUpdateWidget(MenuPageWidget oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
scrollController.animateTo(40, duration: Duration(milliseconds: 300), curve: Curves.easeIn);
|
||||
scrollController.animateTo(5, duration: Duration(milliseconds: 300), curve: Curves.easeIn);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -234,17 +234,18 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
SliverGrid getFilterElements(MenuBloc menuBloc) {
|
||||
List<Widget> list = [];
|
||||
|
||||
int index = 0;
|
||||
menuBloc.exerciseDeviceRepository.getGymDevices().forEach((element) {
|
||||
String deviceName = AppLanguage().appLocal == Locale('en') ? element.name : element.nameTranslation;
|
||||
ChoiceChip chip = ChoiceChip(
|
||||
labelPadding: EdgeInsets.only(right: 5),
|
||||
labelPadding: EdgeInsets.only(right: 3),
|
||||
avatar: Icon(
|
||||
Icons.remove_circle_outline,
|
||||
color: Colors.orange,
|
||||
size: 18,
|
||||
size: 10,
|
||||
),
|
||||
label: Text(deviceName),
|
||||
labelStyle: TextStyle(fontSize: 9, color: Colors.black),
|
||||
labelStyle: TextStyle(fontSize: 9, color: Colors.indigo),
|
||||
selectedColor: Colors.white,
|
||||
selected: menuBloc.selectedDevice(element.exerciseDeviceId),
|
||||
backgroundColor: Colors.blue[100],
|
||||
@ -252,6 +253,11 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
onSelected: (value) => menuBloc.add(MenuFilterExerciseType(deviceId: element.exerciseDeviceId)),
|
||||
);
|
||||
list.add(chip);
|
||||
if (index == 4) {
|
||||
list.add(Divider());
|
||||
}
|
||||
|
||||
index++;
|
||||
});
|
||||
|
||||
SliverGrid sliverList = SliverGrid(
|
||||
|
@ -2,7 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:aitrainer_app/model/workout_menu_tree.dart';
|
||||
import 'package:aitrainer_app/util/trans.dart';
|
||||
import 'package:dropdown_search/dropdown_search.dart';
|
||||
import 'package:aitrainer_app/library/dropdown_search.dart';
|
||||
//import 'package:aitrainer_app/library/dropdown_search.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
@ -35,19 +35,23 @@ class _NumberPickerWidgetState extends State<NumberPickerWidget> with Trans {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scrollController = FixedExtentScrollController(initialItem: widget.initalValue);
|
||||
_scrollController.animateToItem(widget.initalValue, duration: Duration(milliseconds: 100), curve: Curves.easeIn);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(NumberPickerWidget oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
_scrollController.animateToItem(widget.initalValue, duration: Duration(milliseconds: 100), curve: Curves.easeIn);
|
||||
}
|
||||
|
||||
Widget durationPicker({bool inSeconds = false, bool inHundredths = false}) {
|
||||
double value = 0;
|
||||
return CupertinoPicker(
|
||||
scrollController: _scrollController,
|
||||
useMagnifier: true,
|
||||
magnification: 1.2,
|
||||
diameterRatio: 0.85,
|
||||
backgroundColor: Colors.transparent,
|
||||
selectionOverlay: Container(),
|
||||
onSelectedItemChanged: (x) {
|
||||
currentData = x.toDouble();
|
||||
value = x.toDouble();
|
||||
@ -57,7 +61,7 @@ class _NumberPickerWidgetState extends State<NumberPickerWidget> with Trans {
|
||||
},
|
||||
children: List.generate(
|
||||
widget.maxValue, (index) => Text('$index ' + widget.unit, style: TextStyle(color: widget.color, fontSize: widget.fontSize))),
|
||||
itemExtent: 40,
|
||||
itemExtent: 30,
|
||||
);
|
||||
}
|
||||
|
||||
@ -66,10 +70,8 @@ class _NumberPickerWidgetState extends State<NumberPickerWidget> with Trans {
|
||||
Widget build(BuildContext context) {
|
||||
setContext(context);
|
||||
return Container(
|
||||
//color: Colors.white24,
|
||||
color: Colors.transparent,
|
||||
width: MediaQuery.of(context).size.width * .40,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -81,7 +83,8 @@ class _NumberPickerWidgetState extends State<NumberPickerWidget> with Trans {
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
width: MediaQuery.of(context).size.width * 0.95,
|
||||
height: MediaQuery.of(context).size.height * 0.25,
|
||||
//height: MediaQuery.of(context).size.height * 0.25,
|
||||
height: 100,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
@ -92,7 +95,6 @@ class _NumberPickerWidgetState extends State<NumberPickerWidget> with Trans {
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
75
pubspec.lock
75
pubspec.lock
@ -77,14 +77,14 @@ packages:
|
||||
name: build
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.6.3"
|
||||
version: "2.0.0"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_config
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.5"
|
||||
version: "0.4.7"
|
||||
build_daemon:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -98,21 +98,21 @@ packages:
|
||||
name: build_resolvers
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.5.4"
|
||||
version: "2.0.0"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.11.5"
|
||||
version: "1.12.2"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.1.10"
|
||||
version: "6.1.12"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -154,7 +154,7 @@ packages:
|
||||
name: checked_yaml
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
version: "2.0.1"
|
||||
chewie:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -211,6 +211,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
convex_bottom_bar:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: convex_bottom_bar
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
coverage:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -252,7 +259,7 @@ packages:
|
||||
name: dart_style
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.14"
|
||||
version: "2.0.0"
|
||||
devicelocale:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -260,13 +267,6 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.4.1"
|
||||
dropdown_search:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dropdown_search
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
equatable:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -315,7 +315,7 @@ packages:
|
||||
name: firebase_analytics
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "8.0.0-dev.0"
|
||||
version: "8.0.0-dev.2"
|
||||
firebase_analytics_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -336,28 +336,28 @@ packages:
|
||||
name: firebase_auth
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.1"
|
||||
version: "1.0.3"
|
||||
firebase_auth_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_auth_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
version: "4.0.2"
|
||||
firebase_auth_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_auth_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
version: "1.0.5"
|
||||
firebase_core:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_core
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
version: "1.0.3"
|
||||
firebase_core_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -378,21 +378,21 @@ packages:
|
||||
name: firebase_messaging
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "9.1.0"
|
||||
version: "9.1.1"
|
||||
firebase_messaging_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_messaging_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
version: "2.1.1"
|
||||
firebase_messaging_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_messaging_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
version: "1.0.4"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -407,6 +407,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.30.0"
|
||||
flurry:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flurry
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.0.7"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -425,21 +432,21 @@ packages:
|
||||
name: flutter_facebook_auth
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.3.1+1"
|
||||
version: "3.3.2+2"
|
||||
flutter_facebook_auth_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_facebook_auth_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
version: "2.4.1"
|
||||
flutter_facebook_auth_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_facebook_auth_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.4.0+2"
|
||||
version: "2.4.1+1"
|
||||
flutter_html:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -545,7 +552,7 @@ packages:
|
||||
name: graphs
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
version: "1.0.0"
|
||||
html:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -615,7 +622,7 @@ packages:
|
||||
name: json_annotation
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
version: "4.0.1"
|
||||
keyboard_actions:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -636,7 +643,7 @@ packages:
|
||||
name: logging
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.11.4"
|
||||
version: "1.0.1"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -832,7 +839,7 @@ packages:
|
||||
name: pubspec_parse
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.8"
|
||||
version: "1.0.0"
|
||||
purchases_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -881,7 +888,7 @@ packages:
|
||||
name: sentry
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.1.0-nullsafety.1"
|
||||
version: "5.0.0"
|
||||
shared_preferences:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -963,14 +970,14 @@ packages:
|
||||
name: smartlook
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
version: "1.0.7"
|
||||
source_gen:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.9.10+3"
|
||||
version: "1.0.0"
|
||||
source_map_stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1040,7 +1047,7 @@ packages:
|
||||
name: stream_transform
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "2.0.0"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1208,7 +1215,7 @@ packages:
|
||||
name: webkit_inspection_protocol
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
version: "1.0.0"
|
||||
webview_flutter:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
22
pubspec.yaml
22
pubspec.yaml
@ -27,7 +27,7 @@ dependencies:
|
||||
cupertino_icons: ^1.0.0
|
||||
google_fonts: ^2.0.0
|
||||
devicelocale: ^0.4.1
|
||||
sentry: ^4.1.0-nullsafety.1
|
||||
sentry: ^5.0.0
|
||||
flutter_bloc: ^7.0.0
|
||||
equatable: ^2.0.0
|
||||
|
||||
@ -54,25 +54,28 @@ dependencies:
|
||||
confetti: ^0.6.0-nullsafety
|
||||
crypto: ^3.0.0
|
||||
carousel_slider: ^4.0.0-nullsafety.0
|
||||
dropdown_search: ^0.5.0
|
||||
#dropdown_search: ^0.5.0
|
||||
convex_bottom_bar: ^3.0.0
|
||||
|
||||
|
||||
firebase_core: ^1.0.2
|
||||
firebase_analytics: ^8.0.0-dev.0
|
||||
firebase_messaging: ^9.1.0
|
||||
firebase_core: ^1.0.3
|
||||
firebase_analytics: ^8.0.0-dev.2
|
||||
firebase_messaging: ^9.1.1
|
||||
flutter_local_notifications: ^5.0.0
|
||||
firebase_auth: ^1.0.1
|
||||
flutter_facebook_auth: ^3.3.1+1
|
||||
firebase_auth: ^1.0.3
|
||||
flutter_facebook_auth: ^3.3.2
|
||||
google_sign_in: ^5.0.1
|
||||
apple_sign_in: ^0.1.0
|
||||
|
||||
smartlook: ^1.0.6
|
||||
smartlook: ^1.0.7
|
||||
flurry: ^0.0.4
|
||||
|
||||
#animated_widgets: ^1.0.6
|
||||
|
||||
mockito: ^5.0.3
|
||||
sqflite: ^2.0.0+3
|
||||
flutter_secure_storage: ^4.1.0
|
||||
#social_share: ^2.1.1
|
||||
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
@ -167,6 +170,7 @@ flutter:
|
||||
- asset/image/BMI_graph_C.png
|
||||
- asset/image/BMI_mutato.png
|
||||
- asset/image/haken.png
|
||||
- asset/image/kupa.png
|
||||
|
||||
- asset/image/pict_calorie.png
|
||||
- asset/image/pict_development_by_bodypart_percent.png
|
||||
@ -226,6 +230,8 @@ flutter:
|
||||
- asset/menu/3.bcs1.jpg
|
||||
- asset/menu/300m.jpg
|
||||
- asset/menu/400m.jpg
|
||||
- asset/menu/FG_1_test.jpg
|
||||
- asset/menu/FG_1_training.jpg
|
||||
- asset/menu/alternate_dumbbell_presses.jpg
|
||||
- asset/menu/alternate_standing_shoulder_press.jpg
|
||||
- asset/menu/arnold_press.jpg
|
||||
|
Loading…
Reference in New Issue
Block a user