Tesis 1.0.0
Loading...
Searching...
No Matches
ProfileFragment.java
Go to the documentation of this file.
1package com.example.food_front;
2
3import android.app.Activity;
4import android.app.ProgressDialog;
5import android.content.DialogInterface;
6import android.content.Intent;
7import android.graphics.Color;
8import android.graphics.Typeface;
9import android.net.Uri;
10import android.os.Bundle;
11import android.text.InputType;
12import android.view.Gravity;
13import androidx.annotation.NonNull;
14import androidx.annotation.Nullable;
15import androidx.appcompat.app.AlertDialog;
16import androidx.cardview.widget.CardView;
17import androidx.fragment.app.Fragment;
18import androidx.fragment.app.FragmentManager;
19import androidx.fragment.app.FragmentTransaction;
20
21import android.provider.MediaStore;
22import android.util.Log;
23import android.view.LayoutInflater;
24import android.view.View;
25import android.view.ViewGroup;
26import android.widget.Button;
27import android.widget.EditText;
28import android.widget.LinearLayout;
29import android.widget.ScrollView;
30import android.widget.TextView;
31import android.widget.Toast;
32
33import com.android.volley.AuthFailureError;
34import com.android.volley.NetworkResponse;
35import com.android.volley.Request;
36import com.android.volley.RequestQueue;
37import com.android.volley.Response;
38import com.android.volley.VolleyError;
39import com.android.volley.toolbox.JsonObjectRequest;
40import com.android.volley.toolbox.Volley;
41import com.bumptech.glide.Glide;
42import com.example.food_front.utils.ProfileManager;
43import com.example.food_front.utils.SessionManager;
44import com.example.food_front.utils.VolleyMultipartRequest;
45
46import org.json.JSONException;
47import org.json.JSONObject;
48
49import java.io.ByteArrayOutputStream;
50import java.io.FileNotFoundException;
51import java.io.IOException;
52import java.io.InputStream;
53import java.util.HashMap;
54import java.util.Map;
55
56import de.hdodenhof.circleimageview.CircleImageView;
57
58public class ProfileFragment extends Fragment {
59
60 private static final int PICK_IMAGE_REQUEST = 1;
61 private TextView tvNombre, tvEmail;
62 private CircleImageView profileImage;
63 private ProfileManager profileManager;
64 private SessionManager sessionManager;
65
66 public ProfileFragment() {
67 // Required empty public constructor
68 }
69
70 @Nullable
71 @Override
72 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
73 View view = inflater.inflate(R.layout.fragment_profile, container, false);
74
75 // Inicializar las vistas
76 tvNombre = view.findViewById(R.id.user_name);
77 tvEmail = view.findViewById(R.id.user_email);
78 profileImage = view.findViewById(R.id.profile_image);
79
80 profileManager = new ProfileManager(requireContext());
81 sessionManager = new SessionManager(requireContext());
82
83 // Llamar al backend para obtener los datos del perfil
84 displayUserProfile();
85
86 // Hacer la imagen de perfil clickable
87 profileImage.setOnClickListener(v -> selectImage());
88
89 // Encontrar el TextView de "Datos personales"
90 TextView personalData = view.findViewById(R.id.personal_data);
91
92 // Añadir el listener de clic para "Datos personales"
93 personalData.setOnClickListener(v -> {
94 // Navegar al fragmento de datos personales
95 PersonalDataFragment personalDataFragment = new PersonalDataFragment();
96 getParentFragmentManager().beginTransaction()
97 .replace(R.id.fragment_container_view, personalDataFragment)
98 .addToBackStack(null)
99 .commit();
100 });
101
102 // Encontrar el TextView de "Cerrar sesion"
103 TextView closeSession = view.findViewById(R.id.logout);
104
105 // Añadir el listener de clic para "Cerrar sesion"
106 closeSession.setOnClickListener(v -> {
107 // Mostrar un diálogo de confirmación antes de cerrar la sesión
108 new AlertDialog.Builder(requireContext())
109 .setTitle("Cerrar sesión")
110 .setMessage("¿Estás seguro de que deseas cerrar tu sesión?")
111 .setPositiveButton("Sí", (dialog, which) -> {
112 // Cerrar la sesión
113 sessionManager.logout();
114
115 // Mostrar mensaje
116 Toast.makeText(requireContext(), "Sesión cerrada exitosamente", Toast.LENGTH_SHORT).show();
117
118 // Redirigir a la pantalla principal donde se mostrará el LoginFragment
119 Intent intent = new Intent(requireActivity(), MainActivity.class);
120 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
121 startActivity(intent);
122 requireActivity().finish();
123 })
124 .setNegativeButton("No", null)
125 .show();
126 });
127
128 // Encontrar el TextView de "Mis pedidos"
129 TextView viewOrders = view.findViewById(R.id.view_orders);
130 viewOrders.setOnClickListener(v -> {
131 String url = "https://backmobile1.onrender.com/appCART/ver_dashboard/";
132 RequestQueue queue = Volley.newRequestQueue(requireContext());
133 JsonObjectRequest request = new JsonObjectRequest(Request.Method.GET, url, null,
134 response -> {
135 try {
136 Log.d("DASHBOARD", "Respuesta cruda: " + response.toString());
137 if (response.has("results")) {
138 org.json.JSONArray pedidos = response.getJSONArray("results");
139 Log.d("DASHBOARD", "Pedidos recibidos: " + pedidos.length());
140 if (pedidos.length() == 0) {
141 mostrarMensajeNoPedidos();
142 } else {
143 mostrarDialogoConPedidos(pedidos);
144 }
145 } else {
146 Log.e("DASHBOARD", "No hay campo 'results' en la respuesta: " + response.toString());
147 mostrarMensajeSinResultados(response);
148 }
149 } catch (Exception e) {
150 Log.e("DASHBOARD", "Error parseando respuesta: " + Log.getStackTraceString(e));
151 mostrarMensajeError(e, response);
152 }
153 },
154 error -> {
155 String msg = "Error al obtener pedidos";
156 if (error.networkResponse != null) {
157 msg += ". Código: " + error.networkResponse.statusCode;
158 try {
159 String body = new String(error.networkResponse.data, "UTF-8");
160 Log.e("DASHBOARD", "Respuesta error: " + body);
161 msg += "\n" + body;
162 } catch (Exception ex) {
163 Log.e("DASHBOARD", "Error leyendo body de error", ex);
164 }
165 } else {
166 Log.e("DASHBOARD", "Volley error sin networkResponse", error);
167 }
168 Toast.makeText(requireContext(), msg, Toast.LENGTH_LONG).show();
169 }
170 ) {
171 @Override
172 public Map<String, String> getHeaders() throws AuthFailureError {
173 Map<String, String> headers = new HashMap<>();
174 String token = sessionManager.getToken();
175 if (token != null) {
176 headers.put("Authorization", "Bearer " + token);
177 }
178 return headers;
179 }
180 };
181 queue.add(request);
182 // Aumentar el timeout solo para esta petición
183 request.setRetryPolicy(new com.android.volley.DefaultRetryPolicy(
184 60000, // 60 segundos de timeout
185 1, // 1 reintento
186 1.5f // backoff multiplier
187 ));
188 });
189
190 // Encontrar el TextView de "Información legal"
191 TextView legalInfo = view.findViewById(R.id.legal_info);
192 legalInfo.setOnClickListener(v -> {
193 // Navegar al fragmento del EULA
194 EulaFragment eulaFragment = new EulaFragment();
195 getParentFragmentManager().beginTransaction()
196 .replace(R.id.fragment_container_view, eulaFragment)
197 .addToBackStack(null)
198 .commit();
199 });
200
201 // Botón Eliminar cuenta
202 Button btnDeleteAccount = view.findViewById(R.id.btn_delete_account);
203 btnDeleteAccount.setOnClickListener(v -> {
204 new AlertDialog.Builder(requireContext())
205 .setTitle("Eliminar cuenta")
206 .setMessage("¿Estás seguro de que deseas eliminar tu cuenta? Esta acción no se puede deshacer.")
207 .setPositiveButton("Sí, eliminar", (dialog, which) -> eliminarCuenta())
208 .setNegativeButton("Cancelar", null)
209 .show();
210 });
211
212 return view;
213 }
214
218 private void mostrarMensajeNoPedidos() {
219 new AlertDialog.Builder(requireContext())
220 .setTitle("Mis pedidos")
221 .setMessage("No tienes pedidos registrados.")
222 .setPositiveButton("OK", null)
223 .show();
224 }
225
229 private void mostrarMensajeSinResultados(JSONObject response) {
230 new AlertDialog.Builder(requireContext())
231 .setTitle("Sin resultados")
232 .setMessage("No se encontraron pedidos.\n\nRespuesta del servidor: " + response.toString())
233 .setPositiveButton("OK", null)
234 .show();
235 }
236
240 private void mostrarMensajeError(Exception e, JSONObject response) {
241 new AlertDialog.Builder(requireContext())
242 .setTitle("Error")
243 .setMessage("Ocurrió un problema al procesar los pedidos: " + e.getMessage() +
244 "\n\nRespuesta: " + response.toString())
245 .setPositiveButton("OK", null)
246 .show();
247 }
248
252 private void mostrarDialogoConPedidos(org.json.JSONArray pedidos) throws JSONException {
253 // Crear un layout vertical para contener la lista de pedidos
254 LinearLayout contenedorPrincipal = new LinearLayout(requireContext());
255 contenedorPrincipal.setOrientation(LinearLayout.VERTICAL);
256 contenedorPrincipal.setLayoutParams(new LinearLayout.LayoutParams(
257 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
258 contenedorPrincipal.setPadding(32, 32, 32, 32);
259
260 // ScrollView para hacer desplazable la lista
261 ScrollView scrollView = new ScrollView(requireContext());
262 scrollView.setLayoutParams(new ViewGroup.LayoutParams(
263 ViewGroup.LayoutParams.MATCH_PARENT,
264 (int) (350 * getResources().getDisplayMetrics().density)));
265
266 // Crear un LinearLayout para los pedidos dentro del ScrollView
267 LinearLayout contenedorPedidos = new LinearLayout(requireContext());
268 contenedorPedidos.setOrientation(LinearLayout.VERTICAL);
269 contenedorPedidos.setLayoutParams(new LinearLayout.LayoutParams(
270 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
271
272 // Procesar cada pedido y mostrar en el contenedor
273 for (int i = 0; i < pedidos.length(); i++) {
274 org.json.JSONObject pedido = pedidos.getJSONObject(i);
275
276 // Usar id_pedidos si está disponible, o el índice + 1 como respaldo
277 int numeroPedido = pedido.has("id_pedidos") ?
278 pedido.getInt("id_pedidos") : (i + 1);
279
280 // Crear un CardView para cada pedido
281 CardView cardPedido = new CardView(requireContext());
282 cardPedido.setLayoutParams(new CardView.LayoutParams(
283 CardView.LayoutParams.MATCH_PARENT, CardView.LayoutParams.WRAP_CONTENT));
284 cardPedido.setRadius(16);
285 cardPedido.setCardElevation(8);
286 cardPedido.setCardBackgroundColor(Color.parseColor("#FFFFFF"));
287 cardPedido.setUseCompatPadding(true);
288
289 // Layout vertical para el contenido de la tarjeta
290 LinearLayout contenidoCard = new LinearLayout(requireContext());
291 contenidoCard.setOrientation(LinearLayout.VERTICAL);
292 contenidoCard.setLayoutParams(new LinearLayout.LayoutParams(
293 LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
294 contenidoCard.setPadding(16, 16, 16, 16);
295
296 // Título del pedido
297 TextView tvTituloPedido = new TextView(requireContext());
298 tvTituloPedido.setText("\uD83D\uDCC5 Pedido #" + numeroPedido);
299 tvTituloPedido.setTextSize(18);
300 tvTituloPedido.setTypeface(Typeface.DEFAULT_BOLD);
301 tvTituloPedido.setLayoutParams(new LinearLayout.LayoutParams(
302 LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
303 tvTituloPedido.setPadding(0, 0, 0, 8);
304 contenidoCard.addView(tvTituloPedido);
305
306 // Fecha del pedido
307 if (pedido.has("fecha_pedido")) {
308 TextView tvFecha = new TextView(requireContext());
309 tvFecha.setText("Fecha: " + pedido.getString("fecha_pedido"));
310 tvFecha.setLayoutParams(new LinearLayout.LayoutParams(
311 LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
312 tvFecha.setPadding(16, 4, 0, 4);
313 contenidoCard.addView(tvFecha);
314 }
315
316 // Estado del pedido
317 if (pedido.has("estado")) {
318 TextView tvEstado = new TextView(requireContext());
319 tvEstado.setText("Estado: " + pedido.getString("estado"));
320 tvEstado.setLayoutParams(new LinearLayout.LayoutParams(
321 LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
322 tvEstado.setPadding(16, 4, 0, 4);
323 contenidoCard.addView(tvEstado);
324 }
325
326
327
328
329
330 // Layout horizontal para dirección y botón
331 LinearLayout layoutDireccion = new LinearLayout(requireContext());
332 layoutDireccion.setOrientation(LinearLayout.HORIZONTAL);
333 layoutDireccion.setLayoutParams(new LinearLayout.LayoutParams(
334 LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
335
336
337
338
339
340 // ID de pedido para la actualización
341 final int pedidoId = numeroPedido;
342
343
344
345
346 // Detalles del pedido (productos)
347 if (pedido.has("detalles")) {
348 org.json.JSONArray detalles = pedido.getJSONArray("detalles");
349 if (detalles.length() > 0) {
350 TextView tvProductosHeader = new TextView(requireContext());
351 tvProductosHeader.setText("Productos:");
352 tvProductosHeader.setTypeface(Typeface.DEFAULT_BOLD);
353 tvProductosHeader.setLayoutParams(new LinearLayout.LayoutParams(
354 LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
355 tvProductosHeader.setPadding(16, 8, 0, 4);
356 contenidoCard.addView(tvProductosHeader);
357
358 for (int j = 0; j < detalles.length(); j++) {
359 org.json.JSONObject detalle = detalles.getJSONObject(j);
360 TextView tvProducto = new TextView(requireContext());
361 StringBuilder productoInfo = new StringBuilder();
362 productoInfo.append("- Cantidad: ").append(detalle.optInt("cantidad_productos", 0));
363 productoInfo.append(", Precio: $").append(detalle.optDouble("precio_producto", 0));
364 productoInfo.append(", Subtotal: $").append(detalle.optDouble("subtotal", 0));
365
366 tvProducto.setText(productoInfo.toString());
367 tvProducto.setLayoutParams(new LinearLayout.LayoutParams(
368 LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
369 tvProducto.setPadding(32, 2, 0, 2);
370 contenidoCard.addView(tvProducto);
371 }
372 }
373 }
374
375 // Agregar el contenido a la tarjeta
376 cardPedido.addView(contenidoCard);
377
378 // Agregar la tarjeta al contenedor principal
379 contenedorPedidos.addView(cardPedido);
380 }
381
382 // Agregar el contenedor de pedidos al ScrollView
383 scrollView.addView(contenedorPedidos);
384
385 // Agregar el ScrollView al contenedor principal
386 contenedorPrincipal.addView(scrollView);
387
388 // Mostrar el diálogo con todos los pedidos
389 new AlertDialog.Builder(requireContext())
390 .setTitle("Mis pedidos")
391 .setView(contenedorPrincipal)
392 .setPositiveButton("Cerrar", null)
393 .show();
394 }
395
402 private void mostrarDialogoEditarDireccion(int pedidoId, String direccionActual, TextView tvDireccion) {
403 // Crear un EditText para la nueva dirección
404 final EditText input = new EditText(requireContext());
405 input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
406 input.setText(direccionActual);
407 input.setSingleLine(false);
408 input.setLines(3);
409 input.setMaxLines(5);
410 input.setGravity(Gravity.TOP | Gravity.START);
411
412 // Crear y mostrar el diálogo
413 new AlertDialog.Builder(requireContext())
414 .setTitle("Editar dirección de entrega")
415 .setView(input)
416 .setPositiveButton("Guardar", (dialog, which) -> {
417 String nuevaDireccion = input.getText().toString().trim();
418 if (!nuevaDireccion.isEmpty()) {
419 // Actualizar la dirección en el backend
420 actualizarDireccionEnBackend(pedidoId, nuevaDireccion, tvDireccion);
421 } else {
422 Toast.makeText(requireContext(), "La dirección no puede estar vacía", Toast.LENGTH_SHORT).show();
423 }
424 })
425 .setNegativeButton("Cancelar", null)
426 .show();
427 }
428
435 private void actualizarDireccionEnBackend(int pedidoId, String nuevaDireccion, TextView tvDireccion) {
436 // URL para actualizar la dirección de entrega
437 String url = "https://backmobile1.onrender.com/appCART/actualizar_direccion/";
438
439 // Mostrar progreso
440 ProgressDialog progressDialog = new ProgressDialog(requireContext());
441 progressDialog.setMessage("Actualizando dirección...");
442 progressDialog.setCancelable(false);
443 progressDialog.show();
444
445 // Crear el cuerpo de la solicitud
446 JSONObject requestBody = new JSONObject();
447 try {
448 requestBody.put("id_pedidos", pedidoId);
449 requestBody.put("direccion_entrega", nuevaDireccion);
450 } catch (JSONException e) {
451 progressDialog.dismiss();
452 Toast.makeText(requireContext(), "Error al preparar los datos", Toast.LENGTH_SHORT).show();
453 return;
454 }
455
456 // Crear la solicitud
457 JsonObjectRequest request = new JsonObjectRequest(
458 Request.Method.PUT,
459 url,
460 requestBody,
461 response -> {
462 progressDialog.dismiss();
463 try {
464 boolean success = response.optBoolean("success", true);
465 if (success) {
466 // Actualizar la UI con la nueva dirección
467 tvDireccion.setText("Dirección: " + nuevaDireccion);
468 Toast.makeText(requireContext(), "Dirección actualizada correctamente", Toast.LENGTH_SHORT).show();
469 } else {
470 String message = response.optString("message", "Error desconocido");
471 Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show();
472 }
473 } catch (Exception e) {
474 Toast.makeText(requireContext(), "Error al procesar la respuesta", Toast.LENGTH_SHORT).show();
475 }
476 },
477 error -> {
478 progressDialog.dismiss();
479
480 String mensaje = "Error al actualizar la dirección";
481 if (error.networkResponse != null) {
482 mensaje += " (Código: " + error.networkResponse.statusCode + ")";
483 try {
484 String responseBody = new String(error.networkResponse.data, "utf-8");
485 JSONObject errorObj = new JSONObject(responseBody);
486 if (errorObj.has("message")) {
487 mensaje += ": " + errorObj.getString("message");
488 }
489 } catch (Exception e) {
490 // Ignorar errores al leer el mensaje
491 }
492 }
493
494 Toast.makeText(requireContext(), mensaje, Toast.LENGTH_SHORT).show();
495 }
496 ) {
497 @Override
498 public Map<String, String> getHeaders() throws AuthFailureError {
499 Map<String, String> headers = new HashMap<>();
500 String token = sessionManager.getToken();
501 if (token != null) {
502 headers.put("Authorization", "Bearer " + token);
503 }
504 headers.put("Content-Type", "application/json");
505 return headers;
506 }
507 };
508
509 // Agregar la solicitud a la cola
510 RequestQueue requestQueue = Volley.newRequestQueue(requireContext());
511 requestQueue.add(request);
512 }
513
514 private void displayUserProfile() {
515 // Usar los métodos específicos para obtener los datos
516 String name = profileManager.getName();
517 String surname = profileManager.getSurname();
518 String email = profileManager.getEmail();
519 String imageUrl = profileManager.getProfileImageUrl();
520
521 // Mostrar los datos en los TextViews
522 tvNombre.setText(name + " " + surname); // Mostrar nombre completo
523 tvEmail.setText(email);
524
525 // Cargar la imagen de perfil directamente mediante HttpURLConnection
526 if (imageUrl != null && !imageUrl.isEmpty()) {
527 Log.d("ImagenPerfil", "Cargando imagen en ProfileFragment: " + imageUrl);
528
529 // Limpiar caché de Glide
530 com.example.food_front.utils.ImageCacheManager.clearGlideCache(requireContext());
531
532 // Usar un hilo secundario para la descarga directa
533 new Thread(() -> {
534 try {
535 // URL con timestamp para evitar caché de red
536 String imageUrlNoCache = imageUrl + "?nocache=" + System.currentTimeMillis() + "&random=" + Math.random();
537
538 // Configurar conexión HTTP sin caché
539 java.net.URL url = new java.net.URL(imageUrlNoCache);
540 java.net.HttpURLConnection connection = (java.net.HttpURLConnection) url.openConnection();
541 connection.setUseCaches(false);
542 connection.addRequestProperty("Cache-Control", "no-cache, no-store, must-revalidate");
543 connection.addRequestProperty("Pragma", "no-cache");
544 connection.addRequestProperty("Expires", "0");
545 connection.connect();
546
547 // Obtener la imagen como Bitmap
548 final android.graphics.Bitmap bitmap = android.graphics.BitmapFactory.decodeStream(connection.getInputStream());
549
550 // Actualizar UI en hilo principal
551 if (getActivity() != null && !getActivity().isFinishing()) {
552 getActivity().runOnUiThread(() -> {
553 if (bitmap != null) {
554 profileImage.setImageBitmap(bitmap);
555 Log.d("ImagenPerfil", "Imagen cargada exitosamente mediante HttpURLConnection");
556 } else {
557 // Si falla, intentar con Glide como respaldo
558 cargarImagenConGlide(imageUrl);
559 }
560 });
561 }
562 } catch (Exception e) {
563 Log.e("ImagenPerfil", "Error al descargar imagen: " + e.getMessage());
564 if (getActivity() != null && !getActivity().isFinishing()) {
565 getActivity().runOnUiThread(() -> cargarImagenConGlide(imageUrl));
566 }
567 }
568 }).start();
569 } else {
570 Log.d("ImagenPerfil", "No hay URL de imagen, usando imagen predeterminada en ProfileFragment");
571 // Usar imagen predeterminada
572 profileImage.setImageResource(R.drawable.default_profile);
573 }
574 }
575
576 private void cargarImagenConGlide(String imageUrl) {
577 // Agregar timestamp para forzar recarga
578 String imageUrlWithTimestamp = imageUrl + "?t=" + System.currentTimeMillis() + "&r=" + Math.random();
579 Log.d("ImagenPerfil", "Intentando cargar con Glide como respaldo: " + imageUrlWithTimestamp);
580
581 // Intentar cargar con Glide sin caché
582 Glide.with(requireContext())
583 .load(imageUrlWithTimestamp)
584 .skipMemoryCache(true) // Para evitar problemas con la caché
585 .diskCacheStrategy(com.bumptech.glide.load.engine.DiskCacheStrategy.NONE)
586 .placeholder(R.drawable.default_profile)
587 .error(R.drawable.default_profile)
588 .into(profileImage);
589 }
590
591 private void selectImage() {
592 Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
593 intent.setType("image/*");
594 startActivityForResult(Intent.createChooser(intent, "Selecciona una imagen"), PICK_IMAGE_REQUEST);
595 }
596
597 @Override
598 public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
599 super.onActivityResult(requestCode, resultCode, data);
600 if (requestCode == PICK_IMAGE_REQUEST && resultCode == Activity.RESULT_OK && data != null && data.getData() != null) {
601 Uri imageUri = data.getData();
602 try {
603 // Comprimimos la imagen antes de subirla
604 android.graphics.Bitmap bitmap = android.provider.MediaStore.Images.Media.getBitmap(requireContext().getContentResolver(), imageUri);
605
606 // Redimensionamos la imagen para reducir su tamaño
607 android.graphics.Bitmap resizedBitmap = resizeImage(bitmap, 800); // máximo 800px en su dimensión más grande
608
609 // Convertimos el bitmap a bytes con compresión
610 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
611 resizedBitmap.compress(android.graphics.Bitmap.CompressFormat.JPEG, 70, byteArrayOutputStream); // calidad 70%
612 byte[] imageBytes = byteArrayOutputStream.toByteArray();
613
614 // Mostramos el tamaño de la imagen comprimida para diagnóstico
615 Log.d("ImagenPerfil", "Tamaño de la imagen comprimida: " + (imageBytes.length / 1024) + " KB");
616
617 // Intentamos subir la imagen al servidor
618 uploadImage(imageBytes);
619 } catch (FileNotFoundException e) {
620 e.printStackTrace();
621 Toast.makeText(requireContext(), "Archivo no encontrado", Toast.LENGTH_SHORT).show();
622 } catch (IOException e) {
623 e.printStackTrace();
624 Toast.makeText(requireContext(), "Error al leer el archivo", Toast.LENGTH_SHORT).show();
625 }
626 }
627 }
628
635 private android.graphics.Bitmap resizeImage(android.graphics.Bitmap image, int maxSize) {
636 int width = image.getWidth();
637 int height = image.getHeight();
638
639 float ratio = (float) width / (float) height;
640
641 int newWidth;
642 int newHeight;
643
644 if (width > height) {
645 newWidth = maxSize;
646 newHeight = Math.round(maxSize / ratio);
647 } else {
648 newHeight = maxSize;
649 newWidth = Math.round(maxSize * ratio);
650 }
651
652 return android.graphics.Bitmap.createScaledBitmap(image, newWidth, newHeight, true);
653 }
654
655 private void uploadImage(byte[] imageBytes) {
656 // Mostrar un diálogo de progreso
657 ProgressDialog progressDialog = new ProgressDialog(requireContext());
658 progressDialog.setMessage("Preparando imagen...");
659 progressDialog.setCancelable(false);
660 progressDialog.show();
661
662 // Si la imagen es mayor a 1MB, mostrar una advertencia
663 if (imageBytes.length > 1024 * 1024) {
664 progressDialog.dismiss();
665 new AlertDialog.Builder(requireContext())
666 .setTitle("Imagen demasiado grande")
667 .setMessage("La imagen seleccionada es de " + (imageBytes.length / 1024 / 1024) + "MB. Es posible que la subida sea lenta. ¿Deseas intentar subir la imagen de todos modos?")
668 .setPositiveButton("Sí, intentar", (dialog, which) -> {
669 // Reiniciar el proceso con la imagen original
670 uploadToCloudinary(imageBytes, progressDialog);
671 })
672 .setNegativeButton("No, cancelar", (dialog, which) -> {
673 Toast.makeText(requireContext(), "Subida cancelada", Toast.LENGTH_SHORT).show();
674 })
675 .show();
676 } else {
677 // Si la imagen es pequeña, continuar directamente
678 uploadToCloudinary(imageBytes, progressDialog);
679 }
680 }
681
687 private void uploadToCloudinary(byte[] imageBytes, ProgressDialog progressDialog) {
688 if (progressDialog == null) {
689 progressDialog = new ProgressDialog(requireContext());
690 progressDialog.setMessage("Subiendo imagen...");
691 progressDialog.setCancelable(false);
692 progressDialog.show();
693 } else {
694 progressDialog.setMessage("Subiendo imagen a Cloudinary...");
695 }
696
697 final ProgressDialog finalProgressDialog = progressDialog;
698
699 // Subir imagen usando el CloudinaryManager
700 com.example.food_front.utils.CloudinaryManager.uploadImage(
701 requireContext(),
702 imageBytes,
703 "profile_images",
704 new com.example.food_front.utils.CloudinaryManager.CloudinaryUploadCallback() {
705 @Override
706 public void onStart() {
707 if (getActivity() != null && !getActivity().isFinishing()) {
708 getActivity().runOnUiThread(() -> {
709 finalProgressDialog.setMessage("Iniciando subida...");
710 });
711 }
712 }
713
714 @Override
715 public void onProgress(double progress) {
716 if (getActivity() != null && !getActivity().isFinishing()) {
717 getActivity().runOnUiThread(() -> {
718 int progressInt = (int) (progress * 100);
719 finalProgressDialog.setMessage("Subiendo imagen: " + progressInt + "%");
720 });
721 }
722 }
723
724 @Override
725 public void onSuccess(String imageUrl) {
726 if (getActivity() != null && !getActivity().isFinishing()) {
727 getActivity().runOnUiThread(() -> {
728 finalProgressDialog.dismiss();
729
730 // Guardar URL de imagen en SharedPreferences
731 profileManager.saveProfileImageUrl(imageUrl);
732
733 // Actualizar la imagen local
734 cargarImagenConGlide(imageUrl);
735
736 // Actualizar imagen en otros fragmentos
737 MainActivity mainActivity = (MainActivity) getActivity();
738 if (mainActivity != null) {
739 mainActivity.actualizarTodasLasImagenes();
740 }
741
742 Toast.makeText(requireContext(), "Imagen subida correctamente", Toast.LENGTH_SHORT).show();
743
744 // También actualizar en el backend si es necesario
745 actualizarImagenEnBackend(imageUrl);
746 });
747 }
748 }
749
750 @Override
751 public void onError(String errorMessage) {
752 if (getActivity() != null && !getActivity().isFinishing()) {
753 getActivity().runOnUiThread(() -> {
754 finalProgressDialog.dismiss();
755 Toast.makeText(requireContext(), "Error: " + errorMessage, Toast.LENGTH_LONG).show();
756
757 // Si hay un error con Cloudinary, mostrar un diálogo para usar el método anterior
758 new AlertDialog.Builder(requireContext())
759 .setTitle("Error con Cloudinary")
760 .setMessage("¿Deseas intentar subir la imagen usando el método alternativo?")
761 .setPositiveButton("Sí, intentar", (dialog, which) -> {
762 // Usar el método original (backend propio)
763 continueUpload(imageBytes, "https://backmobile1.onrender.com/appUSERS/upload_profile_image/");
764 })
765 .setNegativeButton("Cancelar", null)
766 .show();
767 });
768 }
769 }
770 }
771 );
772 }
773
778 private void actualizarImagenEnBackend(String imageUrl) {
779 String url = "https://backmobile1.onrender.com/appUSERS/update_profile_image_url/";
780
781 try {
782 // Crear solicitud JSON
783 JSONObject jsonBody = new JSONObject();
784 jsonBody.put("imagen_perfil_url", imageUrl);
785
786 // Crear solicitud Volley
787 JsonObjectRequest request = new JsonObjectRequest(
788 Request.Method.POST,
789 url,
790 jsonBody,
791 response -> {
792 Log.d("ImagenPerfil", "URL actualizada en backend: " + response.toString());
793 },
794 error -> {
795 Log.e("ImagenPerfil", "Error al actualizar URL en backend", error);
796 }
797 ) {
798 @Override
799 public Map<String, String> getHeaders() throws AuthFailureError {
800 Map<String, String> headers = new HashMap<>();
801 String token = sessionManager.getToken();
802 if (token != null) {
803 headers.put("Authorization", "Bearer " + token);
804 }
805 return headers;
806 }
807 };
808
809 // Añadir a la cola de Volley
810 RequestQueue requestQueue = Volley.newRequestQueue(requireContext());
811 requestQueue.add(request);
812
813 } catch (Exception e) {
814 Log.e("ImagenPerfil", "Error al crear solicitud para actualizar URL", e);
815 }
816 }
817
822 public void actualizarImagenDePerfil(String imageUrl) {
823 if (imageUrl == null || imageUrl.isEmpty()) return;
824
825 Log.d("ImagenPerfil", "actualizarImagenDePerfil llamado con URL: " + imageUrl);
826
827 // Limpiar caché
828 com.example.food_front.utils.ImageCacheManager.clearGlideCache(requireContext());
829
830 // Guardar la nueva URL
831 profileManager.saveProfileImageUrl(imageUrl);
832
833 // Actualizar la interfaz de usuario
834 if (profileImage != null) {
835 cargarImagenConGlide(imageUrl);
836 }
837 }
838
844 private void continueUpload(byte[] imageBytes, String url) {
845 // Mostrar un diálogo de progreso
846 ProgressDialog progressDialog = new ProgressDialog(requireContext());
847 progressDialog.setMessage("Subiendo imagen...");
848 progressDialog.setCancelable(false);
849 progressDialog.show();
850
851 // Crear una solicitud multipart para subir la imagen
852 VolleyMultipartRequest multipartRequest = new VolleyMultipartRequest(Request.Method.POST, url,
853 response -> {
854 progressDialog.dismiss();
855
856 // Procesar la respuesta del servidor
857 String responseBody = new String(response.data);
858 Log.d("ImagenPerfil", "Respuesta del servidor: " + responseBody);
859
860 try {
861 JSONObject jsonObject = new JSONObject(responseBody);
862
863 // Verificar si hay un mensaje de error en la respuesta
864 if (jsonObject.has("error")) {
865 String error = jsonObject.getString("error");
866 Log.e("ImagenPerfil", "Error del servidor: " + error);
867 Toast.makeText(requireContext(), "Error: " + error, Toast.LENGTH_LONG).show();
868 return;
869 }
870
871 // Obtener URL de la imagen (normalizando la obtención de la URL)
872 String imageUrl = null;
873 if (jsonObject.has("imagen_perfil_url")) {
874 imageUrl = jsonObject.getString("imagen_perfil_url");
875 } else if (jsonObject.has("image_url")) {
876 imageUrl = jsonObject.getString("image_url");
877 } else if (jsonObject.has("url")) {
878 imageUrl = jsonObject.getString("url");
879 }
880
881 if (imageUrl == null || imageUrl.isEmpty()) {
882 Log.e("ImagenPerfil", "URL de imagen no encontrada en la respuesta: " + responseBody);
883 Toast.makeText(requireContext(), "Error: No se recibió la URL de la imagen", Toast.LENGTH_SHORT).show();
884 return;
885 }
886
887 // Guardar la URL final para usar en el código
888 final String finalImageUrl = imageUrl;
889 Log.d("ImagenPerfil", "URL de imagen recibida: " + finalImageUrl);
890
891 // Limpiar caché de Glide
892 com.example.food_front.utils.ImageCacheManager.clearGlideCache(requireContext());
893
894 // Guardar la nueva URL en SharedPreferences
895 profileManager.saveProfileImageUrl(finalImageUrl);
896
897 // Actualizar la interfaz con la nueva imagen
898 cargarImagenConGlide(finalImageUrl);
899
900 // Actualizar en otros fragmentos
901 MainActivity mainActivity = (MainActivity) getActivity();
902 if (mainActivity != null) {
903 mainActivity.actualizarTodasLasImagenes();
904 }
905
906 Toast.makeText(requireContext(), "Imagen de perfil actualizada", Toast.LENGTH_SHORT).show();
907 } catch (JSONException e) {
908 Log.e("ImagenPerfil", "Error al procesar la respuesta JSON: " + e.getMessage() + "\nRespuesta: " + responseBody);
909 Toast.makeText(requireContext(), "Error al procesar la respuesta del servidor", Toast.LENGTH_SHORT).show();
910 }
911 },
912 error -> {
913 progressDialog.dismiss();
914 handleNetworkError(error);
915 }) {
916
917 @Override
918 protected Map<String, String> getParams() {
919 Map<String, String> params = new HashMap<>();
920 return params;
921 }
922
923 @Override
924 public Map<String, String> getHeaders() throws AuthFailureError {
925 Map<String, String> headers = new HashMap<>();
926 // Añadir el token de autenticación
927 String token = sessionManager.getToken();
928 if (token != null) {
929 headers.put("Authorization", "Bearer " + token);
930 }
931 return headers;
932 }
933 };
934
935 // Añadir la imagen como archivo al cuerpo de la solicitud
936 multipartRequest.addByteData("image", imageBytes, "profile_image.jpg", "image/jpeg");
937
938 // Establecer una política de reintento
939 multipartRequest.setRetryPolicy(new com.android.volley.DefaultRetryPolicy(
940 90000, // 90 segundos de timeout
941 2, // 2 reintentos
942 1.5f // backoff multiplier
943 ));
944
945 // Añadir la solicitud a la cola de Volley
946 RequestQueue requestQueue = Volley.newRequestQueue(requireContext());
947 requestQueue.add(multipartRequest);
948 }
949
954 private void handleNetworkError(VolleyError error) {
955 String errorMsg = "Error al subir la imagen: ";
956
957 if (error.networkResponse != null) {
958 int statusCode = error.networkResponse.statusCode;
959 errorMsg += "Código: " + statusCode;
960
961 // Intentar obtener el cuerpo de la respuesta de error
962 try {
963 String responseBody = new String(error.networkResponse.data, "utf-8");
964 Log.e("ImagenPerfil", "Error response body: " + responseBody);
965
966 // Para errores 502, ofrecer alternativa
967 if (statusCode == 502) {
968 showCloudflareErrorDialog();
969 return;
970 }
971
972 errorMsg += " - " + responseBody;
973 } catch (Exception e) {
974 Log.e("ImagenPerfil", "Error al leer el cuerpo de la respuesta de error", e);
975 }
976 } else if (error instanceof com.android.volley.TimeoutError) {
977 errorMsg = "El servidor tardó demasiado en responder. La imagen podría ser demasiado grande.";
978 showCloudflareErrorDialog();
979 return;
980 } else if (error instanceof com.android.volley.NoConnectionError) {
981 errorMsg = "No hay conexión a internet. Por favor, verifica tu conexión e intenta nuevamente.";
982 } else {
983 errorMsg += error.toString();
984 }
985
986 Log.e("ImagenPerfil", errorMsg, error);
987 Toast.makeText(requireContext(), errorMsg, Toast.LENGTH_LONG).show();
988 }
989
993 private void showCloudflareErrorDialog() {
994 if (getContext() == null) return;
995
996 new AlertDialog.Builder(requireContext())
997 .setTitle("Problema con el servidor")
998 .setMessage("El servidor está experimentando problemas temporales (Error 502 Bad Gateway). Esto puede deberse a que:\n\n" +
999 "1. El servidor está ocupado o en mantenimiento\n" +
1000 "2. La imagen es demasiado grande para ser procesada\n\n" +
1001 "¿Qué deseas hacer?")
1002 .setPositiveButton("Intentar con otra imagen", (dialog, which) -> {
1003 // Volver a abrir el selector de imágenes
1004 selectImage();
1005 })
1006 .setNegativeButton("Cancelar", null)
1007 .show();
1008 }
1009
1010 // Método para eliminar la cuenta
1011 private void eliminarCuenta() {
1012 String url = "https://backmobile1.onrender.com/appUSERS/delete/";
1013 ProgressDialog progressDialog = new ProgressDialog(requireContext());
1014 progressDialog.setMessage("Eliminando cuenta...");
1015 progressDialog.setCancelable(false);
1016 progressDialog.show();
1017
1018 com.android.volley.toolbox.StringRequest request = new com.android.volley.toolbox.StringRequest(
1019 com.android.volley.Request.Method.DELETE,
1020 url,
1021 response -> {
1022 progressDialog.dismiss();
1023 Toast.makeText(requireContext(), "Cuenta eliminada satisfactoriamente", Toast.LENGTH_LONG).show();
1024 sessionManager.logout();
1025 Intent intent = new Intent(requireActivity(), MainActivity.class);
1026 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
1027 startActivity(intent);
1028 requireActivity().finish();
1029 },
1030 error -> {
1031 progressDialog.dismiss();
1032
1033 // Mejor manejo de errores
1034 String errorMsg = "Error al eliminar la cuenta: ";
1035
1036 if (error.networkResponse != null) {
1037 int statusCode = error.networkResponse.statusCode;
1038 errorMsg += "Código: " + statusCode;
1039
1040 // Intentar obtener el cuerpo de la respuesta de error
1041 try {
1042 String responseBody = new String(error.networkResponse.data, "utf-8");
1043 Log.e("EliminarCuenta", "Error response body: " + responseBody);
1044 errorMsg += " - " + responseBody;
1045
1046 // Si es error 500 pero la cuenta fue desactivada correctamente en el backend
1047 if (statusCode == 500 && responseBody.contains("Cuenta desactivada correctamente")) {
1048 // A pesar del error 500, el backend procesó correctamente la solicitud
1049 Toast.makeText(requireContext(), "Cuenta eliminada satisfactoriamente", Toast.LENGTH_LONG).show();
1050 sessionManager.logout();
1051 Intent intent = new Intent(requireActivity(), MainActivity.class);
1052 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
1053 startActivity(intent);
1054 requireActivity().finish();
1055 return;
1056 }
1057 } catch (Exception e) {
1058 Log.e("EliminarCuenta", "Error al leer el cuerpo de la respuesta de error", e);
1059 }
1060 } else if (error instanceof com.android.volley.TimeoutError) {
1061 errorMsg = "El servidor tardó demasiado en responder.";
1062 } else if (error instanceof com.android.volley.NoConnectionError) {
1063 errorMsg = "No hay conexión a internet. Por favor, verifica tu conexión e intenta nuevamente.";
1064 } else {
1065 errorMsg += error.toString();
1066 }
1067
1068 Log.e("EliminarCuenta", errorMsg, error);
1069 Toast.makeText(requireContext(), errorMsg, Toast.LENGTH_LONG).show();
1070 }
1071 ) {
1072 @Override
1073 public Map<String, String> getHeaders() throws AuthFailureError {
1074 Map<String, String> headers = new HashMap<>();
1075 String token = sessionManager.getToken();
1076 if (token != null) {
1077 headers.put("Authorization", "Bearer " + token);
1078 }
1079 return headers;
1080 }
1081 };
1082
1083 // Establecer una política de reintento con timeout más largo
1084 request.setRetryPolicy(new com.android.volley.DefaultRetryPolicy(
1085 30000, // 30 segundos de timeout
1086 1, // 1 reintento
1087 1.5f // backoff multiplier
1088 ));
1089
1090 com.android.volley.toolbox.Volley.newRequestQueue(requireContext()).add(request);
1091 }
1092}
void onActivityResult(int requestCode, int resultCode, @Nullable Intent data)
View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)
void saveProfileImageUrl(String profileImageUrl)
void addByteData(String key, byte[] data, String filename, String mimeType)