WT 1.1.14 Tutorial, Sports, New goals

This commit is contained in:
bossanyit 2021-05-02 17:12:42 +02:00
parent 5fe8f76f48
commit c901b07bf8
65 changed files with 2472 additions and 620 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

BIN
asset/image/endurance.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

BIN
asset/image/flexibility.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

BIN
asset/image/weight_loss.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

View File

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

View File

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

View File

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

View File

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

View File

@ -54,8 +54,6 @@
</dict>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
<string>fetch</string>
<string>remote-notification</string>
</array>
<key>UILaunchStoryboardName</key>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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();
}
}
}

View 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();
}

View 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];
}

View File

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

View File

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

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

View File

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

View File

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

View File

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

View 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();
}
}

View File

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

View 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();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View 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;
}
}

View File

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

View File

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

View File

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

View File

@ -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]),
),
),

View File

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

View File

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

View File

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

View File

@ -229,6 +229,9 @@ class _ExerciseControlPage extends State<ExerciseControlPage> with Trans {
numberPickForm(exerciseBloc, 2),
Divider(),
numberPickForm(exerciseBloc, 3),
SizedBox(
height: 80,
)
]),
)),
TimerWidget(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.1.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