Mejora de xml para que se vea mucho mas formal!

This commit is contained in:
santi 2025-06-11 18:23:15 +02:00
parent 2566402280
commit 92fa136769
31 changed files with 883 additions and 610 deletions

Binary file not shown.

View File

@ -7,18 +7,34 @@ import retrofit2.converter.gson.GsonConverterFactory;
public class ApiClient {
/**
* URL base del servidor donde está alojada la API del backend.
* <p>Nota: "10.0.2.2" es una dirección especial utilizada para acceder al localhost
* del host desde el emulador de Android.</p>
*/
private static final String BASE_URL = "http://10.0.2.2:3000/";
/** Instancia única de Retrofit. */
private static Retrofit retrofit = null;
/**
* Devuelve una instancia de Retrofit configurada con la URL base, un cliente HTTP
* con un interceptor para loguear las peticiones/respuestas, y un convertidor JSON.
*
* @return una instancia única de Retrofit lista para usar con las interfaces de servicio.
*/
public static Retrofit getClient() {
if (retrofit == null) {
// Interceptor para mostrar logs del cuerpo de la petición y respuesta HTTP
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.BODY);
// Cliente HTTP personalizado con el interceptor de logging
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(logging)
.build();
// Construcción de la instancia de Retrofit
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)

View File

@ -14,36 +14,58 @@ import com.santiparra.yomitrack.model.RegisterResponse;
import com.santiparra.yomitrack.model.UserStatsResponse;
import com.santiparra.yomitrack.utils.ActivityLog;
import java.util.List;
import java.util.Map;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.HTTP;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Path;
import retrofit2.http.Query;
import retrofit2.http.*;
/**
* Interfaz ApiService que define todos los endpoints disponibles
* para la comunicación entre la app YomiTrack y el backend.
*
* <p>Usa Retrofit para declarar métodos HTTP de forma declarativa.</p>
*/
public interface ApiService {
// ---------------- Usuario ----------------
// -------------------- USUARIO --------------------
/**
* Registra un nuevo usuario en la aplicación.
*
* @param user Objeto con los datos del usuario.
* @return respuesta del backend con éxito o error.
*/
@POST("users/register")
Call<RegisterResponse> registerUser(@Body UserEntity user);
/**
* Inicia sesión con un usuario existente.
*
* @param user Objeto con email y contraseña.
* @return respuesta con los datos del usuario autenticado.
*/
@POST("users/login")
Call<LoginResponse> loginUser(@Body UserEntity user);
/**
* Solicita un enlace de recuperación de contraseña al correo proporcionado.
*
* @param email correo electrónico del usuario.
* @return respuesta de estado de la operación.
*/
@FormUrlEncoded
@POST("users/forgot-password")
Call<ApiResponse> forgotPassword(@Field("email") String email);
/**
* Restablece la contraseña del usuario con un token válido.
*
* @param email correo electrónico del usuario.
* @param token token enviado al correo.
* @param newPassword nueva contraseña.
* @return respuesta de la operación.
*/
@FormUrlEncoded
@POST("users/reset-password")
Call<ApiResponse> resetPassword(
@ -52,11 +74,25 @@ public interface ApiService {
@Field("newPassword") String newPassword
);
// ---------------- Anime ----------------
// -------------------- ANIME --------------------
/**
* Inserta un nuevo anime en la lista del usuario.
*
* @param anime Objeto con los datos del anime.
* @return respuesta del backend.
*/
@POST("anime/add")
Call<ApiResponse> insertAnime(@Body AnimeEntity anime);
// Scroll infinito: obtener lista paginada
/**
* Obtiene la lista paginada de animes de un usuario.
*
* @param userId ID del usuario.
* @param page número de página.
* @param size cantidad de ítems por página.
* @return página con lista de animes.
*/
@GET("/anime/list/{userId}")
Call<AnimePageResponse> getAnimes(
@Path("userId") int userId,
@ -64,16 +100,57 @@ public interface ApiService {
@Query("size") int size
);
/**
* Actualiza un anime existente.
*
* @param animeId ID del anime.
* @param anime Objeto con datos actualizados.
* @return respuesta del backend.
*/
@PUT("anime/{id}")
Call<ApiResponse> updateAnime(@Path("id") int animeId, @Body AnimeEntity anime);
/**
* Elimina un anime por su ID.
*
* @param id ID del anime.
* @return respuesta del backend.
*/
@DELETE("anime/delete/{id}")
Call<ApiResponse> deleteAnime(@Path("id") int id);
// ---------------- Manga ----------------
/**
* Obtiene animes por estado para un usuario específico.
*
* @param userId ID del usuario.
* @param status estado deseado (e.g., Watching, Completed).
* @return lista de animes con dicho estado.
*/
@GET("anime/user/{userId}/status/{status}")
Call<List<AnimeEntity>> getAnimeByUserAndStatus(
@Path("userId") int userId,
@Path("status") String status
);
// -------------------- MANGA --------------------
/**
* Inserta un nuevo manga en la lista del usuario.
*
* @param manga Objeto con los datos del manga.
* @return respuesta del backend.
*/
@POST("manga/add")
Call<ApiResponse> insertManga(@Body MangaEntity manga);
/**
* Obtiene la lista paginada de mangas de un usuario.
*
* @param userId ID del usuario.
* @param page número de página.
* @param size cantidad de ítems por página.
* @return página con lista de mangas.
*/
@GET("/manga/list/{userId}")
Call<MangaPageResponse> getMangas(
@Path("userId") int userId,
@ -81,57 +158,125 @@ public interface ApiService {
@Query("size") int size
);
/**
* Obtiene mangas por estado para un usuario específico.
*
* @param userId ID del usuario.
* @param status estado deseado (e.g., Reading, Completed).
* @return lista de mangas con dicho estado.
*/
@GET("manga/user/{userId}/status/{status}")
Call<List<MangaEntity>> getMangaByUserAndStatus(
@Path("userId") int userId,
@Path("status") String status
);
/**
* Actualiza un manga existente.
*
* @param mangaId ID del manga.
* @param manga Objeto con datos actualizados.
* @return respuesta del backend.
*/
@PUT("manga/{id}")
Call<ApiResponse> updateManga(@Path("id") int mangaId, @Body MangaEntity manga);
/**
* Elimina un manga por su ID.
*
* @param id ID del manga.
* @return respuesta del backend.
*/
@DELETE("manga/delete/{id}")
Call<ApiResponse> deleteManga(@Path("id") int id);
// ---------------- Activity -------------------
// -------------------- ACTIVIDAD --------------------
/**
* Obtiene estadísticas de anime y manga del usuario.
*
* @param userId ID del usuario.
* @return objeto con estadísticas.
*/
@GET("users/{id}/stats")
Call<UserStatsResponse> getUserStats(@Path("id") int userId);
/**
* Obtiene el historial de actividad de un usuario.
*
* @param userId ID del usuario.
* @return lista de actividades.
*/
@GET("api/activity/list/{userId}")
Call<List<ActivityLog>> getActivityLog(@Path("userId") int userId);
/**
* Obtiene los comentarios de una actividad específica.
*
* @param activityId ID de la actividad.
* @return lista de comentarios.
*/
@GET("api/activity/comments/{activityId}")
Call<List<CommentModel>> getCommentsByActivity(@Path("activityId") int activityId);
/**
* Verifica si un usuario dio like a una actividad.
*
* @param userId ID del usuario.
* @param activityId ID de la actividad.
* @return objeto JSON con resultado (true/false).
*/
@GET("/api/activity/like/{userId}/{activityId}")
Call<JsonObject> checkLike(
@Path("userId") int userId,
@Path("activityId") int activityId
);
@GET("anime/user/{userId}/status/{status}")
Call<List<AnimeEntity>> getAnimeByUserAndStatus(
@Path("userId") int userId,
@Path("status") String status
);
/**
* Envía un like a una actividad.
*
* @param body JSON con userId y activityId.
* @return respuesta del backend.
*/
@POST("api/activity/like")
Call<JsonObject> postLike(@Body JsonObject body);
@POST("api/activity/comment")
Call<JsonObject> postComment(@Body JsonObject body);
@POST("api/activity/add")
Call<JsonObject> postActivity(@Body Map<String, Object> body);
/**
* Elimina un like de una actividad.
*
* @param body JSON con userId y activityId.
* @return respuesta del backend.
*/
@HTTP(method = "DELETE", path = "api/activity/like/remove", hasBody = true)
Call<JsonObject> deleteLike(@Body JsonObject body);
/**
* Publica un comentario en una actividad.
*
* @param body JSON con userId, activityId, texto y otros datos.
* @return respuesta del backend.
*/
@POST("api/activity/comment")
Call<JsonObject> postComment(@Body JsonObject body);
// ---------------- AniList API ----------------
/**
* Publica una nueva actividad.
*
* @param body mapa con los datos de la actividad (userId, tipo, contenido...).
* @return respuesta del backend.
*/
@POST("api/activity/add")
Call<JsonObject> postActivity(@Body Map<String, Object> body);
// -------------------- ANIList API --------------------
/**
* Busca animes o mangas en AniList.
*
* @param query término de búsqueda.
* @param type tipo de media: "ANIME" o "MANGA".
* @return lista de resultados obtenidos desde AniList.
*/
@GET("anilist/search")
Call<List<AniListMedia>> searchAniList(@Query("query") String query, @Query("type") String type);
}

View File

@ -4,22 +4,55 @@ import java.io.Serializable;
/**
* Entidad que representa un anime guardado por el usuario en la base de datos local.
* Implementa Serializable para facilitar el paso entre componentes.
*/
public class AnimeEntity implements Serializable {
/** ID único del anime en la base de datos. */
private int id;
/** ID del usuario al que pertenece este anime. */
private int userId;
/** Título del anime. */
private String title;
/** Puntuación asignada por el usuario (por ejemplo, del 1 al 10). */
private int score;
/** Progreso actual del usuario (número de episodios vistos). */
private int progress;
/** Estado del anime (Watching, Completed, Paused, etc.). */
private String status;
/** Tipo del anime (TV, Movie, OVA, etc.). */
private String type;
/** URL de la imagen de portada del anime. */
private String imageUrl;
/** Número total de episodios del anime. */
private int totalEpisodes;
/**
* Constructor vacío requerido por algunas librerías como Room o Retrofit.
*/
public AnimeEntity() {
}
/**
* Constructor completo de la entidad Anime.
*
* @param id ID único del anime.
* @param title Título del anime.
* @param status Estado del anime.
* @param userId ID del usuario propietario.
* @param imageUrl URL de la imagen del anime.
* @param progress Episodios vistos.
* @param score Puntuación asignada.
* @param totalEpisodes Total de episodios del anime.
*/
public AnimeEntity(int id, String title, String status, int userId, String imageUrl, int progress, int score, int totalEpisodes) {
this.id = id;
this.title = title;
@ -31,75 +64,92 @@ public class AnimeEntity implements Serializable {
this.totalEpisodes = totalEpisodes;
}
// Getters y Setters
/** @return ID del anime. */
public int getId() {
return id;
}
/** @param id ID del anime. */
public void setId(int id) {
this.id = id;
}
/** @return ID del usuario. */
public int getUserId() {
return userId;
}
/** @param userId ID del usuario. */
public void setUserId(int userId) {
this.userId = userId;
}
/** @return Título del anime. */
public String getTitle() {
return title;
}
/** @param title Título del anime. */
public void setTitle(String title) {
this.title = title;
}
/** @return Puntuación del anime. */
public int getScore() {
return score;
}
/** @param score Puntuación del anime. */
public void setScore(int score) {
this.score = score;
}
/** @return Progreso del usuario (episodios vistos). */
public int getProgress() {
return progress;
}
/** @param progress Episodios vistos por el usuario. */
public void setProgress(int progress) {
this.progress = progress;
}
/** @return Estado del anime. */
public String getStatus() {
return status;
}
/** @param status Estado del anime. */
public void setStatus(String status) {
this.status = status;
}
/** @return Tipo de anime (TV, Movie, etc.). */
public String getType() {
return type;
}
/** @param type Tipo de anime. */
public void setType(String type) {
this.type = type;
}
/** @return URL de la imagen del anime. */
public String getImageUrl() {
return imageUrl;
}
/** @param imageUrl URL de la imagen del anime. */
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
/** @return Total de episodios del anime. */
public int getTotalEpisodes() {
return totalEpisodes;
}
/** @param totalEpisodes Número total de episodios. */
public void setTotalEpisodes(int totalEpisodes) {
this.totalEpisodes = totalEpisodes;
}

View File

@ -4,22 +4,55 @@ import java.io.Serializable;
/**
* Entidad que representa un manga guardado por el usuario en la base de datos local.
* Implementa Serializable para permitir su paso entre actividades y fragmentos.
*/
public class MangaEntity implements Serializable {
/** ID único del manga en la base de datos. */
private int id;
/** ID del usuario al que pertenece este manga. */
private int userId;
/** Título del manga. */
private String title;
/** Puntuación asignada por el usuario (por ejemplo, del 1 al 10). */
private int score;
/** Progreso actual del usuario (capítulos leídos). */
private int progress;
/** Estado del manga (Reading, Completed, On-Hold, etc.). */
private String status;
/** Tipo del manga (Manga, Manhwa, Doujinshi, etc.). */
private String type;
/** URL de la imagen de portada del manga. */
private String imageUrl;
/** Número total de capítulos del manga. */
private int totalChapters;
/**
* Constructor vacío necesario para serialización y frameworks como Room o Retrofit.
*/
public MangaEntity() {
}
/**
* Constructor completo para inicializar una instancia de manga.
*
* @param id ID único del manga.
* @param title Título del manga.
* @param status Estado actual del manga.
* @param userId ID del usuario que lo añadió.
* @param imageUrl URL de la portada del manga.
* @param progress Capítulos leídos.
* @param score Puntuación dada por el usuario.
* @param totalChapters Total de capítulos disponibles.
*/
public MangaEntity(int id, String title, String status, int userId, String imageUrl, int progress, int score, int totalChapters) {
this.id = id;
this.title = title;
@ -31,76 +64,92 @@ public class MangaEntity implements Serializable {
this.totalChapters = totalChapters;
}
// Getters y Setters
/** @return ID del manga. */
public int getId() {
return id;
}
/** @param id ID del manga. */
public void setId(int id) {
this.id = id;
}
/** @return ID del usuario propietario del manga. */
public int getUserId() {
return userId;
}
/** @param userId ID del usuario propietario. */
public void setUserId(int userId) {
this.userId = userId;
}
/** @return Título del manga. */
public String getTitle() {
return title;
}
/** @param title Título del manga. */
public void setTitle(String title) {
this.title = title;
}
/** @return Puntuación asignada al manga. */
public int getScore() {
return score;
}
/** @param score Puntuación del manga. */
public void setScore(int score) {
this.score = score;
}
/** @return Capítulos leídos por el usuario. */
public int getProgress() {
return progress;
}
/** @param progress Capítulos que el usuario ha leído. */
public void setProgress(int progress) {
this.progress = progress;
}
/** @return Estado del manga (Reading, Dropped, etc.). */
public String getStatus() {
return status;
}
/** @param status Estado actual del manga. */
public void setStatus(String status) {
this.status = status;
}
/** @return Tipo de manga (e.g., Manhwa, Doujinshi). */
public String getType() {
return type;
}
/** @param type Tipo de manga. */
public void setType(String type) {
this.type = type;
}
/** @return URL de la imagen del manga. */
public String getImageUrl() {
return imageUrl;
}
/** @param imageUrl URL de la imagen de portada. */
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
/** @return Total de capítulos del manga. */
public int getTotalChapters() {
return totalChapters;
}
/** @param totalChapters Cantidad total de capítulos. */
public void setTotalChapters(int totalChapters) {
this.totalChapters = totalChapters;
}

View File

@ -3,60 +3,87 @@ package com.santiparra.yomitrack.db.entities;
import com.google.gson.annotations.SerializedName;
/**
* Entidad que representa un usuario.
* Entidad que representa un usuario dentro del sistema.
* Utilizada tanto para autenticación como para registro y obtención de datos del backend.
*/
public class UserEntity {
/** Identificador único del usuario. */
@SerializedName("id")
private int id;
/** Nombre de usuario utilizado para iniciar sesión y mostrar en el perfil. */
@SerializedName("username")
private String username;
/** Contraseña del usuario (debe manejarse con cuidado y encriptación en producción). */
@SerializedName("password")
private String password;
/** Dirección de correo electrónico del usuario. */
@SerializedName("email")
private String email;
/**
* Constructor utilizado para iniciar sesión con username y contraseña.
*
* @param username nombre de usuario.
* @param password contraseña del usuario.
*/
public UserEntity(String username, String password) {
this.username = username;
this.password = password;
}
/**
* Constructor utilizado para registrar un nuevo usuario.
*
* @param username nombre de usuario.
* @param email correo electrónico.
* @param password contraseña del usuario.
*/
public UserEntity(String username, String email, String password) {
this.username = username;
this.email = email;
this.password = password;
}
/** @return ID del usuario. */
public int getId() {
return id;
}
/** @param id ID del usuario. */
public void setId(int id) {
this.id = id;
}
/** @return nombre de usuario. */
public String getUsername() {
return username;
}
/** @param username nombre de usuario. */
public void setUsername(String username) {
this.username = username;
}
/** @return contraseña del usuario. */
public String getPassword() {
return password;
}
/** @param password contraseña del usuario. */
public void setPassword(String password) {
this.password = password;
}
/** @return correo electrónico del usuario. */
public String getEmail() {
return email;
}
/** @param email correo electrónico del usuario. */
public void setEmail(String email) {
this.email = email;
}

View File

@ -1,32 +1,75 @@
package com.santiparra.yomitrack.model;
/**
* Modelo de datos que representa un resultado de búsqueda desde la API de AniList.
* Utilizado tanto para anime como manga.
*/
public class AniListMedia {
/** ID único del media (anime o manga) proporcionado por AniList. */
private int id;
/** Título del anime o manga. */
private String title;
/** URL de la imagen de portada del anime o manga. */
private String imageUrl;
/**
* Constructor vacío necesario para serialización/deserialización automática.
*/
public AniListMedia() {}
/**
* Devuelve el ID del media.
*
* @return ID del anime o manga.
*/
public int getId() {
return id;
}
public String getTitle() {
return title;
}
public String getImageUrl() {
return imageUrl;
}
/**
* Establece el ID del media.
*
* @param id ID del anime o manga.
*/
public void setId(int id) {
this.id = id;
}
/**
* Devuelve el título del media.
*
* @return título del anime o manga.
*/
public String getTitle() {
return title;
}
/**
* Establece el título del media.
*
* @param title título del anime o manga.
*/
public void setTitle(String title) {
this.title = title;
}
/**
* Devuelve la URL de la imagen de portada.
*
* @return URL de la imagen.
*/
public String getImageUrl() {
return imageUrl;
}
/**
* Establece la URL de la imagen de portada.
*
* @param imageUrl URL de la imagen.
*/
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}

View File

@ -1,9 +0,0 @@
package com.santiparra.yomitrack.model;
public class ApiResponse {
private String message;
public String getMessage() {
return message;
}
}

View File

@ -1,41 +0,0 @@
package com.santiparra.yomitrack.model.adapters.airing;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.santiparra.yomitrack.R;
import com.santiparra.yomitrack.model.ItemModel;
public class AiringViewHolder extends RecyclerView.ViewHolder {
public ImageView imageView;
public TextView titleTextView;
public TextView progressTextView;
public AiringViewHolder(@NonNull View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.mediaImage);
titleTextView = itemView.findViewById(R.id.titleTextView);
progressTextView = itemView.findViewById(R.id.progressTextView);
}
public void bind(ItemModel item) {
titleTextView.setText(item.getTitle());
progressTextView.setText("Progress: " + item.getProgress());
if (item.getImageUrl() != null && !item.getImageUrl().isEmpty()) {
Glide.with(itemView.getContext())
.load(item.getImageUrl())
.placeholder(R.drawable.placeholder_image)
.error(R.drawable.error_image)
.into(imageView);
} else {
imageView.setImageResource(R.drawable.placeholder_image);
}
}
}

View File

@ -1,41 +0,0 @@
package com.santiparra.yomitrack.model.adapters.airing;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.santiparra.yomitrack.R;
import com.santiparra.yomitrack.model.ItemModel;
public class AnimeViewHolder extends RecyclerView.ViewHolder {
public ImageView imageView;
public TextView titleTextView;
public TextView progressTextView;
public AnimeViewHolder(@NonNull View itemView) {
super(itemView);
imageView = itemView.findViewById(R.id.mediaImage);
titleTextView = itemView.findViewById(R.id.titleTextView);
progressTextView = itemView.findViewById(R.id.progressTextView);
}
public void bind(ItemModel item) {
titleTextView.setText(item.getTitle());
progressTextView.setText("Progress: " + item.getProgress());
if (item.getImageUrl() != null && !item.getImageUrl().isEmpty()) {
Glide.with(itemView.getContext())
.load(item.getImageUrl())
.placeholder(R.drawable.placeholder_image)
.error(R.drawable.error_image)
.into(imageView);
} else {
imageView.setImageResource(R.drawable.placeholder_image);
}
}
}

View File

@ -20,7 +20,6 @@ import com.santiparra.yomitrack.api.ApiService;
import com.santiparra.yomitrack.db.entities.AnimeEntity;
import com.santiparra.yomitrack.db.entities.MangaEntity;
import com.santiparra.yomitrack.model.AniListMedia;
import com.santiparra.yomitrack.utils.ActivityLog;
import com.santiparra.yomitrack.utils.DateUtils;
import java.util.HashMap;
@ -31,18 +30,36 @@ import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* Adaptador para mostrar resultados de búsqueda provenientes de AniList
* y permitir al usuario añadir animes o mangas a su lista local mediante la API.
*/
public class AniListSearchAdapter extends RecyclerView.Adapter<AniListSearchAdapter.ViewHolder> {
private final List<AniListMedia> mediaList;
private final Context context;
private final String mediaType;
/**
* Constructor del adaptador.
*
* @param context contexto de la aplicación.
* @param mediaList lista de resultados de búsqueda de AniList.
* @param mediaType tipo de media ("ANIME" o "MANGA").
*/
public AniListSearchAdapter(Context context, List<AniListMedia> mediaList, String mediaType) {
this.context = context;
this.mediaList = mediaList;
this.mediaType = mediaType;
}
/**
* Infla el layout de cada ítem de la lista.
*
* @param parent el ViewGroup padre.
* @param viewType el tipo de vista.
* @return ViewHolder que contiene la vista del ítem.
*/
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@ -50,6 +67,12 @@ public class AniListSearchAdapter extends RecyclerView.Adapter<AniListSearchAdap
return new ViewHolder(view);
}
/**
* Asigna los datos de un ítem a su vista correspondiente.
*
* @param holder ViewHolder que contiene la vista.
* @param position posición del ítem en la lista.
*/
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
AniListMedia item = mediaList.get(position);
@ -69,6 +92,7 @@ public class AniListSearchAdapter extends RecyclerView.Adapter<AniListSearchAdap
ApiService apiService = ApiClient.getClient().create(ApiService.class);
if (mediaType.equals("ANIME")) {
// Crear objeto AnimeEntity
AnimeEntity anime = new AnimeEntity(
item.getId(),
item.getTitle(),
@ -81,12 +105,13 @@ public class AniListSearchAdapter extends RecyclerView.Adapter<AniListSearchAdap
);
anime.setType("TV");
// Llamada para insertar anime
apiService.insertAnime(anime).enqueue(new Callback<>() {
@Override
public void onResponse(Call call, Response response) {
Toast.makeText(context, "Anime añadido", Toast.LENGTH_SHORT).show();
// 🔁 Registrar actividad usando Map
// Registrar actividad
Map<String, Object> body = new HashMap<>();
body.put("userId", userId);
body.put("action", "añadió");
@ -96,7 +121,9 @@ public class AniListSearchAdapter extends RecyclerView.Adapter<AniListSearchAdap
apiService.postActivity(body).enqueue(new Callback<>() {
@Override
public void onResponse(Call call, Response response) {}
public void onResponse(Call call, Response response) {
// Actividad registrada (sin acción adicional)
}
@Override
public void onFailure(Call call, Throwable t) {
@ -112,6 +139,7 @@ public class AniListSearchAdapter extends RecyclerView.Adapter<AniListSearchAdap
});
} else {
// Crear objeto MangaEntity
MangaEntity manga = new MangaEntity(
item.getId(),
item.getTitle(),
@ -124,6 +152,7 @@ public class AniListSearchAdapter extends RecyclerView.Adapter<AniListSearchAdap
);
manga.setType("Manga");
// Llamada para insertar manga
apiService.insertManga(manga).enqueue(new Callback<>() {
@Override
public void onResponse(Call call, Response response) {
@ -156,16 +185,35 @@ public class AniListSearchAdapter extends RecyclerView.Adapter<AniListSearchAdap
});
}
/**
* Devuelve la cantidad de elementos en la lista.
*
* @return número total de ítems en la búsqueda.
*/
@Override
public int getItemCount() {
return mediaList.size();
}
/**
* Clase interna que representa un ítem individual del RecyclerView.
*/
public static class ViewHolder extends RecyclerView.ViewHolder {
/** Título del anime/manga. */
TextView title;
/** Imagen de portada del anime/manga. */
ImageView cover;
/** Botón para añadir el ítem a la lista del usuario. */
Button btnAdd;
/**
* Constructor del ViewHolder.
*
* @param itemView vista del ítem individual.
*/
public ViewHolder(View itemView) {
super(itemView);
title = itemView.findViewById(R.id.itemTitle);

View File

@ -1,6 +1,5 @@
package com.santiparra.yomitrack.model.adapters.anime_adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -18,17 +17,41 @@ import com.santiparra.yomitrack.db.entities.AnimeEntity;
import java.util.ArrayList;
import java.util.List;
/**
* Adaptador de RecyclerView para mostrar una lista de animes con soporte para múltiples tipos de vista:
* normal, compacta y grande. Permite manejar clics normales y prolongados sobre los ítems.
*/
public class AnimeAdapter extends RecyclerView.Adapter<AnimeAdapter.AnimeViewHolder> {
/** Vista normal por defecto. */
public static final int VIEW_NORMAL = 0;
/** Vista compacta. */
public static final int VIEW_COMPACT = 1;
/** Vista ampliada. */
public static final int VIEW_LARGE = 2;
/** Lista de animes a mostrar. */
private List<AnimeEntity> animeList;
/** Tipo de vista actual. */
private int viewType;
/** Listener para clics normales (edición). */
private final OnAnimeClickListener onEditClick;
/** Listener para clics prolongados (acciones extendidas). */
private final OnAnimeClickListener onLongClick;
/**
* Constructor del adaptador.
*
* @param animeList lista de animes.
* @param viewType tipo de vista a usar (normal, compacta, grande).
* @param onEditClick callback para clics normales.
* @param onLongClick callback para clics prolongados.
*/
public AnimeAdapter(List<AnimeEntity> animeList, int viewType,
OnAnimeClickListener onEditClick,
OnAnimeClickListener onLongClick) {
@ -38,13 +61,23 @@ public class AnimeAdapter extends RecyclerView.Adapter<AnimeAdapter.AnimeViewHol
this.onLongClick = onLongClick;
}
/**
* Cambia el tipo de vista del adaptador.
*
* @param viewType nuevo tipo de vista.
*/
public void setViewType(int viewType) {
this.viewType = viewType;
notifyDataSetChanged();
}
/**
* Actualiza la lista de animes mostrada.
*
* @param newList nueva lista de animes.
*/
public void updateList(List<AnimeEntity> newList) {
this.animeList = newList != null ? newList : new ArrayList<>();
this.animeList = new ArrayList<>(newList); // o .clear() + .addAll()
notifyDataSetChanged();
}
@ -145,11 +178,37 @@ public class AnimeAdapter extends RecyclerView.Adapter<AnimeAdapter.AnimeViewHol
return viewType;
}
/**
* ViewHolder que representa un ítem individual del RecyclerView de animes.
*/
public static class AnimeViewHolder extends RecyclerView.ViewHolder {
/** Imagen de portada del anime. */
ImageView imageCover;
TextView textTitle, textStatus, textProgress, textScore, textType;
/** Título del anime. */
TextView textTitle;
/** Texto que muestra el estado y tipo del anime. */
TextView textStatus;
/** Texto que muestra el progreso (episodios vistos). */
TextView textProgress;
/** Texto que muestra la puntuación. */
TextView textScore;
/** Texto que muestra el tipo de anime. */
TextView textType;
/** Punto de color que indica el estado visualmente. */
View statusDot;
/**
* Constructor del ViewHolder.
*
* @param itemView vista inflada del ítem.
*/
public AnimeViewHolder(@NonNull View itemView) {
super(itemView);
imageCover = itemView.findViewById(R.id.imageCover);
@ -162,7 +221,15 @@ public class AnimeAdapter extends RecyclerView.Adapter<AnimeAdapter.AnimeViewHol
}
}
/**
* Interfaz para manejar clics sobre un anime.
*/
public interface OnAnimeClickListener {
/**
* Método invocado al hacer clic en un ítem de anime.
*
* @param anime objeto de anime clicado.
*/
void onClick(AnimeEntity anime);
}
}

View File

@ -1,65 +0,0 @@
package com.santiparra.yomitrack.model.adapters.browser_section_adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.santiparra.yomitrack.R;
import com.santiparra.yomitrack.model.ItemModel;
import java.util.List;
public class BrowseGridAdapter extends RecyclerView.Adapter<BrowseGridAdapter.ViewHolder> {
private final List<ItemModel> items;
public BrowseGridAdapter(List<ItemModel> items) {
this.items = items;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_browse_card, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
ItemModel item = items.get(position);
holder.title.setText(item.getTitle());
Glide.with(holder.itemView.getContext())
.load(item.getImageUrl())
.placeholder(R.drawable.placeholder_image)
.into(holder.cover);
// 👉 Animación
Animation animation = AnimationUtils.loadAnimation(holder.itemView.getContext(), R.anim.item_animation_fade_scale);
holder.itemView.startAnimation(animation);
}
@Override
public int getItemCount() {
return items.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
ImageView cover;
TextView title;
ViewHolder(View itemView) {
super(itemView);
cover = itemView.findViewById(R.id.imageViewCover);
title = itemView.findViewById(R.id.textViewTitle);
}
}
}

View File

@ -1,60 +0,0 @@
package com.santiparra.yomitrack.model.adapters.browser_section_adapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.santiparra.yomitrack.R;
import com.santiparra.yomitrack.model.BrowseSection;
import com.santiparra.yomitrack.model.adapters.homeadapter.HomeAdapter;
import java.util.List;
public class BrowseSectionAdapter extends RecyclerView.Adapter<BrowseSectionAdapter.BrowseViewHolder> {
private final List<BrowseSection> sectionList;
public BrowseSectionAdapter(List<BrowseSection> sectionList) {
this.sectionList = sectionList;
}
@NonNull
@Override
public BrowseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_section, parent, false);
return new BrowseViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull BrowseViewHolder holder, int position) {
BrowseSection section = sectionList.get(position);
holder.sectionTitle.setText(section.getTitle());
HomeAdapter adapter = new HomeAdapter(section.getItems(), section.getTitle());
holder.recyclerView.setLayoutManager(new LinearLayoutManager(holder.itemView.getContext(), LinearLayoutManager.HORIZONTAL, false));
holder.recyclerView.setAdapter(adapter);
}
@Override
public int getItemCount() {
return sectionList.size();
}
static class BrowseViewHolder extends RecyclerView.ViewHolder {
TextView sectionTitle;
RecyclerView recyclerView;
public BrowseViewHolder(@NonNull View itemView) {
super(itemView);
sectionTitle = itemView.findViewById(R.id.sectionTitle);
recyclerView = itemView.findViewById(R.id.sectionRecyclerView);
}
}
}

View File

@ -1,71 +0,0 @@
package com.santiparra.yomitrack.model.adapters.homeadapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.santiparra.yomitrack.R;
import com.santiparra.yomitrack.model.ItemModel;
import com.santiparra.yomitrack.model.adapters.airing.AiringViewHolder;
import com.santiparra.yomitrack.model.adapters.airing.AnimeViewHolder;
import java.util.List;
public class HomeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<ItemModel> itemList;
private String sectionTitle;
private static final int TYPE_AIRING = 0;
private static final int TYPE_ANIME_MANGA = 1;
public HomeAdapter(List<ItemModel> itemList, String sectionTitle) {
this.itemList = itemList;
this.sectionTitle = sectionTitle;
}
@Override
public int getItemViewType(int position) {
if (sectionTitle.equalsIgnoreCase("Airing")) {
return TYPE_AIRING;
} else {
return TYPE_ANIME_MANGA;
}
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_media_card, parent, false);
// Ajustamos manualmente el ancho
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
view.setLayoutParams(layoutParams);
if (viewType == TYPE_AIRING) {
return new AiringViewHolder(view);
} else {
return new AnimeViewHolder(view);
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
ItemModel item = itemList.get(position);
if (holder instanceof AiringViewHolder) {
((AiringViewHolder) holder).bind(item);
} else if (holder instanceof AnimeViewHolder) {
((AnimeViewHolder) holder).bind(item);
}
}
@Override
public int getItemCount() {
return itemList.size();
}
}

View File

@ -17,20 +17,48 @@ import com.santiparra.yomitrack.model.ItemModel;
import java.util.List;
/**
* Adaptador para el RecyclerView del fragmento de inicio (Home).
* Muestra tarjetas con imagen, título y progreso de animes o mangas recientes.
*/
public class HomeCardAdapter extends RecyclerView.Adapter<HomeCardAdapter.ViewHolder> {
/**
* Interfaz para manejar clics en los ítems del RecyclerView.
*/
public interface OnItemClickListener {
/**
* Se llama cuando el usuario hace clic sobre un ítem.
*
* @param item el ítem seleccionado.
*/
void onItemClick(ItemModel item);
}
/** Lista de ítems a mostrar (animes o mangas). */
private final List<ItemModel> itemList;
/** Listener que maneja clics en los ítems. */
private final OnItemClickListener listener;
/**
* Constructor del adaptador.
*
* @param itemList lista de ítems a mostrar.
* @param listener manejador de eventos de clic.
*/
public HomeCardAdapter(List<ItemModel> itemList, OnItemClickListener listener) {
this.itemList = itemList;
this.listener = listener;
}
/**
* Infla la vista para un ítem del RecyclerView.
*
* @param parent vista contenedora.
* @param viewType tipo de vista (no usado aquí).
* @return instancia de ViewHolder.
*/
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@ -38,26 +66,58 @@ public class HomeCardAdapter extends RecyclerView.Adapter<HomeCardAdapter.ViewHo
return new ViewHolder(view);
}
/**
* Asocia los datos del ítem con su vista.
*
* @param holder ViewHolder con las vistas del ítem.
* @param position posición del ítem en la lista.
*/
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
ItemModel item = itemList.get(position);
holder.title.setText(item.getTitle());
holder.progress.setText(item.getProgress());
Glide.with(holder.itemView.getContext()).load(item.getImageUrl()).into(holder.cover);
Glide.with(holder.itemView.getContext())
.load(item.getImageUrl())
.placeholder(R.drawable.rectangle_placeholder)
.into(holder.cover);
holder.card.setOnClickListener(v -> listener.onItemClick(item));
}
/**
* Devuelve la cantidad de ítems en la lista.
*
* @return número total de ítems.
*/
@Override
public int getItemCount() {
return itemList.size();
}
/**
* ViewHolder que representa cada tarjeta (ítem) en el RecyclerView.
*/
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView title, progress;
/** Texto que muestra el título del ítem. */
TextView title;
/** Texto que muestra el progreso del ítem. */
TextView progress;
/** Imagen de portada del ítem. */
ImageView cover;
/** Tarjeta contenedora del ítem. */
CardView card;
/**
* Constructor del ViewHolder.
*
* @param itemView vista del ítem inflada desde el layout.
*/
public ViewHolder(@NonNull View itemView) {
super(itemView);
title = itemView.findViewById(R.id.itemTitle);

View File

@ -1,6 +1,5 @@
package com.santiparra.yomitrack.model.adapters.manga_adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -17,17 +16,41 @@ import com.santiparra.yomitrack.db.entities.MangaEntity;
import java.util.List;
/**
* Adaptador para mostrar una lista de mangas en un RecyclerView con distintos tipos de vista:
* normal, compacta y grande. Soporta clics normales y prolongados.
*/
public class MangaAdapter extends RecyclerView.Adapter<MangaAdapter.MangaViewHolder> {
/** Vista estándar. */
public static final int VIEW_NORMAL = 0;
/** Vista compacta. */
public static final int VIEW_COMPACT = 1;
/** Vista grande. */
public static final int VIEW_LARGE = 2;
/** Lista de mangas a mostrar. */
private List<MangaEntity> mangaList;
/** Tipo de vista actual. */
private int viewType;
/** Listener para clic corto (edición). */
private final OnMangaClickListener onEditClick;
/** Listener para clic prolongado (acciones extendidas). */
private final OnMangaClickListener onLongClick;
/**
* Constructor del adaptador.
*
* @param mangaList lista de mangas a mostrar.
* @param viewType tipo de vista deseado.
* @param onEditClick callback para clics normales.
* @param onLongClick callback para clics prolongados.
*/
public MangaAdapter(List<MangaEntity> mangaList, int viewType,
OnMangaClickListener onEditClick,
OnMangaClickListener onLongClick) {
@ -124,21 +147,51 @@ public class MangaAdapter extends RecyclerView.Adapter<MangaAdapter.MangaViewHol
});
}
/**
* Devuelve la cantidad de mangas en la lista.
*
* @return número total de ítems.
*/
@Override
public int getItemCount() {
return mangaList != null ? mangaList.size() : 0;
}
/**
* Devuelve el tipo de vista para el ítem en la posición dada.
*
* @param position posición del ítem.
* @return tipo de vista.
*/
@Override
public int getItemViewType(int position) {
return viewType;
}
/**
* Reemplaza la lista actual por una nueva y actualiza el adaptador.
*
* @param newList nueva lista de mangas.
*/
public void updateList(List<MangaEntity> newList) {
mangaList.clear();
mangaList.addAll(newList);
notifyDataSetChanged();
}
/**
* ViewHolder para representar un ítem de manga en el RecyclerView.
*/
public static class MangaViewHolder extends RecyclerView.ViewHolder {
ImageView imageCover;
TextView textTitle, textStatus, textProgress, textScore, textType;
View statusDot;
/**
* Constructor del ViewHolder.
*
* @param itemView vista inflada del ítem.
*/
public MangaViewHolder(@NonNull View itemView) {
super(itemView);
imageCover = itemView.findViewById(R.id.imageCover);
@ -151,12 +204,15 @@ public class MangaAdapter extends RecyclerView.Adapter<MangaAdapter.MangaViewHol
}
}
public void updateList(List<MangaEntity> newList) {
this.mangaList = newList;
notifyDataSetChanged();
}
/**
* Interfaz para manejar clics sobre ítems de manga.
*/
public interface OnMangaClickListener {
/**
* Se ejecuta cuando se hace clic en un manga.
*
* @param manga el ítem clicado.
*/
void onClick(MangaEntity manga);
}
}

View File

@ -15,25 +15,58 @@ import com.santiparra.yomitrack.model.AniListMedia;
import java.util.List;
/**
* Adaptador para mostrar resultados de búsqueda de mangas desde AniList.
* Utilizado en el fragmento de búsqueda para permitir seleccionar un manga.
*/
public class MangaSearchAdapter extends RecyclerView.Adapter<MangaSearchAdapter.SearchViewHolder> {
/** Lista de mangas obtenidos desde la API de AniList. */
private List<AniListMedia> mangaList;
/** Listener para manejar clics en los ítems del RecyclerView. */
private final OnMangaClickListener clickListener;
/**
* Interfaz que define el callback cuando se hace clic en un manga.
*/
public interface OnMangaClickListener {
/**
* Método invocado al hacer clic sobre un manga.
*
* @param manga objeto de AniList clicado.
*/
void onClick(AniListMedia manga);
}
/**
* Constructor del adaptador.
*
* @param mangaList lista de resultados de búsqueda.
* @param clickListener listener para manejar el clic en cada ítem.
*/
public MangaSearchAdapter(List<AniListMedia> mangaList, OnMangaClickListener clickListener) {
this.mangaList = mangaList;
this.clickListener = clickListener;
}
/**
* Reemplaza la lista de mangas actual por una nueva y actualiza el RecyclerView.
*
* @param mangaList nueva lista de mangas.
*/
public void setMangaList(List<AniListMedia> mangaList) {
this.mangaList = mangaList;
notifyDataSetChanged();
}
/**
* Infla el layout para un ítem individual del RecyclerView.
*
* @param parent el ViewGroup padre.
* @param viewType tipo de vista (no utilizado aquí).
* @return instancia del ViewHolder.
*/
@NonNull
@Override
public SearchViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@ -41,6 +74,12 @@ public class MangaSearchAdapter extends RecyclerView.Adapter<MangaSearchAdapter.
return new SearchViewHolder(view);
}
/**
* Asocia los datos del manga con la vista.
*
* @param holder ViewHolder que contiene la vista.
* @param position posición del ítem en la lista.
*/
@Override
public void onBindViewHolder(@NonNull SearchViewHolder holder, int position) {
AniListMedia manga = mangaList.get(position);
@ -54,15 +93,31 @@ public class MangaSearchAdapter extends RecyclerView.Adapter<MangaSearchAdapter.
holder.itemView.setOnClickListener(v -> clickListener.onClick(manga));
}
/**
* Devuelve la cantidad total de mangas en la lista.
*
* @return tamaño de la lista.
*/
@Override
public int getItemCount() {
return mangaList != null ? mangaList.size() : 0;
}
/**
* ViewHolder que representa cada ítem del RecyclerView.
*/
static class SearchViewHolder extends RecyclerView.ViewHolder {
/** Imagen de portada del manga. */
ImageView imageCover;
/** Título del manga. */
TextView title;
/**
* Constructor del ViewHolder.
*
* @param itemView vista inflada del ítem.
*/
public SearchViewHolder(@NonNull View itemView) {
super(itemView);
imageCover = itemView.findViewById(R.id.imageCover);

View File

@ -7,6 +7,7 @@ import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
@ -18,11 +19,24 @@ import com.santiparra.yomitrack.model.RecentActivityModel;
import java.util.List;
/**
* Adaptador para mostrar la actividad reciente del usuario en forma de tarjetas.
* Cada tarjeta puede incluir una acción, portada, comentarios, botón de like y comentar.
*/
public class RecentActivityAdapter extends RecyclerView.Adapter<RecentActivityAdapter.ActivityViewHolder> {
/** Lista de actividades recientes. */
private List<RecentActivityModel> activityList;
/** ID del usuario actualmente logueado (para comentarios y likes). */
private final int currentUserId;
/**
* Constructor del adaptador.
*
* @param activityList lista de actividades a mostrar.
* @param currentUserId ID del usuario actual.
*/
public RecentActivityAdapter(List<RecentActivityModel> activityList, int currentUserId) {
this.activityList = activityList;
this.currentUserId = currentUserId;
@ -31,7 +45,8 @@ public class RecentActivityAdapter extends RecyclerView.Adapter<RecentActivityAd
@NonNull
@Override
public ActivityViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_activity_card, parent, false);
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_activity_card, parent, false);
return new ActivityViewHolder(view);
}
@ -49,9 +64,11 @@ public class RecentActivityAdapter extends RecyclerView.Adapter<RecentActivityAd
.placeholder(R.drawable.placeholder_image)
.into(holder.image);
// Limpiar contenedor de comentarios antes de agregar los nuevos
holder.commentContainer.removeAllViews();
LayoutInflater inflater = LayoutInflater.from(holder.itemView.getContext());
// Mostrar comentarios
for (CommentModel comment : activity.comments) {
View commentView = inflater.inflate(R.layout.item_comment, holder.commentContainer, false);
@ -75,39 +92,16 @@ public class RecentActivityAdapter extends RecyclerView.Adapter<RecentActivityAd
likeButton.setImageResource(comment.isLiked() ? R.drawable.ic_heart_filled : R.drawable.ic_heart_outline);
likeButton.setColorFilter(commentView.getContext().getColor(comment.isLiked() ? R.color.pink : R.color.gray));
likeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean newLike = !comment.isLiked();
comment.setLiked(newLike);
likeButton.setImageResource(newLike ? R.drawable.ic_heart_filled : R.drawable.ic_heart_outline);
likeButton.setColorFilter(commentView.getContext().getColor(newLike ? R.color.pink : R.color.gray));
}
// Manejo de likes en comentarios
likeButton.setOnClickListener(v -> {
boolean newLike = !comment.isLiked();
comment.setLiked(newLike);
likeButton.setImageResource(newLike ? R.drawable.ic_heart_filled : R.drawable.ic_heart_outline);
likeButton.setColorFilter(commentView.getContext().getColor(newLike ? R.color.pink : R.color.gray));
});
replyButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int adapterPos = holder.getAdapterPosition();
if (adapterPos == RecyclerView.NO_POSITION) return;
RecentActivityModel activityItem = activityList.get(adapterPos);
CommentDialog dialog = new CommentDialog(
holder.itemView.getContext(),
currentUserId,
activityItem.getId(),
() -> notifyItemChanged(adapterPos),
comment.getUsername()
);
dialog.show();
}
});
holder.commentContainer.addView(commentView);
}
holder.commentButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Manejo de respuesta a un comentario
replyButton.setOnClickListener(v -> {
int adapterPos = holder.getAdapterPosition();
if (adapterPos == RecyclerView.NO_POSITION) return;
RecentActivityModel activityItem = activityList.get(adapterPos);
@ -115,39 +109,72 @@ public class RecentActivityAdapter extends RecyclerView.Adapter<RecentActivityAd
holder.itemView.getContext(),
currentUserId,
activityItem.getId(),
() -> notifyItemChanged(adapterPos)
() -> notifyItemChanged(adapterPos),
comment.getUsername()
);
dialog.show();
}
});
holder.commentContainer.addView(commentView);
}
// Botón para añadir nuevo comentario a la actividad
holder.commentButton.setOnClickListener(v -> {
int adapterPos = holder.getAdapterPosition();
if (adapterPos == RecyclerView.NO_POSITION) return;
RecentActivityModel activityItem = activityList.get(adapterPos);
CommentDialog dialog = new CommentDialog(
holder.itemView.getContext(),
currentUserId,
activityItem.getId(),
() -> notifyItemChanged(adapterPos)
);
dialog.show();
});
holder.likeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
activity.liked = !activity.liked;
holder.likeButton.setImageResource(activity.liked ? R.drawable.ic_heart_filled : R.drawable.ic_heart_outline);
holder.likeButton.setColorFilter(holder.itemView.getContext().getColor(
activity.liked ? R.color.pink : R.color.textPrimary));
}
// Botón de like para la actividad
holder.likeButton.setOnClickListener(v -> {
activity.liked = !activity.liked;
holder.likeButton.setImageResource(activity.liked ? R.drawable.ic_heart_filled : R.drawable.ic_heart_outline);
holder.likeButton.setColorFilter(holder.itemView.getContext().getColor(
activity.liked ? R.color.pink : R.color.textPrimary));
});
}
/**
* Devuelve la cantidad de actividades en la lista.
*
* @return tamaño de la lista.
*/
@Override
public int getItemCount() {
return activityList.size();
}
/**
* Reemplaza la lista actual por una nueva y actualiza la vista.
*
* @param newList nueva lista de actividades.
*/
public void updateData(List<RecentActivityModel> newList) {
this.activityList = newList;
notifyDataSetChanged();
}
/**
* ViewHolder que representa una tarjeta de actividad reciente.
*/
static class ActivityViewHolder extends RecyclerView.ViewHolder {
TextView user, action, title, time;
ImageView image;
ImageButton likeButton, commentButton;
LinearLayout commentContainer;
/**
* Constructor del ViewHolder.
*
* @param itemView vista inflada del ítem.
*/
public ActivityViewHolder(@NonNull View itemView) {
super(itemView);
user = itemView.findViewById(R.id.activityUser);

View File

@ -1,63 +0,0 @@
package com.santiparra.yomitrack.model.adapters.sectionadapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.santiparra.yomitrack.R;
import com.santiparra.yomitrack.model.ItemModel;
import com.santiparra.yomitrack.model.adapters.homeadapter.HomeAdapter;
import java.util.List;
import java.util.Map;
public class SectionAdapter extends RecyclerView.Adapter<SectionAdapter.SectionViewHolder> {
private final List<String> sectionTitles;
private final Map<String, List<ItemModel>> sectionItems;
public SectionAdapter(List<String> sectionTitles, Map<String, List<ItemModel>> sectionItems) {
this.sectionTitles = sectionTitles;
this.sectionItems = sectionItems;
}
@NonNull
@Override
public SectionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_section, parent, false);
return new SectionViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull SectionViewHolder holder, int position) {
String sectionTitle = sectionTitles.get(position);
holder.title.setText(sectionTitle);
List<ItemModel> fullList = sectionItems.get(sectionTitle);
HomeAdapter adapter = new HomeAdapter(fullList, sectionTitle);
holder.recyclerView.setLayoutManager(new LinearLayoutManager(holder.itemView.getContext(), LinearLayoutManager.HORIZONTAL, false));
holder.recyclerView.setAdapter(adapter);
}
@Override
public int getItemCount() {
return sectionTitles.size();
}
static class SectionViewHolder extends RecyclerView.ViewHolder {
TextView title;
RecyclerView recyclerView;
public SectionViewHolder(@NonNull View itemView) {
super(itemView);
title = itemView.findViewById(R.id.sectionTitle);
recyclerView = itemView.findViewById(R.id.sectionRecyclerView);
}
}
}

View File

@ -75,18 +75,12 @@ public class AddAnimeFragment extends Fragment {
private void setupSpinners() {
ArrayAdapter<CharSequence> statusAdapter = ArrayAdapter.createFromResource(
requireContext(),
R.array.anime_status_array,
R.layout.item_spinner
);
statusAdapter.setDropDownViewResource(R.layout.item_spinner); // blanco también al desplegar
requireContext(), R.array.anime_status_array, R.layout.item_spinner);
statusAdapter.setDropDownViewResource(R.layout.item_spinner);
statusSpinner.setAdapter(statusAdapter);
ArrayAdapter<CharSequence> typeAdapter = ArrayAdapter.createFromResource(
requireContext(),
R.array.anime_type_array,
R.layout.item_spinner
);
requireContext(), R.array.anime_type_array, R.layout.item_spinner);
typeAdapter.setDropDownViewResource(R.layout.item_spinner);
typeSpinner.setAdapter(typeAdapter);
}
@ -128,23 +122,16 @@ public class AddAnimeFragment extends Fragment {
String status = statusSpinner.getSelectedItem().toString();
String type = typeSpinner.getSelectedItem().toString();
int score = 0;
int progress = 0;
try {
score = Integer.parseInt(scoreEditText.getText().toString());
progress = Integer.parseInt(progressEditText.getText().toString());
} catch (NumberFormatException ignored) {
}
int score = parseIntOrZero(scoreEditText.getText().toString());
int progress = parseIntOrZero(progressEditText.getText().toString());
AnimeEntity anime = new AnimeEntity();
anime.setUserId(userId);
anime.setTitle(selected.getTitle());
if (selected.getImageUrl() == null || selected.getImageUrl().isEmpty()) {
selectedImageUrl = "android.resource://" + requireContext().getPackageName() + "/" + R.drawable.sample_cover;
} else {
selectedImageUrl = selected.getImageUrl();
}
selectedImageUrl = (selected.getImageUrl() == null || selected.getImageUrl().isEmpty())
? "android.resource://" + requireContext().getPackageName() + "/" + R.drawable.sample_cover
: selected.getImageUrl();
anime.setImageUrl(selectedImageUrl);
anime.setStatus(status);
@ -158,6 +145,7 @@ public class AddAnimeFragment extends Fragment {
if (response.isSuccessful() && response.body() != null) {
Toast.makeText(getContext(), response.body().getMessage(), Toast.LENGTH_SHORT).show();
registrarActividad(anime.getTitle());
notificarAñadido();
requireActivity().getSupportFragmentManager().popBackStack();
} else {
Toast.makeText(getContext(), "Error al guardar anime", Toast.LENGTH_SHORT).show();
@ -182,17 +170,29 @@ public class AddAnimeFragment extends Fragment {
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
Log.d("ACTIVITY_POST", "Código de respuesta: " + response.code());
if (!response.isSuccessful()) {
Log.e("ACTIVITY_POST", "Error en response: " + response.errorBody());
}
}
@Override
public void onFailure(Call<JsonObject> call, Throwable t) {
Log.e("ACTIVITY_POST", "Error al registrar actividad: " + t.getMessage(), t);
if (!isAdded()) return;
Toast.makeText(getContext(), "Error al registrar actividad", Toast.LENGTH_SHORT).show();
if (isAdded()) {
Toast.makeText(getContext(), "Error al registrar actividad", Toast.LENGTH_SHORT).show();
}
}
});
}
private void notificarAñadido() {
Bundle result = new Bundle();
result.putBoolean("anime_added", true);
getParentFragmentManager().setFragmentResult("anime_add_request", result);
}
private int parseIntOrZero(String value) {
try {
return Integer.parseInt(value.trim());
} catch (NumberFormatException e) {
return 0;
}
}
}

View File

@ -90,7 +90,6 @@ public class AddMangaFragment extends Fragment {
typeSpinner.setAdapter(typeAdapter);
}
private void setupRecycler() {
searchAdapter = new MangaSearchAdapter(new ArrayList<>(), this::onMangaSelected);
searchResults.setAdapter(searchAdapter);
@ -98,8 +97,7 @@ public class AddMangaFragment extends Fragment {
private void setupSearch() {
searchEditText.setOnEditorActionListener((TextView v, int actionId, KeyEvent event) -> {
if (actionId == EditorInfo.IME_ACTION_SEARCH ||
(event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) {
if (actionId == EditorInfo.IME_ACTION_SEARCH || (event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) {
String query = searchEditText.getText().toString().trim();
if (!query.isEmpty()) {
api.searchAniList(query, "MANGA").enqueue(new Callback<List<AniListMedia>>() {
@ -133,8 +131,7 @@ public class AddMangaFragment extends Fragment {
try {
score = Integer.parseInt(scoreEditText.getText().toString());
progress = Integer.parseInt(progressEditText.getText().toString());
} catch (NumberFormatException ignored) {
}
} catch (NumberFormatException ignored) {}
MangaEntity manga = new MangaEntity();
manga.setUserId(userId);
@ -158,6 +155,9 @@ public class AddMangaFragment extends Fragment {
if (response.isSuccessful() && response.body() != null) {
Toast.makeText(getContext(), response.body().getMessage(), Toast.LENGTH_SHORT).show();
registrarActividad(manga.getTitle());
Bundle result = new Bundle();
result.putBoolean("manga_added", true);
getParentFragmentManager().setFragmentResult("manga_add_request", result);
requireActivity().getSupportFragmentManager().popBackStack();
} else {
Toast.makeText(getContext(), "Error al guardar manga", Toast.LENGTH_SHORT).show();
@ -180,11 +180,7 @@ public class AddMangaFragment extends Fragment {
api.postActivity(actividad).enqueue(new Callback<JsonObject>() {
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
if (!response.isSuccessful()) {
// Puedes logear el error si lo deseas
}
}
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {}
@Override
public void onFailure(Call<JsonObject> call, Throwable t) {

View File

@ -59,15 +59,12 @@ public class FragmentAnime extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_alist, container, false);
}
@Override
public void onViewCreated(@NonNull View view,
@Nullable Bundle savedInstanceState) {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initViews(view);
@ -76,6 +73,7 @@ public class FragmentAnime extends Fragment {
setupSearchListener();
setupFab(view);
setupInsets(view);
setupResultListener();
SharedPreferences prefs = requireContext().getSharedPreferences("user_session", Context.MODE_PRIVATE);
userId = prefs.getInt("user_id", -1);
@ -84,7 +82,7 @@ public class FragmentAnime extends Fragment {
Toast.makeText(getContext(), "Error: sesión no iniciada", Toast.LENGTH_SHORT).show();
return;
}
// Mostrar el nombre del usuario
String username = prefs.getString("username", "Usuario");
TextView textViewUsername = view.findViewById(R.id.textViewUsername);
textViewUsername.setText(username);
@ -124,6 +122,30 @@ public class FragmentAnime extends Fragment {
});
}
private void setupResultListener() {
getParentFragmentManager().setFragmentResultListener("anime_add_request", this, (requestKey, bundle) -> {
if (bundle.getBoolean("anime_added", false)) {
currentPage = 1;
animeList.clear();
adapter.updateList(new ArrayList<>());
loadMoreAnimes(currentPage);
}
});
getParentFragmentManager().setFragmentResultListener("anime_delete_request", this, (requestKey, bundle) -> {
int deletedId = bundle.getInt("anime_id", -1);
if (deletedId != -1) {
for (int i = 0; i < animeList.size(); i++) {
if (animeList.get(i).getId() == deletedId) {
animeList.remove(i);
adapter.updateList(animeList);
break;
}
}
}
});
}
private void setupViewButtons() {
btnViewCompact.setOnClickListener(v -> setViewType(AnimeAdapter.VIEW_COMPACT));
btnViewNormal.setOnClickListener(v -> setViewType(AnimeAdapter.VIEW_NORMAL));
@ -197,9 +219,8 @@ public class FragmentAnime extends Fragment {
if (response.isSuccessful() && response.body() != null) {
Toast.makeText(requireContext(), response.body().getMessage(), Toast.LENGTH_SHORT).show();
currentPage = 1;
animeList.clear();
loadMoreAnimes(currentPage);
animeList.remove(anime);
adapter.updateList(animeList);
} else {
Toast.makeText(requireContext(), "Error al eliminar", Toast.LENGTH_SHORT).show();
}
@ -225,8 +246,15 @@ public class FragmentAnime extends Fragment {
if (response.isSuccessful() && response.body() != null) {
List<AnimeEntity> nuevos = response.body().getData();
animeList.addAll(nuevos);
adapter.notifyItemRangeInserted(animeList.size() - nuevos.size(), nuevos.size());
if (page == 1) {
animeList.clear();
animeList.addAll(nuevos);
adapter.updateList(animeList);
} else {
int start = animeList.size();
animeList.addAll(nuevos);
adapter.notifyItemRangeInserted(start, nuevos.size());
}
isLoading = response.body().isHasNextPage();
} else {
isLoading = false;

View File

@ -67,7 +67,7 @@ public class EditAnimeFragment extends Fragment {
String[] typeArray = getResources().getStringArray(R.array.anime_type_array);
ArrayAdapter<String> statusAdapter = new ArrayAdapter<>(requireContext(), R.layout.item_spinner, statusArray);
statusAdapter.setDropDownViewResource(R.layout.item_spinner); // Aplica color blanco en lista desplegable también
statusAdapter.setDropDownViewResource(R.layout.item_spinner);
spinnerStatus.setAdapter(statusAdapter);
ArrayAdapter<String> typeAdapter = new ArrayAdapter<>(requireContext(), R.layout.item_spinner, typeArray);
@ -142,8 +142,6 @@ public class EditAnimeFragment extends Fragment {
api.updateAnime(anime.getId(), anime).enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(Call<ApiResponse> call, Response<ApiResponse> response) {
Log.d("API_RESPONSE", "onResponse ejecutado: " + response.body());
if (!isAdded()) return;
if (response.isSuccessful()) {
@ -160,7 +158,6 @@ public class EditAnimeFragment extends Fragment {
@Override
public void onFailure(Call<ApiResponse> call, Throwable t) {
Log.e("API_RESPONSE", "onFailure ejecutado: " + t.getMessage(), t);
if (!isAdded()) return;
Toast.makeText(requireContext(), "Fallo en la conexión", Toast.LENGTH_SHORT).show();
}
@ -179,6 +176,10 @@ public class EditAnimeFragment extends Fragment {
Toast.makeText(requireContext(), "Anime eliminado", Toast.LENGTH_SHORT).show();
requireContext().getSharedPreferences("user_session", Context.MODE_PRIVATE)
.edit().putBoolean("refresh_profile", true).apply();
Bundle result = new Bundle();
result.putBoolean("anime_deleted", true);
result.putInt("anime_id", anime.getId());
getParentFragmentManager().setFragmentResult("anime_delete_request", result);
requireActivity().getSupportFragmentManager().popBackStack();
} else {
Toast.makeText(requireContext(), "Error al eliminar", Toast.LENGTH_SHORT).show();
@ -231,7 +232,6 @@ public class EditAnimeFragment extends Fragment {
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
Log.d("ACTIVITY_DELETE", "Actividad de eliminación registrada");
if (getParentFragment() instanceof FragmentProfile) {
((FragmentProfile) getParentFragment()).loadActivity();
}

View File

@ -61,30 +61,18 @@ public class EditMangaFragment extends Fragment {
fillFields();
String[] statusArray = getResources().getStringArray(R.array.manga_status_array);
String[] typeArray = getResources().getStringArray(R.array.manga_type_array);
ArrayAdapter<String> statusAdapter = new ArrayAdapter<>(requireContext(), R.layout.item_spinner, statusArray);
ArrayAdapter<String> statusAdapter = new ArrayAdapter<>(requireContext(), R.layout.item_spinner,
getResources().getStringArray(R.array.manga_status_array));
statusAdapter.setDropDownViewResource(R.layout.item_spinner);
spinnerStatus.setAdapter(statusAdapter);
ArrayAdapter<String> typeAdapter = new ArrayAdapter<>(requireContext(), R.layout.item_spinner, typeArray);
ArrayAdapter<String> typeAdapter = new ArrayAdapter<>(requireContext(), R.layout.item_spinner,
getResources().getStringArray(R.array.manga_type_array));
typeAdapter.setDropDownViewResource(R.layout.item_spinner);
spinnerType.setAdapter(typeAdapter);
for (int i = 0; i < statusArray.length; i++) {
if (statusArray[i].equalsIgnoreCase(manga.getStatus())) {
spinnerStatus.setSelection(i);
break;
}
}
for (int i = 0; i < typeArray.length; i++) {
if (typeArray[i].equalsIgnoreCase(manga.getType())) {
spinnerType.setSelection(i);
break;
}
}
setSpinnerSelection(spinnerStatus, manga.getStatus());
setSpinnerSelection(spinnerType, manga.getType());
buttonSave.setOnClickListener(v -> saveChanges());
@ -104,6 +92,15 @@ public class EditMangaFragment extends Fragment {
editTextProgress.setText(String.valueOf(manga.getProgress()));
}
private void setSpinnerSelection(Spinner spinner, String value) {
for (int i = 0; i < spinner.getCount(); i++) {
if (spinner.getItemAtPosition(i).toString().equalsIgnoreCase(value)) {
spinner.setSelection(i);
break;
}
}
}
private void saveChanges() {
if (manga == null) {
Toast.makeText(requireContext(), "Error: Manga no cargado", Toast.LENGTH_SHORT).show();
@ -128,14 +125,11 @@ public class EditMangaFragment extends Fragment {
return;
}
String status = spinnerStatus.getSelectedItem().toString();
String type = spinnerType.getSelectedItem().toString();
manga.setTitle(title);
manga.setScore(score);
manga.setProgress(progress);
manga.setStatus(status);
manga.setType(type);
manga.setStatus(spinnerStatus.getSelectedItem().toString());
manga.setType(spinnerType.getSelectedItem().toString());
api.updateManga(manga.getId(), manga).enqueue(new Callback<ApiResponse>() {
@Override
@ -144,7 +138,7 @@ public class EditMangaFragment extends Fragment {
if (response.isSuccessful() && response.body() != null) {
Toast.makeText(requireContext(), response.body().getMessage(), Toast.LENGTH_SHORT).show();
registrarActividad(manga.getTitle(), manga.getImageUrl());
registrarActividad("update de un manga", manga.getTitle(), manga.getImageUrl());
requireContext().getSharedPreferences("user_session", Context.MODE_PRIVATE)
.edit().putBoolean("refresh_profile", true).apply();
requireActivity().getSupportFragmentManager().popBackStack();
@ -162,7 +156,7 @@ public class EditMangaFragment extends Fragment {
}
private void deleteManga() {
registrarActividadEliminacionManga(manga.getTitle(), manga.getImageUrl());
registrarActividad("eliminó un manga", manga.getTitle(), manga.getImageUrl());
api.deleteManga(manga.getId()).enqueue(new Callback<ApiResponse>() {
@Override
@ -173,6 +167,12 @@ public class EditMangaFragment extends Fragment {
Toast.makeText(requireContext(), "Manga eliminado", Toast.LENGTH_SHORT).show();
requireContext().getSharedPreferences("user_session", Context.MODE_PRIVATE)
.edit().putBoolean("refresh_profile", true).apply();
Bundle result = new Bundle();
result.putBoolean("manga_deleted", true);
result.putInt("manga_id", manga.getId());
getParentFragmentManager().setFragmentResult("manga_delete_request", result);
requireActivity().getSupportFragmentManager().popBackStack();
} else {
Toast.makeText(requireContext(), "Error al eliminar", Toast.LENGTH_SHORT).show();
@ -187,44 +187,20 @@ public class EditMangaFragment extends Fragment {
});
}
private void registrarActividad(String titulo, String imagen) {
private void registrarActividad(String action, String titulo, String imagen) {
int userId = requireContext().getSharedPreferences("user_session", Context.MODE_PRIVATE).getInt("user_id", -1);
if (userId == -1) return;
Map<String, Object> actividad = new HashMap<>();
actividad.put("userId", userId);
actividad.put("action", "update de un manga");
actividad.put("action", action);
actividad.put("mediaTitle", titulo);
actividad.put("imageUrl", imagen);
api.postActivity(actividad).enqueue(new Callback<JsonObject>() {
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
Log.d("ACTIVITY_DELETE", "Actividad de edicion registrada");
}
@Override
public void onFailure(Call<JsonObject> call, Throwable t) {
Log.e("ACTIVITY_DELETE", "Error al registrar actividad: " + t.getMessage(), t);
}
});
}
private void registrarActividadEliminacionManga(String titulo, String imagen) {
int userId = requireContext().getSharedPreferences("user_session", Context.MODE_PRIVATE).getInt("user_id", -1);
if (userId == -1) return;
Map<String, Object> actividad = new HashMap<>();
actividad.put("userId", userId);
actividad.put("action", "eliminó un manga");
actividad.put("mediaTitle", titulo);
actividad.put("imageUrl", imagen);
api.postActivity(actividad).enqueue(new Callback<JsonObject>() {
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
Log.d("ACTIVITY_DELETE", "Actividad de eliminación registrada");
Log.d("ACTIVITY", "Actividad registrada: " + action);
if (getParentFragment() instanceof FragmentProfile) {
((FragmentProfile) getParentFragment()).loadActivity();
}
@ -232,10 +208,8 @@ public class EditMangaFragment extends Fragment {
@Override
public void onFailure(Call<JsonObject> call, Throwable t) {
Log.e("ACTIVITY_DELETE", "Error al registrar actividad: " + t.getMessage(), t);
Log.e("ACTIVITY", "Error al registrar actividad: " + t.getMessage(), t);
}
});
}
}

View File

@ -1,6 +1,4 @@
// FragmentHome.java
package com.santiparra.yomitrack.ui.fragments.home;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
@ -15,13 +13,11 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.google.gson.JsonObject;
import com.santiparra.yomitrack.R;

View File

@ -70,6 +70,7 @@ public class FragmentManga extends Fragment {
initViews(view);
setupListeners();
setupRecyclerView();
setupResultListeners();
api = ApiClient.getClient().create(ApiService.class);
SharedPreferences prefs = requireContext().getSharedPreferences("user_session", Context.MODE_PRIVATE);
@ -79,10 +80,9 @@ public class FragmentManga extends Fragment {
Toast.makeText(getContext(), "Error: sesión no iniciada", Toast.LENGTH_SHORT).show();
return;
}
// Mostrar el nombre del usuario
String username = prefs.getString("username", "Usuario");
TextView textViewUsername = view.findViewById(R.id.textViewUsername);
textViewUsername.setText(username);
((TextView) view.findViewById(R.id.textViewUsername)).setText(username);
setViewType(currentViewType);
loadMoreMangas(currentPage);
@ -151,15 +151,37 @@ public class FragmentManga extends Fragment {
});
}
private void setupResultListeners() {
getParentFragmentManager().setFragmentResultListener("manga_add_request", this, (key, bundle) -> {
if (bundle.getBoolean("manga_added", false)) {
currentPage = 1;
mangaList.clear();
adapter.updateList(new ArrayList<>());
loadMoreMangas(currentPage);
}
});
getParentFragmentManager().setFragmentResultListener("manga_delete_request", this, (key, bundle) -> {
int deletedId = bundle.getInt("manga_id", -1);
if (deletedId != -1) {
for (int i = 0; i < mangaList.size(); i++) {
if (mangaList.get(i).getId() == deletedId) {
mangaList.remove(i);
adapter.notifyItemRemoved(i);
break;
}
}
}
});
}
private void setViewType(int viewType) {
currentViewType = viewType;
if (viewType == MangaAdapter.VIEW_LARGE) {
recyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
} else {
recyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
}
adapter = new MangaAdapter(mangaList, viewType, this::showEditDialog, this::deleteManga);
recyclerView.setAdapter(adapter);
}
@ -187,12 +209,15 @@ public class FragmentManga extends Fragment {
@Override
public void onResponse(Call<ApiResponse> call, Response<ApiResponse> response) {
if (!isAdded()) return;
if (response.isSuccessful()) {
for (int i = 0; i < mangaList.size(); i++) {
if (mangaList.get(i).getId() == manga.getId()) {
mangaList.remove(i);
adapter.notifyItemRemoved(i);
break;
}
}
Toast.makeText(requireContext(), response.body() != null ? response.body().getMessage() : "Manga eliminado", Toast.LENGTH_SHORT).show();
currentPage = 1;
mangaList.clear();
loadMoreMangas(currentPage);
} else {
Toast.makeText(requireContext(), "Error al eliminar", Toast.LENGTH_SHORT).show();
}
@ -214,7 +239,6 @@ public class FragmentManga extends Fragment {
@Override
public void onResponse(Call<MangaPageResponse> call, Response<MangaPageResponse> response) {
if (!isAdded()) return;
if (response.isSuccessful() && response.body() != null) {
List<MangaEntity> nuevos = response.body().getData();
mangaList.addAll(nuevos);

Binary file not shown.

View File

@ -7,11 +7,10 @@
</shape>
</item>
<!-- Progreso (color por defecto, se sobrescribe desde Java) -->
<item android:id="@android:id/progress">
<clip>
<shape android:shape="rectangle">
<solid android:color="#FFFFFF" /> <!-- No uses referencia aquí -->
<solid android:color="#FFFFFF" />
<corners android:radius="50dp" />
</shape>
</clip>

View File

@ -59,13 +59,6 @@
android:textColor="@android:color/white"
android:layout_marginBottom="12dp" />
<Button
android:id="@+id/buttonGoToReset"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Ya tengo el código"
android:backgroundTint="#2C2F38"
android:textColor="@android:color/white" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</FrameLayout>

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="12dp"
android:paddingBottom="12dp">
<!-- Título de la sección -->
<TextView
android:id="@+id/sectionTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Section Title"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@color/textPrimary"
android:layout_marginBottom="8dp"
android:paddingStart="16dp"
android:paddingEnd="16dp" />
<!-- Lista horizontal de ítems -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/sectionRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:overScrollMode="never"
android:nestedScrollingEnabled="false" />
</LinearLayout>