commit 7ca65a2ce351dd8d6afe1806d6d75879e18bcd6c Author: borsal Date: Fri May 30 01:40:16 2025 +0200 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. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..66cd181 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +Elevate \ No newline at end of file diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b86273d --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..3d2ec82 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,18 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..639c779 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..b2c751a --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/.idea/sqlandroid.xml b/.idea/sqlandroid.xml new file mode 100644 index 0000000..ebc2259 --- /dev/null +++ b/.idea/sqlandroid.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..80454b3 --- /dev/null +++ b/app/build.gradle @@ -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' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -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 \ No newline at end of file diff --git a/app/src/androidTest/java/com/borjabolufer/elevate/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/borjabolufer/elevate/ExampleInstrumentedTest.java new file mode 100644 index 0000000..a48362b --- /dev/null +++ b/app/src/androidTest/java/com/borjabolufer/elevate/ExampleInstrumentedTest.java @@ -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 Testing documentation + */ +@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()); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..246171f --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/LoginActivity.java b/app/src/main/java/com/borjabolufer/elevate/LoginActivity.java new file mode 100644 index 0000000..19dc4c1 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/LoginActivity.java @@ -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); + }); + } +} diff --git a/app/src/main/java/com/borjabolufer/elevate/MainActivity.java b/app/src/main/java/com/borjabolufer/elevate/MainActivity.java new file mode 100644 index 0000000..1a1891c --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/MainActivity.java @@ -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(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/PreguntasActivity.java b/app/src/main/java/com/borjabolufer/elevate/PreguntasActivity.java new file mode 100644 index 0000000..cb4b790 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/PreguntasActivity.java @@ -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; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/RegisterActivity.java b/app/src/main/java/com/borjabolufer/elevate/RegisterActivity.java new file mode 100644 index 0000000..86ffe7b --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/RegisterActivity.java @@ -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(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/SplashActivity.java b/app/src/main/java/com/borjabolufer/elevate/SplashActivity.java new file mode 100644 index 0000000..970baea --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/SplashActivity.java @@ -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); + } +} diff --git a/app/src/main/java/com/borjabolufer/elevate/WelcomeActivity.java b/app/src/main/java/com/borjabolufer/elevate/WelcomeActivity.java new file mode 100644 index 0000000..d256909 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/WelcomeActivity.java @@ -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)); + }); + } +} diff --git a/app/src/main/java/com/borjabolufer/elevate/adapters/ComidaAdapter.java b/app/src/main/java/com/borjabolufer/elevate/adapters/ComidaAdapter.java new file mode 100644 index 0000000..5c499bf --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/adapters/ComidaAdapter.java @@ -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 { + + private static final String TAG = "ComidaAdapter"; + private List comidas; + private Fragment parentFragment; + + public ComidaAdapter(List comidas) { + this.comidas = comidas; + } + + public ComidaAdapter(List 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); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/adapters/EjercicioAdapter.java b/app/src/main/java/com/borjabolufer/elevate/adapters/EjercicioAdapter.java new file mode 100644 index 0000000..ec90411 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/adapters/EjercicioAdapter.java @@ -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 { + + public interface OnItemClickListener { + void onItemClick(HomeFragment.EjercicioItem item); + } + + private static final int TYPE_HEADER = 0; + private static final int TYPE_EJERCICIO = 1; + + private List ejercicios; + private OnItemClickListener listener; + + public EjercicioAdapter(List 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); + } + }); + } + } +} diff --git a/app/src/main/java/com/borjabolufer/elevate/model/Altura.java b/app/src/main/java/com/borjabolufer/elevate/model/Altura.java new file mode 100644 index 0000000..10c40d2 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/model/Altura.java @@ -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 + ", "; + } +} diff --git a/app/src/main/java/com/borjabolufer/elevate/model/Comida.java b/app/src/main/java/com/borjabolufer/elevate/model/Comida.java new file mode 100644 index 0000000..52e618f --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/model/Comida.java @@ -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 ingredientes; + public String receta; + public ValorNutricional valor_nutricional; + public String video_url; + + public Comida(String nombre, List 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 getIngredientes() { + return ingredientes; + } + + public void setIngredientes(List 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; + } +} diff --git a/app/src/main/java/com/borjabolufer/elevate/model/DiasEntrenamiento.java b/app/src/main/java/com/borjabolufer/elevate/model/DiasEntrenamiento.java new file mode 100644 index 0000000..a374aa4 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/model/DiasEntrenamiento.java @@ -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; + } +} + diff --git a/app/src/main/java/com/borjabolufer/elevate/model/Dieta.java b/app/src/main/java/com/borjabolufer/elevate/model/Dieta.java new file mode 100644 index 0000000..da2f86c --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/model/Dieta.java @@ -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; + } +} diff --git a/app/src/main/java/com/borjabolufer/elevate/model/Edad.java b/app/src/main/java/com/borjabolufer/elevate/model/Edad.java new file mode 100644 index 0000000..feaa859 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/model/Edad.java @@ -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 + ", "; + } +} + diff --git a/app/src/main/java/com/borjabolufer/elevate/model/Ejercicio.java b/app/src/main/java/com/borjabolufer/elevate/model/Ejercicio.java new file mode 100644 index 0000000..0b6e864 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/model/Ejercicio.java @@ -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; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/model/Entrenamiento.java b/app/src/main/java/com/borjabolufer/elevate/model/Entrenamiento.java new file mode 100644 index 0000000..d83cab7 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/model/Entrenamiento.java @@ -0,0 +1,40 @@ +package com.borjabolufer.elevate.model; + +import java.util.List; + +public class Entrenamiento { + public List calentamiento; + public List ejercicios_principales; + public List enfriamiento; + public Entrenamiento(List calentamiento, List ejercicios_principales, List enfriamiento) { + this.calentamiento = calentamiento; + this.ejercicios_principales = ejercicios_principales; + this.enfriamiento = enfriamiento; + } + + + + public List getCalentamiento() { + return calentamiento; + } + + public void setCalentamiento(List calentamiento) { + this.calentamiento = calentamiento; + } + + public List getEjercicios_principales() { + return ejercicios_principales; + } + + public void setEjercicios_principales(List ejercicios_principales) { + this.ejercicios_principales = ejercicios_principales; + } + + public List getEnfriamiento() { + return enfriamiento; + } + + public void setEnfriamiento(List enfriamiento) { + this.enfriamiento = enfriamiento; + } +} diff --git a/app/src/main/java/com/borjabolufer/elevate/model/Intensidad.java b/app/src/main/java/com/borjabolufer/elevate/model/Intensidad.java new file mode 100644 index 0000000..138716d --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/model/Intensidad.java @@ -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 + ", "; + } +} diff --git a/app/src/main/java/com/borjabolufer/elevate/model/Objetivo.java b/app/src/main/java/com/borjabolufer/elevate/model/Objetivo.java new file mode 100644 index 0000000..1e4a95d --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/model/Objetivo.java @@ -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 + ", "; + } +} diff --git a/app/src/main/java/com/borjabolufer/elevate/model/Peso.java b/app/src/main/java/com/borjabolufer/elevate/model/Peso.java new file mode 100644 index 0000000..3d9dda8 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/model/Peso.java @@ -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 + ", "; + } +} + diff --git a/app/src/main/java/com/borjabolufer/elevate/model/Semana.java b/app/src/main/java/com/borjabolufer/elevate/model/Semana.java new file mode 100644 index 0000000..bc8eaf1 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/model/Semana.java @@ -0,0 +1,19 @@ +package com.borjabolufer.elevate.model; + +import java.util.List; + +public class Semana { + public List semana; + + public Semana(List semana) { + this.semana = semana; + } + + public List getSemana() { + return semana; + } + + public void setSemana(List semana) { + this.semana = semana; + } +} diff --git a/app/src/main/java/com/borjabolufer/elevate/model/Sexo.java b/app/src/main/java/com/borjabolufer/elevate/model/Sexo.java new file mode 100644 index 0000000..36f0985 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/model/Sexo.java @@ -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 + ", "; + } +} diff --git a/app/src/main/java/com/borjabolufer/elevate/model/Usuario.java b/app/src/main/java/com/borjabolufer/elevate/model/Usuario.java new file mode 100644 index 0000000..5822511 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/model/Usuario.java @@ -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(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/model/UsuarioDbHelper.java b/app/src/main/java/com/borjabolufer/elevate/model/UsuarioDbHelper.java new file mode 100644 index 0000000..c1a6b29 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/model/UsuarioDbHelper.java @@ -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 listarPlanesExistentes(Context context) { + List 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 obtenerTodosLosUsuarios() { + List 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; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/model/UsuarioEntity.java b/app/src/main/java/com/borjabolufer/elevate/model/UsuarioEntity.java new file mode 100644 index 0000000..8ba44d5 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/model/UsuarioEntity.java @@ -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 + + '}'; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/model/ValorNutricional.java b/app/src/main/java/com/borjabolufer/elevate/model/ValorNutricional.java new file mode 100644 index 0000000..c751b32 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/model/ValorNutricional.java @@ -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; + } +} diff --git a/app/src/main/java/com/borjabolufer/elevate/ui/fragments/AlturaFragment.java b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/AlturaFragment.java new file mode 100644 index 0000000..a19b5ba --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/AlturaFragment.java @@ -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; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/ui/fragments/DetalleComidaFragment.java b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/DetalleComidaFragment.java new file mode 100644 index 0000000..63a710b --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/DetalleComidaFragment.java @@ -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 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; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/ui/fragments/DetalleEjercicioFragment.java b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/DetalleEjercicioFragment.java new file mode 100644 index 0000000..ed1406b --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/DetalleEjercicioFragment.java @@ -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); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/ui/fragments/DiasEntrenamientoFragment.java b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/DiasEntrenamientoFragment.java new file mode 100644 index 0000000..e20f584 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/DiasEntrenamientoFragment.java @@ -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 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 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; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/ui/fragments/DietFragment.java b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/DietFragment.java new file mode 100644 index 0000000..eacd63a --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/DietFragment.java @@ -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 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; } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/ui/fragments/EdadFragment.java b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/EdadFragment.java new file mode 100644 index 0000000..23e94ae --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/EdadFragment.java @@ -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"); + } + } +} diff --git a/app/src/main/java/com/borjabolufer/elevate/ui/fragments/HomeFragment.java b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/HomeFragment.java new file mode 100644 index 0000000..2d19e51 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/HomeFragment.java @@ -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 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; } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/ui/fragments/IntensidadFragment.java b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/IntensidadFragment.java new file mode 100644 index 0000000..8d952ca --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/IntensidadFragment.java @@ -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); + } + }); + } +} diff --git a/app/src/main/java/com/borjabolufer/elevate/ui/fragments/LoadingPlanFragment.java b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/LoadingPlanFragment.java new file mode 100644 index 0000000..b3a0c60 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/LoadingPlanFragment.java @@ -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 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 { + private List logItems; + + public LogAdapter(List 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(); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/ui/fragments/LoginFragment.java b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/LoginFragment.java new file mode 100644 index 0000000..cceb0bb --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/LoginFragment.java @@ -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; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/ui/fragments/ObjetivoFragment.java b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/ObjetivoFragment.java new file mode 100644 index 0000000..ec010e8 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/ObjetivoFragment.java @@ -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); + } + }); + } +} diff --git a/app/src/main/java/com/borjabolufer/elevate/ui/fragments/PesoFragment.java b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/PesoFragment.java new file mode 100644 index 0000000..fd18910 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/PesoFragment.java @@ -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; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/ui/fragments/ProfileFragment.java b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/ProfileFragment.java new file mode 100644 index 0000000..229e9f8 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/ProfileFragment.java @@ -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 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 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 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 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(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/ui/fragments/ResumenFragment.java b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/ResumenFragment.java new file mode 100644 index 0000000..fb0fb65 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/ResumenFragment.java @@ -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() + ); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/ui/fragments/SexoFragment.java b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/SexoFragment.java new file mode 100644 index 0000000..8da63ec --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/ui/fragments/SexoFragment.java @@ -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); + } + }); + } +} diff --git a/app/src/main/java/com/borjabolufer/elevate/utils/AiProviderManager.java b/app/src/main/java/com/borjabolufer/elevate/utils/AiProviderManager.java new file mode 100644 index 0000000..4e6e603 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/utils/AiProviderManager.java @@ -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() ? "Sí" : "No").append("\n"); + status.append("Claude disponible: ").append(isClaudeAvailable() ? "Sí" : "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); + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/utils/ChatGptApi.java b/app/src/main/java/com/borjabolufer/elevate/utils/ChatGptApi.java new file mode 100644 index 0000000..302912d --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/utils/ChatGptApi.java @@ -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()); + } + } + }); + } +} diff --git a/app/src/main/java/com/borjabolufer/elevate/utils/ClaudeApi.java b/app/src/main/java/com/borjabolufer/elevate/utils/ClaudeApi.java new file mode 100644 index 0000000..ea96892 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/utils/ClaudeApi.java @@ -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("sí") || 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"; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/utils/ColorUtils.java b/app/src/main/java/com/borjabolufer/elevate/utils/ColorUtils.java new file mode 100644 index 0000000..b795735 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/utils/ColorUtils.java @@ -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); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/utils/JsonParser.java b/app/src/main/java/com/borjabolufer/elevate/utils/JsonParser.java new file mode 100644 index 0000000..1ce9c21 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/utils/JsonParser.java @@ -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 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 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 obtenerEjerciciosDelDia(DiasEntrenamiento dia) { + List 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; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/utils/JsonUtils.java b/app/src/main/java/com/borjabolufer/elevate/utils/JsonUtils.java new file mode 100644 index 0000000..7e8c7a2 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/utils/JsonUtils.java @@ -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 parseEjercicios(JSONArray jsonArray) throws JSONException { + List 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 parseEjerciciosPrincipales(JSONArray jsonArray) throws JSONException { + List 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; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/utils/PasswordUtils.java b/app/src/main/java/com/borjabolufer/elevate/utils/PasswordUtils.java new file mode 100644 index 0000000..7f498e8 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/utils/PasswordUtils.java @@ -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; + } + } +} diff --git a/app/src/main/java/com/borjabolufer/elevate/utils/PlanActualizadoDialog.java b/app/src/main/java/com/borjabolufer/elevate/utils/PlanActualizadoDialog.java new file mode 100644 index 0000000..19ec329 --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/utils/PlanActualizadoDialog.java @@ -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); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/borjabolufer/elevate/utils/UserFileManager.java b/app/src/main/java/com/borjabolufer/elevate/utils/UserFileManager.java new file mode 100644 index 0000000..284e26e --- /dev/null +++ b/app/src/main/java/com/borjabolufer/elevate/utils/UserFileManager.java @@ -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 listarUsuariosConPlanes(Context context) { + List 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; + } +} \ No newline at end of file diff --git a/app/src/main/res/anim/scale_in.xml b/app/src/main/res/anim/scale_in.xml new file mode 100644 index 0000000..1bf9e1b --- /dev/null +++ b/app/src/main/res/anim/scale_in.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_in_bottom.xml b/app/src/main/res/anim/slide_in_bottom.xml new file mode 100644 index 0000000..4013654 --- /dev/null +++ b/app/src/main/res/anim/slide_in_bottom.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_in_left.xml b/app/src/main/res/anim/slide_in_left.xml new file mode 100644 index 0000000..7057f52 --- /dev/null +++ b/app/src/main/res/anim/slide_in_left.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_in_right.xml b/app/src/main/res/anim/slide_in_right.xml new file mode 100644 index 0000000..5ddc574 --- /dev/null +++ b/app/src/main/res/anim/slide_in_right.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_out_left.xml b/app/src/main/res/anim/slide_out_left.xml new file mode 100644 index 0000000..da227fc --- /dev/null +++ b/app/src/main/res/anim/slide_out_left.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_out_right.xml b/app/src/main/res/anim/slide_out_right.xml new file mode 100644 index 0000000..76cab93 --- /dev/null +++ b/app/src/main/res/anim/slide_out_right.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/selector_boton.xml b/app/src/main/res/color/selector_boton.xml new file mode 100644 index 0000000..cad0933 --- /dev/null +++ b/app/src/main/res/color/selector_boton.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/background_toolbar.xml b/app/src/main/res/drawable/background_toolbar.xml new file mode 100644 index 0000000..8354219 --- /dev/null +++ b/app/src/main/res/drawable/background_toolbar.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/baseline_arrow_drop_up_24.xml b/app/src/main/res/drawable/baseline_arrow_drop_up_24.xml new file mode 100644 index 0000000..35814a2 --- /dev/null +++ b/app/src/main/res/drawable/baseline_arrow_drop_up_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_calendar_today_24.xml b/app/src/main/res/drawable/baseline_calendar_today_24.xml new file mode 100644 index 0000000..45cefd6 --- /dev/null +++ b/app/src/main/res/drawable/baseline_calendar_today_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_female_24.xml b/app/src/main/res/drawable/baseline_female_24.xml new file mode 100644 index 0000000..43cf164 --- /dev/null +++ b/app/src/main/res/drawable/baseline_female_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_info_24.xml b/app/src/main/res/drawable/baseline_info_24.xml new file mode 100644 index 0000000..ef3a1fe --- /dev/null +++ b/app/src/main/res/drawable/baseline_info_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_keyboard_arrow_down_24.xml b/app/src/main/res/drawable/baseline_keyboard_arrow_down_24.xml new file mode 100644 index 0000000..1a69b23 --- /dev/null +++ b/app/src/main/res/drawable/baseline_keyboard_arrow_down_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_male_24.xml b/app/src/main/res/drawable/baseline_male_24.xml new file mode 100644 index 0000000..9abb5d4 --- /dev/null +++ b/app/src/main/res/drawable/baseline_male_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_open_in_new_24.xml b/app/src/main/res/drawable/baseline_open_in_new_24.xml new file mode 100644 index 0000000..9998747 --- /dev/null +++ b/app/src/main/res/drawable/baseline_open_in_new_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_pause_24.xml b/app/src/main/res/drawable/baseline_pause_24.xml new file mode 100644 index 0000000..ae853f2 --- /dev/null +++ b/app/src/main/res/drawable/baseline_pause_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_perm_identity_24.xml b/app/src/main/res/drawable/baseline_perm_identity_24.xml new file mode 100644 index 0000000..a3be1b8 --- /dev/null +++ b/app/src/main/res/drawable/baseline_perm_identity_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_play_arrow_24.xml b/app/src/main/res/drawable/baseline_play_arrow_24.xml new file mode 100644 index 0000000..b176182 --- /dev/null +++ b/app/src/main/res/drawable/baseline_play_arrow_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_replay_24.xml b/app/src/main/res/drawable/baseline_replay_24.xml new file mode 100644 index 0000000..de6f5a2 --- /dev/null +++ b/app/src/main/res/drawable/baseline_replay_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_timelapse_24.xml b/app/src/main/res/drawable/baseline_timelapse_24.xml new file mode 100644 index 0000000..80dc29e --- /dev/null +++ b/app/src/main/res/drawable/baseline_timelapse_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/button_border_selector.xml b/app/src/main/res/drawable/button_border_selector.xml new file mode 100644 index 0000000..b46f448 --- /dev/null +++ b/app/src/main/res/drawable/button_border_selector.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/circle_gradient_background.xml b/app/src/main/res/drawable/circle_gradient_background.xml new file mode 100644 index 0000000..c091233 --- /dev/null +++ b/app/src/main/res/drawable/circle_gradient_background.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/descripcion.png b/app/src/main/res/drawable/descripcion.png new file mode 100644 index 0000000..18ed80b Binary files /dev/null and b/app/src/main/res/drawable/descripcion.png differ diff --git a/app/src/main/res/drawable/edittext_background.xml b/app/src/main/res/drawable/edittext_background.xml new file mode 100644 index 0000000..78da34d --- /dev/null +++ b/app/src/main/res/drawable/edittext_background.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/form_card_background.xml b/app/src/main/res/drawable/form_card_background.xml new file mode 100644 index 0000000..b9dc37a --- /dev/null +++ b/app/src/main/res/drawable/form_card_background.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/goal_section_background.xml b/app/src/main/res/drawable/goal_section_background.xml new file mode 100644 index 0000000..32409bd --- /dev/null +++ b/app/src/main/res/drawable/goal_section_background.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/drawable/gradient_background_dark.xml b/app/src/main/res/drawable/gradient_background_dark.xml new file mode 100644 index 0000000..1cf2bdb --- /dev/null +++ b/app/src/main/res/drawable/gradient_background_dark.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_alimentacion.png b/app/src/main/res/drawable/ic_alimentacion.png new file mode 100644 index 0000000..7fc4323 Binary files /dev/null and b/app/src/main/res/drawable/ic_alimentacion.png differ diff --git a/app/src/main/res/drawable/ic_arrow_drop_down.xml b/app/src/main/res/drawable/ic_arrow_drop_down.xml new file mode 100644 index 0000000..e7fcde1 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_drop_down.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_arrow_forward.xml b/app/src/main/res/drawable/ic_arrow_forward.xml new file mode 100644 index 0000000..c4aa9ea --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_forward.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_cake.png b/app/src/main/res/drawable/ic_cake.png new file mode 100644 index 0000000..fd09298 Binary files /dev/null and b/app/src/main/res/drawable/ic_cake.png differ diff --git a/app/src/main/res/drawable/ic_calendar_today.xml b/app/src/main/res/drawable/ic_calendar_today.xml new file mode 100644 index 0000000..0aab453 --- /dev/null +++ b/app/src/main/res/drawable/ic_calendar_today.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_calentamiento.png b/app/src/main/res/drawable/ic_calentamiento.png new file mode 100644 index 0000000..320e7fd Binary files /dev/null and b/app/src/main/res/drawable/ic_calentamiento.png differ diff --git a/app/src/main/res/drawable/ic_cena.png b/app/src/main/res/drawable/ic_cena.png new file mode 100644 index 0000000..e289190 Binary files /dev/null and b/app/src/main/res/drawable/ic_cena.png differ diff --git a/app/src/main/res/drawable/ic_check_circle.xml b/app/src/main/res/drawable/ic_check_circle.xml new file mode 100644 index 0000000..f2132a7 --- /dev/null +++ b/app/src/main/res/drawable/ic_check_circle.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_comida.png b/app/src/main/res/drawable/ic_comida.png new file mode 100644 index 0000000..db60503 Binary files /dev/null and b/app/src/main/res/drawable/ic_comida.png differ diff --git a/app/src/main/res/drawable/ic_desayuno.png b/app/src/main/res/drawable/ic_desayuno.png new file mode 100644 index 0000000..4ebb064 Binary files /dev/null and b/app/src/main/res/drawable/ic_desayuno.png differ diff --git a/app/src/main/res/drawable/ic_enfriamiento.png b/app/src/main/res/drawable/ic_enfriamiento.png new file mode 100644 index 0000000..f70f746 Binary files /dev/null and b/app/src/main/res/drawable/ic_enfriamiento.png differ diff --git a/app/src/main/res/drawable/ic_entrenamiento.png b/app/src/main/res/drawable/ic_entrenamiento.png new file mode 100644 index 0000000..54d51d7 Binary files /dev/null and b/app/src/main/res/drawable/ic_entrenamiento.png differ diff --git a/app/src/main/res/drawable/ic_fitness_center.xml b/app/src/main/res/drawable/ic_fitness_center.xml new file mode 100644 index 0000000..6abd17a --- /dev/null +++ b/app/src/main/res/drawable/ic_fitness_center.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_height.xml b/app/src/main/res/drawable/ic_height.xml new file mode 100644 index 0000000..78ffc47 --- /dev/null +++ b/app/src/main/res/drawable/ic_height.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_home.xml b/app/src/main/res/drawable/ic_home.xml new file mode 100644 index 0000000..21deb28 --- /dev/null +++ b/app/src/main/res/drawable/ic_home.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_info.xml b/app/src/main/res/drawable/ic_info.xml new file mode 100644 index 0000000..e654b85 --- /dev/null +++ b/app/src/main/res/drawable/ic_info.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_lock.png b/app/src/main/res/drawable/ic_lock.png new file mode 100644 index 0000000..637e8a5 Binary files /dev/null and b/app/src/main/res/drawable/ic_lock.png differ diff --git a/app/src/main/res/drawable/ic_menu_camera.xml b/app/src/main/res/drawable/ic_menu_camera.xml new file mode 100644 index 0000000..634fe92 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_camera.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_menu_gallery.xml b/app/src/main/res/drawable/ic_menu_gallery.xml new file mode 100644 index 0000000..03c7709 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_gallery.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu_slideshow.xml b/app/src/main/res/drawable/ic_menu_slideshow.xml new file mode 100644 index 0000000..5e9e163 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_slideshow.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_person.png b/app/src/main/res/drawable/ic_person.png new file mode 100644 index 0000000..c38dc5e Binary files /dev/null and b/app/src/main/res/drawable/ic_person.png differ diff --git a/app/src/main/res/drawable/ic_principal.png b/app/src/main/res/drawable/ic_principal.png new file mode 100644 index 0000000..bbb23e9 Binary files /dev/null and b/app/src/main/res/drawable/ic_principal.png differ diff --git a/app/src/main/res/drawable/ic_restar.png b/app/src/main/res/drawable/ic_restar.png new file mode 100644 index 0000000..1e12d52 Binary files /dev/null and b/app/src/main/res/drawable/ic_restar.png differ diff --git a/app/src/main/res/drawable/ic_restaurante.png b/app/src/main/res/drawable/ic_restaurante.png new file mode 100644 index 0000000..3d5cfcf Binary files /dev/null and b/app/src/main/res/drawable/ic_restaurante.png differ diff --git a/app/src/main/res/drawable/ic_scale.xml b/app/src/main/res/drawable/ic_scale.xml new file mode 100644 index 0000000..01504c4 --- /dev/null +++ b/app/src/main/res/drawable/ic_scale.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_snack.png b/app/src/main/res/drawable/ic_snack.png new file mode 100644 index 0000000..611a8e0 Binary files /dev/null and b/app/src/main/res/drawable/ic_snack.png differ diff --git a/app/src/main/res/drawable/ic_star.png b/app/src/main/res/drawable/ic_star.png new file mode 100644 index 0000000..5cd8490 Binary files /dev/null and b/app/src/main/res/drawable/ic_star.png differ diff --git a/app/src/main/res/drawable/ic_terminal.xml b/app/src/main/res/drawable/ic_terminal.xml new file mode 100644 index 0000000..299539b --- /dev/null +++ b/app/src/main/res/drawable/ic_terminal.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_weight.xml b/app/src/main/res/drawable/ic_weight.xml new file mode 100644 index 0000000..1fe6500 --- /dev/null +++ b/app/src/main/res/drawable/ic_weight.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/image_overlay_gradient.xml b/app/src/main/res/drawable/image_overlay_gradient.xml new file mode 100644 index 0000000..afc5f25 --- /dev/null +++ b/app/src/main/res/drawable/image_overlay_gradient.xml @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/input_background.xml b/app/src/main/res/drawable/input_background.xml new file mode 100644 index 0000000..8f93e54 --- /dev/null +++ b/app/src/main/res/drawable/input_background.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/input_background_focused.xml b/app/src/main/res/drawable/input_background_focused.xml new file mode 100644 index 0000000..2a2f0c8 --- /dev/null +++ b/app/src/main/res/drawable/input_background_focused.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/log_header_background.xml b/app/src/main/res/drawable/log_header_background.xml new file mode 100644 index 0000000..b4b3241 --- /dev/null +++ b/app/src/main/res/drawable/log_header_background.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/login.PNG b/app/src/main/res/drawable/login.PNG new file mode 100644 index 0000000..2b1cd4b Binary files /dev/null and b/app/src/main/res/drawable/login.PNG differ diff --git a/app/src/main/res/drawable/logo.png b/app/src/main/res/drawable/logo.png new file mode 100644 index 0000000..a66f420 Binary files /dev/null and b/app/src/main/res/drawable/logo.png differ diff --git a/app/src/main/res/drawable/parametros.png b/app/src/main/res/drawable/parametros.png new file mode 100644 index 0000000..f717fe5 Binary files /dev/null and b/app/src/main/res/drawable/parametros.png differ diff --git a/app/src/main/res/drawable/progress_bar_background.xml b/app/src/main/res/drawable/progress_bar_background.xml new file mode 100644 index 0000000..3ade054 --- /dev/null +++ b/app/src/main/res/drawable/progress_bar_background.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/register.PNG b/app/src/main/res/drawable/register.PNG new file mode 100644 index 0000000..fa3a2fd Binary files /dev/null and b/app/src/main/res/drawable/register.PNG differ diff --git a/app/src/main/res/drawable/rounded_rect_blue.xml b/app/src/main/res/drawable/rounded_rect_blue.xml new file mode 100644 index 0000000..3882851 --- /dev/null +++ b/app/src/main/res/drawable/rounded_rect_blue.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rounded_rect_green.xml b/app/src/main/res/drawable/rounded_rect_green.xml new file mode 100644 index 0000000..94b2c3a --- /dev/null +++ b/app/src/main/res/drawable/rounded_rect_green.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rounded_rect_light_blue.xml b/app/src/main/res/drawable/rounded_rect_light_blue.xml new file mode 100644 index 0000000..f073208 --- /dev/null +++ b/app/src/main/res/drawable/rounded_rect_light_blue.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rounded_rect_light_green.xml b/app/src/main/res/drawable/rounded_rect_light_green.xml new file mode 100644 index 0000000..eb3a160 --- /dev/null +++ b/app/src/main/res/drawable/rounded_rect_light_green.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rounded_rect_light_orange.xml b/app/src/main/res/drawable/rounded_rect_light_orange.xml new file mode 100644 index 0000000..8250b8e --- /dev/null +++ b/app/src/main/res/drawable/rounded_rect_light_orange.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rounded_rect_orange.xml b/app/src/main/res/drawable/rounded_rect_orange.xml new file mode 100644 index 0000000..30456c9 --- /dev/null +++ b/app/src/main/res/drawable/rounded_rect_orange.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rounded_rect_yellow.xml b/app/src/main/res/drawable/rounded_rect_yellow.xml new file mode 100644 index 0000000..1f34f4d --- /dev/null +++ b/app/src/main/res/drawable/rounded_rect_yellow.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/side_nav_bar.xml b/app/src/main/res/drawable/side_nav_bar.xml new file mode 100644 index 0000000..5c13548 --- /dev/null +++ b/app/src/main/res/drawable/side_nav_bar.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/stat_item_background.xml b/app/src/main/res/drawable/stat_item_background.xml new file mode 100644 index 0000000..444bf4f --- /dev/null +++ b/app/src/main/res/drawable/stat_item_background.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/app/src/main/res/drawable/status_indicator_active.xml b/app/src/main/res/drawable/status_indicator_active.xml new file mode 100644 index 0000000..57eed69 --- /dev/null +++ b/app/src/main/res/drawable/status_indicator_active.xml @@ -0,0 +1,5 @@ + + + + diff --git a/app/src/main/res/drawable/timestamp_background.xml b/app/src/main/res/drawable/timestamp_background.xml new file mode 100644 index 0000000..cd9b09c --- /dev/null +++ b/app/src/main/res/drawable/timestamp_background.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/welcome.png b/app/src/main/res/drawable/welcome.png new file mode 100644 index 0000000..2c5863f Binary files /dev/null and b/app/src/main/res/drawable/welcome.png differ diff --git a/app/src/main/res/layout/activity_loging.xml b/app/src/main/res/layout/activity_loging.xml new file mode 100644 index 0000000..44af5ac --- /dev/null +++ b/app/src/main/res/layout/activity_loging.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..87c58f0 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,34 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_preguntas.xml b/app/src/main/res/layout/activity_preguntas.xml new file mode 100644 index 0000000..631cefa --- /dev/null +++ b/app/src/main/res/layout/activity_preguntas.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_register.xml b/app/src/main/res/layout/activity_register.xml new file mode 100644 index 0000000..24c315e --- /dev/null +++ b/app/src/main/res/layout/activity_register.xml @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_welcome.xml b/app/src/main/res/layout/activity_welcome.xml new file mode 100644 index 0000000..f15a0d0 --- /dev/null +++ b/app/src/main/res/layout/activity_welcome.xml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/app_bar_main.xml b/app/src/main/res/layout/app_bar_main.xml new file mode 100644 index 0000000..0936c45 --- /dev/null +++ b/app/src/main/res/layout/app_bar_main.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml new file mode 100644 index 0000000..6e0ea39 --- /dev/null +++ b/app/src/main/res/layout/content_main.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_plan_actualizado.xml b/app/src/main/res/layout/dialog_plan_actualizado.xml new file mode 100644 index 0000000..c20ffb3 --- /dev/null +++ b/app/src/main/res/layout/dialog_plan_actualizado.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + +