commit beafddb5760b9ef308fb77391dec2db82c7908ac Author: Tatiana Villa Ema Date: Fri May 1 23:30:59 2026 +0200 first commit 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..3c525bd --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +El Faro del Peregrino \ 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/appInsightsSettings.xml b/.idea/appInsightsSettings.xml new file mode 100644 index 0000000..371f2e2 --- /dev/null +++ b/.idea/appInsightsSettings.xml @@ -0,0 +1,26 @@ + + + + + + \ 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..ca16a99 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml new file mode 100644 index 0000000..91f9558 --- /dev/null +++ b/.idea/deviceManager.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..02c4aa5 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + + \ 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/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.kts b/app/build.gradle.kts new file mode 100644 index 0000000..41dfaac --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,50 @@ +plugins { + alias(libs.plugins.android.application) +} + +android { + namespace = "es.tatvil.elfarodelperegrino" + compileSdk { + version = release(36) { + minorApiLevel = 1 + } + } + + defaultConfig { + applicationId = "es.tatvil.elfarodelperegrino" + minSdk = 27 + targetSdk = 36 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + +dependencies { + implementation(libs.appcompat) + implementation(libs.material) + implementation(libs.activity) + implementation(libs.constraintlayout) + testImplementation(libs.junit) + androidTestImplementation(libs.ext.junit) + androidTestImplementation(libs.espresso.core) + implementation("com.google.android.gms:play-services-location:21.3.0") + implementation(libs.volley) + implementation(libs.glide) + annotationProcessor(libs.glide.compiler) +} \ 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/es/tatvil/elfarodelperegrino/ExampleInstrumentedTest.java b/app/src/androidTest/java/es/tatvil/elfarodelperegrino/ExampleInstrumentedTest.java new file mode 100644 index 0000000..acba532 --- /dev/null +++ b/app/src/androidTest/java/es/tatvil/elfarodelperegrino/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package es.tatvil.elfarodelperegrino; + +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("es.tatvil.elfarodelperegrino", 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..0621925 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/es/tatvil/elfarodelperegrino/Albergue.java b/app/src/main/java/es/tatvil/elfarodelperegrino/Albergue.java new file mode 100644 index 0000000..60a35df --- /dev/null +++ b/app/src/main/java/es/tatvil/elfarodelperegrino/Albergue.java @@ -0,0 +1,28 @@ +package es.tatvil.elfarodelperegrino; + +import java.io.Serializable; + +public class Albergue implements Serializable { + private String nombre, telefono, tipo, comentario; + private double latitud, longitud; + private boolean tieneCocina; + + public Albergue(String nombre, String telefono, String tipo, double lat, double lon, boolean cocina, String comentario) { + this.nombre = nombre; + this.telefono = telefono; + this.tipo = tipo; + this.latitud = lat; + this.longitud = lon; + this.tieneCocina = cocina; + this.comentario = comentario; + } + + // Getters + public String getNombre() { return nombre; } + public String getTelefono() { return telefono; } + public String getTipo() { return tipo; } + public double getLatitud() { return latitud; } + public double getLongitud() { return longitud; } + public boolean isTieneCocina() { return tieneCocina; } + public String getComentario() { return comentario; } +} \ No newline at end of file diff --git a/app/src/main/java/es/tatvil/elfarodelperegrino/AlbergueAdapter.java b/app/src/main/java/es/tatvil/elfarodelperegrino/AlbergueAdapter.java new file mode 100644 index 0000000..f72bda3 --- /dev/null +++ b/app/src/main/java/es/tatvil/elfarodelperegrino/AlbergueAdapter.java @@ -0,0 +1,61 @@ +package es.tatvil.elfarodelperegrino; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import java.util.List; +import java.util.Locale; + +public class AlbergueAdapter extends RecyclerView.Adapter { + + private List listaAlbergues; + private double etapaLat, etapaLon; + + public AlbergueAdapter(List listaAlbergues, double etapaLat, double etapaLon) { + this.listaAlbergues = listaAlbergues; + this.etapaLat = etapaLat; + this.etapaLon = etapaLon; + } + + @NonNull + @Override + public AlbergueViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_albergue, parent, false); + return new AlbergueViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull AlbergueViewHolder holder, int position) { + Albergue albergue = listaAlbergues.get(position); + holder.tvNombre.setText(albergue.getNombre()); + holder.tvTipo.setText("Tipo: " + albergue.getTipo()); + holder.tvTelefono.setText("Tel: " + albergue.getTelefono()); + + float[] results = new float[1]; + android.location.Location.distanceBetween(etapaLat, etapaLon, albergue.getLatitud(), albergue.getLongitud(), results); + float distanciaKm = results[0] / 1000; + + holder.tvDistancia.setText(String.format(Locale.getDefault(), "A %.1f km del destino", distanciaKm)); + } + + @Override + public int getItemCount() { + return listaAlbergues.size(); + } + + public static class AlbergueViewHolder extends RecyclerView.ViewHolder { + TextView tvNombre, tvTipo, tvDistancia, tvTelefono; + + public AlbergueViewHolder(@NonNull View itemView) { + super(itemView); + tvNombre = itemView.findViewById(R.id.tvAlbergueNombre); + tvTipo = itemView.findViewById(R.id.tvAlbergueTipo); + tvDistancia = itemView.findViewById(R.id.tvAlbergueDistancia); + tvTelefono = itemView.findViewById(R.id.tvAlbergueTelefono); + } + } +} diff --git a/app/src/main/java/es/tatvil/elfarodelperegrino/CaminoData.java b/app/src/main/java/es/tatvil/elfarodelperegrino/CaminoData.java new file mode 100644 index 0000000..cd893b5 --- /dev/null +++ b/app/src/main/java/es/tatvil/elfarodelperegrino/CaminoData.java @@ -0,0 +1,142 @@ +package es.tatvil.elfarodelperegrino; + +import android.content.Context; +import org.json.JSONArray; +import org.json.JSONObject; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +public class CaminoData { + + private static String loadJSONFromRaw(Context context, int resourceId) { + try { + InputStream is = context.getResources().openRawResource(resourceId); + int size = is.available(); + byte[] buffer = new byte[size]; + is.read(buffer); + is.close(); + return new String(buffer, StandardCharsets.UTF_8); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public static List getTodasLasEtapas(Context context) { + List lista = new ArrayList<>(); + String json = loadJSONFromRaw(context, R.raw.etapas); + if (json == null) return lista; + try { + JSONArray jsonArray = new JSONArray(json); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject obj = jsonArray.getJSONObject(i); + lista.add(new Etapa( + obj.getString("nombre"), + obj.optString("origen", ""), + obj.optString("destino", ""), + obj.optString("ruta", obj.optString("origen") + " - " + obj.optString("destino")), + obj.optString("camino", "Norte"), + obj.getDouble("distancia"), + obj.optInt("dificultad", 0), + obj.optString("reflexionEspiritual", obj.optString("reflexion", "")), + obj.optString("hitoHistorico", obj.optString("historia", "")), + obj.getDouble("latitud"), + obj.getDouble("longitud") + )); + } + } catch (Exception e) { + e.printStackTrace(); + } + return lista; + } + + public static List getEtapasPorCamino(Context context, String nombreCamino) { + List todas = getTodasLasEtapas(context); + List filtradas = new ArrayList<>(); + for (Etapa e : todas) { + if (e.getCamino().equalsIgnoreCase(nombreCamino)) { + filtradas.add(e); + } + } + return filtradas; + } + + public static List getAlbergues(Context context) { + List lista = new ArrayList<>(); + String json = loadJSONFromRaw(context, R.raw.albergues); + if (json == null) return lista; + try { + JSONArray jsonArray = new JSONArray(json); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject obj = jsonArray.getJSONObject(i); + + // Manejo de servicios para determinar si tiene cocina + boolean tieneCocina = false; + JSONArray servicios = obj.optJSONArray("servicios"); + if (servicios != null) { + for (int j = 0; j < servicios.length(); j++) { + if (servicios.getString(j).equalsIgnoreCase("cocina")) { + tieneCocina = true; + break; + } + } + } + + lista.add(new Albergue( + obj.getString("nombre"), + obj.optString("telefono", "Sin teléfono"), + obj.optString("tipo", "Albergue"), + obj.getDouble("latitud"), + obj.getDouble("longitud"), + tieneCocina, + obj.optString("comentarioEspiritual", "") + )); + } + } catch (Exception e) { + e.printStackTrace(); + } + return lista; + } + + public static List getIglesias(Context context) { + List lista = new ArrayList<>(); + String json = loadJSONFromRaw(context, R.raw.iglesias); + if (json == null) return lista; + try { + JSONArray jsonArray = new JSONArray(json); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject obj = jsonArray.getJSONObject(i); + lista.add(new Iglesia( + obj.getString("nombre"), + obj.getDouble("latitud"), + obj.getDouble("longitud") + )); + } + } catch (Exception e) { + e.printStackTrace(); + } + return lista; + } + + public static List getRestaurantes(Context context) { + List lista = new ArrayList<>(); + String json = loadJSONFromRaw(context, R.raw.restaurantes); + if (json == null) return lista; + try { + JSONArray jsonArray = new JSONArray(json); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject obj = jsonArray.getJSONObject(i); + lista.add(new Restaurante( + obj.getString("nombre"), + obj.getDouble("latitud"), + obj.getDouble("longitud") + )); + } + } catch (Exception e) { + e.printStackTrace(); + } + return lista; + } +} diff --git a/app/src/main/java/es/tatvil/elfarodelperegrino/DetalleEtapaActivity.java b/app/src/main/java/es/tatvil/elfarodelperegrino/DetalleEtapaActivity.java new file mode 100644 index 0000000..57ce0d1 --- /dev/null +++ b/app/src/main/java/es/tatvil/elfarodelperegrino/DetalleEtapaActivity.java @@ -0,0 +1,166 @@ +package es.tatvil.elfarodelperegrino; + +import android.location.Location; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.android.volley.Request; +import com.android.volley.RequestQueue; +import com.android.volley.toolbox.JsonObjectRequest; +import com.android.volley.toolbox.Volley; +import com.bumptech.glide.Glide; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +public class DetalleEtapaActivity extends AppCompatActivity { + + private static final String API_KEY = "69ef7f26726bba12b03c74b1e97b550f"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_detalle_etapa); + + Etapa etapa = (Etapa) getIntent().getSerializableExtra("etapa"); + + if (etapa != null) { + ImageView ivImagen = findViewById(R.id.ivDetalleImagen); + TextView tvTitulo = findViewById(R.id.tvDetalleTitulo); + TextView tvDistancia = findViewById(R.id.tvDetalleDistancia); + TextView tvHistoria = findViewById(R.id.tvDetalleHistoria); + TextView tvReflexion = findViewById(R.id.tvDetalleReflexion); + TextView tvClima = findViewById(R.id.tvDetalleClima); + + tvTitulo.setText(etapa.getOrigen() + " - " + etapa.getDestino()); + tvDistancia.setText("Distancia: " + etapa.getDistancia() + " km"); + tvHistoria.setText(etapa.getHitoHistorico()); + tvReflexion.setText(etapa.getReflexionEspiritual()); + + Glide.with(this) + .load(etapa.getImagenUrl()) + .placeholder(android.R.drawable.ic_menu_gallery) + .into(ivImagen); + + obtenerClima(etapa.getLatitud(), etapa.getLongitud(), tvClima); + + findViewById(R.id.btnVerClima).setOnClickListener(v -> + obtenerClima(etapa.getLatitud(), etapa.getLongitud(), tvClima)); + + // Botón Albergues + findViewById(R.id.btnVerAlbergues).setOnClickListener(v -> { + List cercanos = filtrarAlbergues(CaminoData.getAlbergues(this), etapa); + mostrarDialogoAlbergues("Albergues en " + etapa.getDestino(), cercanos, etapa); + }); + + // Botón Iglesias + findViewById(R.id.btnVerIglesias).setOnClickListener(v -> { + List todas = CaminoData.getIglesias(this); + List cercanas = new ArrayList<>(); + for(Iglesia i : todas) { + if (calcularDistancia(etapa.getLatitud(), etapa.getLongitud(), i.getLatitud(), i.getLongitud()) < 10) { + cercanas.add(i); + } + } + if (cercanas.isEmpty()) { + Toast.makeText(this, "No hay iglesias registradas cerca", Toast.LENGTH_SHORT).show(); + } else { + mostrarDialogoSimple("Iglesias cercanas", cercanas); + } + }); + + // Botón Restaurantes + findViewById(R.id.btnVerRestaurantes).setOnClickListener(v -> { + List todos = CaminoData.getRestaurantes(this); + List cercanos = new ArrayList<>(); + for(Restaurante r : todos) { + if (calcularDistancia(etapa.getLatitud(), etapa.getLongitud(), r.getLatitud(), r.getLongitud()) < 10) { + cercanos.add(r); + } + } + if (cercanos.isEmpty()) { + Toast.makeText(this, "No hay restaurantes registrados cerca", Toast.LENGTH_SHORT).show(); + } else { + mostrarDialogoSimple("Restaurantes cercanos", cercanos); + } + }); + } + } + + private List filtrarAlbergues(List todos, Etapa etapa) { + List filtrados = new ArrayList<>(); + for (Albergue a : todos) { + if (calcularDistancia(etapa.getLatitud(), etapa.getLongitud(), a.getLatitud(), a.getLongitud()) < 10) { + filtrados.add(a); + } + } + return filtrados; + } + + private void mostrarDialogoAlbergues(String titulo, List lista, Etapa etapa) { + if (lista.isEmpty()) { + Toast.makeText(this, "No hay albergues registrados cerca de " + etapa.getDestino(), Toast.LENGTH_SHORT).show(); + return; + } + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + View view = LayoutInflater.from(this).inflate(R.layout.dialog_lista_albergues, null); + RecyclerView rv = view.findViewById(R.id.rvDialogo); + rv.setLayoutManager(new LinearLayoutManager(this)); + rv.setAdapter(new AlbergueAdapter(lista, etapa.getLatitud(), etapa.getLongitud())); + + builder.setTitle(titulo) + .setView(view) + .setPositiveButton("Cerrar", null) + .show(); + } + + private void mostrarDialogoSimple(String titulo, List items) { + StringBuilder sb = new StringBuilder(); + for (Object item : items) { + if (item instanceof Iglesia) sb.append("• ").append(((Iglesia) item).getNombre()).append("\n"); + if (item instanceof Restaurante) sb.append("• ").append(((Restaurante) item).getNombre()).append("\n"); + } + + new AlertDialog.Builder(this) + .setTitle(titulo) + .setMessage(sb.toString()) + .setPositiveButton("OK", null) + .show(); + } + + private double calcularDistancia(double lat1, double lon1, double lat2, double lon2) { + float[] results = new float[1]; + Location.distanceBetween(lat1, lon1, lat2, lon2, results); + return results[0] / 1000; // Retorna km + } + + private void obtenerClima(double lat, double lon, TextView tvClima) { + String url = "https://api.openweathermap.org/data/2.5/weather?lat=" + lat + "&lon=" + lon + "&appid=" + API_KEY + "&units=metric&lang=es"; + RequestQueue queue = Volley.newRequestQueue(this); + JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, url, null, + response -> { + try { + JSONObject main = response.getJSONObject("main"); + double temp = main.getDouble("temp"); + String desc = response.getJSONArray("weather").getJSONObject(0).getString("description"); + tvClima.setText("Clima actual: " + temp + "°C, " + desc); + } catch (JSONException e) { + tvClima.setText("Error al procesar clima"); + } + }, + error -> tvClima.setText("Error al obtener clima")); + queue.add(request); + } +} diff --git a/app/src/main/java/es/tatvil/elfarodelperegrino/Etapa.java b/app/src/main/java/es/tatvil/elfarodelperegrino/Etapa.java new file mode 100644 index 0000000..90ed31f --- /dev/null +++ b/app/src/main/java/es/tatvil/elfarodelperegrino/Etapa.java @@ -0,0 +1,62 @@ +package es.tatvil.elfarodelperegrino; + +import java.io.Serializable; + +public class Etapa implements Serializable { + private String nombre; + private String origen; + private String destino; + private String ruta; + private String camino; + private double distancia; + private int dificultad; + private String reflexionEspiritual; + private String hitoHistorico; + private double latitud, longitud; + private String imagenUrl; + + public Etapa(String nombre, String origen, String destino, String ruta, String camino, double distancia, + int dificultad, String reflexionEspiritual, String hitoHistorico, + double latitud, double longitud) { + this.nombre = nombre; + this.origen = origen; + this.destino = destino; + this.ruta = ruta; + this.camino = camino; + this.distancia = distancia; + this.dificultad = dificultad; + this.reflexionEspiritual = reflexionEspiritual; + this.hitoHistorico = hitoHistorico; + this.latitud = latitud; + this.longitud = longitud; + this.imagenUrl = "https://picsum.photos/seed/" + nombre.replace(" ", "") + "/800/400"; + } + + // Getters + public String getImagenUrl() { return imagenUrl; } + public void setImagenUrl(String imagenUrl) { this.imagenUrl = imagenUrl; } + public String getNombre() { return nombre; } + public String getOrigen() { return origen; } + public String getDestino() { return destino; } + public String getRuta() { return ruta; } + public String getCamino() { return camino; } + public double getDistancia() { return distancia; } + public int getDificultad() { return dificultad; } + public String getReflexionEspiritual() { return reflexionEspiritual; } + public String getHitoHistorico() { return hitoHistorico; } + public double getLatitud() { return latitud; } + public double getLongitud() { return longitud; } + + // Setters + public void setNombre(String nombre) { this.nombre = nombre; } + public void setOrigen(String origen) { this.origen = origen; } + public void setDestino(String destino) { this.destino = destino; } + public void setRuta(String ruta) { this.ruta = ruta; } + public void setCamino(String camino) { this.camino = camino; } + public void setDistancia(double distancia) { this.distancia = distancia; } + public void setDificultad(int dificultad) { this.dificultad = dificultad; } + public void setReflexionEspiritual(String reflexionEspiritual) { this.reflexionEspiritual = reflexionEspiritual; } + public void setHitoHistorico(String hitoHistorico) { this.hitoHistorico = hitoHistorico; } + public void setLatitud(double latitud) { this.latitud = latitud; } + public void setLongitud(double longitud) { this.longitud = longitud; } +} diff --git a/app/src/main/java/es/tatvil/elfarodelperegrino/EtapaAdapter.java b/app/src/main/java/es/tatvil/elfarodelperegrino/EtapaAdapter.java new file mode 100644 index 0000000..ecd0597 --- /dev/null +++ b/app/src/main/java/es/tatvil/elfarodelperegrino/EtapaAdapter.java @@ -0,0 +1,66 @@ +package es.tatvil.elfarodelperegrino; + +import android.content.Intent; +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.bumptech.glide.Glide; +import java.util.List; + +public class EtapaAdapter extends RecyclerView.Adapter { + + private List listaEtapas; + + public EtapaAdapter(List listaEtapas) { + this.listaEtapas = listaEtapas; + } + + @NonNull + @Override + public EtapaViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_etapa, parent, false); + return new EtapaViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull EtapaViewHolder holder, int position) { + Etapa etapa = listaEtapas.get(position); + String titulo = (position + 1) + ". " + etapa.getOrigen() + " - " + etapa.getDestino(); + holder.tvNombre.setText(titulo); + holder.tvDistancia.setText(etapa.getDistancia() + " km"); + + Glide.with(holder.itemView.getContext()) + .load(etapa.getImagenUrl()) + .placeholder(android.R.drawable.ic_menu_gallery) + .into(holder.ivMiniatura); + + holder.itemView.setOnClickListener(v -> { + Intent intent = new Intent(v.getContext(), DetalleEtapaActivity.class); + intent.putExtra("etapa", etapa); + v.getContext().startActivity(intent); + }); + } + + @Override + public int getItemCount() { + return listaEtapas.size(); + } + + // El ViewHolder es el "molde" para los elementos de la vista + public static class EtapaViewHolder extends RecyclerView.ViewHolder { + TextView tvNombre, tvDistancia; + ImageView ivMiniatura; + + public EtapaViewHolder(@NonNull View itemView) { + super(itemView); + tvNombre = itemView.findViewById(R.id.tvNombreEtapa); + tvDistancia = itemView.findViewById(R.id.tvDistancia); + ivMiniatura = itemView.findViewById(R.id.ivEtapaMiniatura); + } + } +} diff --git a/app/src/main/java/es/tatvil/elfarodelperegrino/Iglesia.java b/app/src/main/java/es/tatvil/elfarodelperegrino/Iglesia.java new file mode 100644 index 0000000..825d14b --- /dev/null +++ b/app/src/main/java/es/tatvil/elfarodelperegrino/Iglesia.java @@ -0,0 +1,18 @@ +package es.tatvil.elfarodelperegrino; + +import java.io.Serializable; + +public class Iglesia implements Serializable { + private String nombre; + private double latitud, longitud; + + public Iglesia(String nombre, double latitud, double longitud) { + this.nombre = nombre; + this.latitud = latitud; + this.longitud = longitud; + } + + public String getNombre() { return nombre; } + public double getLatitud() { return latitud; } + public double getLongitud() { return longitud; } +} diff --git a/app/src/main/java/es/tatvil/elfarodelperegrino/MainActivity.java b/app/src/main/java/es/tatvil/elfarodelperegrino/MainActivity.java new file mode 100644 index 0000000..e13c801 --- /dev/null +++ b/app/src/main/java/es/tatvil/elfarodelperegrino/MainActivity.java @@ -0,0 +1,174 @@ +package es.tatvil.elfarodelperegrino; + +import android.Manifest; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Spinner; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.gms.location.FusedLocationProviderClient; +import com.google.android.gms.location.LocationServices; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +public class MainActivity extends AppCompatActivity { + + private FusedLocationProviderClient fusedLocationClient; + + @Override + protected void onCreate(Bundle savedInstanceState) { + // 1. SIEMPRE debes llamar al super y establecer la vista + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); // Esto vincula el XML activity_main + + // 2. Ahora sí, inicializamos el RecyclerView después de que la vista exista + RecyclerView recyclerView = findViewById(R.id.recyclerViewEtapas); + + // 3. Configuración del LayoutManager + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + + // 4. Configuración del Spinner para seleccionar el camino + Spinner spinner = findViewById(R.id.spinnerCaminos); + String[] opcionesCaminos = {"Norte", "Francés"}; + + ArrayAdapter spinnerAdapter = new ArrayAdapter<>(this, + android.R.layout.simple_spinner_item, opcionesCaminos); + spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(spinnerAdapter); + + spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + String caminoSeleccionado = opcionesCaminos[position]; + actualizarListaEtapas(caminoSeleccionado, recyclerView); + } + + @Override + public void onNothingSelected(AdapterView parent) { + } + }); + + fusedLocationClient = LocationServices.getFusedLocationProviderClient(this); + obtenerUbicacionActual(); + } + + private void actualizarListaEtapas(String camino, RecyclerView recyclerView) { + List etapas = CaminoData.getEtapasPorCamino(this, camino); + EtapaAdapter adapter = new EtapaAdapter(etapas); + recyclerView.setAdapter(adapter); + } + + private void obtenerUbicacionActual() { + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { + fusedLocationClient.getLastLocation().addOnSuccessListener(this, location -> { + if (location != null) { + double miLat = location.getLatitude(); + double miLon = location.getLongitude(); + // Aquí se compararía con las etapas + } + }); + } else { + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == 1) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + obtenerUbicacionActual(); + } + } + } + + private List cargarEtapasDesdeJSON() { + List lista = new ArrayList<>(); + try { + InputStream is = getResources().openRawResource(R.raw.etapas); + byte[] buffer = new byte[is.available()]; + is.read(buffer); + is.close(); + String json = new String(buffer, "UTF-8"); + + JSONArray jsonArray = new JSONArray(json); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject obj = jsonArray.getJSONObject(i); + lista.add(new Etapa( + obj.getString("nombre"), + obj.optString("origen", ""), + obj.optString("destino", ""), + obj.optString("ruta", obj.optString("origen") + " - " + obj.optString("destino")), + obj.optString("camino", "Norte"), + obj.getDouble("distancia"), + obj.optInt("dificultad", 0), + obj.optString("reflexionEspiritual", obj.optString("reflexion", "")), + obj.optString("hitoHistorico", obj.optString("historia", "")), + obj.getDouble("latitud"), + obj.getDouble("longitud") + )); + } + } catch (Exception e) { + e.printStackTrace(); + } + return lista; + } + + private List leerEtapasDesdeRaw() { + List lista = new ArrayList<>(); + try { + // Abrimos el archivo etapas.json que debes crear en res/raw + InputStream is = getResources().openRawResource(R.raw.etapas); + int size = is.available(); + byte[] buffer = new byte[size]; + is.read(buffer); + is.close(); + + String jsonString = new String(buffer, "UTF-8"); + JSONArray jsonArray = new JSONArray(jsonString); + + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject obj = jsonArray.getJSONObject(i); + lista.add(new Etapa( + obj.getString("nombre"), + obj.optString("origen", ""), + obj.optString("destino", ""), + obj.optString("ruta", obj.optString("origen") + " - " + obj.optString("destino")), + obj.optString("camino", "Norte"), + obj.getDouble("distancia"), + obj.optInt("dificultad", 0), + obj.optString("reflexionEspiritual", obj.optString("reflexion", "")), + obj.optString("hitoHistorico", obj.optString("historia", "")), + obj.getDouble("latitud"), + obj.getDouble("longitud") + )); + } + } catch (Exception e) { + e.printStackTrace(); + } + return lista; + } + + // Método para calcular distancia entre dos puntos (Fórmula de Haversine simplificada) + public double calcularDistancia(double lat1, double lon1, double lat2, double lon2) { + double theta = lon1 - lon2; + double dist = Math.sin(Math.toRadians(lat1)) * Math.sin(Math.toRadians(lat2)) + + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * + Math.cos(Math.toRadians(theta)); + dist = Math.acos(dist); + dist = Math.toDegrees(dist); + return dist * 60 * 1.1515 * 1.609344; // Retorna Kilómetros + } +} \ No newline at end of file diff --git a/app/src/main/java/es/tatvil/elfarodelperegrino/Restaurante.java b/app/src/main/java/es/tatvil/elfarodelperegrino/Restaurante.java new file mode 100644 index 0000000..d7eaa9a --- /dev/null +++ b/app/src/main/java/es/tatvil/elfarodelperegrino/Restaurante.java @@ -0,0 +1,18 @@ +package es.tatvil.elfarodelperegrino; + +import java.io.Serializable; + +public class Restaurante implements Serializable { + private String nombre; + private double latitud, longitud; + + public Restaurante(String nombre, double latitud, double longitud) { + this.nombre = nombre; + this.latitud = latitud; + this.longitud = longitud; + } + + public String getNombre() { return nombre; } + public double getLatitud() { return latitud; } + public double getLongitud() { return longitud; } +} 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/layout/activity_detalle_etapa.xml b/app/src/main/res/layout/activity_detalle_etapa.xml new file mode 100644 index 0000000..e1469b3 --- /dev/null +++ b/app/src/main/res/layout/activity_detalle_etapa.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + +