WT 1.1.14 Tutorial, Sports, New goals
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 164 KiB |
BIN
asset/image/endurance.jpg
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
asset/image/explosiveness.jpg
Normal file
After Width: | Height: | Size: 236 KiB |
BIN
asset/image/flexibility.jpg
Normal file
After Width: | Height: | Size: 111 KiB |
BIN
asset/image/gain_strength.jpg
Normal file
After Width: | Height: | Size: 117 KiB |
BIN
asset/image/muscle_endurance.jpg
Normal file
After Width: | Height: | Size: 95 KiB |
BIN
asset/image/shape_forming.jpg
Normal file
After Width: | Height: | Size: 113 KiB |
BIN
asset/image/weight_loss.jpg
Normal file
After Width: | Height: | Size: 87 KiB |
46
i18n/en.json
@ -51,8 +51,7 @@
|
||||
"Aerobic": "Aerobic",
|
||||
"Anaerobic": "Anaerobic",
|
||||
"Cooper": "Cooper",
|
||||
"Strength": "Strength",
|
||||
"Endurance": "Strength Endurance",
|
||||
"Strength": "Strength",
|
||||
"Pushups": "Pushups",
|
||||
"Timed Pushups": "Timed Pushups",
|
||||
"Core": "Core",
|
||||
@ -116,7 +115,7 @@
|
||||
"Next": "Next",
|
||||
"Select a gender": "Select a gender",
|
||||
|
||||
"Set Your Goals": "Set Your Goals",
|
||||
"Set Your Primary Goal": "Set Your Primary Goal",
|
||||
"Gain Muscle": "Gain Muscle",
|
||||
"Loose Weight": "Loose Weight",
|
||||
"Your Fitness State": "Your Fitness State",
|
||||
@ -353,10 +352,6 @@
|
||||
|
||||
"Activity":"Activity",
|
||||
"Body Type":"Body Type",
|
||||
"Goal":"Goal",
|
||||
"gain_muscle": "Gain Muscle",
|
||||
"weight_loss":"Weight Loss",
|
||||
"Set your goal":"Set your goal",
|
||||
"Set your fitness level":"Set your fitness level",
|
||||
"Set your body type":"Set your body type",
|
||||
"These equipments and devices are available":"These equipments and devices are available",
|
||||
@ -441,6 +436,41 @@
|
||||
"Result":"Result",
|
||||
"Name too short":"Name too short",
|
||||
"No, bring me there":"No, bring me there",
|
||||
"You are about to add a new parallel test":"You are about to add a new parallel test"
|
||||
"You are about to add a new parallel test":"You are about to add a new parallel test",
|
||||
"Your Primary Sport":"Your Primary Sport",
|
||||
"and":"and",
|
||||
"Sport":"Sport",
|
||||
|
||||
"Goal":"Goal",
|
||||
|
||||
"gain_muscle": "Gain Muscle",
|
||||
"gain_strength": "Gain Strength",
|
||||
"weight_loss":"Weight Loss",
|
||||
"muscle_endurance": "Muscle Endurance",
|
||||
"flexibility":"Flexibility",
|
||||
"explosiveness":"Explosiveness",
|
||||
"shape_forming":"Shape Forming",
|
||||
"endurance": "Endurance",
|
||||
|
||||
"Set your primary goal":"Set your primary goal",
|
||||
|
||||
"Endurance": "Endurance",
|
||||
"Muscle Endurance": "Muscle Endurance",
|
||||
"Flexibility":"Flexibility",
|
||||
"Explosiveness":"Explosiveness",
|
||||
"Shape Forming":"Shape Forming",
|
||||
"Loss Weight":"Loss Weight",
|
||||
|
||||
"Skip":"Skip",
|
||||
"Exception: Network error, try again later!":"Network error, try again later!",
|
||||
"Exception: Exception: Network Error, please try again later": "Exception: Network Error, please try again later",
|
||||
|
||||
"Warning":"Warning",
|
||||
"No Registration":"No Registration",
|
||||
"You will skip the registration process.":"You will skip the registration process.",
|
||||
"Please take a short tour in the app":"Please take a short tour in the app",
|
||||
"No Login":"No Login",
|
||||
"You will skip the login.":"You will skip the login.",
|
||||
"The app functionalitity will be restricted, but please take a tour!":"The app functionalitity will be restricted, but please take a tour!"
|
||||
|
||||
}
|
33
i18n/hu.json
@ -58,7 +58,7 @@
|
||||
"Anaerobic": "Anaerob",
|
||||
"Cooper": "Cooper teszt",
|
||||
"Strength": "Erő",
|
||||
"Endurance": "Erő állóképesség",
|
||||
"Endurance": "Állóképesség",
|
||||
"Pushups": "Fekvőtámasz",
|
||||
"Timed Pushups": "Fekvőtámasz időre",
|
||||
"Core": "Core (plank)",
|
||||
@ -120,7 +120,7 @@
|
||||
"Next": "Tovább",
|
||||
"Select a gender": "Válaszd ki a nemet",
|
||||
|
||||
"Set Your Goals": "Mi a célod?",
|
||||
"Set Your Primary Goal": "Mi az elsődleges célod?",
|
||||
"Gain Muscle": "Izomépítés",
|
||||
"Loose Weight": "Fogyás",
|
||||
"Your Fitness State": "Milyen a fizikai állapotod?",
|
||||
@ -436,5 +436,32 @@
|
||||
"Result":"Értékelés",
|
||||
"Name too short":"A név túl rövid",
|
||||
"No, bring me there":"Nem, vigyél oda",
|
||||
"You are about to add a new parallel test":"Egy új gyakorlatot készülsz párhuzamosan végrehajtani"
|
||||
"You are about to add a new parallel test":"Egy új gyakorlatot készülsz párhuzamosan végrehajtani",
|
||||
"Your Primary Sport":"Elsődleges sportág",
|
||||
"and":"és",
|
||||
"Sport":"sport",
|
||||
|
||||
"gain_strength": "Erőnövelés",
|
||||
"muscle_endurance": "Erő állóképesség",
|
||||
"flexibility":"Rugalmasság",
|
||||
"explosiveness":"Robbanékonyság",
|
||||
"shape_forming":"Alakformálás",
|
||||
"endurance": "Állóképesség",
|
||||
|
||||
"Muscle Endurance": "Erő állóképesség",
|
||||
"Flexibility":"Rugalmasság",
|
||||
"Explosiveness":"Robbanékonyság",
|
||||
"Shape Forming":"Alakformálás",
|
||||
"Loss Weight":"Fogyás",
|
||||
|
||||
"Skip":"Kihagyom",
|
||||
"Exception: Network error, try again later!":"Hálózati hiba, próbálkozz később!",
|
||||
"Exception: Exception: Network Error, please try again later": "Hálózati hiba, próbálkozz később!",
|
||||
"Warning":"Figyelmeztetés",
|
||||
"No Registration":"Regisztráció kimaradt",
|
||||
"You will skip the registration process.":"Átugrod a regisztrációs folyamatot.",
|
||||
"Please take a short tour in the app":"Kérlek tégy egy rövid túrát az applikációban",
|
||||
"No Login":"Bejelentkezés kimaradt",
|
||||
"You will skip the login.":"Átugrod a bejelentkezést.",
|
||||
"The app functionalitity will be restricted, but please take a tour!":"Az applikációt korlátozottan tudod így használni, de tégy egy túrát!"
|
||||
}
|
@ -91,6 +91,8 @@ PODS:
|
||||
- Flutter
|
||||
- Flurry-iOS-SDK/FlurrySDK (11.2.0)
|
||||
- Flutter (1.0.0)
|
||||
- flutter_app_badger (0.0.1):
|
||||
- Flutter
|
||||
- flutter_facebook_auth (2.0.0):
|
||||
- FBSDKCoreKit (~> 9.1.0)
|
||||
- FBSDKLoginKit (~> 9.1.0)
|
||||
@ -99,6 +101,9 @@ PODS:
|
||||
- Flutter
|
||||
- flutter_secure_storage (3.3.1):
|
||||
- Flutter
|
||||
- flutter_uxcam (1.3.2):
|
||||
- Flutter
|
||||
- UXCam (~> 3.3.3)
|
||||
- FMDB (2.7.5):
|
||||
- FMDB/standard (= 2.7.5)
|
||||
- FMDB/standard (2.7.5)
|
||||
@ -153,6 +158,8 @@ PODS:
|
||||
- nanopb/encode (2.30906.0)
|
||||
- package_info (0.0.1):
|
||||
- Flutter
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
- path_provider (0.0.1):
|
||||
- Flutter
|
||||
- PromisesObjC (1.2.12)
|
||||
@ -164,13 +171,18 @@ PODS:
|
||||
- PurchasesCoreSwift (3.10.7)
|
||||
- PurchasesHybridCommon (1.6.2):
|
||||
- Purchases (= 3.10.7)
|
||||
- shared_preferences (0.0.1):
|
||||
- Sentry (6.2.1):
|
||||
- Sentry/Core (= 6.2.1)
|
||||
- Sentry/Core (6.2.1)
|
||||
- sentry_flutter (0.0.1):
|
||||
- Flutter
|
||||
- smartlook (0.0.5):
|
||||
- Sentry (~> 6.2.1)
|
||||
- shared_preferences (0.0.1):
|
||||
- Flutter
|
||||
- sqflite (0.0.2):
|
||||
- Flutter
|
||||
- FMDB (>= 2.7.5)
|
||||
- UXCam (3.3.4)
|
||||
- video_player (0.0.1):
|
||||
- Flutter
|
||||
- wakelock (0.0.1):
|
||||
@ -187,16 +199,19 @@ DEPENDENCIES:
|
||||
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
||||
- flurry (from `.symlinks/plugins/flurry/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_app_badger (from `.symlinks/plugins/flutter_app_badger/ios`)
|
||||
- flutter_facebook_auth (from `.symlinks/plugins/flutter_facebook_auth/ios`)
|
||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||
- flutter_uxcam (from `.symlinks/plugins/flutter_uxcam/ios`)
|
||||
- google_sign_in (from `.symlinks/plugins/google_sign_in/ios`)
|
||||
- modal_progress_hud_nsn (from `.symlinks/plugins/modal_progress_hud_nsn/ios`)
|
||||
- package_info (from `.symlinks/plugins/package_info/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider (from `.symlinks/plugins/path_provider/ios`)
|
||||
- purchases_flutter (from `.symlinks/plugins/purchases_flutter/ios`)
|
||||
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
|
||||
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
|
||||
- smartlook (from `.symlinks/plugins/smartlook/ios`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||
- video_player (from `.symlinks/plugins/video_player/ios`)
|
||||
- wakelock (from `.symlinks/plugins/wakelock/ios`)
|
||||
@ -228,6 +243,8 @@ SPEC REPOS:
|
||||
- Purchases
|
||||
- PurchasesCoreSwift
|
||||
- PurchasesHybridCommon
|
||||
- Sentry
|
||||
- UXCam
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
apple_sign_in:
|
||||
@ -246,26 +263,32 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/flurry/ios"
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
flutter_app_badger:
|
||||
:path: ".symlinks/plugins/flutter_app_badger/ios"
|
||||
flutter_facebook_auth:
|
||||
:path: ".symlinks/plugins/flutter_facebook_auth/ios"
|
||||
flutter_local_notifications:
|
||||
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||
flutter_secure_storage:
|
||||
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||
flutter_uxcam:
|
||||
:path: ".symlinks/plugins/flutter_uxcam/ios"
|
||||
google_sign_in:
|
||||
:path: ".symlinks/plugins/google_sign_in/ios"
|
||||
modal_progress_hud_nsn:
|
||||
:path: ".symlinks/plugins/modal_progress_hud_nsn/ios"
|
||||
package_info:
|
||||
:path: ".symlinks/plugins/package_info/ios"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider:
|
||||
:path: ".symlinks/plugins/path_provider/ios"
|
||||
purchases_flutter:
|
||||
:path: ".symlinks/plugins/purchases_flutter/ios"
|
||||
sentry_flutter:
|
||||
:path: ".symlinks/plugins/sentry_flutter/ios"
|
||||
shared_preferences:
|
||||
:path: ".symlinks/plugins/shared_preferences/ios"
|
||||
smartlook:
|
||||
:path: ".symlinks/plugins/smartlook/ios"
|
||||
sqflite:
|
||||
:path: ".symlinks/plugins/sqflite/ios"
|
||||
video_player:
|
||||
@ -296,9 +319,11 @@ SPEC CHECKSUMS:
|
||||
flurry: 15b01f664ab1367c62b50291541ea7f78ca85aad
|
||||
Flurry-iOS-SDK: 6636d30c30f12010e7c7c71d84b443416a168efc
|
||||
Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
|
||||
flutter_app_badger: 65de4d6f0c34a891df49e6cfb8a1c0496426fa68
|
||||
flutter_facebook_auth: 4b170c07b7fce791497093fcc3f134fb215f3f07
|
||||
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
|
||||
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
|
||||
flutter_uxcam: 87dd981feb200bc81a2f062edb5ca2d16dae595a
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
google_sign_in: 6bd214b9c154f881422f5fe27b66aaa7bbd580cc
|
||||
GoogleAppMeasurement: 8d3c0aeede16ab7764144b5a4ca8e1d4323841b7
|
||||
@ -310,15 +335,18 @@ SPEC CHECKSUMS:
|
||||
modal_progress_hud_nsn: f6fb744cd060653d66ed8f325360ef3650eb2fde
|
||||
nanopb: 1bf24dd71191072e120b83dd02d08f3da0d65e53
|
||||
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
|
||||
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
|
||||
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
|
||||
PromisesObjC: 3113f7f76903778cf4a0586bd1ab89329a0b7b97
|
||||
Purchases: b8b8fb6e856ac8166e217f6e014df894d821dda1
|
||||
purchases_flutter: 0130970b895c903e4e0aad793dd3a4c1b70bb434
|
||||
PurchasesCoreSwift: 8ae0f08e020f0bc97c1befa4e38a0dbc8e9732e0
|
||||
PurchasesHybridCommon: 5f5c1c245b12fc5e8760af7d11cb10f888109a9b
|
||||
Sentry: 9b922b396b0e0bca8516a10e36b0ea3ebea5faf7
|
||||
sentry_flutter: 5b3c6d717db5b7482504a313c831b318297d4d37
|
||||
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
|
||||
smartlook: bbc5c73a85c752a31dabf100c8930838c646342e
|
||||
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
|
||||
UXCam: c2c00873595ab89be227f197213dc3679ff88ae5
|
||||
video_player: 9cc823b1d9da7e8427ee591e8438bfbcde500e6e
|
||||
wakelock: b0843b2479edbf6504d8d262c2959446f35373aa
|
||||
webview_flutter: 9f491a9b5a66f2573946a389b2677987b0ff8c0b
|
||||
|
@ -388,7 +388,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = SFJJBDCU6Z;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@ -405,7 +405,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
MARKETING_VERSION = 1.1.13;
|
||||
MARKETING_VERSION = 1.1.14;
|
||||
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 = 2;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = SFJJBDCU6Z;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@ -548,7 +548,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
MARKETING_VERSION = 1.1.13;
|
||||
MARKETING_VERSION = 1.1.14;
|
||||
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 = 2;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = SFJJBDCU6Z;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@ -583,7 +583,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Flutter",
|
||||
);
|
||||
MARKETING_VERSION = 1.1.13;
|
||||
MARKETING_VERSION = 1.1.14;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.aitrainer.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
|
@ -54,8 +54,6 @@
|
||||
</dict>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
<string>fetch</string>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:aitrainer_app/model/cache.dart';
|
||||
import 'package:aitrainer_app/model/sport.dart';
|
||||
import 'package:aitrainer_app/repository/customer_repository.dart';
|
||||
import 'package:aitrainer_app/util/common.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
@ -8,8 +9,6 @@ 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';
|
||||
|
||||
@ -30,24 +29,7 @@ class CustomerChangeBloc extends Bloc<CustomerChangeEvent, CustomerChangeState>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
selectedSport = customerRepository.getSport();
|
||||
print("selected: $selectedFitnessItem sport: $selectedSport " + customerRepository.customer!.fitnessLevel.toString());
|
||||
}
|
||||
|
||||
@ -63,13 +45,16 @@ class CustomerChangeBloc extends Bloc<CustomerChangeEvent, CustomerChangeState>
|
||||
yield CustomerChangeLoading();
|
||||
yield CustomerDataChanged();
|
||||
} else if (event is CustomerGoalChange) {
|
||||
yield CustomerChangeLoading();
|
||||
customerRepository.setGoal(event.goal);
|
||||
yield CustomerDataChanged();
|
||||
} else if (event is CustomerChangePasswordObscure) {
|
||||
yield CustomerChangeLoading();
|
||||
visiblePassword = !visiblePassword;
|
||||
yield CustomerDataChanged();
|
||||
} else if (event is CustomerFitnessChange) {
|
||||
//customerRepository.setFitnessLevel(event.fitness);
|
||||
yield CustomerChangeLoading();
|
||||
customerRepository.setFitnessLevel(event.fitness);
|
||||
selectedFitnessItem = event.fitness;
|
||||
yield CustomerDataChanged();
|
||||
} else if (event is CustomerBirthYearChange) {
|
||||
@ -108,22 +93,17 @@ class CustomerChangeBloc extends Bloc<CustomerChangeEvent, CustomerChangeState>
|
||||
} 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!);
|
||||
}
|
||||
customerRepository.setFitnessLevel(selectedFitnessItem!);
|
||||
}
|
||||
if (selectedSport != null) {
|
||||
customerRepository.customer!.sportId = selectedSport!.sportId;
|
||||
}
|
||||
|
||||
await customerRepository.saveCustomer();
|
||||
Cache().initBadges();
|
||||
yield CustomerSaveSuccess();
|
||||
|
@ -369,7 +369,7 @@ class DevelopmentByMuscleBloc extends Bloc<DevelopmentByMuscleEvent, Development
|
||||
if (event is DevelopmentByMuscleLoad) {
|
||||
yield DevelopmentByMuscleLoadingState();
|
||||
Track().track(TrackingEvent.my_muscle_development);
|
||||
Cache().setMuscleDevelopmentSeen();
|
||||
Cache().setActivityDonePrefs(ActivityDone.isMuscleDevelopmentSeen);
|
||||
await getData();
|
||||
yield DevelopmentByMuscleReadyState();
|
||||
} else if (event is DevelopmentByMuscleDiagramTypeChange) {
|
||||
|
@ -22,7 +22,7 @@ class ExerciseLogBloc extends Bloc<ExerciseLogEvent, ExerciseLogState> {
|
||||
try {
|
||||
if (event is ExerciseLogLoad) {
|
||||
yield ExerciseLogLoading();
|
||||
await Cache().setExerciseLogSeen();
|
||||
await Cache().setActivityDonePrefs(ActivityDone.isExerciseLogSeen);
|
||||
Track().track(TrackingEvent.exercise_log_open);
|
||||
yield ExerciseLogReady();
|
||||
} else if (event is ExerciseLogDelete) {
|
||||
|
@ -158,6 +158,9 @@ class ExerciseNewBloc extends Bloc<ExerciseNewEvent, ExerciseNewState> with Logg
|
||||
} else if (event is ExerciseNewBMIAnimate) {
|
||||
yield ExerciseNewLoading();
|
||||
yield ExerciseNewReady();
|
||||
} else if (event is ExerciseNewAddError) {
|
||||
yield ExerciseNewLoading();
|
||||
yield ExerciseNewError(message: event.message);
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
yield ExerciseNewError(message: e.toString());
|
||||
|
@ -73,7 +73,7 @@ class ExerciseNewSaveWeight extends ExerciseNewEvent {
|
||||
|
||||
class ExerciseNewBMIAnimate extends ExerciseNewEvent {
|
||||
final dynamic value;
|
||||
const ExerciseNewBMIAnimate({this.value});
|
||||
const ExerciseNewBMIAnimate({required this.value});
|
||||
|
||||
@override
|
||||
List<Object> get props => [value];
|
||||
@ -82,3 +82,10 @@ class ExerciseNewBMIAnimate extends ExerciseNewEvent {
|
||||
class ExerciseNewSubmit extends ExerciseNewEvent {
|
||||
const ExerciseNewSubmit();
|
||||
}
|
||||
|
||||
class ExerciseNewAddError extends ExerciseNewEvent {
|
||||
final String message;
|
||||
const ExerciseNewAddError({required this.message});
|
||||
@override
|
||||
List<Object> get props => [message];
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> with Trans {
|
||||
final BuildContext context;
|
||||
final bool isRegistration;
|
||||
bool dataPolicyAllowed = false;
|
||||
bool emailSubscription = false;
|
||||
bool obscure = true;
|
||||
|
||||
LoginBloc({required this.accountBloc, required this.userRepository, required this.context, required this.isRegistration})
|
||||
@ -81,6 +82,7 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> with Trans {
|
||||
} else {
|
||||
await userRepository.addUser();
|
||||
accountBloc.add(AccountLogInFinished(customer: Cache().userLoggedIn!));
|
||||
customerRepository.customer!.emailSubscription = emailSubscription == true ? 1 : 0;
|
||||
await saveCustomer();
|
||||
Track().track(TrackingEvent.registration, eventValue: "email");
|
||||
Cache().setLoginType(LoginType.email);
|
||||
@ -94,6 +96,7 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> with Trans {
|
||||
Cache().setLoginType(LoginType.fb);
|
||||
await userRepository.addUserFB();
|
||||
accountBloc.add(AccountLogInFinished(customer: Cache().userLoggedIn!));
|
||||
customerRepository.customer!.emailSubscription = emailSubscription == true ? 1 : 0;
|
||||
await saveCustomer();
|
||||
Track().track(TrackingEvent.registration, eventValue: "FB");
|
||||
yield LoginSuccess();
|
||||
@ -105,6 +108,7 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> with Trans {
|
||||
Cache().setLoginType(LoginType.google);
|
||||
await userRepository.addUserGoogle();
|
||||
accountBloc.add(AccountLogInFinished(customer: Cache().userLoggedIn!));
|
||||
customerRepository.customer!.emailSubscription = emailSubscription == true ? 1 : 0;
|
||||
await saveCustomer();
|
||||
Track().track(TrackingEvent.registration, eventValue: "Google");
|
||||
yield LoginSuccess();
|
||||
@ -116,6 +120,7 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> with Trans {
|
||||
Cache().setLoginType(LoginType.apple);
|
||||
await userRepository.addUserApple();
|
||||
accountBloc.add(AccountLogInFinished(customer: Cache().userLoggedIn!));
|
||||
customerRepository.customer!.emailSubscription = emailSubscription == true ? 1 : 0;
|
||||
await saveCustomer();
|
||||
Track().track(TrackingEvent.registration, eventValue: "Apple");
|
||||
|
||||
@ -124,10 +129,18 @@ class LoginBloc extends Bloc<LoginEvent, LoginState> with Trans {
|
||||
yield LoginLoading();
|
||||
this.dataPolicyAllowed = !dataPolicyAllowed;
|
||||
yield LoginReady();
|
||||
} else if (event is EmailSubscriptionClicked) {
|
||||
yield LoginLoading();
|
||||
this.emailSubscription = !emailSubscription;
|
||||
yield LoginReady();
|
||||
} else if (event is LoginPasswordChangeObscure) {
|
||||
yield LoginLoading();
|
||||
this.obscure = !this.obscure;
|
||||
yield LoginReady();
|
||||
} else if (event is LoginSkip) {
|
||||
yield LoginLoading();
|
||||
Cache().startPage = "home";
|
||||
yield LoginSkipped();
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
yield LoginError(message: e.toString());
|
||||
|
@ -43,11 +43,20 @@ class LoginApple extends LoginEvent {
|
||||
const LoginApple();
|
||||
}
|
||||
|
||||
class LoginSkip extends LoginEvent {
|
||||
const LoginSkip();
|
||||
}
|
||||
|
||||
class DataProtectionClicked extends LoginEvent {
|
||||
final bool marked;
|
||||
const DataProtectionClicked({required this.marked});
|
||||
}
|
||||
|
||||
class EmailSubscriptionClicked extends LoginEvent {
|
||||
final bool marked;
|
||||
const EmailSubscriptionClicked({required this.marked});
|
||||
}
|
||||
|
||||
class RegistrationSubmit extends LoginEvent {
|
||||
const RegistrationSubmit();
|
||||
}
|
||||
|
@ -23,6 +23,10 @@ class LoginSuccess extends LoginState {
|
||||
const LoginSuccess();
|
||||
}
|
||||
|
||||
class LoginSkipped extends LoginState {
|
||||
const LoginSkipped();
|
||||
}
|
||||
|
||||
class LoginError extends LoginState {
|
||||
final String message;
|
||||
const LoginError({required this.message});
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:aitrainer_app/bloc/tutorial/tutorial_bloc.dart';
|
||||
import 'package:aitrainer_app/model/cache.dart';
|
||||
import 'package:aitrainer_app/model/exercise_ability.dart';
|
||||
import 'package:aitrainer_app/model/workout_menu_tree.dart';
|
||||
@ -26,11 +27,6 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> with Trans, Logging {
|
||||
WorkoutMenuTree? workoutItem;
|
||||
final List<int> listFilterDevice = [];
|
||||
|
||||
String infoTitle = "";
|
||||
String infoText = "";
|
||||
String infoText2 = "";
|
||||
String infoText3 = "";
|
||||
String infoLink = "";
|
||||
int missingParent = 0;
|
||||
String? missingTreeName = "";
|
||||
|
||||
@ -42,48 +38,6 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> with Trans, Logging {
|
||||
parent = 0;
|
||||
}
|
||||
|
||||
void setMenuInfo() {
|
||||
double percent = Cache().getPercentExercises();
|
||||
if (percent == -1) {
|
||||
exerciseRepository.getBaseExerciseFinishedPercent();
|
||||
percent = Cache().getPercentExercises();
|
||||
}
|
||||
|
||||
percent = percent * 100;
|
||||
//log("Percent " + percent.toString());
|
||||
if (percent == -1 || percent == 0) {
|
||||
infoTitle = t("Greetings!");
|
||||
infoText = t("The purpose is to measure you physical condition") +
|
||||
" " +
|
||||
t("The suggested order of the exercises: chest - biceps - triceps - back - shoulders - core - tigh - calf.");
|
||||
infoText2 = t("I suggest begin your tests with a");
|
||||
infoText3 = t("Go to the menu Strength - One Rep Max - Chest, and select your favourite exercise.");
|
||||
infoLink = t("Bring me there");
|
||||
} else if (percent > 0 && percent < 20) {
|
||||
infoTitle = t("Nice! This is a good start");
|
||||
} else if (percent > 20 && percent < 40) {
|
||||
infoTitle = t("Go on!") + " " + t("You are on track");
|
||||
} else if (percent > 60 && percent < 80) {
|
||||
infoTitle = t("Persistence!") + " " + t("Not so much left");
|
||||
} else if (percent > 80 && percent < 100) {
|
||||
infoTitle = t("Almost!") + " " + t("You have only 1-2 exercise left to finish!");
|
||||
} else {
|
||||
infoTitle = t("Congratulation!");
|
||||
infoText2 = t("You have achieved to first 100% test-round!");
|
||||
infoText3 = t("Now you unlocked: Development By Muscles and the Suggested Trainings Plan");
|
||||
}
|
||||
|
||||
menuTreeRepository.sortByMuscleType();
|
||||
missingTreeName = exerciseRepository.nextMissingBaseExercise(menuTreeRepository.sortedTree);
|
||||
//log("Missing " + missingTreeName);
|
||||
if (missingTreeName != null) {
|
||||
if (percent > 0) {
|
||||
infoText = t("Please continue your tests with a") + " '" + missingTreeName! + "' " + t("exercise!");
|
||||
infoLink = t("Bring me there");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setContext(BuildContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
@ -118,7 +72,6 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> with Trans, Logging {
|
||||
final LinkedHashMap<String, WorkoutMenuTree> branch = menuTreeRepository.getBranch(event.parent);
|
||||
|
||||
await getImages(branch);
|
||||
//await Future.delayed(Duration(seconds: 2));
|
||||
yield MenuReady();
|
||||
} else if (event is MenuTreeUp) {
|
||||
yield MenuLoading();
|
||||
|
@ -61,6 +61,11 @@ class SettingsBloc extends Bloc<SettingsEvent, SettingsState> with Logging {
|
||||
}
|
||||
Cache().initBadges();
|
||||
yield SettingsReady();
|
||||
} else if (event is SettingsActivateTutorial) {
|
||||
yield SettingsLoading();
|
||||
Cache().activitiesDone[event.activity.toStr()] = false;
|
||||
print(" ----------------- Setting ${event.activity} to false");
|
||||
yield SettingsReady();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,3 +31,11 @@ class SettingsSetHardware extends SettingsEvent {
|
||||
@override
|
||||
List<Object> get props => [this.hasHardware];
|
||||
}
|
||||
|
||||
class SettingsActivateTutorial extends SettingsEvent {
|
||||
final ActivityDone activity;
|
||||
const SettingsActivateTutorial({required this.activity});
|
||||
|
||||
@override
|
||||
List<Object> get props => [this.activity];
|
||||
}
|
||||
|
@ -113,6 +113,8 @@ class TestSetEditBloc extends Bloc<TestSetEditEvent, TestSetEditState> {
|
||||
Cache().saveActiveExercisePlan(exercisePlan, details);
|
||||
Track().track(TrackingEvent.test_set_edit, eventValue: templateName);
|
||||
yield TestSetEditSaved();
|
||||
} else if (event is TestSetEditAddError) {
|
||||
yield TestSetEditError(message: event.message);
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
yield TestSetEditError(message: e.toString());
|
||||
|
@ -47,3 +47,10 @@ class TestSetEditSkipExerciseType extends TestSetEditEvent {
|
||||
class TestSetEditSubmit extends TestSetEditEvent {
|
||||
const TestSetEditSubmit();
|
||||
}
|
||||
|
||||
class TestSetEditAddError extends TestSetEditEvent {
|
||||
final String message;
|
||||
const TestSetEditAddError({required this.message});
|
||||
@override
|
||||
List<Object> get props => [message];
|
||||
}
|
||||
|
214
lib/bloc/tutorial/tutorial_bloc.dart
Normal file
@ -0,0 +1,214 @@
|
||||
import 'dart:async';
|
||||
import 'package:aitrainer_app/bloc/menu/menu_bloc.dart';
|
||||
import 'package:aitrainer_app/model/cache.dart';
|
||||
import 'package:aitrainer_app/model/tutorial.dart';
|
||||
import 'package:aitrainer_app/model/tutorial_step.dart';
|
||||
import 'package:aitrainer_app/service/logging.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
part 'tutorial_event.dart';
|
||||
part 'tutorial_state.dart';
|
||||
|
||||
class TutorialBloc extends Bloc<TutorialEvent, TutorialState> with Logging {
|
||||
late String tutorialName;
|
||||
bool isActive = false;
|
||||
bool canActivate = false;
|
||||
|
||||
Tutorial? tutorial;
|
||||
String? actualText;
|
||||
String? actualCheck;
|
||||
|
||||
List<String> checks = [];
|
||||
|
||||
double calculatedHeight = 0;
|
||||
|
||||
TutorialStepAction? action;
|
||||
double? top;
|
||||
double left = 20;
|
||||
bool showCheckText = true;
|
||||
int parent = 0;
|
||||
|
||||
int step = 0;
|
||||
|
||||
MenuBloc? menuBloc;
|
||||
|
||||
TutorialBloc({required this.tutorialName}) : super(TutorialInitial());
|
||||
|
||||
init() {
|
||||
if (Cache().tutorials != null) {
|
||||
print("Actual step: $step");
|
||||
final List<Tutorial> tutorials = Cache().tutorials!;
|
||||
tutorials.forEach((element) {
|
||||
final String realTutorialName = tutorialName.replaceFirst("tutorial", "").toLowerCase();
|
||||
if (element.name.toLowerCase() == realTutorialName) {
|
||||
tutorial = element;
|
||||
|
||||
setNextStepData(step);
|
||||
isActive = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void setNextStepData(int step) {
|
||||
if (step == -1) {
|
||||
isActive = false;
|
||||
return;
|
||||
}
|
||||
if (tutorial != null && tutorial!.steps != null) {
|
||||
actualText = tutorial!.steps![step].tutorialTextTranslation;
|
||||
print("Step: $step, text: $actualText");
|
||||
this.actualCheck = tutorial!.steps![step].checkText;
|
||||
this.checks = [];
|
||||
|
||||
if (this.actualCheck != null) {
|
||||
this.checks = this.actualCheck!.split("|");
|
||||
} else {
|
||||
this.checks.add("Next");
|
||||
}
|
||||
|
||||
if (this.tutorial!.steps![step].action != null) {
|
||||
this.action = this.tutorial!.steps![step].action;
|
||||
this.top = action!.top.toDouble();
|
||||
this.left = action!.left == -1 ? 20 : action!.left.toDouble();
|
||||
this.showCheckText = action!.showCheckText == true;
|
||||
this.parent = action!.parent;
|
||||
}
|
||||
|
||||
calculateHeight();
|
||||
}
|
||||
/* else {
|
||||
this.add(TutorialFinished());
|
||||
} */
|
||||
}
|
||||
|
||||
bool activateTutorial() {
|
||||
if (!canActivate) {
|
||||
print("Tutorial canActivate false");
|
||||
return false;
|
||||
}
|
||||
ActivityDone? activityDone = ActivityDone.tutorialBasic.searchByString(tutorialName);
|
||||
|
||||
if (activityDone == null) {
|
||||
return false;
|
||||
}
|
||||
bool? isActivityDone = Cache().activitiesDone[activityDone.toStr()];
|
||||
log("isActivityDone? $isActivityDone");
|
||||
if (isActivityDone == null || isActivityDone == true) {
|
||||
return false;
|
||||
}
|
||||
log("Running tutorial $activityDone");
|
||||
init();
|
||||
return true;
|
||||
}
|
||||
|
||||
void calculateHeight() {
|
||||
int count = getElementCount('<p>');
|
||||
count += getElementCount('<br/>');
|
||||
double lines = (actualText!.length / 32 + count);
|
||||
if (lines < 5) {
|
||||
lines = lines + 2;
|
||||
}
|
||||
calculatedHeight = lines * 15;
|
||||
print("Calculated Height: $calculatedHeight length: ${actualText!.length} lines: $lines count: $count");
|
||||
}
|
||||
|
||||
int getElementCount(String elem) {
|
||||
int pos = 0;
|
||||
int count = 0;
|
||||
bool out = false;
|
||||
while (out == false) {
|
||||
pos = actualText!.indexOf(elem, pos);
|
||||
if (pos == -1) {
|
||||
out = true;
|
||||
} else {
|
||||
count++;
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
int getNextStep(int step) {
|
||||
int next = 0;
|
||||
if (action == null) {
|
||||
return step + 1;
|
||||
}
|
||||
/* bool found = false;
|
||||
if (tutorial != null && tutorial!.steps != null) {
|
||||
for (var nextStep in tutorial!.steps!) {
|
||||
print("step $step parent: ${nextStep.action!.parent}");
|
||||
if (step + 1 == nextStep.step) {
|
||||
next = nextStep.step!;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} */
|
||||
step++;
|
||||
next = step;
|
||||
print("Next step:! $next");
|
||||
if (step >= tutorial!.steps!.length) {
|
||||
next = -1;
|
||||
this.add(TutorialFinished());
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
bool checkAction(String checkText) {
|
||||
final bool nextStep = checkText == actualCheck;
|
||||
if (nextStep) {
|
||||
this.add(TutorialNext(text: actualCheck!));
|
||||
}
|
||||
return nextStep;
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<TutorialState> mapEventToState(TutorialEvent event) async* {
|
||||
if (event is TutorialLoad) {
|
||||
init();
|
||||
if (menuBloc != null) {
|
||||
menuBloc!.add(MenuCreate());
|
||||
}
|
||||
} else if (event is TutorialNext) {
|
||||
if (tutorial != null) {
|
||||
yield TutorialLoading();
|
||||
|
||||
step = this.getNextStep(step);
|
||||
if (step == -1) {
|
||||
print("Tutorial $tutorialName finished!");
|
||||
return;
|
||||
}
|
||||
print("Step: $step");
|
||||
setNextStepData(step);
|
||||
|
||||
print("Menu rebuild $menuBloc");
|
||||
if (menuBloc != null) {
|
||||
menuBloc!.add(MenuCreate());
|
||||
}
|
||||
yield TutorialReady();
|
||||
}
|
||||
} else if (event is TutorialWrongAction) {
|
||||
yield TutorialLoading();
|
||||
actualText = tutorial!.steps![step].errorTextTranslation!;
|
||||
yield TutorialReady();
|
||||
} else if (event is TutorialStart) {
|
||||
yield TutorialLoading();
|
||||
isActive = true;
|
||||
canActivate = true;
|
||||
step = 0;
|
||||
yield TutorialReady();
|
||||
} else if (event is TutorialFinished) {
|
||||
yield TutorialLoading();
|
||||
isActive = false;
|
||||
canActivate = false;
|
||||
ActivityDone? activityDone = ActivityDone.tutorialBasic.searchByString(tutorialName);
|
||||
print("activity Finished: $activityDone");
|
||||
if (activityDone != null) {
|
||||
await Cache().setActivityDonePrefs(activityDone);
|
||||
}
|
||||
yield TutorialReady();
|
||||
}
|
||||
}
|
||||
}
|
29
lib/bloc/tutorial/tutorial_event.dart
Normal file
@ -0,0 +1,29 @@
|
||||
part of 'tutorial_bloc.dart';
|
||||
|
||||
abstract class TutorialEvent extends Equatable {
|
||||
const TutorialEvent();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class TutorialLoad extends TutorialEvent {
|
||||
const TutorialLoad();
|
||||
}
|
||||
|
||||
class TutorialStart extends TutorialEvent {
|
||||
const TutorialStart();
|
||||
}
|
||||
|
||||
class TutorialNext extends TutorialEvent {
|
||||
final String text;
|
||||
const TutorialNext({required this.text});
|
||||
}
|
||||
|
||||
class TutorialFinished extends TutorialEvent {
|
||||
const TutorialFinished();
|
||||
}
|
||||
|
||||
class TutorialWrongAction extends TutorialEvent {
|
||||
const TutorialWrongAction();
|
||||
}
|
28
lib/bloc/tutorial/tutorial_state.dart
Normal file
@ -0,0 +1,28 @@
|
||||
part of 'tutorial_bloc.dart';
|
||||
|
||||
abstract class TutorialState extends Equatable {
|
||||
const TutorialState();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class TutorialInitial extends TutorialState {
|
||||
const TutorialInitial();
|
||||
}
|
||||
|
||||
class TutorialLoading extends TutorialState {
|
||||
const TutorialLoading();
|
||||
}
|
||||
|
||||
class TutorialReady extends TutorialState {
|
||||
const TutorialReady();
|
||||
}
|
||||
|
||||
class TutorialError extends TutorialState {
|
||||
final String message;
|
||||
const TutorialError({required this.message});
|
||||
|
||||
@override
|
||||
List<Object> get props => [message];
|
||||
}
|
@ -1,138 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class FadeIn extends StatefulWidget {
|
||||
/// Fade-in controller
|
||||
final FadeInController controller;
|
||||
|
||||
/// Child widget to fade-in
|
||||
final Widget child;
|
||||
|
||||
/// Duration of fade-in. Defaults to 250ms
|
||||
final Duration duration;
|
||||
|
||||
/// Fade-in curve. Defaults to [Curves.easeIn]
|
||||
final Curve curve;
|
||||
|
||||
const FadeIn({
|
||||
Key? key,
|
||||
required this.controller,
|
||||
required this.child,
|
||||
this.duration = const Duration(milliseconds: 250),
|
||||
this.curve = Curves.easeIn,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_FadeInState createState() => _FadeInState();
|
||||
}
|
||||
|
||||
enum FadeInAction {
|
||||
fadeIn,
|
||||
fadeOut,
|
||||
}
|
||||
|
||||
/// Fade-in controller which dispatches fade-in/fade-out actions
|
||||
class FadeInController {
|
||||
final _streamController = StreamController<FadeInAction>();
|
||||
|
||||
/// Automatically starts the initial fade-in. Defaults to true
|
||||
final bool autoStart;
|
||||
|
||||
FadeInController({this.autoStart = true});
|
||||
|
||||
void dispose() => _streamController.close();
|
||||
|
||||
/// Fades-in child
|
||||
void fadeIn() => run(FadeInAction.fadeIn);
|
||||
|
||||
/// Fades-out child
|
||||
void fadeOut() => run(FadeInAction.fadeOut);
|
||||
|
||||
/// Dispatches a [FadeInAction]
|
||||
void run(FadeInAction action) => _streamController.add(action);
|
||||
|
||||
/// Stream of [FadeInAction]s dispatched by this controller
|
||||
Stream<FadeInAction> get stream => _streamController.stream;
|
||||
}
|
||||
|
||||
class _FadeInState extends State<FadeIn> with TickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late StreamSubscription<FadeInAction> _listener;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: widget.duration,
|
||||
);
|
||||
|
||||
_setupCurve();
|
||||
|
||||
if (widget.controller.autoStart != false) {
|
||||
fadeIn();
|
||||
}
|
||||
|
||||
_listener = widget.controller.stream.listen(_onAction);
|
||||
}
|
||||
|
||||
void _setupCurve() {
|
||||
final curve = CurvedAnimation(parent: _controller, curve: widget.curve);
|
||||
|
||||
Tween(
|
||||
begin: 0.0,
|
||||
end: 1.0,
|
||||
).animate(curve);
|
||||
}
|
||||
|
||||
void _onAction(FadeInAction action) {
|
||||
switch (action) {
|
||||
case FadeInAction.fadeIn:
|
||||
fadeIn();
|
||||
break;
|
||||
case FadeInAction.fadeOut:
|
||||
fadeOut();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(FadeIn oldWidget) {
|
||||
if (oldWidget.controller != widget.controller) {
|
||||
_listener = widget.controller.stream.listen(_onAction);
|
||||
}
|
||||
|
||||
if (oldWidget.duration != widget.duration) {
|
||||
_controller.duration = widget.duration;
|
||||
}
|
||||
|
||||
if (oldWidget.curve != widget.curve) {
|
||||
_setupCurve();
|
||||
}
|
||||
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
_listener.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FadeTransition(
|
||||
opacity: _controller,
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
/// Fades-in child
|
||||
void fadeIn() => _controller.forward();
|
||||
|
||||
/// Fades-out child
|
||||
void fadeOut() => _controller.reverse();
|
||||
}
|
@ -59,9 +59,9 @@ class _LiquidLinearProgressIndicatorState extends State<LiquidLinearProgressIndi
|
||||
radius: widget.borderRadius!,
|
||||
),
|
||||
foregroundPainter: _LinearBorderPainter(
|
||||
color: widget.borderColor!,
|
||||
width: widget.borderWidth!,
|
||||
radius: widget.borderRadius!,
|
||||
color: widget.borderColor != null ? widget.borderColor! : Colors.transparent,
|
||||
width: widget.borderWidth != null ? widget.borderWidth! : 1,
|
||||
radius: widget.borderRadius != null ? widget.borderRadius! : 12,
|
||||
),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
|
973
lib/library/super_tooltip.dart
Normal file
@ -0,0 +1,973 @@
|
||||
import 'dart:math';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
enum TooltipDirection { up, down, left, right }
|
||||
enum ShowCloseButton { inside, outside, none }
|
||||
enum ClipAreaShape { oval, rectangle }
|
||||
|
||||
typedef OutSideTapHandler = void Function();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// Super flexible Tooltip class that allows you to show any content
|
||||
/// inside a Tooltip in the overlay of the screen.
|
||||
///
|
||||
class SuperTooltip {
|
||||
/// Allows to accedd the closebutton for UI Testing
|
||||
static Key closeButtonKey = const Key("CloseButtonKey");
|
||||
|
||||
/// Signals if the Tooltip is visible at the moment
|
||||
bool isOpen = false;
|
||||
|
||||
///
|
||||
/// The content of the Tooltip
|
||||
final Widget content;
|
||||
|
||||
///
|
||||
/// The direcion in which the tooltip should open
|
||||
TooltipDirection popupDirection;
|
||||
|
||||
///
|
||||
/// optional handler that gets called when the Tooltip is closed
|
||||
final OutSideTapHandler? onClose;
|
||||
|
||||
///
|
||||
/// [minWidth], [minHeight], [maxWidth], [maxHeight] optional size constraints.
|
||||
/// If a constraint is not set the size will ajust to the content
|
||||
double? minWidth, minHeight, maxWidth, maxHeight;
|
||||
|
||||
///
|
||||
/// The minium padding from the Tooltip to the screen limits
|
||||
final double minimumOutSidePadding;
|
||||
|
||||
///
|
||||
/// If [snapsFarAwayVertically== true] the bigger free space above or below the target will be
|
||||
/// covered completely by the ToolTip. All other dimension or position constraints get overwritten
|
||||
final bool snapsFarAwayVertically;
|
||||
|
||||
///
|
||||
/// If [snapsFarAwayHorizontally== true] the bigger free space left or right of the target will be
|
||||
/// covered completely by the ToolTip. All other dimension or position constraints get overwritten
|
||||
final bool snapsFarAwayHorizontally;
|
||||
|
||||
/// [top], [right], [bottom], [left] position the Tooltip absolute relative to the whole screen
|
||||
double? top, right, bottom, left;
|
||||
|
||||
///
|
||||
/// A Tooltip can have none, an inside or an outside close icon
|
||||
final ShowCloseButton showCloseButton;
|
||||
|
||||
///
|
||||
/// [hasShadow] defines if the tooltip should have a shadow
|
||||
final bool hasShadow;
|
||||
|
||||
///
|
||||
/// The shadow color.
|
||||
final Color shadowColor;
|
||||
|
||||
///
|
||||
/// The shadow blur radius.
|
||||
final double shadowBlurRadius;
|
||||
|
||||
///
|
||||
/// The shadow spread radius.
|
||||
final double shadowSpreadRadius;
|
||||
|
||||
///
|
||||
/// the stroke width of the border
|
||||
final double borderWidth;
|
||||
|
||||
///
|
||||
/// The corder radii of the border
|
||||
final double borderRadius;
|
||||
|
||||
///
|
||||
/// The color of the border
|
||||
final Color borderColor;
|
||||
|
||||
///
|
||||
/// The color of the close icon
|
||||
final Color closeButtonColor;
|
||||
|
||||
///
|
||||
/// The size of the close button
|
||||
final double closeButtonSize;
|
||||
|
||||
///
|
||||
/// The icon for the close button
|
||||
final IconData closeButtonIcon;
|
||||
|
||||
///
|
||||
/// The length of the Arrow
|
||||
final double arrowLength;
|
||||
|
||||
///
|
||||
/// The width of the arrow at its base
|
||||
final double arrowBaseWidth;
|
||||
|
||||
///
|
||||
/// The distance of the tip of the arrow's tip to the center of the target
|
||||
final double arrowTipDistance;
|
||||
|
||||
///
|
||||
/// The backgroundcolor of the Tooltip
|
||||
final Color backgroundColor;
|
||||
|
||||
/// The color of the rest of the overlay surrounding the Tooltip.
|
||||
/// typically a translucent color.
|
||||
final Color outsideBackgroundColor;
|
||||
|
||||
///
|
||||
/// By default touching the surrounding of the Tooltip closes the tooltip.
|
||||
/// you can define a rectangle area where the background is completely transparent
|
||||
/// and the widgets below react to touch
|
||||
final Rect? touchThrougArea;
|
||||
|
||||
///
|
||||
/// The shape of the [touchThrougArea].
|
||||
final ClipAreaShape touchThroughAreaShape;
|
||||
|
||||
///
|
||||
/// If [touchThroughAreaShape] is [ClipAreaShape.rectangle] you can define a border radius
|
||||
final double touchThroughAreaCornerRadius;
|
||||
|
||||
///
|
||||
/// Let's you pass a key to the Tooltips cotainer for UI Testing
|
||||
final Key? tooltipContainerKey;
|
||||
|
||||
///
|
||||
/// Allow the tooltip to be dismissed tapping outside
|
||||
final bool dismissOnTapOutside;
|
||||
|
||||
///
|
||||
/// Enable background overlay
|
||||
final bool containsBackgroundOverlay;
|
||||
|
||||
final bool custom;
|
||||
|
||||
Offset? _targetCenter;
|
||||
OverlayEntry? _backGroundOverlay;
|
||||
OverlayEntry? _ballonOverlay;
|
||||
|
||||
SuperTooltip({
|
||||
this.tooltipContainerKey,
|
||||
required this.content, // The contents of the tooltip.
|
||||
required this.popupDirection,
|
||||
this.onClose,
|
||||
this.minWidth,
|
||||
this.minHeight,
|
||||
this.maxWidth,
|
||||
this.maxHeight,
|
||||
this.top,
|
||||
this.right,
|
||||
this.bottom,
|
||||
this.left,
|
||||
this.minimumOutSidePadding = 20.0,
|
||||
this.showCloseButton = ShowCloseButton.none,
|
||||
this.snapsFarAwayVertically = false,
|
||||
this.snapsFarAwayHorizontally = false,
|
||||
this.hasShadow = true,
|
||||
this.shadowColor = Colors.black54,
|
||||
this.shadowBlurRadius = 10.0,
|
||||
this.shadowSpreadRadius = 5.0,
|
||||
this.borderWidth = 2.0,
|
||||
this.borderRadius = 10.0,
|
||||
this.borderColor = Colors.black,
|
||||
this.closeButtonIcon = Icons.close,
|
||||
this.closeButtonColor = Colors.black,
|
||||
this.closeButtonSize = 30.0,
|
||||
this.arrowLength = 20.0,
|
||||
this.arrowBaseWidth = 20.0,
|
||||
this.arrowTipDistance = 2.0,
|
||||
this.backgroundColor = Colors.white,
|
||||
this.outsideBackgroundColor = const Color.fromARGB(50, 255, 255, 255),
|
||||
this.touchThroughAreaShape = ClipAreaShape.oval,
|
||||
this.touchThroughAreaCornerRadius = 5.0,
|
||||
this.touchThrougArea,
|
||||
this.dismissOnTapOutside = true,
|
||||
this.containsBackgroundOverlay = true,
|
||||
this.custom = false,
|
||||
}) : assert((maxWidth ?? double.infinity) >= (minWidth ?? 0.0)),
|
||||
assert((maxHeight ?? double.infinity) >= (minHeight ?? 0.0));
|
||||
|
||||
///
|
||||
/// Removes the Tooltip from the overlay
|
||||
void close() {
|
||||
if (onClose != null) {
|
||||
onClose!();
|
||||
}
|
||||
|
||||
_ballonOverlay!.remove();
|
||||
_backGroundOverlay?.remove();
|
||||
isOpen = false;
|
||||
}
|
||||
|
||||
void rebuild() {
|
||||
_ballonOverlay!.remove();
|
||||
_backGroundOverlay?.remove();
|
||||
isOpen = false;
|
||||
}
|
||||
|
||||
///
|
||||
/// Displays the tooltip
|
||||
/// The center of [targetContext] is used as target of the arrow
|
||||
void show(BuildContext targetContext) {
|
||||
final renderBox = targetContext.findRenderObject() as RenderBox;
|
||||
final overlay = Overlay.of(targetContext)!.context.findRenderObject() as RenderBox?;
|
||||
|
||||
_targetCenter = renderBox.localToGlobal(renderBox.size.center(Offset.zero), ancestor: overlay);
|
||||
|
||||
// Create the background below the popup including the clipArea.
|
||||
if (containsBackgroundOverlay) {
|
||||
_backGroundOverlay = OverlayEntry(
|
||||
builder: (context) => _AnimationWrapper(
|
||||
builder: (context, opacity) => AnimatedOpacity(
|
||||
opacity: opacity,
|
||||
duration: const Duration(milliseconds: 600),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (dismissOnTapOutside) {
|
||||
close();
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
decoration: ShapeDecoration(
|
||||
shape: _ShapeOverlay(
|
||||
touchThrougArea, touchThroughAreaShape, touchThroughAreaCornerRadius, outsideBackgroundColor))),
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
/// Handling snap far away feature.
|
||||
if (snapsFarAwayVertically) {
|
||||
maxHeight = null;
|
||||
left = 0.0;
|
||||
right = 0.0;
|
||||
if (_targetCenter!.dy > overlay!.size.center(Offset.zero).dy) {
|
||||
popupDirection = TooltipDirection.up;
|
||||
top = 0.0;
|
||||
} else {
|
||||
popupDirection = TooltipDirection.down;
|
||||
bottom = 0.0;
|
||||
}
|
||||
} // Only one of of them is possible, and vertical has higher priority.
|
||||
else if (snapsFarAwayHorizontally) {
|
||||
maxWidth = null;
|
||||
top = 0.0;
|
||||
bottom = 0.0;
|
||||
if (_targetCenter!.dx < overlay!.size.center(Offset.zero).dx) {
|
||||
popupDirection = TooltipDirection.right;
|
||||
right = 0.0;
|
||||
} else {
|
||||
popupDirection = TooltipDirection.left;
|
||||
left = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
_ballonOverlay = OverlayEntry(
|
||||
builder: (context) => _AnimationWrapper(
|
||||
builder: (context, opacity) => AnimatedOpacity(
|
||||
duration: Duration(
|
||||
milliseconds: 300,
|
||||
),
|
||||
opacity: opacity,
|
||||
child: Center(
|
||||
child: CustomSingleChildLayout(
|
||||
delegate: _PopupBallonLayoutDelegate(
|
||||
popupDirection: popupDirection,
|
||||
targetCenter: _targetCenter,
|
||||
minWidth: minWidth,
|
||||
maxWidth: maxWidth,
|
||||
minHeight: minHeight,
|
||||
maxHeight: maxHeight,
|
||||
outSidePadding: minimumOutSidePadding,
|
||||
top: top,
|
||||
bottom: bottom,
|
||||
left: left,
|
||||
right: right,
|
||||
),
|
||||
child: Stack(
|
||||
fit: StackFit.passthrough,
|
||||
children: [_buildPopUp(), _buildCloseButton()],
|
||||
))),
|
||||
),
|
||||
));
|
||||
|
||||
var overlays = <OverlayEntry>[];
|
||||
|
||||
if (containsBackgroundOverlay) {
|
||||
overlays.add(_backGroundOverlay!);
|
||||
}
|
||||
overlays.add(_ballonOverlay!);
|
||||
|
||||
Overlay.of(targetContext)!.insertAll(overlays);
|
||||
isOpen = true;
|
||||
}
|
||||
|
||||
Widget _buildPopUp() {
|
||||
return Positioned(
|
||||
child: Container(
|
||||
key: tooltipContainerKey,
|
||||
decoration: ShapeDecoration(
|
||||
color: backgroundColor,
|
||||
shadows: hasShadow ? [BoxShadow(color: shadowColor, blurRadius: shadowBlurRadius, spreadRadius: shadowSpreadRadius)] : null,
|
||||
shape: !custom
|
||||
? _BubbleShape(popupDirection, _targetCenter, borderRadius, arrowBaseWidth, arrowTipDistance, borderColor, borderWidth,
|
||||
left, top, right, bottom)
|
||||
: RoundedRectangleBorder(
|
||||
side: BorderSide(color: borderColor, width: borderWidth),
|
||||
borderRadius: BorderRadius.all(Radius.circular(borderRadius)))),
|
||||
margin: _getBallonContainerMargin(),
|
||||
child: content,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCloseButton() {
|
||||
const internalClickAreaPadding = 2.0;
|
||||
|
||||
//
|
||||
if (showCloseButton == ShowCloseButton.none) {
|
||||
return new SizedBox();
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
double right;
|
||||
double top;
|
||||
|
||||
switch (popupDirection) {
|
||||
//
|
||||
// LEFT: -------------------------------------
|
||||
case TooltipDirection.left:
|
||||
right = arrowLength + arrowTipDistance + 3.0;
|
||||
if (showCloseButton == ShowCloseButton.inside) {
|
||||
top = 2.0;
|
||||
} else if (showCloseButton == ShowCloseButton.outside) {
|
||||
top = 0.0;
|
||||
} else
|
||||
throw AssertionError(showCloseButton);
|
||||
break;
|
||||
|
||||
// RIGHT/UP: ---------------------------------
|
||||
case TooltipDirection.right:
|
||||
case TooltipDirection.up:
|
||||
right = 5.0;
|
||||
if (showCloseButton == ShowCloseButton.inside) {
|
||||
top = 2.0;
|
||||
} else if (showCloseButton == ShowCloseButton.outside) {
|
||||
top = 0.0;
|
||||
} else
|
||||
throw AssertionError(showCloseButton);
|
||||
break;
|
||||
|
||||
// DOWN: -------------------------------------
|
||||
case TooltipDirection.down:
|
||||
// If this value gets negative the Shadow gets clipped. The problem occurs is arrowlength + arrowTipDistance
|
||||
// is smaller than _outSideCloseButtonPadding which would mean arrowLength would need to be increased if the button is ouside.
|
||||
right = 2.0;
|
||||
if (showCloseButton == ShowCloseButton.inside) {
|
||||
top = arrowLength + arrowTipDistance + 2.0;
|
||||
} else if (showCloseButton == ShowCloseButton.outside) {
|
||||
top = 0.0;
|
||||
} else
|
||||
throw AssertionError(showCloseButton);
|
||||
break;
|
||||
|
||||
// ---------------------------------------------
|
||||
|
||||
default:
|
||||
throw AssertionError(popupDirection);
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
return Positioned(
|
||||
right: right,
|
||||
top: top,
|
||||
child: GestureDetector(
|
||||
onTap: close,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(internalClickAreaPadding),
|
||||
child: Icon(
|
||||
closeButtonIcon,
|
||||
size: closeButtonSize,
|
||||
color: closeButtonColor,
|
||||
),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
EdgeInsets _getBallonContainerMargin() {
|
||||
var top = (showCloseButton == ShowCloseButton.outside) ? closeButtonSize + 5 : 0.0;
|
||||
|
||||
switch (popupDirection) {
|
||||
//
|
||||
case TooltipDirection.down:
|
||||
return EdgeInsets.only(
|
||||
top: arrowTipDistance + arrowLength,
|
||||
);
|
||||
|
||||
case TooltipDirection.up:
|
||||
return EdgeInsets.only(bottom: arrowTipDistance + arrowLength, top: top);
|
||||
|
||||
case TooltipDirection.left:
|
||||
return EdgeInsets.only(right: arrowTipDistance + arrowLength, top: top);
|
||||
|
||||
case TooltipDirection.right:
|
||||
return EdgeInsets.only(left: arrowTipDistance + arrowLength, top: top);
|
||||
|
||||
default:
|
||||
throw AssertionError(popupDirection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _CustomBallonLayoutDelegate extends SingleChildLayoutDelegate {
|
||||
final TooltipDirection? _popupDirection;
|
||||
final Offset? _targetCenter;
|
||||
final double? _minWidth;
|
||||
final double? _maxWidth;
|
||||
final double? _minHeight;
|
||||
final double? _maxHeight;
|
||||
final double _top;
|
||||
final double? _bottom;
|
||||
final double _left;
|
||||
final double? _right;
|
||||
final double? _outSidePadding;
|
||||
|
||||
_CustomBallonLayoutDelegate({
|
||||
TooltipDirection? popupDirection,
|
||||
Offset? targetCenter,
|
||||
double? minWidth,
|
||||
double? maxWidth,
|
||||
double? minHeight,
|
||||
double? maxHeight,
|
||||
double? outSidePadding,
|
||||
required double top,
|
||||
double? bottom,
|
||||
required double left,
|
||||
double? right,
|
||||
}) : _targetCenter = targetCenter,
|
||||
_popupDirection = popupDirection,
|
||||
_minWidth = minWidth,
|
||||
_maxWidth = maxWidth,
|
||||
_minHeight = minHeight,
|
||||
_maxHeight = maxHeight,
|
||||
_top = top,
|
||||
_bottom = bottom,
|
||||
_left = left,
|
||||
_right = right,
|
||||
_outSidePadding = outSidePadding;
|
||||
|
||||
@override
|
||||
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
|
||||
return super.getConstraintsForChild(constraints);
|
||||
}
|
||||
|
||||
@override
|
||||
Offset getPositionForChild(Size size, Size childSize) {
|
||||
print(" ------ getPositionFroChild: $_top - $_left");
|
||||
//we place the widget at the cnter, by dividing the width and height by 2 to get the center
|
||||
return Offset(_left, _top);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRelayout(covariant SingleChildLayoutDelegate oldDelegate) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class _PopupBallonLayoutDelegate extends SingleChildLayoutDelegate {
|
||||
final TooltipDirection? _popupDirection;
|
||||
final Offset? _targetCenter;
|
||||
final double? _minWidth;
|
||||
final double? _maxWidth;
|
||||
final double? _minHeight;
|
||||
final double? _maxHeight;
|
||||
final double? _top;
|
||||
final double? _bottom;
|
||||
final double? _left;
|
||||
final double? _right;
|
||||
final double? _outSidePadding;
|
||||
|
||||
_PopupBallonLayoutDelegate({
|
||||
TooltipDirection? popupDirection,
|
||||
Offset? targetCenter,
|
||||
double? minWidth,
|
||||
double? maxWidth,
|
||||
double? minHeight,
|
||||
double? maxHeight,
|
||||
double? outSidePadding,
|
||||
double? top,
|
||||
double? bottom,
|
||||
double? left,
|
||||
double? right,
|
||||
}) : _targetCenter = targetCenter,
|
||||
_popupDirection = popupDirection,
|
||||
_minWidth = minWidth,
|
||||
_maxWidth = maxWidth,
|
||||
_minHeight = minHeight,
|
||||
_maxHeight = maxHeight,
|
||||
_top = top,
|
||||
_bottom = bottom,
|
||||
_left = left,
|
||||
_right = right,
|
||||
_outSidePadding = outSidePadding;
|
||||
|
||||
@override
|
||||
Offset getPositionForChild(Size size, Size childSize) {
|
||||
double? calcLeftMostXtoTarget() {
|
||||
double? leftMostXtoTarget;
|
||||
if (_left != null) {
|
||||
leftMostXtoTarget = _left;
|
||||
} else if (_right != null) {
|
||||
leftMostXtoTarget = max(
|
||||
size.topLeft(Offset.zero).dx + _outSidePadding!, size.topRight(Offset.zero).dx - _outSidePadding! - childSize.width - _right!);
|
||||
} else {
|
||||
leftMostXtoTarget = max(_outSidePadding!,
|
||||
min(_targetCenter!.dx - childSize.width / 2, size.topRight(Offset.zero).dx - _outSidePadding! - childSize.width));
|
||||
}
|
||||
return leftMostXtoTarget;
|
||||
}
|
||||
|
||||
double? calcTopMostYtoTarget() {
|
||||
double? topmostYtoTarget;
|
||||
if (_top != null) {
|
||||
topmostYtoTarget = _top!;
|
||||
} else if (_bottom != null) {
|
||||
topmostYtoTarget = max(size.topLeft(Offset.zero).dy + _outSidePadding!,
|
||||
size.bottomRight(Offset.zero).dy - _outSidePadding! - childSize.height - _bottom!);
|
||||
} else {
|
||||
topmostYtoTarget = max(_outSidePadding!,
|
||||
min(_targetCenter!.dy - childSize.height / 2, size.bottomRight(Offset.zero).dy - _outSidePadding! - childSize.height));
|
||||
}
|
||||
return topmostYtoTarget;
|
||||
}
|
||||
|
||||
switch (_popupDirection) {
|
||||
//
|
||||
case TooltipDirection.down:
|
||||
return new Offset(calcLeftMostXtoTarget()!, _targetCenter!.dy);
|
||||
|
||||
case TooltipDirection.up:
|
||||
var top = _top ?? _targetCenter!.dy - childSize.height;
|
||||
return new Offset(calcLeftMostXtoTarget()!, top);
|
||||
|
||||
case TooltipDirection.left:
|
||||
var left = _left ?? _targetCenter!.dx - childSize.width;
|
||||
return new Offset(left, calcTopMostYtoTarget()!);
|
||||
|
||||
case TooltipDirection.right:
|
||||
return new Offset(
|
||||
_targetCenter!.dx,
|
||||
calcTopMostYtoTarget()!,
|
||||
);
|
||||
|
||||
default:
|
||||
throw AssertionError(_popupDirection);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
|
||||
// print("ParentConstraints: $constraints");
|
||||
|
||||
var calcMinWidth = _minWidth ?? 0.0;
|
||||
var calcMaxWidth = _maxWidth ?? double.infinity;
|
||||
var calcMinHeight = _minHeight ?? 0.0;
|
||||
var calcMaxHeight = _maxHeight ?? double.infinity;
|
||||
|
||||
void calcMinMaxWidth() {
|
||||
if (_left != null && _right != null) {
|
||||
calcMaxWidth = constraints.maxWidth - (_left! + _right!);
|
||||
} else if ((_left != null && _right == null) || (_left == null && _right != null)) {
|
||||
// make sure that the sum of left, right + maxwidth isn't bigger than the screen width.
|
||||
var sideDelta = (_left ?? 0.0) + (_right ?? 0.0) + _outSidePadding!;
|
||||
if (calcMaxWidth > constraints.maxWidth - sideDelta) {
|
||||
calcMaxWidth = constraints.maxWidth - sideDelta;
|
||||
}
|
||||
} else {
|
||||
if (calcMaxWidth > constraints.maxWidth - 2 * _outSidePadding!) {
|
||||
calcMaxWidth = constraints.maxWidth - 2 * _outSidePadding!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void calcMinMaxHeight() {
|
||||
if (_top != null && _bottom != null) {
|
||||
calcMaxHeight = constraints.maxHeight - (_top! + _bottom!);
|
||||
} else if ((_top != null && _bottom == null) || (_top == null && _bottom != null)) {
|
||||
// make sure that the sum of top, bottom + maxHeight isn't bigger than the screen Height.
|
||||
var sideDelta = (_top ?? 0.0) + (_bottom ?? 0.0) + _outSidePadding!;
|
||||
if (calcMaxHeight > constraints.maxHeight - sideDelta) {
|
||||
calcMaxHeight = constraints.maxHeight - sideDelta;
|
||||
}
|
||||
} else {
|
||||
if (calcMaxHeight > constraints.maxHeight - 2 * _outSidePadding!) {
|
||||
calcMaxHeight = constraints.maxHeight - 2 * _outSidePadding!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (_popupDirection) {
|
||||
//
|
||||
case TooltipDirection.down:
|
||||
calcMinMaxWidth();
|
||||
if (_bottom != null) {
|
||||
calcMinHeight = calcMaxHeight = constraints.maxHeight - _bottom! - _targetCenter!.dy;
|
||||
} else {
|
||||
calcMaxHeight = min((_maxHeight ?? constraints.maxHeight), constraints.maxHeight - _targetCenter!.dy) - _outSidePadding!;
|
||||
}
|
||||
break;
|
||||
|
||||
case TooltipDirection.up:
|
||||
calcMinMaxWidth();
|
||||
|
||||
if (_top != null) {
|
||||
calcMinHeight = calcMaxHeight = _targetCenter!.dy - _top!;
|
||||
} else {
|
||||
calcMaxHeight = min((_maxHeight ?? constraints.maxHeight), _targetCenter!.dy) - _outSidePadding!;
|
||||
}
|
||||
break;
|
||||
|
||||
case TooltipDirection.right:
|
||||
calcMinMaxHeight();
|
||||
if (_right != null) {
|
||||
calcMinWidth = calcMaxWidth = constraints.maxWidth - _right! - _targetCenter!.dx;
|
||||
} else {
|
||||
calcMaxWidth = min((_maxWidth ?? constraints.maxWidth), constraints.maxWidth - _targetCenter!.dx) - _outSidePadding!;
|
||||
}
|
||||
break;
|
||||
|
||||
case TooltipDirection.left:
|
||||
calcMinMaxHeight();
|
||||
if (_left != null) {
|
||||
calcMinWidth = calcMaxWidth = _targetCenter!.dx - _left!;
|
||||
} else {
|
||||
calcMaxWidth = min((_maxWidth ?? constraints.maxWidth), _targetCenter!.dx) - _outSidePadding!;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw AssertionError(_popupDirection);
|
||||
}
|
||||
|
||||
var childConstraints = new BoxConstraints(
|
||||
minWidth: calcMinWidth > calcMaxWidth ? calcMaxWidth : calcMinWidth,
|
||||
maxWidth: calcMaxWidth,
|
||||
minHeight: calcMinHeight > calcMaxHeight ? calcMaxHeight : calcMinHeight,
|
||||
maxHeight: calcMaxHeight);
|
||||
|
||||
// print("Child constraints: $childConstraints");
|
||||
|
||||
return childConstraints;
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRelayout(SingleChildLayoutDelegate oldDelegate) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class _BubbleShape extends ShapeBorder {
|
||||
final Offset? targetCenter;
|
||||
final double arrowBaseWidth;
|
||||
final double arrowTipDistance;
|
||||
final double borderRadius;
|
||||
final Color borderColor;
|
||||
final double borderWidth;
|
||||
final double? left, top, right, bottom;
|
||||
final TooltipDirection popupDirection;
|
||||
|
||||
_BubbleShape(this.popupDirection, this.targetCenter, this.borderRadius, this.arrowBaseWidth, this.arrowTipDistance, this.borderColor,
|
||||
this.borderWidth, this.left, this.top, this.right, this.bottom);
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry get dimensions => new EdgeInsets.all(10.0);
|
||||
|
||||
@override
|
||||
Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
|
||||
return new Path()
|
||||
..fillType = PathFillType.evenOdd
|
||||
..addPath(getOuterPath(rect), Offset.zero);
|
||||
}
|
||||
|
||||
@override
|
||||
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
|
||||
//
|
||||
late double topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius;
|
||||
|
||||
Path _getLeftTopPath(Rect rect) {
|
||||
return new Path()
|
||||
..moveTo(rect.left, rect.bottom - bottomLeftRadius)
|
||||
..lineTo(rect.left, rect.top + topLeftRadius)
|
||||
..arcToPoint(Offset(rect.left + topLeftRadius, rect.top), radius: new Radius.circular(topLeftRadius))
|
||||
..lineTo(rect.right - topRightRadius, rect.top)
|
||||
..arcToPoint(Offset(rect.right, rect.top + topRightRadius), radius: new Radius.circular(topRightRadius), clockwise: true);
|
||||
}
|
||||
|
||||
Path _getBottomRightPath(Rect rect) {
|
||||
return new Path()
|
||||
..moveTo(rect.left + bottomLeftRadius, rect.bottom)
|
||||
..lineTo(rect.right - bottomRightRadius, rect.bottom)
|
||||
..arcToPoint(Offset(rect.right, rect.bottom - bottomRightRadius), radius: new Radius.circular(bottomRightRadius), clockwise: false)
|
||||
..lineTo(rect.right, rect.top + topRightRadius)
|
||||
..arcToPoint(Offset(rect.right - topRightRadius, rect.top), radius: new Radius.circular(topRightRadius), clockwise: false);
|
||||
}
|
||||
|
||||
topLeftRadius = (left == 0 || top == 0) ? 0.0 : borderRadius;
|
||||
topRightRadius = (right == 0 || top == 0) ? 0.0 : borderRadius;
|
||||
bottomLeftRadius = (left == 0 || bottom == 0) ? 0.0 : borderRadius;
|
||||
bottomRightRadius = (right == 0 || bottom == 0) ? 0.0 : borderRadius;
|
||||
|
||||
switch (popupDirection) {
|
||||
//
|
||||
|
||||
case TooltipDirection.down:
|
||||
return _getBottomRightPath(rect)
|
||||
..lineTo(min(max(targetCenter!.dx + arrowBaseWidth / 2, rect.left + borderRadius + arrowBaseWidth), rect.right - topRightRadius),
|
||||
rect.top)
|
||||
..lineTo(targetCenter!.dx, targetCenter!.dy + arrowTipDistance) // up to arrow tip \
|
||||
..lineTo(max(min(targetCenter!.dx - arrowBaseWidth / 2, rect.right - topLeftRadius - arrowBaseWidth), rect.left + topLeftRadius),
|
||||
rect.top) // down /
|
||||
|
||||
..lineTo(rect.left + topLeftRadius, rect.top)
|
||||
..arcToPoint(Offset(rect.left, rect.top + topLeftRadius), radius: new Radius.circular(topLeftRadius), clockwise: false)
|
||||
..lineTo(rect.left, rect.bottom - bottomLeftRadius)
|
||||
..arcToPoint(Offset(rect.left + bottomLeftRadius, rect.bottom), radius: new Radius.circular(bottomLeftRadius), clockwise: false);
|
||||
|
||||
case TooltipDirection.up:
|
||||
return _getLeftTopPath(rect)
|
||||
..lineTo(rect.right, rect.bottom - bottomRightRadius)
|
||||
..arcToPoint(Offset(rect.right - bottomRightRadius, rect.bottom), radius: new Radius.circular(bottomRightRadius), clockwise: true)
|
||||
..lineTo(
|
||||
min(max(targetCenter!.dx + arrowBaseWidth / 2, rect.left + bottomLeftRadius + arrowBaseWidth),
|
||||
rect.right - bottomRightRadius),
|
||||
rect.bottom)
|
||||
|
||||
// up to arrow tip \
|
||||
..lineTo(targetCenter!.dx, targetCenter!.dy - arrowTipDistance)
|
||||
|
||||
// down /
|
||||
..lineTo(
|
||||
max(min(targetCenter!.dx - arrowBaseWidth / 2, rect.right - bottomRightRadius - arrowBaseWidth),
|
||||
rect.left + bottomLeftRadius),
|
||||
rect.bottom)
|
||||
..lineTo(rect.left + bottomLeftRadius, rect.bottom)
|
||||
..arcToPoint(Offset(rect.left, rect.bottom - bottomLeftRadius), radius: new Radius.circular(bottomLeftRadius), clockwise: true)
|
||||
..lineTo(rect.left, rect.top + topLeftRadius)
|
||||
..arcToPoint(Offset(rect.left + topLeftRadius, rect.top), radius: new Radius.circular(topLeftRadius), clockwise: true);
|
||||
|
||||
case TooltipDirection.left:
|
||||
return _getLeftTopPath(rect)
|
||||
..lineTo(rect.right,
|
||||
max(min(targetCenter!.dy - arrowBaseWidth / 2, rect.bottom - bottomRightRadius - arrowBaseWidth), rect.top + topRightRadius))
|
||||
..lineTo(targetCenter!.dx - arrowTipDistance, targetCenter!.dy) // right to arrow tip \
|
||||
// left /
|
||||
..lineTo(rect.right, min(targetCenter!.dy + arrowBaseWidth / 2, rect.bottom - bottomRightRadius))
|
||||
..lineTo(rect.right, rect.bottom - borderRadius)
|
||||
..arcToPoint(Offset(rect.right - bottomRightRadius, rect.bottom), radius: new Radius.circular(bottomRightRadius), clockwise: true)
|
||||
..lineTo(rect.left + bottomLeftRadius, rect.bottom)
|
||||
..arcToPoint(Offset(rect.left, rect.bottom - bottomLeftRadius), radius: new Radius.circular(bottomLeftRadius), clockwise: true);
|
||||
|
||||
case TooltipDirection.right:
|
||||
return _getBottomRightPath(rect)
|
||||
..lineTo(rect.left + topLeftRadius, rect.top)
|
||||
..arcToPoint(Offset(rect.left, rect.top + topLeftRadius), radius: new Radius.circular(topLeftRadius), clockwise: false)
|
||||
..lineTo(rect.left,
|
||||
max(min(targetCenter!.dy - arrowBaseWidth / 2, rect.bottom - bottomLeftRadius - arrowBaseWidth), rect.top + topLeftRadius))
|
||||
|
||||
//left to arrow tip /
|
||||
..lineTo(targetCenter!.dx + arrowTipDistance, targetCenter!.dy)
|
||||
|
||||
// right \
|
||||
..lineTo(rect.left, min(targetCenter!.dy + arrowBaseWidth / 2, rect.bottom - bottomLeftRadius))
|
||||
..lineTo(rect.left, rect.bottom - bottomLeftRadius)
|
||||
..arcToPoint(Offset(rect.left + bottomLeftRadius, rect.bottom), radius: new Radius.circular(bottomLeftRadius), clockwise: false);
|
||||
|
||||
default:
|
||||
throw AssertionError(popupDirection);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {
|
||||
var paint = new Paint()
|
||||
..color = borderColor
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = borderWidth;
|
||||
|
||||
canvas.drawPath(getOuterPath(rect), paint);
|
||||
paint = new Paint()
|
||||
..color = Colors.white
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = borderWidth;
|
||||
|
||||
if (right == 0.0) {
|
||||
if (top == 0.0 && bottom == 0.0) {
|
||||
canvas.drawPath(
|
||||
new Path()
|
||||
..moveTo(rect.right, rect.top)
|
||||
..lineTo(rect.right, rect.bottom),
|
||||
paint);
|
||||
} else {
|
||||
canvas.drawPath(
|
||||
new Path()
|
||||
..moveTo(rect.right, rect.top + borderWidth / 2)
|
||||
..lineTo(rect.right, rect.bottom - borderWidth / 2),
|
||||
paint);
|
||||
}
|
||||
}
|
||||
if (left == 0.0) {
|
||||
if (top == 0.0 && bottom == 0.0) {
|
||||
canvas.drawPath(
|
||||
new Path()
|
||||
..moveTo(rect.left, rect.top)
|
||||
..lineTo(rect.left, rect.bottom),
|
||||
paint);
|
||||
} else {
|
||||
canvas.drawPath(
|
||||
new Path()
|
||||
..moveTo(rect.left, rect.top + borderWidth / 2)
|
||||
..lineTo(rect.left, rect.bottom - borderWidth / 2),
|
||||
paint);
|
||||
}
|
||||
}
|
||||
if (top == 0.0) {
|
||||
if (left == 0.0 && right == 0.0) {
|
||||
canvas.drawPath(
|
||||
new Path()
|
||||
..moveTo(rect.right, rect.top)
|
||||
..lineTo(rect.left, rect.top),
|
||||
paint);
|
||||
} else {
|
||||
canvas.drawPath(
|
||||
new Path()
|
||||
..moveTo(rect.right - borderWidth / 2, rect.top)
|
||||
..lineTo(rect.left + borderWidth / 2, rect.top),
|
||||
paint);
|
||||
}
|
||||
}
|
||||
if (bottom == 0.0) {
|
||||
if (left == 0.0 && right == 0.0) {
|
||||
canvas.drawPath(
|
||||
new Path()
|
||||
..moveTo(rect.right, rect.bottom)
|
||||
..lineTo(rect.left, rect.bottom),
|
||||
paint);
|
||||
} else {
|
||||
canvas.drawPath(
|
||||
new Path()
|
||||
..moveTo(rect.right - borderWidth / 2, rect.bottom)
|
||||
..lineTo(rect.left + borderWidth / 2, rect.bottom),
|
||||
paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
ShapeBorder scale(double t) {
|
||||
return new _BubbleShape(
|
||||
popupDirection, targetCenter, borderRadius, arrowBaseWidth, arrowTipDistance, borderColor, borderWidth, left, top, right, bottom);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class _ShapeOverlay extends ShapeBorder {
|
||||
final Rect? clipRect;
|
||||
final Color outsideBackgroundColor;
|
||||
final ClipAreaShape clipAreaShape;
|
||||
final double clipAreaCornerRadius;
|
||||
|
||||
_ShapeOverlay(this.clipRect, this.clipAreaShape, this.clipAreaCornerRadius, this.outsideBackgroundColor);
|
||||
|
||||
@override
|
||||
EdgeInsetsGeometry get dimensions => new EdgeInsets.all(10.0);
|
||||
|
||||
@override
|
||||
Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
|
||||
return new Path()..addOval(clipRect!);
|
||||
}
|
||||
|
||||
@override
|
||||
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
|
||||
var outer = new Path()..addRect(rect);
|
||||
|
||||
if (clipRect == null) {
|
||||
return outer;
|
||||
}
|
||||
Path exclusion;
|
||||
if (clipAreaShape == ClipAreaShape.oval) {
|
||||
exclusion = new Path()..addOval(clipRect!);
|
||||
} else {
|
||||
exclusion = new Path()
|
||||
..moveTo(clipRect!.left + clipAreaCornerRadius, clipRect!.top)
|
||||
..lineTo(clipRect!.right - clipAreaCornerRadius, clipRect!.top)
|
||||
..arcToPoint(Offset(clipRect!.right, clipRect!.top + clipAreaCornerRadius), radius: new Radius.circular(clipAreaCornerRadius))
|
||||
..lineTo(clipRect!.right, clipRect!.bottom - clipAreaCornerRadius)
|
||||
..arcToPoint(Offset(clipRect!.right - clipAreaCornerRadius, clipRect!.bottom), radius: new Radius.circular(clipAreaCornerRadius))
|
||||
..lineTo(clipRect!.left + clipAreaCornerRadius, clipRect!.bottom)
|
||||
..arcToPoint(Offset(clipRect!.left, clipRect!.bottom - clipAreaCornerRadius), radius: new Radius.circular(clipAreaCornerRadius))
|
||||
..lineTo(clipRect!.left, clipRect!.top + clipAreaCornerRadius)
|
||||
..arcToPoint(Offset(clipRect!.left + clipAreaCornerRadius, clipRect!.top), radius: new Radius.circular(clipAreaCornerRadius))
|
||||
..close();
|
||||
}
|
||||
|
||||
return Path.combine(ui.PathOperation.difference, outer, exclusion);
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {
|
||||
canvas.drawPath(getOuterPath(rect), new Paint()..color = outsideBackgroundColor);
|
||||
}
|
||||
|
||||
@override
|
||||
ShapeBorder scale(double t) {
|
||||
return new _ShapeOverlay(clipRect, clipAreaShape, clipAreaCornerRadius, outsideBackgroundColor);
|
||||
}
|
||||
}
|
||||
|
||||
typedef FadeBuilder = Widget Function(BuildContext, double);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class _AnimationWrapper extends StatefulWidget {
|
||||
final FadeBuilder? builder;
|
||||
|
||||
_AnimationWrapper({this.builder});
|
||||
|
||||
@override
|
||||
_AnimationWrapperState createState() => new _AnimationWrapperState();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class _AnimationWrapperState extends State<_AnimationWrapper> {
|
||||
double opacity = 0.0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance!.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
opacity = 1.0;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.builder!(context, opacity);
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:aitrainer_app/bloc/test_set_execute/test_set_execute_bloc.dart';
|
||||
import 'package:aitrainer_app/bloc/tutorial/tutorial_bloc.dart';
|
||||
import 'package:aitrainer_app/push_notifications.dart';
|
||||
import 'package:aitrainer_app/repository/customer_repository.dart';
|
||||
import 'package:aitrainer_app/repository/workout_tree_repository.dart';
|
||||
@ -46,9 +47,9 @@ import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:aitrainer_app/util/app_localization.dart';
|
||||
import 'package:flutter_uxcam/flutter_uxcam.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:sentry/sentry.dart';
|
||||
import 'package:smartlook/smartlook.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'bloc/account/account_bloc.dart';
|
||||
import 'bloc/body_development/body_development_bloc.dart';
|
||||
import 'bloc/development_by_muscle/development_by_muscle_bloc.dart';
|
||||
@ -60,7 +61,7 @@ import 'bloc/settings/settings_bloc.dart';
|
||||
import 'bloc/timer/timer_bloc.dart';
|
||||
import 'model/cache.dart';
|
||||
|
||||
const dsn = 'https://5fac40cbfcfb4c15aa80c7a8638d7310@o418565.ingest.sentry.io/5322520';
|
||||
const dsn = 'https://0f635b7225564abc9089f8106f25eb5c@sentry.aitrainer.app/1';
|
||||
|
||||
/// Whether the VM is running in debug mode.
|
||||
///
|
||||
@ -88,6 +89,9 @@ Future<Null> _reportError(dynamic error, dynamic stackTrace) async {
|
||||
|
||||
print('Reporting to Sentry.io...');
|
||||
final String customerId = Cache().userLoggedIn != null ? Cache().userLoggedIn!.customerId.toString() : "0";
|
||||
Sentry.configureScope(
|
||||
(scope) => scope.user = SentryUser(id: customerId),
|
||||
);
|
||||
final String platform = Platform.isAndroid ? "Android" : "iOS";
|
||||
final String version = Cache().packageInfo != null ? Cache().packageInfo!.version + "+" + Cache().packageInfo!.buildNumber : "";
|
||||
final sentryId =
|
||||
@ -121,9 +125,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(
|
||||
await SentryFlutter.init(
|
||||
(options) {
|
||||
options.dsn = dsn;
|
||||
options.release = Cache().packageInfo != null ? Cache().packageInfo!.version + "+" + Cache().packageInfo!.buildNumber : "";
|
||||
options.enableAutoSessionTracking = true;
|
||||
},
|
||||
);
|
||||
final WorkoutTreeRepository menuTreeRepository = WorkoutTreeRepository();
|
||||
@ -164,6 +170,7 @@ Future<Null> main() async {
|
||||
BlocProvider<TestSetExecuteBloc>(
|
||||
create: (BuildContext context) => TestSetExecuteBloc(),
|
||||
),
|
||||
BlocProvider<TutorialBloc>(create: (BuildContext context) => TutorialBloc(tutorialName: ActivityDone.tutorialBasic.toStr())),
|
||||
],
|
||||
child: WorkoutTestApp(),
|
||||
));
|
||||
@ -176,14 +183,18 @@ Future<void> initFlurry() async {
|
||||
if (!isInDebugMode) {
|
||||
await Flurry.initialize(androidKey: "JNYCTCWBT34FM3J8TV36", iosKey: "3QBG7BSMGPDH24S8TRQP", enableLog: true);
|
||||
|
||||
SetupOptions options = (new SetupOptionsBuilder('682883e5cd71a46160c4f6ed070530ee593f49c6')
|
||||
/* SetupOptions options = (new SetupOptionsBuilder('682883e5cd71a46160c4f6ed070530ee593f49c6')
|
||||
..Fps = 2
|
||||
..StartNewSession = true)
|
||||
.build();
|
||||
|
||||
Smartlook.setupAndStartRecording(options);
|
||||
Smartlook.enableCrashlytics(true);
|
||||
Smartlook.setEventTrackingMode(EventTrackingMode.FULL_TRACKING);
|
||||
Smartlook.setEventTrackingMode(EventTrackingMode.FULL_TRACKING); */
|
||||
|
||||
FlutterUxcam.optIntoSchematicRecordings();
|
||||
// FlutterUxcam.optIntoVideoRecording();
|
||||
FlutterUxcam.startWithKey("wvdstyoml4tiwfd");
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,6 +204,7 @@ class WorkoutTestApp extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||
final FirebaseAnalytics analytics = FirebaseAnalytics();
|
||||
|
||||
//facebookAppEvents.setAdvertiserTracking(enabled: true);
|
||||
initFlurry();
|
||||
PushNotificationsManager().init();
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
import 'package:aitrainer_app/model/customer.dart';
|
||||
import 'package:aitrainer_app/model/customer_activity.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';
|
||||
@ -13,6 +14,8 @@ import 'package:aitrainer_app/model/product.dart';
|
||||
import 'package:aitrainer_app/model/product_test.dart';
|
||||
import 'package:aitrainer_app/model/property.dart';
|
||||
import 'package:aitrainer_app/model/purchase.dart';
|
||||
import 'package:aitrainer_app/model/sport.dart';
|
||||
import 'package:aitrainer_app/model/tutorial.dart';
|
||||
import 'package:aitrainer_app/model/workout_menu_tree.dart';
|
||||
import 'package:aitrainer_app/repository/customer_repository.dart';
|
||||
import 'package:aitrainer_app/service/firebase_api.dart';
|
||||
@ -24,11 +27,11 @@ import 'package:aitrainer_app/util/env.dart';
|
||||
import 'package:aitrainer_app/util/track.dart';
|
||||
import 'package:flurry/flurry.dart';
|
||||
import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
|
||||
import 'package:flutter_uxcam/flutter_uxcam.dart';
|
||||
import 'package:package_info/package_info.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:aitrainer_app/model/exercise_type.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:smartlook/smartlook.dart';
|
||||
|
||||
import 'customer_exercise_device.dart';
|
||||
import 'exercise_device.dart';
|
||||
@ -55,6 +58,24 @@ enum SharePrefsChange {
|
||||
- is_logged_in
|
||||
*/
|
||||
|
||||
enum ActivityDone { tutorialBasic, tutorialDevelopment, isExerciseLogSeen, isMuscleDevelopmentSeen }
|
||||
|
||||
extension ActivityDoneExt on ActivityDone {
|
||||
String toStr() => this.toString().split(".").last;
|
||||
bool equalsTo(ActivityDone value) => this.toString() == value.toString();
|
||||
bool equalsStringTo(String value) => this.toStr() == value;
|
||||
|
||||
ActivityDone? searchByString(String activityString) {
|
||||
ActivityDone? activity;
|
||||
ActivityDone.values.forEach((element) {
|
||||
if (element.equalsStringTo(activityString)) {
|
||||
activity = element;
|
||||
}
|
||||
});
|
||||
return activity;
|
||||
}
|
||||
}
|
||||
|
||||
class Cache with Logging {
|
||||
static final Cache _singleton = Cache._internal();
|
||||
|
||||
@ -73,8 +94,6 @@ class Cache with Logging {
|
||||
static final String activeExercisePlanKey = "active_exercise_plan";
|
||||
static final String activeExercisePlanDateKey = "active_exercise_plan_date";
|
||||
static final String activeExercisePlanDetailsKey = "active_exercise_details_plan";
|
||||
static final String exerciseLogSeenKey = "exercise_log_seen";
|
||||
static final String muscleDevelopmentSeenKey = "muscle_development_seen_key";
|
||||
|
||||
static String baseUrl = 'http://aitrainer.info:8888/api/';
|
||||
static final String mediaUrl = 'https://aitrainer.info:4343/media/';
|
||||
@ -99,6 +118,7 @@ class Cache with Logging {
|
||||
List<Exercise>? _exercises;
|
||||
ExercisePlan? _myExercisePlan;
|
||||
List<Property>? _properties;
|
||||
List<Sport>? _sports;
|
||||
List<wt_product.Product>? _products;
|
||||
List<Purchase> _purchases = [];
|
||||
List<ProductTest>? _productTests;
|
||||
@ -109,6 +129,8 @@ class Cache with Logging {
|
||||
|
||||
List<ExerciseDevice>? _devices;
|
||||
List<CustomerExerciseDevice>? _customerDevices;
|
||||
List<CustomerActivity>? _customerActivities;
|
||||
List<Tutorial>? _tutorials;
|
||||
|
||||
LinkedHashMap<int, ExercisePlanDetail> _myExercisesPlanDetails = LinkedHashMap<int, ExercisePlanDetail>();
|
||||
|
||||
@ -126,8 +148,8 @@ class Cache with Logging {
|
||||
late String testEnvironment;
|
||||
bool liveServer = true;
|
||||
bool? hasHardware = false;
|
||||
bool? isExerciseLogSeen = false;
|
||||
bool? isMuscleDevelopmentSeen = false;
|
||||
|
||||
HashMap<String, bool> activitiesDone = HashMap();
|
||||
|
||||
factory Cache() {
|
||||
return _singleton;
|
||||
@ -140,6 +162,10 @@ class Cache with Logging {
|
||||
baseUrl = 'http://aitrainer.app:8899/api/';
|
||||
liveServer = false;
|
||||
}
|
||||
|
||||
ActivityDone.values.forEach((element) {
|
||||
activitiesDone[element.toStr()] = false;
|
||||
});
|
||||
}
|
||||
|
||||
void setTestBaseUrl() {
|
||||
@ -461,9 +487,11 @@ class Cache with Logging {
|
||||
}
|
||||
|
||||
void setProperties(List<Property> properties) => this._properties = properties;
|
||||
|
||||
List<Property>? getProperties() => _properties;
|
||||
|
||||
List<Sport>? getSports() => _sports;
|
||||
void setSports(List<Sport> sports) => this._sports = sports;
|
||||
|
||||
void setDevices(List<ExerciseDevice> devices) => this._devices = devices;
|
||||
|
||||
List<ExerciseDevice>? getDevices() => this._devices;
|
||||
@ -559,11 +587,11 @@ class Cache with Logging {
|
||||
setBadge("account", true);
|
||||
}
|
||||
if (this._exercises != null && this._exercises!.isNotEmpty) {
|
||||
if (!isExerciseLogSeen!) {
|
||||
if (!activitiesDone[ActivityDone.isExerciseLogSeen.toStr()]!) {
|
||||
setBadge("exerciseLog", true);
|
||||
setBadge("development", true);
|
||||
}
|
||||
if (!isMuscleDevelopmentSeen!) {
|
||||
if (!activitiesDone[ActivityDone.isMuscleDevelopmentSeen.toStr()]!) {
|
||||
setBadge("muscleDevelopment", true);
|
||||
setBadge("development", true);
|
||||
}
|
||||
@ -589,14 +617,16 @@ class Cache with Logging {
|
||||
|
||||
if (!isInDebugMode) {
|
||||
Flurry.setUserId(customerId.toString());
|
||||
Smartlook.setUserIdentifier(customerId.toString());
|
||||
//Smartlook.setUserIdentifier(customerId.toString());
|
||||
FlutterUxcam.setUserProperty("username", customerId.toString());
|
||||
Track().track(TrackingEvent.enter);
|
||||
}
|
||||
|
||||
await setLoginTypeFromPrefs();
|
||||
await getActiveExercisePlan();
|
||||
await isExerciseLogSeenPrefs();
|
||||
await isMuscleDevelopmentSeenPrefs();
|
||||
await Future.forEach(ActivityDone.values, (element) async {
|
||||
ActivityDone activity = element as ActivityDone;
|
||||
await isActivityDonePrefs(activity);
|
||||
});
|
||||
|
||||
Cache().startPage = "home";
|
||||
}
|
||||
|
||||
@ -609,41 +639,29 @@ class Cache with Logging {
|
||||
List get exercisePlanTemplates => this._exercisePlanTemplates;
|
||||
setExercisePlanTemplates(value) => this._exercisePlanTemplates = value;
|
||||
|
||||
setExerciseLogSeen() async {
|
||||
isActivityDonePrefs(ActivityDone activity) async {
|
||||
Future<SharedPreferences> prefs = SharedPreferences.getInstance();
|
||||
SharedPreferences sharedPreferences = await prefs;
|
||||
isExerciseLogSeen = true;
|
||||
sharedPreferences.setBool(Cache.exerciseLogSeenKey, true);
|
||||
}
|
||||
|
||||
Future<bool> isExerciseLogSeenPrefs() async {
|
||||
Future<SharedPreferences> prefs = SharedPreferences.getInstance();
|
||||
SharedPreferences sharedPreferences = await prefs;
|
||||
isExerciseLogSeen = sharedPreferences.getBool(Cache.exerciseLogSeenKey);
|
||||
if (isExerciseLogSeen == null) {
|
||||
isExerciseLogSeen = false;
|
||||
if (sharedPreferences.getBool(activity.toStr()) != null) {
|
||||
activitiesDone[activity.toStr()] = sharedPreferences.getBool(activity.toStr())!;
|
||||
}
|
||||
//print("ExerciseLogSeen $isExerciseLogSeen");
|
||||
return isExerciseLogSeen!;
|
||||
|
||||
return activitiesDone[activity.toStr()]!;
|
||||
}
|
||||
|
||||
setMuscleDevelopmentSeen() async {
|
||||
setActivityDonePrefs(ActivityDone activity) async {
|
||||
Future<SharedPreferences> prefs = SharedPreferences.getInstance();
|
||||
SharedPreferences sharedPreferences = await prefs;
|
||||
isMuscleDevelopmentSeen = true;
|
||||
sharedPreferences.setBool(Cache.muscleDevelopmentSeenKey, true);
|
||||
}
|
||||
|
||||
Future<bool> isMuscleDevelopmentSeenPrefs() async {
|
||||
Future<SharedPreferences> prefs = SharedPreferences.getInstance();
|
||||
SharedPreferences sharedPreferences = await prefs;
|
||||
isMuscleDevelopmentSeen = sharedPreferences.getBool(Cache.muscleDevelopmentSeenKey);
|
||||
if (isMuscleDevelopmentSeen == null) {
|
||||
isMuscleDevelopmentSeen = false;
|
||||
}
|
||||
return isMuscleDevelopmentSeen!;
|
||||
activitiesDone[activity.toStr()] = true;
|
||||
sharedPreferences.setBool(activity.toStr(), true);
|
||||
}
|
||||
|
||||
List<Evaluation>? get evaluations => this._evaluations;
|
||||
set evaluations(List<Evaluation>? value) => this._evaluations = value;
|
||||
|
||||
List<CustomerActivity>? get customerActivities => this._customerActivities;
|
||||
setCustomerActivities(List<CustomerActivity>? value) => this._customerActivities = value;
|
||||
|
||||
List<Tutorial>? get tutorials => this._tutorials;
|
||||
setTutorials(List<Tutorial>? value) => this._tutorials = value;
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ class Customer {
|
||||
String? firebaseUid;
|
||||
DateTime? dateAdd;
|
||||
DateTime? dateChange;
|
||||
int? emailSubscription;
|
||||
int? sportId;
|
||||
|
||||
LinkedHashMap<String, CustomerProperty> properties = LinkedHashMap();
|
||||
|
||||
@ -65,6 +67,10 @@ class Customer {
|
||||
this.trainer = json['trainer'];
|
||||
this.firebaseUid = json['firebaseUid'];
|
||||
|
||||
this.dataPolicyAllowed = json['dataPolicyAllowed'];
|
||||
this.emailSubscription = json['emailSubscription'];
|
||||
this.sportId = json['sportId'];
|
||||
|
||||
this.dateAdd = json['dateAdd'] == null ? DateTime.parse("0000-00-00") : DateTime.parse(json['dateAdd']);
|
||||
this.dateChange = json['dateChange'] == null ? DateTime.parse("0000-00-00") : DateTime.parse(json['dateChange']);
|
||||
}
|
||||
@ -86,6 +92,8 @@ class Customer {
|
||||
"dataPolicyAllowed": dataPolicyAllowed,
|
||||
"dateAdd": DateFormat('yyyy-MM-dd HH:mm:ss').format(this.dateAdd!),
|
||||
"dateChange": DateFormat('yyyy-MM-dd HH:mm:ss').format(this.dateChange!),
|
||||
"emailSubscription": this.emailSubscription,
|
||||
"sportId": this.sportId
|
||||
};
|
||||
|
||||
double getProperty(String propertyName) {
|
||||
|
30
lib/model/customer_activity.dart
Normal file
@ -0,0 +1,30 @@
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class CustomerActivity {
|
||||
late int activityId;
|
||||
late int customerId;
|
||||
late String type;
|
||||
late DateTime? dateAdd;
|
||||
bool? skipped;
|
||||
|
||||
CustomerActivity.fromJson(Map json) {
|
||||
activityId = json['activityId'];
|
||||
customerId = json['custoemrId'];
|
||||
type = json['type'];
|
||||
skipped = json['skipped'];
|
||||
this.dateAdd = DateTime.parse(json['dateAdd']);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'activityId': this.activityId,
|
||||
'customerId': this.customerId,
|
||||
'type': this.type,
|
||||
'skipped': this.skipped,
|
||||
"dateAdd": DateFormat('yyyy-MM-dd HH:mm:ss').format(this.dateAdd!),
|
||||
};
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return this.toJson().toString();
|
||||
}
|
||||
}
|
@ -1,23 +1,3 @@
|
||||
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;
|
||||
|
22
lib/model/sport.dart
Normal file
@ -0,0 +1,22 @@
|
||||
class Sport {
|
||||
late int sportId;
|
||||
late String name;
|
||||
late String sportNameTranslation;
|
||||
|
||||
Sport.fromJson(Map json) {
|
||||
this.sportId = json['sportId'];
|
||||
this.name = json['name'];
|
||||
this.sportNameTranslation =
|
||||
json['translations'] != null && (json['translations']).length > 0 ? json['translations'][0]['sportName'] : this.name;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"sportId": sportId,
|
||||
"name": name,
|
||||
};
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return this.toJson().toString();
|
||||
}
|
||||
}
|
24
lib/model/tutorial.dart
Normal file
@ -0,0 +1,24 @@
|
||||
import 'package:aitrainer_app/model/tutorial_step.dart';
|
||||
|
||||
enum TutorialEnum { basic, development, training }
|
||||
|
||||
class Tutorial {
|
||||
late int tutorialId;
|
||||
late String name;
|
||||
|
||||
List<TutorialStep>? steps;
|
||||
|
||||
Tutorial.fromJson(Map<String, dynamic> json) {
|
||||
this.tutorialId = json['tutorialId'];
|
||||
this.name = json['name'];
|
||||
|
||||
if (json['steps'] != null && json['steps'].length > 0) {
|
||||
steps = json['steps'].map<TutorialStep>((step) => TutorialStep.fromJson(step)).toList();
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {'tutorialId': this.tutorialId, 'name': this.name, 'steps': steps.toString()};
|
||||
|
||||
@override
|
||||
String toString() => this.toJson().toString();
|
||||
}
|
99
lib/model/tutorial_step.dart
Normal file
@ -0,0 +1,99 @@
|
||||
import 'dart:ui';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:aitrainer_app/util/app_language.dart';
|
||||
|
||||
enum TutorialEnum { basic, development, training }
|
||||
|
||||
class TutorialStepAction {
|
||||
late String direction;
|
||||
late int top;
|
||||
late int left;
|
||||
late bool showBubble;
|
||||
late int bubbleX;
|
||||
late int bubbleY;
|
||||
late int bubbleWidth;
|
||||
late int bubbleHeight;
|
||||
late bool showCheckText;
|
||||
late int parent;
|
||||
|
||||
TutorialStepAction.fromJson(Map json) {
|
||||
this.direction = json['direction'];
|
||||
this.top = json['top'];
|
||||
this.left = json['left'];
|
||||
this.showBubble = json['show_bubble'];
|
||||
this.bubbleX = json['bubble_x'];
|
||||
this.bubbleY = json['bubble_y'];
|
||||
this.bubbleWidth = json['bubble_width'];
|
||||
this.bubbleHeight = json['bubble_height'];
|
||||
this.showCheckText = json['show_check_text'];
|
||||
this.parent = json['parent'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"direction": this.direction,
|
||||
"top": this.top,
|
||||
"left": this.left,
|
||||
"showBubble": this.showBubble,
|
||||
"bubbleX": this.bubbleX,
|
||||
"bubbleY": this.bubbleY,
|
||||
"bubbleWidth": this.bubbleWidth,
|
||||
"bubbleHeight": this.bubbleHeight,
|
||||
"showCheckText": this.showCheckText,
|
||||
"parent": this.parent,
|
||||
};
|
||||
|
||||
@override
|
||||
String toString() => this.toJson().toString();
|
||||
}
|
||||
|
||||
class TutorialStep {
|
||||
int? tutorialStepId;
|
||||
int? tutorialId;
|
||||
int? step;
|
||||
String? tutorialText;
|
||||
String? direction;
|
||||
String? checkText;
|
||||
String? condition;
|
||||
String? branch;
|
||||
int? parentId;
|
||||
TutorialStepAction? action;
|
||||
|
||||
String? tutorialTextTranslation;
|
||||
String? errorTextTranslation;
|
||||
|
||||
TutorialStep.fromJson(Map json) {
|
||||
this.tutorialStepId = json['tutorialStepId'];
|
||||
this.tutorialId = json['tutorialId'];
|
||||
this.step = json['step'];
|
||||
this.tutorialText = json['tutorialText'];
|
||||
this.checkText = json['checkText'];
|
||||
this.condition = json['condition'];
|
||||
if (this.condition != null) {
|
||||
this.condition = condition!.replaceAll(r'\\', "replace");
|
||||
print("Json condition $condition");
|
||||
this.action = TutorialStepAction.fromJson(jsonDecode(condition!));
|
||||
}
|
||||
|
||||
if (json['translations'] != null && json['translations'].length > 0) {
|
||||
this.tutorialTextTranslation =
|
||||
AppLanguage().appLocal == Locale('hu') ? json['translations'][0]['tutorialText'] : json['tutorialText'];
|
||||
this.errorTextTranslation = AppLanguage().appLocal == Locale('hu') ? json['translations'][0]['errorText'] : json['errorText'];
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
"tutorialStepId": this.tutorialStepId,
|
||||
"tutorialId": this.tutorialId,
|
||||
"step": this.step,
|
||||
"tutorialText": this.tutorialText,
|
||||
"checkText": this.checkText,
|
||||
"tutorialTextTranslation": this.tutorialTextTranslation,
|
||||
"errorTextTranslation": this.errorTextTranslation,
|
||||
"condition": this.condition,
|
||||
"action": this.action != null ? this.action!.toJson() : ""
|
||||
};
|
||||
|
||||
@override
|
||||
String toString() => this.toJson().toString();
|
||||
}
|
@ -6,6 +6,7 @@ import 'package:aitrainer_app/model/customer_property.dart';
|
||||
import 'package:aitrainer_app/model/product_test.dart';
|
||||
import 'package:aitrainer_app/model/property.dart';
|
||||
import 'package:aitrainer_app/model/purchase.dart';
|
||||
import 'package:aitrainer_app/model/sport.dart';
|
||||
import 'package:aitrainer_app/repository/property_repository.dart';
|
||||
import 'package:aitrainer_app/service/customer_service.dart';
|
||||
import 'package:aitrainer_app/service/logging.dart';
|
||||
@ -90,6 +91,36 @@ class CustomerRepository with Logging {
|
||||
return this.customer!.goal;
|
||||
}
|
||||
|
||||
String? getSportString() {
|
||||
if (this.customer == null) throw Exception("Initialize the customer object");
|
||||
String? sport;
|
||||
List<Sport>? sports = Cache().getSports();
|
||||
if (sports != null) {
|
||||
for (Sport sportObject in sports) {
|
||||
if (sportObject.sportId == this.customer!.sportId) {
|
||||
sport = sportObject.name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return sport;
|
||||
}
|
||||
|
||||
Sport? getSport() {
|
||||
if (this.customer == null) throw Exception("Initialize the customer object");
|
||||
Sport? sport;
|
||||
List<Sport>? sports = Cache().getSports();
|
||||
if (sports != null) {
|
||||
for (Sport sportObject in sports) {
|
||||
if (sportObject.sportId == this.customer!.sportId) {
|
||||
sport = sportObject;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return sport;
|
||||
}
|
||||
|
||||
String? get fitnessLevel {
|
||||
if (this.customer == null) throw Exception("Initialize the customer object");
|
||||
return this.customer!.fitnessLevel;
|
||||
@ -189,6 +220,19 @@ class CustomerRepository with Logging {
|
||||
this.customer!.goal = goal;
|
||||
}
|
||||
|
||||
setSportString(String selectedSport) {
|
||||
if (this.customer == null) throw Exception("Initialize the customer object");
|
||||
List<Sport>? sports = Cache().getSports();
|
||||
if (sports != null) {
|
||||
for (Sport sportObject in sports) {
|
||||
if (sportObject.name == selectedSport) {
|
||||
this.customer!.sportId = sportObject.sportId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setBodyType(String bodyType) {
|
||||
if (this.customer == null) throw Exception("Initialize the customer object");
|
||||
this.customer!.bodyType = bodyType;
|
||||
|
@ -74,6 +74,9 @@ class ExerciseRepository {
|
||||
Exercise? getExercise() => this.exercise;
|
||||
|
||||
Future<Exercise> addExercise() async {
|
||||
if (this.customer == null) {
|
||||
throw Exception("Please log in");
|
||||
}
|
||||
final Exercise modelExercise = this.exercise!;
|
||||
modelExercise.customerId = this.customer!.customerId;
|
||||
modelExercise.exerciseTypeId = this.exerciseType!.exerciseTypeId;
|
||||
|
@ -59,16 +59,17 @@ class APIClient with Common, Logging {
|
||||
final responseCode = response.statusCode;
|
||||
if (responseCode != 200) {
|
||||
trace("authentication response: $responseCode");
|
||||
return {
|
||||
throw Exception("Network error, try again later!");
|
||||
/* return {
|
||||
"error": "Authentication error, total failure",
|
||||
};
|
||||
}; */
|
||||
}
|
||||
|
||||
final responseJson = json.decode(response.body);
|
||||
return responseJson;
|
||||
} catch (exception) {
|
||||
print(exception.toString());
|
||||
return {"error": "Network error, try again later " + exception.toString()};
|
||||
throw Exception("Network error, try again later!");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
||||
|
||||
import 'package:aitrainer_app/model/cache.dart';
|
||||
import 'package:aitrainer_app/model/customer.dart';
|
||||
import 'package:aitrainer_app/model/customer_activity.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';
|
||||
@ -15,10 +16,12 @@ import 'package:aitrainer_app/model/product.dart';
|
||||
import 'package:aitrainer_app/model/product_test.dart';
|
||||
import 'package:aitrainer_app/model/property.dart';
|
||||
import 'package:aitrainer_app/model/purchase.dart';
|
||||
import 'package:aitrainer_app/model/tutorial.dart';
|
||||
import 'package:aitrainer_app/service/api.dart';
|
||||
import 'package:aitrainer_app/service/exercise_type_service.dart';
|
||||
import 'package:aitrainer_app/util/not_found_exception.dart';
|
||||
|
||||
import '../model/sport.dart';
|
||||
import 'customer_service.dart';
|
||||
import 'exercise_tree_service.dart';
|
||||
|
||||
@ -63,6 +66,14 @@ class PackageApi {
|
||||
} else if (headRecord[0] == "Evaluation") {
|
||||
final List<Evaluation> evaluations = json.map((evaluation) => Evaluation.fromJson(evaluation)).toList();
|
||||
Cache().evaluations = evaluations;
|
||||
} else if (headRecord[0] == "Sport") {
|
||||
final List<Sport> sports = json.map((sport) => Sport.fromJson(sport)).toList();
|
||||
Cache().setSports(sports);
|
||||
} else if (headRecord[0] == "Tutorial") {
|
||||
final Iterable json = jsonDecode(headRecord[1]);
|
||||
final List<Tutorial> tutorials = json.map((tutorial) => Tutorial.fromJson(tutorial)).toList();
|
||||
print("Tutorial: $tutorials");
|
||||
Cache().setTutorials(tutorials);
|
||||
}
|
||||
});
|
||||
|
||||
@ -145,6 +156,10 @@ class PackageApi {
|
||||
return item;
|
||||
}).toList();
|
||||
// ToDo */
|
||||
} else if (headRecord[0] == "CustomerActivity") {
|
||||
final Iterable json = jsonDecode(headRecord[1]);
|
||||
final List<CustomerActivity> customerActivities = json.map((activity) => CustomerActivity.fromJson(activity)).toList();
|
||||
Cache().setCustomerActivities(customerActivities);
|
||||
}
|
||||
});
|
||||
} on NotFoundException catch (_) {
|
||||
|
@ -1,9 +1,10 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:aitrainer_app/model/cache.dart';
|
||||
import 'package:aitrainer_app/model/property.dart';
|
||||
import 'package:aitrainer_app/service/api.dart';
|
||||
|
||||
import '../model/property.dart';
|
||||
|
||||
class PropertyApi {
|
||||
final APIClient _client = new APIClient();
|
||||
|
||||
|
18
lib/service/sport_service.dart
Normal file
@ -0,0 +1,18 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:aitrainer_app/model/cache.dart';
|
||||
import 'package:aitrainer_app/service/api.dart';
|
||||
|
||||
import '../model/sport.dart';
|
||||
|
||||
class SportApi {
|
||||
final APIClient _client = new APIClient();
|
||||
|
||||
Future<List<Sport>> getSports() async {
|
||||
final body = await _client.get("sports/", "");
|
||||
final Iterable json = jsonDecode(body);
|
||||
final List<Sport> sports = json.map((sport) => Sport.fromJson(sport)).toList();
|
||||
Cache().setSports(sports);
|
||||
return sports;
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ import 'package:aitrainer_app/service/package_service.dart';
|
||||
import 'package:aitrainer_app/util/purchases.dart';
|
||||
import 'package:devicelocale/devicelocale.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_app_badger/flutter_app_badger.dart';
|
||||
import 'package:package_info/package_info.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:aitrainer_app/model/cache.dart';
|
||||
@ -31,6 +32,10 @@ class Session with Logging {
|
||||
Cache().getHardware(_sharedPreferences);
|
||||
await _fetchToken(_sharedPreferences);
|
||||
await RevenueCatPurchases().initPlatform();
|
||||
bool res = await FlutterAppBadger.isAppBadgeSupported();
|
||||
if (res == true) {
|
||||
FlutterAppBadger.removeBadge();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ import 'package:aitrainer_app/service/tracking_service.dart';
|
||||
import 'package:aitrainer_app/util/enums.dart';
|
||||
import 'package:aitrainer_app/model/tracking.dart' as model;
|
||||
import 'package:flurry/flurry.dart';
|
||||
import 'package:smartlook/smartlook.dart';
|
||||
import 'package:flutter_uxcam/flutter_uxcam.dart';
|
||||
|
||||
class Track with Logging {
|
||||
static final Track _singleton = Track._internal();
|
||||
@ -19,7 +19,8 @@ class Track with Logging {
|
||||
void track(TrackingEvent event, {String eventValue = ""}) {
|
||||
if (!isInDebugMode) {
|
||||
Flurry.logEvent(event.toString());
|
||||
Smartlook.setGlobalEventProperty(event.toString(), eventValue, false);
|
||||
// Smartlook.setGlobalEventProperty(event.toString(), eventValue, false);
|
||||
FlutterUxcam.logEventWithProperties(event.enumToString(), {"value": eventValue});
|
||||
model.Tracking tracking = model.Tracking();
|
||||
tracking.customerId = Cache().userLoggedIn == null ? 0 : Cache().userLoggedIn!.customerId!;
|
||||
tracking.event = event.enumToString();
|
||||
|
@ -107,7 +107,7 @@ class AccountPage extends StatelessWidget with Trans {
|
||||
),
|
||||
ListTile(
|
||||
leading: Common.badgedIcon(Colors.grey, Icons.perm_contact_cal, "FitnessLevel"), //Icon(Icons.perm_contact_cal),
|
||||
subtitle: Text(t("Activity")),
|
||||
subtitle: Text(t("Activity") + " " + t("and") + " " + t("Sport")),
|
||||
title: TextButton(
|
||||
child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
||||
Text(fitnessLevel, style: TextStyle(color: Colors.blue)),
|
||||
|
@ -1,6 +1,8 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:aitrainer_app/bloc/customer_change/customer_change_bloc.dart';
|
||||
import 'package:aitrainer_app/model/cache.dart';
|
||||
import 'package:aitrainer_app/model/sport.dart';
|
||||
import 'package:aitrainer_app/util/app_localization.dart';
|
||||
import 'package:aitrainer_app/repository/customer_repository.dart';
|
||||
import 'package:aitrainer_app/model/fitness_state.dart';
|
||||
@ -82,10 +84,9 @@ class _CustomerFitnessPageState extends State<CustomerFitnessPage> with Trans {
|
||||
Text(
|
||||
t("Your Fitness State"),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
style: GoogleFonts.archivoBlack(
|
||||
color: Colors.orange,
|
||||
fontSize: 42,
|
||||
fontFamily: 'Arial',
|
||||
fontSize: 30,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
)
|
||||
@ -218,7 +219,15 @@ class _CustomerFitnessPageState extends State<CustomerFitnessPage> with Trans {
|
||||
}),
|
||||
}),
|
||||
Divider(),
|
||||
selected == FitnessState.professional ? getSport(changeBloc) : Offstage(),
|
||||
Text(
|
||||
t("Your Primary Sport") + ":",
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.archivoBlack(
|
||||
color: Colors.orange,
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
getSport(changeBloc),
|
||||
Divider(),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
@ -253,7 +262,7 @@ class _CustomerFitnessPageState extends State<CustomerFitnessPage> with Trans {
|
||||
}
|
||||
|
||||
Widget getSport(CustomerChangeBloc bloc) {
|
||||
Sport? selected = bloc.getSelectedSport;
|
||||
Sport? selected = bloc.selectedSport;
|
||||
return Container(
|
||||
padding: EdgeInsets.only(left: 65, right: 65),
|
||||
child: DropdownSearch<Sport>(
|
||||
@ -270,10 +279,16 @@ class _CustomerFitnessPageState extends State<CustomerFitnessPage> with Trans {
|
||||
),
|
||||
),
|
||||
mode: Mode.MENU,
|
||||
compareFn: (Sport i, Sport s) => i.equalsTo(s),
|
||||
compareFn: (Sport? i, Sport? s) {
|
||||
if (i == null || s == null) {
|
||||
return false;
|
||||
} else {
|
||||
return i.sportId == s.sportId;
|
||||
}
|
||||
},
|
||||
showSelectedItem: true,
|
||||
selectedItem: selected,
|
||||
itemAsString: (data) => t(data.toStr()),
|
||||
itemAsString: (data) => t(data.sportNameTranslation),
|
||||
onChanged: (data) {
|
||||
bloc.add(CustomerSportChange(sport: data));
|
||||
},
|
||||
@ -281,7 +296,7 @@ class _CustomerFitnessPageState extends State<CustomerFitnessPage> with Trans {
|
||||
popupItemBuilder: _customMenuBuilder,
|
||||
popupBarrierColor: Colors.white10,
|
||||
//popupBackgroundColor: Colors.yellow,
|
||||
items: Sport.values,
|
||||
items: Cache().getSports(),
|
||||
dropDownButton: Icon(
|
||||
Icons.arrow_drop_down,
|
||||
color: Colors.indigo,
|
||||
@ -291,7 +306,6 @@ class _CustomerFitnessPageState extends State<CustomerFitnessPage> with Trans {
|
||||
}
|
||||
|
||||
Widget _customMenuBuilder(BuildContext context, Sport sport, bool isSelected) {
|
||||
//bool selected = bloc.getSelectedSport;
|
||||
return Container(
|
||||
decoration: !isSelected
|
||||
? BoxDecoration(color: Colors.grey[300])
|
||||
@ -303,11 +317,11 @@ class _CustomerFitnessPageState extends State<CustomerFitnessPage> with Trans {
|
||||
child: ListTile(
|
||||
selected: isSelected,
|
||||
title: Text(
|
||||
t(sport.toStr()),
|
||||
t(sport.sportNameTranslation),
|
||||
style: GoogleFonts.archivoBlack(fontSize: 20, color: Colors.blue[600]),
|
||||
),
|
||||
subtitle: Text(
|
||||
t(sport.description(sport)),
|
||||
t(sport.name),
|
||||
style: GoogleFonts.inter(fontSize: 12, color: Colors.blue[600]),
|
||||
),
|
||||
),
|
||||
@ -327,11 +341,11 @@ class _CustomerFitnessPageState extends State<CustomerFitnessPage> with Trans {
|
||||
: ListTile(
|
||||
contentPadding: EdgeInsets.all(0),
|
||||
title: Text(
|
||||
t(item.toStr()),
|
||||
t(item.sportNameTranslation),
|
||||
style: GoogleFonts.archivoBlack(fontSize: 20, color: Colors.blue[600]),
|
||||
),
|
||||
subtitle: Text(
|
||||
t(item.description(item)),
|
||||
t(item.name),
|
||||
style: GoogleFonts.inter(fontSize: 12, color: Colors.blue[600]),
|
||||
),
|
||||
),
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:aitrainer_app/bloc/customer_change/customer_change_bloc.dart';
|
||||
import 'package:aitrainer_app/util/app_localization.dart';
|
||||
import 'package:aitrainer_app/library/custom_icon_icons.dart';
|
||||
import 'package:aitrainer_app/repository/customer_repository.dart';
|
||||
import 'package:aitrainer_app/util/trans.dart';
|
||||
import 'package:aitrainer_app/widgets/app_bar_min.dart';
|
||||
@ -11,9 +11,45 @@ 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";
|
||||
static String weight = "weight_loss";
|
||||
enum Goals { gain_muscle, weight_loss, endurance, muscle_endurance, flexibility, gain_strength, explosiveness, shape_forming }
|
||||
|
||||
extension GoalsExt on Goals {
|
||||
String toStr() => this.toString().split(".").last;
|
||||
bool equalsTo(Goals goal) => this.toString() == goal.toString();
|
||||
bool equalsStringTo(String goal) => this.toStr() == goal;
|
||||
|
||||
String description(Goals goal) {
|
||||
switch (goal) {
|
||||
case Goals.endurance:
|
||||
return "Endurance";
|
||||
case Goals.weight_loss:
|
||||
return "Loss Weight";
|
||||
case Goals.gain_muscle:
|
||||
return "Gain Muscle";
|
||||
case Goals.gain_strength:
|
||||
return "Gain Strength";
|
||||
case Goals.muscle_endurance:
|
||||
return "Muscle Endurance";
|
||||
case Goals.flexibility:
|
||||
return "Flexibility";
|
||||
case Goals.explosiveness:
|
||||
return "Explosiveness";
|
||||
case Goals.shape_forming:
|
||||
return "Shape Forming";
|
||||
default:
|
||||
return "Gain Muscle";
|
||||
}
|
||||
}
|
||||
|
||||
Goals getGoal(Goals goal) {
|
||||
Goals selected = Goals.gain_muscle;
|
||||
Goals.values.forEach((element) {
|
||||
if (goal.equalsTo(element)) {
|
||||
selected = element;
|
||||
}
|
||||
});
|
||||
return selected;
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: must_be_immutable
|
||||
@ -25,6 +61,7 @@ class CustomerGoalPage extends StatefulWidget {
|
||||
class _CustomerGoalPage extends State<CustomerGoalPage> with Trans {
|
||||
String? selected;
|
||||
bool fulldata = false;
|
||||
late CustomerChangeBloc changeBloc;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -47,107 +84,135 @@ class _CustomerGoalPage extends State<CustomerGoalPage> with Trans {
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: _bar,
|
||||
body: Container(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage('asset/image/WT_light_background.jpg'),
|
||||
fit: BoxFit.cover,
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
appBar: _bar,
|
||||
body: Container(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage('asset/image/WT_light_background.jpg'),
|
||||
fit: BoxFit.cover,
|
||||
alignment: Alignment.center,
|
||||
),
|
||||
height: double.infinity,
|
||||
width: double.infinity,
|
||||
child: BlocProvider(
|
||||
create: (context) => CustomerChangeBloc(customerRepository: customerRepository),
|
||||
child: Builder(builder: (context) {
|
||||
CustomerChangeBloc changeBloc = BlocProvider.of<CustomerChangeBloc>(context);
|
||||
),
|
||||
height: double.infinity,
|
||||
width: double.infinity,
|
||||
child: BlocProvider(
|
||||
create: (context) => CustomerChangeBloc(customerRepository: customerRepository),
|
||||
child: Builder(builder: (context) {
|
||||
changeBloc = BlocProvider.of<CustomerChangeBloc>(context);
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Divider(),
|
||||
Wrap(alignment: WrapAlignment.center, children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.translate("Set Your Goals"),
|
||||
style: GoogleFonts.archivoBlack(
|
||||
color: Colors.orange,
|
||||
fontSize: 42,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
return SingleChildScrollView(
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Divider(),
|
||||
Wrap(alignment: WrapAlignment.center, children: [
|
||||
Text(
|
||||
t("Set Your Primary Goal"),
|
||||
maxLines: 2,
|
||||
style: GoogleFonts.archivoBlack(
|
||||
color: Colors.orange,
|
||||
fontSize: 30,
|
||||
shadows: <Shadow>[
|
||||
Shadow(
|
||||
offset: Offset(2.0, 2.0),
|
||||
blurRadius: 3.0,
|
||||
color: Colors.black87,
|
||||
),
|
||||
],
|
||||
),
|
||||
]),
|
||||
Divider(),
|
||||
Stack(alignment: Alignment.bottomLeft, children: [
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.all(0.0),
|
||||
shape: getShape(changeBloc, GoalsItem.muscle),
|
||||
),
|
||||
child: Image.asset(
|
||||
"asset/image/Gain_muscle.jpg",
|
||||
height: 180,
|
||||
),
|
||||
onPressed: () => {
|
||||
setState(() {
|
||||
selected = GoalsItem.muscle;
|
||||
changeBloc.add(CustomerGoalChange(goal: GoalsItem.muscle));
|
||||
}),
|
||||
}),
|
||||
InkWell(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.translate("Gain Muscle"),
|
||||
style: TextStyle(color: Colors.white, fontSize: 32, fontFamily: 'Arial', fontWeight: FontWeight.w900),
|
||||
),
|
||||
highlightColor: Colors.white,
|
||||
)
|
||||
]),
|
||||
Divider(),
|
||||
Stack(alignment: Alignment.bottomLeft, children: [
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.all(0.0),
|
||||
shape: getShape(changeBloc, GoalsItem.weight),
|
||||
),
|
||||
child: Image.asset(
|
||||
"asset/image/WT_weight_loss.jpg",
|
||||
height: 180,
|
||||
),
|
||||
onPressed: () => {
|
||||
setState(() {
|
||||
selected = GoalsItem.muscle;
|
||||
changeBloc.add(CustomerGoalChange(goal: GoalsItem.weight));
|
||||
}),
|
||||
}),
|
||||
InkWell(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.translate("Loose Weight"),
|
||||
style: TextStyle(color: Colors.white, fontSize: 32, fontFamily: 'Arial', fontWeight: FontWeight.w900),
|
||||
),
|
||||
highlightColor: Colors.white,
|
||||
)
|
||||
]),
|
||||
Divider(),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
onPrimary: Colors.white,
|
||||
primary: Colors.orange,
|
||||
),
|
||||
child: Text(fulldata ? t("Save") : t("Next")),
|
||||
onPressed: () => {
|
||||
//changingViewModel.saveCustomer(),
|
||||
changeBloc.add(CustomerSave()),
|
||||
Navigator.of(context).pop(),
|
||||
if (!fulldata) {Navigator.of(context).pushNamed("customerFitnessPage", arguments: changeBloc.customerRepository)}
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
}),
|
||||
),
|
||||
]),
|
||||
Divider(),
|
||||
getItem(changeBloc, Goals.gain_muscle),
|
||||
Divider(),
|
||||
getItem(changeBloc, Goals.weight_loss),
|
||||
Divider(),
|
||||
getItem(changeBloc, Goals.shape_forming),
|
||||
Divider(),
|
||||
getItem(changeBloc, Goals.endurance),
|
||||
Divider(),
|
||||
getItem(changeBloc, Goals.gain_strength),
|
||||
Divider(),
|
||||
getItem(changeBloc, Goals.muscle_endurance),
|
||||
Divider(),
|
||||
getItem(changeBloc, Goals.flexibility),
|
||||
Divider(),
|
||||
getItem(changeBloc, Goals.explosiveness),
|
||||
Divider(),
|
||||
/* ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
onPrimary: Colors.white,
|
||||
primary: Colors.orange,
|
||||
),
|
||||
child: Text(fulldata ? t("Save") : t("Next")),
|
||||
onPressed: () => {
|
||||
//changingViewModel.saveCustomer(),
|
||||
changeBloc.add(CustomerSave()),
|
||||
Navigator.of(context).pop(),
|
||||
if (!fulldata) {Navigator.of(context).pushNamed("customerFitnessPage", arguments: changeBloc.customerRepository)}
|
||||
},
|
||||
) */
|
||||
],
|
||||
),
|
||||
));
|
||||
}),
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () => {
|
||||
//changingViewModel.saveCustomer(),
|
||||
changeBloc.add(CustomerSave()),
|
||||
Navigator.of(context).pop(),
|
||||
if (!fulldata) {Navigator.of(context).pushNamed("customerFitnessPage", arguments: changeBloc.customerRepository)}
|
||||
},
|
||||
backgroundColor: Colors.orange[800],
|
||||
icon: Icon(
|
||||
CustomIcon.save,
|
||||
size: 20,
|
||||
),
|
||||
label: Text(
|
||||
fulldata ? t("Save") : t("Next"),
|
||||
style: GoogleFonts.inter(fontWeight: FontWeight.bold, fontSize: 12),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget getItem(CustomerChangeBloc changeBloc, Goals goal) {
|
||||
return Stack(alignment: Alignment.bottomLeft, children: [
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.all(0.0),
|
||||
shape: getShape(changeBloc, goal.toStr()),
|
||||
),
|
||||
));
|
||||
child: Image.asset(
|
||||
"asset/image/" + goal.toStr() + ".jpg",
|
||||
height: 180,
|
||||
),
|
||||
onPressed: () => {
|
||||
setState(() {
|
||||
selected = goal.toStr();
|
||||
changeBloc.add(CustomerGoalChange(goal: goal.toStr()));
|
||||
}),
|
||||
}),
|
||||
Container(
|
||||
padding: EdgeInsets.only(bottom: 5, left: 10),
|
||||
child: Text(
|
||||
t(goal.description(goal)),
|
||||
style: GoogleFonts.archivoBlack(
|
||||
color: Colors.yellow[300],
|
||||
fontSize: 28,
|
||||
shadows: <Shadow>[
|
||||
Shadow(
|
||||
offset: Offset(2.0, 2.0),
|
||||
blurRadius: 5.0,
|
||||
color: Colors.black87,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
dynamic getShape(CustomerChangeBloc customerBloc, String goal) {
|
||||
|
@ -126,37 +126,6 @@ class CustomerModifyPage extends StatelessWidget with Trans {
|
||||
Divider(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
/* Cache().getLoginType() == LoginType.email
|
||||
? TextFormField(
|
||||
key: LibraryKeys.loginPasswordField,
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
labelStyle: TextStyle(fontSize: 14),
|
||||
contentPadding: EdgeInsets.only(left: 15, top: 15, bottom: 15),
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () => {customerBloc.add(CustomerChangePasswordObscure())},
|
||||
icon: Icon(Icons.remove_red_eye),
|
||||
),
|
||||
labelText: t('Password (Leave empty if no change)'),
|
||||
fillColor: Colors.white24,
|
||||
filled: true,
|
||||
border: OutlineInputBorder(
|
||||
gapPadding: 1.0,
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
borderSide: BorderSide(color: Colors.green[50]!, width: 0.4),
|
||||
),
|
||||
),
|
||||
initialValue: customerBloc.customerRepository.customer!.password,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
validator: (val) {
|
||||
String? validator = customerBloc.passwordValidation(val);
|
||||
return validator == null ? null : t(validator);
|
||||
},
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
style: new TextStyle(fontSize: 16, color: Colors.indigo),
|
||||
onChanged: (value) => {customerBloc.add(CustomerPasswordChange(password: value))})
|
||||
)
|
||||
: Offstage(), */
|
||||
Divider(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
|
@ -1,6 +1,8 @@
|
||||
import 'dart:collection';
|
||||
import 'dart:ui';
|
||||
import 'package:aitrainer_app/bloc/tutorial/tutorial_bloc.dart';
|
||||
import 'package:aitrainer_app/util/enums.dart';
|
||||
import 'package:aitrainer_app/widgets/tutorial_widget.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:aitrainer_app/bloc/result/result_bloc.dart';
|
||||
import 'package:aitrainer_app/util/app_language.dart';
|
||||
@ -56,6 +58,12 @@ class EvaluationPage extends StatelessWidget with Trans {
|
||||
imageUrl = 'asset/image/WT_Results_for_runners.jpg';
|
||||
}
|
||||
|
||||
final TutorialBloc tutorialBloc = BlocProvider.of<TutorialBloc>(context);
|
||||
print("Evaluation page tutorial isActive? ${tutorialBloc.isActive}");
|
||||
if (tutorialBloc.isActive == false) {
|
||||
TutorialWidget().close();
|
||||
}
|
||||
|
||||
setContext(context);
|
||||
return Scaffold(
|
||||
appBar: AppBarMin(
|
||||
|
@ -229,6 +229,9 @@ class _ExerciseControlPage extends State<ExerciseControlPage> with Trans {
|
||||
numberPickForm(exerciseBloc, 2),
|
||||
Divider(),
|
||||
numberPickForm(exerciseBloc, 3),
|
||||
SizedBox(
|
||||
height: 80,
|
||||
)
|
||||
]),
|
||||
)),
|
||||
TimerWidget(
|
||||
|
@ -3,6 +3,7 @@ import 'dart:collection';
|
||||
import 'package:aitrainer_app/bloc/exercise_new/exercise_new_bloc.dart';
|
||||
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/bloc/tutorial/tutorial_bloc.dart';
|
||||
import 'package:aitrainer_app/library/custom_icon_icons.dart';
|
||||
import 'package:aitrainer_app/model/cache.dart';
|
||||
import 'package:aitrainer_app/model/exercise_ability.dart';
|
||||
@ -15,6 +16,7 @@ import 'package:aitrainer_app/widgets/app_bar.dart';
|
||||
import 'package:aitrainer_app/widgets/bmi_widget.dart';
|
||||
import 'package:aitrainer_app/widgets/bmr_widget.dart';
|
||||
import 'package:aitrainer_app/widgets/bottom_bar_multiple_exercises.dart';
|
||||
import 'package:aitrainer_app/widgets/dialog_common.dart';
|
||||
import 'package:aitrainer_app/widgets/exercise_save.dart';
|
||||
import 'package:aitrainer_app/widgets/size_widget.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@ -45,8 +47,20 @@ class _ExerciseNewPageState extends State<ExerciseNewPage> with Trans, Logging {
|
||||
child: BlocConsumer<ExerciseNewBloc, ExerciseNewState>(
|
||||
listener: (context, state) {
|
||||
if (state is ExerciseNewError) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(backgroundColor: Colors.orange, content: Text(state.message, style: TextStyle(color: Colors.white))));
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return DialogCommon(
|
||||
warning: true,
|
||||
title: t("Warning"),
|
||||
descriptions: t(state.message),
|
||||
text: "OK",
|
||||
onTap: () => Navigator.of(context).pushNamed("login"),
|
||||
onCancel: () => {
|
||||
Navigator.of(context).pop(),
|
||||
},
|
||||
);
|
||||
});
|
||||
} else if (state is ExerciseNewSaved) {
|
||||
final LinkedHashMap args = LinkedHashMap();
|
||||
// ignore: close_sinks
|
||||
@ -84,13 +98,25 @@ class _ExerciseNewPageState extends State<ExerciseNewPage> with Trans, Logging {
|
||||
|
||||
Widget getExerciseSaveWidget(ExerciseNewBloc exerciseBloc, ExerciseType exerciseType, MenuBloc menuBloc) {
|
||||
if (exerciseBloc.exerciseRepository.exerciseType!.name == "BMR") {
|
||||
return BMR(exerciseBloc: exerciseBloc);
|
||||
if (Cache().userLoggedIn == null) {
|
||||
exerciseBloc.add(ExerciseNewAddError(message: "Please log in"));
|
||||
} else {
|
||||
return BMR(exerciseBloc: exerciseBloc);
|
||||
}
|
||||
}
|
||||
if (exerciseBloc.exerciseRepository.exerciseType!.name == "BMI") {
|
||||
return BMI(exerciseBloc: exerciseBloc);
|
||||
if (Cache().userLoggedIn == null) {
|
||||
exerciseBloc.add(ExerciseNewAddError(message: "Please log in"));
|
||||
} else {
|
||||
return BMI(exerciseBloc: exerciseBloc);
|
||||
}
|
||||
}
|
||||
if (exerciseBloc.exerciseRepository.exerciseType!.name == "Sizes") {
|
||||
return SizeWidget(exerciseBloc: exerciseBloc);
|
||||
if (Cache().userLoggedIn == null) {
|
||||
exerciseBloc.add(ExerciseNewAddError(message: "Please log in"));
|
||||
} else {
|
||||
return SizeWidget(exerciseBloc: exerciseBloc);
|
||||
}
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
@ -143,6 +169,20 @@ class _ExerciseNewPageState extends State<ExerciseNewPage> with Trans, Logging {
|
||||
// ignore: close_sinks
|
||||
final TestSetExecuteBloc? executeBloc = BlocProvider.of<TestSetExecuteBloc>(context);
|
||||
|
||||
final TutorialBloc tutorialBloc = BlocProvider.of<TutorialBloc>(context);
|
||||
if (tutorialBloc.isActive) {
|
||||
final String checkText = "Save";
|
||||
if (!tutorialBloc.checkAction(checkText)) {
|
||||
return;
|
||||
}
|
||||
if (Cache().userLoggedIn != null) {
|
||||
saveAll(bloc);
|
||||
return;
|
||||
} else {
|
||||
Navigator.of(context).pushNamed("registration");
|
||||
}
|
||||
}
|
||||
|
||||
if (executeBloc != null && executeBloc.existsActivePlan() == true) {
|
||||
confirmationOverride(bloc);
|
||||
} else {
|
||||
@ -203,14 +243,19 @@ class _ExerciseNewPageState extends State<ExerciseNewPage> with Trans, Logging {
|
||||
TextButton(
|
||||
child: Text(t("Yes")),
|
||||
onPressed: () {
|
||||
saveAll(bloc);
|
||||
if (executeBloc.existsActivePlan() == true) {
|
||||
executeBloc.add(TestSetExecuteExerciseFinished(
|
||||
exerciseTypeId: bloc.exerciseRepository.exerciseType!.exerciseTypeId,
|
||||
quantity: bloc.exerciseRepository.exercise!.quantity!,
|
||||
unitQuantity: bloc.exerciseRepository.exercise!.unitQuantity!));
|
||||
if (Cache().userLoggedIn == null) {
|
||||
Navigator.pop(context);
|
||||
bloc.add(ExerciseNewAddError(message: "Please log in"));
|
||||
} else {
|
||||
saveAll(bloc);
|
||||
if (executeBloc.existsActivePlan() == true) {
|
||||
executeBloc.add(TestSetExecuteExerciseFinished(
|
||||
exerciseTypeId: bloc.exerciseRepository.exerciseType!.exerciseTypeId,
|
||||
quantity: bloc.exerciseRepository.exercise!.quantity!,
|
||||
unitQuantity: bloc.exerciseRepository.exercise!.unitQuantity!));
|
||||
}
|
||||
Navigator.pop(context);
|
||||
}
|
||||
Navigator.pop(context);
|
||||
},
|
||||
)
|
||||
],
|
||||
|
@ -5,6 +5,7 @@ import 'package:aitrainer_app/bloc/login/login_bloc.dart';
|
||||
import 'package:aitrainer_app/repository/user_repository.dart';
|
||||
import 'package:aitrainer_app/util/trans.dart';
|
||||
import 'package:aitrainer_app/widgets/app_bar_min.dart';
|
||||
import 'package:aitrainer_app/widgets/dialog_common.dart';
|
||||
import 'package:aitrainer_app/widgets/dialog_long.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@ -33,6 +34,21 @@ class LoginPage extends StatelessWidget with Trans {
|
||||
SnackBar(backgroundColor: Colors.orange, content: Text(t(state.message), style: TextStyle(color: Colors.white))));
|
||||
} else if (state is LoginSuccess) {
|
||||
Navigator.of(context).pushNamed('home');
|
||||
} else if (state is LoginSkipped) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return DialogCommon(
|
||||
title: t("No Login"),
|
||||
descriptions: t("You will skip the login."),
|
||||
description2: t("The app functionalitity will be restricted, but please take a tour!"),
|
||||
text: "OK",
|
||||
onTap: () => {Navigator.of(context).pushNamed('home')},
|
||||
onCancel: () => {
|
||||
Navigator.of(context).pop(),
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}, builder: (context, state) {
|
||||
final loginBloc = BlocProvider.of<LoginBloc>(context);
|
||||
@ -69,8 +85,17 @@ class LoginPage extends StatelessWidget with Trans {
|
||||
key: _scaffoldKey,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(left: 20, right: 20),
|
||||
child: ListView(shrinkWrap: false, padding: EdgeInsets.only(top: 150.0), children: <Widget>[
|
||||
ListTile(title: Text(t("Login"), style: GoogleFonts.inter(fontSize: 24))),
|
||||
child: ListView(shrinkWrap: false, padding: EdgeInsets.only(top: 10.0), children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () => loginBloc.add(LoginSkip()),
|
||||
child: Text(
|
||||
t("Skip"),
|
||||
textAlign: TextAlign.right,
|
||||
style: GoogleFonts.inter(color: Colors.black, decoration: TextDecoration.underline),
|
||||
)),
|
||||
SizedBox(
|
||||
height: 140,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
@ -102,8 +127,9 @@ class LoginPage extends StatelessWidget with Trans {
|
||||
],
|
||||
),
|
||||
Divider(),
|
||||
ListTile(title: Text(t("OR"), style: GoogleFonts.inter())),
|
||||
Divider(),
|
||||
SizedBox(
|
||||
height: 50,
|
||||
),
|
||||
TextFormField(
|
||||
key: LibraryKeys.loginEmailField,
|
||||
decoration: InputDecoration(
|
||||
@ -178,22 +204,31 @@ class LoginPage extends StatelessWidget with Trans {
|
||||
//Image.asset('asset/icon/gomb_zold_b-1.png', width: 100, height: 100),
|
||||
onPressed: () => {loginBloc.add(LoginSubmit())}),
|
||||
]),
|
||||
Divider(
|
||||
color: Colors.transparent,
|
||||
SizedBox(
|
||||
height: 50,
|
||||
),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: <Widget>[
|
||||
InkWell(
|
||||
child: Text(t('SignUpLink')),
|
||||
child: Text(
|
||||
t('SignUpLink'),
|
||||
style: GoogleFonts.inter(color: Colors.black, decoration: TextDecoration.underline),
|
||||
),
|
||||
onTap: () => Navigator.of(context).pushNamed('registration'),
|
||||
),
|
||||
Spacer(flex: 2),
|
||||
InkWell(
|
||||
child: Text(t('I forgot the password')),
|
||||
child: Text(
|
||||
t('I forgot the password'),
|
||||
style: GoogleFonts.inter(color: Colors.black, decoration: TextDecoration.underline),
|
||||
),
|
||||
onTap: () => Navigator.of(context).pushNamed('resetPassword'),
|
||||
),
|
||||
Spacer(flex: 2),
|
||||
InkWell(
|
||||
child: Text(t('Privacy')),
|
||||
child: Text(
|
||||
t('Privacy'),
|
||||
style: GoogleFonts.inter(color: Colors.black, decoration: TextDecoration.underline),
|
||||
),
|
||||
onTap: () => {
|
||||
showDialog(
|
||||
context: context,
|
||||
|
@ -33,7 +33,6 @@ class RegistrationPage extends StatelessWidget with Trans {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(backgroundColor: Colors.orange, content: Text(t(state.message), style: TextStyle(color: Colors.white))));
|
||||
} else if (state is LoginSuccess) {
|
||||
//Navigator.of(context).pushNamed('customerModifyPage');
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
@ -48,6 +47,21 @@ class RegistrationPage extends StatelessWidget with Trans {
|
||||
},
|
||||
);
|
||||
});
|
||||
} else if (state is LoginSkipped) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return DialogCommon(
|
||||
title: t("No Registration"),
|
||||
descriptions: t("You will skip the registration process."),
|
||||
description2: t("Please take a short tour in the app"),
|
||||
text: "OK",
|
||||
onTap: () => {Navigator.of(context).pushNamed('home')},
|
||||
onCancel: () => {
|
||||
Navigator.of(context).pop(),
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}, builder: (context, state) {
|
||||
final loginBloc = BlocProvider.of<LoginBloc>(context);
|
||||
@ -84,7 +98,17 @@ class RegistrationPage extends StatelessWidget with Trans {
|
||||
key: _scaffoldKey,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(left: 20, right: 20),
|
||||
child: ListView(shrinkWrap: false, padding: EdgeInsets.only(top: 150.0), children: <Widget>[
|
||||
child: ListView(shrinkWrap: false, padding: EdgeInsets.only(top: 10.0), children: <Widget>[
|
||||
GestureDetector(
|
||||
onTap: () => loginBloc.add(LoginSkip()),
|
||||
child: Text(
|
||||
t("Skip"),
|
||||
textAlign: TextAlign.right,
|
||||
style: GoogleFonts.inter(color: Colors.black, decoration: TextDecoration.underline),
|
||||
)),
|
||||
SizedBox(
|
||||
height: 120,
|
||||
),
|
||||
ListTile(title: Text(t("SignUp"), style: GoogleFonts.inter())),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@ -116,7 +140,10 @@ class RegistrationPage extends StatelessWidget with Trans {
|
||||
: Offstage(),
|
||||
],
|
||||
),
|
||||
ListTile(title: Text(t("OR"), style: GoogleFonts.inter())),
|
||||
//ListTile(title: Text(t("OR"), style: GoogleFonts.inter())),
|
||||
Divider(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
TextFormField(
|
||||
key: LibraryKeys.loginEmailField,
|
||||
decoration: InputDecoration(
|
||||
@ -176,6 +203,7 @@ class RegistrationPage extends StatelessWidget with Trans {
|
||||
color: Colors.transparent,
|
||||
),
|
||||
getDataProtection(loginBloc),
|
||||
getEmailSubscription(loginBloc),
|
||||
Divider(
|
||||
color: Colors.transparent,
|
||||
),
|
||||
@ -203,12 +231,18 @@ class RegistrationPage extends StatelessWidget with Trans {
|
||||
),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: <Widget>[
|
||||
InkWell(
|
||||
child: Text(t('Login')),
|
||||
child: Text(
|
||||
t('Login'),
|
||||
style: GoogleFonts.inter(decoration: TextDecoration.underline),
|
||||
),
|
||||
onTap: () => Navigator.of(context).pushNamed('login'),
|
||||
),
|
||||
Spacer(flex: 2),
|
||||
InkWell(
|
||||
child: Text(t('Privacy')),
|
||||
child: Text(
|
||||
t('Privacy'),
|
||||
style: GoogleFonts.inter(decoration: TextDecoration.underline),
|
||||
),
|
||||
onTap: () => {
|
||||
showDialog(
|
||||
context: context,
|
||||
@ -234,4 +268,18 @@ class RegistrationPage extends StatelessWidget with Trans {
|
||||
controlAffinity: ListTileControlAffinity.leading, // <-- leading Checkbox
|
||||
);
|
||||
}
|
||||
|
||||
Widget getEmailSubscription(LoginBloc loginBloc) {
|
||||
return CheckboxListTile(
|
||||
title: Text(t("Email notifications")),
|
||||
subtitle: Text(t("We may ask you about your opinion, send events in email")),
|
||||
dense: true,
|
||||
value: loginBloc.emailSubscription,
|
||||
activeColor: Colors.indigo,
|
||||
onChanged: (value) {
|
||||
loginBloc.add(DataProtectionClicked(marked: value!));
|
||||
},
|
||||
controlAffinity: ListTileControlAffinity.leading, // <-- leading Checkbox
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:aitrainer_app/bloc/menu/menu_bloc.dart';
|
||||
import 'package:aitrainer_app/bloc/settings/settings_bloc.dart';
|
||||
import 'package:aitrainer_app/bloc/tutorial/tutorial_bloc.dart';
|
||||
import 'package:aitrainer_app/library/custom_icon_icons.dart';
|
||||
import 'package:aitrainer_app/util/app_language.dart';
|
||||
import 'package:aitrainer_app/model/cache.dart';
|
||||
@ -43,6 +44,7 @@ class SettingsPage extends StatelessWidget with Trans {
|
||||
SnackBar(backgroundColor: Colors.orange, content: Text(state.message, style: TextStyle(color: Colors.white))));
|
||||
} else if (state is SettingsReady) {
|
||||
menuBloc.add(MenuRecreateTree());
|
||||
Navigator.of(context).pushNamed("home");
|
||||
}
|
||||
}, builder: (context, state) {
|
||||
return ModalProgressHUD(
|
||||
@ -76,6 +78,7 @@ class SettingsPage extends StatelessWidget with Trans {
|
||||
Track().track(TrackingEvent.settings_lang, eventValue: lang)
|
||||
})),
|
||||
getServer(settingsBloc),
|
||||
getTuturialBasic(settingsBloc),
|
||||
//getDevice(settingsBloc),
|
||||
]);
|
||||
}
|
||||
@ -131,4 +134,27 @@ class SettingsPage extends StatelessWidget with Trans {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ListTile getTuturialBasic(SettingsBloc settingsBloc) {
|
||||
final TutorialBloc tutorialBloc = BlocProvider.of<TutorialBloc>(context);
|
||||
return ListTile(
|
||||
leading: Icon(CustomIcon.question_circle),
|
||||
subtitle: Text("Activating the basic tutorial"),
|
||||
title: ToggleSwitch(
|
||||
minWidth: 120.0,
|
||||
minHeight: 30.0,
|
||||
fontSize: 14.0,
|
||||
initialLabelIndex: 0,
|
||||
activeBgColor: Colors.indigo,
|
||||
activeFgColor: Colors.white,
|
||||
inactiveBgColor: Colors.white60,
|
||||
inactiveFgColor: Colors.grey[900],
|
||||
labels: [t('Basic Tutorial'), t('Activate')],
|
||||
onToggle: (index) {
|
||||
settingsBloc.add(SettingsActivateTutorial(activity: ActivityDone.tutorialBasic));
|
||||
tutorialBloc.add(TutorialStart());
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ class TestSetEdit extends StatelessWidget with Trans {
|
||||
final String templateNameTranslation = args['templateNameTranslation'];
|
||||
// ignore: close_sinks
|
||||
final MenuBloc menuBloc = BlocProvider.of<MenuBloc>(context);
|
||||
late TestSetEditBloc? bloc;
|
||||
late TestSetEditBloc bloc;
|
||||
final bool activeExercisePlan = Cache().activeExercisePlan != null;
|
||||
|
||||
setContext(context);
|
||||
@ -51,8 +51,20 @@ class TestSetEdit extends StatelessWidget with Trans {
|
||||
menuBloc: menuBloc),
|
||||
child: BlocConsumer<TestSetEditBloc, TestSetEditState>(listener: (context, state) {
|
||||
if (state is TestSetEditError) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(backgroundColor: Colors.orange, content: Text(state.message, style: TextStyle(color: Colors.white))));
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return DialogCommon(
|
||||
warning: true,
|
||||
title: t("Warning"),
|
||||
descriptions: t(state.message),
|
||||
text: "OK",
|
||||
onTap: () => Navigator.of(context).pushNamed("login"),
|
||||
onCancel: () => {
|
||||
Navigator.of(context).pop(),
|
||||
},
|
||||
);
|
||||
});
|
||||
} else if (state is TestSetEditSaved) {
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pushNamed("testSetExecute");
|
||||
@ -60,7 +72,7 @@ class TestSetEdit extends StatelessWidget with Trans {
|
||||
}, builder: (context, state) {
|
||||
bloc = BlocProvider.of<TestSetEditBloc>(context);
|
||||
return ModalProgressHUD(
|
||||
child: getTestSetWidget(bloc!, templateNameTranslation),
|
||||
child: getTestSetWidget(bloc, templateNameTranslation),
|
||||
inAsyncCall: state is TestSetEditLoading,
|
||||
opacity: 0.5,
|
||||
color: Colors.black54,
|
||||
@ -69,36 +81,40 @@ class TestSetEdit extends StatelessWidget with Trans {
|
||||
}))),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () {
|
||||
if (activeExercisePlan) {
|
||||
showCupertinoDialog(
|
||||
useRootNavigator: true,
|
||||
context: context,
|
||||
builder: (_) => CupertinoAlertDialog(
|
||||
title: Text(t("You have an active Test Set!") + "\n" + Cache().activeExercisePlan!.name),
|
||||
content: Column(children: [
|
||||
Divider(),
|
||||
Text(t("Do you want to override it?"), style: GoogleFonts.inter(color: Colors.black, fontSize: 16)),
|
||||
]),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(t("No, bring me there"), textAlign: TextAlign.center),
|
||||
onPressed: () => {
|
||||
Navigator.pop(context),
|
||||
Navigator.pop(context),
|
||||
Navigator.of(context).pushNamed("testSetExecute"),
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(t("Yes")),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
startTrainingDialog(bloc);
|
||||
},
|
||||
)
|
||||
],
|
||||
));
|
||||
if (Cache().userLoggedIn == null) {
|
||||
bloc.add(TestSetEditAddError(message: "Please log in"));
|
||||
} else {
|
||||
startTrainingDialog(bloc);
|
||||
if (activeExercisePlan) {
|
||||
showCupertinoDialog(
|
||||
useRootNavigator: true,
|
||||
context: context,
|
||||
builder: (_) => CupertinoAlertDialog(
|
||||
title: Text(t("You have an active Test Set!") + "\n" + Cache().activeExercisePlan!.name),
|
||||
content: Column(children: [
|
||||
Divider(),
|
||||
Text(t("Do you want to override it?"), style: GoogleFonts.inter(color: Colors.black, fontSize: 16)),
|
||||
]),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(t("No, bring me there"), textAlign: TextAlign.center),
|
||||
onPressed: () => {
|
||||
Navigator.pop(context),
|
||||
Navigator.pop(context),
|
||||
Navigator.of(context).pushNamed("testSetExecute"),
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(t("Yes")),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
startTrainingDialog(bloc);
|
||||
},
|
||||
)
|
||||
],
|
||||
));
|
||||
} else {
|
||||
startTrainingDialog(bloc);
|
||||
}
|
||||
}
|
||||
},
|
||||
backgroundColor: Colors.orange[800],
|
||||
@ -249,7 +265,9 @@ class TestSetEdit extends StatelessWidget with Trans {
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(24.0),
|
||||
child: GestureDetector(
|
||||
onTap: () => bloc.add(TestSetEditAddExerciseType(indexKey: index)),
|
||||
onTap: () {
|
||||
bloc.add(TestSetEditAddExerciseType(indexKey: index));
|
||||
},
|
||||
child: Container(
|
||||
color: Colors.yellow[700],
|
||||
child: Center(
|
||||
|
@ -9,6 +9,7 @@ class DialogCommon extends StatefulWidget {
|
||||
final VoidCallback? onCancel;
|
||||
String? description2, description3;
|
||||
final Image? img;
|
||||
final bool warning;
|
||||
|
||||
DialogCommon(
|
||||
{Key? key,
|
||||
@ -19,7 +20,8 @@ class DialogCommon extends StatefulWidget {
|
||||
required this.text,
|
||||
this.img,
|
||||
required this.onTap,
|
||||
required this.onCancel})
|
||||
required this.onCancel,
|
||||
this.warning = false})
|
||||
: super(key: key) {
|
||||
description2 = description2 ?? "";
|
||||
description3 = description3 ?? "";
|
||||
@ -72,11 +74,11 @@ class _DialogPremiumState extends State<DialogCommon> with Trans {
|
||||
alignment: AlignmentDirectional.topEnd,
|
||||
children: [
|
||||
Text(
|
||||
widget.title + " ",
|
||||
widget.title,
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.archivoBlack(
|
||||
fontSize: 20,
|
||||
color: Colors.yellow[400],
|
||||
color: widget.warning ? Colors.red[800] : Colors.yellow[400],
|
||||
shadows: <Shadow>[
|
||||
Shadow(
|
||||
offset: Offset(5.0, 5.0),
|
||||
@ -173,7 +175,9 @@ class _DialogPremiumState extends State<DialogCommon> with Trans {
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Image.asset('asset/icon/gomb_orange_c.png', width: 100, height: 45),
|
||||
widget.warning
|
||||
? Image.asset('asset/icon/gomb_pink_b.png', width: 100, height: 45)
|
||||
: Image.asset('asset/icon/gomb_orange_c.png', width: 100, height: 45),
|
||||
Text(
|
||||
t("OK"),
|
||||
style: TextStyle(fontSize: 16, color: Colors.white),
|
||||
|
@ -2,9 +2,11 @@ import 'package:aitrainer_app/bloc/session/session_bloc.dart';
|
||||
import 'package:aitrainer_app/bloc/settings/settings_bloc.dart';
|
||||
import 'package:aitrainer_app/model/cache.dart';
|
||||
import 'package:aitrainer_app/service/logging.dart';
|
||||
import 'package:aitrainer_app/util/trans.dart';
|
||||
import 'package:aitrainer_app/view/login.dart';
|
||||
import 'package:aitrainer_app/view/menu_page.dart';
|
||||
import 'package:aitrainer_app/view/registration.dart';
|
||||
import 'package:aitrainer_app/widgets/dialog_common.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
@ -22,7 +24,7 @@ class AitrainerHome extends StatefulWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _HomePageState extends State<AitrainerHome> with Logging {
|
||||
class _HomePageState extends State<AitrainerHome> with Logging, Trans {
|
||||
GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
||||
|
||||
@override
|
||||
@ -50,16 +52,26 @@ class _HomePageState extends State<AitrainerHome> with Logging {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
setContext(context);
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
body: BlocConsumer<SessionBloc, SessionState>(listener: (context, state) {
|
||||
if (state is SessionFailure) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text(
|
||||
state.message,
|
||||
),
|
||||
backgroundColor: Colors.orange,
|
||||
));
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return DialogCommon(
|
||||
title: t("Error"),
|
||||
descriptions: t(state.message),
|
||||
text: "OK",
|
||||
onTap: () => {
|
||||
Navigator.of(context).pop(),
|
||||
},
|
||||
onCancel: () => {
|
||||
Navigator.of(context).pop(),
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}, builder: (context, state) {
|
||||
if (state is SessionInitial) {
|
||||
@ -78,8 +90,8 @@ class _HomePageState extends State<AitrainerHome> with Logging {
|
||||
}
|
||||
} else {
|
||||
log("home: unknown state");
|
||||
//return MenuPage(parent: 0);
|
||||
return LoginPage();
|
||||
return MenuPage(parent: 0);
|
||||
//return LoginPage();
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'dart:collection';
|
||||
import 'dart:ui';
|
||||
import 'package:aitrainer_app/bloc/tutorial/tutorial_bloc.dart';
|
||||
import 'package:aitrainer_app/model/exercise_ability.dart';
|
||||
import 'package:aitrainer_app/bloc/menu/menu_bloc.dart';
|
||||
import 'package:aitrainer_app/library/custom_icon_icons.dart';
|
||||
import 'package:aitrainer_app/util/enums.dart';
|
||||
import 'package:aitrainer_app/util/track.dart';
|
||||
import 'package:aitrainer_app/widgets/menu_image.dart';
|
||||
@ -14,6 +14,7 @@ import 'package:aitrainer_app/model/workout_menu_tree.dart';
|
||||
import 'package:aitrainer_app/service/logging.dart';
|
||||
import 'package:aitrainer_app/util/trans.dart';
|
||||
import 'package:aitrainer_app/widgets/dialog_common.dart';
|
||||
import 'package:aitrainer_app/widgets/tutorial_widget.dart';
|
||||
import 'package:badges/badges.dart';
|
||||
import 'package:ezanimation/ezanimation.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@ -39,6 +40,7 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
final double baseWidth = 312;
|
||||
final double baseHeight = 675.2;
|
||||
late MenuBloc menuBloc;
|
||||
late TutorialBloc tutorialBloc;
|
||||
final scrollController = ScrollController();
|
||||
final bool activeExercisePlan = Cache().activeExercisePlan != null;
|
||||
final EzAnimation animation = EzAnimation(35.0, 10.0, Duration(seconds: 2), reverseCurve: Curves.linear);
|
||||
@ -52,29 +54,52 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
animation.reverse();
|
||||
}
|
||||
});
|
||||
animation.addListener(() {
|
||||
//setState(() {});
|
||||
});
|
||||
animation.addListener(() {});
|
||||
}
|
||||
|
||||
/// We require the initializers to run after the loading screen is rendered
|
||||
SchedulerBinding.instance!.addPostFrameCallback((_) {
|
||||
menuBloc.add(MenuCreate());
|
||||
//runDelayedEvent();
|
||||
});
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Future runDelayedEvent() async {
|
||||
print("runDelayedEvent start");
|
||||
bool isFirst = false;
|
||||
await Future.delayed(Duration(milliseconds: 600), () async {
|
||||
if (tutorialBloc.isActive == false) {
|
||||
print("Activate tutorial");
|
||||
tutorialBloc.canActivate = true;
|
||||
tutorialBloc.isActive = true;
|
||||
tutorialBloc.menuBloc = menuBloc;
|
||||
tutorialBloc.add(TutorialLoad());
|
||||
tutorialBloc.init();
|
||||
isFirst = true;
|
||||
}
|
||||
});
|
||||
final bool canActivate = tutorialBloc.activateTutorial();
|
||||
if (canActivate) {
|
||||
if (!isFirst) {
|
||||
TutorialWidget().tip(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool didUpdateWidget(MenuPageWidget oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
scrollController.animateTo(5, duration: Duration(milliseconds: 300), curve: Curves.easeIn);
|
||||
runDelayedEvent();
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
menuBloc = BlocProvider.of<MenuBloc>(context);
|
||||
tutorialBloc = BlocProvider.of<TutorialBloc>(context);
|
||||
setContext(context);
|
||||
double cWidth = MediaQuery.of(context).size.width;
|
||||
double cHeight = MediaQuery.of(context).size.height;
|
||||
@ -83,11 +108,12 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
widget.parent = 0;
|
||||
}
|
||||
|
||||
return CustomScrollView(
|
||||
// Must add scrollController to sliver root
|
||||
controller: scrollController,
|
||||
scrollDirection: Axis.vertical,
|
||||
slivers: buildMenuColumn(widget.parent!, context, menuBloc, cWidth, cHeight));
|
||||
return Stack(children: [
|
||||
CustomScrollView(
|
||||
controller: scrollController,
|
||||
scrollDirection: Axis.vertical,
|
||||
slivers: buildMenuColumn(widget.parent!, context, menuBloc, cWidth, cHeight)),
|
||||
]);
|
||||
}
|
||||
|
||||
List<Widget> buildMenuColumn(int parent, BuildContext context, MenuBloc menuBloc, double cWidth, double cHeight) {
|
||||
@ -151,19 +177,12 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
child: Stack(alignment: Alignment.bottomLeft, children: [
|
||||
TextButton(
|
||||
child: badgedIcon(workoutTree, cWidth, cHeight),
|
||||
onPressed: () => menuClick(workoutTree, menuBloc, context),
|
||||
onPressed: () => menuClick(workoutTree, menuBloc),
|
||||
),
|
||||
/* Container(
|
||||
padding: EdgeInsets.only(left: 5, bottom: 5, right: 5),
|
||||
height: 80,
|
||||
child: Container(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
),
|
||||
), */
|
||||
Container(
|
||||
padding: EdgeInsets.only(left: 15, bottom: 8, right: 15),
|
||||
child: GestureDetector(
|
||||
onTap: () => menuClick(workoutTree, menuBloc, context),
|
||||
onTap: () => menuClick(workoutTree, menuBloc),
|
||||
child: Text(
|
||||
workoutTree.name,
|
||||
maxLines: 4,
|
||||
@ -174,19 +193,6 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
])))));
|
||||
});
|
||||
}
|
||||
/* LiveSliverList sliverList = LiveSliverList(
|
||||
// And attach root sliver scrollController to widgets
|
||||
controller: scrollController,
|
||||
|
||||
itemCount: _columnChildren.length,
|
||||
reAnimateOnVisibility: false,
|
||||
showItemDuration: Duration(milliseconds: 100),
|
||||
itemBuilder: (BuildContext context, int index, Animation<double> animation) => FadeTransition(
|
||||
opacity: animation,
|
||||
child: _columnChildren[index],
|
||||
),
|
||||
*/
|
||||
//delegate: SliverChildListDelegate(_columnChildren),
|
||||
|
||||
SliverList sliverList = SliverList(
|
||||
delegate: SliverChildListDelegate(_columnChildren),
|
||||
@ -273,7 +279,6 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
|
||||
SliverAppBar getInfoWidget(BuildContext context, MenuBloc menuBloc) {
|
||||
menuBloc.setContext(context);
|
||||
menuBloc.setMenuInfo();
|
||||
|
||||
SliverAppBar sliverAppBar = SliverAppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
@ -286,28 +291,6 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
: SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () => {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return DialogCommon(
|
||||
title: menuBloc.infoTitle,
|
||||
descriptions: menuBloc.infoText,
|
||||
description2: menuBloc.infoText2,
|
||||
description3: menuBloc.infoText3,
|
||||
text: "OK",
|
||||
onTap: () => {Navigator.of(context).pop()},
|
||||
onCancel: () => {Navigator.of(context).pop()},
|
||||
);
|
||||
})
|
||||
},
|
||||
child: Icon(
|
||||
CustomIcon.question_circle,
|
||||
color: Colors.orange[400],
|
||||
size: 40,
|
||||
)),
|
||||
MenuSearchBar(
|
||||
listItems: menuBloc.menuTreeRepository.menuAsExercise,
|
||||
onFind: (value) {
|
||||
@ -365,28 +348,28 @@ class _MenuPageWidgetState extends State<MenuPageWidget> with Trans, Logging {
|
||||
return sliverAppBar;
|
||||
}
|
||||
|
||||
void menuClick(WorkoutMenuTree workoutTree, MenuBloc menuBloc, BuildContext context) {
|
||||
if (Cache().userLoggedIn == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
backgroundColor: Colors.orange,
|
||||
content: Text(AppLocalizations.of(context)!.translate('Please log in'), style: TextStyle(color: Colors.white))));
|
||||
void menuClick(WorkoutMenuTree workoutTree, MenuBloc menuBloc) {
|
||||
if (tutorialBloc.isActive) {
|
||||
final String checkText = workoutTree.nameEnglish;
|
||||
if (!tutorialBloc.checkAction(checkText)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (workoutTree.child == false) {
|
||||
if (menuBloc.ability != null && ExerciseAbility.mini_test_set.equalsTo(menuBloc.ability!) && workoutTree.parent != 0) {
|
||||
HashMap args = HashMap();
|
||||
args['templateName'] = workoutTree.nameEnglish;
|
||||
args['templateNameTranslation'] = workoutTree.name;
|
||||
Navigator.of(context).pushNamed('testSetEdit', arguments: args);
|
||||
}
|
||||
menuBloc.add(MenuTreeDown(item: workoutTree, parent: workoutTree.id));
|
||||
} else {
|
||||
if (workoutTree.child == false) {
|
||||
if (menuBloc.ability != null && ExerciseAbility.mini_test_set.equalsTo(menuBloc.ability!) && workoutTree.parent != 0) {
|
||||
HashMap args = HashMap();
|
||||
args['templateName'] = workoutTree.nameEnglish;
|
||||
args['templateNameTranslation'] = workoutTree.name;
|
||||
Navigator.of(context).pushNamed('testSetEdit', arguments: args);
|
||||
}
|
||||
menuBloc.add(MenuTreeDown(item: workoutTree, parent: workoutTree.id));
|
||||
} else {
|
||||
menuBloc.add(MenuClickExercise(exerciseTypeId: workoutTree.id));
|
||||
menuBloc.add(MenuClickExercise(exerciseTypeId: workoutTree.id));
|
||||
|
||||
if (workoutTree.exerciseType!.name == "Custom" && Cache().userLoggedIn!.admin == 1) {
|
||||
Navigator.of(context).pushNamed('exerciseCustomPage', arguments: workoutTree.exerciseType);
|
||||
} else {
|
||||
Navigator.of(context).pushNamed('exerciseNewPage', arguments: workoutTree.exerciseType);
|
||||
}
|
||||
if (workoutTree.exerciseType!.name == "Custom" && Cache().userLoggedIn!.admin == 1) {
|
||||
Navigator.of(context).pushNamed('exerciseCustomPage', arguments: workoutTree.exerciseType);
|
||||
} else {
|
||||
Navigator.of(context).pushNamed('exerciseNewPage', arguments: workoutTree.exerciseType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
152
lib/widgets/tutorial_widget.dart
Normal file
@ -0,0 +1,152 @@
|
||||
import 'package:aitrainer_app/bloc/tutorial/tutorial_bloc.dart';
|
||||
import 'package:aitrainer_app/service/logging.dart';
|
||||
import 'package:aitrainer_app/util/trans.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:flutter_html/style.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:aitrainer_app/library/super_tooltip.dart';
|
||||
|
||||
class TutorialWidget with Trans, Logging {
|
||||
static final TutorialWidget _singleton = TutorialWidget._internal();
|
||||
SuperTooltip? tooltip;
|
||||
TutorialWidget._internal();
|
||||
factory TutorialWidget() {
|
||||
return _singleton;
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (tooltip != null && tooltip!.isOpen) {
|
||||
tooltip!.close();
|
||||
}
|
||||
}
|
||||
|
||||
void tip(BuildContext context) {
|
||||
setContext(context);
|
||||
var renderBox = context.findRenderObject() as RenderBox;
|
||||
final overlay = Overlay.of(context)!.context.findRenderObject() as RenderBox?;
|
||||
|
||||
var targetGlobalCenter = renderBox.localToGlobal(renderBox.size.center(Offset.zero), ancestor: overlay);
|
||||
|
||||
final TutorialBloc bloc = BlocProvider.of<TutorialBloc>(context);
|
||||
|
||||
if (tooltip != null && tooltip!.isOpen) {
|
||||
tooltip!.rebuild();
|
||||
}
|
||||
|
||||
Rect? area;
|
||||
if (bloc.action != null) {
|
||||
area = bloc.action!.showBubble == true
|
||||
? Rect.fromLTWH(targetGlobalCenter.dx - bloc.action!.bubbleX, targetGlobalCenter.dy - bloc.action!.bubbleY,
|
||||
bloc.action!.bubbleWidth.toDouble(), bloc.action!.bubbleHeight.toDouble())
|
||||
: null;
|
||||
}
|
||||
|
||||
final double mediaSize = MediaQuery.of(context).size.width;
|
||||
|
||||
double fontSize = 14;
|
||||
if (mediaSize > 400) {
|
||||
fontSize = 15;
|
||||
} else if (mediaSize < 375) {
|
||||
fontSize = 13;
|
||||
}
|
||||
tooltip = SuperTooltip(
|
||||
top: bloc.top,
|
||||
left: bloc.left,
|
||||
backgroundColor: Colors.black.withOpacity(0.7),
|
||||
popupDirection: bloc.action == null || bloc.action!.direction == "up" ? TooltipDirection.up : TooltipDirection.down,
|
||||
maxWidth: 390,
|
||||
minWidth: 300,
|
||||
minHeight: 100,
|
||||
maxHeight: 300,
|
||||
borderColor: Colors.orange,
|
||||
borderWidth: 1.0,
|
||||
minimumOutSidePadding: 20,
|
||||
snapsFarAwayVertically: false,
|
||||
showCloseButton: ShowCloseButton.inside,
|
||||
closeButtonColor: Colors.grey,
|
||||
dismissOnTapOutside: false,
|
||||
outsideBackgroundColor: Colors.black.withOpacity(0.6),
|
||||
hasShadow: true,
|
||||
touchThrougArea: area,
|
||||
onClose: () => bloc.add(TutorialFinished()),
|
||||
custom: true,
|
||||
touchThroughAreaShape: ClipAreaShape.oval,
|
||||
content: new Material(
|
||||
color: Colors.transparent,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 20.0),
|
||||
child: Stack(alignment: Alignment.bottomRight, children: [
|
||||
SingleChildScrollView(
|
||||
child: Html(
|
||||
data: bloc.actualText! + "<p><br/> </p>",
|
||||
//Optional parameters:
|
||||
style: {
|
||||
"p": Style(
|
||||
color: Colors.white,
|
||||
fontSize: FontSize(fontSize),
|
||||
padding: const EdgeInsets.all(4),
|
||||
),
|
||||
"li": Style(
|
||||
color: Colors.white,
|
||||
fontSize: FontSize(fontSize),
|
||||
padding: const EdgeInsets.only(left: 4),
|
||||
),
|
||||
"h2": Style(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: FontSize.larger,
|
||||
//padding: const EdgeInsets.all(4),
|
||||
),
|
||||
"h1": Style(
|
||||
color: Colors.yellow[400],
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: FontSize.larger,
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.all(4),
|
||||
),
|
||||
},
|
||||
)),
|
||||
bloc.showCheckText
|
||||
? bloc.checks.length > 1
|
||||
? Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary: Colors.transparent,
|
||||
),
|
||||
onPressed: () => {bloc.add(TutorialNext(text: bloc.checks[0]))},
|
||||
child: Text("« " + t(bloc.checks[0]),
|
||||
style: GoogleFonts.archivoBlack(color: Colors.orange[400]!, fontSize: fontSize)),
|
||||
),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary: Colors.transparent,
|
||||
),
|
||||
onPressed: () => {bloc.add(TutorialNext(text: bloc.checks[1]))},
|
||||
child: Text(t(bloc.checks[1]) + " »",
|
||||
style: GoogleFonts.archivoBlack(color: Colors.orange[400]!, fontSize: fontSize)),
|
||||
),
|
||||
],
|
||||
)
|
||||
: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
primary: Colors.transparent,
|
||||
),
|
||||
onPressed: () => {
|
||||
//tooltip!.rebuild(context),
|
||||
bloc.add(TutorialNext(text: bloc.checks[0])),
|
||||
},
|
||||
child: Text(t(bloc.checks[0]) + " »",
|
||||
style: GoogleFonts.archivoBlack(color: Colors.orange[400]!, fontSize: fontSize)),
|
||||
)
|
||||
: Offstage(),
|
||||
]),
|
||||
)),
|
||||
);
|
||||
|
||||
tooltip!.show(context);
|
||||
}
|
||||
}
|
25
pubspec.yaml
@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 1.1.13+67
|
||||
version: 1.1.14+68
|
||||
|
||||
environment:
|
||||
sdk: ">=2.12.0 <3.0.0"
|
||||
@ -27,7 +27,7 @@ dependencies:
|
||||
cupertino_icons: ^1.0.0
|
||||
google_fonts: ^2.0.0
|
||||
devicelocale: ^0.4.1
|
||||
sentry: ^5.0.0
|
||||
sentry_flutter: ^5.0.0
|
||||
flutter_bloc: ^7.0.0
|
||||
equatable: ^2.0.0
|
||||
|
||||
@ -49,13 +49,15 @@ dependencies:
|
||||
purchases_flutter: ^3.2.1
|
||||
package_info: ^2.0.0
|
||||
ezanimation: ^0.5.0
|
||||
flutter_fadein: ^2.0.0
|
||||
confetti: ^0.6.0-nullsafety
|
||||
crypto: ^3.0.0
|
||||
carousel_slider: ^4.0.0-nullsafety.0
|
||||
#dropdown_search: ^0.5.0
|
||||
convex_bottom_bar: ^3.0.0
|
||||
|
||||
|
||||
flutter_app_badger: ^1.2.0
|
||||
#super_tooltip: ^1.0.1
|
||||
|
||||
firebase_core: ^1.0.3
|
||||
firebase_analytics: ^8.0.0-dev.2
|
||||
firebase_messaging: ^9.1.1
|
||||
@ -65,10 +67,11 @@ dependencies:
|
||||
google_sign_in: ^5.0.1
|
||||
apple_sign_in: ^0.1.0
|
||||
|
||||
smartlook: ^1.0.7
|
||||
#smartlook: ^1.0.7
|
||||
flurry: ^0.0.4
|
||||
flutter_uxcam: ^1.3.2
|
||||
|
||||
#animated_widgets: ^1.0.6
|
||||
animated_widgets: ^1.0.6
|
||||
|
||||
mockito: ^5.0.3
|
||||
sqflite: ^2.0.0+3
|
||||
@ -139,8 +142,6 @@ flutter:
|
||||
- asset/image/WT_sales_background.jpg
|
||||
- asset/image/WT_sales_background_3x5.jpg
|
||||
- asset/image/WT_menu_dark.jpg
|
||||
- asset/image/Gain_muscle.jpg
|
||||
- asset/image/WT_weight_loss.jpg
|
||||
- asset/image/WT_welcome.jpg
|
||||
- asset/image/WT_Results_for_runners.jpg
|
||||
- asset/image/WT_Results_for_female.jpg
|
||||
@ -156,14 +157,22 @@ flutter:
|
||||
- asset/image/testemfejl400x400.jpg
|
||||
- asset/image/izomcsop400400.jpg
|
||||
- asset/image/edzesnaplom400400.jpg
|
||||
- asset/image/endurance.jpg
|
||||
- asset/image/exercise_plan_stars.jpg
|
||||
- asset/image/exercise_plan_special.jpg
|
||||
- asset/image/exercise_plan_execute.jpg
|
||||
- asset/image/exercise_plan_custom.jpg
|
||||
- asset/image/exercise_plan_suggested.jpg
|
||||
- asset/image/explosiveness.jpg
|
||||
- asset/image/flexibility.jpg
|
||||
- asset/image/predictions.jpg
|
||||
- asset/image/man_sizes.png
|
||||
- asset/image/gain_muscle.jpg
|
||||
- asset/image/gain_strength.jpg
|
||||
- asset/image/muscle_endurance.jpg
|
||||
- asset/image/shape_forming.jpg
|
||||
- asset/image/woman_sizes.png
|
||||
- asset/image/weight_loss.jpg
|
||||
- asset/image/merleg.png
|
||||
- asset/image/BMI_graph_C.png
|
||||
- asset/image/BMI_mutato.png
|
||||
|