v1.0.0 outsourced from aitrainer_app
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:workouttest_util/util/logging.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class AppLanguage with Logging {
|
||||
static final AppLanguage _singleton = AppLanguage._internal();
|
||||
|
||||
Locale? _appLocale = Locale('en');
|
||||
|
||||
factory AppLanguage() {
|
||||
return _singleton;
|
||||
}
|
||||
|
||||
AppLanguage._internal();
|
||||
|
||||
static Future<AppLanguage> getInstance() async {
|
||||
return _singleton;
|
||||
}
|
||||
|
||||
Locale get appLocal => _appLocale ?? Locale("en");
|
||||
|
||||
Future<void> fetchLocale() async {
|
||||
var prefs = await SharedPreferences.getInstance();
|
||||
String? langCode = prefs.getString('language_code');
|
||||
log(" ---- lang code $langCode");
|
||||
if (langCode == null) {
|
||||
_appLocale = Locale('en');
|
||||
} else {
|
||||
_appLocale = Locale(langCode);
|
||||
}
|
||||
log(" ---- Fetched lang: " + _appLocale.toString());
|
||||
}
|
||||
|
||||
getLocale(SharedPreferences prefs) {
|
||||
String? langCode = prefs.getString('language_code');
|
||||
if (langCode == null) {
|
||||
final String localName = Platform.localeName;
|
||||
if (localName.endsWith("HU")) {
|
||||
_appLocale = Locale('hu');
|
||||
langCode = "hu";
|
||||
} else {
|
||||
_appLocale = Locale('en');
|
||||
langCode = "en";
|
||||
}
|
||||
}
|
||||
_appLocale = Locale(langCode);
|
||||
log(" ---- Get lang: " + _appLocale.toString() + " lang code $langCode");
|
||||
}
|
||||
|
||||
void changeLanguage(Locale type) async {
|
||||
var prefs = await SharedPreferences.getInstance();
|
||||
if (_appLocale == type) {
|
||||
return;
|
||||
}
|
||||
if (type == Locale("hu")) {
|
||||
_appLocale = Locale("hu");
|
||||
await prefs.setString('language_code', 'hu');
|
||||
await prefs.setString('countryCode', 'HU');
|
||||
} else {
|
||||
_appLocale = Locale("en");
|
||||
await prefs.setString('language_code', 'en');
|
||||
await prefs.setString('countryCode', 'US');
|
||||
}
|
||||
log(" ---- Stored lang: " + _appLocale.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:workouttest_util/util/logging.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class AppLocalizations with Logging {
|
||||
late Locale locale;
|
||||
bool isTest;
|
||||
late Map<String, String> _localizedStrings;
|
||||
|
||||
AppLocalizations(this.locale, {this.isTest = false});
|
||||
|
||||
// Helper method to keep the code in the widgets concise
|
||||
// Localizations are accessed using an InheritedWidget "of" syntax
|
||||
static AppLocalizations? of(BuildContext context) {
|
||||
return Localizations.of<AppLocalizations>(context, AppLocalizations);
|
||||
}
|
||||
|
||||
// Static member to have a simple access to the delegate from the MaterialApp
|
||||
static const LocalizationsDelegate<AppLocalizations> delegate = AppLocalizationsDelegate();
|
||||
|
||||
setLocale(Locale locale) {
|
||||
this.locale = locale;
|
||||
}
|
||||
|
||||
Future<bool> load() async {
|
||||
// Load the language JSON file from the "lang" folder
|
||||
log(" -- load language pieces " + locale.languageCode);
|
||||
String jsonString = await rootBundle.loadString('i18n/${locale.languageCode}.json');
|
||||
Map<String, dynamic> jsonMap = json.decode(jsonString);
|
||||
|
||||
_localizedStrings = jsonMap.map((key, value) {
|
||||
return MapEntry(key, value.toString());
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<AppLocalizations> loadTest(Locale locale) async {
|
||||
return AppLocalizations(locale);
|
||||
}
|
||||
|
||||
// This method will be called from every widget which needs a localized text
|
||||
String translate(String key) {
|
||||
if (isTest) return key;
|
||||
String? translated = _localizedStrings[key];
|
||||
return translated != null ? translated : key;
|
||||
}
|
||||
}
|
||||
|
||||
class AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||
final bool isTest;
|
||||
// This delegate instance will never change (it doesn't even have fields!)
|
||||
// It can provide a constant constructor.
|
||||
const AppLocalizationsDelegate({this.isTest = false});
|
||||
|
||||
@override
|
||||
bool isSupported(Locale locale) {
|
||||
// Include all of your supported language codes here
|
||||
return ['en', 'hu'].contains(locale.languageCode);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AppLocalizations> load(Locale locale) async {
|
||||
// AppLocalizations class is where the JSON loading actually runs
|
||||
AppLocalizations localizations = new AppLocalizations(locale, isTest: this.isTest);
|
||||
if (isTest) {
|
||||
await localizations.loadTest(locale);
|
||||
} else {
|
||||
await localizations.load();
|
||||
}
|
||||
return localizations;
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReload(AppLocalizationsDelegate old) => false;
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
import 'dart:convert';
|
||||
import 'package:workouttest_util/util/app_language.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class DateRate {
|
||||
static String daily = "daily";
|
||||
static String weekly = "weekly";
|
||||
static String monthly = "monthly";
|
||||
static String yearly = "yearly";
|
||||
}
|
||||
|
||||
mixin Common {
|
||||
final emailError = "Please type a right email address here.";
|
||||
final passwordError = "The password must have at least 8 characters.";
|
||||
|
||||
String toJson(Map<String, String> map) {
|
||||
String rc = "{";
|
||||
map.forEach((key, value) {
|
||||
rc += "'$key':'$value'";
|
||||
});
|
||||
rc += "}";
|
||||
return rc;
|
||||
}
|
||||
|
||||
String getDateLocale(DateTime datetime, bool timeDisplay) {
|
||||
var date = datetime;
|
||||
|
||||
String dateName = DateFormat(DateFormat.YEAR_NUM_MONTH_DAY, AppLanguage().appLocal.toString()).format(date.toUtc());
|
||||
if (timeDisplay) {
|
||||
dateName += " " + DateFormat(DateFormat.HOUR_MINUTE, AppLanguage().appLocal.toString()).format(date.toUtc());
|
||||
}
|
||||
|
||||
return dateName;
|
||||
}
|
||||
|
||||
String utf8convert(String text) {
|
||||
List<int> bytes = text.toString().codeUnits;
|
||||
return utf8.decode(bytes);
|
||||
}
|
||||
|
||||
double mediaSizeWidth(BuildContext context) {
|
||||
return MediaQuery.of(context).size.width;
|
||||
}
|
||||
|
||||
/// Calculates week number from a date as per https://en.wikipedia.org/wiki/ISO_week_date#Calculation
|
||||
int weekNumber(DateTime date) {
|
||||
int dayOfYear = int.parse(DateFormat("D").format(date));
|
||||
return ((dayOfYear - date.weekday + 10) / 7).floor();
|
||||
}
|
||||
|
||||
static String? emailValidation(String? email) {
|
||||
final String error = "Please type an email address";
|
||||
if (email == null) {
|
||||
return error;
|
||||
}
|
||||
bool emailValid = RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+@[a-zA-Z0-9]+\.[a-zA-Z]+").hasMatch(email);
|
||||
return emailValid ? null : error;
|
||||
}
|
||||
|
||||
static String? passwordValidation(String? value) {
|
||||
final String error = "Password too short";
|
||||
if (value == null || value.length == 0) {
|
||||
return error;
|
||||
}
|
||||
bool valid = 8 < value.length;
|
||||
return valid ? null : error;
|
||||
}
|
||||
|
||||
static normalizeDecimal(String value) {
|
||||
if (value.isEmpty) {
|
||||
return 0;
|
||||
}
|
||||
value = value.replaceFirst(",", ".");
|
||||
value = value.replaceAll(RegExp(r'[^0-9.]'), "");
|
||||
return value;
|
||||
}
|
||||
|
||||
double calculate1RM(double weight, double repeat) {
|
||||
if (weight == 0 || repeat == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
double rmWendler = weight * repeat * 0.0333 + weight;
|
||||
double rmOconner = weight * (1 + repeat / 40);
|
||||
//print("Weight: $weight repeat: $repeat, $rmWendler, Oconner: $rmOconner");
|
||||
double average = (rmWendler + rmOconner) / 2;
|
||||
|
||||
return average;
|
||||
}
|
||||
|
||||
double getRepeatByOneRepMax(double oneRepMax, double weight) {
|
||||
double repeats = ( 80 * oneRepMax - 80 * weight ) / ( weight * ( 40 * 0.0333 + 1) );
|
||||
//print("getRepeatsByOneRepMax: $repeats");
|
||||
return repeats;
|
||||
}
|
||||
|
||||
int calculateRepeatBy1RMPercent(double oneRepMax, double weight, double percent) {
|
||||
return getRepeatByOneRepMax(oneRepMax , weight * percent).round();
|
||||
}
|
||||
|
||||
double calculate1RMPercentByRepeat(double oneRepMax, double weight, double repeat) {
|
||||
double percent = 80 * oneRepMax / (weight * ( repeat * ( 40 * 0.0333 + 1 ) + 80));
|
||||
return percent;
|
||||
}
|
||||
|
||||
static double get1RMPercent(int repeats) {
|
||||
double percent = 1;
|
||||
|
||||
if (repeats >= 35) {
|
||||
percent = 0.50;
|
||||
} else if (repeats > 12) {
|
||||
percent = (100 - 2 * repeats) / 100;
|
||||
} else {
|
||||
percent = (100.0 - 2 * repeats) / 100;
|
||||
}
|
||||
//print("1RM Percent: $percent repeats: $repeats");
|
||||
return percent;
|
||||
}
|
||||
|
||||
static double roundWeight(double weight) {
|
||||
double rounded = weight.round().toDouble();
|
||||
|
||||
if (weight > 35) {
|
||||
final double remainder = weight % 5;
|
||||
if (remainder < 1) {
|
||||
rounded = ((weight / 5).floor() * 5).toDouble();
|
||||
} else if (remainder > 1 && remainder <= 2.5) {
|
||||
rounded = (weight / 5).floor() * 5 + 2.5;
|
||||
} else if (remainder > 2.5 && remainder < 3.25) {
|
||||
rounded = (weight / 5).floor() * 5 + 2.5;
|
||||
} else {
|
||||
rounded = (((weight / 5).ceil() * 5)).toDouble();
|
||||
}
|
||||
}
|
||||
|
||||
return rounded;
|
||||
}
|
||||
|
||||
static int calculateQuantityByChangedWeight(double initialRM, double weight, double repeat) {
|
||||
final double repeatWendler = (initialRM - weight) / 0.0333 / weight;
|
||||
final double repeatOconner = (initialRM / weight - 1) * 40;
|
||||
final newRepeat = ((repeatOconner + repeatWendler) / 2).ceil();
|
||||
print("Initial 1RM: $initialRM Weight: $weight repeatWendler: $repeatWendler repeat Oconner: $repeatOconner. NEW REPEAT: $newRepeat");
|
||||
return newRepeat;
|
||||
}
|
||||
|
||||
static int reCalculateRepeatsByChangedWeight(double weight, double repeat, double changedWeight) {
|
||||
final double rmWendler = weight * repeat * 0.0333 + weight;
|
||||
final double rmOconner = weight * (1 + repeat / 40);
|
||||
|
||||
final double repeatWendler = (rmWendler - changedWeight) / 0.0333 / changedWeight;
|
||||
final double repeatOconner = (rmOconner / changedWeight - 1) * 40;
|
||||
final newRepeat = ((repeatOconner + repeatWendler) / 2).ceil();
|
||||
print("Weight: $weight changedWeight: $changedWeight repeatWendler: $repeatWendler repeat Oconner: $repeatOconner. NEW REPEAT: $newRepeat");
|
||||
return newRepeat;
|
||||
}
|
||||
|
||||
static double calculateWeigthByChangedQuantity(double weight, double repeat, double changedRepeats) {
|
||||
final double rmWendler = weight * repeat * 0.0333 + weight;
|
||||
final double rmOconner = weight * (1 + repeat / 40);
|
||||
final double initialRM = (rmWendler + rmOconner) / 2;
|
||||
|
||||
final double weightWendler = rmWendler / (changedRepeats * 0.0333 + 1);
|
||||
final double weightOconner = rmOconner / (1 + changedRepeats / 40);
|
||||
final double newWeight = ((weightWendler + weightOconner) / 2);
|
||||
print(
|
||||
"Initial 1RM: $initialRM repeat: $repeat changedRepeat: $changedRepeats Weight: $weight weightWendler: $weightWendler weight Oconner: $weightOconner. NEW WEIGHT: $newWeight");
|
||||
return newWeight;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class CommonHoldingClass with Common {}
|
||||
@@ -0,0 +1,129 @@
|
||||
import 'package:workouttest_util/util/string_extension.dart';
|
||||
|
||||
enum LoginType { email, fb, google, apple }
|
||||
|
||||
extension LoginTypeExt on LoginType {
|
||||
bool equalsTo(LoginType type) => this.toString() == type.toString();
|
||||
bool equalsStringTo(String type) => this.toString() == type;
|
||||
}
|
||||
|
||||
enum TrackingEvent {
|
||||
enter,
|
||||
login,
|
||||
logout,
|
||||
registration,
|
||||
login_skip,
|
||||
home,
|
||||
sizes,
|
||||
sizes_save,
|
||||
my_development,
|
||||
my_exerciseplan,
|
||||
account,
|
||||
settings,
|
||||
sales_page,
|
||||
purchase_request,
|
||||
purchase_successful,
|
||||
exercise_new,
|
||||
exercise_new_no_registration,
|
||||
exercise_new_paralell,
|
||||
result,
|
||||
exercise_log,
|
||||
exercise_log_open,
|
||||
exercise_log_delete,
|
||||
exercise_log_result,
|
||||
my_body_development,
|
||||
my_muscle_development,
|
||||
my_size_development,
|
||||
my_custom_exercise_plan,
|
||||
my_custom_exercise_plan_save,
|
||||
my_exercise_plan_execute_open,
|
||||
my_exercise_plan_execute_save,
|
||||
my_special_plan,
|
||||
my_suggested_plan,
|
||||
prediction,
|
||||
search,
|
||||
exercise_device,
|
||||
customer_change,
|
||||
settings_lang,
|
||||
settings_server,
|
||||
test_set_edit,
|
||||
test_set_new,
|
||||
tutorial_step,
|
||||
tutorial_finished,
|
||||
tutorial_activate,
|
||||
terms_of_use,
|
||||
data_privacy,
|
||||
delete_account,
|
||||
faq,
|
||||
training_plan_open,
|
||||
training_plan_start,
|
||||
training_plan_execute,
|
||||
training_plan_finished,
|
||||
training_plan_custom,
|
||||
trial,
|
||||
feedback_email,
|
||||
}
|
||||
|
||||
T enumFromString<T>(Iterable<T> values, String value) {
|
||||
return values.firstWhere((type) => type.toString().split(".").last == value);
|
||||
}
|
||||
|
||||
extension TrackingEventExt on TrackingEvent {
|
||||
String enumToString() => this.toString().split(".").last;
|
||||
|
||||
bool equalsTo(TrackingEvent event) => this.toString() == event.toString();
|
||||
bool equalsStringTo(String event) => this.toString() == event;
|
||||
}
|
||||
|
||||
enum PropertyEnum { Ectomorph, Mesomorph, Endomorph }
|
||||
|
||||
extension PropertyExt on PropertyEnum {
|
||||
String toStr() => this.toString().split(".").last;
|
||||
|
||||
bool equalsTo(PropertyEnum event) => this.toString() == event.toString();
|
||||
bool equalsStringTo(String event) => this.toString() == event;
|
||||
}
|
||||
|
||||
enum SizesEnum { Weight, Height, Shoulder, Neck, Biceps, Chest, Belly, Hip, ThighTop, ThighMiddle, Knee, Calf, Ankle, Underarm, Lowerarm }
|
||||
|
||||
extension SizesExt on SizesEnum {
|
||||
String toStr() => this.toString().split(".").last;
|
||||
bool equalsTo(SizesEnum event) => this.toString() == event.toString();
|
||||
bool equalsStringTo(String event) => this.toString() == event;
|
||||
}
|
||||
|
||||
enum EvaluationText { very_poor, poor, fair, below_average, average, above_average, good, excellent, elite }
|
||||
|
||||
extension EvaluationTextExt on EvaluationText {
|
||||
String toStr() => this.toString().split(".").last;
|
||||
bool equalsTo(EvaluationText eval) => this.toString() == eval.toString();
|
||||
bool equalsStringTo(String eval) => this.toStr() == eval;
|
||||
}
|
||||
|
||||
enum ExerciseTypeTrainingPlanState { none, added, executed }
|
||||
|
||||
extension ExerciseTypeTrainingPlanStateExt on ExerciseTypeTrainingPlanState {
|
||||
String toStr() => this.toString().split(".").last;
|
||||
bool equalsTo(ExerciseTypeTrainingPlanState state) => this.toString() == state.toString();
|
||||
bool equalsStringTo(String state) => this.toStr() == state;
|
||||
}
|
||||
|
||||
enum ExerciseSaveType { test, training, test_set }
|
||||
|
||||
extension ExerciseSaveTypeExt on ExerciseSaveType {
|
||||
String toStr() => this.toString().split(".").last;
|
||||
bool equalsTo(ExerciseSaveType type) => this.toString() == type.toString();
|
||||
bool equalsStringTo(String type) => this.toStr() == type;
|
||||
}
|
||||
|
||||
enum MuscleGroup { chest, biceps, triceps, back, shoulders, core, thigh, calf }
|
||||
|
||||
extension MuscleGroupExt on MuscleGroup {
|
||||
String toStr() => this.toString().split(".").last;
|
||||
String toText() => this.toString().split(".").last.capitalize();
|
||||
bool equalsTo(MuscleGroup type) => this.toString() == type.toString();
|
||||
bool equalsStringTo(String type) => this.toStr() == type;
|
||||
String getStrByIndex(int index) => MuscleGroup.values.elementAt(index).toStr();
|
||||
MuscleGroup getByIndex(int index) => MuscleGroup.values.elementAt(index);
|
||||
String getTextByIndex(int index) => MuscleGroup.values.elementAt(index).toText();
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
class EnvironmentConfig {
|
||||
static const test_env = String.fromEnvironment('test_env');
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'package:workouttest_util/util/env.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
mixin Logging {
|
||||
void log(String message) {
|
||||
DateTime time = DateTime.now();
|
||||
print(DateFormat('yyyy-MM-dd HH:mm:ss ').format(time) + message);
|
||||
}
|
||||
|
||||
void trace(String message) {
|
||||
String testEnv = EnvironmentConfig.test_env;
|
||||
if (testEnv == "1") {
|
||||
log(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
class NotFoundException implements Exception {
|
||||
final String message;
|
||||
const NotFoundException({required this.message});
|
||||
}
|
||||
|
||||
class WorkoutTestException implements Exception {
|
||||
static const String CUSTOMER_EXISTS = "customer-exists";
|
||||
static const String LOGIN_CANCELLED = "login-cancelled";
|
||||
|
||||
final String message;
|
||||
final String code;
|
||||
const WorkoutTestException({required this.message, required this.code});
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
extension StringExtension on String {
|
||||
String capitalize() {
|
||||
return "${this[0].toUpperCase()}${this.substring(1).toLowerCase()}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import 'package:workouttest_util/model/cache.dart';
|
||||
import 'package:workouttest_util/util/logging.dart';
|
||||
import 'package:workouttest_util/service/tracking_service.dart';
|
||||
import 'package:workouttest_util/util/enums.dart';
|
||||
import 'package:workouttest_util/model/tracking.dart' as model;
|
||||
import 'package:firebase_analytics/firebase_analytics.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:posthog_session/posthog_session.dart';
|
||||
import 'package:matomo_tracker/matomo_tracker.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class Track with Logging {
|
||||
static final Track _singleton = Track._internal();
|
||||
static FirebaseAnalytics analytics = FirebaseAnalytics.instance;
|
||||
|
||||
factory Track() {
|
||||
return _singleton;
|
||||
}
|
||||
|
||||
Track._internal();
|
||||
|
||||
void track(TrackingEvent event, {String eventValue = ""}) {
|
||||
model.Tracking tracking = model.Tracking();
|
||||
tracking.customerId = Cache().userLoggedIn == null ? 0 : Cache().userLoggedIn!.customerId!;
|
||||
if (kReleaseMode) {
|
||||
tracking.event = event.enumToString();
|
||||
if (eventValue.isNotEmpty) {
|
||||
tracking.eventValue = eventValue;
|
||||
}
|
||||
tracking.dateAdd = DateTime.now();
|
||||
|
||||
TrackingApi().saveTracking(tracking);
|
||||
|
||||
FirebaseMessaging.instance.subscribeToTopic(event.enumToString());
|
||||
analytics.logEvent(name: event.enumToString(), parameters: {"value": eventValue});
|
||||
|
||||
if (eventValue.isNotEmpty) {
|
||||
MatomoTracker.instance.trackEvent(eventCategory: "wt", action: event.enumToString(), eventName: eventValue);
|
||||
} else {
|
||||
MatomoTracker.instance.trackEvent(eventCategory: "wt", action: event.enumToString());
|
||||
}
|
||||
Posthog().capture(
|
||||
eventName: event.enumToString(),
|
||||
properties: {
|
||||
'eventValue': eventValue,
|
||||
'customer': tracking.customerId,
|
||||
},
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user