Commit del proyecto final DAM Elevate.

App para el desarrollo personal encargada de generarte un plan de ejercicios fisicos segun los datos introducidos por el usuatio y una dieta semanal.
This commit is contained in:
borsal 2025-05-30 01:40:16 +02:00
commit 7ca65a2ce3
211 changed files with 14938 additions and 0 deletions

15
.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

3
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

1
.idea/.name Normal file
View File

@ -0,0 +1 @@
Elevate

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

6
.idea/compiler.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-05-28T15:57:14.003490400Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=b1fdb6a8" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>
</project>

19
.idea/gradle.xml Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

10
.idea/migrations.xml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

9
.idea/misc.xml Normal file
View File

@ -0,0 +1,9 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

6
.idea/sqlandroid.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SQLAndroidSettings">
<option name="storageVersion" value="2" />
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

50
app/build.gradle Normal file
View File

@ -0,0 +1,50 @@
plugins {
alias(libs.plugins.android.application)
}
android {
namespace 'com.borjabolufer.elevate'
compileSdk 35
defaultConfig {
applicationId "com.borjabolufer.elevate"
minSdk 24
targetSdk 35
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
buildFeatures {
viewBinding true
}
}
dependencies {
implementation libs.appcompat
implementation libs.material
implementation libs.constraintlayout
implementation libs.lifecycle.livedata.ktx
implementation libs.lifecycle.viewmodel.ktx
implementation libs.navigation.fragment
implementation libs.navigation.ui
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.pierfrancescosoffritti.androidyoutubeplayer:core:11.1.0'
}

21
app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,26 @@
package com.borjabolufer.elevate;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.borjabolufer.elevate", appContext.getPackageName());
}
}

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@drawable/logo"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApp"
tools:targetApi="31">
<activity
android:name=".SplashActivity"
android:exported="true"
android:theme="@style/Theme.MyApp">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".PreguntasActivity"
android:exported="true"
android:theme="@style/Theme.MyApp" />
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.MyApp" />
<activity
android:name=".WelcomeActivity"
android:theme="@style/Theme.MyApp"/>
<activity
android:name=".LoginActivity"
android:exported="true"
android:theme="@style/Theme.MyApp" />
<activity
android:name=".RegisterActivity"
android:theme="@style/Theme.MyApp"/>
</application>
</manifest>

View File

@ -0,0 +1,73 @@
package com.borjabolufer.elevate;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import com.borjabolufer.elevate.model.UsuarioDbHelper;
import com.borjabolufer.elevate.model.UsuarioEntity;
public class LoginActivity extends AppCompatActivity {
private static final String TAG = "LoginActivity";
private EditText editEmail, editPassword;
private Button btnLogin;
private UsuarioDbHelper dbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_loging);
editEmail = findViewById(R.id.editLoginEmail);
editPassword = findViewById(R.id.editLoginPassword);
btnLogin = findViewById(R.id.btnIniciarSesion);
TextView textIrRegistro = findViewById(R.id.textIrRegistro);
dbHelper = new UsuarioDbHelper(this);
btnLogin.setOnClickListener(v -> {
String email = editEmail.getText().toString().trim();
String password = editPassword.getText().toString();
if (email.isEmpty() || password.isEmpty()) {
Toast.makeText(LoginActivity.this, "Por favor, completa todos los campos", Toast.LENGTH_SHORT).show();
return;
}
Log.d(TAG, "Intentando iniciar sesión para el email: " + email);
UsuarioEntity usuarioVerificado = dbHelper.verificarUsuario(email, password);
if (usuarioVerificado != null) {
Log.i(TAG, "Inicio de sesión exitoso para: " + email);
Toast.makeText(LoginActivity.this, "Inicio de sesión exitoso", Toast.LENGTH_SHORT).show();
SharedPreferences prefs = getSharedPreferences("ElevatePrefs", MODE_PRIVATE);
prefs.edit().putString("user_email", email).apply();
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
finish();
} else {
Log.w(TAG, "Fallo en el inicio de sesión para: " + email);
Toast.makeText(LoginActivity.this, "Email o contraseña incorrectos", Toast.LENGTH_SHORT).show();
}
});
textIrRegistro.setOnClickListener(v -> {
Intent intent = new Intent(LoginActivity.this, RegisterActivity.class);
startActivity(intent);
});
}
}

View File

@ -0,0 +1,480 @@
package com.borjabolufer.elevate;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import com.borjabolufer.elevate.databinding.ActivityMainBinding;
import com.borjabolufer.elevate.model.Ejercicio;
import com.borjabolufer.elevate.model.Usuario;
import com.borjabolufer.elevate.model.UsuarioDbHelper;
import com.borjabolufer.elevate.ui.fragments.DetalleEjercicioFragment;
import com.borjabolufer.elevate.ui.fragments.HomeFragment;
import com.borjabolufer.elevate.ui.fragments.DietFragment;
import com.borjabolufer.elevate.utils.JsonParser;
import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.Snackbar;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private static final String PREFS_NAME = "ElevatePrefs";
private static final String KEY_USER_EMAIL = "user_email";
private AppBarConfiguration mAppBarConfiguration;
private ActivityMainBinding binding;
private String currentUserEmail;
private UsuarioDbHelper dbHelper;
private JsonParser jsonParser;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
inicializarComponentes();
setSupportActionBar(binding.appBarMain.toolbar);
configurarNavegacion();
limpiarNavegacionSiEsNecesario();
}
private void inicializarComponentes() {
dbHelper = new UsuarioDbHelper(this);
jsonParser = new JsonParser(this);
SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
currentUserEmail = prefs.getString(KEY_USER_EMAIL, null);
Log.d(TAG, "Usuario actual: " + currentUserEmail);
}
private void configurarNavegacion() {
DrawerLayout drawer = binding.drawerLayout;
NavigationView navigationView = binding.navView;
mAppBarConfiguration = new AppBarConfiguration.Builder(
R.id.nav_home,
R.id.nav_diet,
R.id.nav_profile
)
.setOpenableLayout(drawer)
.build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(navigationView, navController);
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
updateToolbarTitle(destination.getId());
Log.d(TAG, "Navegando a: " + destination.getLabel() + " (ID: " + destination.getId() + ")");
});
configurarNavigationHeader(navigationView);
}
private void configurarNavigationHeader(NavigationView navigationView) {
UsuarioDbHelper usuarioDbHelper = new UsuarioDbHelper(getApplicationContext());
View headerView = navigationView.getHeaderView(0);
TextView headerTitle = headerView.findViewById(R.id.nav_header_title);
TextView headerSubtitle = headerView.findViewById(R.id.nav_header_subtitle);
SharedPreferences prefs = getSharedPreferences("ElevatePrefs", MODE_PRIVATE);
String email = prefs.getString("user_email", "usuario@desconocido.com");
String nombre = usuarioDbHelper.obtenerNombreUsuario(email);
headerTitle.setText(nombre != null ? nombre : "Usuario");
headerSubtitle.setText(email);
}
private void limpiarNavegacionSiEsNecesario() {
try {
FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 3) {
Log.d(TAG, "Back stack muy grande (" + fm.getBackStackEntryCount() + "), limpiando...");
while (fm.getBackStackEntryCount() > 1) {
fm.popBackStackImmediate();
}
}
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
if (navController.getCurrentDestination() != null) {
int currentId = navController.getCurrentDestination().getId();
if (currentId != R.id.nav_home && currentId != R.id.nav_diet && currentId != R.id.nav_profile) {
Log.d(TAG, "Destino inválido detectado, navegando a home");
navController.navigate(R.id.nav_home);
}
}
} catch (Exception e) {
Log.e(TAG, "Error limpiando navegación", e);
}
}
@Override
protected void onResume() {
super.onResume();
verificarEstadoNavegacion();
}
private void verificarEstadoNavegacion() {
try {
FragmentManager fm = getSupportFragmentManager();
Log.d(TAG, "Fragments en back stack: " + fm.getBackStackEntryCount());
for (int i = 0; i < fm.getBackStackEntryCount(); i++) {
FragmentManager.BackStackEntry entry = fm.getBackStackEntryAt(i);
if (entry.getName() != null && entry.getName().contains("Loading")) {
Log.d(TAG, "Detectado LoadingFragment problemático, limpiando...");
fm.popBackStackImmediate(entry.getId(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
break;
}
}
limpiarFragmentsHuerfanos();
} catch (Exception e) {
Log.e(TAG, "Error verificando navegación", e);
}
}
private void limpiarFragmentsHuerfanos() {
try {
FragmentManager fm = getSupportFragmentManager();
for (Fragment fragment : fm.getFragments()) {
if (fragment != null && fragment.getClass().getSimpleName().contains("Loading")) {
Log.d(TAG, "Removiendo fragment huérfano: " + fragment.getClass().getSimpleName());
fm.beginTransaction()
.remove(fragment)
.commitAllowingStateLoss();
}
}
} catch (Exception e) {
Log.w(TAG, "Error limpiando fragments huérfanos", e);
}
}
private void mostrarMensajeSinPlan() {
Snackbar.make(binding.getRoot(),
"Complete su perfil para generar su plan personalizado",
Snackbar.LENGTH_LONG)
.setAction("Completar", v -> {
Intent intent = new Intent(this, PreguntasActivity.class);
startActivity(intent);
})
.show();
}
private void iniciarEntrenamiento() {
try {
HomeFragment homeFragment = getCurrentHomeFragment();
if (homeFragment != null) {
Log.d(TAG, "Iniciando entrenamiento desde HomeFragment");
}
} catch (Exception e) {
Log.e(TAG, "Error al iniciar entrenamiento", e);
}
}
private void verRecetaCompleta() {
try {
DietFragment dietFragment = getCurrentDietFragment();
if (dietFragment != null) {
Log.d(TAG, "Mostrando receta completa desde DietFragment");
}
} catch (Exception e) {
Log.e(TAG, "Error al mostrar receta", e);
}
}
private HomeFragment getCurrentHomeFragment() {
try {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
if (navController.getCurrentDestination() != null &&
navController.getCurrentDestination().getId() == R.id.nav_home) {
return null;
}
} catch (Exception e) {
Log.e(TAG, "Error al obtener HomeFragment", e);
}
return null;
}
private DietFragment getCurrentDietFragment() {
try {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
if (navController.getCurrentDestination() != null &&
navController.getCurrentDestination().getId() == R.id.nav_diet) {
return null;
}
} catch (Exception e) {
Log.e(TAG, "Error al obtener DietFragment", e);
}
return null;
}
private void updateToolbarTitle(int destinationId) {
String title;
if (destinationId == R.id.nav_home) {
title = "Entrenamientos";
} else if (destinationId == R.id.nav_diet) {
title = "Plan Nutricional";
} else if (destinationId == R.id.nav_profile) {
title = "Mi Perfil";
} else {
title = "Elevate Fitness";
}
if (getSupportActionBar() != null) {
getSupportActionBar().setTitle(title);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
return super.onOptionsItemSelected(item);
}
private void abrirConfiguracion() {
Snackbar.make(binding.getRoot(), "Abriendo configuración...", Snackbar.LENGTH_SHORT).show();
}
private void sincronizarDatos() {
Snackbar.make(binding.getRoot(), "Sincronizando datos...", Snackbar.LENGTH_LONG)
.setAction("Cancelar", v -> {
Log.d(TAG, "Sincronización cancelada");
})
.show();
new Thread(() -> {
try {
Thread.sleep(2000);
runOnUiThread(() -> {
Snackbar.make(binding.getRoot(), "Datos sincronizados correctamente",
Snackbar.LENGTH_SHORT).show();
});
} catch (InterruptedException e) {
Log.e(TAG, "Error en sincronización", e);
}
}).start();
}
private void refrescarDatos() {
Log.d(TAG, "Refrescando datos de ejercicios y dietas");
try {
String planJson = dbHelper.obtenerPlanJson(currentUserEmail);
if (planJson != null && !planJson.trim().isEmpty()) {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
if (navController.getCurrentDestination() != null) {
int currentId = navController.getCurrentDestination().getId();
if (currentId == R.id.nav_home || currentId == R.id.nav_diet) {
Snackbar.make(binding.getRoot(), "Datos actualizados", Snackbar.LENGTH_SHORT).show();
}
}
} else {
mostrarMensajeSinPlan();
}
} catch (Exception e) {
Log.e(TAG, "Error al refrescar datos", e);
Snackbar.make(binding.getRoot(), "Error al actualizar datos", Snackbar.LENGTH_SHORT).show();
}
}
@Override
public boolean onSupportNavigateUp() {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
return NavigationUI.navigateUp(navController, mAppBarConfiguration)
|| super.onSupportNavigateUp();
}
@Override
public void onBackPressed() {
try {
if (binding.drawerLayout.isDrawerOpen(binding.navView)) {
binding.drawerLayout.closeDrawer(binding.navView);
return;
}
FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 0) {
super.onBackPressed();
} else {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
if (navController.getCurrentDestination() != null &&
navController.getCurrentDestination().getId() != R.id.nav_home) {
navController.navigate(R.id.nav_home);
} else {
super.onBackPressed();
}
}
} catch (Exception e) {
Log.e(TAG, "Error en onBackPressed", e);
super.onBackPressed();
}
}
public String getCurrentUserEmail() {
return currentUserEmail;
}
public UsuarioDbHelper getDbHelper() {
return dbHelper;
}
public void navegarA(int destinationId) {
try {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
if (getSupportFragmentManager().getBackStackEntryCount() > 2) {
limpiarNavegacionSiEsNecesario();
}
navController.navigate(destinationId);
} catch (Exception e) {
Log.e(TAG, "Error navegando a destino: " + destinationId, e);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (dbHelper != null) {
dbHelper.close();
}
binding = null;
Log.d(TAG, "MainActivity destruida correctamente");
}
public void resetNavigation() {
try {
Log.d(TAG, "Reseteando navegación de emergencia");
FragmentManager fm = getSupportFragmentManager();
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
navController.navigate(R.id.nav_home);
} catch (Exception e) {
Log.e(TAG, "Error en reset de navegación", e);
recreate();
}
}
}

View File

@ -0,0 +1,302 @@
package com.borjabolufer.elevate;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import com.borjabolufer.elevate.model.Altura;
import com.borjabolufer.elevate.model.DiasEntrenamiento;
import com.borjabolufer.elevate.model.Edad;
import com.borjabolufer.elevate.model.Intensidad;
import com.borjabolufer.elevate.model.Objetivo;
import com.borjabolufer.elevate.model.Peso;
import com.borjabolufer.elevate.model.Sexo;
import com.borjabolufer.elevate.model.Usuario;
import com.borjabolufer.elevate.ui.fragments.AlturaFragment;
import com.borjabolufer.elevate.ui.fragments.DiasEntrenamientoFragment;
import com.borjabolufer.elevate.ui.fragments.EdadFragment;
import com.borjabolufer.elevate.ui.fragments.IntensidadFragment;
import com.borjabolufer.elevate.ui.fragments.LoadingPlanFragment;
import com.borjabolufer.elevate.ui.fragments.ObjetivoFragment;
import com.borjabolufer.elevate.ui.fragments.PesoFragment;
import com.borjabolufer.elevate.ui.fragments.ResumenFragment;
import com.borjabolufer.elevate.ui.fragments.SexoFragment;
public class PreguntasActivity extends AppCompatActivity
implements AlturaFragment.IOnAlturaListener,
PesoFragment.IOnPesoListener,
EdadFragment.IOnEdadListener,
SexoFragment.IOnSexoListener,
IntensidadFragment.IOnIntensidadListener,
ObjetivoFragment.IOnObjetivoListener,
DiasEntrenamientoFragment.IOnDiasEntrenamientoListener {
private static final String TAG = "PreguntasActivity";
private Usuario usuario = new Usuario();
private String nombreCompleto;
private String emailUsuario;
private String passwordUsuario;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_preguntas);
if (!obtenerDatosDelIntent()) {
return;
}
if (savedInstanceState == null) {
mostrarFragment(new SexoFragment());
}
}
private boolean obtenerDatosDelIntent() {
Intent intent = getIntent();
if (intent == null) {
Log.e(TAG, "Intent nulo");
mostrarErrorYCerrar("Error: No se recibieron datos para configurar tu perfil.");
return false;
}
nombreCompleto = intent.getStringExtra(RegisterActivity.EXTRA_NOMBRE);
emailUsuario = intent.getStringExtra(RegisterActivity.EXTRA_EMAIL);
passwordUsuario = intent.getStringExtra(RegisterActivity.EXTRA_PASSWORD);
if (nombreCompleto == null || nombreCompleto.trim().isEmpty()) {
Log.e(TAG, "Nombre completo no recibido");
mostrarErrorYCerrar("Error: Falta el nombre completo.");
return false;
}
if (emailUsuario == null || emailUsuario.trim().isEmpty()) {
Log.e(TAG, "Email no recibido");
mostrarErrorYCerrar("Error: Falta el email del usuario.");
return false;
}
if (passwordUsuario == null || passwordUsuario.trim().isEmpty()) {
Log.e(TAG, "Contraseña no recibida");
mostrarErrorYCerrar("Error: Falta la contraseña del usuario.");
return false;
}
Log.d(TAG, "Datos recibidos correctamente:");
Log.d(TAG, "- Nombre: " + nombreCompleto);
Log.d(TAG, "- Email: " + emailUsuario);
Log.d(TAG, "- Password: " + (passwordUsuario != null ? "[PRESENTE]" : "[AUSENTE]"));
return true;
}
private void mostrarErrorYCerrar(String mensaje) {
Toast.makeText(this, mensaje, Toast.LENGTH_LONG).show();
finish();
}
private void mostrarFragment(Fragment fragment) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragmentContainerViewPreguntas, fragment)
.addToBackStack(null)
.commit();
}
@Override
public void onSexoSelected(Sexo sexo) {
usuario.setSexo(sexo);
Log.d(TAG, "Sexo seleccionado: " + sexo.getSexo());
mostrarFragment(new EdadFragment());
}
@Override
public void onEdadSelected(Edad edad) {
usuario.setEdad(edad);
Log.d(TAG, "Edad seleccionada: " + edad.getEdad());
mostrarFragment(new PesoFragment());
}
@Override
public void onPesoSelected(Peso peso) {
usuario.setPeso(peso);
Log.d(TAG, "Peso seleccionado: " + peso.getPeso());
mostrarFragment(new AlturaFragment());
}
@Override
public void onAlturaSelected(Altura altura) {
usuario.setAltura(altura);
Log.d(TAG, "Altura seleccionada: " + altura.getAltura());
mostrarFragment(new IntensidadFragment());
}
@Override
public void onIntensidadSelected(Intensidad intensidad) {
usuario.setIntensidad(intensidad);
Log.d(TAG, "Intensidad seleccionada: " + intensidad.getIntensidad());
mostrarFragment(new ObjetivoFragment());
}
@Override
public void onObjetivoSelected(Objetivo objetivo) {
usuario.setObjetivo(objetivo);
Log.d(TAG, "Objetivo seleccionado: " + objetivo.getObjetivo());
mostrarFragment(new DiasEntrenamientoFragment());
}
@Override
public void onDiasEntrenamientoSelected(DiasEntrenamiento diasEntrenamiento) {
usuario.setDias(diasEntrenamiento);
Log.d(TAG, "Días de entrenamiento seleccionados: " + diasEntrenamiento.getCantidad());
if (!validarUsuarioCompleto()) {
return;
}
Log.d(TAG, "Perfil de usuario completado. Mostrando resumen antes de generar plan.");
mostrarResumen();
}
private boolean validarUsuarioCompleto() {
StringBuilder errores = new StringBuilder();
if (usuario.getSexo() == null) errores.append("- Sexo\n");
if (usuario.getEdad() == null) errores.append("- Edad\n");
if (usuario.getPeso() == null) errores.append("- Peso\n");
if (usuario.getAltura() == null) errores.append("- Altura\n");
if (usuario.getIntensidad() == null) errores.append("- Intensidad\n");
if (usuario.getObjetivo() == null) errores.append("- Objetivo\n");
if (usuario.getDias() == null) errores.append("- Días de entrenamiento\n");
if (errores.length() > 0) {
Log.e(TAG, "Datos faltantes en el usuario:\n" + errores.toString());
Toast.makeText(this, "Error: Faltan datos del perfil:\n" + errores.toString(),
Toast.LENGTH_LONG).show();
return false;
}
return true;
}
private void mostrarResumen() {
try {
Log.d(TAG, "=== INICIANDO MOSTRAR RESUMEN ===");
ResumenFragment resumenFragment = ResumenFragment.newInstance(
usuario,
emailUsuario,
passwordUsuario
);
Log.d(TAG, "Creando ResumenFragment con:");
Log.d(TAG, "- Usuario completo: " + (usuario != null ? "" : ""));
Log.d(TAG, "- Email: " + emailUsuario);
Log.d(TAG, "- Nombre: " + nombreCompleto);
Log.d(TAG, "- Datos del usuario:");
Log.d(TAG, " * Sexo: " + (usuario.getSexo() != null ? usuario.getSexo().getSexo() : "null"));
Log.d(TAG, " * Edad: " + (usuario.getEdad() != null ? usuario.getEdad().getEdad() : "null"));
Log.d(TAG, " * Peso: " + (usuario.getPeso() != null ? usuario.getPeso().getPeso() : "null"));
Log.d(TAG, " * Altura: " + (usuario.getAltura() != null ? usuario.getAltura().getAltura() : "null"));
Log.d(TAG, " * Objetivo: " + (usuario.getObjetivo() != null ? usuario.getObjetivo().getObjetivo() : "null"));
Log.d(TAG, " * Intensidad: " + (usuario.getIntensidad() != null ? usuario.getIntensidad().getIntensidad() : "null"));
Log.d(TAG, " * Días: " + (usuario.getDias() != null ? usuario.getDias().getCantidad() : "null"));
Log.d(TAG, "Mostrando ResumenFragment...");
mostrarFragment(resumenFragment);
Toast.makeText(this, "¡Perfil completo! Revisa tu información antes de continuar.",
Toast.LENGTH_SHORT).show();
Log.d(TAG, "=== RESUMEN MOSTRADO EXITOSAMENTE ===");
} catch (Exception e) {
Log.e(TAG, "Error al crear ResumenFragment", e);
Toast.makeText(this, "Error al mostrar resumen. Inténtalo de nuevo.",
Toast.LENGTH_LONG).show();
}
}
public void procederAGeneracionDePlan() {
try {
Log.d(TAG, "=== PROCEDIENDO A GENERACIÓN DE PLAN ===");
Log.d(TAG, "Método llamado desde ResumenFragment");
LoadingPlanFragment loadingFragment = LoadingPlanFragment.newInstance(
usuario,
emailUsuario,
passwordUsuario
);
Log.d(TAG, "LoadingPlanFragment creado correctamente");
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragmentContainerViewPreguntas, loadingFragment)
.commit();
Toast.makeText(this, "Generando tu plan personalizado...",
Toast.LENGTH_SHORT).show();
Log.d(TAG, "=== NAVEGACIÓN A LOADING COMPLETADA ===");
} catch (Exception e) {
Log.e(TAG, "Error al crear LoadingPlanFragment", e);
Toast.makeText(this, "Error al generar el plan. Inténtalo de nuevo.",
Toast.LENGTH_LONG).show();
}
}
@Override
public void onBackPressed() {
int backStackCount = getSupportFragmentManager().getBackStackEntryCount();
if (backStackCount > 0) {
super.onBackPressed();
} else {
Toast.makeText(this, "¿Seguro que quieres cancelar la configuración?",
Toast.LENGTH_SHORT).show();
super.onBackPressed();
}
}
public String getNombreCompleto() {
return nombreCompleto;
}
public String getEmailUsuario() {
return emailUsuario;
}
public Usuario getUsuario() {
return usuario;
}
public String getPasswordUsuario() {
return passwordUsuario;
}
}

View File

@ -0,0 +1,155 @@
package com.borjabolufer.elevate;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Patterns;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.borjabolufer.elevate.model.UsuarioDbHelper;
public class RegisterActivity extends AppCompatActivity {
private EditText editNombre, editEmail, editPassword, editConfirmPassword;
private Button btnRegister;
private TextView textIrLogin;
private UsuarioDbHelper dbHelper;
public static final String EXTRA_NOMBRE = "com.borjabolufer.elevate.EXTRA_NOMBRE";
public static final String EXTRA_EMAIL = "com.borjabolufer.elevate.EXTRA_EMAIL";
public static final String EXTRA_PASSWORD = "com.borjabolufer.elevate.EXTRA_PASSWORD";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register);
initializeViews();
dbHelper = new UsuarioDbHelper(this);
setupListeners();
}
private void initializeViews() {
editNombre = findViewById(R.id.editNombre);
editEmail = findViewById(R.id.editEmail);
editPassword = findViewById(R.id.editPassword);
editConfirmPassword = findViewById(R.id.editConfirmPassword);
btnRegister = findViewById(R.id.btnCrearCuenta);
textIrLogin = findViewById(R.id.textIrLogin);
}
private void setupListeners() {
btnRegister.setOnClickListener(v -> attemptRegistration());
textIrLogin.setOnClickListener(v -> {
startActivity(new Intent(this, LoginActivity.class));
finish();
});
}
private void attemptRegistration() {
clearErrors();
String nombreCompleto = editNombre.getText().toString().trim();
String email = editEmail.getText().toString().trim();
String password = editPassword.getText().toString();
String confirmPassword = editConfirmPassword.getText().toString();
if (!validateInputs(nombreCompleto, email, password, confirmPassword)) {
return;
}
Toast.makeText(this, "Datos correctos, configurando tu perfil...", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(this, PreguntasActivity.class);
intent.putExtra(EXTRA_NOMBRE, nombreCompleto);
intent.putExtra(EXTRA_EMAIL, email);
intent.putExtra(EXTRA_PASSWORD, password);
startActivity(intent);
finish();
}
private void clearErrors() {
editNombre.setError(null);
editEmail.setError(null);
editPassword.setError(null);
editConfirmPassword.setError(null);
}
private boolean validateInputs(String nombre, String email, String password, String confirmPassword) {
boolean isValid = true;
EditText focusView = null;
if (!password.equals(confirmPassword)) {
editConfirmPassword.setError("Las contraseñas no coinciden");
focusView = editConfirmPassword;
isValid = false;
}
if (TextUtils.isEmpty(password)) {
editPassword.setError("Este campo es requerido");
focusView = editPassword;
isValid = false;
} else if (password.length() < 6) {
editPassword.setError("La contraseña debe tener al menos 6 caracteres");
focusView = editPassword;
isValid = false;
}
if (TextUtils.isEmpty(email)) {
editEmail.setError("Este campo es requerido");
focusView = editEmail;
isValid = false;
} else if (!isValidEmail(email)) {
editEmail.setError("Este email no es válido");
focusView = editEmail;
isValid = false;
} else if (dbHelper.checkUserEmailExists(email)) {
editEmail.setError("Este email ya está registrado");
focusView = editEmail;
isValid = false;
}
if (TextUtils.isEmpty(nombre)) {
editNombre.setError("Este campo es requerido");
focusView = editNombre;
isValid = false;
} else if (nombre.length() < 2) {
editNombre.setError("El nombre debe tener al menos 2 caracteres");
focusView = editNombre;
isValid = false;
} else if (!isValidName(nombre)) {
editNombre.setError("El nombre solo puede contener letras y espacios");
focusView = editNombre;
isValid = false;
}
if (focusView != null) {
focusView.requestFocus();
}
return isValid;
}
private boolean isValidEmail(CharSequence target) {
return (!TextUtils.isEmpty(target) && Patterns.EMAIL_ADDRESS.matcher(target).matches());
}
private boolean isValidName(String name) {
return name.matches("^[a-zA-ZáéíóúüñÁÉÍÓÚÜÑ\\s'-]+$");
}
@Override
protected void onDestroy() {
if (dbHelper != null) {
dbHelper.close();
}
super.onDestroy();
}
}

View File

@ -0,0 +1,28 @@
package com.borjabolufer.elevate;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import androidx.appcompat.app.AppCompatActivity;
public class SplashActivity extends AppCompatActivity {
private static final int DURACION_SPLASH = 2000;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.splash);
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
Intent intent = new Intent(SplashActivity.this, WelcomeActivity.class);
startActivity(intent);
finish();
}
}, DURACION_SPLASH);
}
}

View File

@ -0,0 +1,27 @@
package com.borjabolufer.elevate;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;
public class WelcomeActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_welcome);
Button btnIniciar = findViewById(R.id.btnIniciar);
Button btnRegistrar = findViewById(R.id.btnRegistrar);
btnIniciar.setOnClickListener(v -> {
startActivity(new Intent(this, LoginActivity.class));
});
btnRegistrar.setOnClickListener(v -> {
startActivity(new Intent(this, RegisterActivity.class));
});
}
}

View File

@ -0,0 +1,153 @@
package com.borjabolufer.elevate.adapters;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.RecyclerView;
import com.borjabolufer.elevate.R;
import com.borjabolufer.elevate.model.Comida;
import com.borjabolufer.elevate.model.ValorNutricional;
import com.borjabolufer.elevate.ui.fragments.DietFragment;
import java.util.List;
public class ComidaAdapter extends RecyclerView.Adapter<ComidaAdapter.ComidaViewHolder> {
private static final String TAG = "ComidaAdapter";
private List<DietFragment.ComidaItem> comidas;
private Fragment parentFragment;
public ComidaAdapter(List<DietFragment.ComidaItem> comidas) {
this.comidas = comidas;
}
public ComidaAdapter(List<DietFragment.ComidaItem> comidas, Fragment parentFragment) {
this.comidas = comidas;
this.parentFragment = parentFragment;
}
@NonNull
@Override
public ComidaViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_comida, parent, false);
return new ComidaViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ComidaViewHolder holder, int position) {
DietFragment.ComidaItem item = comidas.get(position);
holder.bind(item, parentFragment);
}
@Override
public int getItemCount() {
return comidas.size();
}
static class ComidaViewHolder extends RecyclerView.ViewHolder {
TextView textNombreComida, textDescripcion, textCalorias, textProteinas, textCarbos, textGrasas;
ImageView iconComida;
ComidaViewHolder(@NonNull View itemView) {
super(itemView);
textNombreComida = itemView.findViewById(R.id.text_nombre_comida);
textDescripcion = itemView.findViewById(R.id.text_descripcion_comida);
textCalorias = itemView.findViewById(R.id.text_calorias_comida);
textProteinas = itemView.findViewById(R.id.text_proteinas_comida);
textCarbos = itemView.findViewById(R.id.text_carbos_comida);
textGrasas = itemView.findViewById(R.id.text_grasas_comida);
iconComida = itemView.findViewById(R.id.icon_comida);
}
void bind(DietFragment.ComidaItem item, Fragment parentFragment) {
Comida comida = item.getComida();
textNombreComida.setText(item.getNombre());
textDescripcion.setText(comida.getNombre());
ValorNutricional vn = comida.getValor_nutricional();
if (vn != null) {
textCalorias.setText(vn.getCalorías() + " kcal");
textProteinas.setText(vn.getProteínas() + "g");
textCarbos.setText(vn.getCarbohidratos() + "g");
textGrasas.setText(vn.getGrasas() + "g");
} else {
textCalorias.setText("0 kcal");
textProteinas.setText("0g");
textCarbos.setText("0g");
textGrasas.setText("0g");
}
int iconResource;
int tintColor;
switch (item.getTipo()) {
case "desayuno":
iconResource = R.drawable.ic_desayuno;
tintColor = R.color.warning;
break;
case "comida":
iconResource = R.drawable.ic_comida;
tintColor = R.color.primary;
break;
case "cena":
iconResource = R.drawable.ic_cena;
tintColor = R.color.info;
break;
case "snacks":
iconResource = R.drawable.ic_snack;
tintColor = R.color.success;
break;
default:
iconResource = R.drawable.ic_restaurante;
tintColor = R.color.info;
break;
}
iconComida.setImageResource(iconResource);
iconComida.setColorFilter(itemView.getContext().getColor(tintColor));
itemView.setOnClickListener(v -> {
try {
Bundle args = new Bundle();
args.putSerializable("comida", comida);
args.putString("tipo_comida", item.getTipo());
Log.d(TAG, "Navegando al detalle de comida: " + comida.getNombre());
Navigation.findNavController(v).navigate(R.id.nav_detalle_comida, args);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Error de navegación - ID no encontrado en navigation.xml: " + e.getMessage());
if (itemView.getContext() instanceof android.app.Activity) {
((android.app.Activity) itemView.getContext()).runOnUiThread(() -> {
android.widget.Toast.makeText(itemView.getContext(),
"Error: Destino de navegación no configurado",
android.widget.Toast.LENGTH_SHORT).show();
});
}
} catch (Exception e) {
Log.e(TAG, "Error general de navegación: " + e.getMessage());
if (itemView.getContext() instanceof android.app.Activity) {
((android.app.Activity) itemView.getContext()).runOnUiThread(() -> {
android.widget.Toast.makeText(itemView.getContext(),
"Error al navegar al detalle",
android.widget.Toast.LENGTH_SHORT).show();
});
}
}
});
itemView.setClickable(true);
itemView.setFocusable(true);
}
}
}

View File

@ -0,0 +1,135 @@
package com.borjabolufer.elevate.adapters;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.borjabolufer.elevate.R;
import com.borjabolufer.elevate.ui.fragments.HomeFragment;
import java.util.List;
public class EjercicioAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public interface OnItemClickListener {
void onItemClick(HomeFragment.EjercicioItem item);
}
private static final int TYPE_HEADER = 0;
private static final int TYPE_EJERCICIO = 1;
private List<HomeFragment.EjercicioItem> ejercicios;
private OnItemClickListener listener;
public EjercicioAdapter(List<HomeFragment.EjercicioItem> ejercicios, OnItemClickListener listener) {
this.ejercicios = ejercicios;
this.listener = listener;
}
@Override
public int getItemViewType(int position) {
return ejercicios.get(position).isEsHeader() ? TYPE_HEADER : TYPE_EJERCICIO;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == TYPE_HEADER) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_ejercicio_header, parent, false);
return new HeaderViewHolder(view);
} else {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_ejercicio, parent, false);
return new EjercicioViewHolder(view);
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
HomeFragment.EjercicioItem item = ejercicios.get(position);
if (holder instanceof HeaderViewHolder) {
((HeaderViewHolder) holder).bind(item);
} else if (holder instanceof EjercicioViewHolder) {
((EjercicioViewHolder) holder).bind(item, listener);
}
}
@Override
public int getItemCount() {
return ejercicios.size();
}
static class HeaderViewHolder extends RecyclerView.ViewHolder {
TextView textHeader;
HeaderViewHolder(@NonNull View itemView) {
super(itemView);
textHeader = itemView.findViewById(R.id.text_header);
}
void bind(HomeFragment.EjercicioItem item) {
textHeader.setText(item.getNombre());
}
}
static class EjercicioViewHolder extends RecyclerView.ViewHolder {
TextView textNombre, textSeries, textRepeticiones;
ImageView iconEjercicio;
EjercicioViewHolder(@NonNull View itemView) {
super(itemView);
textNombre = itemView.findViewById(R.id.text_nombre_ejercicio);
textSeries = itemView.findViewById(R.id.text_series);
textRepeticiones = itemView.findViewById(R.id.text_repeticiones);
iconEjercicio = itemView.findViewById(R.id.icon_ejercicio);
}
void bind(HomeFragment.EjercicioItem item, OnItemClickListener listener) {
textNombre.setText(item.getNombre());
if ("calentamiento".equals(item.getTipo()) || "enfriamiento".equals(item.getTipo())) {
textSeries.setText("");
textRepeticiones.setText("");
} else {
textSeries.setText(item.getSeries() > 0 ? item.getSeries() + " series" : "");
textRepeticiones.setText(item.getRepeticiones() > 0 ? item.getRepeticiones() + " reps" : "");
}
int iconResource;
int tintColor;
switch (item.getTipo()) {
case "calentamiento":
iconResource = R.drawable.ic_calentamiento;
tintColor = R.color.warning;
break;
case "principal":
iconResource = R.drawable.ic_principal;
tintColor = R.color.primary;
break;
case "enfriamiento":
iconResource = R.drawable.ic_enfriamiento;
tintColor = R.color.success;
break;
default:
iconResource = R.drawable.ic_calentamiento;
tintColor = R.color.warning;
break;
}
iconEjercicio.setImageResource(iconResource);
iconEjercicio.setColorFilter(itemView.getContext().getColor(tintColor));
itemView.setOnClickListener(v -> {
if (listener != null) {
listener.onItemClick(item);
}
});
}
}
}

View File

@ -0,0 +1,22 @@
package com.borjabolufer.elevate.model;
public class Altura {
private int altura;
public Altura(int altura) {
this.altura = altura;
}
public int getAltura() {
return altura;
}
public void setAltura(int altura) {
this.altura = altura;
}
@Override
public String toString() {
return "Altura: " + altura + ", ";
}
}

View File

@ -0,0 +1,60 @@
package com.borjabolufer.elevate.model;
import java.io.Serializable;
import java.util.List;
public class Comida implements Serializable {
public String nombre;
public List<String> ingredientes;
public String receta;
public ValorNutricional valor_nutricional;
public String video_url;
public Comida(String nombre, List<String> ingredientes, String receta, ValorNutricional valor_nutricional, String video_url) {
this.nombre = nombre;
this.ingredientes = ingredientes;
this.receta = receta;
this.valor_nutricional = valor_nutricional;
this.video_url = video_url;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public List<String> getIngredientes() {
return ingredientes;
}
public void setIngredientes(List<String> ingredientes) {
this.ingredientes = ingredientes;
}
public String getReceta() {
return receta;
}
public void setReceta(String receta) {
this.receta = receta;
}
public ValorNutricional getValor_nutricional() {
return valor_nutricional;
}
public void setValor_nutricional(ValorNutricional valor_nutricional) {
this.valor_nutricional = valor_nutricional;
}
public String getVideo_url() {
return video_url;
}
public void setVideo_url(String video_url) {
this.video_url = video_url;
}
}

View File

@ -0,0 +1,53 @@
package com.borjabolufer.elevate.model;
public class DiasEntrenamiento {
private int cantidad;
public String dia;
public Entrenamiento entrenamiento;
public Dieta dieta;
public DiasEntrenamiento(int cantidad) {
this.cantidad = cantidad;
}
public DiasEntrenamiento(int cantidad, String dia, Entrenamiento entrenamiento, Dieta dieta) {
this.cantidad = cantidad;
this.dia = dia;
this.entrenamiento = entrenamiento;
this.dieta = dieta;
}
public int getCantidad() {
return cantidad;
}
public void setCantidad(int cantidad) {
this.cantidad = cantidad;
}
public String getDia() {
return dia;
}
public void setDia(String dia) {
this.dia = dia;
}
public Entrenamiento getEntrenamiento() {
return entrenamiento;
}
public void setEntrenamiento(Entrenamiento entrenamiento) {
this.entrenamiento = entrenamiento;
}
public Dieta getDieta() {
return dieta;
}
public void setDieta(Dieta dieta) {
this.dieta = dieta;
}
}

View File

@ -0,0 +1,47 @@
package com.borjabolufer.elevate.model;
public class Dieta {
public Comida desayuno;
public Comida comida;
public Comida cena;
public Comida snacks;
public Dieta(Comida desayuno, Comida comida, Comida cena, Comida snacks) {
this.desayuno = desayuno;
this.comida = comida;
this.cena = cena;
this.snacks = snacks;
}
public Comida getDesayuno() {
return desayuno;
}
public void setDesayuno(Comida desayuno) {
this.desayuno = desayuno;
}
public Comida getComida() {
return comida;
}
public void setComida(Comida comida) {
this.comida = comida;
}
public Comida getCena() {
return cena;
}
public void setCena(Comida cena) {
this.cena = cena;
}
public Comida getSnacks() {
return snacks;
}
public void setSnacks(Comida snacks) {
this.snacks = snacks;
}
}

View File

@ -0,0 +1,23 @@
package com.borjabolufer.elevate.model;
public class Edad {
private int edad;
public Edad(int edad) {
this.edad = edad;
}
public int getEdad() {
return edad;
}
public void setEdad(int edad) {
this.edad = edad;
}
@Override
public String toString() {
return "Edad: " + edad + ", ";
}
}

View File

@ -0,0 +1,45 @@
package com.borjabolufer.elevate.model;
import java.io.Serializable;
public class Ejercicio implements Serializable {
private String nombre;
private String descripcion;
private int series;
private int repeticiones;
private String descanso;
private String videoUrl;
public Ejercicio(String nombre, String descripcion, int series, int repeticiones, String descanso, String videoUrl) {
this.nombre = nombre;
this.descripcion = descripcion;
this.series = series;
this.repeticiones = repeticiones;
this.descanso = descanso;
this.videoUrl = videoUrl;
}
public String getNombre() {
return nombre;
}
public String getDescripcion() {
return descripcion;
}
public int getSeries() {
return series;
}
public int getRepeticiones() {
return repeticiones;
}
public String getDescanso() {
return descanso;
}
public String getVideoUrl() {
return videoUrl;
}
}

View File

@ -0,0 +1,40 @@
package com.borjabolufer.elevate.model;
import java.util.List;
public class Entrenamiento {
public List<Ejercicio> calentamiento;
public List<Ejercicio> ejercicios_principales;
public List<Ejercicio> enfriamiento;
public Entrenamiento(List<Ejercicio> calentamiento, List<Ejercicio> ejercicios_principales, List<Ejercicio> enfriamiento) {
this.calentamiento = calentamiento;
this.ejercicios_principales = ejercicios_principales;
this.enfriamiento = enfriamiento;
}
public List<Ejercicio> getCalentamiento() {
return calentamiento;
}
public void setCalentamiento(List<Ejercicio> calentamiento) {
this.calentamiento = calentamiento;
}
public List<Ejercicio> getEjercicios_principales() {
return ejercicios_principales;
}
public void setEjercicios_principales(List<Ejercicio> ejercicios_principales) {
this.ejercicios_principales = ejercicios_principales;
}
public List<Ejercicio> getEnfriamiento() {
return enfriamiento;
}
public void setEnfriamiento(List<Ejercicio> enfriamiento) {
this.enfriamiento = enfriamiento;
}
}

View File

@ -0,0 +1,22 @@
package com.borjabolufer.elevate.model;
public class Intensidad {
private String intensidad;
public Intensidad(String intensidad) {
this.intensidad = intensidad;
}
public String getIntensidad() {
return intensidad;
}
public void setIntensidad(String intensidad) {
this.intensidad = intensidad;
}
@Override
public String toString() {
return "Intensidad: " + intensidad + ", ";
}
}

View File

@ -0,0 +1,22 @@
package com.borjabolufer.elevate.model;
public class Objetivo {
private String objetivo;
public Objetivo(String objetivo) {
this.objetivo = objetivo;
}
public String getObjetivo() {
return objetivo;
}
public void setObjetivo(String objetivo) {
this.objetivo = objetivo;
}
@Override
public String toString() {
return "Objetivo: " + objetivo + ", ";
}
}

View File

@ -0,0 +1,24 @@
package com.borjabolufer.elevate.model;
public class Peso {
private float peso;
public Peso(float peso) {
this.peso = peso;
}
public float getPeso() {
return peso;
}
public void setPeso(float peso) {
this.peso = peso;
}
@Override
public String toString() {
return "Peso: " + peso + ", ";
}
}

View File

@ -0,0 +1,19 @@
package com.borjabolufer.elevate.model;
import java.util.List;
public class Semana {
public List<DiasEntrenamiento> semana;
public Semana(List<DiasEntrenamiento> semana) {
this.semana = semana;
}
public List<DiasEntrenamiento> getSemana() {
return semana;
}
public void setSemana(List<DiasEntrenamiento> semana) {
this.semana = semana;
}
}

View File

@ -0,0 +1,22 @@
package com.borjabolufer.elevate.model;
public class Sexo {
private String sexo;
public Sexo(String sexo) {
this.sexo = sexo;
}
public String getSexo() {
return sexo;
}
public void setSexo(String sexo) {
this.sexo = sexo;
}
@Override
public String toString() {
return "Sexo:" + sexo + ", ";
}
}

View File

@ -0,0 +1,79 @@
package com.borjabolufer.elevate.model;
import java.io.Serializable;
public class Usuario implements Serializable {
private static final long serialVersionUID = 1L;
private Sexo sexo;
private Objetivo objetivo;
private Intensidad intensidad;
private Edad edad;
private Peso peso;
private Altura altura;
private DiasEntrenamiento dias;
public Usuario(Sexo sexo, Objetivo objetivo, Intensidad intensidad,
Edad edad, Peso peso, Altura altura, DiasEntrenamiento dias) {
this.sexo = sexo;
this.objetivo = objetivo;
this.intensidad = intensidad;
this.edad = edad;
this.peso = peso;
this.altura = altura;
this.dias = dias;
}
public Usuario() {
}
public Sexo getSexo() { return sexo; }
public Objetivo getObjetivo() { return objetivo; }
public Intensidad getIntensidad() { return intensidad; }
public Edad getEdad() { return edad; }
public Peso getPeso() { return peso; }
public Altura getAltura() { return altura; }
public DiasEntrenamiento getDias() { return dias; }
public void setSexo(Sexo sexo) {
this.sexo = sexo;
}
public void setObjetivo(Objetivo objetivo) {
this.objetivo = objetivo;
}
public void setIntensidad(Intensidad intensidad) {
this.intensidad = intensidad;
}
public void setEdad(Edad edad) {
this.edad = edad;
}
public void setPeso(Peso peso) {
this.peso = peso;
}
public void setAltura(Altura altura) {
this.altura = altura;
}
public void setDias(DiasEntrenamiento dias) {
this.dias = dias;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("UsuarioFitness {\n");
sb.append(" Sexo = '").append(sexo != null ? sexo.getSexo() : "N/A").append("',\n");
sb.append(" Objetivo = '").append(objetivo != null ? objetivo.getObjetivo() : "N/A").append("',\n");
sb.append(" Intensidad = '").append(intensidad != null ? intensidad.getIntensidad() : "N/A").append("',\n");
sb.append(" Edad = ").append(edad != null ? edad.getEdad() : "N/A").append(",\n");
sb.append(" Peso = ").append(peso != null ? peso.getPeso() : "N/A").append(" kg,\n");
sb.append(" Altura = ").append(altura != null ? altura.getAltura() : "N/A").append(" cm,\n");
sb.append(" Días de entrenamiento = ").append(dias != null ? dias.getCantidad() : "N/A").append("\n}");
return sb.toString();
}
}

View File

@ -0,0 +1,556 @@
package com.borjabolufer.elevate.model;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import androidx.annotation.Nullable;
import com.borjabolufer.elevate.utils.PasswordUtils;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class UsuarioDbHelper extends SQLiteOpenHelper {
private static final String TAG = "UsuarioDbHelper";
private static final String DB_NAME = "elevate_fitness.db";
private static final int DB_VERSION = 2;
public static final String TABLE_USUARIOS = "usuarios";
public static final String COLUMN_ID = "id";
public static final String COLUMN_EMAIL = "email";
public static final String COLUMN_NOMBRE_COMPLETO = "nombre_completo";
public static final String COLUMN_PASSWORD_HASH = "password_hash";
public static final String COLUMN_SEXO = "sexo";
public static final String COLUMN_EDAD = "edad";
public static final String COLUMN_OBJETIVO = "objetivo";
public static final String COLUMN_DIAS_ENTRENAMIENTO = "dias_entrenamiento";
public static final String COLUMN_INTENSIDAD = "intensidad";
public static final String COLUMN_PESO = "peso";
public static final String COLUMN_ALTURA = "altura";
public static final String COLUMN_PLAN_JSON = "plan_json";
public static final String COLUMN_FECHA_REGISTRO = "fecha_registro";
public UsuarioDbHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
String CREATE_USUARIOS_TABLE = "CREATE TABLE " + TABLE_USUARIOS + "("
+ COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
+ COLUMN_EMAIL + " TEXT UNIQUE NOT NULL,"
+ COLUMN_NOMBRE_COMPLETO + " TEXT NOT NULL,"
+ COLUMN_PASSWORD_HASH + " TEXT NOT NULL,"
+ COLUMN_SEXO + " TEXT,"
+ COLUMN_EDAD + " INTEGER,"
+ COLUMN_OBJETIVO + " TEXT,"
+ COLUMN_DIAS_ENTRENAMIENTO + " INTEGER,"
+ COLUMN_INTENSIDAD + " TEXT,"
+ COLUMN_PESO + " REAL,"
+ COLUMN_ALTURA + " REAL,"
+ COLUMN_PLAN_JSON + " TEXT,"
+ COLUMN_FECHA_REGISTRO + " TEXT DEFAULT CURRENT_TIMESTAMP"
+ ")";
db.execSQL(CREATE_USUARIOS_TABLE);
Log.i(TAG, "Tabla usuarios creada.");
}
public void delete(SQLiteDatabase db) {
String DROP_USUARIOS_TABLE = "DROP TABLE " + TABLE_USUARIOS;
db.execSQL(DROP_USUARIOS_TABLE);
Log.i(TAG, "Tabla usuarios eliminada.");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(TAG, "Actualizando base de datos de la versión " + oldVersion + " a " + newVersion + ", se perderán los datos antiguos.");
db.execSQL("DROP TABLE IF EXISTS " + TABLE_USUARIOS);
onCreate(db);
}
public long insertarUsuarioCompleto(UsuarioEntity usuario) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(COLUMN_EMAIL, usuario.getEmail());
values.put(COLUMN_NOMBRE_COMPLETO, usuario.getNombreCompleto());
values.put(COLUMN_PASSWORD_HASH, usuario.getPasswordHash());
values.put(COLUMN_SEXO, usuario.getSexo());
values.put(COLUMN_EDAD, usuario.getEdad());
values.put(COLUMN_OBJETIVO, usuario.getObjetivo());
values.put(COLUMN_DIAS_ENTRENAMIENTO, usuario.getDiasEntrenamiento());
values.put(COLUMN_INTENSIDAD, usuario.getIntensidad());
values.put(COLUMN_PESO, usuario.getPeso());
values.put(COLUMN_ALTURA, usuario.getAltura());
values.put(COLUMN_PLAN_JSON, usuario.getPlanJson());
long id = -1;
try {
id = db.insertOrThrow(TABLE_USUARIOS, null, values);
Log.i(TAG, "Nuevo usuario insertado con ID: " + id + " y email: " + usuario.getEmail());
} catch (Exception e) {
Log.e(TAG, "Error al insertar usuario: " + e.getMessage());
} finally {
db.close();
}
return id;
}
public boolean checkUserEmailExists(String email) {
SQLiteDatabase db = this.getReadableDatabase();
String[] columns = {COLUMN_ID};
String selection = COLUMN_EMAIL + " = ?";
String[] selectionArgs = {email};
Cursor cursor = null;
try {
cursor = db.query(TABLE_USUARIOS, columns, selection, selectionArgs, null, null, null);
int count = cursor.getCount();
return count > 0;
} catch (Exception e) {
Log.e(TAG, "Error al verificar email: " + e.getMessage());
return false;
} finally {
if (cursor != null) {
cursor.close();
}
db.close();
}
}
@Nullable
public UsuarioEntity verificarUsuario(String email, String plainPassword) {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = null;
UsuarioEntity usuario = null;
try {
cursor = db.query(TABLE_USUARIOS,
new String[]{COLUMN_ID, COLUMN_EMAIL, COLUMN_NOMBRE_COMPLETO, COLUMN_PASSWORD_HASH, COLUMN_SEXO, COLUMN_EDAD, COLUMN_OBJETIVO, COLUMN_DIAS_ENTRENAMIENTO, COLUMN_INTENSIDAD, COLUMN_PESO, COLUMN_ALTURA, COLUMN_PLAN_JSON, COLUMN_FECHA_REGISTRO},
COLUMN_EMAIL + " = ?",
new String[]{email},
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
String storedPasswordHash = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_PASSWORD_HASH));
Log.d(TAG, "plainPassword: " + plainPassword);
Log.d(TAG, "storedPasswordHash: " + storedPasswordHash);
if (PasswordUtils.verifyPassword(plainPassword, storedPasswordHash)) {
usuario = new UsuarioEntity();
usuario.setId(cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_ID)));
usuario.setEmail(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_EMAIL)));
usuario.setNombreCompleto(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_NOMBRE_COMPLETO)));
usuario.setPasswordHash(storedPasswordHash);
usuario.setSexo(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_SEXO)));
usuario.setEdad(cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_EDAD)));
usuario.setObjetivo(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_OBJETIVO)));
usuario.setDiasEntrenamiento(cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_DIAS_ENTRENAMIENTO)));
usuario.setIntentensidad(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_INTENSIDAD)));
usuario.setPeso(cursor.getDouble(cursor.getColumnIndexOrThrow(COLUMN_PESO)));
usuario.setAltura(cursor.getDouble(cursor.getColumnIndexOrThrow(COLUMN_ALTURA)));
usuario.setPlanJson(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_PLAN_JSON)));
usuario.setFechaRegistro(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_FECHA_REGISTRO)));
Log.i(TAG, "Usuario verificado: " + email);
} else {
Log.w(TAG, "Contraseña incorrecta para el usuario: " + email);
}
} else {
Log.w(TAG, "Usuario no encontrado con email: " + email);
}
} catch (Exception e) {
Log.e(TAG, "Error al verificar usuario: " + e.getMessage());
} finally {
if (cursor != null) {
cursor.close();
}
db.close();
}
return usuario;
}
@Nullable
public UsuarioEntity getUsuarioPorEmail(String email) {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = null;
UsuarioEntity usuario = null;
try {
cursor = db.query(TABLE_USUARIOS,
new String[]{COLUMN_ID, COLUMN_EMAIL, COLUMN_NOMBRE_COMPLETO, COLUMN_PASSWORD_HASH, COLUMN_SEXO, COLUMN_EDAD, COLUMN_OBJETIVO, COLUMN_DIAS_ENTRENAMIENTO, COLUMN_INTENSIDAD, COLUMN_PESO, COLUMN_ALTURA, COLUMN_PLAN_JSON, COLUMN_FECHA_REGISTRO},
COLUMN_EMAIL + " = ?",
new String[]{email},
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
usuario = new UsuarioEntity();
usuario.setId(cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_ID)));
usuario.setEmail(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_EMAIL)));
usuario.setNombreCompleto(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_NOMBRE_COMPLETO)));
usuario.setPasswordHash(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_PASSWORD_HASH)));
usuario.setSexo(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_SEXO)));
usuario.setEdad(cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_EDAD)));
usuario.setObjetivo(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_OBJETIVO)));
usuario.setDiasEntrenamiento(cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_DIAS_ENTRENAMIENTO)));
usuario.setIntentensidad(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_INTENSIDAD)));
usuario.setPeso(cursor.getDouble(cursor.getColumnIndexOrThrow(COLUMN_PESO)));
usuario.setAltura(cursor.getDouble(cursor.getColumnIndexOrThrow(COLUMN_ALTURA)));
usuario.setPlanJson(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_PLAN_JSON)));
usuario.setFechaRegistro(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_FECHA_REGISTRO)));
}
} catch (Exception e) {
Log.e(TAG, "Error al obtener usuario por email: " + e.getMessage());
} finally {
if (cursor != null) {
cursor.close();
}
db.close();
}
return usuario;
}
public int actualizarUsuario(UsuarioEntity usuario) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(COLUMN_NOMBRE_COMPLETO, usuario.getNombreCompleto());
values.put(COLUMN_EMAIL, usuario.getEmail());
if (usuario.getPasswordHash() != null && !usuario.getPasswordHash().isEmpty()) {
values.put(COLUMN_PASSWORD_HASH, usuario.getPasswordHash());
}
values.put(COLUMN_SEXO, usuario.getSexo());
values.put(COLUMN_EDAD, usuario.getEdad());
values.put(COLUMN_OBJETIVO, usuario.getObjetivo());
values.put(COLUMN_DIAS_ENTRENAMIENTO, usuario.getDiasEntrenamiento());
values.put(COLUMN_INTENSIDAD, usuario.getIntensidad());
values.put(COLUMN_PESO, usuario.getPeso());
values.put(COLUMN_ALTURA, usuario.getAltura());
int rowsAffected = 0;
try {
rowsAffected = db.update(TABLE_USUARIOS, values, COLUMN_ID + " = ?",
new String[]{String.valueOf(usuario.getId())});
if (rowsAffected > 0) {
Log.i(TAG, "Usuario actualizado correctamente: " + usuario.getEmail());
} else {
Log.w(TAG, "No se pudo actualizar el usuario: " + usuario.getEmail());
}
} catch (Exception e) {
Log.e(TAG, "Error al actualizar usuario: " + e.getMessage());
} finally {
db.close();
}
return rowsAffected;
}
public boolean migrarPlanJson(String emailAnterior, String emailNuevo) {
SQLiteDatabase db = this.getWritableDatabase();
try {
String planJson = obtenerPlanJson(emailAnterior);
if (planJson != null && !planJson.trim().isEmpty()) {
ContentValues values = new ContentValues();
values.put(COLUMN_PLAN_JSON, planJson);
int updated = db.update(TABLE_USUARIOS, values, COLUMN_EMAIL + " = ?",
new String[]{emailNuevo});
Log.d(TAG, "Plan JSON migrado de " + emailAnterior + " a " + emailNuevo);
return updated > 0;
}
return true;
} catch (Exception e) {
Log.e(TAG, "Error al migrar plan JSON: " + e.getMessage());
return false;
} finally {
db.close();
}
}
public int updatePlanJson(String email, String nuevoPlanJson) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(COLUMN_PLAN_JSON, nuevoPlanJson);
int rowsAffected = 0;
try {
rowsAffected = db.update(TABLE_USUARIOS, values, COLUMN_EMAIL + " = ?", new String[]{email});
if (rowsAffected > 0) {
Log.i(TAG, "Plan JSON actualizado para el usuario: " + email);
} else {
Log.w(TAG, "No se pudo actualizar el plan JSON para el usuario (no encontrado o sin cambios): " + email);
}
} catch (Exception e) {
Log.e(TAG, "Error al actualizar plan JSON: " + e.getMessage());
} finally {
db.close();
}
return rowsAffected;
}
public void actualizarPlanJson(String email, String planJson) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(COLUMN_PLAN_JSON, planJson);
try {
db.update(TABLE_USUARIOS, values, COLUMN_EMAIL + " = ?", new String[]{email});
Log.i(TAG, "Plan JSON actualizado (método legacy) para: " + email);
} catch (Exception e) {
Log.e(TAG, "Error al actualizar plan JSON (método legacy): " + e.getMessage());
} finally {
db.close();
}
}
public String obtenerPlanJson(String email) {
SQLiteDatabase db = this.getReadableDatabase();
String planJson = null;
Cursor cursor = null;
try {
cursor = db.query(TABLE_USUARIOS,
new String[]{COLUMN_PLAN_JSON},
COLUMN_EMAIL + "=?",
new String[]{email},
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
planJson = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_PLAN_JSON));
}
} catch (Exception e) {
Log.e(TAG, "Error al obtener plan JSON: " + e.getMessage());
} finally {
if (cursor != null) {
cursor.close();
}
db.close();
}
return planJson;
}
public boolean guardarPlanJsonEnArchivo(Context context, String email) {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = null;
boolean success = false;
try {
cursor = db.query(TABLE_USUARIOS,
new String[]{COLUMN_PLAN_JSON},
COLUMN_EMAIL + "=?",
new String[]{email},
null, null, null);
if (cursor != null && cursor.moveToFirst()) {
String planJson = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_PLAN_JSON));
if (planJson != null && !planJson.trim().isEmpty()) {
planJson = planJson.replace("```json", "").replace("```", "").trim();
String fileName = "plan_" + email.replace("@", "_").replace(".", "_") + ".json";
File jsonFile = new File(context.getFilesDir(), fileName);
FileWriter writer = new FileWriter(jsonFile);
writer.write(planJson);
writer.flush();
writer.close();
Log.i(TAG, "Plan guardado en archivo: " + fileName);
success = true;
}
}
} catch (IOException e) {
Log.e(TAG, "Error al guardar JSON en archivo interno: ", e);
} catch (Exception e) {
Log.e(TAG, "Error general al guardar JSON: ", e);
} finally {
if (cursor != null) {
cursor.close();
}
db.close();
}
return success;
}
public String cargarPlanJsonDesdeArchivo(Context context, String email) {
try {
String fileName = "plan_" + email.replace("@", "_").replace(".", "_") + ".json";
File jsonFile = new File(context.getFilesDir(), fileName);
if (!jsonFile.exists()) {
Log.w(TAG, "Archivo no encontrado: " + fileName);
return null;
}
StringBuilder jsonContent = new StringBuilder();
java.io.FileInputStream fis = new java.io.FileInputStream(jsonFile);
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
jsonContent.append(new String(buffer, 0, bytesRead, "UTF-8"));
}
fis.close();
Log.i(TAG, "Plan cargado desde archivo: " + fileName);
return jsonContent.toString();
} catch (IOException e) {
Log.e(TAG, "Error al cargar JSON desde archivo: ", e);
return null;
}
}
public boolean existePlanJsonArchivo(Context context, String email) {
String fileName = "plan_" + email.replace("@", "_").replace(".", "_") + ".json";
File jsonFile = new File(context.getFilesDir(), fileName);
return jsonFile.exists();
}
public boolean eliminarPlanJsonArchivo(Context context, String email) {
try {
String fileName = "plan_" + email.replace("@", "_").replace(".", "_") + ".json";
File jsonFile = new File(context.getFilesDir(), fileName);
if (jsonFile.exists()) {
boolean deleted = jsonFile.delete();
Log.i(TAG, "Archivo eliminado: " + fileName + " - Éxito: " + deleted);
return deleted;
}
return true;
} catch (Exception e) {
Log.e(TAG, "Error al eliminar archivo JSON: ", e);
return false;
}
}
public List<String> listarPlanesExistentes(Context context) {
List<String> planes = new ArrayList<>();
File filesDir = context.getFilesDir();
File[] files = filesDir.listFiles();
if (files != null) {
for (File file : files) {
if (file.getName().startsWith("plan_") && file.getName().endsWith(".json")) {
String fileName = file.getName();
String email = fileName.replace("plan_", "").replace(".json", "")
.replace("_", "@");
planes.add(email);
}
}
}
return planes;
}
public String obtenerNombreUsuario(String email) {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = null;
String nombre = "Usuario";
try {
cursor = db.rawQuery("SELECT nombre_completo FROM usuarios WHERE email = ?", new String[]{email});
if (cursor != null && cursor.moveToFirst()) {
nombre = cursor.getString(0);
}
} catch (Exception e) {
Log.e(TAG, "Error al obtener nombre de usuario: " + e.getMessage());
} finally {
if (cursor != null) {
cursor.close();
}
db.close();
}
return nombre;
}
public boolean eliminarUsuario(String email) {
SQLiteDatabase db = this.getWritableDatabase();
try {
int deleted = db.delete(TABLE_USUARIOS, COLUMN_EMAIL + " = ?", new String[]{email});
if (deleted > 0) {
Log.i(TAG, "Usuario eliminado: " + email);
return true;
} else {
Log.w(TAG, "No se encontró usuario para eliminar: " + email);
return false;
}
} catch (Exception e) {
Log.e(TAG, "Error al eliminar usuario: " + e.getMessage());
return false;
} finally {
db.close();
}
}
public List<UsuarioEntity> obtenerTodosLosUsuarios() {
List<UsuarioEntity> usuarios = new ArrayList<>();
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = null;
try {
cursor = db.query(TABLE_USUARIOS, null, null, null, null, null, COLUMN_FECHA_REGISTRO + " DESC");
if (cursor != null && cursor.moveToFirst()) {
do {
UsuarioEntity usuario = new UsuarioEntity();
usuario.setId(cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_ID)));
usuario.setEmail(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_EMAIL)));
usuario.setNombreCompleto(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_NOMBRE_COMPLETO)));
usuario.setPasswordHash(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_PASSWORD_HASH)));
usuario.setSexo(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_SEXO)));
usuario.setEdad(cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_EDAD)));
usuario.setObjetivo(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_OBJETIVO)));
usuario.setDiasEntrenamiento(cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_DIAS_ENTRENAMIENTO)));
usuario.setIntentensidad(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_INTENSIDAD)));
usuario.setPeso(cursor.getDouble(cursor.getColumnIndexOrThrow(COLUMN_PESO)));
usuario.setAltura(cursor.getDouble(cursor.getColumnIndexOrThrow(COLUMN_ALTURA)));
usuario.setPlanJson(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_PLAN_JSON)));
usuario.setFechaRegistro(cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_FECHA_REGISTRO)));
usuarios.add(usuario);
} while (cursor.moveToNext());
}
} catch (Exception e) {
Log.e(TAG, "Error al obtener todos los usuarios: " + e.getMessage());
} finally {
if (cursor != null) {
cursor.close();
}
db.close();
}
return usuarios;
}
public int contarUsuarios() {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = null;
int count = 0;
try {
cursor = db.rawQuery("SELECT COUNT(*) FROM " + TABLE_USUARIOS, null);
if (cursor != null && cursor.moveToFirst()) {
count = cursor.getInt(0);
}
} catch (Exception e) {
Log.e(TAG, "Error al contar usuarios: " + e.getMessage());
} finally {
if (cursor != null) {
cursor.close();
}
db.close();
}
return count;
}
}

View File

@ -0,0 +1,151 @@
package com.borjabolufer.elevate.model;
public class UsuarioEntity {
private long id;
private String email;
private String nombreCompleto;
private String passwordHash;
private String sexo;
private int edad;
private String objetivo;
private int diasEntrenamiento;
private String intensidad;
private double peso;
private double altura;
private String planJson;
private String fechaRegistro;
public UsuarioEntity() {
}
public UsuarioEntity(String email, String nombreCompleto, String passwordHash,
String sexo, int edad, String objetivo, int diasEntrenamiento,
String intensidad, double peso, double altura, String planJson) {
this.email = email;
this.nombreCompleto = nombreCompleto;
this.passwordHash = passwordHash;
this.sexo = sexo;
this.edad = edad;
this.objetivo = objetivo;
this.diasEntrenamiento = diasEntrenamiento;
this.intensidad = intensidad;
this.peso = peso;
this.altura = altura;
this.planJson = planJson;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getNombreCompleto() {
return nombreCompleto;
}
public void setNombreCompleto(String nombreCompleto) {
this.nombreCompleto = nombreCompleto;
}
public String getPasswordHash() {
return passwordHash;
}
public void setPasswordHash(String passwordHash) {
this.passwordHash = passwordHash;
}
public String getSexo() {
return sexo;
}
public void setSexo(String sexo) {
this.sexo = sexo;
}
public int getEdad() {
return edad;
}
public void setEdad(int edad) {
this.edad = edad;
}
public String getObjetivo() {
return objetivo;
}
public void setObjetivo(String objetivo) {
this.objetivo = objetivo;
}
public int getDiasEntrenamiento() {
return diasEntrenamiento;
}
public void setDiasEntrenamiento(int diasEntrenamiento) {
this.diasEntrenamiento = diasEntrenamiento;
}
public String getIntensidad() {
return intensidad;
}
public void setIntentensidad(String intensidad) {
this.intensidad = intensidad;
}
public double getPeso() {
return peso;
}
public void setPeso(double peso) {
this.peso = peso;
}
public double getAltura() {
return altura;
}
public void setAltura(double altura) {
this.altura = altura;
}
public String getPlanJson() {
return planJson;
}
public void setPlanJson(String planJson) {
this.planJson = planJson;
}
public String getFechaRegistro() {
return fechaRegistro;
}
public void setFechaRegistro(String fechaRegistro) {
this.fechaRegistro = fechaRegistro;
}
@Override
public String toString() {
return "UsuarioEntity{" +
"id=" + id +
", email='" + email + '\'' +
", nombreCompleto='" + nombreCompleto + '\'' +
", sexo='" + sexo + '\'' +
", edad=" + edad +
'}';
}
}

View File

@ -0,0 +1,47 @@
package com.borjabolufer.elevate.model;
public class ValorNutricional {
public int calorías;
public int proteínas;
public int carbohidratos;
public int grasas;
public ValorNutricional(int calorías, int proteínas, int carbohidratos, int grasas) {
this.calorías = calorías;
this.proteínas = proteínas;
this.carbohidratos = carbohidratos;
this.grasas = grasas;
}
public int getCalorías() {
return calorías;
}
public void setCalorías(int calorías) {
this.calorías = calorías;
}
public int getProteínas() {
return proteínas;
}
public void setProteínas(int proteínas) {
this.proteínas = proteínas;
}
public int getCarbohidratos() {
return carbohidratos;
}
public void setCarbohidratos(int carbohidratos) {
this.carbohidratos = carbohidratos;
}
public int getGrasas() {
return grasas;
}
public void setGrasas(int grasas) {
this.grasas = grasas;
}
}

View File

@ -0,0 +1,135 @@
package com.borjabolufer.elevate.ui.fragments;
import android.content.Context;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.borjabolufer.elevate.R;
import com.borjabolufer.elevate.model.Altura;
public class AlturaFragment extends Fragment {
public interface IOnAlturaListener {
void onAlturaSelected(Altura altura);
}
private IOnAlturaListener alturaListener;
private EditText editAltura;
private Button btnContinuar;
public AlturaFragment() {
super(R.layout.fragment_altura);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initializeViews(view);
setupTextWatcher();
setupClickListener();
}
private void initializeViews(View view) {
editAltura = view.findViewById(R.id.editAltura);
btnContinuar = view.findViewById(R.id.btnContinuarAltura);
btnContinuar.setEnabled(false);
btnContinuar.setAlpha(0.5f);
}
private void setupTextWatcher() {
editAltura.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
validateInput(s.toString().trim());
}
});
}
private void validateInput(String input) {
boolean isValid = false;
if (!input.isEmpty()) {
try {
int altura = Integer.parseInt(input);
if (altura >= 100 && altura <= 250) {
isValid = true;
}
} catch (NumberFormatException e) {
isValid = false;
}
}
btnContinuar.setEnabled(isValid);
btnContinuar.setAlpha(isValid ? 1.0f : 0.5f);
}
private void setupClickListener() {
btnContinuar.setOnClickListener(v -> {
String alturaTexto = editAltura.getText().toString().trim();
if (alturaTexto.isEmpty()) {
Toast.makeText(getContext(), "Por favor, ingresa tu altura", Toast.LENGTH_SHORT).show();
return;
}
try {
int alturaValor = Integer.parseInt(alturaTexto);
if (alturaValor < 100 || alturaValor > 250) {
Toast.makeText(getContext(), "Por favor, ingresa una altura entre 100 y 250 cm",
Toast.LENGTH_SHORT).show();
return;
}
Altura altura = new Altura(alturaValor);
if (alturaListener != null) {
alturaListener.onAlturaSelected(altura);
} else {
Toast.makeText(getContext(), "Error: No se pudo procesar la altura",
Toast.LENGTH_SHORT).show();
}
} catch (NumberFormatException e) {
Toast.makeText(getContext(), "Por favor, ingresa un número válido",
Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof IOnAlturaListener) {
alturaListener = (IOnAlturaListener) context;
} else {
throw new ClassCastException(context.toString() + " must implement IOnAlturaListener");
}
}
@Override
public void onDetach() {
super.onDetach();
alturaListener = null;
}
}

View File

@ -0,0 +1,213 @@
package com.borjabolufer.elevate.ui.fragments;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.borjabolufer.elevate.R;
import com.borjabolufer.elevate.databinding.FragmentComidaDetalleBinding;
import com.borjabolufer.elevate.model.Comida;
import com.borjabolufer.elevate.model.ValorNutricional;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.List;
public class DetalleComidaFragment extends Fragment {
private static final String TAG = "DetalleComidaFragment";
private FragmentComidaDetalleBinding binding;
private Comida comida;
private String tipoComida;
private String nombreComida;
public static DetalleComidaFragment newInstance(Comida comida, String tipo, String nombre) {
DetalleComidaFragment fragment = new DetalleComidaFragment();
Bundle args = new Bundle();
args.putSerializable("comida", comida);
args.putString("tipo_comida", tipo);
args.putString("nombre_comida", nombre);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
comida = (Comida) getArguments().getSerializable("comida");
tipoComida = getArguments().getString("tipo_comida", "");
nombreComida = getArguments().getString("nombre_comida", "");
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
binding = FragmentComidaDetalleBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (comida != null) {
configurarUI();
} else {
mostrarError();
}
}
private void configurarUI() {
String titulo = nombreComida != null && !nombreComida.isEmpty() ? nombreComida : comida.getNombre();
binding.textComidaTitle.setText(titulo);
configurarIconoComida();
configurarInformacionNutricional();
configurarIngredientes();
configurarReceta();
}
private void configurarIconoComida() {
int iconResource;
int tintColor;
switch (tipoComida.toLowerCase()) {
case "desayuno":
iconResource = R.drawable.ic_desayuno;
tintColor = R.color.warning;
break;
case "almuerzo":
case "comida":
iconResource = R.drawable.ic_comida;
tintColor = R.color.primary;
break;
case "cena":
iconResource = R.drawable.ic_cena;
tintColor = R.color.info;
break;
case "snack":
case "snacks":
iconResource = R.drawable.ic_snack;
tintColor = R.color.success;
break;
default:
iconResource = R.drawable.ic_restaurante;
tintColor = R.color.primary;
break;
}
binding.iconComidaDetalle.setImageResource(iconResource);
binding.iconComidaDetalle.setColorFilter(requireContext().getColor(tintColor));
}
private void configurarInformacionNutricional() {
ValorNutricional vn = comida.getValor_nutricional();
if (vn != null) {
binding.textCaloriesValue.setText(vn.getCalorías() + " kcal");
binding.textProteinsValue.setText(vn.getProteínas() + "g");
binding.textCarbsValue.setText(vn.getCarbohidratos() + "g");
binding.textFatsValue.setText(vn.getGrasas() + "g");
} else {
binding.textCaloriesValue.setText("-- kcal");
binding.textProteinsValue.setText("--g");
binding.textCarbsValue.setText("--g");
binding.textFatsValue.setText("--g");
}
}
private void configurarIngredientes() {
List<String> ingredientes = comida.getIngredientes();
if (ingredientes != null && !ingredientes.isEmpty()) {
StringBuilder ingredientesTexto = new StringBuilder();
for (int i = 0; i < ingredientes.size(); i++) {
ingredientesTexto.append("").append(ingredientes.get(i));
if (i < ingredientes.size() - 1) {
ingredientesTexto.append("\n");
}
}
binding.textIngredientsContent.setText(ingredientesTexto.toString());
} else {
binding.textIngredientsContent.setText("No se especificaron ingredientes");
}
}
private void configurarReceta() {
String receta = comida.getReceta();
if (receta != null && !receta.trim().isEmpty()) {
String recetaFormateada = formatearReceta(receta);
binding.textRecipeContent.setText(recetaFormateada);
} else {
binding.textRecipeContent.setText("No se proporcionaron instrucciones de preparación");
}
}
private String formatearReceta(String receta) {
if (receta == null) return "";
return receta
.trim()
.replaceAll("\\s*\\n\\s*", "\n")
.replaceAll("(\\d+\\.)\\s*", "\n$1 ");
}
private void mostrarError() {
Toast.makeText(getContext(), "Error al cargar la información de la comida", Toast.LENGTH_LONG).show();
if (getParentFragmentManager().getBackStackEntryCount() > 0) {
getParentFragmentManager().popBackStack();
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
}
public Comida getComida() {
return comida;
}
public String getTipoComida() {
return tipoComida;
}
public String getNombreComida() {
return nombreComida;
}
public int getCaloriasComida() {
if (comida != null && comida.getValor_nutricional() != null) {
return comida.getValor_nutricional().getCalorías();
}
return 0;
}
}

View File

@ -0,0 +1,656 @@
package com.borjabolufer.elevate.ui.fragments;
import android.content.Intent;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.VideoView;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import com.borjabolufer.elevate.R;
import com.borjabolufer.elevate.model.Ejercicio;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.chip.Chip;
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.PlayerConstants;
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.YouTubePlayer;
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.listeners.AbstractYouTubePlayerListener;
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.views.YouTubePlayerView;
import com.pierfrancescosoffritti.androidyoutubeplayer.core.ui.DefaultPlayerUiController;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class DetalleEjercicioFragment extends Fragment implements MediaPlayer.OnPreparedListener,
MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener {
private static final String TAG = "DetalleEjercicioFragment";
private static final String ARG_EJERCICIO = "ejercicio";
private static final String ARG_TIPO = "tipo";
private Ejercicio ejercicio;
private String tipoEjercicio;
private TextView nombreTextView;
private TextView descripcionTextView;
private TextView grupoMuscularTextView;
private TextView nivelDificultadTextView;
private TextView seriesTextView;
private TextView repeticionesTextView;
private TextView descansoTextView;
private Chip chipTipoEjercicio;
private ImageButton btnVolver;
private VideoView videoView;
private ImageButton btnPlayPause;
private SeekBar seekBar;
private TextView durationTextView;
private MaterialButton externalVideoButton;
private View descripcionContainer;
private View grupoMuscularContainer;
private View nivelDificultadContainer;
private View parametersContainer;
private View videoContainer;
private View noVideoOverlay;
private View duracionContainer;
private Handler handler = new Handler();
private boolean isVideoReady = false;
private boolean isPlaying = false;
private YouTubePlayerView youTubePlayerView;
public static DetalleEjercicioFragment newInstance(Ejercicio ejercicio) {
return newInstance(ejercicio, "principal");
}
public static DetalleEjercicioFragment newInstance(Ejercicio ejercicio, String tipo) {
DetalleEjercicioFragment fragment = new DetalleEjercicioFragment();
Bundle args = new Bundle();
args.putSerializable(ARG_EJERCICIO, ejercicio);
args.putString(ARG_TIPO, tipo);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
ejercicio = (Ejercicio) getArguments().getSerializable(ARG_EJERCICIO);
tipoEjercicio = getArguments().getString(ARG_TIPO, "principal");
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_ejercicio_detalle, container, false);
inicializarViews(view);
configurarBotonVolver();
configurarVistasSegunTipo();
configurarVideo();
return view;
}
private void inicializarViews(View view) {
nombreTextView = view.findViewById(R.id.text_exercise_title);
descripcionTextView = view.findViewById(R.id.text_description_content);
grupoMuscularTextView = view.findViewById(R.id.text_grupo_muscular);
nivelDificultadTextView = view.findViewById(R.id.text_nivel_dificultad);
seriesTextView = view.findViewById(R.id.text_series_value);
repeticionesTextView = view.findViewById(R.id.text_repetitions_value);
descansoTextView = view.findViewById(R.id.text_rest_value);
chipTipoEjercicio = view.findViewById(R.id.chip_tipo_ejercicio);
youTubePlayerView = view.findViewById(R.id.youtube_player_view);
externalVideoButton = view.findViewById(R.id.button_external_video);
descripcionContainer = view.findViewById(R.id.description_container);
grupoMuscularContainer = view.findViewById(R.id.grupo_muscular_container);
nivelDificultadContainer = view.findViewById(R.id.nivel_dificultad_container);
parametersContainer = view.findViewById(R.id.parameters_container);
videoContainer = view.findViewById(R.id.video_container);
noVideoOverlay = view.findViewById(R.id.no_video_overlay);
duracionContainer = view.findViewById(R.id.duracion_container);
}
private void configurarBotonVolver() {
if (btnVolver != null) {
btnVolver.setOnClickListener(v -> {
try {
androidx.navigation.NavController navController =
androidx.navigation.Navigation.findNavController(requireView());
navController.popBackStack();
} catch (Exception e) {
requireActivity().getSupportFragmentManager().popBackStack();
}
});
}
}
private void configurarVistasSegunTipo() {
nombreTextView.setText(ejercicio.getNombre());
configurarChipTipo();
switch (tipoEjercicio.toLowerCase()) {
case "calentamiento":
configurarParaCalentamiento();
break;
case "enfriamiento":
configurarParaEnfriamiento();
break;
default:
configurarParaEjercicioPrincipal();
break;
}
}
private void configurarChipTipo() {
if (chipTipoEjercicio != null) {
switch (tipoEjercicio.toLowerCase()) {
case "calentamiento":
chipTipoEjercicio.setText("🔥 Calentamiento");
chipTipoEjercicio.setChipBackgroundColorResource(R.color.warning);
break;
case "enfriamiento":
chipTipoEjercicio.setText("🧘 Enfriamiento");
chipTipoEjercicio.setChipBackgroundColorResource(R.color.success);
break;
default:
chipTipoEjercicio.setText("💪 Principal");
chipTipoEjercicio.setChipBackgroundColorResource(R.color.primary);
break;
}
}
}
private void configurarParaCalentamiento() {
Log.d(TAG, "Configurando vista para calentamiento");
mostrarVista(descripcionContainer);
descripcionTextView.setText(extraerDescripcionLimpia());
descansoTextView.setText(ejercicio.getDescanso());
mostrarVista(parametersContainer);
configurarVideoParaCalentamientoEnfriamiento();
}
private void configurarParaEnfriamiento() {
Log.d(TAG, "Configurando vista para enfriamiento");
mostrarVista(descripcionContainer);
mostrarVista(parametersContainer);
descripcionTextView.setText(extraerDescripcionLimpia());
descansoTextView.setText(ejercicio.getDescanso() != null ? ejercicio.getDescanso() : "No especificado");
}
private void configurarParaEjercicioPrincipal() {
Log.d(TAG, "Configurando vista para ejercicio principal");
mostrarVista(descripcionContainer);
mostrarVista(parametersContainer);
descripcionTextView.setText(extraerDescripcionLimpia());
seriesTextView.setText(String.valueOf(ejercicio.getSeries()));
repeticionesTextView.setText(String.valueOf(ejercicio.getRepeticiones()));
descansoTextView.setText(ejercicio.getDescanso() != null ? ejercicio.getDescanso() : "No especificado");
String grupoMuscular = extraerGrupoMuscular();
String nivelDificultad = extraerNivelDificultad();
if (!grupoMuscular.isEmpty()) {
mostrarVista(grupoMuscularContainer);
grupoMuscularTextView.setText(grupoMuscular);
}
if (!nivelDificultad.isEmpty()) {
mostrarVista(nivelDificultadContainer);
nivelDificultadTextView.setText(nivelDificultad);
}
}
private String extraerDescripcionLimpia() {
String descripcion = ejercicio.getDescripcion();
if (descripcion == null || descripcion.trim().isEmpty()) {
return "Sin descripción disponible";
}
return descripcion
.replaceAll("\\s*\\|\\s*Grupo muscular:.*?(?=\\||$)", "")
.replaceAll("\\s*\\|\\s*Nivel:.*?(?=\\||$)", "")
.replaceAll("\\s*\\|\\s*Duración:.*?(?=\\||$)", "")
.trim();
}
private String extraerGrupoMuscular() {
String descripcion = ejercicio.getDescripcion();
if (descripcion != null && descripcion.contains("Grupo muscular:")) {
String[] partes = descripcion.split("\\|");
for (String parte : partes) {
if (parte.trim().startsWith("Grupo muscular:")) {
return parte.replace("Grupo muscular:", "").trim();
}
}
}
return "";
}
private String extraerNivelDificultad() {
String descripcion = ejercicio.getDescripcion();
if (descripcion != null && descripcion.contains("Nivel:")) {
String[] partes = descripcion.split("\\|");
for (String parte : partes) {
if (parte.trim().startsWith("Nivel:")) {
return parte.replace("Nivel:", "").trim();
}
}
}
return "";
}
private String extraerDuracion() {
String descripcion = ejercicio.getDescripcion();
if (descripcion != null && descripcion.contains("Duración:")) {
String[] partes = descripcion.split("\\|");
for (String parte : partes) {
if (parte.trim().startsWith("Duración:")) {
return parte.replace("Duración:", "").trim();
}
}
}
return "";
}
private void configurarVideoParaCalentamientoEnfriamiento() {
String videoUrl = ejercicio.getVideoUrl();
if (videoUrl == null || videoUrl.trim().isEmpty()) {
ocultarVista(videoContainer);
ocultarVista(youTubePlayerView);
ocultarVista(externalVideoButton);
ocultarVista(noVideoOverlay);
return;
}
ocultarVista(videoContainer);
ocultarVista(youTubePlayerView);
ocultarVista(noVideoOverlay);
mostrarVista(externalVideoButton);
if (esYouTubeUrl(videoUrl)) {
externalVideoButton.setText("📹 Ver Video de " +
(tipoEjercicio.equals("calentamiento") ? "Calentamiento" : "Enfriamiento"));
} else {
externalVideoButton.setText("📹 Ver Video Demostrativo");
}
externalVideoButton.setOnClickListener(v -> {
try {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(videoUrl));
if (esYouTubeUrl(videoUrl)) {
intent.setPackage("com.google.android.youtube");
if (intent.resolveActivity(requireContext().getPackageManager()) != null) {
startActivity(intent);
} else {
intent.setPackage(null);
startActivity(intent);
}
} else {
startActivity(intent);
}
} catch (Exception e) {
Log.e(TAG, "Error abriendo video externo", e);
Toast.makeText(getContext(), "No se pudo abrir el video", Toast.LENGTH_SHORT).show();
}
});
}
private void configurarVideo() {
String videoUrl = ejercicio.getVideoUrl();
if (videoUrl == null || videoUrl.trim().isEmpty()) {
mostrarVistaNoVideo();
return;
}
if (esYouTubeUrl(videoUrl)) {
String videoId = extraerIdVideoYouTube(videoUrl);
configurarVideoYouTube(videoId);
} else if (esVideoLocal(videoUrl)) {
configurarVideoLocal(videoUrl);
} else {
configurarVideoDirecto(videoUrl);
}
}
private String extraerIdVideoYouTube(String url) {
Pattern pattern = Pattern.compile("(?<=watch\\?v=|/videos/|embed\\/|youtu.be\\/|\\/v\\/|\\/e\\/|watch\\?feature=player_embedded&v=)([^#\\&\\?]*)");
Matcher matcher = pattern.matcher(url);
if (matcher.find()) {
return matcher.group();
}
return null;
}
private void configurarVideoYouTube(String videoId) {
if (videoId == null) {
mostrarVistaNoVideo();
return;
}
ocultarVista(noVideoOverlay);
ocultarVista(externalVideoButton);
mostrarVista(youTubePlayerView);
getLifecycle().addObserver(youTubePlayerView);
youTubePlayerView.addYouTubePlayerListener(new AbstractYouTubePlayerListener() {
@Override
public void onReady(@NonNull YouTubePlayer youTubePlayer) {
youTubePlayer.loadVideo(videoId, 0);
}
@Override
public void onError(@NonNull YouTubePlayer youTubePlayer, @NonNull PlayerConstants.PlayerError error) {
Log.e(TAG, "Error en YouTube Player: " + error.name());
configurarVideoExterno(ejercicio.getVideoUrl());
}
});
}
private boolean esVideoLocal(String url) {
return url.startsWith("android.resource://") ||
url.startsWith("file://") ||
(!url.startsWith("http://") && !url.startsWith("https://"));
}
private boolean esYouTubeUrl(String url) {
return url.contains("youtube.com") || url.contains("youtu.be");
}
private void configurarVideoLocal(String videoUrl) {
try {
mostrarVista(videoContainer);
ocultarVista(noVideoOverlay);
ocultarVista(externalVideoButton);
Uri videoUri = Uri.parse(videoUrl);
videoView.setVideoURI(videoUri);
videoView.setOnPreparedListener(this);
videoView.setOnCompletionListener(this);
videoView.setOnErrorListener(this);
configurarControlesVideo();
} catch (Exception e) {
Log.e(TAG, "Error configurando video local", e);
mostrarVistaNoVideo();
}
}
private void configurarVideoDirecto(String videoUrl) {
try {
mostrarVista(videoContainer);
ocultarVista(noVideoOverlay);
ocultarVista(externalVideoButton);
Uri videoUri = Uri.parse(videoUrl);
videoView.setVideoURI(videoUri);
videoView.setOnPreparedListener(this);
videoView.setOnCompletionListener(this);
videoView.setOnErrorListener(this);
configurarControlesVideo();
} catch (Exception e) {
Log.e(TAG, "Error configurando video directo", e);
configurarVideoExterno(videoUrl);
}
}
private void configurarVideoExterno(String videoUrl) {
ocultarVista(videoContainer);
ocultarVista(noVideoOverlay);
mostrarVista(externalVideoButton);
externalVideoButton.setText("▶️ Ver en YouTube");
externalVideoButton.setOnClickListener(v -> {
try {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(videoUrl));
intent.setPackage("com.google.android.youtube");
if (intent.resolveActivity(requireContext().getPackageManager()) != null) {
startActivity(intent);
} else {
intent.setPackage(null);
startActivity(intent);
}
} catch (Exception e) {
Log.e(TAG, "Error abriendo video externo", e);
Toast.makeText(getContext(), "No se pudo abrir el video", Toast.LENGTH_SHORT).show();
}
});
}
private void mostrarVistaNoVideo() {
ocultarVista(videoContainer);
ocultarVista(externalVideoButton);
mostrarVista(noVideoOverlay);
}
private void configurarControlesVideo() {
btnPlayPause.setOnClickListener(v -> {
if (isVideoReady) {
if (isPlaying) {
pausarVideo();
} else {
reproducirVideo();
}
}
});
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser && isVideoReady) {
videoView.seekTo(progress);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
});
}
private void reproducirVideo() {
if (videoView != null && isVideoReady) {
videoView.start();
isPlaying = true;
btnPlayPause.setImageResource(R.drawable.baseline_pause_24);
actualizarSeekBar();
}
}
private void pausarVideo() {
if (videoView != null && videoView.isPlaying()) {
videoView.pause();
isPlaying = false;
btnPlayPause.setImageResource(R.drawable.baseline_play_arrow_24);
handler.removeCallbacks(updateSeekBarTask);
}
}
private void actualizarSeekBar() {
if (isPlaying && videoView != null) {
handler.post(updateSeekBarTask);
}
}
private Runnable updateSeekBarTask = new Runnable() {
@Override
public void run() {
if (videoView != null && isPlaying) {
int currentPosition = videoView.getCurrentPosition();
seekBar.setProgress(currentPosition);
durationTextView.setText(formatearTiempo(currentPosition));
handler.postDelayed(this, 1000);
}
}
};
private String formatearTiempo(int milliseconds) {
return String.format("%02d:%02d",
TimeUnit.MILLISECONDS.toMinutes(milliseconds),
TimeUnit.MILLISECONDS.toSeconds(milliseconds) % 60);
}
private void mostrarVista(View view) {
if (view != null) {
view.setVisibility(View.VISIBLE);
}
}
private void ocultarVista(View view) {
if (view != null) {
view.setVisibility(View.GONE);
}
}
@Override
public void onPrepared(MediaPlayer mp) {
Log.d(TAG, "Video preparado para reproducción");
isVideoReady = true;
int duration = videoView.getDuration();
seekBar.setMax(duration);
durationTextView.setText(formatearTiempo(duration));
reproducirVideo();
}
@Override
public void onCompletion(MediaPlayer mp) {
Log.d(TAG, "Video completado");
isPlaying = false;
btnPlayPause.setImageResource(R.drawable.baseline_play_arrow_24);
handler.removeCallbacks(updateSeekBarTask);
seekBar.setProgress(0);
durationTextView.setText("00:00");
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
Log.e(TAG, "Error en video: what=" + what + ", extra=" + extra);
String videoUrl = ejercicio.getVideoUrl();
if (esYouTubeUrl(videoUrl)) {
configurarVideoExterno(videoUrl);
} else {
mostrarVistaNoVideo();
}
return true;
}
@Override
public void onPause() {
super.onPause();
if (videoView != null && videoView.isPlaying()) {
pausarVideo();
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (handler != null) {
handler.removeCallbacks(updateSeekBarTask);
}
if (videoView != null) {
videoView.stopPlayback();
}
}
public Ejercicio getEjercicio() {
return ejercicio;
}
public String getTipoEjercicio() {
return tipoEjercicio;
}
public boolean tieneVideo() {
String videoUrl = ejercicio.getVideoUrl();
return videoUrl != null && !videoUrl.trim().isEmpty();
}
public boolean esEjercicioPrincipal() {
return "principal".equals(tipoEjercicio);
}
}

View File

@ -0,0 +1,142 @@
package com.borjabolufer.elevate.ui.fragments;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.borjabolufer.elevate.R;
import com.borjabolufer.elevate.model.DiasEntrenamiento;
import java.util.Arrays;
import java.util.List;
public class DiasEntrenamientoFragment extends Fragment {
public interface IOnDiasEntrenamientoListener {
void onDiasEntrenamientoSelected(DiasEntrenamiento diasEntrenamiento);
}
private IOnDiasEntrenamientoListener diasEntrenamientoListener;
private Spinner spinnerDias;
private Button btnContinuar;
private int diasSeleccionados = -1;
public DiasEntrenamientoFragment() {
super(R.layout.fragment_dias_entrenamiento);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initializeViews(view);
setupSpinner();
setupClickListener();
}
private void initializeViews(View view) {
spinnerDias = view.findViewById(R.id.spinnerDias);
btnContinuar = view.findViewById(R.id.btnContinuarDias);
btnContinuar.setEnabled(false);
btnContinuar.setAlpha(0.5f);
}
private void setupSpinner() {
List<String> diasOpciones = Arrays.asList(
"Selecciona días por semana",
"1 días por semana",
"2 días por semana",
"3 días por semana",
"4 días por semana",
"5 días por semana",
"6 días por semana",
"7 días por semana"
);
ArrayAdapter<String> adapter = new ArrayAdapter<>(
requireContext(),
android.R.layout.simple_spinner_item,
diasOpciones
);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerDias.setAdapter(adapter);
spinnerDias.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (position == 0) {
diasSeleccionados = -1;
btnContinuar.setEnabled(false);
btnContinuar.setAlpha(0.5f);
} else {
diasSeleccionados = position;
btnContinuar.setEnabled(true);
btnContinuar.setAlpha(1.0f);
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
diasSeleccionados = -1;
btnContinuar.setEnabled(false);
btnContinuar.setAlpha(0.5f);
}
});
}
private void setupClickListener() {
btnContinuar.setOnClickListener(v -> {
if (diasSeleccionados == -1) {
Toast.makeText(getContext(), "Por favor, selecciona los días de entrenamiento",
Toast.LENGTH_SHORT).show();
return;
}
if (diasSeleccionados < 3 || diasSeleccionados > 6) {
Toast.makeText(getContext(), "Selecciona entre 3 y 6 días por semana",
Toast.LENGTH_SHORT).show();
return;
}
if (diasEntrenamientoListener != null) {
DiasEntrenamiento seleccionado = new DiasEntrenamiento(diasSeleccionados);
diasEntrenamientoListener.onDiasEntrenamientoSelected(seleccionado);
} else {
Toast.makeText(getContext(), "Error: No se pudo procesar la selección",
Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof IOnDiasEntrenamientoListener) {
diasEntrenamientoListener = (IOnDiasEntrenamientoListener) context;
} else {
throw new ClassCastException(context + " must implement IOnDiasEntrenamientoListener");
}
}
@Override
public void onDetach() {
super.onDetach();
diasEntrenamientoListener = null;
}
}

View File

@ -0,0 +1,242 @@
package com.borjabolufer.elevate.ui.fragments;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.borjabolufer.elevate.adapters.ComidaAdapter;
import com.borjabolufer.elevate.databinding.FragmentDietBinding;
import com.borjabolufer.elevate.model.DiasEntrenamiento;
import com.borjabolufer.elevate.model.Dieta;
import com.borjabolufer.elevate.model.Comida;
import com.borjabolufer.elevate.model.ValorNutricional;
import com.borjabolufer.elevate.model.Semana;
import com.borjabolufer.elevate.utils.JsonParser;
import com.borjabolufer.elevate.utils.JsonUtils;
import com.google.android.material.chip.Chip;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
public class DietFragment extends Fragment {
private FragmentDietBinding binding;
private JsonParser jsonParser;
private Semana semanaActual;
private ComidaAdapter comidaAdapter;
private List<ComidaItem> comidasList;
private String diaSeleccionado = obtenerDiaActual();
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
binding = FragmentDietBinding.inflate(inflater, container, false);
View root = binding.getRoot();
inicializarComponentes();
configurarRecyclerView();
cargarDatos();
configurarChipsDias();
return root;
}
private String obtenerDiaActual() {
String[] diasSemana = {"Domingo", "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado"};
Calendar calendar = Calendar.getInstance();
int dia = calendar.get(Calendar.DAY_OF_WEEK);
return diasSemana[dia - 1];
}
private void inicializarComponentes() {
jsonParser = new JsonParser(getContext());
comidasList = new ArrayList<>();
}
private void cargarDatos() {
String email = obtenerEmailUsuarioActual();
if (email == null) {
Toast.makeText(getContext(), "Usuario no identificado", Toast.LENGTH_SHORT).show();
return;
}
String contenidoJson = JsonUtils.cargarPlanDelUsuario(getContext(), email);
if (contenidoJson == null) {
Toast.makeText(getContext(), "No se encontró el plan del usuario", Toast.LENGTH_SHORT).show();
return;
}
JSONObject rawJson;
try {
rawJson = new JSONObject(contenidoJson);
} catch (Exception e) {
Toast.makeText(getContext(), "Error al procesar el JSON", Toast.LENGTH_SHORT).show();
return;
}
semanaActual = jsonParser.convertirJsonASemana(rawJson);
if (jsonParser.validarSemana(semanaActual)) {
cargarDietaDelDia(diaSeleccionado);
} else {
Toast.makeText(getContext(), "Error al cargar los datos", Toast.LENGTH_SHORT).show();
}
}
private void configurarRecyclerView() {
comidaAdapter = new ComidaAdapter(comidasList, this);
binding.recyclerComidas.setLayoutManager(new LinearLayoutManager(getContext()));
binding.recyclerComidas.setAdapter(comidaAdapter);
}
private void configurarChipsDias() {
String[] diasSemana = {"Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado", "Domingo"};
for (String dia : diasSemana) {
Chip chip = new Chip(getContext());
chip.setText(dia);
chip.setCheckable(true);
chip.setChecked(dia.equals(diaSeleccionado));
chip.setOnCheckedChangeListener((compoundButton, isChecked) -> {
if (isChecked) {
diaSeleccionado = dia;
cargarDietaDelDia(dia);
for (int i = 0; i < binding.chipGroupDiasDieta.getChildCount(); i++) {
Chip otherChip = (Chip) binding.chipGroupDiasDieta.getChildAt(i);
if (!otherChip.getText().equals(dia)) {
otherChip.setChecked(false);
}
}
}
});
binding.chipGroupDiasDieta.addView(chip);
}
}
private void cargarDietaDelDia(String dia) {
DiasEntrenamiento diaEntrenamiento = jsonParser.obtenerDia(semanaActual, dia);
if (diaEntrenamiento == null || diaEntrenamiento.getDieta() == null) {
mostrarSinDieta();
return;
}
Dieta dieta = diaEntrenamiento.getDieta();
comidasList.clear();
int totalCalorias = jsonParser.calcularCaloriasTotales(diaEntrenamiento);
actualizarResumenNutricional(dieta, totalCalorias);
if (dieta.getDesayuno() != null) {
comidasList.add(new ComidaItem("Desayuno", dieta.getDesayuno(), "desayuno"));
}
if (dieta.getComida() != null) {
comidasList.add(new ComidaItem("Comida", dieta.getComida(), "comida"));
}
if (dieta.getCena() != null) {
comidasList.add(new ComidaItem("Cena", dieta.getCena(), "cena"));
}
if (dieta.getSnacks() != null) {
comidasList.add(new ComidaItem("Snacks", dieta.getSnacks(), "snacks"));
}
comidaAdapter.notifyDataSetChanged();
}
private void mostrarSinDieta() {
binding.textCaloriasTotales.setText("0");
binding.textProteinas.setText("0g");
binding.textCarbohidratos.setText("0g");
binding.textGrasas.setText("0g");
comidasList.clear();
comidaAdapter.notifyDataSetChanged();
}
private void actualizarResumenNutricional(Dieta dieta, int totalCalorias) {
int totalProteinas = 0, totalCarbohidratos = 0, totalGrasas = 0;
ValorNutricional[] valores = {
dieta.getDesayuno() != null ? dieta.getDesayuno().getValor_nutricional() : null,
dieta.getComida() != null ? dieta.getComida().getValor_nutricional() : null,
dieta.getCena() != null ? dieta.getCena().getValor_nutricional() : null,
dieta.getSnacks() != null ? dieta.getSnacks().getValor_nutricional() : null
};
for (ValorNutricional valor : valores) {
if (valor != null) {
totalProteinas += valor.getProteínas();
totalCarbohidratos += valor.getCarbohidratos();
totalGrasas += valor.getGrasas();
}
}
binding.textCaloriasTotales.setText(String.valueOf(totalCalorias));
binding.textProteinas.setText(totalProteinas + "g");
binding.textCarbohidratos.setText(totalCarbohidratos + "g");
binding.textGrasas.setText(totalGrasas + "g");
actualizarBarrasProgreso(totalProteinas, totalCarbohidratos, totalGrasas);
}
private void actualizarBarrasProgreso(int proteinas, int carbos, int grasas) {
int objetivoProteinas = 150;
int objetivoCarbos = 200;
int objetivoGrasas = 80;
binding.progressProteinas.setProgress((proteinas * 100) / objetivoProteinas);
binding.progressCarbohidratos.setProgress((carbos * 100) / objetivoCarbos);
binding.progressGrasas.setProgress((grasas * 100) / objetivoGrasas);
}
private String obtenerEmailUsuarioActual() {
SharedPreferences prefs = getActivity().getSharedPreferences("ElevatePrefs", Context.MODE_PRIVATE);
String email = prefs.getString("user_email", null);
Log.d("UsuarioActual", "Email cargado: " + email);
return email;
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
public static class ComidaItem {
private String nombre;
private Comida comida;
private String tipo;
public ComidaItem(String nombre, Comida comida, String tipo) {
this.nombre = nombre;
this.comida = comida;
this.tipo = tipo;
}
public String getNombre() { return nombre; }
public Comida getComida() { return comida; }
public String getTipo() { return tipo; }
}
}

View File

@ -0,0 +1,54 @@
package com.borjabolufer.elevate.ui.fragments;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.borjabolufer.elevate.R;
import com.borjabolufer.elevate.model.Edad;
public class EdadFragment extends Fragment {
public interface IOnEdadListener {
void onEdadSelected(Edad edad);
}
private IOnEdadListener edadListener;
public EdadFragment() {
super(R.layout.fragment_edad);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
EditText editEdad = view.findViewById(R.id.editEdad);
Button btnContinuar = view.findViewById(R.id.btnContinuarEdad);
btnContinuar.setOnClickListener(v -> {
String edadTexto = editEdad.getText().toString().trim();
if (!edadTexto.isEmpty()) {
int edadValor = Integer.parseInt(edadTexto);
Edad edad = new Edad(edadValor);
edadListener.onEdadSelected(edad);
}
});
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof IOnEdadListener) {
edadListener = (IOnEdadListener) context;
} else {
throw new ClassCastException(context.toString() + " must implement IOnEdadListener");
}
}
}

View File

@ -0,0 +1,527 @@
package com.borjabolufer.elevate.ui.fragments;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.borjabolufer.elevate.MainActivity;
import com.borjabolufer.elevate.R;
import com.borjabolufer.elevate.adapters.EjercicioAdapter;
import com.borjabolufer.elevate.databinding.FragmentHomeBinding;
import com.borjabolufer.elevate.model.DiasEntrenamiento;
import com.borjabolufer.elevate.model.Entrenamiento;
import com.borjabolufer.elevate.model.Ejercicio;
import com.borjabolufer.elevate.model.Semana;
import com.borjabolufer.elevate.ui.fragments.DetalleEjercicioFragment;
import com.borjabolufer.elevate.utils.JsonParser;
import com.borjabolufer.elevate.utils.JsonUtils;
import com.google.android.material.chip.Chip;
import org.json.JSONObject;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class HomeFragment extends Fragment implements EjercicioAdapter.OnItemClickListener {
private static final String TAG = "HomeFragment";
private FragmentHomeBinding binding;
private JsonParser jsonParser;
private Semana semanaActual;
private EjercicioAdapter ejercicioAdapter;
private List<EjercicioItem> ejerciciosList;
private String diaSeleccionado = obtenerDiaActual();
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
binding = FragmentHomeBinding.inflate(inflater, container, false);
View root = binding.getRoot();
Log.d(TAG, "HomeFragment creado");
inicializarComponentes();
configurarRecyclerView();
configurarChipsDias();
cargarDatos();
return root;
}
private void inicializarComponentes() {
jsonParser = new JsonParser(getContext());
ejerciciosList = new ArrayList<>();
Calendar calendar = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("EEEE, dd MMM yyyy", new Locale("es", "ES"));
sdf.setTimeZone(calendar.getTimeZone());
binding.textFechaActual.setText(sdf.format(calendar.getTime()));
Log.d(TAG, "Componentes inicializados, día seleccionado: " + diaSeleccionado);
}
private void cargarDatos() {
String email = obtenerEmailUsuarioActual();
if (email == null) {
mostrarError("Usuario no identificado");
return;
}
Log.d(TAG, "Cargando datos para usuario: " + email);
String contenidoJson = JsonUtils.cargarPlanDelUsuario(getContext(), email);
if (contenidoJson == null) {
mostrarError("No se encontró el plan del usuario");
return;
}
JSONObject rawJson;
try {
rawJson = new JSONObject(contenidoJson);
} catch (Exception e) {
Log.e(TAG, "Error al procesar JSON", e);
mostrarError("Error al procesar el JSON");
return;
}
semanaActual = jsonParser.convertirJsonASemana(rawJson);
if (jsonParser.validarSemana(semanaActual)) {
Log.d(TAG, "Semana validada correctamente");
cargarEjerciciosDelDia(diaSeleccionado);
} else {
Log.e(TAG, "Error validando semana");
mostrarError("Error al cargar los datos");
}
}
private void configurarRecyclerView() {
ejercicioAdapter = new EjercicioAdapter(ejerciciosList, this);
binding.recyclerEjercicios.setLayoutManager(new LinearLayoutManager(getContext()));
binding.recyclerEjercicios.setAdapter(ejercicioAdapter);
}
private void configurarChipsDias() {
String[] diasSemana = {"Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado", "Domingo"};
for (String dia : diasSemana) {
Chip chip = new Chip(getContext());
chip.setText(dia);
chip.setCheckable(true);
chip.setChecked(dia.equals(diaSeleccionado));
chip.setOnCheckedChangeListener((compoundButton, isChecked) -> {
if (isChecked) {
diaSeleccionado = dia;
Log.d(TAG, "Día seleccionado cambiado a: " + dia);
cargarEjerciciosDelDia(dia);
for (int i = 0; i < binding.chipGroupDias.getChildCount(); i++) {
Chip otherChip = (Chip) binding.chipGroupDias.getChildAt(i);
if (!otherChip.getText().equals(dia)) {
otherChip.setChecked(false);
}
}
}
});
binding.chipGroupDias.addView(chip);
}
}
private void cargarEjerciciosDelDia(String dia) {
if (semanaActual == null) {
Log.w(TAG, "semanaActual es null, no se pueden cargar ejercicios");
return;
}
DiasEntrenamiento diaEntrenamiento = jsonParser.obtenerDia(semanaActual, dia);
if (diaEntrenamiento == null || diaEntrenamiento.getEntrenamiento() == null) {
Log.d(TAG, "Día de descanso: " + dia);
mostrarDiaDescanso(dia);
return;
}
Entrenamiento entrenamiento = diaEntrenamiento.getEntrenamiento();
mostrarDiaEntrenamiento(dia, entrenamiento);
}
private void mostrarDiaDescanso(String dia) {
binding.textDiaActual.setText("Día de descanso");
binding.textDescripcionDia.setText("Hoy es tu día de recuperación. ¡Disfrútalo!");
ejerciciosList.clear();
if (ejercicioAdapter != null) {
ejercicioAdapter.notifyDataSetChanged();
}
}
private void mostrarDiaEntrenamiento(String dia, Entrenamiento entrenamiento) {
binding.textDiaActual.setText(dia);
binding.textDescripcionDia.setText("Entrenamiento de " + dia.toLowerCase());
ejerciciosList.clear();
if (entrenamiento.getCalentamiento() != null && !entrenamiento.getCalentamiento().isEmpty()) {
ejerciciosList.add(new EjercicioItem("CALENTAMIENTO", 0, 0, "calentamiento", true));
for (Ejercicio ejercicio : entrenamiento.getCalentamiento()) {
ejerciciosList.add(new EjercicioItem(
ejercicio.getNombre(),
ejercicio.getSeries(),
ejercicio.getDescripcion(),
ejercicio.getRepeticiones(),
ejercicio.getDescanso(),
ejercicio.getVideoUrl(),
"calentamiento"
));
}
}
if (entrenamiento.getEjercicios_principales() != null && !entrenamiento.getEjercicios_principales().isEmpty()) {
ejerciciosList.add(new EjercicioItem("EJERCICIOS PRINCIPALES", 0, 0, "principal", true));
for (Ejercicio ejercicio : entrenamiento.getEjercicios_principales()) {
ejerciciosList.add(new EjercicioItem(
ejercicio.getNombre(),
ejercicio.getSeries(),
ejercicio.getDescripcion(),
ejercicio.getRepeticiones(),
ejercicio.getDescanso(),
ejercicio.getVideoUrl(),
"principal"
));
}
}
if (entrenamiento.getEnfriamiento() != null && !entrenamiento.getEnfriamiento().isEmpty()) {
ejerciciosList.add(new EjercicioItem("ENFRIAMIENTO", 0, 0, "enfriamiento", true));
for (Ejercicio ejercicio : entrenamiento.getEnfriamiento()) {
ejerciciosList.add(new EjercicioItem(
ejercicio.getNombre(),
ejercicio.getSeries(),
ejercicio.getDescripcion(),
ejercicio.getRepeticiones(),
ejercicio.getDescanso(),
ejercicio.getVideoUrl(),
"enfriamiento"
));
}
}
if (ejercicioAdapter != null) {
ejercicioAdapter.notifyDataSetChanged();
}
Log.d(TAG, "Ejercicios cargados para " + dia + ": " + ejerciciosList.size() + " items");
}
private String obtenerEmailUsuarioActual() {
try {
SharedPreferences prefs = getActivity().getSharedPreferences("ElevatePrefs", Context.MODE_PRIVATE);
String email = prefs.getString("user_email", null);
Log.d(TAG, "Email cargado: " + email);
return email;
} catch (Exception e) {
Log.e(TAG, "Error obteniendo email usuario", e);
return null;
}
}
private String obtenerDiaActual() {
try {
SimpleDateFormat sdf = new SimpleDateFormat("EEEE", new Locale("es", "ES"));
Calendar calendar = Calendar.getInstance();
String diaFormateado = sdf.format(calendar.getTime());
return diaFormateado.substring(0, 1).toUpperCase() + diaFormateado.substring(1).toLowerCase();
} catch (Exception e) {
Log.e(TAG, "Error obteniendo día actual", e);
return "Lunes";
}
}
private void mostrarError(String mensaje) {
Log.e(TAG, "Error: " + mensaje);
if (getContext() != null) {
Toast.makeText(getContext(), mensaje, Toast.LENGTH_SHORT).show();
}
}
@Override
public void onItemClick(EjercicioItem item) {
if (item.isEsHeader()) {
Log.d(TAG, "Click en header ignorado: " + item.getNombre());
return;
}
Log.d(TAG, "Click en ejercicio: " + item.getNombre());
try {
if (item.getNombre() == null || item.getTipo() == null) {
mostrarError("Datos del ejercicio incompletos");
return;
}
Ejercicio ejercicio = new Ejercicio(
item.getNombre(),
item.getDescripcion() != null ? item.getDescripcion() : "",
item.getSeries(),
item.getRepeticiones(),
item.getDescanso() != null ? item.getDescanso() : "",
item.getVideoURL() != null ? item.getVideoURL() : ""
);
if (!isNavigationSafe()) {
Log.w(TAG, "Navegación no segura, reintentando...");
mostrarError("Espera un momento e inténtalo de nuevo");
return;
}
Bundle args = new Bundle();
args.putSerializable("ejercicio", ejercicio);
args.putString("tipo_ejercicio", item.getTipo());
navegarConNavController(args);
} catch (Exception e) {
Log.e(TAG, "Error navegando a detalle ejercicio", e);
mostrarError("Error al abrir detalle del ejercicio");
}
}
private boolean isNavigationSafe() {
try {
return isAdded() &&
getContext() != null &&
getActivity() != null &&
!getActivity().isFinishing() &&
!getActivity().isDestroyed() &&
getView() != null;
} catch (Exception e) {
Log.e(TAG, "Error verificando seguridad navegación", e);
return false;
}
}
private void navegarConNavController(Bundle args) {
try {
NavController navController = Navigation.findNavController(requireView());
if (navController.getCurrentDestination() != null) {
int currentDestId = navController.getCurrentDestination().getId();
if (currentDestId == R.id.nav_home) {
navController.navigate(R.id.action_home_to_detalle_ejercicio, args);
Log.d(TAG, "Navegación exitosa con NavController");
} else {
Log.w(TAG, "No estamos en home fragment, usando fallback");
navegarConFragmentManager(args);
}
} else {
Log.w(TAG, "Destino actual es null, usando fallback");
navegarConFragmentManager(args);
}
} catch (Exception navException) {
Log.w(TAG, "NavController falló, usando FragmentManager", navException);
navegarConFragmentManager(args);
}
}
private void navegarConFragmentManager(Bundle args) {
try {
Ejercicio ejercicio = (Ejercicio) args.getSerializable("ejercicio");
String tipo = args.getString("tipo_ejercicio");
if (ejercicio == null || tipo == null) {
mostrarError("Error en datos del ejercicio");
return;
}
DetalleEjercicioFragment detalleFragment = DetalleEjercicioFragment.newInstance(ejercicio, tipo);
requireActivity().getSupportFragmentManager()
.beginTransaction()
.replace(R.id.nav_host_fragment_content_main, detalleFragment)
.addToBackStack("detalle_ejercicio")
.commitAllowingStateLoss();
Log.d(TAG, "Navegación exitosa con FragmentManager");
} catch (Exception e) {
Log.e(TAG, "Error en navegación con FragmentManager", e);
mostrarError("No se pudo abrir el detalle");
}
}
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "HomeFragment onResume");
verificarEstadoFragment();
if (semanaActual == null) {
Log.d(TAG, "Recargando datos en onResume");
cargarDatos();
}
}
private void verificarEstadoFragment() {
try {
if (getActivity() != null) {
FragmentManager fm = getActivity().getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 5) {
Log.w(TAG, "Demasiados fragments en back stack: " + fm.getBackStackEntryCount());
if (getActivity() instanceof MainActivity) {
((MainActivity) getActivity()).resetNavigation();
}
}
}
} catch (Exception e) {
Log.e(TAG, "Error verificando estado fragment", e);
}
}
public void refrescarDatos() {
Log.d(TAG, "Refrescando datos del HomeFragment");
if (isAdded() && getContext() != null) {
cargarDatos();
}
}
public void navegarADia(String dia) {
Log.d(TAG, "Navegando a día: " + dia);
if (dia != null && !dia.isEmpty()) {
diaSeleccionado = dia;
for (int i = 0; i < binding.chipGroupDias.getChildCount(); i++) {
Chip chip = (Chip) binding.chipGroupDias.getChildAt(i);
chip.setChecked(chip.getText().equals(dia));
}
cargarEjerciciosDelDia(dia);
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
Log.d(TAG, "HomeFragment onDestroyView");
ejercicioAdapter = null;
ejerciciosList = null;
semanaActual = null;
binding = null;
}
public static class EjercicioItem implements Serializable {
private String nombre;
private String descripcion;
private int series;
private int repeticiones;
private String descanso;
private String tipo;
private String videoURL;
private boolean esHeader;
public EjercicioItem(String nombre, int series, int repeticiones, String tipo, boolean esHeader) {
this.nombre = nombre != null ? nombre : "";
this.series = series;
this.repeticiones = repeticiones;
this.tipo = tipo != null ? tipo : "";
this.esHeader = esHeader;
}
public EjercicioItem(String nombre, int series, String descripcion, int repeticiones, String descanso, String videoURL, String tipo) {
this.nombre = nombre != null ? nombre : "";
this.series = series;
this.descripcion = descripcion != null ? descripcion : "";
this.repeticiones = repeticiones;
this.descanso = descanso != null ? descanso : "";
this.videoURL = videoURL != null ? videoURL : "";
this.tipo = tipo != null ? tipo : "";
this.esHeader = false;
}
public String getNombre() { return nombre != null ? nombre : ""; }
public void setNombre(String nombre) { this.nombre = nombre != null ? nombre : ""; }
public String getDescripcion() { return descripcion != null ? descripcion : ""; }
public void setDescripcion(String descripcion) { this.descripcion = descripcion != null ? descripcion : ""; }
public int getSeries() { return series; }
public void setSeries(int series) { this.series = series; }
public int getRepeticiones() { return repeticiones; }
public void setRepeticiones(int repeticiones) { this.repeticiones = repeticiones; }
public String getDescanso() { return descanso != null ? descanso : ""; }
public void setDescanso(String descanso) { this.descanso = descanso != null ? descanso : ""; }
public String getTipo() { return tipo != null ? tipo : ""; }
public void setTipo(String tipo) { this.tipo = tipo != null ? tipo : ""; }
public String getVideoURL() { return videoURL != null ? videoURL : ""; }
public void setVideoURL(String videoURL) { this.videoURL = videoURL != null ? videoURL : ""; }
public boolean isEsHeader() { return esHeader; }
public void setEsHeader(boolean esHeader) { this.esHeader = esHeader; }
}
}

View File

@ -0,0 +1,80 @@
package com.borjabolufer.elevate.ui.fragments;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.borjabolufer.elevate.R;
import com.borjabolufer.elevate.model.Intensidad;
import com.borjabolufer.elevate.utils.ColorUtils;
import com.google.android.material.button.MaterialButton;
public class IntensidadFragment extends Fragment {
public interface IOnIntensidadListener {
void onIntensidadSelected(Intensidad intensidad);
}
private IOnIntensidadListener intensidadListener;
private Intensidad intensidadSeleccionada;
private MaterialButton selectedButton;
public IntensidadFragment() {
super(R.layout.fragment_intensidad);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof IOnIntensidadListener) {
intensidadListener = (IOnIntensidadListener) context;
} else {
throw new ClassCastException(context.toString() + " must implement IOnIntensidadListener");
}
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
MaterialButton btnBaja = view.findViewById(R.id.intensidad_baja);
MaterialButton btnMedia = view.findViewById(R.id.intensidad_media);
MaterialButton btnAlta = view.findViewById(R.id.intensidad_alta);
Button btnContinuar = view.findViewById(R.id.btnContinuarIntensidad);
btnContinuar.setEnabled(false);
View.OnClickListener listener = v -> {
if (selectedButton != null) {
ColorUtils.limpiarSeleccion(selectedButton);
}
selectedButton = (MaterialButton) v;
ColorUtils.marcarSeleccion(selectedButton);
if (v.getId() == R.id.intensidad_baja) {
intensidadSeleccionada = new Intensidad("Baja");
} else if (v.getId() == R.id.intensidad_media) {
intensidadSeleccionada = new Intensidad("Media");
} else if (v.getId() == R.id.intensidad_alta) {
intensidadSeleccionada = new Intensidad("Alta");
}
btnContinuar.setEnabled(true);
};
btnBaja.setOnClickListener(listener);
btnMedia.setOnClickListener(listener);
btnAlta.setOnClickListener(listener);
btnContinuar.setOnClickListener(v -> {
if (intensidadSeleccionada != null && intensidadListener != null) {
intensidadListener.onIntensidadSelected(intensidadSeleccionada);
}
});
}
}

View File

@ -0,0 +1,890 @@
package com.borjabolufer.elevate.ui.fragments;
import android.animation.ObjectAnimator;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.borjabolufer.elevate.LoginActivity;
import com.borjabolufer.elevate.MainActivity;
import com.borjabolufer.elevate.PreguntasActivity;
import com.borjabolufer.elevate.R;
import com.borjabolufer.elevate.model.Usuario;
import com.borjabolufer.elevate.model.UsuarioDbHelper;
import com.borjabolufer.elevate.model.UsuarioEntity;
import com.borjabolufer.elevate.utils.ChatGptApi;
import com.borjabolufer.elevate.utils.PasswordUtils;
import com.borjabolufer.elevate.utils.PlanActualizadoDialog;
import org.json.JSONException;
import java.util.ArrayList;
import java.util.List;
public class LoadingPlanFragment extends Fragment {
private static final String ARG_USUARIO = "usuario";
private static final String ARG_NOMBRE_USUARIO = "nombre_usuario";
private static final String ARG_CONTRASENA_USUARIO = "contrasena_usuario";
private static final String ARG_ES_REGENERACION = "es_regeneracion";
private static final String TAG = "LoadingPlanFragment";
private ImageView loadingIcon;
private ProgressBar progressBar;
private TextView progressText;
private TextView statusText;
private RecyclerView logRecyclerView;
private LogAdapter logAdapter;
private Usuario usuario;
private String nombreUsuario;
private String contrasenaUsuario;
private boolean esRegeneracion = false;
private List<LogItem> logItems;
private Handler handler;
private int currentProgress = 0;
private int currentLogStep = 0;
private final String[] logMessages = {
"🔍 Analizando tu perfil fitness...",
"📊 Calculando tu tasa metabólica basal (TMB)...",
"🎯 Definiendo objetivos personalizados...",
"💪 Seleccionando ejercicios para tu nivel...",
"🏋️ Creando rutina de calentamiento dinámico...",
"⚡ Diseñando ejercicios principales...",
"🧘 Configurando rutina de enfriamiento...",
"🥗 Planificando tu menú nutricional...",
"🍎 Calculando macronutrientes necesarios...",
"📱 Seleccionando recetas saludables...",
"⏰ Optimizando horarios de comida...",
"💧 Configurando hidratación diaria...",
"😴 Estableciendo recomendaciones de descanso...",
"📦 Evaluando compatibilidad de alimentos...",
"🔄 Ajustando proporciones de entrenamiento...",
"📆 Distribuyendo días de ejercicio y descanso...",
"🧠 Analizando patrones de comportamiento previos...",
"📈 Comparando métricas con otros usuarios similares...",
"📚 Revisando literatura científica reciente...",
"🧮 Recalculando parámetros por coherencia...",
"⚙️ Verificando integridad del plan...",
"💡 Incorporando sugerencias inteligentes...",
"🎯 Refinando los objetivos establecidos...",
"🔗 Integrando plan de ejercicios y nutrición...",
"✨ Aplicando algoritmos de personalización avanzada...",
"🔐 Encriptando tus datos de forma segura...",
"📤 Subiendo tu configuración a la nube...",
"📥 Sincronizando con tu perfil...",
"📂 Generando informe detallado...",
"✅ Finalizando y guardando tu plan personalizado..."
};
private final int[] logDurations = {
1500, 2000, 1800, 2200, 1600, 2500, 1400, 2000,
1700, 1900, 1300, 1200, 1400, 1800, 1600, 2100,
1900, 2300, 1800, 1400, 1500, 1700, 1600, 2400,
1100, 1200, 1400, 1300, 2000, 1800
};
public static LoadingPlanFragment newInstance(Usuario usuario, String nombreUsuario, String contrasenaUsuario) {
LoadingPlanFragment fragment = new LoadingPlanFragment();
Bundle args = new Bundle();
args.putSerializable(ARG_USUARIO, usuario);
args.putString(ARG_NOMBRE_USUARIO, nombreUsuario);
args.putString(ARG_CONTRASENA_USUARIO, contrasenaUsuario);
args.putBoolean(ARG_ES_REGENERACION, false);
fragment.setArguments(args);
return fragment;
}
public static LoadingPlanFragment newInstanceRegeneration(Usuario usuario, String nombreUsuario) {
LoadingPlanFragment fragment = new LoadingPlanFragment();
Bundle args = new Bundle();
args.putSerializable(ARG_USUARIO, usuario);
args.putString(ARG_NOMBRE_USUARIO, nombreUsuario);
args.putString(ARG_CONTRASENA_USUARIO, "regeneration");
args.putBoolean(ARG_ES_REGENERACION, true);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
usuario = (Usuario) getArguments().getSerializable(ARG_USUARIO);
nombreUsuario = getArguments().getString(ARG_NOMBRE_USUARIO);
contrasenaUsuario = getArguments().getString(ARG_CONTRASENA_USUARIO);
esRegeneracion = getArguments().getBoolean(ARG_ES_REGENERACION, false);
}
handler = new Handler();
logItems = new ArrayList<>();
Log.d(TAG, "LoadingPlanFragment creado - Es regeneración: " + esRegeneracion);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_loading_plan, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
try {
FragmentManager fm = requireActivity().getSupportFragmentManager();
for (int i = 0; i < fm.getBackStackEntryCount(); i++) {
FragmentManager.BackStackEntry entry = fm.getBackStackEntryAt(i);
if (entry.getName() != null && entry.getName().contains("loading")) {
fm.popBackStackImmediate(entry.getId(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
break;
}
}
} catch (Exception e) {
Log.w(TAG, "Error limpiando back stack en onViewCreated", e);
}
initViews(view);
setupRecyclerView();
startLoadingAnimation();
startLogSimulation();
generatePlan();
}
private void autodestruirse() {
try {
if (isAdded() && getActivity() != null) {
getActivity().runOnUiThread(() -> {
try {
FragmentManager fm = requireActivity().getSupportFragmentManager();
fm.beginTransaction()
.remove(LoadingPlanFragment.this)
.commitNowAllowingStateLoss();
} catch (Exception e) {
Log.e(TAG, "Error en autodestrucción", e);
}
});
}
} catch (Exception e) {
Log.e(TAG, "Error general en autodestruirse", e);
}
}
private void initViews(View view) {
loadingIcon = view.findViewById(R.id.loadingIcon);
progressBar = view.findViewById(R.id.progressBar);
progressText = view.findViewById(R.id.progressText);
statusText = view.findViewById(R.id.statusText);
logRecyclerView = view.findViewById(R.id.logRecyclerView);
}
private void setupRecyclerView() {
logAdapter = new LogAdapter(logItems);
logRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
logRecyclerView.setAdapter(logAdapter);
}
private void startLoadingAnimation() {
RotateAnimation rotateAnimation = new RotateAnimation(
0f, 360f,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f
);
rotateAnimation.setDuration(2000);
rotateAnimation.setRepeatCount(Animation.INFINITE);
rotateAnimation.setInterpolator(new LinearInterpolator());
loadingIcon.startAnimation(rotateAnimation);
if (esRegeneracion) {
statusText.setText("Regenerando tu plan personalizado...");
} else {
statusText.setText("Creando tu plan personalizado...");
}
}
private void startLogSimulation() {
handler.postDelayed(new Runnable() {
@Override
public void run() {
if (currentLogStep < logMessages.length && isAdded()) {
addLogMessage(logMessages[currentLogStep]);
updateProgress();
currentLogStep++;
if (currentLogStep < logMessages.length) {
int delay = logDurations[currentLogStep - 1];
handler.postDelayed(this, delay);
}
}
}
}, 1000);
}
private void addLogMessage(String message) {
if (Looper.myLooper() == Looper.getMainLooper()) {
addLogMessageInternal(message);
} else {
runOnUiThreadSafe(() -> addLogMessageInternal(message));
}
}
private void addLogMessageInternal(String message) {
if (!isAdded() || getContext() == null) {
Log.w(TAG, "Fragment no está adjunto, no se puede actualizar UI");
return;
}
try {
LogItem logItem = new LogItem(message, System.currentTimeMillis());
logItems.add(logItem);
if (logAdapter != null) {
logAdapter.notifyItemInserted(logItems.size() - 1);
if (logRecyclerView != null) {
logRecyclerView.scrollToPosition(logItems.size() - 1);
}
}
} catch (Exception e) {
Log.e(TAG, "Error al actualizar log: " + e.getMessage());
}
}
private void updateProgress() {
runOnUiThreadSafe(() -> {
currentProgress = (currentLogStep * 100) / logMessages.length;
if (progressBar != null && progressText != null && statusText != null) {
ObjectAnimator progressAnimator = ObjectAnimator.ofInt(progressBar, "progress", progressBar.getProgress(), currentProgress);
progressAnimator.setDuration(500);
progressAnimator.start();
progressText.setText(currentProgress + "%");
if (currentProgress < 30) {
statusText.setText(esRegeneracion ? "Reanalyzando tu perfil..." : "Analizando tu perfil...");
} else if (currentProgress < 60) {
statusText.setText(esRegeneracion ? "Actualizando rutinas..." : "Creando rutinas personalizadas...");
} else if (currentProgress < 90) {
statusText.setText(esRegeneracion ? "Rediseñando plan nutricional..." : "Diseñando plan nutricional...");
} else {
statusText.setText(esRegeneracion ? "Finalizando actualización..." : "Finalizando personalización...");
}
}
});
}
private void generatePlan() {
if (usuario == null || nombreUsuario == null) {
showError("Datos de usuario incompletos");
return;
}
String prompt = buildPrompt();
if (!esRegeneracion && contrasenaUsuario != null && !contrasenaUsuario.equals("regeneration")) {
Log.d(TAG, "Creando nuevo usuario en BD");
crearUsuarioEnBD();
} else {
Log.d(TAG, "Regenerando plan para usuario existente");
}
try {
ChatGptApi.enviarPrompt(prompt, new ChatGptApi.ChatGptCallback() {
@Override
public void onSuccess(String jsonResponse) {
if (getActivity() != null) {
getActivity().runOnUiThread(() -> {
new Thread(() -> {
UsuarioDbHelper dbHelper = new UsuarioDbHelper(requireContext());
int updated = dbHelper.updatePlanJson(nombreUsuario, jsonResponse);
Log.d(TAG, "Filas actualizadas: " + updated);
if (updated > 0) {
boolean saved = dbHelper.guardarPlanJsonEnArchivo(getContext(), nombreUsuario);
Log.d(TAG, "Plan guardado en archivo: " + saved);
}
if (getActivity() != null) {
getActivity().runOnUiThread(() -> {
handler.postDelayed(() -> {
if (isAdded()) {
addLogMessage(esRegeneracion ? "🎉 ¡Tu plan ha sido actualizado!" : "🎉 ¡Tu plan está listo!");
finishLoading();
}
}, 2000);
});
}
}).start();
});
}
}
@Override
public void onFailure(String error) {
if (getActivity() != null) {
getActivity().runOnUiThread(() -> {
if (isAdded()) {
addLogMessage("❌ Error: " + error);
showError("Error al generar el plan: " + error);
}
});
}
}
});
} catch (JSONException e) {
if (isAdded()) {
showError("Error en configuración: " + e.getMessage());
}
}
}
private void runOnUiThreadSafe(Runnable action) {
if (getActivity() != null) {
getActivity().runOnUiThread(() -> {
if (isAdded() && getContext() != null) {
action.run();
}
});
}
}
private void crearUsuarioEnBD() {
new Thread(() -> {
UsuarioDbHelper dbHelper = new UsuarioDbHelper(requireContext());
if (!dbHelper.checkUserEmailExists(nombreUsuario)) {
UsuarioEntity usuarioEntity = new UsuarioEntity();
usuarioEntity.setEmail(nombreUsuario);
String nombreCompleto = nombreUsuario;
if (getActivity() instanceof PreguntasActivity) {
PreguntasActivity preguntasActivity = (PreguntasActivity) getActivity();
String nombre = preguntasActivity.getNombreCompleto();
if (nombre != null && !nombre.trim().isEmpty()) {
nombreCompleto = nombre;
}
}
usuarioEntity.setNombreCompleto(nombreCompleto);
usuarioEntity.setPasswordHash(PasswordUtils.hashPassword(contrasenaUsuario));
usuarioEntity.setSexo(usuario.getSexo() != null ? usuario.getSexo().getSexo() : "");
usuarioEntity.setEdad(usuario.getEdad() != null ? usuario.getEdad().getEdad() : 0);
usuarioEntity.setObjetivo(usuario.getObjetivo() != null ? usuario.getObjetivo().getObjetivo() : "");
usuarioEntity.setDiasEntrenamiento(usuario.getDias() != null ? usuario.getDias().getCantidad() : 0);
usuarioEntity.setIntentensidad(usuario.getIntensidad() != null ? usuario.getIntensidad().getIntensidad() : "");
usuarioEntity.setPeso(usuario.getPeso() != null ? usuario.getPeso().getPeso() : 0.0);
usuarioEntity.setAltura(usuario.getAltura() != null ? usuario.getAltura().getAltura() : 0.0);
long result = dbHelper.insertarUsuarioCompleto(usuarioEntity);
Log.d(TAG, "Usuario insertado con ID: " + result + ", Nombre: " + nombreCompleto + ", Email: " + nombreUsuario);
} else {
Log.d(TAG, "Usuario ya existe: " + nombreUsuario);
}
}).start();
}
@NonNull
private String buildPrompt() {
String tipoOperacion = esRegeneracion ? "REGENERA y ACTUALIZA" : "Genera";
int diasEntrenamiento = usuario.getDias().getCantidad();
String distribucionDias = generarDistribucionDias(diasEntrenamiento);
String explicacionDias = generarExplicacionDias(diasEntrenamiento);
return "Actúa como un entrenador personal certificado y nutricionista experto. " + tipoOperacion + " un plan semanal personalizado en formato JSON ESTRICTO y FUNCIONAL a partir de los siguientes datos personales:" +
"\n\n👤 DATOS DEL USUARIO:" +
"\n- Edad: " + usuario.getEdad().getEdad() + " años" +
"\n- Sexo: " + usuario.getSexo().getSexo() +
"\n- Altura: " + usuario.getAltura().getAltura() + " cm" +
"\n- Peso: " + usuario.getPeso().getPeso() + " kg" +
"\n- Objetivo: " + usuario.getObjetivo().getObjetivo() +
"\n- Intensidad: " + usuario.getIntensidad().getIntensidad() +
"\n- Días de entrenamiento por semana: " + diasEntrenamiento +
"\n\n🗓 DISTRIBUCIÓN OBLIGATORIA DE ENTRENAMIENTOS:" +
explicacionDias +
"\n" + distribucionDias +
"\n\n📌 FORMATO DE RESPUESTA:" +
"\n- SOLO un JSON válido, funcional y parseable" +
"\n- SIN explicaciones, comentarios ni formato markdown" +
"\n- Claves en minúsculas, sin tildes ni espacios" +
"\n- Estructura JSON igual a la del siguiente ejemplo (entrenamiento y dieta separados por día):" +
"\n\n🎯 ESTRUCTURA ESPERADA:" +
"\n{" +
"\n \"semana\": [" +
"\n {" +
"\n \"dia\": \"Lunes\"," +
"\n \"entrenamiento\": {" +
"\n \"calentamiento\": [ ]," +
"\n \"ejercicios_principales\": [ ]," +
"\n \"enfriamiento\": [ ]" +
"\n }," +
"\n \"dieta\": {" +
"\n \"desayuno\": { }," +
"\n \"comida\": { }," +
"\n \"cena\": { }," +
"\n \"snack\": { }" +
"\n }" +
"\n }," +
"\n {" +
"\n \"dia\": \"Martes\"," +
"\n \"entrenamiento\": null," +
"\n \"dieta\": {" +
"\n \"desayuno\": { }," +
"\n \"comida\": { }," +
"\n \"cena\": { }," +
"\n \"snack\": { }" +
"\n }" +
"\n }," +
"\n " +
"\n ]" +
"\n}" +
"\n\n🧠 DETALLES OBLIGATORIOS EN CADA SECCIÓN:" +
"\n\n📍 ENTRENAMIENTO:" +
"\n- EXACTAMENTE " + diasEntrenamiento + " días con entrenamiento según la distribución especificada" +
"\n- Los días SIN entrenamiento deben tener 'entrenamiento': null" +
"\n- calentamiento, ejercicios_principales y enfriamiento por día de entrenamiento" +
"\n- en los descansos msotrar valores como estos 90s no 90 segundos" +
"\n- los videos en los calentamientos y enfriamientos seran link a yotube donde busce en el buscador el nombre del ejercicio. En los ejercicios_principales si que tendras que buscar directamente un video de ese ejercicio" +
"\n- Campos por ejercicio: nombre, descripcion, series, repeticiones, descanso, video_url" +
"\n- ejercicios_principales tendrá entre 8 a 12, calentamiento de 2 a 4 y de enfriamiento de 2 a 4" +
"\n La descripcion que sea mas detallada sobre todo en calentamiento y enfriamientos"+
"\n\n📍 DIETA (obligatoria todos los días):" +
"\n- TODOS los 7 días deben tener dieta completa" +
"\n- Cada día: desayuno, comida, cena, snack" +
"\n- Campos por comida: nombre_del_plato, ingredientes (array), receta, valor_nutricional (con calorias, proteinas, carbohidratos, grasas), video_url" +
"\n\n⚠ CRÍTICO - DISTRIBUCIÓN DE DÍAS:" +
"\n- RESPETAR EXACTAMENTE la distribución especificada" +
"\n- NO crear más días de entrenamiento de los indicados" +
"\n- Los días de descanso son parte fundamental del plan" +
"\n\n⚠ IMPORTANTE:" +
"\n- Usa YouTube en español para los video_url" +
"\n- No repitas ejercicios ni platos durante la semana" +
"\n- Adecúa calorías y tipos de ejercicios al objetivo e intensidad" +
(esRegeneracion ? "\n- Este es un plan ACTUALIZADO, asegúrate de que sea diferente al anterior" : "") +
"\n\n🚫 NO INCLUYAS ningún texto adicional. Solo responde con un JSON válido exactamente con la estructura descrita.";
}
private String generarDistribucionDias(int diasEntrenamiento) {
StringBuilder distribucion = new StringBuilder();
switch (diasEntrenamiento) {
case 1:
distribucion.append("- Lunes: ENTRENAMIENTO\n");
distribucion.append("- Martes: DESCANSO\n");
distribucion.append("- Miércoles: DESCANSO\n");
distribucion.append("- Jueves: DESCANSO\n");
distribucion.append("- Viernes: DESCANSO\n");
distribucion.append("- Sábado: DESCANSO\n");
distribucion.append("- Domingo: DESCANSO");
break;
case 2:
distribucion.append("- Lunes: ENTRENAMIENTO\n");
distribucion.append("- Martes: DESCANSO\n");
distribucion.append("- Miércoles: DESCANSO\n");
distribucion.append("- Jueves: ENTRENAMIENTO\n");
distribucion.append("- Viernes: DESCANSO\n");
distribucion.append("- Sábado: DESCANSO\n");
distribucion.append("- Domingo: DESCANSO");
break;
case 3:
distribucion.append("- Lunes: ENTRENAMIENTO\n");
distribucion.append("- Martes: DESCANSO\n");
distribucion.append("- Miércoles: ENTRENAMIENTO\n");
distribucion.append("- Jueves: DESCANSO\n");
distribucion.append("- Viernes: ENTRENAMIENTO\n");
distribucion.append("- Sábado: DESCANSO\n");
distribucion.append("- Domingo: DESCANSO");
break;
case 4:
distribucion.append("- Lunes: ENTRENAMIENTO\n");
distribucion.append("- Martes: ENTRENAMIENTO\n");
distribucion.append("- Miércoles: DESCANSO\n");
distribucion.append("- Jueves: ENTRENAMIENTO\n");
distribucion.append("- Viernes: ENTRENAMIENTO\n");
distribucion.append("- Sábado: DESCANSO\n");
distribucion.append("- Domingo: DESCANSO");
break;
case 5:
distribucion.append("- Lunes: ENTRENAMIENTO\n");
distribucion.append("- Martes: ENTRENAMIENTO\n");
distribucion.append("- Miércoles: ENTRENAMIENTO\n");
distribucion.append("- Jueves: DESCANSO\n");
distribucion.append("- Viernes: ENTRENAMIENTO\n");
distribucion.append("- Sábado: ENTRENAMIENTO\n");
distribucion.append("- Domingo: DESCANSO");
break;
case 6:
distribucion.append("- Lunes: ENTRENAMIENTO\n");
distribucion.append("- Martes: ENTRENAMIENTO\n");
distribucion.append("- Miércoles: ENTRENAMIENTO\n");
distribucion.append("- Jueves: ENTRENAMIENTO\n");
distribucion.append("- Viernes: ENTRENAMIENTO\n");
distribucion.append("- Sábado: ENTRENAMIENTO\n");
distribucion.append("- Domingo: DESCANSO");
break;
case 7:
distribucion.append("- Lunes: ENTRENAMIENTO\n");
distribucion.append("- Martes: ENTRENAMIENTO\n");
distribucion.append("- Miércoles: ENTRENAMIENTO\n");
distribucion.append("- Jueves: ENTRENAMIENTO\n");
distribucion.append("- Viernes: ENTRENAMIENTO\n");
distribucion.append("- Sábado: ENTRENAMIENTO\n");
distribucion.append("- Domingo: ENTRENAMIENTO");
break;
default:
distribucion.append("- Lunes: ENTRENAMIENTO\n");
distribucion.append("- Martes: DESCANSO\n");
distribucion.append("- Miércoles: ENTRENAMIENTO\n");
distribucion.append("- Jueves: DESCANSO\n");
distribucion.append("- Viernes: ENTRENAMIENTO\n");
distribucion.append("- Sábado: DESCANSO\n");
distribucion.append("- Domingo: DESCANSO");
break;
}
return distribucion.toString();
}
private String generarExplicacionDias(int diasEntrenamiento) {
switch (diasEntrenamiento) {
case 1:
return "\n- SOLO 1 día de entrenamiento (recomendado para principiantes absolutos)";
case 2:
return "\n- SOLO 2 días de entrenamiento con descanso intermedio (principiantes)";
case 3:
return "\n- SOLO 3 días de entrenamiento con descanso intermedio (nivel básico)";
case 4:
return "\n- SOLO 4 días de entrenamiento con 2 días consecutivos y descanso de fin de semana";
case 5:
return "\n- SOLO 5 días de entrenamiento con 1 día de descanso intermedio y domingo libre";
case 6:
return "\n- SOLO 6 días de entrenamiento con domingo de descanso completo";
case 7:
return "\n- 7 días de entrenamiento (atletas avanzados con variación de intensidad)";
default:
return "\n- SOLO 3 días de entrenamiento con descanso intermedio (configuración por defecto)";
}
}
private void finishLoading() {
progressBar.setProgress(100);
progressText.setText("100%");
statusText.setText(esRegeneracion ? "¡Plan actualizado exitosamente!" : "¡Plan generado exitosamente!");
loadingIcon.clearAnimation();
handler.postDelayed(() -> {
if (isAdded()) {
if (esRegeneracion) {
volverAlHomeLimpio();
} else {
irAlLogin();
}
}
}, 2000);
}
private void volverAlHomeLimpio() {
try {
if (getActivity() != null) {
getActivity().runOnUiThread(() -> {
try {
mostrarPopupPlanActualizado();
} catch (Exception e) {
Log.e(TAG, "Error mostrando popup", e);
navegarSinPopup();
}
});
}
} catch (Exception e) {
Log.e(TAG, "Error general en volverAlHomeLimpio", e);
}
}
private void mostrarPopupPlanActualizado() {
try {
PlanActualizadoDialog dialog = PlanActualizadoDialog.newInstance(esRegeneracion);
dialog.setOnDialogCloseListener(() -> {
Log.d(TAG, "Popup cerrado, iniciando navegación");
navegarDespuesDelPopup();
});
if (getActivity() != null && !getActivity().isFinishing()) {
dialog.show(requireActivity().getSupportFragmentManager(), "plan_actualizado_dialog");
Log.d(TAG, "Popup mostrado exitosamente");
} else {
navegarSinPopup();
}
} catch (Exception e) {
Log.e(TAG, "Error mostrando popup", e);
navegarSinPopup();
}
}
private void navegarDespuesDelPopup() {
try {
new Handler(Looper.getMainLooper()).postDelayed(() -> {
if (getActivity() != null && isAdded()) {
FragmentManager fm = requireActivity().getSupportFragmentManager();
fm.beginTransaction()
.remove(LoadingPlanFragment.this)
.commitNowAllowingStateLoss();
while (fm.getBackStackEntryCount() > 0) {
String entryName = fm.getBackStackEntryAt(fm.getBackStackEntryCount() - 1).getName();
if (entryName != null && entryName.contains("loading")) {
fm.popBackStackImmediate();
} else {
break;
}
}
if (getActivity() instanceof MainActivity) {
((MainActivity) getActivity()).navegarA(R.id.nav_home);
}
Log.d(TAG, "Navegación post-popup completada");
}
}, 300);
} catch (Exception e) {
Log.e(TAG, "Error en navegación post-popup", e);
navegarSinPopup();
}
}
private void navegarSinPopup() {
try {
if (getActivity() != null) {
getActivity().runOnUiThread(() -> {
try {
FragmentManager fm = requireActivity().getSupportFragmentManager();
fm.beginTransaction()
.remove(LoadingPlanFragment.this)
.commitNowAllowingStateLoss();
if (getActivity() instanceof MainActivity) {
((MainActivity) getActivity()).navegarA(R.id.nav_home);
}
Toast.makeText(getActivity(), "¡Plan actualizado correctamente!", Toast.LENGTH_LONG).show();
} catch (Exception e) {
Log.e(TAG, "Error en navegación fallback", e);
if (getActivity() != null) {
getActivity().recreate();
}
}
});
}
} catch (Exception e) {
Log.e(TAG, "Error general navegación sin popup", e);
}
}
private void irAlLogin() {
Intent intent = new Intent(requireContext(), LoginActivity.class);
intent.putExtra("plan_generated", true);
intent.putExtra("user_email", nombreUsuario);
startActivity(intent);
requireActivity().finish();
}
private void showError(String error) {
runOnUiThreadSafe(() -> {
addLogMessageInternal("" + error);
if (statusText != null) {
statusText.setText("Error en la generación");
}
handler.postDelayed(() -> {
if (isAdded()) {
if (esRegeneracion) {
Toast.makeText(getContext(), "Error al regenerar plan. Inténtalo más tarde.", Toast.LENGTH_LONG).show();
requireActivity().onBackPressed();
} else {
requireActivity().onBackPressed();
}
}
}, 3000);
});
}
@Override
public void onDestroy() {
super.onDestroy();
if (handler != null) {
handler.removeCallbacksAndMessages(null);
}
}
public static class LogItem {
private String message;
private long timestamp;
public LogItem(String message, long timestamp) {
this.message = message;
this.timestamp = timestamp;
}
public String getMessage() { return message; }
public long getTimestamp() { return timestamp; }
}
private static class LogAdapter extends RecyclerView.Adapter<LogAdapter.LogViewHolder> {
private List<LogItem> logItems;
public LogAdapter(List<LogItem> logItems) {
this.logItems = logItems;
}
@NonNull
@Override
public LogViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_log, parent, false);
return new LogViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull LogViewHolder holder, int position) {
LogItem item = logItems.get(position);
holder.bind(item, position);
}
@Override
public int getItemCount() {
return logItems.size();
}
static class LogViewHolder extends RecyclerView.ViewHolder {
TextView messageText;
TextView timestampText;
View statusIndicator;
public LogViewHolder(@NonNull View itemView) {
super(itemView);
messageText = itemView.findViewById(R.id.messageText);
timestampText = itemView.findViewById(R.id.timestampText);
statusIndicator = itemView.findViewById(R.id.statusIndicator);
}
public void bind(LogItem item, int position) {
messageText.setText(item.getMessage());
long seconds = (System.currentTimeMillis() - item.getTimestamp()) / 1000;
timestampText.setText("+" + seconds + "s");
itemView.setAlpha(0f);
itemView.animate()
.alpha(1f)
.setDuration(300)
.setStartDelay(100)
.start();
}
}
}
}

View File

@ -0,0 +1,165 @@
package com.borjabolufer.elevate.ui.fragments;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import com.borjabolufer.elevate.R;
import com.borjabolufer.elevate.model.Usuario;
public class LoginFragment extends Fragment {
private static final String TAG = "LoginFragment";
private static final String ARG_USUARIO = "usuario";
private static final String ARG_NOMBRE_USUARIO = "nombre_usuario";
private static final String ARG_CONTRASENA_USUARIO = "contrasena_usuario";
private static final String ARG_ES_REGENERACION = "es_regeneracion";
private Usuario usuario;
private String nombreUsuario;
private String contrasenaUsuario;
private boolean esRegeneracion = false;
private TextView placeholderText;
private View loadingContainer;
public static LoginFragment newInstance(Usuario usuario, String nombreUsuario, String contrasenaUsuario) {
LoginFragment fragment = new LoginFragment();
Bundle args = new Bundle();
args.putSerializable(ARG_USUARIO, usuario);
args.putString(ARG_NOMBRE_USUARIO, nombreUsuario);
args.putString(ARG_CONTRASENA_USUARIO, contrasenaUsuario);
args.putBoolean(ARG_ES_REGENERACION, false);
fragment.setArguments(args);
return fragment;
}
public static LoginFragment newInstanceRegeneration(Usuario usuario, String nombreUsuario) {
LoginFragment fragment = new LoginFragment();
Bundle args = new Bundle();
args.putSerializable(ARG_USUARIO, usuario);
args.putString(ARG_NOMBRE_USUARIO, nombreUsuario);
args.putString(ARG_CONTRASENA_USUARIO, "regeneration");
args.putBoolean(ARG_ES_REGENERACION, true);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
usuario = (Usuario) getArguments().getSerializable(ARG_USUARIO);
nombreUsuario = getArguments().getString(ARG_NOMBRE_USUARIO);
contrasenaUsuario = getArguments().getString(ARG_CONTRASENA_USUARIO);
esRegeneracion = getArguments().getBoolean(ARG_ES_REGENERACION, false);
}
Log.d(TAG, "LoginFragment creado - Es regeneración: " + esRegeneracion);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_loading, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
loadingContainer = view.findViewById(R.id.loading_container);
cargarLoadingPlanFragment();
}
private void cargarLoadingPlanFragment() {
if (usuario == null || nombreUsuario == null) {
Log.e(TAG, "Datos de usuario incompletos");
mostrarError("Datos de usuario incompletos");
return;
}
try {
if (placeholderText != null) {
placeholderText.setVisibility(View.GONE);
}
LoadingPlanFragment loadingFragment;
if (esRegeneracion) {
loadingFragment = LoadingPlanFragment.newInstanceRegeneration(usuario, nombreUsuario);
} else {
loadingFragment = LoadingPlanFragment.newInstance(usuario, nombreUsuario, contrasenaUsuario);
}
FragmentManager childFragmentManager = getChildFragmentManager();
childFragmentManager.beginTransaction()
.replace(R.id.loading_container, loadingFragment, "loading_plan_child")
.commitAllowingStateLoss();
Log.d(TAG, "LoadingPlanFragment cargado en contenedor específico");
} catch (Exception e) {
Log.e(TAG, "Error cargando LoadingPlanFragment", e);
mostrarError("Error al iniciar la generación del plan");
}
}
private void mostrarError(String mensaje) {
if (placeholderText != null) {
placeholderText.setText("" + mensaje);
placeholderText.setVisibility(View.VISIBLE);
}
if (getView() != null) {
getView().postDelayed(() -> {
if (isAdded() && getActivity() != null) {
getActivity().onBackPressed();
}
}, 3000);
}
}
@Override
public void onDestroy() {
super.onDestroy();
try {
FragmentManager childFragmentManager = getChildFragmentManager();
if (childFragmentManager.getBackStackEntryCount() > 0) {
childFragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
} catch (Exception e) {
Log.w(TAG, "Error limpiando child fragments: " + e.getMessage());
}
}
public Usuario getUsuario() {
return usuario;
}
public String getNombreUsuario() {
return nombreUsuario;
}
public boolean isEsRegeneracion() {
return esRegeneracion;
}
}

View File

@ -0,0 +1,80 @@
package com.borjabolufer.elevate.ui.fragments;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.borjabolufer.elevate.R;
import com.borjabolufer.elevate.model.Objetivo;
import com.borjabolufer.elevate.utils.ColorUtils;
import com.google.android.material.button.MaterialButton;
public class ObjetivoFragment extends Fragment {
public interface IOnObjetivoListener {
void onObjetivoSelected(Objetivo objetivo);
}
private IOnObjetivoListener objetivoListener;
private Objetivo objetivoSeleccionado;
private MaterialButton selectedButton;
public ObjetivoFragment() {
super(R.layout.fragment_objetivo);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof IOnObjetivoListener) {
objetivoListener = (IOnObjetivoListener) context;
} else {
throw new ClassCastException(context.toString() + " must implement IOnObjetivoListener");
}
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
MaterialButton btnPerder = view.findViewById(R.id.objetivo_perder);
MaterialButton btnGanar = view.findViewById(R.id.objetivo_ganar);
MaterialButton btnTonificar = view.findViewById(R.id.objetivo_tonificar);
Button btnContinuar = view.findViewById(R.id.btnContinuarObjetivo);
btnContinuar.setEnabled(false);
View.OnClickListener listener = v -> {
if (selectedButton != null) {
ColorUtils.limpiarSeleccion(selectedButton);
}
selectedButton = (MaterialButton) v;
ColorUtils.marcarSeleccion(selectedButton);
if (v.getId() == R.id.objetivo_perder) {
objetivoSeleccionado = new Objetivo("Perder grasa");
} else if (v.getId() == R.id.objetivo_ganar) {
objetivoSeleccionado = new Objetivo("Ganar masa");
} else if (v.getId() == R.id.objetivo_tonificar) {
objetivoSeleccionado = new Objetivo("Tonificar");
}
btnContinuar.setEnabled(true);
};
btnPerder.setOnClickListener(listener);
btnGanar.setOnClickListener(listener);
btnTonificar.setOnClickListener(listener);
btnContinuar.setOnClickListener(v -> {
if (objetivoSeleccionado != null && objetivoListener != null) {
objetivoListener.onObjetivoSelected(objetivoSeleccionado);
}
});
}
}

View File

@ -0,0 +1,143 @@
package com.borjabolufer.elevate.ui.fragments;
import android.content.Context;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.borjabolufer.elevate.R;
import com.borjabolufer.elevate.model.Peso;
public class PesoFragment extends Fragment {
public interface IOnPesoListener {
void onPesoSelected(Peso peso);
}
private IOnPesoListener pesoListener;
private EditText editPeso;
private Button btnContinuar;
public PesoFragment() {
super(R.layout.fragment_peso);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initializeViews(view);
setupTextWatcher();
setupClickListener();
}
private void initializeViews(View view) {
editPeso = view.findViewById(R.id.editPeso);
btnContinuar = view.findViewById(R.id.btnContinuarPeso);
btnContinuar.setEnabled(false);
btnContinuar.setAlpha(0.5f);
}
private void setupTextWatcher() {
editPeso.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
validateInput(s.toString().trim());
}
});
}
private void validateInput(String input) {
boolean isValid = false;
if (!input.isEmpty()) {
try {
float peso = Float.parseFloat(input);
if (peso >= 30.0f && peso <= 300.0f) {
isValid = true;
}
} catch (NumberFormatException e) {
isValid = false;
}
}
btnContinuar.setEnabled(isValid);
btnContinuar.setAlpha(isValid ? 1.0f : 0.5f);
}
private void setupClickListener() {
btnContinuar.setOnClickListener(v -> {
String pesoTexto = editPeso.getText().toString().trim();
if (pesoTexto.isEmpty()) {
Toast.makeText(getContext(), "Por favor, ingresa tu peso", Toast.LENGTH_SHORT).show();
return;
}
try {
float pesoValor = Float.parseFloat(pesoTexto);
if (pesoValor < 30.0f || pesoValor > 300.0f) {
Toast.makeText(getContext(), "Por favor, ingresa un peso entre 30 y 300 kg",
Toast.LENGTH_SHORT).show();
return;
}
Peso peso = new Peso(pesoValor);
if (pesoListener != null) {
pesoListener.onPesoSelected(peso);
} else {
Toast.makeText(getContext(), "Error: No se pudo procesar el peso",
Toast.LENGTH_SHORT).show();
}
} catch (NumberFormatException e) {
Toast.makeText(getContext(), "Por favor, ingresa un número válido",
Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof IOnPesoListener) {
pesoListener = (IOnPesoListener) context;
} else {
throw new ClassCastException(context.toString() + " must implement IOnPesoListener");
}
}
@Override
public void onDetach() {
super.onDetach();
pesoListener = null;
}
}

View File

@ -0,0 +1,466 @@
package com.borjabolufer.elevate.ui.fragments;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.cardview.widget.CardView;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import com.borjabolufer.elevate.MainActivity;
import com.borjabolufer.elevate.R;
import com.borjabolufer.elevate.model.Altura;
import com.borjabolufer.elevate.model.DiasEntrenamiento;
import com.borjabolufer.elevate.model.Edad;
import com.borjabolufer.elevate.model.Intensidad;
import com.borjabolufer.elevate.model.Objetivo;
import com.borjabolufer.elevate.model.Peso;
import com.borjabolufer.elevate.model.Sexo;
import com.borjabolufer.elevate.model.Usuario;
import com.borjabolufer.elevate.model.UsuarioDbHelper;
import com.borjabolufer.elevate.model.UsuarioEntity;
import com.borjabolufer.elevate.utils.JsonUtils;
import com.borjabolufer.elevate.utils.PasswordUtils;
import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.Snackbar;
import java.io.File;
public class ProfileFragment extends Fragment {
private static final String TAG = "ProfileFragment";
private static final String PREFS_NAME = "ElevatePrefs";
private static final String KEY_USER_EMAIL = "user_email";
private EditText editNombre;
private EditText editEmail;
private EditText editPassword;
private Button btnGuardarPerfil;
private CardView cardPerfil;
private Spinner spinnerSexo;
private EditText editEdad;
private EditText editPeso;
private EditText editAltura;
private Spinner spinnerObjetivo;
private Spinner spinnerIntensidad;
private Spinner spinnerDias;
private Button btnActualizarPlan;
private CardView cardPlan;
private UsuarioDbHelper dbHelper;
private String currentUserEmail;
private UsuarioEntity usuarioActual;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_profile, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
inicializarComponentes(view);
configurarSpinners();
cargarDatosUsuario();
configurarListeners();
}
private void inicializarComponentes(View view) {
dbHelper = new UsuarioDbHelper(getContext());
SharedPreferences prefs = getActivity().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
currentUserEmail = prefs.getString(KEY_USER_EMAIL, null);
cardPerfil = view.findViewById(R.id.card_perfil);
editNombre = view.findViewById(R.id.edit_nombre);
editEmail = view.findViewById(R.id.edit_email);
editPassword = view.findViewById(R.id.edit_password);
btnGuardarPerfil = view.findViewById(R.id.btn_guardar_perfil);
cardPlan = view.findViewById(R.id.card_plan);
spinnerSexo = view.findViewById(R.id.spinner_sexo);
editEdad = view.findViewById(R.id.edit_edad);
editPeso = view.findViewById(R.id.edit_peso);
editAltura = view.findViewById(R.id.edit_altura);
spinnerObjetivo = view.findViewById(R.id.spinner_objetivo);
spinnerIntensidad = view.findViewById(R.id.spinner_intensidad);
spinnerDias = view.findViewById(R.id.spinner_dias);
btnActualizarPlan = view.findViewById(R.id.btn_actualizar_plan);
Log.d(TAG, "Componentes inicializados para usuario: " + currentUserEmail);
}
private void configurarSpinners() {
String[] sexoOpciones = {"Masculino", "Femenino" ,"Otro"};
ArrayAdapter<String> sexoAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, sexoOpciones);
sexoAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerSexo.setAdapter(sexoAdapter);
String[] objetivoOpciones = {"Perder grasa", "Tonificar", "Ganar masa"};
ArrayAdapter<String> objetivoAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, objetivoOpciones);
objetivoAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerObjetivo.setAdapter(objetivoAdapter);
String[] intensidadOpciones = {"Baja", "Media", "Alta"};
ArrayAdapter<String> intensidadAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, intensidadOpciones);
intensidadAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerIntensidad.setAdapter(intensidadAdapter);
String[] diasOpciones = {"1","2" ,"3", "4", "5", "6", "7"};
ArrayAdapter<String> diasAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, diasOpciones);
diasAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerDias.setAdapter(diasAdapter);
}
private void cargarDatosUsuario() {
if (currentUserEmail == null) {
Toast.makeText(getContext(), "Error: Usuario no identificado", Toast.LENGTH_SHORT).show();
return;
}
usuarioActual = dbHelper.getUsuarioPorEmail(currentUserEmail);
if (usuarioActual == null) {
Toast.makeText(getContext(), "Error al cargar datos del usuario", Toast.LENGTH_SHORT).show();
return;
}
editNombre.setText(usuarioActual.getNombreCompleto());
editEmail.setText(usuarioActual.getEmail());
editPassword.setText("");
if (usuarioActual.getSexo() != null) {
int sexoPosition = usuarioActual.getSexo().equals("Masculino") ? 0 : 1;
spinnerSexo.setSelection(sexoPosition);
}
if (usuarioActual.getEdad() > 0) {
editEdad.setText(String.valueOf(usuarioActual.getEdad()));
}
if (usuarioActual.getPeso() > 0) {
editPeso.setText(String.valueOf(usuarioActual.getPeso()));
}
if (usuarioActual.getAltura() > 0) {
editAltura.setText(String.valueOf(usuarioActual.getAltura()));
}
configurarSeleccionSpinner(spinnerObjetivo, usuarioActual.getObjetivo());
configurarSeleccionSpinner(spinnerIntensidad, usuarioActual.getIntensidad());
if (usuarioActual.getDiasEntrenamiento() > 0) {
int diasPosition = usuarioActual.getDiasEntrenamiento() - 1;
if (diasPosition >= 0 && diasPosition < 4) {
spinnerDias.setSelection(diasPosition);
}
}
Log.d(TAG, "Datos cargados para usuario: " + currentUserEmail);
}
private void configurarSeleccionSpinner(Spinner spinner, String valor) {
if (valor == null) return;
ArrayAdapter adapter = (ArrayAdapter) spinner.getAdapter();
for (int i = 0; i < adapter.getCount(); i++) {
if (adapter.getItem(i).toString().equals(valor)) {
spinner.setSelection(i);
break;
}
}
}
private void configurarListeners() {
btnGuardarPerfil.setOnClickListener(v -> guardarPerfilPersonal());
btnActualizarPlan.setOnClickListener(v -> actualizarPlanEntrenamiento());
}
private void guardarPerfilPersonal() {
String nuevoNombre = editNombre.getText().toString().trim();
String nuevoEmail = editEmail.getText().toString().trim();
String nuevaPassword = editPassword.getText().toString().trim();
if (nuevoNombre.isEmpty() || nuevoEmail.isEmpty()) {
Toast.makeText(getContext(), "Nombre y email son obligatorios", Toast.LENGTH_SHORT).show();
return;
}
if (!android.util.Patterns.EMAIL_ADDRESS.matcher(nuevoEmail).matches()) {
Toast.makeText(getContext(), "Formato de email inválido", Toast.LENGTH_SHORT).show();
return;
}
if (!nuevoEmail.equals(currentUserEmail) && dbHelper.checkUserEmailExists(nuevoEmail)) {
Toast.makeText(getContext(), "Este email ya está en uso", Toast.LENGTH_SHORT).show();
return;
}
String emailOriginal = currentUserEmail;
usuarioActual.setNombreCompleto(nuevoNombre);
usuarioActual.setEmail(nuevoEmail);
if (!nuevaPassword.isEmpty()) {
String passwordHash = PasswordUtils.hashPassword(nuevaPassword);
usuarioActual.setPasswordHash(passwordHash);
}
boolean actualizado = actualizarUsuarioEnBD();
if (actualizado) {
if (!nuevoEmail.equals(emailOriginal)) {
gestionarCambioEmailArchivo(emailOriginal, nuevoEmail);
SharedPreferences prefs = getActivity().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit().putString(KEY_USER_EMAIL, nuevoEmail).apply();
currentUserEmail = nuevoEmail;
}
actualizarHeaderNavigationDrawer(nuevoNombre, nuevoEmail);
Toast.makeText(getContext(), "Perfil actualizado correctamente", Toast.LENGTH_SHORT).show();
editPassword.setText("");
} else {
Toast.makeText(getContext(), "Error al actualizar perfil", Toast.LENGTH_SHORT).show();
}
}
private void actualizarHeaderNavigationDrawer(String nuevoNombre, String nuevoEmail) {
try {
if (getActivity() instanceof MainActivity) {
MainActivity mainActivity = (MainActivity) getActivity();
NavigationView navigationView = mainActivity.findViewById(R.id.nav_view);
if (navigationView != null) {
View headerView = navigationView.getHeaderView(0);
TextView headerTitle = headerView.findViewById(R.id.nav_header_title);
TextView headerSubtitle = headerView.findViewById(R.id.nav_header_subtitle);
if (headerTitle != null) {
headerTitle.setText(nuevoNombre);
Log.d(TAG, "Header title actualizado: " + nuevoNombre);
}
if (headerSubtitle != null) {
headerSubtitle.setText(nuevoEmail);
Log.d(TAG, "Header subtitle actualizado: " + nuevoEmail);
}
}
}
} catch (Exception e) {
Log.e(TAG, "Error al actualizar header del navigation drawer", e);
}
}
private void gestionarCambioEmailArchivo(String emailOriginal, String nuevoEmail) {
new Thread(() -> {
try {
boolean migracionBD = dbHelper.migrarPlanJson(emailOriginal, nuevoEmail);
Log.d(TAG, "Migración en BD: " + migracionBD);
File fileOriginal = JsonUtils.obtenerArchivoPlan(getContext(), emailOriginal);
File fileNuevo = JsonUtils.obtenerArchivoPlan(getContext(), nuevoEmail);
if (fileOriginal.exists()) {
try {
String contenidoPlan = JsonUtils.cargarPlanDelUsuario(getContext(), emailOriginal);
if (contenidoPlan != null && !contenidoPlan.trim().isEmpty()) {
java.io.FileWriter writer = new java.io.FileWriter(fileNuevo);
writer.write(contenidoPlan);
writer.flush();
writer.close();
boolean eliminado = fileOriginal.delete();
Log.d(TAG, "Migración de archivos exitosa:");
Log.d(TAG, "- Archivo original eliminado: " + eliminado);
Log.d(TAG, "- Nuevo archivo creado: " + fileNuevo.exists());
Log.d(TAG, "- Plan migrado de " + fileOriginal.getName() + " a " + fileNuevo.getName());
} else {
Log.w(TAG, "Archivo original vacío o no se pudo leer");
}
} catch (java.io.IOException e) {
Log.e(TAG, "Error de I/O al migrar archivo: " + e.getMessage());
}
} else {
Log.w(TAG, "Archivo original no existe: " + fileOriginal.getAbsolutePath());
if (migracionBD) {
boolean creado = dbHelper.guardarPlanJsonEnArchivo(getContext(), nuevoEmail);
Log.d(TAG, "Archivo creado desde BD: " + creado);
}
}
} catch (Exception e) {
Log.e(TAG, "Error general al gestionar cambio de email", e);
}
}).start();
}
private void actualizarPlanEntrenamiento() {
String edadStr = editEdad.getText().toString().trim();
String pesoStr = editPeso.getText().toString().trim();
String alturaStr = editAltura.getText().toString().trim();
if (edadStr.isEmpty() || pesoStr.isEmpty() || alturaStr.isEmpty()) {
Toast.makeText(getContext(), "Todos los campos del plan son obligatorios", Toast.LENGTH_SHORT).show();
return;
}
try {
int edad = Integer.parseInt(edadStr);
double peso = Double.parseDouble(pesoStr);
double altura = Double.parseDouble(alturaStr);
if (edad < 16 || edad > 100) {
Toast.makeText(getContext(), "Edad debe estar entre 16 y 100 años", Toast.LENGTH_SHORT).show();
return;
}
if (peso < 30 || peso > 300) {
Toast.makeText(getContext(), "Peso debe estar entre 30 y 300 kg", Toast.LENGTH_SHORT).show();
return;
}
if (altura < 100 || altura > 250) {
Toast.makeText(getContext(), "Altura debe estar entre 100 y 250 cm", Toast.LENGTH_SHORT).show();
return;
}
usuarioActual.setSexo(spinnerSexo.getSelectedItem().toString());
usuarioActual.setEdad(edad);
usuarioActual.setPeso(peso);
usuarioActual.setAltura(altura);
usuarioActual.setObjetivo(spinnerObjetivo.getSelectedItem().toString());
usuarioActual.setIntentensidad(spinnerIntensidad.getSelectedItem().toString());
usuarioActual.setDiasEntrenamiento(Integer.parseInt(spinnerDias.getSelectedItem().toString()));
boolean actualizado = actualizarUsuarioEnBD();
if (actualizado) {
mostrarDialogoRegenerarPlan();
} else {
Toast.makeText(getContext(), "Error al actualizar plan", Toast.LENGTH_SHORT).show();
}
} catch (NumberFormatException e) {
Toast.makeText(getContext(), "Valores numéricos inválidos", Toast.LENGTH_SHORT).show();
}
}
private boolean actualizarUsuarioEnBD() {
try {
return dbHelper.actualizarUsuario(usuarioActual) > 0;
} catch (Exception e) {
Log.e(TAG, "Error al actualizar usuario", e);
return false;
}
}
private void mostrarDialogoRegenerarPlan() {
Snackbar.make(getView(), "¿Regenerar plan de entrenamiento con los nuevos datos?", Snackbar.LENGTH_LONG)
.setAction("Regenerar", v -> regenerarPlan())
.show();
}
private void regenerarPlan() {
Usuario usuario = crearUsuarioParaRegenerar();
LoadingPlanFragment loadingFragment = LoadingPlanFragment.newInstanceRegeneration(
usuario,
currentUserEmail
);
requireActivity().getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(
androidx.navigation.ui.R.anim.nav_default_enter_anim,
androidx.navigation.ui.R.anim.nav_default_exit_anim,
androidx.navigation.ui.R.anim.nav_default_pop_enter_anim,
androidx.navigation.ui.R.anim.nav_default_pop_exit_anim
)
.replace(R.id.nav_host_fragment_content_main, loadingFragment)
.commit();
}
private Usuario crearUsuarioParaRegenerar() {
Usuario usuario = new Usuario();
usuario.setSexo(new Sexo(usuarioActual.getSexo()));
usuario.setEdad(new Edad(usuarioActual.getEdad()));
usuario.setPeso(new Peso((float) usuarioActual.getPeso()));
usuario.setAltura(new Altura((int) usuarioActual.getAltura()));
usuario.setObjetivo(new Objetivo(usuarioActual.getObjetivo()));
usuario.setIntensidad(new Intensidad(usuarioActual.getIntensidad()));
usuario.setDias(new DiasEntrenamiento(usuarioActual.getDiasEntrenamiento()));
return usuario;
}
@Override
public void onDestroy() {
super.onDestroy();
if (dbHelper != null) {
dbHelper.close();
}
}
}

View File

@ -0,0 +1,241 @@
package com.borjabolufer.elevate.ui.fragments;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import com.borjabolufer.elevate.R;
import com.borjabolufer.elevate.model.Usuario;
import java.io.Serializable;
public class ResumenFragment extends Fragment {
private static final String ARG_USUARIO = "usuario";
private static final String ARG_NOMBRE_USUARIO = "nombre_usuario";
private static final String ARG_CONTRASENA_USUARIO = "contrasena_usuario";
private static final String TAG = "ResumenFragment";
private Usuario usuario;
private String nombreUsuario;
private String contrasenaUsuario;
private TextView tvResumen;
private Button btnGuardarPerfil;
public ResumenFragment() {
}
public static ResumenFragment newInstance(Usuario usuario, String nombreUsuario, String contrasenaUsuario) {
ResumenFragment fragment = new ResumenFragment();
Bundle args = new Bundle();
args.putSerializable(ARG_USUARIO, (Serializable) usuario);
args.putString(ARG_NOMBRE_USUARIO, nombreUsuario);
args.putString(ARG_CONTRASENA_USUARIO, contrasenaUsuario);
fragment.setArguments(args);
Log.d("ResumenFragment_newInstance", "newInstance llamado con usuario: " + (usuario == null ? "null" : "OK") +
", nombreUsuario: " + nombreUsuario +
", contrasenaUsuario: " + (contrasenaUsuario == null ? "null" : "Presente"));
return fragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
Log.d(TAG, "onCreate: Cargando argumentos del Bundle.");
if (getArguments().containsKey(ARG_USUARIO)) {
usuario = (Usuario) getArguments().getSerializable(ARG_USUARIO);
Log.d(TAG, "onCreate: usuario cargado: " + (usuario == null ? "null" : "OK"));
} else {
Log.w(TAG, "onCreate: No se encontró la clave ARG_USUARIO en los argumentos.");
}
if (getArguments().containsKey(ARG_NOMBRE_USUARIO)) {
nombreUsuario = getArguments().getString(ARG_NOMBRE_USUARIO);
Log.d(TAG, "onCreate: nombreUsuario cargado: " + nombreUsuario);
} else {
Log.w(TAG, "onCreate: No se encontró la clave ARG_NOMBRE_USUARIO en los argumentos.");
}
if (getArguments().containsKey(ARG_CONTRASENA_USUARIO)) {
contrasenaUsuario = getArguments().getString(ARG_CONTRASENA_USUARIO);
Log.d(TAG, "onCreate: contrasenaUsuario cargada: " + (contrasenaUsuario == null ? "null" : "Presente"));
} else {
Log.w(TAG, "onCreate: No se encontró la clave ARG_CONTRASENA_USUARIO en los argumentos.");
}
} else {
Log.e(TAG, "onCreate: ¡getArguments() es null! No se pasaron argumentos al fragmento.");
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_resumen, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
tvResumen = view.findViewById(R.id.tvResumen);
btnGuardarPerfil = view.findViewById(R.id.btnGuardarPerfil);
mostrarDatosUsuario(view);
btnGuardarPerfil.setOnClickListener(v -> {
if (validarDatos()) {
navegarALoadingPlan();
} else {
Toast.makeText(getContext(), "Faltan datos esenciales para generar el plan.", Toast.LENGTH_LONG).show();
}
});
}
private boolean validarDatos() {
return usuario != null &&
nombreUsuario != null && !nombreUsuario.isEmpty() &&
contrasenaUsuario != null && !contrasenaUsuario.isEmpty();
}
private void navegarALoadingPlan() {
try {
LoadingPlanFragment loadingFragment = LoadingPlanFragment.newInstance(
usuario,
nombreUsuario,
contrasenaUsuario
);
FragmentTransaction transaction = getParentFragmentManager().beginTransaction();
transaction.setCustomAnimations(
R.anim.slide_in_right,
R.anim.slide_out_left,
R.anim.slide_in_left,
R.anim.slide_out_right
);
transaction.replace(R.id.fragmentContainerViewPreguntas, loadingFragment);
transaction.addToBackStack(null);
transaction.commit();
Log.d(TAG, "Navegando al LoadingPlanFragment");
} catch (Exception e) {
Log.e(TAG, "Error al navegar al LoadingPlanFragment", e);
Toast.makeText(getContext(), "Error al iniciar la generación del plan", Toast.LENGTH_SHORT).show();
}
}
private void mostrarDatosUsuario(View view) {
if (usuario != null) {
String alturaStr = (usuario.getAltura() != null)
? String.valueOf((int)usuario.getAltura().getAltura()) : "N/A";
String pesoStr = (usuario.getPeso() != null)
? String.valueOf((int)usuario.getPeso().getPeso()) : "N/A";
String edadStr = (usuario.getEdad() != null)
? String.valueOf(usuario.getEdad().getEdad()) : "N/A";
String sexoStr = (usuario.getSexo() != null && usuario.getSexo().getSexo() != null)
? usuario.getSexo().getSexo() : "N/A";
String intensidadStr = (usuario.getIntensidad() != null && usuario.getIntensidad().getIntensidad() != null)
? usuario.getIntensidad().getIntensidad() : "N/A";
String objetivoStr = (usuario.getObjetivo() != null && usuario.getObjetivo().getObjetivo() != null)
? usuario.getObjetivo().getObjetivo() : "N/A";
String diasStr = (usuario.getDias() != null)
? String.valueOf(usuario.getDias().getCantidad()) : "N/A";
TextView tvAlturaValue = view.findViewById(R.id.tvAlturaValue);
TextView tvPesoValue = view.findViewById(R.id.tvPesoValue);
TextView tvEdadValue = view.findViewById(R.id.tvEdadValue);
TextView tvSexoValue = view.findViewById(R.id.tvSexoValue);
TextView tvObjetivoValue = view.findViewById(R.id.tvObjetivoValue);
TextView tvIntensidadValue = view.findViewById(R.id.tvIntensidadValue);
TextView tvDiasValue = view.findViewById(R.id.tvDiasValue);
if (tvAlturaValue != null) tvAlturaValue.setText(alturaStr + " cm");
if (tvPesoValue != null) tvPesoValue.setText(pesoStr + " kg");
if (tvEdadValue != null) tvEdadValue.setText(edadStr + " años");
if (tvSexoValue != null) tvSexoValue.setText(sexoStr);
if (tvObjetivoValue != null) tvObjetivoValue.setText(objetivoStr);
if (tvIntensidadValue != null) tvIntensidadValue.setText(intensidadStr);
if (tvDiasValue != null) tvDiasValue.setText(diasStr + " días/semana");
String resumenText = "📊 Resumen de tu perfil:\n\n" +
"• Altura: " + alturaStr + " cm\n" +
"• Peso: " + pesoStr + " kg\n" +
"• Edad: " + edadStr + " años\n" +
"• Sexo: " + sexoStr + "\n" +
"• Objetivo: " + objetivoStr + "\n" +
"• Intensidad: " + intensidadStr + "\n" +
"• Días de Entrenamiento: " + diasStr + " días/semana\n\n" +
"🚀 Tu plan será generado basado en estos datos.";
if (tvResumen != null) {
tvResumen.setText(resumenText);
}
Log.d(TAG, "Datos del usuario mostrados correctamente");
} else {
if (tvResumen != null) {
tvResumen.setText("❌ No se han proporcionado datos del usuario.");
}
Log.w(TAG, "No hay datos de usuario para mostrar");
}
}
public Usuario getUsuario() {
return usuario;
}
public String getNombreUsuario() {
return nombreUsuario;
}
public boolean tieneInformacionCompleta() {
return validarDatos() &&
usuario.getAltura() != null &&
usuario.getPeso() != null &&
usuario.getEdad() != null &&
usuario.getSexo() != null &&
usuario.getObjetivo() != null &&
usuario.getIntensidad() != null &&
usuario.getDias() != null;
}
public String obtenerResumenTexto() {
if (!tieneInformacionCompleta()) {
return "Información incompleta";
}
return String.format(
"Perfil: %s, %d años, %.0f cm, %.0f kg | Objetivo: %s | Intensidad: %s | %d días/semana",
usuario.getSexo().getSexo(),
usuario.getEdad().getEdad(),
usuario.getAltura().getAltura(),
usuario.getPeso().getPeso(),
usuario.getObjetivo().getObjetivo(),
usuario.getIntensidad().getIntensidad(),
usuario.getDias().getCantidad()
);
}
}

View File

@ -0,0 +1,80 @@
package com.borjabolufer.elevate.ui.fragments;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.borjabolufer.elevate.R;
import com.borjabolufer.elevate.model.Sexo;
import com.borjabolufer.elevate.utils.ColorUtils;
import com.google.android.material.button.MaterialButton;
public class SexoFragment extends Fragment {
public interface IOnSexoListener {
void onSexoSelected(Sexo sexo);
}
private IOnSexoListener sexoListener;
private Sexo sexoSeleccionado;
private MaterialButton selectedButton;
public SexoFragment() {
super(R.layout.fragment_sexo);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof IOnSexoListener) {
sexoListener = (IOnSexoListener) context;
} else {
throw new ClassCastException(context.toString() + " must implement IOnSexoListener");
}
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
MaterialButton btnMasculino = view.findViewById(R.id.sexo_m);
MaterialButton btnFemenino = view.findViewById(R.id.sexo_f);
MaterialButton btnOtro = view.findViewById(R.id.sexo_o);
Button btnContinuar = view.findViewById(R.id.btnContinuarSexo);
btnContinuar.setEnabled(false);
View.OnClickListener listener = v -> {
if (selectedButton != null) {
ColorUtils.limpiarSeleccion(selectedButton);
}
selectedButton = (MaterialButton) v;
ColorUtils.marcarSeleccion(selectedButton);
if (v.getId() == R.id.sexo_m) {
sexoSeleccionado = new Sexo("Masculino");
} else if (v.getId() == R.id.sexo_f) {
sexoSeleccionado = new Sexo("Femenino");
} else if (v.getId() == R.id.sexo_o) {
sexoSeleccionado = new Sexo("Otro");
}
btnContinuar.setEnabled(true);
};
btnMasculino.setOnClickListener(listener);
btnFemenino.setOnClickListener(listener);
btnOtro.setOnClickListener(listener);
btnContinuar.setOnClickListener(v -> {
if (sexoSeleccionado != null && sexoListener != null) {
sexoListener.onSexoSelected(sexoSeleccionado);
}
});
}
}

View File

@ -0,0 +1,209 @@
package com.borjabolufer.elevate.utils;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import org.json.JSONException;
public class AiProviderManager {
private static final String TAG = "AiProviderManager";
private static final String PREFS_NAME = "AiProviderPrefs";
private static final String KEY_SELECTED_PROVIDER = "selected_provider";
public enum AiProvider {
CHATGPT("ChatGPT", "OpenAI GPT-3.5 Turbo"),
CLAUDE("Claude", "Anthropic Claude 3 Sonnet");
private final String displayName;
private final String description;
AiProvider(String displayName, String description) {
this.displayName = displayName;
this.description = description;
}
public String getDisplayName() { return displayName; }
public String getDescription() { return description; }
}
public interface AiCallback {
void onSuccess(String response);
void onFailure(String error);
}
private final Context context;
private AiProvider currentProvider;
public AiProviderManager(Context context) {
this.context = context;
this.currentProvider = getSelectedProvider();
}
public void enviarPrompt(String prompt, AiCallback callback) {
Log.d(TAG, "Enviando prompt usando: " + currentProvider.getDisplayName());
try {
switch (currentProvider) {
case CLAUDE:
if (ClaudeApi.isApiKeyConfigured()) {
ClaudeApi.enviarPrompt(prompt, new ClaudeApi.ClaudeCallback() {
@Override
public void onSuccess(String jsonResponse) {
Log.d(TAG, "Claude respondió exitosamente");
callback.onSuccess(jsonResponse);
}
@Override
public void onFailure(String error) {
Log.e(TAG, "Claude falló: " + error);
intentarConChatGPT(prompt, callback);
}
});
} else {
Log.w(TAG, "Claude API no configurada, usando ChatGPT");
intentarConChatGPT(prompt, callback);
}
break;
case CHATGPT:
if (ChatGptApi.class != null) {
ChatGptApi.enviarPrompt(prompt, new ChatGptApi.ChatGptCallback() {
@Override
public void onSuccess(String jsonResponse) {
Log.d(TAG, "ChatGPT respondió exitosamente");
callback.onSuccess(jsonResponse);
}
@Override
public void onFailure(String error) {
Log.e(TAG, "ChatGPT falló: " + error);
if (ClaudeApi.isApiKeyConfigured()) {
Log.d(TAG, "Intentando fallback a Claude...");
intentarConClaude(prompt, callback);
} else {
callback.onFailure("ChatGPT falló: " + error);
}
}
});
}
break;
}
} catch (JSONException e) {
Log.e(TAG, "Error en JSON: " + e.getMessage());
callback.onFailure("Error en configuración: " + e.getMessage());
}
}
private void intentarConClaude(String prompt, AiCallback callback) {
try {
ClaudeApi.enviarPrompt(prompt, new ClaudeApi.ClaudeCallback() {
@Override
public void onSuccess(String jsonResponse) {
Log.d(TAG, "Fallback a Claude exitoso");
callback.onSuccess(jsonResponse);
}
@Override
public void onFailure(String error) {
Log.e(TAG, "Fallback a Claude también falló: " + error);
callback.onFailure("Ambos proveedores fallaron. ChatGPT y Claude no disponibles.");
}
});
} catch (JSONException e) {
callback.onFailure("Error en fallback a Claude: " + e.getMessage());
}
}
private void intentarConChatGPT(String prompt, AiCallback callback) {
try {
ChatGptApi.enviarPrompt(prompt, new ChatGptApi.ChatGptCallback() {
@Override
public void onSuccess(String jsonResponse) {
Log.d(TAG, "Fallback a ChatGPT exitoso");
callback.onSuccess(jsonResponse);
}
@Override
public void onFailure(String error) {
Log.e(TAG, "Fallback a ChatGPT también falló: " + error);
callback.onFailure("Ambos proveedores fallaron. Claude y ChatGPT no disponibles.");
}
});
} catch (JSONException e) {
callback.onFailure("Error en fallback a ChatGPT: " + e.getMessage());
}
}
public void setProvider(AiProvider provider) {
this.currentProvider = provider;
saveSelectedProvider(provider);
Log.d(TAG, "Proveedor cambiado a: " + provider.getDisplayName());
}
public AiProvider getCurrentProvider() {
return currentProvider;
}
private void saveSelectedProvider(AiProvider provider) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit().putString(KEY_SELECTED_PROVIDER, provider.name()).apply();
}
private AiProvider getSelectedProvider() {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
String providerName = prefs.getString(KEY_SELECTED_PROVIDER, AiProvider.CHATGPT.name());
try {
return AiProvider.valueOf(providerName);
} catch (IllegalArgumentException e) {
Log.w(TAG, "Proveedor no reconocido, usando ChatGPT por defecto");
return AiProvider.CHATGPT;
}
}
public boolean isChatGptAvailable() {
return ChatGptApi.class != null;
}
public boolean isClaudeAvailable() {
return ClaudeApi.isApiKeyConfigured();
}
public String getProviderStatus() {
StringBuilder status = new StringBuilder();
status.append("Proveedor actual: ").append(currentProvider.getDisplayName()).append("\n");
status.append("ChatGPT disponible: ").append(isChatGptAvailable() ? "" : "No").append("\n");
status.append("Claude disponible: ").append(isClaudeAvailable() ? "" : "No").append("\n");
return status.toString();
}
public void verificarConectividad(AiCallback callback) {
String promptPrueba = "Responde solo con 'OK' para confirmar que estás funcionando.";
enviarPrompt(promptPrueba, new AiCallback() {
@Override
public void onSuccess(String response) {
if (response.toLowerCase().contains("ok")) {
callback.onSuccess("Conectividad verificada con " + currentProvider.getDisplayName());
} else {
callback.onSuccess("Conectado con " + currentProvider.getDisplayName() +
" pero respuesta inesperada: " + response);
}
}
@Override
public void onFailure(String error) {
callback.onFailure("Fallo en verificación de conectividad: " + error);
}
});
}
}

View File

@ -0,0 +1,73 @@
package com.borjabolufer.elevate.utils;
import okhttp3.*;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class ChatGptApi {
private static final String API_URL = "https://api.openai.com/v1/chat/completions";
private static final String API_KEY = "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";
public interface ChatGptCallback {
void onSuccess(String jsonResponse);
void onFailure(String error);
}
public static void enviarPrompt(String prompt, ChatGptCallback callback) throws JSONException {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.MINUTES)
.writeTimeout(10, TimeUnit.MINUTES)
.build();
JSONObject jsonBody = new JSONObject();
jsonBody.put("model", "gpt-4o");
jsonBody.put("messages", new org.json.JSONArray()
.put(new JSONObject().put("role", "user").put("content", prompt))
);
jsonBody.put("temperature", 0.7);
RequestBody body = RequestBody.create(jsonBody.toString(), MediaType.parse("application/json"));
Request request = new Request.Builder()
.url(API_URL)
.addHeader("Authorization", "Bearer " + API_KEY)
.addHeader("Content-Type", "application/json")
.post(body)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
callback.onFailure(e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) {
callback.onFailure("Error HTTP: " + response.code());
return;
}
String responseBody = response.body().string();
JSONObject json;
try {
json = new JSONObject(responseBody);
String result = json.getJSONArray("choices")
.getJSONObject(0)
.getJSONObject("message")
.getString("content");
callback.onSuccess(result);
} catch (JSONException e) {
callback.onFailure("Error al procesar JSON: " + e.getMessage());
}
}
});
}
}

View File

@ -0,0 +1,176 @@
package com.borjabolufer.elevate.utils;
import okhttp3.*;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class ClaudeApi {
private static final String API_URL = "https://api.anthropic.com/v1/messages";
private static final String API_KEY = "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";
private static final String MODEL = "claude-3-5-haiku-20241022";
public interface ClaudeCallback {
void onSuccess(String jsonResponse);
void onFailure(String error);
}
public static void enviarPrompt(String prompt, ClaudeCallback callback) throws JSONException {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.MINUTES)
.writeTimeout(10, TimeUnit.MINUTES)
.build();
JSONObject jsonBody = new JSONObject();
jsonBody.put("model", MODEL);
jsonBody.put("max_tokens", 4096);
jsonBody.put("messages", new org.json.JSONArray()
.put(new JSONObject().put("role", "user").put("content", prompt))
);
jsonBody.put("temperature", 0.7);
jsonBody.put("top_p", 0.9);
RequestBody body = RequestBody.create(jsonBody.toString(), MediaType.parse("application/json"));
Request request = new Request.Builder()
.url(API_URL)
.addHeader("x-api-key", API_KEY)
.addHeader("Content-Type", "application/json")
.addHeader("anthropic-version", "2023-06-01")
.post(body)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
callback.onFailure("Error de conexión: " + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) {
String errorBody = response.body() != null ? response.body().string() : "Sin detalles";
callback.onFailure("Error HTTP " + response.code() + ": " + errorBody);
return;
}
String responseBody = response.body().string();
try {
JSONObject json = new JSONObject(responseBody);
String result = json.getJSONArray("content")
.getJSONObject(0)
.getString("text");
callback.onSuccess(result);
} catch (JSONException e) {
callback.onFailure("Error al procesar respuesta de Claude: " + e.getMessage());
}
}
});
}
public static void enviarPromptPersonalizado(String prompt, int maxTokens, double temperature, ClaudeCallback callback) throws JSONException {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.MINUTES)
.writeTimeout(10, TimeUnit.MINUTES)
.build();
JSONObject jsonBody = new JSONObject();
jsonBody.put("model", MODEL);
jsonBody.put("max_tokens", maxTokens);
jsonBody.put("messages", new org.json.JSONArray()
.put(new JSONObject().put("role", "user").put("content", prompt))
);
jsonBody.put("temperature", temperature);
jsonBody.put("top_p", 0.9);
RequestBody body = RequestBody.create(jsonBody.toString(), MediaType.parse("application/json"));
Request request = new Request.Builder()
.url(API_URL)
.addHeader("x-api-key", API_KEY)
.addHeader("Content-Type", "application/json")
.addHeader("anthropic-version", "2023-06-01")
.post(body)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
callback.onFailure("Error de conexión: " + e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) {
String errorBody = response.body() != null ? response.body().string() : "Sin detalles";
callback.onFailure("Error HTTP " + response.code() + ": " + errorBody);
return;
}
String responseBody = response.body().string();
try {
JSONObject json = new JSONObject(responseBody);
String result = json.getJSONArray("content")
.getJSONObject(0)
.getString("text");
callback.onSuccess(result);
} catch (JSONException e) {
callback.onFailure("Error al procesar respuesta de Claude: " + e.getMessage());
}
}
});
}
public static void verificarConexion(ClaudeCallback callback) {
try {
enviarPrompt("Hola, ¿estás funcionando correctamente? Responde solo con 'Sí'.", new ClaudeCallback() {
@Override
public void onSuccess(String jsonResponse) {
if (jsonResponse.toLowerCase().contains("") || jsonResponse.toLowerCase().contains("si")) {
callback.onSuccess("Conexión exitosa con Claude API");
} else {
callback.onSuccess("Conectado, pero respuesta inesperada: " + jsonResponse);
}
}
@Override
public void onFailure(String error) {
callback.onFailure("Fallo en verificación: " + error);
}
});
} catch (JSONException e) {
callback.onFailure("Error en verificación: " + e.getMessage());
}
}
public static boolean isApiKeyConfigured() {
return API_KEY != null &&
!API_KEY.isEmpty() &&
!API_KEY.equals("TU_CLAUDE_API_KEY_AQUI");
}
public static String getModelInfo() {
return "Modelo: " + MODEL + "\n" +
"Provider: Anthropic Claude\n" +
"Configuración: Optimizado para generación de planes de fitness";
}
}

View File

@ -0,0 +1,89 @@
package com.borjabolufer.elevate.utils;
import android.content.res.ColorStateList;
import android.graphics.Color;
import com.google.android.material.button.MaterialButton;
public class ColorUtils {
public static final int COLOR_SELECCIONADO = Color.parseColor("#64B5F6");
public static final int COLOR_NORMAL = Color.parseColor("#FAFAFA");
public static final int COLOR_STROKE_SELECCIONADO = Color.parseColor("#2196F3");
public static final int COLOR_STROKE_NORMAL = Color.parseColor("#E0E0E0");
public static final int COLOR_TEXTO_SELECCIONADO = Color.parseColor("#FFFFFF");
public static final int COLOR_TEXTO_NORMAL = Color.parseColor("#2E2E2E");
public static void marcarSeleccion(MaterialButton boton) {
boton.setBackgroundTintList(ColorStateList.valueOf(COLOR_SELECCIONADO));
boton.setStrokeColor(ColorStateList.valueOf(COLOR_STROKE_SELECCIONADO));
boton.setTextColor(COLOR_TEXTO_SELECCIONADO);
if (boton.getStrokeWidth() == 0) {
boton.setStrokeWidth(2);
}
}
public static void limpiarSeleccion(MaterialButton boton) {
boton.setBackgroundTintList(ColorStateList.valueOf(COLOR_NORMAL));
boton.setStrokeColor(ColorStateList.valueOf(COLOR_STROKE_NORMAL));
boton.setTextColor(COLOR_TEXTO_NORMAL);
boton.setStrokeWidth(2);
}
public static boolean estaSeleccionado(MaterialButton boton) {
ColorStateList backgroundTint = boton.getBackgroundTintList();
if (backgroundTint != null) {
int color = backgroundTint.getDefaultColor();
return color == COLOR_SELECCIONADO;
}
return false;
}
public static void alternarSeleccion(MaterialButton boton) {
if (estaSeleccionado(boton)) {
limpiarSeleccion(boton);
} else {
marcarSeleccion(boton);
}
}
public static void limpiarSeleccionMultiple(MaterialButton... botones) {
for (MaterialButton boton : botones) {
if (boton != null) {
limpiarSeleccion(boton);
}
}
}
public static void seleccionarUnico(MaterialButton botonSeleccionado, MaterialButton... todosBotones) {
limpiarSeleccionMultiple(todosBotones);
if (botonSeleccionado != null) {
marcarSeleccion(botonSeleccionado);
}
}
}

View File

@ -0,0 +1,278 @@
package com.borjabolufer.elevate.utils;
import android.content.Context;
import android.util.Log;
import com.borjabolufer.elevate.model.Comida;
import com.borjabolufer.elevate.model.DiasEntrenamiento;
import com.borjabolufer.elevate.model.Dieta;
import com.borjabolufer.elevate.model.Ejercicio;
import com.borjabolufer.elevate.model.Entrenamiento;
import com.borjabolufer.elevate.model.Semana;
import com.borjabolufer.elevate.model.ValorNutricional;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileInputStream;
import java.util.List;
import java.util.ArrayList;
public class JsonParser {
private static final String TAG = "JsonElevateParser";
private Gson gson;
public JsonParser(Context context) {
this.gson = new GsonBuilder()
.setPrettyPrinting()
.create();
}
public JSONObject parsearDesdeArchivoInterno(Context context) {
try {
File file = new File(context.getFilesDir(), "plan_entrenamiento.json");
FileInputStream fis = new FileInputStream(file);
int size = fis.available();
byte[] buffer = new byte[size];
int bytesRead = fis.read(buffer);
fis.close();
if (bytesRead < size) {
Log.w(TAG, "No se pudo leer el archivo completo.");
}
String jsonStr = new String(buffer, "UTF-8");
return new JSONObject(jsonStr);
} catch (Exception e) {
Log.e(TAG, "Error al parsear desde archivo interno: " + e.getMessage(), e);
return null;
}
}
private ValorNutricional parseValorNutricional(JSONObject vnObj) throws JSONException {
if (vnObj == null) return null;
return new ValorNutricional(
vnObj.optInt("calorias", 0),
vnObj.optInt("proteinas", 0),
vnObj.optInt("carbohidratos", 0),
vnObj.optInt("grasas", 0)
);
}
private Comida parseComida(JSONObject comidaObj) throws JSONException {
if (comidaObj == null) return null;
String nombre = comidaObj.optString("nombre_del_plato", "Comida sin nombre");
String receta = comidaObj.optString("receta", "");
String videoUrl = comidaObj.optString("video_url", "");
List<String> ingredientes = new ArrayList<>();
JSONArray alimentosArray = comidaObj.optJSONArray("ingredientes");
if (alimentosArray != null) {
for (int i = 0; i < alimentosArray.length(); i++) {
ingredientes.add(alimentosArray.getString(i));
}
}
ValorNutricional valorNutricional = null;
if (comidaObj.has("valor_nutricional") && !comidaObj.isNull("valor_nutricional")) {
valorNutricional = parseValorNutricional(comidaObj.getJSONObject("valor_nutricional"));
} else {
valorNutricional = new ValorNutricional(0, 0, 0, 0);
}
return new Comida(nombre, ingredientes, receta, valorNutricional, videoUrl);
}
public Semana convertirJsonASemana(JSONObject jsonObj) {
List<DiasEntrenamiento> listaDias = new ArrayList<>();
if (jsonObj == null) {
Log.e(TAG, "El objeto JSON principal es null en convertirJsonASemana");
return null;
}
try {
JSONArray diasArray = jsonObj.getJSONArray("semana");
for (int i = 0; i < diasArray.length(); i++) {
JSONObject diaObj = diasArray.getJSONObject(i);
String nombreDia = diaObj.getString("dia");
Entrenamiento entrenamiento = null;
if (diaObj.has("entrenamiento") && !diaObj.isNull("entrenamiento")) {
JSONObject entObj = diaObj.getJSONObject("entrenamiento");
entrenamiento = new Entrenamiento(
JsonUtils.parseEjercicios(entObj.getJSONArray("calentamiento")),
JsonUtils.parseEjerciciosPrincipales(entObj.getJSONArray("ejercicios_principales")),
JsonUtils.parseEjercicios(entObj.getJSONArray("enfriamiento"))
);
}
Dieta dieta = null;
if (diaObj.has("dieta") && !diaObj.isNull("dieta")) {
JSONObject dietaObj = diaObj.getJSONObject("dieta");
Comida desayuno = dietaObj.has("desayuno") && !dietaObj.isNull("desayuno") ? parseComida(dietaObj.getJSONObject("desayuno")) : null;
Comida comida = dietaObj.has("comida") && !dietaObj.isNull("comida") ? parseComida(dietaObj.getJSONObject("comida")) : null;
Comida cena = dietaObj.has("cena") && !dietaObj.isNull("cena") ? parseComida(dietaObj.getJSONObject("cena")) : null;
Comida snacks = dietaObj.has("snack") && !dietaObj.isNull("snack") ? parseComida(dietaObj.getJSONObject("snack")) : null;
dieta = new Dieta(desayuno, comida, cena, snacks);
}
DiasEntrenamiento dia = new DiasEntrenamiento(0, nombreDia, entrenamiento, dieta);
listaDias.add(dia);
}
} catch (JSONException e) {
Log.e(TAG, "Error al convertir JSON a Semana: " + e.getMessage(), e);
return null;
}
return new Semana(listaDias);
}
public Semana parsearDesdeString(String jsonString) {
try {
Semana semana = gson.fromJson(jsonString, Semana.class);
Log.d(TAG, "JSON parseado exitosamente desde String con Gson");
return semana;
} catch (Exception e) {
Log.e(TAG, "Error al parsear JSON desde String con Gson: " + e.getMessage(), e);
return null;
}
}
public String convertirAJson(Semana semana) {
try {
return gson.toJson(semana);
} catch (Exception e) {
Log.e(TAG, "Error al convertir a JSON con Gson: " + e.getMessage(), e);
return null;
}
}
public DiasEntrenamiento obtenerDia(Semana semana, String nombreDia) {
if (semana == null || semana.getSemana() == null) return null;
for (DiasEntrenamiento dia : semana.getSemana()) {
if (dia.getDia().equalsIgnoreCase(nombreDia)) {
return dia;
}
}
return null;
}
public List<String> obtenerEjerciciosDelDia(DiasEntrenamiento dia) {
List<String> ejercicios = new ArrayList<>();
if (dia == null || dia.getEntrenamiento() == null) return ejercicios;
Entrenamiento entrenamiento = dia.getEntrenamiento();
if (entrenamiento.getCalentamiento() != null) {
for (Ejercicio ejercicio : entrenamiento.getCalentamiento()) {
if (ejercicio != null) ejercicios.add("Calentamiento: " + ejercicio.getNombre());
}
}
if (entrenamiento.getEjercicios_principales() != null) {
for (Ejercicio ejercicio : entrenamiento.getEjercicios_principales()) {
if (ejercicio != null) ejercicios.add("Principal: " + ejercicio.getNombre() +
" (" + ejercicio.getSeries() + "x" + ejercicio.getRepeticiones() + ")");
}
}
if (entrenamiento.getEnfriamiento() != null) {
for (Ejercicio ejercicio : entrenamiento.getEnfriamiento()) {
if (ejercicio != null) ejercicios.add("Enfriamiento: " + ejercicio.getNombre());
}
}
return ejercicios;
}
public int calcularCaloriasTotales(DiasEntrenamiento dia) {
if (dia == null || dia.getDieta() == null) return 0;
Dieta dieta = dia.getDieta();
int total = 0;
if (dieta.getDesayuno() != null && dieta.getDesayuno().getValor_nutricional() != null) {
total += dieta.getDesayuno().getValor_nutricional().getCalorías();
}
if (dieta.getComida() != null && dieta.getComida().getValor_nutricional() != null) {
total += dieta.getComida().getValor_nutricional().getCalorías();
}
if (dieta.getCena() != null && dieta.getCena().getValor_nutricional() != null) {
total += dieta.getCena().getValor_nutricional().getCalorías();
}
if (dieta.getSnacks() != null && dieta.getSnacks().getValor_nutricional() != null) {
total += dieta.getSnacks().getValor_nutricional().getCalorías();
}
return total;
}
public String obtenerResumenNutricional(DiasEntrenamiento dia) {
if (dia == null || dia.getDieta() == null) return "Sin información";
Dieta dieta = dia.getDieta();
int calorias = 0, proteinas = 0, carbohidratos = 0, grasas = 0;
ValorNutricional[] valores = {
dieta.getDesayuno() != null ? dieta.getDesayuno().getValor_nutricional() : null,
dieta.getComida() != null ? dieta.getComida().getValor_nutricional() : null,
dieta.getCena() != null ? dieta.getCena().getValor_nutricional() : null,
dieta.getSnacks() != null ? dieta.getSnacks().getValor_nutricional() : null
};
for (ValorNutricional vn : valores) {
if (vn != null) {
calorias += vn.getCalorías();
proteinas += vn.getProteínas();
carbohidratos += vn.getCarbohidratos();
grasas += vn.getGrasas();
}
}
return String.format("Calorías: %d | Proteínas: %dg | Carbohidratos: %dg | Grasas: %dg",
calorias, proteinas, carbohidratos, grasas);
}
public boolean validarSemana(Semana semana) {
if (semana == null) {
Log.w(TAG, "Semana es null");
return false;
}
if (semana.getSemana() == null || semana.getSemana().isEmpty()) {
Log.w(TAG, "Lista de días está vacía");
return false;
}
Log.d(TAG, "Semana válida con " + semana.getSemana().size() + " días");
return true;
}
}

View File

@ -0,0 +1,183 @@
package com.borjabolufer.elevate.utils;
import static android.content.ContentValues.TAG;
import android.content.Context;
import android.os.Build;
import android.util.Log;
import com.borjabolufer.elevate.model.Ejercicio;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
public class JsonUtils {
public static List<Ejercicio> parseEjercicios(JSONArray jsonArray) throws JSONException {
List<Ejercicio> lista = new ArrayList<>();
if (jsonArray == null) return lista;
for (int i = 0; i < jsonArray.length(); i++) {
Object element = jsonArray.get(i);
if (element instanceof JSONObject) {
JSONObject obj = (JSONObject) element;
String nombre = obj.optString("nombre", "Ejercicio sin nombre");
String descripcion = obj.optString("descripcion", "");
String videoUrl = obj.optString("video_url", "");
String duracion = obj.optString("descanso", "");
int series = obj.optInt("series");
int repeticiones = obj.optInt("repeticiones");
lista.add(new Ejercicio(nombre, descripcion, series, repeticiones, duracion, videoUrl));
} else if (element instanceof String) {
String nombre = (String) element;
lista.add(new Ejercicio(nombre, "", 0, 0, "", ""));
}
}
return lista;
}
public static List<Ejercicio> parseEjerciciosPrincipales(JSONArray jsonArray) throws JSONException {
List<Ejercicio> lista = new ArrayList<>();
if (jsonArray == null) return lista;
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject obj = jsonArray.getJSONObject(i);
String nombre = obj.optString("nombre", "Ejercicio sin nombre");
String descripcion = obj.optString("descripcion", "");
String videoUrl = obj.optString("video_url", "");
int series = obj.optInt("series", 0);
int repeticiones = obj.optInt("repeticiones", 0);
String descanso = obj.optString("descanso", "");
StringBuilder descripcionCompleta = new StringBuilder(descripcion);
lista.add(new Ejercicio(nombre, descripcionCompleta.toString(), series, repeticiones, descanso, videoUrl));
}
return lista;
}
public static File obtenerArchivoPlan(Context context, String emailUsuario) {
emailUsuario = normalizarEmail(emailUsuario);
File archivo = new File(context.getFilesDir(), "plan_"+ emailUsuario + ".json");
Log.d(TAG, "Buscando archivo: " + archivo.getAbsolutePath());
return archivo;
}
public static String normalizarEmail(String email) {
return email.replace("@", "_").replace(".", "_");
}
public static String cargarPlanDelUsuario(Context context, String emailUsuario) {
try {
File archivo = obtenerArchivoPlan(context, emailUsuario);
if (archivo.exists()) {
Log.d(TAG, "Archivo encontrado. Leyendo contenido...");
return leerArchivoCompatible(archivo);
} else {
Log.w(TAG, "Archivo no encontrado para el usuario: " + emailUsuario);
}
} catch (IOException e) {
Log.e(TAG, "Error leyendo el archivo JSON del usuario", e);
}
return null;
}
private static String leerArchivoCompatible(File archivo) throws IOException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
return new String(Files.readAllBytes(archivo.toPath()));
} else {
return leerArchivoTradicional(archivo);
}
}
private static String leerArchivoTradicional(File archivo) throws IOException {
FileInputStream fis = null;
try {
fis = new FileInputStream(archivo);
byte[] buffer = new byte[(int) archivo.length()];
int bytesRead = fis.read(buffer);
if (bytesRead > 0) {
return new String(buffer, 0, bytesRead, "UTF-8");
} else {
throw new IOException("No se pudieron leer datos del archivo");
}
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
Log.w(TAG, "Error al cerrar FileInputStream: " + e.getMessage());
}
}
}
}
public static boolean existeArchivoPlan(Context context, String emailUsuario) {
File archivo = obtenerArchivoPlan(context, emailUsuario);
boolean existe = archivo.exists();
Log.d(TAG, "Archivo de plan para " + emailUsuario + " existe: " + existe);
return existe;
}
public static boolean eliminarArchivoPlan(Context context, String emailUsuario) {
try {
File archivo = obtenerArchivoPlan(context, emailUsuario);
if (archivo.exists()) {
boolean eliminado = archivo.delete();
Log.d(TAG, "Archivo de plan eliminado para " + emailUsuario + ": " + eliminado);
return eliminado;
}
return true;
} catch (Exception e) {
Log.e(TAG, "Error al eliminar archivo de plan: " + e.getMessage());
return false;
}
}
public static boolean guardarPlanEnArchivo(Context context, String emailUsuario, String contenidoJson) {
try {
File archivo = obtenerArchivoPlan(context, emailUsuario);
FileOutputStream fos = new FileOutputStream(archivo);
fos.write(contenidoJson.getBytes("UTF-8"));
fos.flush();
fos.close();
Log.d(TAG, "Plan guardado en archivo para usuario: " + emailUsuario);
return true;
} catch (IOException e) {
Log.e(TAG, "Error al guardar plan en archivo: " + e.getMessage());
return false;
}
}
}

View File

@ -0,0 +1,80 @@
package com.borjabolufer.elevate.utils;
import android.util.Log;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Arrays;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
public class PasswordUtils {
private static final String TAG = "PasswordUtils";
private static final String ALGORITHM = "PBKDF2WithHmacSHA256";
private static final int ITERATION_COUNT = 65536;
private static final int KEY_LENGTH = 256;
private static final int SALT_SIZE = 16;
public static String hashPassword(String password) {
if (password == null || password.isEmpty()) {
Log.e(TAG, "La contraseña no puede ser nula o vacía.");
return null;
}
try {
byte[] salt = new byte[SALT_SIZE];
new SecureRandom().nextBytes(salt);
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH);
SecretKeyFactory factory = SecretKeyFactory.getInstance(ALGORITHM);
byte[] hash = factory.generateSecret(spec).getEncoded();
String saltBase64 = android.util.Base64.encodeToString(salt, android.util.Base64.NO_WRAP);
String hashBase64 = android.util.Base64.encodeToString(hash, android.util.Base64.NO_WRAP);
return saltBase64 + ":" + hashBase64;
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
Log.e(TAG, "Error al hashear la contraseña", e);
return null;
}
}
public static boolean verifyPassword(String plainPassword, String storedPasswordWithSalt) {
if (plainPassword == null || plainPassword.isEmpty() || storedPasswordWithSalt == null || storedPasswordWithSalt.isEmpty()) {
Log.w(TAG, "Contraseña o hash almacenado vacío.");
return false;
}
try {
Log.d(TAG, "Hash recibido: " + storedPasswordWithSalt);
Log.e(TAG, "Valor real recibido para verificar: [" + storedPasswordWithSalt + "]");
String[] parts = storedPasswordWithSalt.split(":");
if (parts.length != 2) {
Log.e(TAG, "Formato de hash incorrecto. Se esperaba 'salt:hash'");
return false;
}
byte[] salt = android.util.Base64.decode(parts[0], android.util.Base64.NO_WRAP);
byte[] storedHash = android.util.Base64.decode(parts[1], android.util.Base64.NO_WRAP);
KeySpec spec = new PBEKeySpec(plainPassword.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH);
SecretKeyFactory factory = SecretKeyFactory.getInstance(ALGORITHM);
byte[] inputHash = factory.generateSecret(spec).getEncoded();
return Arrays.equals(storedHash, inputHash);
} catch (Exception e) {
Log.e(TAG, "Error al verificar la contraseña", e);
return false;
}
}
}

View File

@ -0,0 +1,160 @@
package com.borjabolufer.elevate.utils;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import com.borjabolufer.elevate.R;
public class PlanActualizadoDialog extends DialogFragment {
private static final String ARG_TITULO = "titulo";
private static final String ARG_MENSAJE = "mensaje";
private static final String ARG_ES_REGENERACION = "es_regeneracion";
private OnDialogCloseListener listener;
public interface OnDialogCloseListener {
void onDialogClosed();
}
public static PlanActualizadoDialog newInstance(boolean esRegeneracion) {
PlanActualizadoDialog fragment = new PlanActualizadoDialog();
Bundle args = new Bundle();
if (esRegeneracion) {
args.putString(ARG_TITULO, "¡Plan Actualizado!");
args.putString(ARG_MENSAJE, "Tu plan personalizado ha sido actualizado con éxito. Los nuevos ejercicios y recetas ya están disponibles.");
} else {
args.putString(ARG_TITULO, "¡Plan Creado!");
args.putString(ARG_MENSAJE, "Tu plan personalizado ha sido creado con éxito. ¡Ya puedes comenzar tu journey fitness!");
}
args.putBoolean(ARG_ES_REGENERACION, esRegeneracion);
fragment.setArguments(args);
return fragment;
}
public void setOnDialogCloseListener(OnDialogCloseListener listener) {
this.listener = listener;
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
Dialog dialog = super.onCreateDialog(savedInstanceState);
Window window = dialog.getWindow();
if (window != null) {
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
window.requestFeature(Window.FEATURE_NO_TITLE);
}
return dialog;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.dialog_plan_actualizado, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Bundle args = getArguments();
if (args == null) return;
String titulo = args.getString(ARG_TITULO, "¡Plan Actualizado!");
String mensaje = args.getString(ARG_MENSAJE, "Tu plan ha sido actualizado con éxito.");
boolean esRegeneracion = args.getBoolean(ARG_ES_REGENERACION, true);
ImageView iconoExito = view.findViewById(R.id.iconoExito);
TextView tituloText = view.findViewById(R.id.tituloDialog);
TextView mensajeText = view.findViewById(R.id.mensajeDialog);
Button btnAceptar = view.findViewById(R.id.btnAceptar);
tituloText.setText(titulo);
mensajeText.setText(mensaje);
if (esRegeneracion) {
btnAceptar.setText("Continuar");
}
animarEntrada(view);
btnAceptar.setOnClickListener(v -> cerrarDialog());
view.setOnClickListener(v -> {
if (v.getId() == R.id.backgroundDialog) {
cerrarDialog();
}
});
}
private void animarEntrada(View view) {
try {
View contenedor = view.findViewById(R.id.contenedorDialog);
Animation slideIn = AnimationUtils.loadAnimation(getContext(), R.anim.slide_in_bottom);
contenedor.startAnimation(slideIn);
ImageView icono = view.findViewById(R.id.iconoExito);
Animation scaleIn = AnimationUtils.loadAnimation(getContext(), R.anim.scale_in);
icono.postDelayed(() -> icono.startAnimation(scaleIn), 300);
} catch (Exception e) {
}
}
private void cerrarDialog() {
try {
if (listener != null) {
listener.onDialogClosed();
}
dismiss();
} catch (Exception e) {
dismissAllowingStateLoss();
}
}
@Override
public void onStart() {
super.onStart();
Dialog dialog = getDialog();
if (dialog != null && dialog.getWindow() != null) {
int width = (int) (getResources().getDisplayMetrics().widthPixels * 0.9);
dialog.getWindow().setLayout(width, ViewGroup.LayoutParams.WRAP_CONTENT);
}
}
}

View File

@ -0,0 +1,80 @@
package com.borjabolufer.elevate.utils;
import android.content.Context;
import android.util.Log;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class UserFileManager {
private static final String TAG = "UserFileManager";
public static String generarNombreArchivo(String email, String tipo) {
String emailLimpio = email.replace("@", "_").replace(".", "_");
return tipo + "_" + emailLimpio + ".json";
}
public static boolean existeArchivo(Context context, String email, String tipo) {
String fileName = generarNombreArchivo(email, tipo);
File file = new File(context.getFilesDir(), fileName);
return file.exists();
}
public static long obtenerTamanoArchivo(Context context, String email, String tipo) {
String fileName = generarNombreArchivo(email, tipo);
File file = new File(context.getFilesDir(), fileName);
return file.exists() ? file.length() : 0;
}
public static boolean eliminarArchivo(Context context, String email, String tipo) {
String fileName = generarNombreArchivo(email, tipo);
File file = new File(context.getFilesDir(), fileName);
if (file.exists()) {
boolean deleted = file.delete();
Log.i(TAG, "Archivo " + fileName + (deleted ? " eliminado" : " NO eliminado"));
return deleted;
}
return true;
}
public static List<String> listarUsuariosConPlanes(Context context) {
List<String> usuarios = new ArrayList<>();
File[] files = context.getFilesDir().listFiles();
if (files != null) {
for (File file : files) {
if (file.getName().startsWith("plan_") && file.getName().endsWith(".json")) {
String email = extraerEmailDeNombreArchivo(file.getName());
if (email != null) {
usuarios.add(email);
}
}
}
}
return usuarios;
}
private static String extraerEmailDeNombreArchivo(String fileName) {
try {
String sinExtension = fileName.replace("plan_", "").replace(".json", "");
int lastUnderscore = sinExtension.lastIndexOf("_");
if (lastUnderscore > 0) {
String parte1 = sinExtension.substring(0, lastUnderscore);
String parte2 = sinExtension.substring(lastUnderscore + 1);
return parte1.replace("_", "") + "@" + parte2 + ".com";
}
} catch (Exception e) {
Log.e(TAG, "Error al extraer email: " + e.getMessage());
}
return null;
}
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<scale
android:duration="400"
android:fromXScale="0.0"
android:fromYScale="0.0"
android:toXScale="1.0"
android:toYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
android:interpolator="@android:anim/bounce_interpolator" />
</set>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="300"
android:fromYDelta="100%"
android:toYDelta="0%"
android:interpolator="@android:anim/decelerate_interpolator" />
<alpha
android:duration="300"
android:fromAlpha="0.0"
android:toAlpha="1.0" />
</set>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="-100%p"
android:toXDelta="0"
android:duration="300" />
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="300" />
</set>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="100%p"
android:toXDelta="0"
android:duration="300" />
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="300" />
</set>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="0"
android:toXDelta="-100%p"
android:duration="300" />
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:duration="300" />
</set>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="0"
android:toXDelta="100%p"
android:duration="300" />
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:duration="300" />
</set>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#0D47A1" android:state_checked="false"/> <!-- Azul oscuro cuando no seleccionado -->
<item android:color="#4CAF50" android:state_checked="true"/> <!-- Verde cuando seleccionado -->
</selector>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<gradient
android:type="linear"
android:angle="135"
android:startColor="#71FDF0"
android:centerColor="#03A9F4"
android:endColor="#2196F3"
android:useLevel="false" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
<solid android:color="#66000000" />
</shape>
</item>
</layer-list>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M7,14l5,-5 5,5z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M20,3h-1L19,1h-2v2L7,3L7,1L5,1v2L4,3c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,5c0,-1.1 -0.9,-2 -2,-2zM20,21L4,21L4,8h16v13z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M17.5,9.5C17.5,6.46 15.04,4 12,4S6.5,6.46 6.5,9.5c0,2.7 1.94,4.93 4.5,5.4V17H9v2h2v2h2v-2h2v-2h-2v-2.1C15.56,14.43 17.5,12.2 17.5,9.5zM8.5,9.5C8.5,7.57 10.07,6 12,6s3.5,1.57 3.5,3.5S13.93,13 12,13S8.5,11.43 8.5,9.5z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M7.41,8.59L12,13.17l4.59,-4.58L18,10l-6,6 -6,-6 1.41,-1.41z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M9.5,11c1.93,0 3.5,1.57 3.5,3.5S11.43,18 9.5,18S6,16.43 6,14.5S7.57,11 9.5,11zM9.5,9C6.46,9 4,11.46 4,14.5S6.46,20 9.5,20s5.5,-2.46 5.5,-5.5c0,-1.16 -0.36,-2.23 -0.97,-3.12L18,7.42V10h2V4h-6v2h2.58l-3.97,3.97C11.73,9.36 10.66,9 9.5,9z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M19,19H5V5h7V3H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2v-7h-2v7zM14,3v2h3.59l-9.83,9.83 1.41,1.41L19,6.41V10h2V3h-7z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,5.9c1.16,0 2.1,0.94 2.1,2.1s-0.94,2.1 -2.1,2.1S9.9,9.16 9.9,8s0.94,-2.1 2.1,-2.1m0,9c2.97,0 6.1,1.46 6.1,2.1v1.1L5.9,18.1L5.9,17c0,-0.64 3.13,-2.1 6.1,-2.1M12,4C9.79,4 8,5.79 8,8s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,13c-2.67,0 -8,1.34 -8,4v3h16v-3c0,-2.66 -5.33,-4 -8,-4z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M8,5v14l11,-7z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,5V1L7,6l5,5V7c3.31,0 6,2.69 6,6s-2.69,6 -6,6 -6,-2.69 -6,-6H4c0,4.42 3.58,8 8,8s8,-3.58 8,-8 -3.58,-8 -8,-8z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M16.24,7.76C15.07,6.59 13.54,6 12,6v6l-4.24,4.24c2.34,2.34 6.14,2.34 8.49,0 2.34,-2.34 2.34,-6.14 -0.01,-8.48zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
</vector>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@android:color/white" />
<stroke android:width="2dp" android:color="@color/primary" />
<corners android:radius="12dp" />
</shape>
</item>
</selector>

View File

@ -0,0 +1,10 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<gradient
android:startColor="@color/primary"
android:endColor="@color/primary_dark"
android:angle="135" />
<size
android:width="80dp"
android:height="80dp" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/white" />
<corners android:radius="8dp" />
<stroke
android:width="1dp"
android:color="#CCCCCC" /> </shape>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="20dp" />
<solid android:color="@android:color/white" />
<stroke
android:width="1dp"
android:color="#E9ECEF" />
</shape>

View File

@ -0,0 +1,11 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="#EBF4FF"
android:endColor="#DBEAFE"
android:angle="135" />
<corners android:radius="12dp" />
<stroke
android:width="1dp"
android:color="@color/primary_light" />
</shape>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#2C3E50"
android:endColor="#4A6741"
android:angle="135" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorOnPrimary">
<path
android:fillColor="@android:color/white"
android:pathData="M7,10l5,5 5,-5z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorOnPrimary">
<path
android:fillColor="@android:color/white"
android:pathData="M12,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorOnPrimary">
<path
android:fillColor="@android:color/white"
android:pathData="M20,3h-1L19,1h-2v2L7,3L7,1L5,1v2L4,3c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,5c0,-1.1 -0.9,-2 -2,-2zM20,21L4,21L4,8h16v13z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorOnSurface">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM10,17l-5,-5 1.41,-1.41L10,14.17l7.59,-7.59L19,8l-9,9z" />
</vector>

Some files were not shown because too many files have changed in this diff Show More