diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 297c41e..55bc189 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -63,4 +63,6 @@ dependencies { // (Opcional) Logging para depurar peticiones implementation ("com.squareup.okhttp3:logging-interceptor:4.9.0") + + implementation ("com.google.code.gson:gson:2.10.1") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ece5962..c460c5f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,9 +13,10 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" + android:enableOnBackInvokedCallback="true" android:theme="@style/Theme.YomiTrack" android:networkSecurityConfig="@xml/network_security_config" - tools:targetApi="31"> + tools:targetApi="33"> diff --git a/app/src/main/java.zip b/app/src/main/java.zip index 87d8a96..bb06bcd 100644 Binary files a/app/src/main/java.zip and b/app/src/main/java.zip differ diff --git a/app/src/main/java/com/santiparra/yomitrack/api/ApiService.java b/app/src/main/java/com/santiparra/yomitrack/api/ApiService.java index 7620976..c143d55 100644 --- a/app/src/main/java/com/santiparra/yomitrack/api/ApiService.java +++ b/app/src/main/java/com/santiparra/yomitrack/api/ApiService.java @@ -3,9 +3,15 @@ package com.santiparra.yomitrack.api; import com.santiparra.yomitrack.db.entities.AnimeEntity; import com.santiparra.yomitrack.db.entities.MangaEntity; import com.santiparra.yomitrack.db.entities.UserEntity; -import com.santiparra.yomitrack.model.AniListAnime; +import com.santiparra.yomitrack.model.AniListMedia; +import com.santiparra.yomitrack.model.AnimePageResponse; import com.santiparra.yomitrack.model.LoginResponse; +import com.santiparra.yomitrack.model.MangaPageResponse; import com.santiparra.yomitrack.model.RegisterResponse; +import com.santiparra.yomitrack.model.UserStatsResponse; +import com.santiparra.yomitrack.utils.ActivityLog; + +import org.json.JSONObject; import java.util.List; import java.util.Map; @@ -23,7 +29,7 @@ public interface ApiService { // ---------------- Usuario ---------------- @POST("users/register") - Call registerUser(@Body Map request); + Call registerUser(@Body UserEntity user); @POST("users/login") Call loginUser(@Body UserEntity user); @@ -34,6 +40,14 @@ public interface ApiService { @GET("anime/list/{userId}") Call> getAnimeByUser(@Path("userId") int userId); + // Scroll infinito: obtener lista paginada + @GET("/anime/list/{userId}") + Call getAnimes( + @Path("userId") int userId, + @Query("page") int page, + @Query("size") int size + ); + @PUT("anime/{id}") Call updateAnime(@Path("id") int animeId, @Body AnimeEntity anime); @@ -47,16 +61,33 @@ public interface ApiService { @GET("manga/list/{userId}") Call> getMangaByUser(@Path("userId") int userId); + @GET("/manga/list/{userId}") + Call getMangas( + @Path("userId") int userId, + @Query("page") int page, + @Query("size") int size + ); + @PUT("manga/{id}") Call updateManga(@Path("id") int mangaId, @Body MangaEntity manga); @DELETE("manga/delete/{id}") Call deleteManga(@Path("id") int id); - // ---------------- AniList API ---------------- - @GET("/anilist/search") - Call> searchAnimeAniList(@Query("query") String query); + // ---------------- Activity ------------------- + + @GET("user/{id}/stats") + Call>> getUserStats(@Path("id") int userId); + + @GET("api/activity/list/{userId}") + Call> getActivityLog(@Path("userId") int userId); + @POST("activity/add") + Call postActivity(@Body Map body); + + + // ---------------- AniList API ---------------- + + @GET("anilist/search") + Call> searchAniList(@Query("query") String query, @Query("type") String type); - @GET("anilist/search/manga") - Call> searchMangaAniList(@Query("query") String query); } diff --git a/app/src/main/java/com/santiparra/yomitrack/db/entities/AnimeEntity.java b/app/src/main/java/com/santiparra/yomitrack/db/entities/AnimeEntity.java index e1df88b..50ad6af 100644 --- a/app/src/main/java/com/santiparra/yomitrack/db/entities/AnimeEntity.java +++ b/app/src/main/java/com/santiparra/yomitrack/db/entities/AnimeEntity.java @@ -15,37 +15,79 @@ public class AnimeEntity implements Serializable { private String status; private String type; private String imageUrl; + private int totalEpisodes; + // Getters y Setters - public int getId() { return id; } + public int getId() { + return id; + } - public void setId(int id) { this.id = id; } + public void setId(int id) { + this.id = id; + } - public int getUserId() { return userId; } + public int getUserId() { + return userId; + } - public void setUserId(int userId) { this.userId = userId; } + public void setUserId(int userId) { + this.userId = userId; + } - public String getTitle() { return title; } + public String getTitle() { + return title; + } - public void setTitle(String title) { this.title = title; } + public void setTitle(String title) { + this.title = title; + } - public int getScore() { return score; } + public int getScore() { + return score; + } - public void setScore(int score) { this.score = score; } + public void setScore(int score) { + this.score = score; + } - public int getProgress() { return progress; } + public int getProgress() { + return progress; + } - public void setProgress(int progress) { this.progress = progress; } + public void setProgress(int progress) { + this.progress = progress; + } - public String getStatus() { return status; } + public String getStatus() { + return status; + } - public void setStatus(String status) { this.status = status; } + public void setStatus(String status) { + this.status = status; + } - public String getType() { return type; } + public String getType() { + return type; + } - public void setType(String type) { this.type = type; } + public void setType(String type) { + this.type = type; + } - public String getImageUrl() { return imageUrl; } + public String getImageUrl() { + return imageUrl; + } - public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; } + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public int getTotalEpisodes() { + return totalEpisodes; + } + + public void setTotalEpisodes(int totalEpisodes) { + this.totalEpisodes = totalEpisodes; + } } diff --git a/app/src/main/java/com/santiparra/yomitrack/db/entities/MangaEntity.java b/app/src/main/java/com/santiparra/yomitrack/db/entities/MangaEntity.java index 1c0c115..165fef1 100644 --- a/app/src/main/java/com/santiparra/yomitrack/db/entities/MangaEntity.java +++ b/app/src/main/java/com/santiparra/yomitrack/db/entities/MangaEntity.java @@ -15,37 +15,79 @@ public class MangaEntity implements Serializable { private String status; private String type; private String imageUrl; + private int totalChapters; // Getters y Setters - public int getId() { return id; } - public void setId(int id) { this.id = id; } + public int getId() { + return id; + } - public int getUserId() { return userId; } + public void setId(int id) { + this.id = id; + } - public void setUserId(int userId) { this.userId = userId; } + public int getUserId() { + return userId; + } - public String getTitle() { return title; } + public void setUserId(int userId) { + this.userId = userId; + } - public void setTitle(String title) { this.title = title; } + public String getTitle() { + return title; + } - public int getScore() { return score; } + public void setTitle(String title) { + this.title = title; + } - public void setScore(int score) { this.score = score; } + public int getScore() { + return score; + } - public int getProgress() { return progress; } + public void setScore(int score) { + this.score = score; + } - public void setProgress(int progress) { this.progress = progress; } + public int getProgress() { + return progress; + } - public String getStatus() { return status; } + public void setProgress(int progress) { + this.progress = progress; + } - public void setStatus(String status) { this.status = status; } + public String getStatus() { + return status; + } - public String getType() { return type; } + public void setStatus(String status) { + this.status = status; + } - public void setType(String type) { this.type = type; } + public String getType() { + return type; + } - public String getImageUrl() { return imageUrl; } + public void setType(String type) { + this.type = type; + } - public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; } + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public int getTotalChapters() { + return totalChapters; + } + + public void setTotalChapters(int totalChapters) { + this.totalChapters = totalChapters; + } } diff --git a/app/src/main/java/com/santiparra/yomitrack/db/entities/UserEntity.java b/app/src/main/java/com/santiparra/yomitrack/db/entities/UserEntity.java index c6290f3..84aa753 100644 --- a/app/src/main/java/com/santiparra/yomitrack/db/entities/UserEntity.java +++ b/app/src/main/java/com/santiparra/yomitrack/db/entities/UserEntity.java @@ -1,10 +1,47 @@ package com.santiparra.yomitrack.db.entities; +import com.google.gson.annotations.SerializedName; + /** - * Entidad que representa un usuario en la base de datos + * Entidad que representa un usuario. */ public class UserEntity { - public int id; - public String username; - public String password; + + @SerializedName("id") + private int id; + + @SerializedName("username") + private String username; + + @SerializedName("password") + private String password; + + public UserEntity(String username, String password) { + this.username = username; + this.password = password; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } } diff --git a/app/src/main/java/com/santiparra/yomitrack/model/AniListAnime.java b/app/src/main/java/com/santiparra/yomitrack/model/AniListAnime.java deleted file mode 100644 index 9151a80..0000000 --- a/app/src/main/java/com/santiparra/yomitrack/model/AniListAnime.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.santiparra.yomitrack.model; - -public class AniListAnime { - private int id; - private String title; - private String imageUrl; - - // Constructor vacío - public AniListAnime() {} - - public int getId() { - return id; - } - - public String getTitle() { - return title; - } - - public String getImageUrl() { - return imageUrl; - } - - public void setId(int id) { - this.id = id; - } - - public void setTitle(String title) { - this.title = title; - } - - public void setImageUrl(String imageUrl) { - this.imageUrl = imageUrl; - } -} - - diff --git a/app/src/main/java/com/santiparra/yomitrack/model/LoginResponse.java b/app/src/main/java/com/santiparra/yomitrack/model/LoginResponse.java index 31193bb..91a2cc5 100644 --- a/app/src/main/java/com/santiparra/yomitrack/model/LoginResponse.java +++ b/app/src/main/java/com/santiparra/yomitrack/model/LoginResponse.java @@ -1,9 +1,31 @@ package com.santiparra.yomitrack.model; +import com.google.gson.annotations.SerializedName; import com.santiparra.yomitrack.db.entities.UserEntity; +/** + * Respuesta del servidor al intentar iniciar sesión. + */ public class LoginResponse { - public boolean success; - public String message; - public UserEntity user; + + @SerializedName("success") + private boolean success; + + @SerializedName("message") + private String message; + + @SerializedName("user") + private UserEntity user; + + public boolean isSuccess() { + return success; + } + + public String getMessage() { + return message; + } + + public UserEntity getUser() { + return user; + } } diff --git a/app/src/main/java/com/santiparra/yomitrack/model/RegisterResponse.java b/app/src/main/java/com/santiparra/yomitrack/model/RegisterResponse.java index 5ce1bcb..24a7e91 100644 --- a/app/src/main/java/com/santiparra/yomitrack/model/RegisterResponse.java +++ b/app/src/main/java/com/santiparra/yomitrack/model/RegisterResponse.java @@ -1,13 +1,27 @@ package com.santiparra.yomitrack.model; +import com.google.gson.annotations.SerializedName; + public class RegisterResponse { + + @SerializedName("success") + private boolean success; + + @SerializedName("message") + private String message; + + @SerializedName("userId") private int userId; + public boolean isSuccess() { + return success; + } + + public String getMessage() { + return message; + } + public int getUserId() { return userId; } - - public void setUserId(int userId) { - this.userId = userId; - } } diff --git a/app/src/main/java/com/santiparra/yomitrack/model/UserStats.java b/app/src/main/java/com/santiparra/yomitrack/model/UserStats.java deleted file mode 100644 index 1434fde..0000000 --- a/app/src/main/java/com/santiparra/yomitrack/model/UserStats.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.santiparra.yomitrack.model; - -/** - * Representa una estadística del usuario, usada para barras de progreso. - * Incluye categoría, cantidad y porcentaje de completado. - */ -public class UserStats { - private String category; - private int count; - private int percentage; - - public UserStats(String category, int count, int percentage) { - this.category = category; - this.count = count; - this.percentage = percentage; - } - - public String getCategory() { - return category; - } - - public int getCount() { - return count; - } - - public int getPercentage() { - return percentage; - } -} diff --git a/app/src/main/java/com/santiparra/yomitrack/model/adapters/anime_adapter/AnimeAdapter.java b/app/src/main/java/com/santiparra/yomitrack/model/adapters/anime_adapter/AnimeAdapter.java index b90fd46..2e5d319 100644 --- a/app/src/main/java/com/santiparra/yomitrack/model/adapters/anime_adapter/AnimeAdapter.java +++ b/app/src/main/java/com/santiparra/yomitrack/model/adapters/anime_adapter/AnimeAdapter.java @@ -29,18 +29,10 @@ public class AnimeAdapter extends RecyclerView.Adapter(); - this.viewType = VIEW_NORMAL; - this.onEditClick = null; - this.onLongClick = null; - } - public AnimeAdapter(List animeList, int viewType, OnAnimeClickListener onEditClick, OnAnimeClickListener onLongClick) { - this.animeList = animeList; + this.animeList = animeList != null ? animeList : new ArrayList<>(); this.viewType = viewType; this.onEditClick = onEditClick; this.onLongClick = onLongClick; @@ -51,6 +43,11 @@ public class AnimeAdapter extends RecyclerView.Adapter newList) { + this.animeList = newList != null ? newList : new ArrayList<>(); + notifyDataSetChanged(); + } + @NonNull @Override public AnimeViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { @@ -165,14 +162,7 @@ public class AnimeAdapter extends RecyclerView.Adapter newList) { - this.animeList = newList; - notifyDataSetChanged(); - } - - public interface OnAnimeClickListener { void onClick(AnimeEntity anime); } - -} +} \ No newline at end of file diff --git a/app/src/main/java/com/santiparra/yomitrack/model/adapters/anime_adapter/AnimeSearchAdapter.java b/app/src/main/java/com/santiparra/yomitrack/model/adapters/anime_adapter/AnimeSearchAdapter.java index 0424b32..c502a95 100644 --- a/app/src/main/java/com/santiparra/yomitrack/model/adapters/anime_adapter/AnimeSearchAdapter.java +++ b/app/src/main/java/com/santiparra/yomitrack/model/adapters/anime_adapter/AnimeSearchAdapter.java @@ -11,25 +11,25 @@ import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; import com.santiparra.yomitrack.R; -import com.santiparra.yomitrack.model.AniListAnime; +import com.santiparra.yomitrack.model.AniListMedia; import java.util.List; public class AnimeSearchAdapter extends RecyclerView.Adapter { - private List animeList; + private List animeList; private final OnAnimeClickListener clickListener; public interface OnAnimeClickListener { - void onClick(AniListAnime anime); + void onClick(AniListMedia anime); } - public AnimeSearchAdapter(List animeList, OnAnimeClickListener clickListener) { + public AnimeSearchAdapter(List animeList, OnAnimeClickListener clickListener) { this.animeList = animeList; this.clickListener = clickListener; } - public void setAnimeList(List animeList) { + public void setAnimeList(List animeList) { this.animeList = animeList; notifyDataSetChanged(); } @@ -43,7 +43,7 @@ public class AnimeSearchAdapter extends RecyclerView.Adapter newList) { + this.mangaList = newList; + notifyDataSetChanged(); + } + public interface OnMangaClickListener { void onClick(MangaEntity manga); } diff --git a/app/src/main/java/com/santiparra/yomitrack/model/adapters/manga_adapter/MangaSearchAdapter.java b/app/src/main/java/com/santiparra/yomitrack/model/adapters/manga_adapter/MangaSearchAdapter.java index 23b5735..2d760fb 100644 --- a/app/src/main/java/com/santiparra/yomitrack/model/adapters/manga_adapter/MangaSearchAdapter.java +++ b/app/src/main/java/com/santiparra/yomitrack/model/adapters/manga_adapter/MangaSearchAdapter.java @@ -11,25 +11,25 @@ import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; import com.santiparra.yomitrack.R; -import com.santiparra.yomitrack.model.AniListAnime; +import com.santiparra.yomitrack.model.AniListMedia; import java.util.List; public class MangaSearchAdapter extends RecyclerView.Adapter { - private List mangaList; + private List mangaList; private final OnMangaClickListener clickListener; public interface OnMangaClickListener { - void onClick(AniListAnime manga); + void onClick(AniListMedia manga); } - public MangaSearchAdapter(List mangaList, OnMangaClickListener clickListener) { + public MangaSearchAdapter(List mangaList, OnMangaClickListener clickListener) { this.mangaList = mangaList; this.clickListener = clickListener; } - public void setMangaList(List mangaList) { + public void setMangaList(List mangaList) { this.mangaList = mangaList; notifyDataSetChanged(); } @@ -43,7 +43,7 @@ public class MangaSearchAdapter extends RecyclerView.Adapter>() { + api.searchAniList(query, "ANIME").enqueue(new Callback>() { @Override - public void onResponse(Call> call, Response> response) { + public void onResponse(Call> call, Response> response) { if (response.isSuccessful() && response.body() != null) { searchAdapter.setAnimeList(response.body()); } else { @@ -103,7 +105,7 @@ public class AddAnimeFragment extends Fragment { } @Override - public void onFailure(Call> call, Throwable t) { + public void onFailure(Call> call, Throwable t) { Toast.makeText(getContext(), "Error de red", Toast.LENGTH_SHORT).show(); } }); @@ -114,7 +116,7 @@ public class AddAnimeFragment extends Fragment { }); } - private void onAnimeSelected(AniListAnime selected) { + private void onAnimeSelected(AniListMedia selected) { String status = statusSpinner.getSelectedItem().toString(); String type = typeSpinner.getSelectedItem().toString(); @@ -139,6 +141,7 @@ public class AddAnimeFragment extends Fragment { public void onResponse(Call call, Response response) { if (response.isSuccessful()) { Toast.makeText(getContext(), "Anime añadido", Toast.LENGTH_SHORT).show(); + registrarActividad(anime.getTitle()); requireActivity().getSupportFragmentManager().popBackStack(); } else { Toast.makeText(getContext(), "Error al guardar anime", Toast.LENGTH_SHORT).show(); @@ -151,4 +154,18 @@ public class AddAnimeFragment extends Fragment { } }); } + + private void registrarActividad(String titulo) { + Map actividad = new HashMap<>(); + actividad.put("userId", userId); + actividad.put("action", "Añadió"); + actividad.put("mediaTitle", titulo); + + api.postActivity(actividad).enqueue(new Callback<>() { + @Override + public void onResponse(Call call, Response response) {} + @Override + public void onFailure(Call call, Throwable t) {} + }); + } } diff --git a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/addmanga/AddMangaFragment.java b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/addmanga/AddMangaFragment.java index 1ea0dc2..62161ed 100644 --- a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/addmanga/AddMangaFragment.java +++ b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/addmanga/AddMangaFragment.java @@ -24,11 +24,13 @@ import com.santiparra.yomitrack.R; import com.santiparra.yomitrack.api.ApiClient; import com.santiparra.yomitrack.api.ApiService; import com.santiparra.yomitrack.db.entities.MangaEntity; -import com.santiparra.yomitrack.model.AniListAnime; +import com.santiparra.yomitrack.model.AniListMedia; import com.santiparra.yomitrack.model.adapters.manga_adapter.MangaSearchAdapter; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import retrofit2.Call; import retrofit2.Callback; @@ -46,7 +48,7 @@ public class AddMangaFragment extends Fragment { @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_add_anime, container, false); + View view = inflater.inflate(R.layout.fragment_add_manga, container, false); searchEditText = view.findViewById(R.id.editTextSearch); scoreEditText = view.findViewById(R.id.editTextScore); @@ -69,12 +71,12 @@ public class AddMangaFragment extends Fragment { private void setupSpinners() { ArrayAdapter statusAdapter = ArrayAdapter.createFromResource( - requireContext(), R.array.anime_status_array, android.R.layout.simple_spinner_item); + requireContext(), R.array.manga_status_array, android.R.layout.simple_spinner_item); statusAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); statusSpinner.setAdapter(statusAdapter); ArrayAdapter typeAdapter = ArrayAdapter.createFromResource( - requireContext(), R.array.anime_type_array, android.R.layout.simple_spinner_item); + requireContext(), R.array.manga_type_array, android.R.layout.simple_spinner_item); typeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); typeSpinner.setAdapter(typeAdapter); } @@ -90,9 +92,9 @@ public class AddMangaFragment extends Fragment { (event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) { String query = searchEditText.getText().toString().trim(); if (!query.isEmpty()) { - api.searchMangaAniList(query).enqueue(new Callback>() { + api.searchAniList(query, "MANGA").enqueue(new Callback>() { @Override - public void onResponse(Call> call, Response> response) { + public void onResponse(Call> call, Response> response) { if (response.isSuccessful() && response.body() != null) { searchAdapter.setMangaList(response.body()); } else { @@ -101,7 +103,7 @@ public class AddMangaFragment extends Fragment { } @Override - public void onFailure(Call> call, Throwable t) { + public void onFailure(Call> call, Throwable t) { Toast.makeText(getContext(), "Error de red", Toast.LENGTH_SHORT).show(); } }); @@ -112,7 +114,7 @@ public class AddMangaFragment extends Fragment { }); } - private void onMangaSelected(AniListAnime selected) { + private void onMangaSelected(AniListMedia selected) { String status = statusSpinner.getSelectedItem().toString(); String type = typeSpinner.getSelectedItem().toString(); @@ -136,7 +138,8 @@ public class AddMangaFragment extends Fragment { @Override public void onResponse(Call call, Response response) { if (response.isSuccessful()) { - Toast.makeText(getContext(), "Manga añadido", Toast.LENGTH_SHORT).show(); + Toast.makeText(getContext(), "Manga añadido correctamente", Toast.LENGTH_SHORT).show(); + registrarActividad(manga.getTitle()); requireActivity().getSupportFragmentManager().popBackStack(); } else { Toast.makeText(getContext(), "Error al guardar manga", Toast.LENGTH_SHORT).show(); @@ -149,4 +152,18 @@ public class AddMangaFragment extends Fragment { } }); } + + private void registrarActividad(String titulo) { + Map actividad = new HashMap<>(); + actividad.put("userId", userId); + actividad.put("action", "Añadió"); + actividad.put("mediaTitle", titulo); + + api.postActivity(actividad).enqueue(new Callback<>() { + @Override + public void onResponse(Call call, Response response) {} + @Override + public void onFailure(Call call, Throwable t) {} + }); + } } diff --git a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/anime_list/FragmentAnime.java b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/anime_list/FragmentAnime.java index c52c4af..48e06c7 100644 --- a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/anime_list/FragmentAnime.java +++ b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/anime_list/FragmentAnime.java @@ -14,6 +14,8 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; @@ -24,6 +26,7 @@ import com.santiparra.yomitrack.R; import com.santiparra.yomitrack.api.ApiClient; import com.santiparra.yomitrack.api.ApiService; import com.santiparra.yomitrack.db.entities.AnimeEntity; +import com.santiparra.yomitrack.model.AnimePageResponse; import com.santiparra.yomitrack.model.adapters.anime_adapter.AnimeAdapter; import com.santiparra.yomitrack.ui.fragments.addanime.AddAnimeFragment; import com.santiparra.yomitrack.ui.fragments.editanime.EditAnimeFragment; @@ -45,7 +48,11 @@ public class FragmentAnime extends Fragment { private ImageButton btnViewCompact, btnViewNormal, btnViewLarge; private int currentViewType = AnimeAdapter.VIEW_NORMAL; - private List animeList = new ArrayList<>(); + private final List animeList = new ArrayList<>(); + + private boolean isLoading = false; + private int currentPage = 1; + private final int PAGE_SIZE = 20; @Nullable @Override @@ -68,26 +75,25 @@ public class FragmentAnime extends Fragment { editSearch = view.findViewById(R.id.editSearch); editSearch.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) {} - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { + @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + @Override public void onTextChanged(CharSequence s, int start, int before, int count) { filterAnimeList(s.toString()); } - - @Override - public void afterTextChanged(Editable s) {} + @Override public void afterTextChanged(Editable s) {} }); api = ApiClient.getClient().create(ApiService.class); - SharedPreferences prefs = requireContext().getSharedPreferences("app_prefs", Context.MODE_PRIVATE); - userId = prefs.getInt("current_user_id", -1); + SharedPreferences prefs = requireContext().getSharedPreferences("user_session", Context.MODE_PRIVATE); + userId = prefs.getInt("user_id", -1); - // Botones de vista - btnViewCompact.setOnClickListener(v -> setViewType(AnimeAdapter.VIEW_COMPACT)); - btnViewNormal.setOnClickListener(v -> setViewType(AnimeAdapter.VIEW_NORMAL)); - btnViewLarge.setOnClickListener(v -> setViewType(AnimeAdapter.VIEW_LARGE)); + if (userId == -1) { + Toast.makeText(getContext(), "Error: sesión no iniciada", Toast.LENGTH_SHORT).show(); + return; + } + + adapter = new AnimeAdapter(animeList, currentViewType, this::showEditDialog, this::deleteAnime); + recyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); + recyclerView.setAdapter(adapter); fabAdd.setOnClickListener(v -> requireActivity().getSupportFragmentManager() .beginTransaction() @@ -95,7 +101,39 @@ public class FragmentAnime extends Fragment { .addToBackStack(null) .commit()); - fetchAnimeList(); + btnViewCompact.setOnClickListener(v -> setViewType(AnimeAdapter.VIEW_COMPACT)); + btnViewNormal.setOnClickListener(v -> setViewType(AnimeAdapter.VIEW_NORMAL)); + btnViewLarge.setOnClickListener(v -> setViewType(AnimeAdapter.VIEW_LARGE)); + + setViewType(currentViewType); + loadMoreAnimes(currentPage); + + recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); + if (layoutManager == null) return; + + int visibleItemCount = layoutManager.getChildCount(); + int totalItemCount = layoutManager.getItemCount(); + int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition(); + + if (!isLoading && (firstVisibleItemPosition + visibleItemCount) >= totalItemCount - 4) { + currentPage++; + loadMoreAnimes(currentPage); + } + } + }); + + ViewCompat.setOnApplyWindowInsetsListener(view, (v, insets) -> { + int bottomInset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom; + recyclerView.setPadding( + recyclerView.getPaddingLeft(), + recyclerView.getPaddingTop(), + recyclerView.getPaddingRight(), + bottomInset + 95 + ); + return insets; + }); } private void filterAnimeList(String query) { @@ -108,33 +146,13 @@ public class FragmentAnime extends Fragment { adapter.updateList(filtered); } - private void fetchAnimeList() { - api.getAnimeByUser(userId).enqueue(new Callback>() { - @Override - public void onResponse(@NonNull Call> call, - @NonNull Response> response) { - if (response.isSuccessful() && response.body() != null) { - animeList = response.body(); - setViewType(currentViewType); - } else { - Toast.makeText(getContext(), "Error al cargar animes", Toast.LENGTH_SHORT).show(); - } - } - - @Override - public void onFailure(@NonNull Call> call, @NonNull Throwable t) { - Toast.makeText(getContext(), "Fallo de conexión", Toast.LENGTH_SHORT).show(); - } - }); - } - private void setViewType(int viewType) { currentViewType = viewType; if (viewType == AnimeAdapter.VIEW_LARGE) { - recyclerView.setLayoutManager(new GridLayoutManager(getContext(), 2)); + recyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2)); } else { - recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + recyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); } adapter = new AnimeAdapter(animeList, viewType, @@ -155,18 +173,53 @@ public class FragmentAnime extends Fragment { api.deleteAnime(anime.getId()).enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { + if (!isAdded()) return; + if (response.isSuccessful()) { - Toast.makeText(getContext(), "Anime eliminado", Toast.LENGTH_SHORT).show(); - fetchAnimeList(); + Toast.makeText(requireContext(), "Anime eliminado", Toast.LENGTH_SHORT).show(); + currentPage = 1; + animeList.clear(); + loadMoreAnimes(currentPage); } else { - Toast.makeText(getContext(), "Error al eliminar", Toast.LENGTH_SHORT).show(); + Toast.makeText(requireContext(), "Error al eliminar", Toast.LENGTH_SHORT).show(); } } @Override public void onFailure(Call call, Throwable t) { - Toast.makeText(getContext(), "Fallo de conexión", Toast.LENGTH_SHORT).show(); + if (isAdded()) { + Toast.makeText(requireContext(), "Fallo de conexión", Toast.LENGTH_SHORT).show(); + } } }); } -} \ No newline at end of file + + private void loadMoreAnimes(int page) { + isLoading = true; + + api.getAnimes(userId, page, PAGE_SIZE).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (!isAdded()) return; + + if (response.isSuccessful() && response.body() != null) { + List nuevos = response.body().getData(); + animeList.addAll(nuevos); + adapter.notifyItemRangeInserted(animeList.size() - nuevos.size(), nuevos.size()); + + isLoading = response.body().isHasNextPage(); + } else { + isLoading = false; + } + } + + @Override + public void onFailure(Call call, Throwable t) { + isLoading = false; + if (isAdded()) { + Toast.makeText(requireContext(), "Error al cargar más animes", Toast.LENGTH_SHORT).show(); + } + } + }); + } +} diff --git a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/editanime/EditAnimeFragment.java b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/editanime/EditAnimeFragment.java index 4986fab..b370cfe 100644 --- a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/editanime/EditAnimeFragment.java +++ b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/editanime/EditAnimeFragment.java @@ -1,18 +1,12 @@ -// EditAnimeFragment.java optimizado con setupSpinners y safe saveChanges - package com.santiparra.yomitrack.ui.fragments.editanime; +import android.app.AlertDialog; import android.content.Context; -import android.content.SharedPreferences; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.EditText; -import android.widget.Spinner; -import android.widget.Toast; +import android.widget.*; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -31,19 +25,24 @@ public class EditAnimeFragment extends Fragment { private EditText editTextTitle, editTextScore, editTextProgress; private Spinner spinnerStatus, spinnerType; - private Button buttonSave; + private Button buttonSave, buttonDelete; private AnimeEntity anime; private ApiService api; - private int userId; public EditAnimeFragment(AnimeEntity anime) { this.anime = anime; } - @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_edit_anime, container, false); + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_edit_anime, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, + @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); editTextTitle = view.findViewById(R.id.editTextAnimeTitle); editTextScore = view.findViewById(R.id.editTextScore); @@ -51,51 +50,47 @@ public class EditAnimeFragment extends Fragment { spinnerStatus = view.findViewById(R.id.spinnerStatus); spinnerType = view.findViewById(R.id.spinnerType); buttonSave = view.findViewById(R.id.buttonSaveAnime); + buttonDelete = view.findViewById(R.id.buttonDeleteAnime); api = ApiClient.getClient().create(ApiService.class); - SharedPreferences prefs = requireContext().getSharedPreferences("app_prefs", Context.MODE_PRIVATE); - userId = prefs.getInt("current_user_id", -1); - if (getArguments() != null && getArguments().containsKey("anime")) { - anime = (AnimeEntity) getArguments().getSerializable("anime"); - fillFields(); + // Llenar campos + fillFields(); + + // Spinner datos + String[] statusArray = getResources().getStringArray(R.array.anime_status_array); + String[] typeArray = getResources().getStringArray(R.array.anime_type_array); + + ArrayAdapter statusAdapter = new ArrayAdapter<>(requireContext(), android.R.layout.simple_spinner_item, statusArray); + spinnerStatus.setAdapter(statusAdapter); + + ArrayAdapter typeAdapter = new ArrayAdapter<>(requireContext(), android.R.layout.simple_spinner_item, typeArray); + spinnerType.setAdapter(typeAdapter); + + for (int i = 0; i < statusArray.length; i++) { + if (statusArray[i].equalsIgnoreCase(anime.getStatus())) { + spinnerStatus.setSelection(i); + break; + } } - setupSpinners(); + for (int i = 0; i < typeArray.length; i++) { + if (typeArray[i].equalsIgnoreCase(anime.getType())) { + spinnerType.setSelection(i); + break; + } + } buttonSave.setOnClickListener(v -> saveChanges()); - return view; - } - - private void setupSpinners() { - ArrayAdapter statusAdapter = ArrayAdapter.createFromResource( - requireContext(), R.array.anime_status_array, android.R.layout.simple_spinner_item); - statusAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - spinnerStatus.setAdapter(statusAdapter); - - ArrayAdapter typeAdapter = ArrayAdapter.createFromResource( - requireContext(), R.array.anime_type_array, android.R.layout.simple_spinner_item); - typeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - spinnerType.setAdapter(typeAdapter); - - if (anime != null) { - String[] statusArray = getResources().getStringArray(R.array.anime_status_array); - String[] typeArray = getResources().getStringArray(R.array.anime_type_array); - - for (int i = 0; i < statusArray.length; i++) { - if (statusArray[i].equalsIgnoreCase(anime.getStatus())) { - spinnerStatus.setSelection(i); - break; - } - } - for (int i = 0; i < typeArray.length; i++) { - if (typeArray[i].equalsIgnoreCase(anime.getType())) { - spinnerType.setSelection(i); - break; - } - } - } + buttonDelete.setOnClickListener(v -> { + new AlertDialog.Builder(requireContext()) + .setTitle("Eliminar anime") + .setMessage("¿Estás seguro de que quieres eliminar este anime?") + .setPositiveButton("Sí", (dialog, which) -> deleteAnime()) + .setNegativeButton("Cancelar", null) + .show(); + }); } private void fillFields() { @@ -128,13 +123,8 @@ public class EditAnimeFragment extends Fragment { return; } - String status = spinnerStatus.getSelectedItem() != null ? spinnerStatus.getSelectedItem().toString() : ""; - String type = spinnerType.getSelectedItem() != null ? spinnerType.getSelectedItem().toString() : ""; - - if (status.isEmpty() || type.isEmpty()) { - Toast.makeText(requireContext(), "Debe seleccionar estado y tipo", Toast.LENGTH_SHORT).show(); - return; - } + String status = spinnerStatus.getSelectedItem().toString(); + String type = spinnerType.getSelectedItem().toString(); anime.setTitle(title); anime.setScore(score); @@ -142,11 +132,13 @@ public class EditAnimeFragment extends Fragment { anime.setStatus(status); anime.setType(type); - api.updateAnime(anime.getId(),anime).enqueue(new Callback() { + api.updateAnime(anime.getId(), anime).enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { if (response.isSuccessful()) { Toast.makeText(requireContext(), "Anime actualizado", Toast.LENGTH_SHORT).show(); + requireContext().getSharedPreferences("user_profile", Context.MODE_PRIVATE) + .edit().putBoolean("refresh_profile", true).apply(); requireActivity().getSupportFragmentManager().popBackStack(); } else { Toast.makeText(requireContext(), "Error al actualizar", Toast.LENGTH_SHORT).show(); @@ -159,4 +151,25 @@ public class EditAnimeFragment extends Fragment { } }); } + + private void deleteAnime() { + api.deleteAnime(anime.getId()).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + Toast.makeText(requireContext(), "Anime eliminado", Toast.LENGTH_SHORT).show(); + requireContext().getSharedPreferences("user_profile", Context.MODE_PRIVATE) + .edit().putBoolean("refresh_profile", true).apply(); + requireActivity().getSupportFragmentManager().popBackStack(); + } else { + Toast.makeText(requireContext(), "Error al eliminar", Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(requireContext(), "Fallo en la conexión", Toast.LENGTH_SHORT).show(); + } + }); + } } diff --git a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/editmanga/EditMangaFragment.java b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/editmanga/EditMangaFragment.java index 1cb431a..ce8caac 100644 --- a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/editmanga/EditMangaFragment.java +++ b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/editmanga/EditMangaFragment.java @@ -1,18 +1,12 @@ -// EditMangaFragment.java conectado a fragment_edit_manga.xml y funcional - package com.santiparra.yomitrack.ui.fragments.editmanga; +import android.app.AlertDialog; import android.content.Context; -import android.content.SharedPreferences; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.EditText; -import android.widget.Spinner; -import android.widget.Toast; +import android.widget.*; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -31,20 +25,24 @@ public class EditMangaFragment extends Fragment { private EditText editTextTitle, editTextScore, editTextProgress; private Spinner spinnerStatus, spinnerType; - private Button buttonSave; + private Button buttonSave, buttonDelete; private MangaEntity manga; private ApiService api; - private int userId; public EditMangaFragment(MangaEntity manga) { this.manga = manga; } - - @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_edit_manga, container, false); + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_edit_manga, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, + @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); editTextTitle = view.findViewById(R.id.editTextMangaTitle); editTextScore = view.findViewById(R.id.editTextScore); @@ -52,41 +50,21 @@ public class EditMangaFragment extends Fragment { spinnerStatus = view.findViewById(R.id.spinnerStatus); spinnerType = view.findViewById(R.id.spinnerType); buttonSave = view.findViewById(R.id.buttonSaveManga); + buttonDelete = view.findViewById(R.id.buttonDeleteManga); api = ApiClient.getClient().create(ApiService.class); - SharedPreferences prefs = requireContext().getSharedPreferences("app_prefs", Context.MODE_PRIVATE); - userId = prefs.getInt("current_user_id", -1); - setupSpinners(); + fillFields(); - if (getArguments() != null && getArguments().containsKey("manga")) { - manga = (MangaEntity) getArguments().getSerializable("manga"); - fillFields(); - } + String[] statusArray = getResources().getStringArray(R.array.manga_status_array); + String[] typeArray = getResources().getStringArray(R.array.manga_type_array); - buttonSave.setOnClickListener(v -> updateManga()); - - return view; - } - - private void setupSpinners() { - ArrayAdapter statusAdapter = ArrayAdapter.createFromResource( - requireContext(), R.array.anime_status_array, android.R.layout.simple_spinner_item); - statusAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + ArrayAdapter statusAdapter = new ArrayAdapter<>(requireContext(), android.R.layout.simple_spinner_item, statusArray); spinnerStatus.setAdapter(statusAdapter); - ArrayAdapter typeAdapter = ArrayAdapter.createFromResource( - requireContext(), R.array.anime_type_array, android.R.layout.simple_spinner_item); - typeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + ArrayAdapter typeAdapter = new ArrayAdapter<>(requireContext(), android.R.layout.simple_spinner_item, typeArray); spinnerType.setAdapter(typeAdapter); - } - private void fillFields() { - editTextTitle.setText(manga.getTitle()); - editTextScore.setText(String.valueOf(manga.getScore())); - editTextProgress.setText(String.valueOf(manga.getProgress())); - - String[] statusArray = getResources().getStringArray(R.array.anime_status_array); for (int i = 0; i < statusArray.length; i++) { if (statusArray[i].equalsIgnoreCase(manga.getStatus())) { spinnerStatus.setSelection(i); @@ -94,19 +72,55 @@ public class EditMangaFragment extends Fragment { } } - String[] typeArray = getResources().getStringArray(R.array.anime_type_array); for (int i = 0; i < typeArray.length; i++) { if (typeArray[i].equalsIgnoreCase(manga.getType())) { spinnerType.setSelection(i); break; } } + + buttonSave.setOnClickListener(v -> saveChanges()); + + buttonDelete.setOnClickListener(v -> { + new AlertDialog.Builder(requireContext()) + .setTitle("Eliminar manga") + .setMessage("¿Estás seguro de que quieres eliminar este manga?") + .setPositiveButton("Sí", (dialog, which) -> deleteManga()) + .setNegativeButton("Cancelar", null) + .show(); + }); } - private void updateManga() { + private void fillFields() { + editTextTitle.setText(manga.getTitle()); + editTextScore.setText(String.valueOf(manga.getScore())); + editTextProgress.setText(String.valueOf(manga.getProgress())); + } + + private void saveChanges() { + if (manga == null) { + Toast.makeText(requireContext(), "Error: Manga no cargado", Toast.LENGTH_SHORT).show(); + return; + } + String title = editTextTitle.getText().toString().trim(); - int score = Integer.parseInt(editTextScore.getText().toString()); - int progress = Integer.parseInt(editTextProgress.getText().toString()); + String scoreStr = editTextScore.getText().toString().trim(); + String progressStr = editTextProgress.getText().toString().trim(); + + if (title.isEmpty()) { + Toast.makeText(requireContext(), "El título no puede estar vacío", Toast.LENGTH_SHORT).show(); + return; + } + + int score = 0, progress = 0; + try { + score = Integer.parseInt(scoreStr); + progress = Integer.parseInt(progressStr); + } catch (NumberFormatException e) { + Toast.makeText(requireContext(), "Score y Progreso deben ser números", Toast.LENGTH_SHORT).show(); + return; + } + String status = spinnerStatus.getSelectedItem().toString(); String type = spinnerType.getSelectedItem().toString(); @@ -116,20 +130,43 @@ public class EditMangaFragment extends Fragment { manga.setStatus(status); manga.setType(type); - api.updateManga(manga.getId(),manga).enqueue(new Callback() { + api.updateManga(manga.getId(), manga).enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { if (response.isSuccessful()) { - Toast.makeText(getContext(), "Manga actualizado", Toast.LENGTH_SHORT).show(); + Toast.makeText(requireContext(), "Manga actualizado", Toast.LENGTH_SHORT).show(); + requireContext().getSharedPreferences("user_profile", Context.MODE_PRIVATE) + .edit().putBoolean("refresh_profile", true).apply(); requireActivity().getSupportFragmentManager().popBackStack(); } else { - Toast.makeText(getContext(), "Error al actualizar", Toast.LENGTH_SHORT).show(); + Toast.makeText(requireContext(), "Error al actualizar", Toast.LENGTH_SHORT).show(); } } @Override public void onFailure(Call call, Throwable t) { - Toast.makeText(getContext(), "Fallo de conexión", Toast.LENGTH_SHORT).show(); + Toast.makeText(requireContext(), "Fallo en la conexión", Toast.LENGTH_SHORT).show(); + } + }); + } + + private void deleteManga() { + api.deleteManga(manga.getId()).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + Toast.makeText(requireContext(), "Manga eliminado", Toast.LENGTH_SHORT).show(); + requireContext().getSharedPreferences("user_profile", Context.MODE_PRIVATE) + .edit().putBoolean("refresh_profile", true).apply(); + requireActivity().getSupportFragmentManager().popBackStack(); + } else { + Toast.makeText(requireContext(), "Error al eliminar", Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(requireContext(), "Fallo en la conexión", Toast.LENGTH_SHORT).show(); } }); } diff --git a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/login/LoginActivity.java b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/login/LoginActivity.java index dd6c82b..3043f60 100644 --- a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/login/LoginActivity.java +++ b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/login/LoginActivity.java @@ -12,6 +12,6 @@ public class LoginActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_login); // contiene el NavHostFragment + setContentView(R.layout.activity_login); } } diff --git a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/login/LoginFragment.java b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/login/LoginFragment.java index dd3264f..d39395c 100644 --- a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/login/LoginFragment.java +++ b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/login/LoginFragment.java @@ -4,18 +4,19 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.EditText; -import android.widget.Toast; +import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.navigation.NavController; import androidx.navigation.fragment.NavHostFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + import com.santiparra.yomitrack.R; +import com.santiparra.yomitrack.databinding.FragmentLoginBinding; import com.santiparra.yomitrack.api.ApiClient; import com.santiparra.yomitrack.api.ApiService; import com.santiparra.yomitrack.db.entities.UserEntity; @@ -26,86 +27,85 @@ import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -/** - * Fragmento que gestiona el inicio de sesión y entrada como invitado. - */ public class LoginFragment extends Fragment { - private EditText usernameEditText, passwordEditText; - private Button loginButton, guestButton, registerButton; + private FragmentLoginBinding binding; + + public LoginFragment() { + // Required empty public constructor + } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_login, container, false); + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + binding = FragmentLoginBinding.inflate(inflater, container, false); - usernameEditText = view.findViewById(R.id.editTextUsername); - passwordEditText = view.findViewById(R.id.editTextPassword); - loginButton = view.findViewById(R.id.buttonLogin); - guestButton = view.findViewById(R.id.buttonGuest); - registerButton = view.findViewById(R.id.buttonGoRegister); + binding.buttonLogin.setOnClickListener(v -> loginUser()); + binding.buttonGuest.setOnClickListener(v -> loginAsGuest()); + binding.buttonGoRegister.setOnClickListener(v -> { + NavController navController = NavHostFragment.findNavController(LoginFragment.this); + navController.navigate(R.id.action_loginFragment_to_registerFragment); + }); - loginButton.setOnClickListener(v -> { - String username = usernameEditText.getText().toString().trim(); - String password = passwordEditText.getText().toString().trim(); + return binding.getRoot(); + } - if (username.isEmpty() || password.isEmpty()) { - Toast.makeText(getContext(), "Rellena todos los campos", Toast.LENGTH_SHORT).show(); - return; + private void loginUser() { + String username = binding.editTextUsername.getText().toString().trim(); + String password = binding.editTextPassword.getText().toString().trim(); + + if (username.isEmpty() || password.isEmpty()) { + showToast("Ingrese usuario y contraseña"); + return; + } + + UserEntity user = new UserEntity(username, password); + ApiService apiService = ApiClient.getClient().create(ApiService.class); + + apiService.loginUser(user).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null && response.body().isSuccess()) { + saveUserSession(response.body().getUser().getId(), response.body().getUser().getUsername()); + showToast("Inicio de sesión exitoso"); + goToMainActivity(); + } else { + showToast("Credenciales incorrectas"); + } } - ApiService api = ApiClient.getClient().create(ApiService.class); - UserEntity user = new UserEntity(); - user.username = username; - user.password = password; - - Call call = api.loginUser(user); - call.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful() && response.body() != null) { - LoginResponse result = response.body(); - - if (result.success) { - UserEntity user = result.user; - Toast.makeText(getContext(), "Bienvenido " + user.username, Toast.LENGTH_SHORT).show(); - - saveSession(user.id); // ✅ Guardamos el ID del usuario - navigateToHome(); // ✅ Entramos a MainActivity - } else { - Toast.makeText(getContext(), result.message, Toast.LENGTH_SHORT).show(); - } - } else { - Toast.makeText(getContext(), "Error inesperado", Toast.LENGTH_SHORT).show(); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - Toast.makeText(getContext(), "Error de red: " + t.getMessage(), Toast.LENGTH_SHORT).show(); - } - }); + @Override + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + showToast("Error de red: " + t.getMessage()); + } }); - - guestButton.setOnClickListener(v -> { - saveSession(-1); // usuario invitado - navigateToHome(); - }); - - registerButton.setOnClickListener(v -> { - NavController navController = NavHostFragment.findNavController(LoginFragment.this); - navController.navigate(R.id.action_login_to_register); - }); - - return view; } - private void saveSession(int userId) { - SharedPreferences prefs = requireContext().getSharedPreferences("app_prefs", Context.MODE_PRIVATE); - prefs.edit().putInt("current_user_id", userId).apply(); + private void loginAsGuest() { + saveUserSession(-1, "Invitado"); + goToMainActivity(); } - private void navigateToHome() { + private void saveUserSession(int userId, String username) { + SharedPreferences prefs = requireActivity().getSharedPreferences("user_session", Context.MODE_PRIVATE); + prefs.edit() + .putInt("user_id", userId) + .putString("username", username) + .apply(); + } + + private void goToMainActivity() { startActivity(new Intent(getActivity(), MainActivity.class)); requireActivity().finish(); } + + private void showToast(String message) { + Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } } diff --git a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/manga_list/FragmentManga.java b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/manga_list/FragmentManga.java index 8065eea..a216738 100644 --- a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/manga_list/FragmentManga.java +++ b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/manga_list/FragmentManga.java @@ -1,15 +1,23 @@ package com.santiparra.yomitrack.ui.fragments.manga_list; +import android.content.Context; +import android.content.SharedPreferences; import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; +import android.widget.EditText; +import android.widget.ImageButton; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -18,10 +26,12 @@ import com.santiparra.yomitrack.R; import com.santiparra.yomitrack.api.ApiClient; import com.santiparra.yomitrack.api.ApiService; import com.santiparra.yomitrack.db.entities.MangaEntity; +import com.santiparra.yomitrack.model.MangaPageResponse; import com.santiparra.yomitrack.model.adapters.manga_adapter.MangaAdapter; import com.santiparra.yomitrack.ui.fragments.addmanga.AddMangaFragment; import com.santiparra.yomitrack.ui.fragments.editmanga.EditMangaFragment; +import java.util.ArrayList; import java.util.List; import retrofit2.Call; @@ -30,29 +40,57 @@ import retrofit2.Response; public class FragmentManga extends Fragment { + private EditText editSearch; private RecyclerView recyclerView; private MangaAdapter adapter; private ApiService api; + private int userId = 1; + + private ImageButton btnViewCompact, btnViewNormal, btnViewLarge; private int currentViewType = MangaAdapter.VIEW_NORMAL; - private int userId = 1; // Deberías usar SharedPreferences si tienes login + private List mangaList = new ArrayList<>(); + + private boolean isLoading = false; + private int currentPage = 1; + private final int PAGE_SIZE = 20; @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_mlist, container, false); + public View onCreateView(@NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_mlist, container, false); + } + + @Override + public void onViewCreated(@NonNull View view, + @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); recyclerView = view.findViewById(R.id.recyclerViewManga); - recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); - - ImageView changeViewButton = view.findViewById(R.id.buttonChangeViewType); + btnViewCompact = view.findViewById(R.id.btnViewCompact); + btnViewNormal = view.findViewById(R.id.btnViewNormal); + btnViewLarge = view.findViewById(R.id.btnViewLarge); FloatingActionButton fabAdd = view.findViewById(R.id.fabAddManga); + editSearch = view.findViewById(R.id.editSearch); + + editSearch.addTextChangedListener(new TextWatcher() { + @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + @Override public void afterTextChanged(Editable s) {} + @Override public void onTextChanged(CharSequence s, int start, int before, int count) { + filterMangaList(s.toString()); + } + }); api = ApiClient.getClient().create(ApiService.class); + SharedPreferences prefs = requireContext().getSharedPreferences("user_session", Context.MODE_PRIVATE); + userId = prefs.getInt("user_id", -1); + + if (userId == -1) { + Toast.makeText(getContext(), "Error: sesión no iniciada", Toast.LENGTH_SHORT).show(); + return; + } - changeViewButton.setOnClickListener(v -> { - currentViewType = (currentViewType + 1) % 3; - if (adapter != null) adapter.setViewType(currentViewType); - }); fabAdd.setOnClickListener(v -> requireActivity().getSupportFragmentManager() .beginTransaction() @@ -60,30 +98,65 @@ public class FragmentManga extends Fragment { .addToBackStack(null) .commit()); - loadMangaList(); + btnViewCompact.setOnClickListener(v -> setViewType(MangaAdapter.VIEW_COMPACT)); + btnViewNormal.setOnClickListener(v -> setViewType(MangaAdapter.VIEW_NORMAL)); + btnViewLarge.setOnClickListener(v -> setViewType(MangaAdapter.VIEW_LARGE)); - return view; - } + setViewType(currentViewType); + loadMoreMangas(currentPage); - private void loadMangaList() { - api.getMangaByUser(userId).enqueue(new Callback>() { + recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override - public void onResponse(Call> call, Response> response) { - if (response.isSuccessful() && response.body() != null) { - adapter = new MangaAdapter(response.body(), currentViewType, - FragmentManga.this::showEditDialog, - FragmentManga.this::deleteManga); - recyclerView.setAdapter(adapter); - } else { - Toast.makeText(getContext(), "Error al cargar la lista", Toast.LENGTH_SHORT).show(); + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); + if (layoutManager == null) return; + + int visibleItemCount = layoutManager.getChildCount(); + int totalItemCount = layoutManager.getItemCount(); + int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition(); + + if (!isLoading && (firstVisibleItemPosition + visibleItemCount) >= totalItemCount - 4) { + currentPage++; + loadMoreMangas(currentPage); } } - - @Override - public void onFailure(Call> call, Throwable t) { - Toast.makeText(getContext(), "Fallo en la conexión", Toast.LENGTH_SHORT).show(); - } }); + + ViewCompat.setOnApplyWindowInsetsListener(view, (v, insets) -> { + int bottomInset = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom; + recyclerView.setPadding( + recyclerView.getPaddingLeft(), + recyclerView.getPaddingTop(), + recyclerView.getPaddingRight(), + bottomInset + 95 + ); + return insets; + }); + } + + private void filterMangaList(String query) { + List filtered = new ArrayList<>(); + for (MangaEntity manga : mangaList) { + if (manga.getTitle().toLowerCase().contains(query.toLowerCase())) { + filtered.add(manga); + } + } + adapter.updateList(filtered); + } + + 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); } private void showEditDialog(MangaEntity manga) { @@ -98,17 +171,48 @@ public class FragmentManga extends Fragment { api.deleteManga(manga.getId()).enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { + if (!isAdded()) return; if (response.isSuccessful()) { - Toast.makeText(getContext(), "Manga eliminado", Toast.LENGTH_SHORT).show(); - loadMangaList(); + Toast.makeText(requireContext(), "Manga eliminado", Toast.LENGTH_SHORT).show(); + currentPage = 1; + mangaList.clear(); + loadMoreMangas(currentPage); } else { - Toast.makeText(getContext(), "Error al eliminar", Toast.LENGTH_SHORT).show(); + Toast.makeText(requireContext(), "Error al eliminar", Toast.LENGTH_SHORT).show(); } } @Override public void onFailure(Call call, Throwable t) { - Toast.makeText(getContext(), "Fallo en la conexión", Toast.LENGTH_SHORT).show(); + if (isAdded()) { + Toast.makeText(requireContext(), "Fallo de conexión", Toast.LENGTH_SHORT).show(); + } + } + }); + } + + private void loadMoreMangas(int page) { + isLoading = true; + api.getMangas(userId, page, PAGE_SIZE).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (!isAdded()) return; + if (response.isSuccessful() && response.body() != null) { + List nuevos = response.body().getData(); + mangaList.addAll(nuevos); + adapter.notifyItemRangeInserted(mangaList.size() - nuevos.size(), nuevos.size()); + isLoading = response.body().isHasNextPage(); + } else { + isLoading = false; + } + } + + @Override + public void onFailure(Call call, Throwable t) { + isLoading = false; + if (isAdded()) { + Toast.makeText(requireContext(), "Error al cargar más mangas", Toast.LENGTH_SHORT).show(); + } } }); } diff --git a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/profile/FragmentProfile.java b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/profile/FragmentProfile.java index a52bde3..b3a2590 100644 --- a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/profile/FragmentProfile.java +++ b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/profile/FragmentProfile.java @@ -3,25 +3,29 @@ package com.santiparra.yomitrack.ui.fragments.profile; import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; +import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; - +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; - import com.santiparra.yomitrack.R; import com.santiparra.yomitrack.api.ApiClient; import com.santiparra.yomitrack.api.ApiService; -import com.santiparra.yomitrack.db.entities.AnimeEntity; -import com.santiparra.yomitrack.db.entities.MangaEntity; - +import com.santiparra.yomitrack.utils.ActivityLog; +import org.json.JSONObject; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import retrofit2.Call; @@ -30,111 +34,140 @@ import retrofit2.Response; public class FragmentProfile extends Fragment { - private TextView textUsername; - private LinearLayout animeStatsContainer, mangaStatsContainer; + private ImageView avatarImage, coverImage; + private TextView usernameText; + private EditText editStatus, editBiography; + private Button buttonPostStatus, buttonSaveBio; + private LinearLayout animeStatsContainer, mangaStatsContainer, activityContainer; private ApiService api; private int userId; + private String username; + @Nullable @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_profile, container, false); - textUsername = view.findViewById(R.id.usernameText); + avatarImage = view.findViewById(R.id.avatarImage); + coverImage = view.findViewById(R.id.coverImage); + usernameText = view.findViewById(R.id.usernameText); + editStatus = view.findViewById(R.id.editStatus); + editBiography = view.findViewById(R.id.editBiography); + buttonPostStatus = view.findViewById(R.id.buttonPostStatus); + buttonSaveBio = view.findViewById(R.id.buttonSaveBio); animeStatsContainer = view.findViewById(R.id.animeStatsContainer); mangaStatsContainer = view.findViewById(R.id.mangaStatsContainer); + activityContainer = view.findViewById(R.id.activityContainer); + + SharedPreferences prefs = requireContext().getSharedPreferences("user_prefs", Context.MODE_PRIVATE); + userId = prefs.getInt("userId", -1); + username = prefs.getString("username", "Usuario"); + api = ApiClient.getClient().create(ApiService.class); - SharedPreferences prefs = requireContext().getSharedPreferences("app_prefs", Context.MODE_PRIVATE); - userId = prefs.getInt("current_user_id", -1); - - if (userId == -1) { - textUsername.setText("Invitado"); - Toast.makeText(getContext(), "Estadísticas no disponibles en modo invitado", Toast.LENGTH_SHORT).show(); - return view; - } - - textUsername.setText("Usuario #" + userId); + usernameText.setText(username); loadStats(); + loadActivity(); + + buttonPostStatus.setOnClickListener(v -> postStatus()); + buttonSaveBio.setOnClickListener(v -> saveBiography()); return view; } private void loadStats() { - animeStatsContainer.removeAllViews(); - mangaStatsContainer.removeAllViews(); - - api.getAnimeByUser(userId).enqueue(new Callback>() { + api.getUserStats(userId).enqueue(new Callback>>() { @Override - public void onResponse(Call> call, Response> response) { + public void onResponse(Call>> call, Response>> response) { if (response.isSuccessful() && response.body() != null) { - showStats(animeStatsContainer, "Anime", countByStatus(response.body())); + Map animeStats = response.body().get("animeStats"); + Map mangaStats = response.body().get("mangaStats"); + + populateStats(animeStatsContainer, animeStats); + populateStats(mangaStatsContainer, mangaStats); } } @Override - public void onFailure(Call> call, Throwable t) { - Toast.makeText(getContext(), "Error al cargar anime", Toast.LENGTH_SHORT).show(); - } - }); - - api.getMangaByUser(userId).enqueue(new Callback>() { - @Override - public void onResponse(Call> call, Response> response) { - if (response.isSuccessful() && response.body() != null) { - showStats(mangaStatsContainer, "Manga", countByStatus(response.body())); - } - } - - @Override - public void onFailure(Call> call, Throwable t) { - Toast.makeText(getContext(), "Error al cargar manga", Toast.LENGTH_SHORT).show(); - } + public void onFailure(Call>> call, Throwable t) {} }); } - private Map countByStatus(List items) { - Map counts = new HashMap<>(); - for (Object item : items) { - String status = ""; - if (item instanceof AnimeEntity) { - status = ((AnimeEntity) item).getStatus(); - } else if (item instanceof MangaEntity) { - status = ((MangaEntity) item).getStatus(); - } - counts.put(status, counts.getOrDefault(status, 0) + 1); - } - return counts; - } - - private void showStats(LinearLayout container, String category, Map data) { - TextView title = new TextView(getContext()); - title.setText(category.toUpperCase()); - title.setTextSize(18); - title.setPadding(0, 24, 0, 12); - container.addView(title); - + private void populateStats(LinearLayout container, Map stats) { + container.removeAllViews(); int total = 0; - for (int count : data.values()) total += count; + for (int count : stats.values()) total += count; - for (Map.Entry entry : data.entrySet()) { - String status = entry.getKey(); - int count = entry.getValue(); - int percent = (int) ((count / (float) total) * 100); + LayoutInflater inflater = LayoutInflater.from(getContext()); + for (Map.Entry entry : stats.entrySet()) { + View statView = inflater.inflate(R.layout.item_stat_bar, container, false); + TextView label = statView.findViewById(R.id.statLabelFull); + ProgressBar bar = statView.findViewById(R.id.statProgressBar); - TextView label = new TextView(getContext()); - label.setText(status + ": " + count + " (" + percent + "%)"); - label.setTextSize(16); - container.addView(label); - - ProgressBar progress = new ProgressBar(getContext(), null, android.R.attr.progressBarStyleHorizontal); - progress.setMax(100); - progress.setProgress(percent); - progress.setLayoutParams(new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - )); - container.addView(progress); + label.setText(String.format(Locale.getDefault(), "%s • %d", entry.getKey(), entry.getValue())); + int progress = total > 0 ? (entry.getValue() * 100 / total) : 0; + bar.setProgress(progress); + container.addView(statView); } } + + private void loadActivity() { + api.getActivityLog(userId).enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + activityContainer.removeAllViews(); + LayoutInflater inflater = LayoutInflater.from(getContext()); + + for (ActivityLog log : response.body()) { + View card = inflater.inflate(R.layout.item_activity_card, activityContainer, false); + ((TextView) card.findViewById(R.id.activityUser)).setText(username); + ((TextView) card.findViewById(R.id.activityAction)).setText(log.getAction()); + ((TextView) card.findViewById(R.id.activityTitle)).setText(log.getMediaTitle()); + ((TextView) card.findViewById(R.id.activityTime)).setText(log.getTimestamp()); + activityContainer.addView(card); + } + } + } + + @Override + public void onFailure(Call> call, Throwable t) {} + }); + } + + private void postStatus() { + String status = editStatus.getText().toString().trim(); + if (TextUtils.isEmpty(status)) { + Toast.makeText(getContext(), "Escribe algo primero", Toast.LENGTH_SHORT).show(); + return; + } + + Map post = new HashMap<>(); + post.put("userId", userId); + post.put("action", "publicó"); + post.put("mediaTitle", status); + + api.postActivity(post).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + editStatus.setText(""); + loadActivity(); + Toast.makeText(getContext(), "Publicado", Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(getContext(), "Error al publicar", Toast.LENGTH_SHORT).show(); + } + }); + } + + private void saveBiography() { + String bio = editBiography.getText().toString().trim(); + // Aquí podrías guardar biografía en base de datos si se desea. + Toast.makeText(getContext(), "Biografía guardada", Toast.LENGTH_SHORT).show(); + } } diff --git a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/register/RegisterFragment.java b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/register/RegisterFragment.java index ba6223f..e184e90 100644 --- a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/register/RegisterFragment.java +++ b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/register/RegisterFragment.java @@ -1,91 +1,84 @@ -// RegisterFragment.java solucionado para evitar NPE en onFailure() - package com.santiparra.yomitrack.ui.fragments.register; -import android.content.Context; -import android.content.SharedPreferences; import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.navigation.NavController; +import androidx.navigation.Navigation; + import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; -import android.widget.EditText; import android.widget.Toast; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; - -import com.santiparra.yomitrack.R; import com.santiparra.yomitrack.api.ApiClient; import com.santiparra.yomitrack.api.ApiService; +import com.santiparra.yomitrack.databinding.FragmentRegisterBinding; +import com.santiparra.yomitrack.db.entities.UserEntity; import com.santiparra.yomitrack.model.RegisterResponse; -import java.util.HashMap; - import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; public class RegisterFragment extends Fragment { - private EditText usernameEditText; - private EditText passwordEditText; - private Button registerButton; - private ApiService api; + private FragmentRegisterBinding binding; - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_register, container, false); - - usernameEditText = view.findViewById(R.id.editTextUsernameRegister); - passwordEditText = view.findViewById(R.id.editTextPasswordRegister); - registerButton = view.findViewById(R.id.buttonRegister); - api = ApiClient.getClient().create(ApiService.class); - - registerButton.setOnClickListener(v -> attemptRegister()); - - return view; + public RegisterFragment() { } - private void attemptRegister() { - String username = usernameEditText.getText().toString().trim(); - String password = passwordEditText.getText().toString().trim(); + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + binding = FragmentRegisterBinding.inflate(inflater, container, false); + + binding.buttonRegister.setOnClickListener(v -> registerUser()); + + return binding.getRoot(); + } + + private void registerUser() { + String username = binding.editTextUsernameRegister.getText().toString().trim(); + String password = binding.editTextPasswordRegister.getText().toString().trim(); if (username.isEmpty() || password.isEmpty()) { - safeToast("Todos los campos son obligatorios"); + showToast("Todos los campos son obligatorios"); return; } - HashMap request = new HashMap<>(); - request.put("username", username); - request.put("password", password); + UserEntity user = new UserEntity(username, password); + ApiService apiService = ApiClient.getClient().create(ApiService.class); - api.registerUser(request).enqueue(new Callback() { + apiService.registerUser(user).enqueue(new Callback() { @Override - public void onResponse(Call call, Response response) { - if (isAdded() && response.isSuccessful() && response.body() != null && response.body().getUserId() > 0) { - int userId = response.body().getUserId(); - SharedPreferences prefs = requireContext().getSharedPreferences("app_prefs", Context.MODE_PRIVATE); - prefs.edit().putInt("current_user_id", userId).apply(); - safeToast("Registro exitoso"); - requireActivity().finish(); + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.isSuccessful() && response.body() != null && response.body().isSuccess()) { + showToast("Registro exitoso"); + NavController navController = Navigation.findNavController(binding.getRoot()); + navController.popBackStack(); // Volver al LoginFragment } else { - safeToast("Error al registrar usuario"); + String errorMsg = (response.body() != null && response.body().getMessage() != null) + ? response.body().getMessage() + : "Error desconocido al registrar"; + showToast(errorMsg); } } @Override - public void onFailure(Call call, Throwable t) { - safeToast("Fallo de conexión: " + t.getMessage()); + public void onFailure(@NonNull Call call, @NonNull Throwable t) { + showToast("Fallo de red: " + t.getMessage()); } }); } - private void safeToast(String message) { - if (isAdded() && getContext() != null) { - Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show(); - } + private void showToast(String msg) { + Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; } } diff --git a/app/src/main/java/com/santiparra/yomitrack/utils/StatsHelper.java b/app/src/main/java/com/santiparra/yomitrack/utils/StatsHelper.java deleted file mode 100644 index 151c6fa..0000000 --- a/app/src/main/java/com/santiparra/yomitrack/utils/StatsHelper.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.santiparra.yomitrack.utils; - -import com.santiparra.yomitrack.model.UserStats; - -import java.util.ArrayList; -import java.util.List; - -public class StatsHelper { - - public static List getAnimeStats() { - List stats = new ArrayList<>(); - stats.add(new UserStats("Watching", 42, 8)); - stats.add(new UserStats("Completed", 265, 51)); - stats.add(new UserStats("On Hold", 10, 2)); - stats.add(new UserStats("Dropped", 5, 1)); - stats.add(new UserStats("Plan to Watch", 198, 38)); - return stats; - } - - public static List getMangaStats() { - List stats = new ArrayList<>(); - stats.add(new UserStats("Reading", 31, 10)); - stats.add(new UserStats("Completed", 121, 55)); - stats.add(new UserStats("On Hold", 5, 3)); - stats.add(new UserStats("Dropped", 4, 2)); - stats.add(new UserStats("Plan to Read", 28, 30)); - return stats; - } -} diff --git a/app/src/main/res/drawable/ic_add.png b/app/src/main/res/drawable/ic_add.png deleted file mode 100644 index a0bbac7..0000000 Binary files a/app/src/main/res/drawable/ic_add.png and /dev/null differ diff --git a/app/src/main/res/layout/fragment_add_manga.xml b/app/src/main/res/layout/fragment_add_manga.xml index 8bd5c7f..87fd307 100644 --- a/app/src/main/res/layout/fragment_add_manga.xml +++ b/app/src/main/res/layout/fragment_add_manga.xml @@ -1,3 +1,4 @@ + - + app:layout_constraintTop_toTopOf="parent"> + android:src="@drawable/sample_cover" /> + android:contentDescription="Avatar" + android:scaleType="centerCrop" + android:src="@drawable/ic_profile" /> + android:textSize="18sp" + android:textStyle="bold" /> - + app:layout_constraintTop_toBottomOf="@id/bannerLayout"> + android:textSize="14sp" /> + android:contentDescription="Vista Compacta" + android:src="@drawable/ic_view_compact" /> + android:contentDescription="Vista Normal" + android:src="@drawable/ic_view_normal" /> + android:contentDescription="Vista Grande" + android:src="@drawable/ic_view_large" /> @@ -117,14 +113,14 @@ android:id="@+id/recyclerViewAnime" android:layout_width="0dp" android:layout_height="0dp" + android:clipToPadding="false" android:padding="8dp" android:paddingBottom="96dp" - android:clipToPadding="false" - android:scrollbars="vertical" - app:layout_constraintTop_toBottomOf="@id/searchLayout" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" /> + android:scrollbars="vertical" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/searchLayout" /> @@ -134,10 +130,9 @@ android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_marginEnd="16dp" - android:layout_marginBottom="40dp" + android:layout_marginBottom="65dp" android:contentDescription="Agregar anime" android:src="@drawable/ic_add" - android:tint="@android:color/white" app:backgroundTint="@color/primary" /> diff --git a/app/src/main/res/layout/fragment_edit_anime.xml b/app/src/main/res/layout/fragment_edit_anime.xml index 0dcb888..33ef005 100644 --- a/app/src/main/res/layout/fragment_edit_anime.xml +++ b/app/src/main/res/layout/fragment_edit_anime.xml @@ -2,40 +2,40 @@ + android:paddingBottom="80dp"> + android:orientation="vertical" + android:padding="16dp"> + android:padding="12dp" /> + android:inputType="number" /> + android:inputType="number" /> + +