Creacion de nuevas clases y de nuevos xml, funcionamiento de la api de animelist , tambien ya se consume los datos del anime list y ya puedo adaptar el anime, es decir puedo añadir y editar flata por implementar eliminar!

This commit is contained in:
santi 2025-05-21 03:19:59 +02:00
parent a040e9c755
commit 8d22a6d7da
41 changed files with 1216 additions and 828 deletions

View File

@ -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")
}

View File

@ -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">
<!-- PANTALLA INICIAL: SplashActivity -->

Binary file not shown.

View File

@ -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<RegisterResponse> registerUser(@Body Map<String, String> request);
Call<RegisterResponse> registerUser(@Body UserEntity user);
@POST("users/login")
Call<LoginResponse> loginUser(@Body UserEntity user);
@ -34,6 +40,14 @@ public interface ApiService {
@GET("anime/list/{userId}")
Call<List<AnimeEntity>> getAnimeByUser(@Path("userId") int userId);
// Scroll infinito: obtener lista paginada
@GET("/anime/list/{userId}")
Call<AnimePageResponse> getAnimes(
@Path("userId") int userId,
@Query("page") int page,
@Query("size") int size
);
@PUT("anime/{id}")
Call<String> updateAnime(@Path("id") int animeId, @Body AnimeEntity anime);
@ -47,16 +61,33 @@ public interface ApiService {
@GET("manga/list/{userId}")
Call<List<MangaEntity>> getMangaByUser(@Path("userId") int userId);
@GET("/manga/list/{userId}")
Call<MangaPageResponse> getMangas(
@Path("userId") int userId,
@Query("page") int page,
@Query("size") int size
);
@PUT("manga/{id}")
Call<String> updateManga(@Path("id") int mangaId, @Body MangaEntity manga);
@DELETE("manga/delete/{id}")
Call<String> deleteManga(@Path("id") int id);
// ---------------- AniList API ----------------
@GET("/anilist/search")
Call<List<AniListAnime>> searchAnimeAniList(@Query("query") String query);
// ---------------- Activity -------------------
@GET("user/{id}/stats")
Call<Map<String, Map<String, Integer>>> getUserStats(@Path("id") int userId);
@GET("api/activity/list/{userId}")
Call<List<ActivityLog>> getActivityLog(@Path("userId") int userId);
@POST("activity/add")
Call<JSONObject> postActivity(@Body Map<String, Object> body);
// ---------------- AniList API ----------------
@GET("anilist/search")
Call<List<AniListMedia>> searchAniList(@Query("query") String query, @Query("type") String type);
@GET("anilist/search/manga")
Call<List<AniListAnime>> searchMangaAniList(@Query("query") String query);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -29,18 +29,10 @@ public class AnimeAdapter extends RecyclerView.Adapter<AnimeAdapter.AnimeViewHol
private final OnAnimeClickListener onEditClick;
private final OnAnimeClickListener onLongClick;
// Constructor optimizado para uso simple
public AnimeAdapter(Context context) {
this.animeList = new ArrayList<>();
this.viewType = VIEW_NORMAL;
this.onEditClick = null;
this.onLongClick = null;
}
public AnimeAdapter(List<AnimeEntity> 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<AnimeAdapter.AnimeViewHol
notifyDataSetChanged();
}
public void updateList(List<AnimeEntity> 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<AnimeAdapter.AnimeViewHol
}
}
public void updateList(List<AnimeEntity> newList) {
this.animeList = newList;
notifyDataSetChanged();
}
public interface OnAnimeClickListener {
void onClick(AnimeEntity anime);
}
}

View File

@ -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<AnimeSearchAdapter.SearchViewHolder> {
private List<AniListAnime> animeList;
private List<AniListMedia> animeList;
private final OnAnimeClickListener clickListener;
public interface OnAnimeClickListener {
void onClick(AniListAnime anime);
void onClick(AniListMedia anime);
}
public AnimeSearchAdapter(List<AniListAnime> animeList, OnAnimeClickListener clickListener) {
public AnimeSearchAdapter(List<AniListMedia> animeList, OnAnimeClickListener clickListener) {
this.animeList = animeList;
this.clickListener = clickListener;
}
public void setAnimeList(List<AniListAnime> animeList) {
public void setAnimeList(List<AniListMedia> animeList) {
this.animeList = animeList;
notifyDataSetChanged();
}
@ -43,7 +43,7 @@ public class AnimeSearchAdapter extends RecyclerView.Adapter<AnimeSearchAdapter.
@Override
public void onBindViewHolder(@NonNull SearchViewHolder holder, int position) {
AniListAnime anime = animeList.get(position);
AniListMedia anime = animeList.get(position);
holder.title.setText(anime.getTitle());
Glide.with(holder.itemView.getContext())

View File

@ -1,7 +1,6 @@
// MangaAdapter.java actualizado con statusDot dinámico
package com.santiparra.yomitrack.model.adapters.manga_adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -38,11 +37,6 @@ public class MangaAdapter extends RecyclerView.Adapter<MangaAdapter.MangaViewHol
this.onLongClick = onLongClick;
}
public void setViewType(int viewType) {
this.viewType = viewType;
notifyDataSetChanged();
}
@NonNull
@Override
public MangaViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@ -71,7 +65,8 @@ public class MangaAdapter extends RecyclerView.Adapter<MangaAdapter.MangaViewHol
}
if (holder.textProgress != null) {
holder.textProgress.setText(manga.getProgress() + " caps");
String progress = manga.getProgress() + " caps";
holder.textProgress.setText(progress);
}
if (holder.textScore != null) {
@ -97,7 +92,7 @@ public class MangaAdapter extends RecyclerView.Adapter<MangaAdapter.MangaViewHol
case "Completed":
colorResId = R.color.status_completed;
break;
case "Reading":
case "Watching":
colorResId = R.color.status_watching;
break;
case "Paused":
@ -156,6 +151,11 @@ public class MangaAdapter extends RecyclerView.Adapter<MangaAdapter.MangaViewHol
}
}
public void updateList(List<MangaEntity> newList) {
this.mangaList = newList;
notifyDataSetChanged();
}
public interface OnMangaClickListener {
void onClick(MangaEntity manga);
}

View File

@ -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<MangaSearchAdapter.SearchViewHolder> {
private List<AniListAnime> mangaList;
private List<AniListMedia> mangaList;
private final OnMangaClickListener clickListener;
public interface OnMangaClickListener {
void onClick(AniListAnime manga);
void onClick(AniListMedia manga);
}
public MangaSearchAdapter(List<AniListAnime> mangaList, OnMangaClickListener clickListener) {
public MangaSearchAdapter(List<AniListMedia> mangaList, OnMangaClickListener clickListener) {
this.mangaList = mangaList;
this.clickListener = clickListener;
}
public void setMangaList(List<AniListAnime> mangaList) {
public void setMangaList(List<AniListMedia> mangaList) {
this.mangaList = mangaList;
notifyDataSetChanged();
}
@ -43,7 +43,7 @@ public class MangaSearchAdapter extends RecyclerView.Adapter<MangaSearchAdapter.
@Override
public void onBindViewHolder(@NonNull SearchViewHolder holder, int position) {
AniListAnime manga = mangaList.get(position);
AniListMedia manga = mangaList.get(position);
holder.title.setText(manga.getTitle());
Glide.with(holder.itemView.getContext())

View File

@ -1,4 +1,4 @@
// AddAnimeFragment.java actualizado con LinearLayoutManager asignado al RecyclerView
// AddAnimeFragment.java
package com.santiparra.yomitrack.ui.fragments.addanime;
@ -26,11 +26,13 @@ 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.AniListAnime;
import com.santiparra.yomitrack.model.AniListMedia;
import com.santiparra.yomitrack.model.adapters.anime_adapter.AnimeSearchAdapter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import retrofit2.Call;
import retrofit2.Callback;
@ -92,9 +94,9 @@ public class AddAnimeFragment extends Fragment {
(event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) {
String query = searchEditText.getText().toString().trim();
if (!query.isEmpty()) {
api.searchAnimeAniList(query).enqueue(new Callback<List<AniListAnime>>() {
api.searchAniList(query, "ANIME").enqueue(new Callback<List<AniListMedia>>() {
@Override
public void onResponse(Call<List<AniListAnime>> call, Response<List<AniListAnime>> response) {
public void onResponse(Call<List<AniListMedia>> call, Response<List<AniListMedia>> 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<List<AniListAnime>> call, Throwable t) {
public void onFailure(Call<List<AniListMedia>> 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<String> call, Response<String> 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<String, Object> 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) {}
});
}
}

View File

@ -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<CharSequence> 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<CharSequence> 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<List<AniListAnime>>() {
api.searchAniList(query, "MANGA").enqueue(new Callback<List<AniListMedia>>() {
@Override
public void onResponse(Call<List<AniListAnime>> call, Response<List<AniListAnime>> response) {
public void onResponse(Call<List<AniListMedia>> call, Response<List<AniListMedia>> 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<List<AniListAnime>> call, Throwable t) {
public void onFailure(Call<List<AniListMedia>> 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<String> call, Response<String> 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<String, Object> 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) {}
});
}
}

View File

@ -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<AnimeEntity> animeList = new ArrayList<>();
private final List<AnimeEntity> 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<List<AnimeEntity>>() {
@Override
public void onResponse(@NonNull Call<List<AnimeEntity>> call,
@NonNull Response<List<AnimeEntity>> 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<List<AnimeEntity>> 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,17 +173,52 @@ public class FragmentAnime extends Fragment {
api.deleteAnime(anime.getId()).enqueue(new Callback<String>() {
@Override
public void onResponse(Call<String> call, Response<String> 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<String> 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();
}
}
});
}
private void loadMoreAnimes(int page) {
isLoading = true;
api.getAnimes(userId, page, PAGE_SIZE).enqueue(new Callback<AnimePageResponse>() {
@Override
public void onResponse(Call<AnimePageResponse> call, Response<AnimePageResponse> response) {
if (!isAdded()) return;
if (response.isSuccessful() && response.body() != null) {
List<AnimeEntity> 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<AnimePageResponse> call, Throwable t) {
isLoading = false;
if (isAdded()) {
Toast.makeText(requireContext(), "Error al cargar más animes", Toast.LENGTH_SHORT).show();
}
}
});
}

View File

@ -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<String> statusAdapter = new ArrayAdapter<>(requireContext(), android.R.layout.simple_spinner_item, statusArray);
spinnerStatus.setAdapter(statusAdapter);
ArrayAdapter<String> 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<CharSequence> 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<CharSequence> 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("", (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<String>() {
api.updateAnime(anime.getId(), anime).enqueue(new Callback<String>() {
@Override
public void onResponse(Call<String> call, Response<String> 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<String>() {
@Override
public void onResponse(Call<String> call, Response<String> 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<String> call, Throwable t) {
Toast.makeText(requireContext(), "Fallo en la conexión", Toast.LENGTH_SHORT).show();
}
});
}
}

View File

@ -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<CharSequence> 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<String> statusAdapter = new ArrayAdapter<>(requireContext(), android.R.layout.simple_spinner_item, statusArray);
spinnerStatus.setAdapter(statusAdapter);
ArrayAdapter<CharSequence> 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<String> 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("", (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<String>() {
api.updateManga(manga.getId(), manga).enqueue(new Callback<String>() {
@Override
public void onResponse(Call<String> call, Response<String> 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<String> 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<String>() {
@Override
public void onResponse(Call<String> call, Response<String> 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<String> call, Throwable t) {
Toast.makeText(requireContext(), "Fallo en la conexión", Toast.LENGTH_SHORT).show();
}
});
}

View File

@ -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);
}
}

View File

@ -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<LoginResponse>() {
@Override
public void onResponse(Call<LoginResponse> call, Response<LoginResponse> 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<LoginResponse> call = api.loginUser(user);
call.enqueue(new Callback<LoginResponse>() {
@Override
public void onResponse(Call<LoginResponse> call, Response<LoginResponse> 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<LoginResponse> call, Throwable t) {
Toast.makeText(getContext(), "Error de red: " + t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
@Override
public void onFailure(@NonNull Call<LoginResponse> 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;
}
}

View File

@ -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<MangaEntity> 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<List<MangaEntity>>() {
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onResponse(Call<List<MangaEntity>> call, Response<List<MangaEntity>> 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<List<MangaEntity>> 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<MangaEntity> 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<String>() {
@Override
public void onResponse(Call<String> call, Response<String> 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<String> 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<MangaPageResponse>() {
@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);
adapter.notifyItemRangeInserted(mangaList.size() - nuevos.size(), nuevos.size());
isLoading = response.body().isHasNextPage();
} else {
isLoading = false;
}
}
@Override
public void onFailure(Call<MangaPageResponse> call, Throwable t) {
isLoading = false;
if (isAdded()) {
Toast.makeText(requireContext(), "Error al cargar más mangas", Toast.LENGTH_SHORT).show();
}
}
});
}

View File

@ -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<List<AnimeEntity>>() {
api.getUserStats(userId).enqueue(new Callback<Map<String, Map<String, Integer>>>() {
@Override
public void onResponse(Call<List<AnimeEntity>> call, Response<List<AnimeEntity>> response) {
public void onResponse(Call<Map<String, Map<String, Integer>>> call, Response<Map<String, Map<String, Integer>>> response) {
if (response.isSuccessful() && response.body() != null) {
showStats(animeStatsContainer, "Anime", countByStatus(response.body()));
Map<String, Integer> animeStats = response.body().get("animeStats");
Map<String, Integer> mangaStats = response.body().get("mangaStats");
populateStats(animeStatsContainer, animeStats);
populateStats(mangaStatsContainer, mangaStats);
}
}
@Override
public void onFailure(Call<List<AnimeEntity>> call, Throwable t) {
Toast.makeText(getContext(), "Error al cargar anime", Toast.LENGTH_SHORT).show();
}
});
api.getMangaByUser(userId).enqueue(new Callback<List<MangaEntity>>() {
@Override
public void onResponse(Call<List<MangaEntity>> call, Response<List<MangaEntity>> response) {
if (response.isSuccessful() && response.body() != null) {
showStats(mangaStatsContainer, "Manga", countByStatus(response.body()));
}
}
@Override
public void onFailure(Call<List<MangaEntity>> call, Throwable t) {
Toast.makeText(getContext(), "Error al cargar manga", Toast.LENGTH_SHORT).show();
}
public void onFailure(Call<Map<String, Map<String, Integer>>> call, Throwable t) {}
});
}
private Map<String, Integer> countByStatus(List<?> items) {
Map<String, Integer> 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<String, Integer> 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<String, Integer> stats) {
container.removeAllViews();
int total = 0;
for (int count : data.values()) total += count;
for (int count : stats.values()) total += count;
for (Map.Entry<String, Integer> 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<String, Integer> 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<List<ActivityLog>>() {
@Override
public void onResponse(Call<List<ActivityLog>> call, Response<List<ActivityLog>> 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<List<ActivityLog>> 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<String, Object> post = new HashMap<>();
post.put("userId", userId);
post.put("action", "publicó");
post.put("mediaTitle", status);
api.postActivity(post).enqueue(new Callback<JSONObject>() {
@Override
public void onResponse(Call<JSONObject> call, Response<JSONObject> response) {
if (response.isSuccessful()) {
editStatus.setText("");
loadActivity();
Toast.makeText(getContext(), "Publicado", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<JSONObject> 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();
}
}

View File

@ -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<String, String> 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<RegisterResponse>() {
apiService.registerUser(user).enqueue(new Callback<RegisterResponse>() {
@Override
public void onResponse(Call<RegisterResponse> call, Response<RegisterResponse> 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<RegisterResponse> call, @NonNull Response<RegisterResponse> 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<RegisterResponse> call, Throwable t) {
safeToast("Fallo de conexión: " + t.getMessage());
public void onFailure(@NonNull Call<RegisterResponse> 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;
}
}

View File

@ -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<UserStats> getAnimeStats() {
List<UserStats> 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<UserStats> getMangaStats() {
List<UserStats> 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;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 911 KiB

View File

@ -1,3 +1,4 @@
<?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="match_parent"

View File

@ -1,8 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background"
@ -16,21 +15,19 @@
<!-- Banner -->
<RelativeLayout
android:id="@+id/bannerLayout"
android:layout_width="0dp"
android:layout_height="120dp"
android:layout_width="match_parent"
android:layout_height="160dp"
android:background="@drawable/sample_cover"
android:padding="12dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/imageViewBanner"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="Banner"
android:scaleType="centerCrop"
android:src="@drawable/sample_cover"
android:contentDescription="Banner" />
android:src="@drawable/sample_cover" />
<ImageView
android:id="@+id/imageViewAvatar"
@ -39,39 +36,38 @@
android:layout_alignParentBottom="true"
android:layout_marginStart="12dp"
android:layout_marginBottom="12dp"
android:src="@drawable/ic_profile"
android:background="@drawable/circle_mask"
android:scaleType="centerCrop"
android:clipToOutline="true"
android:contentDescription="Avatar" />
android:contentDescription="Avatar"
android:scaleType="centerCrop"
android:src="@drawable/ic_profile" />
<TextView
android:id="@+id/textViewUsername"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/imageViewAvatar"
android:layout_alignBottom="@id/imageViewAvatar"
android:layout_marginStart="12dp"
android:layout_toEndOf="@id/imageViewAvatar"
android:text="Usuario"
android:textColor="@android:color/white"
android:textStyle="bold"
android:textSize="18sp" />
android:textSize="18sp"
android:textStyle="bold" />
</RelativeLayout>
<!-- Búsqueda + botones -->
<LinearLayout
android:id="@+id/searchLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingBottom="4dp"
app:layout_constraintTop_toBottomOf="@id/bannerLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
app:layout_constraintTop_toBottomOf="@id/bannerLayout">
<EditText
android:id="@+id/editSearch"
@ -80,36 +76,36 @@
android:layout_weight="1"
android:background="@drawable/edit_text_background"
android:hint="Buscar anime..."
android:inputType="text"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:textColor="@android:color/black"
android:textSize="14sp"
android:inputType="text" />
android:textSize="14sp" />
<ImageButton
android:id="@+id/btnViewCompact"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:src="@drawable/ic_view_compact"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="Vista Compacta" />
android:contentDescription="Vista Compacta"
android:src="@drawable/ic_view_compact" />
<ImageButton
android:id="@+id/btnViewNormal"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_view_normal"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="Vista Normal" />
android:contentDescription="Vista Normal"
android:src="@drawable/ic_view_normal" />
<ImageButton
android:id="@+id/btnViewLarge"
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_view_large"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="Vista Grande" />
android:contentDescription="Vista Grande"
android:src="@drawable/ic_view_large" />
</LinearLayout>
<!-- Lista de animes -->
@ -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" />
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- FAB correctamente posicionado -->
@ -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" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -2,40 +2,40 @@
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:paddingTop="100dp"
android:paddingBottom="80dp"
android:fillViewport="true">
android:paddingBottom="80dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:orientation="vertical">
android:orientation="vertical"
android:padding="16dp">
<EditText
android:id="@+id/editTextAnimeTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/edit_text_background"
android:hint="Título del anime"
android:inputType="text"
android:padding="12dp"
android:background="@drawable/edit_text_background" />
android:padding="12dp" />
<EditText
android:id="@+id/editTextScore"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:hint="Puntuación (0-100)"
android:inputType="number"
android:layout_marginTop="12dp" />
android:inputType="number" />
<EditText
android:id="@+id/editTextProgress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:hint="Progreso (episodios vistos)"
android:inputType="number"
android:layout_marginTop="12dp" />
android:inputType="number" />
<Spinner
android:id="@+id/spinnerStatus"
@ -55,9 +55,18 @@
android:id="@+id/buttonSaveAnime"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Guardar cambios"
android:layout_marginTop="24dp"
android:backgroundTint="@color/primary"
android:text="Guardar cambios"
android:textColor="@android:color/white" />
<Button
android:id="@+id/buttonDeleteAnime"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:backgroundTint="@android:color/holo_red_dark"
android:text="Eliminar anime"
android:textColor="@android:color/white" />
</LinearLayout>
</ScrollView>

View File

@ -1,40 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:paddingTop="100dp"
android:paddingBottom="80dp"
android:fillViewport="true">
android:paddingBottom="80dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:orientation="vertical">
android:orientation="vertical"
android:padding="16dp">
<EditText
android:id="@+id/editTextMangaTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/edit_text_background"
android:hint="Título del manga"
android:inputType="text"
android:padding="12dp"
android:background="@drawable/edit_text_background" />
android:padding="12dp" />
<EditText
android:id="@+id/editTextScore"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:hint="Puntuación (0-100)"
android:inputType="number"
android:layout_marginTop="12dp" />
android:inputType="number" />
<EditText
android:id="@+id/editTextProgress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:hint="Progreso (capítulos leídos)"
android:inputType="number"
android:layout_marginTop="12dp" />
android:inputType="number" />
<Spinner
android:id="@+id/spinnerStatus"
@ -54,9 +55,18 @@
android:id="@+id/buttonSaveManga"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Guardar cambios"
android:layout_marginTop="24dp"
android:backgroundTint="@color/primary"
android:text="Guardar cambios"
android:textColor="@android:color/white" />
<Button
android:id="@+id/buttonDeleteManga"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:backgroundTint="@android:color/holo_red_dark"
android:text="Eliminar manga"
android:textColor="@android:color/white" />
</LinearLayout>
</ScrollView>

View File

@ -1,111 +1,140 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background"
android:fitsSystemWindows="true"
tools:context=".ui.fragments.manga_list.FragmentManga">
<LinearLayout
android:orientation="vertical"
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Banner superior reducido -->
<!-- Banner -->
<RelativeLayout
android:id="@+id/bannerLayout"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_height="160dp"
android:background="@drawable/sample_cover"
android:padding="12dp">
android:padding="12dp"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/imageViewBanner"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="Banner"
android:scaleType="centerCrop"
android:src="@drawable/sample_cover"
android:contentDescription="Banner" />
android:src="@drawable/sample_cover" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="bottom|start"
android:paddingBottom="12dp"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:layout_alignParentBottom="true">
<ImageView
android:id="@+id/imageViewAvatar"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentBottom="true"
android:layout_marginStart="12dp"
android:layout_marginBottom="12dp"
android:background="@drawable/circle_mask"
android:clipToOutline="true"
android:contentDescription="Avatar"
android:scaleType="centerCrop"
android:src="@drawable/ic_profile" />
<!-- Avatar -->
<ImageView
android:id="@+id/imageViewAvatar"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_profile"
android:background="@drawable/circle_mask"
android:scaleType="centerCrop"
android:clipToOutline="true"
android:contentDescription="Avatar"
android:layout_marginEnd="12dp" />
<!-- Nombre -->
<TextView
android:id="@+id/textViewUsername"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Usuario"
android:textColor="@android:color/white"
android:textStyle="bold"
android:textSize="18sp"
android:gravity="center_vertical" />
<!-- Botón de cambiar vista -->
<ImageButton
android:id="@+id/btnViewCompact"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_view_compact" />
<ImageButton
android:id="@+id/btnViewNormal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_view_normal" />
<ImageButton
android:id="@+id/btnViewLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_view_large" />
</LinearLayout>
<TextView
android:id="@+id/textViewUsername"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/imageViewAvatar"
android:layout_marginStart="12dp"
android:layout_toEndOf="@id/imageViewAvatar"
android:text="Usuario"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold" />
</RelativeLayout>
<!-- Lista scrollable -->
<!-- Búsqueda + botones -->
<LinearLayout
android:id="@+id/searchLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingBottom="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/bannerLayout">
<EditText
android:id="@+id/editSearch"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_weight="1"
android:background="@drawable/edit_text_background"
android:hint="Buscar manga..."
android:inputType="text"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:textColor="@android:color/black"
android:textSize="14sp" />
<ImageButton
android:id="@+id/btnViewCompact"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="Vista Compacta"
android:src="@drawable/ic_view_compact" />
<ImageButton
android:id="@+id/btnViewNormal"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="Vista Normal"
android:src="@drawable/ic_view_normal" />
<ImageButton
android:id="@+id/btnViewLarge"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="Vista Grande"
android:src="@drawable/ic_view_large" />
</LinearLayout>
<!-- Lista de mangas -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewManga"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:paddingBottom="100dp"
android:clipToPadding="false"
android:scrollbars="vertical" />
</LinearLayout>
android:padding="8dp"
android:paddingBottom="96dp"
android:scrollbars="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/searchLayout" />
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- FAB flotante -->
<!-- FAB para añadir manga -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fabAddManga"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="24dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="65dp"
android:contentDescription="Agregar manga"
android:src="@drawable/ic_add"
android:tint="@android:color/white"
app:backgroundTint="@color/primary" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
@ -6,12 +5,12 @@
android:fillViewport="true">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="32dp">
<!-- Header con avatar + nombre -->
<!-- Encabezado: Cover, Avatar y Nombre -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="200dp">
@ -27,9 +26,9 @@
android:id="@+id/avatarImage"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_margin="16dp"
android:layout_alignParentBottom="true"
android:layout_alignParentStart="true"
android:layout_alignParentBottom="true"
android:layout_margin="16dp"
android:background="@drawable/circle_mask"
android:clipToOutline="true"
android:scaleType="centerCrop"
@ -40,114 +39,93 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/avatarImage"
android:layout_toEndOf="@id/avatarImage"
android:layout_marginStart="12dp"
android:layout_toEndOf="@id/avatarImage"
android:text="BtwIsSanti"
android:textColor="@color/textPrimary"
android:textSize="20sp"
android:textStyle="bold" />
</RelativeLayout>
<!-- Descripción en tarjeta -->
<androidx.cardview.widget.CardView
<!-- Biografía -->
<EditText
android:id="@+id/editBiography"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:cardCornerRadius="16dp"
app:cardElevation="6dp">
android:layout_marginTop="12dp"
android:background="@drawable/edittext_background"
android:hint="Escribe tu biografía aquí"
android:inputType="textMultiLine"
android:minLines="3" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp"
android:background="@color/primary">
<Button
android:id="@+id/buttonSaveBio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Guardar Biografía" />
<TextView
android:id="@+id/descriptionTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Description"
android:textColor="@color/textPrimary"
android:textStyle="bold"
android:textSize="18sp" />
<TextView
android:id="@+id/profileDescriptionText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Eiko is my waifu right now. Mai and Mikasa is my second wife."
android:textColor="@color/textPrimary"
android:textSize="16sp"
android:maxLines="2"
android:ellipsize="end"
android:layout_marginTop="8dp" />
<TextView
android:id="@+id/textViewSeeMore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ver más"
android:textColor="@color/activeTint"
android:textSize="14sp"
android:paddingTop="4dp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<!-- Estadísticas de anime -->
<!-- Estadísticas de Anime -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Anime Stats"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@color/textPrimary"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp" />
android:textStyle="bold" />
<LinearLayout
android:id="@+id/animeStatsContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_margin="16dp" />
android:layout_marginBottom="16dp"
android:orientation="vertical" />
<!-- Estadísticas de manga -->
<!-- Estadísticas de Manga -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Manga Stats"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@color/textPrimary"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp" />
android:textStyle="bold" />
<LinearLayout
android:id="@+id/mangaStatsContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_margin="16dp" />
android:layout_marginBottom="16dp"
android:orientation="vertical" />
<!-- Estado / Pensamiento -->
<EditText
android:id="@+id/editStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:background="@drawable/edittext_background"
android:hint="¿Qué estás pensando?"
android:inputType="text" />
<Button
android:id="@+id/buttonPostStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Publicar Estado" />
<!-- Actividad reciente -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Recent Activity"
android:layout_marginTop="16dp"
android:text="Actividad Reciente"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@color/textPrimary"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp" />
android:textStyle="bold" />
<LinearLayout
android:id="@+id/activityContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="8dp"
android:layout_marginBottom="32dp"
android:paddingBottom="8dp" />
android:orientation="vertical" />
</LinearLayout>
</ScrollView>

View File

@ -1,6 +1,8 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:background="@drawable/bg_card"
android:orientation="horizontal"
android:padding="12dp">
@ -8,31 +10,31 @@
android:id="@+id/imageCover"
android:layout_width="72dp"
android:layout_height="96dp"
android:scaleType="centerCrop"
android:layout_marginEnd="12dp"
android:contentDescription="Portada" />
android:contentDescription="Portada"
android:scaleType="centerCrop" />
<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/textTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Título"
android:textStyle="bold"
android:textSize="16sp" />
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/textStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Estado"
android:textSize="14sp"
android:textColor="@android:color/darker_gray" />
android:textColor="@android:color/darker_gray"
android:textSize="14sp" />
<TextView
android:id="@+id/textScore"
@ -48,6 +50,7 @@
android:text="Progreso: 10 eps"
android:textSize="14sp" />
<TextView
android:id="@+id/textType"
android:layout_width="wrap_content"

View File

@ -1,6 +1,8 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:background="@drawable/bg_card"
android:orientation="vertical"
android:padding="12dp">
@ -9,14 +11,16 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Título Compacto"
android:textStyle="bold"
android:textSize="14sp" />
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:id="@+id/textStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Estado"
android:textSize="12sp"
android:textColor="@android:color/darker_gray" />
android:textColor="@android:color/darker_gray"
android:textSize="12sp" />
</LinearLayout>

View File

@ -8,9 +8,9 @@
android:id="@+id/imageCover"
android:layout_width="match_parent"
android:layout_height="240dp"
android:scaleType="centerCrop"
android:background="@drawable/rectangle_placeholder"
android:contentDescription="Portada" />
android:contentDescription="Portada"
android:scaleType="centerCrop" />
<!-- Progreso en esquina inferior izquierda -->
<TextView
@ -19,11 +19,11 @@
android:layout_height="wrap_content"
android:layout_gravity="bottom|start"
android:layout_margin="6dp"
android:background="@android:color/transparent"
android:text="6/12"
android:textColor="#FF4444"
android:textStyle="bold"
android:textSize="14sp"
android:background="@android:color/transparent" />
android:textStyle="bold" />
<!-- Título con fondo oscuro -->
<TextView
@ -32,12 +32,12 @@
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#99000000"
android:ellipsize="end"
android:maxLines="2"
android:padding="6dp"
android:text="Nombre del Anime"
android:textColor="@android:color/white"
android:textSize="14sp"
android:padding="6dp"
android:maxLines="2"
android:ellipsize="end" />
android:textSize="14sp" />
<!-- Indicador de estado (ej: Watching) -->
<View

View File

@ -1,6 +1,8 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:background="@drawable/bg_card"
android:orientation="horizontal"
android:padding="12dp">
@ -8,31 +10,31 @@
android:id="@+id/imageCover"
android:layout_width="72dp"
android:layout_height="96dp"
android:scaleType="centerCrop"
android:layout_marginEnd="12dp"
android:contentDescription="Portada" />
android:contentDescription="Portada"
android:scaleType="centerCrop" />
<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/textTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Título"
android:textStyle="bold"
android:textSize="16sp" />
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/textStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Estado"
android:textSize="14sp"
android:textColor="@android:color/darker_gray" />
android:textColor="@android:color/darker_gray"
android:textSize="14sp" />
<TextView
android:id="@+id/textScore"

View File

@ -1,6 +1,8 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:background="@drawable/bg_card"
android:orientation="vertical"
android:padding="12dp">
@ -9,14 +11,14 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Título Compacto"
android:textStyle="bold"
android:textSize="14sp" />
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:id="@+id/textStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Estado"
android:textSize="12sp"
android:textColor="@android:color/darker_gray" />
android:textColor="@android:color/darker_gray"
android:textSize="12sp" />
</LinearLayout>

View File

@ -8,9 +8,9 @@
android:id="@+id/imageCover"
android:layout_width="match_parent"
android:layout_height="240dp"
android:scaleType="centerCrop"
android:background="@drawable/rectangle_placeholder"
android:contentDescription="Portada" />
android:contentDescription="Portada"
android:scaleType="centerCrop" />
<!-- Progreso en esquina inferior izquierda -->
<TextView
@ -19,11 +19,11 @@
android:layout_height="wrap_content"
android:layout_gravity="bottom|start"
android:layout_margin="6dp"
android:background="@android:color/transparent"
android:text="6/12"
android:textColor="#FF4444"
android:textStyle="bold"
android:textSize="14sp"
android:background="@android:color/transparent" />
android:textStyle="bold" />
<!-- Título con fondo oscuro -->
<TextView
@ -32,12 +32,12 @@
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#99000000"
android:ellipsize="end"
android:maxLines="2"
android:padding="6dp"
android:text="Nombre del Manga"
android:textColor="@android:color/white"
android:textSize="14sp"
android:padding="6dp"
android:maxLines="2"
android:ellipsize="end" />
android:textSize="14sp" />
<!-- Indicador de estado (ej: Watching) -->
<View

View File

@ -7,9 +7,9 @@
<fragment
android:id="@+id/loginFragment"
android:name="com.santiparra.yomitrack.ui.fragments.login.LoginFragment"
android:label="Login" >
android:label="Login">
<action
android:id="@+id/action_login_to_register"
android:id="@+id/action_loginFragment_to_registerFragment"
app:destination="@id/registerFragment" />
</fragment>

View File

@ -51,4 +51,5 @@
<item name="buttonSaveAnime" type="id" />
<item name="statusDot" type="id" />
<item name="editSearch" type="id" />
<item name="animeStatText" type="id" />
</resources>