diff --git a/app/src/main/java.zip b/app/src/main/java.zip new file mode 100644 index 0000000..87d8a96 Binary files /dev/null and b/app/src/main/java.zip differ diff --git a/app/src/main/java/com/santiparra/yomitrack/api/ApiClient.java b/app/src/main/java/com/santiparra/yomitrack/api/ApiClient.java new file mode 100644 index 0000000..5017e9d --- /dev/null +++ b/app/src/main/java/com/santiparra/yomitrack/api/ApiClient.java @@ -0,0 +1,30 @@ +package com.santiparra.yomitrack.api; + +import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +public class ApiClient { + + private static final String BASE_URL = "http://10.0.2.2:3000/"; + private static Retrofit retrofit = null; + + public static Retrofit getClient() { + if (retrofit == null) { + HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); + logging.setLevel(HttpLoggingInterceptor.Level.BODY); + + OkHttpClient client = new OkHttpClient.Builder() + .addInterceptor(logging) + .build(); + + retrofit = new Retrofit.Builder() + .baseUrl(BASE_URL) + .client(client) + .addConverterFactory(GsonConverterFactory.create()) + .build(); + } + return retrofit; + } +} diff --git a/app/src/main/java/com/santiparra/yomitrack/api/ApiService.java b/app/src/main/java/com/santiparra/yomitrack/api/ApiService.java new file mode 100644 index 0000000..7620976 --- /dev/null +++ b/app/src/main/java/com/santiparra/yomitrack/api/ApiService.java @@ -0,0 +1,62 @@ +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.LoginResponse; +import com.santiparra.yomitrack.model.RegisterResponse; + +import java.util.List; +import java.util.Map; + +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.DELETE; +import retrofit2.http.GET; +import retrofit2.http.POST; +import retrofit2.http.PUT; +import retrofit2.http.Path; +import retrofit2.http.Query; + +public interface ApiService { + + // ---------------- Usuario ---------------- + @POST("users/register") + Call registerUser(@Body Map request); + @POST("users/login") + Call loginUser(@Body UserEntity user); + + // ---------------- Anime ---------------- + @POST("anime/add") + Call insertAnime(@Body AnimeEntity anime); + + @GET("anime/list/{userId}") + Call> getAnimeByUser(@Path("userId") int userId); + + @PUT("anime/{id}") + Call updateAnime(@Path("id") int animeId, @Body AnimeEntity anime); + + @DELETE("anime/delete/{id}") + Call deleteAnime(@Path("id") int id); + + // ---------------- Manga ---------------- + @POST("manga/add") + Call insertManga(@Body MangaEntity manga); + + @GET("manga/list/{userId}") + Call> getMangaByUser(@Path("userId") int userId); + + @PUT("manga/{id}") + Call updateManga(@Path("id") int mangaId, @Body MangaEntity manga); + + @DELETE("manga/delete/{id}") + Call deleteManga(@Path("id") int id); + + // ---------------- AniList API ---------------- + @GET("/anilist/search") + Call> searchAnimeAniList(@Query("query") String query); + + @GET("anilist/search/manga") + Call> searchMangaAniList(@Query("query") String query); +} diff --git a/app/src/main/java/com/santiparra/yomitrack/db/entities/AnimeEntity.java b/app/src/main/java/com/santiparra/yomitrack/db/entities/AnimeEntity.java new file mode 100644 index 0000000..e1df88b --- /dev/null +++ b/app/src/main/java/com/santiparra/yomitrack/db/entities/AnimeEntity.java @@ -0,0 +1,51 @@ +package com.santiparra.yomitrack.db.entities; + +import java.io.Serializable; + +/** + * Entidad que representa un anime guardado por el usuario en la base de datos local. + */ +public class AnimeEntity implements Serializable { + + private int id; + private int userId; + private String title; + private int score; + private int progress; + private String status; + private String type; + private String imageUrl; + + // Getters y Setters + public int getId() { return id; } + + public void setId(int id) { this.id = id; } + + public int getUserId() { return userId; } + + public void setUserId(int userId) { this.userId = userId; } + + public String getTitle() { return title; } + + public void setTitle(String title) { this.title = title; } + + public int getScore() { return score; } + + public void setScore(int score) { this.score = score; } + + public int getProgress() { return progress; } + + public void setProgress(int progress) { this.progress = progress; } + + public String getStatus() { return status; } + + public void setStatus(String status) { this.status = status; } + + public String getType() { return type; } + + public void setType(String type) { this.type = type; } + + public String getImageUrl() { return imageUrl; } + + public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; } +} diff --git a/app/src/main/java/com/santiparra/yomitrack/db/entities/MangaEntity.java b/app/src/main/java/com/santiparra/yomitrack/db/entities/MangaEntity.java new file mode 100644 index 0000000..1c0c115 --- /dev/null +++ b/app/src/main/java/com/santiparra/yomitrack/db/entities/MangaEntity.java @@ -0,0 +1,51 @@ +package com.santiparra.yomitrack.db.entities; + +import java.io.Serializable; + +/** + * Entidad que representa un manga guardado por el usuario en la base de datos local. + */ +public class MangaEntity implements Serializable { + + private int id; + private int userId; + private String title; + private int score; + private int progress; + private String status; + private String type; + private String imageUrl; + + // Getters y Setters + public int getId() { return id; } + + public void setId(int id) { this.id = id; } + + public int getUserId() { return userId; } + + public void setUserId(int userId) { this.userId = userId; } + + public String getTitle() { return title; } + + public void setTitle(String title) { this.title = title; } + + public int getScore() { return score; } + + public void setScore(int score) { this.score = score; } + + public int getProgress() { return progress; } + + public void setProgress(int progress) { this.progress = progress; } + + public String getStatus() { return status; } + + public void setStatus(String status) { this.status = status; } + + public String getType() { return type; } + + public void setType(String type) { this.type = type; } + + public String getImageUrl() { return imageUrl; } + + public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; } +} diff --git a/app/src/main/java/com/santiparra/yomitrack/db/entities/UserEntity.java b/app/src/main/java/com/santiparra/yomitrack/db/entities/UserEntity.java new file mode 100644 index 0000000..c6290f3 --- /dev/null +++ b/app/src/main/java/com/santiparra/yomitrack/db/entities/UserEntity.java @@ -0,0 +1,10 @@ +package com.santiparra.yomitrack.db.entities; + +/** + * Entidad que representa un usuario en la base de datos + */ +public class UserEntity { + public int id; + public String username; + public String password; +} diff --git a/app/src/main/java/com/santiparra/yomitrack/model/AniListAnime.java b/app/src/main/java/com/santiparra/yomitrack/model/AniListAnime.java new file mode 100644 index 0000000..9151a80 --- /dev/null +++ b/app/src/main/java/com/santiparra/yomitrack/model/AniListAnime.java @@ -0,0 +1,36 @@ +package com.santiparra.yomitrack.model; + +public class AniListAnime { + private int id; + private String title; + private String imageUrl; + + // Constructor vacío + public AniListAnime() {} + + public int getId() { + return id; + } + + public String getTitle() { + return title; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setId(int id) { + this.id = id; + } + + public void setTitle(String title) { + this.title = title; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } +} + + diff --git a/app/src/main/java/com/santiparra/yomitrack/model/BrowseSection.java b/app/src/main/java/com/santiparra/yomitrack/model/BrowseSection.java new file mode 100644 index 0000000..dd2a138 --- /dev/null +++ b/app/src/main/java/com/santiparra/yomitrack/model/BrowseSection.java @@ -0,0 +1,24 @@ +package com.santiparra.yomitrack.model; + +import java.util.List; + +/** + * Modelo que representa una sección de la pantalla de exploración con su título y lista de ítems. + */ +public class BrowseSection { + private final String title; + private final List items; + + public BrowseSection(String title, List items) { + this.title = title; + this.items = items; + } + + public String getTitle() { + return title; + } + + public List getItems() { + return items; + } +} diff --git a/app/src/main/java/com/santiparra/yomitrack/model/LoginResponse.java b/app/src/main/java/com/santiparra/yomitrack/model/LoginResponse.java new file mode 100644 index 0000000..31193bb --- /dev/null +++ b/app/src/main/java/com/santiparra/yomitrack/model/LoginResponse.java @@ -0,0 +1,9 @@ +package com.santiparra.yomitrack.model; + +import com.santiparra.yomitrack.db.entities.UserEntity; + +public class LoginResponse { + public boolean success; + public String message; + public UserEntity user; +} diff --git a/app/src/main/java/com/santiparra/yomitrack/model/RegisterResponse.java b/app/src/main/java/com/santiparra/yomitrack/model/RegisterResponse.java new file mode 100644 index 0000000..5ce1bcb --- /dev/null +++ b/app/src/main/java/com/santiparra/yomitrack/model/RegisterResponse.java @@ -0,0 +1,13 @@ +package com.santiparra.yomitrack.model; + +public class RegisterResponse { + private int userId; + + public int getUserId() { + return userId; + } + + public void setUserId(int userId) { + this.userId = userId; + } +} diff --git a/app/src/main/java/com/santiparra/yomitrack/model/adapters/airing/AiringViewHolder.java b/app/src/main/java/com/santiparra/yomitrack/model/adapters/airing/AiringViewHolder.java new file mode 100644 index 0000000..827e45e --- /dev/null +++ b/app/src/main/java/com/santiparra/yomitrack/model/adapters/airing/AiringViewHolder.java @@ -0,0 +1,41 @@ +package com.santiparra.yomitrack.model.adapters.airing; + +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.santiparra.yomitrack.R; +import com.santiparra.yomitrack.model.ItemModel; + +public class AiringViewHolder extends RecyclerView.ViewHolder { + + public ImageView imageView; + public TextView titleTextView; + public TextView progressTextView; + + public AiringViewHolder(@NonNull View itemView) { + super(itemView); + imageView = itemView.findViewById(R.id.mediaImage); + titleTextView = itemView.findViewById(R.id.titleTextView); + progressTextView = itemView.findViewById(R.id.progressTextView); + } + + public void bind(ItemModel item) { + titleTextView.setText(item.getTitle()); + progressTextView.setText("Progress: " + item.getProgress()); + + if (item.getImageUrl() != null && !item.getImageUrl().isEmpty()) { + Glide.with(itemView.getContext()) + .load(item.getImageUrl()) + .placeholder(R.drawable.placeholder_image) + .error(R.drawable.error_image) + .into(imageView); + } else { + imageView.setImageResource(R.drawable.placeholder_image); + } + } +} diff --git a/app/src/main/java/com/santiparra/yomitrack/model/adapters/airing/AnimeViewHolder.java b/app/src/main/java/com/santiparra/yomitrack/model/adapters/airing/AnimeViewHolder.java new file mode 100644 index 0000000..e630cbc --- /dev/null +++ b/app/src/main/java/com/santiparra/yomitrack/model/adapters/airing/AnimeViewHolder.java @@ -0,0 +1,41 @@ +package com.santiparra.yomitrack.model.adapters.airing; + +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.santiparra.yomitrack.R; +import com.santiparra.yomitrack.model.ItemModel; + +public class AnimeViewHolder extends RecyclerView.ViewHolder { + + public ImageView imageView; + public TextView titleTextView; + public TextView progressTextView; + + public AnimeViewHolder(@NonNull View itemView) { + super(itemView); + imageView = itemView.findViewById(R.id.mediaImage); + titleTextView = itemView.findViewById(R.id.titleTextView); + progressTextView = itemView.findViewById(R.id.progressTextView); + } + + public void bind(ItemModel item) { + titleTextView.setText(item.getTitle()); + progressTextView.setText("Progress: " + item.getProgress()); + + if (item.getImageUrl() != null && !item.getImageUrl().isEmpty()) { + Glide.with(itemView.getContext()) + .load(item.getImageUrl()) + .placeholder(R.drawable.placeholder_image) + .error(R.drawable.error_image) + .into(imageView); + } else { + imageView.setImageResource(R.drawable.placeholder_image); + } + } +} diff --git a/app/src/main/java/com/santiparra/yomitrack/model/adapters/anime_adapter/AnimeSearchAdapter.java b/app/src/main/java/com/santiparra/yomitrack/model/adapters/anime_adapter/AnimeSearchAdapter.java new file mode 100644 index 0000000..0424b32 --- /dev/null +++ b/app/src/main/java/com/santiparra/yomitrack/model/adapters/anime_adapter/AnimeSearchAdapter.java @@ -0,0 +1,72 @@ +package com.santiparra.yomitrack.model.adapters.anime_adapter; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.santiparra.yomitrack.R; +import com.santiparra.yomitrack.model.AniListAnime; + +import java.util.List; + +public class AnimeSearchAdapter extends RecyclerView.Adapter { + + private List animeList; + private final OnAnimeClickListener clickListener; + + public interface OnAnimeClickListener { + void onClick(AniListAnime anime); + } + + public AnimeSearchAdapter(List animeList, OnAnimeClickListener clickListener) { + this.animeList = animeList; + this.clickListener = clickListener; + } + + public void setAnimeList(List animeList) { + this.animeList = animeList; + notifyDataSetChanged(); + } + + @NonNull + @Override + public SearchViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_anime, parent, false); + return new SearchViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull SearchViewHolder holder, int position) { + AniListAnime anime = animeList.get(position); + holder.title.setText(anime.getTitle()); + + Glide.with(holder.itemView.getContext()) + .load(anime.getImageUrl()) + .placeholder(R.drawable.rectangle_placeholder) + .into(holder.imageCover); + + holder.itemView.setOnClickListener(v -> clickListener.onClick(anime)); + } + + @Override + public int getItemCount() { + return animeList != null ? animeList.size() : 0; + } + + static class SearchViewHolder extends RecyclerView.ViewHolder { + ImageView imageCover; + TextView title; + + public SearchViewHolder(@NonNull View itemView) { + super(itemView); + imageCover = itemView.findViewById(R.id.imageCover); + title = itemView.findViewById(R.id.textTitle); + } + } +} diff --git a/app/src/main/java/com/santiparra/yomitrack/model/adapters/browser_section_adapter/BrowseGridAdapter.java b/app/src/main/java/com/santiparra/yomitrack/model/adapters/browser_section_adapter/BrowseGridAdapter.java new file mode 100644 index 0000000..328c285 --- /dev/null +++ b/app/src/main/java/com/santiparra/yomitrack/model/adapters/browser_section_adapter/BrowseGridAdapter.java @@ -0,0 +1,65 @@ +package com.santiparra.yomitrack.model.adapters.browser_section_adapter; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.santiparra.yomitrack.R; +import com.santiparra.yomitrack.model.ItemModel; + +import java.util.List; + +public class BrowseGridAdapter extends RecyclerView.Adapter { + + private final List items; + + public BrowseGridAdapter(List items) { + this.items = items; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_browse_card, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + ItemModel item = items.get(position); + holder.title.setText(item.getTitle()); + Glide.with(holder.itemView.getContext()) + .load(item.getImageUrl()) + .placeholder(R.drawable.placeholder_image) + .into(holder.cover); + + // 👉 Animación + Animation animation = AnimationUtils.loadAnimation(holder.itemView.getContext(), R.anim.item_animation_fade_scale); + holder.itemView.startAnimation(animation); + } + + @Override + public int getItemCount() { + return items.size(); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + ImageView cover; + TextView title; + + ViewHolder(View itemView) { + super(itemView); + cover = itemView.findViewById(R.id.imageViewCover); + title = itemView.findViewById(R.id.textViewTitle); + } + } +} + diff --git a/app/src/main/java/com/santiparra/yomitrack/model/adapters/browser_section_adapter/BrowseSectionAdapter.java b/app/src/main/java/com/santiparra/yomitrack/model/adapters/browser_section_adapter/BrowseSectionAdapter.java new file mode 100644 index 0000000..33d1c83 --- /dev/null +++ b/app/src/main/java/com/santiparra/yomitrack/model/adapters/browser_section_adapter/BrowseSectionAdapter.java @@ -0,0 +1,60 @@ +package com.santiparra.yomitrack.model.adapters.browser_section_adapter; + + + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.santiparra.yomitrack.R; +import com.santiparra.yomitrack.model.BrowseSection; +import com.santiparra.yomitrack.model.adapters.homeadapter.HomeAdapter; + +import java.util.List; + +public class BrowseSectionAdapter extends RecyclerView.Adapter { + + private final List sectionList; + + public BrowseSectionAdapter(List sectionList) { + this.sectionList = sectionList; + } + + @NonNull + @Override + public BrowseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_section, parent, false); + return new BrowseViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull BrowseViewHolder holder, int position) { + BrowseSection section = sectionList.get(position); + holder.sectionTitle.setText(section.getTitle()); + + HomeAdapter adapter = new HomeAdapter(section.getItems(), section.getTitle()); + holder.recyclerView.setLayoutManager(new LinearLayoutManager(holder.itemView.getContext(), LinearLayoutManager.HORIZONTAL, false)); + holder.recyclerView.setAdapter(adapter); + } + + @Override + public int getItemCount() { + return sectionList.size(); + } + + static class BrowseViewHolder extends RecyclerView.ViewHolder { + TextView sectionTitle; + RecyclerView recyclerView; + + public BrowseViewHolder(@NonNull View itemView) { + super(itemView); + sectionTitle = itemView.findViewById(R.id.sectionTitle); + recyclerView = itemView.findViewById(R.id.sectionRecyclerView); + } + } +} diff --git a/app/src/main/java/com/santiparra/yomitrack/model/adapters/manga_adapter/MangaSearchAdapter.java b/app/src/main/java/com/santiparra/yomitrack/model/adapters/manga_adapter/MangaSearchAdapter.java new file mode 100644 index 0000000..23b5735 --- /dev/null +++ b/app/src/main/java/com/santiparra/yomitrack/model/adapters/manga_adapter/MangaSearchAdapter.java @@ -0,0 +1,72 @@ +package com.santiparra.yomitrack.model.adapters.manga_adapter; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.santiparra.yomitrack.R; +import com.santiparra.yomitrack.model.AniListAnime; + +import java.util.List; + +public class MangaSearchAdapter extends RecyclerView.Adapter { + + private List mangaList; + private final OnMangaClickListener clickListener; + + public interface OnMangaClickListener { + void onClick(AniListAnime manga); + } + + public MangaSearchAdapter(List mangaList, OnMangaClickListener clickListener) { + this.mangaList = mangaList; + this.clickListener = clickListener; + } + + public void setMangaList(List mangaList) { + this.mangaList = mangaList; + notifyDataSetChanged(); + } + + @NonNull + @Override + public SearchViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_manga, parent, false); + return new SearchViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull SearchViewHolder holder, int position) { + AniListAnime manga = mangaList.get(position); + holder.title.setText(manga.getTitle()); + + Glide.with(holder.itemView.getContext()) + .load(manga.getImageUrl()) + .placeholder(R.drawable.rectangle_placeholder) + .into(holder.imageCover); + + holder.itemView.setOnClickListener(v -> clickListener.onClick(manga)); + } + + @Override + public int getItemCount() { + return mangaList != null ? mangaList.size() : 0; + } + + static class SearchViewHolder extends RecyclerView.ViewHolder { + ImageView imageCover; + TextView title; + + public SearchViewHolder(@NonNull View itemView) { + super(itemView); + imageCover = itemView.findViewById(R.id.imageCover); + title = itemView.findViewById(R.id.textTitle); + } + } +} diff --git a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/addanime/AddAnimeFragment.java b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/addanime/AddAnimeFragment.java new file mode 100644 index 0000000..668eba3 --- /dev/null +++ b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/addanime/AddAnimeFragment.java @@ -0,0 +1,154 @@ +// AddAnimeFragment.java actualizado con LinearLayoutManager asignado al RecyclerView + +package com.santiparra.yomitrack.ui.fragments.addanime; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.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.adapters.anime_adapter.AnimeSearchAdapter; + +import java.util.ArrayList; +import java.util.List; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class AddAnimeFragment extends Fragment { + + private EditText searchEditText, scoreEditText, progressEditText; + private Spinner statusSpinner, typeSpinner; + private RecyclerView searchResults; + private AnimeSearchAdapter searchAdapter; + private ApiService api; + private int userId; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_add_anime, container, false); + + searchEditText = view.findViewById(R.id.editTextSearch); + scoreEditText = view.findViewById(R.id.editTextScore); + progressEditText = view.findViewById(R.id.editTextProgress); + statusSpinner = view.findViewById(R.id.spinnerStatus); + typeSpinner = view.findViewById(R.id.spinnerType); + searchResults = view.findViewById(R.id.recyclerSearchResults); + searchResults.setLayoutManager(new LinearLayoutManager(getContext())); + + api = ApiClient.getClient().create(ApiService.class); + SharedPreferences prefs = requireContext().getSharedPreferences("app_prefs", Context.MODE_PRIVATE); + userId = prefs.getInt("current_user_id", -1); + + setupSpinners(); + setupRecycler(); + setupSearch(); + + return view; + } + + private void setupSpinners() { + ArrayAdapter statusAdapter = ArrayAdapter.createFromResource( + requireContext(), R.array.anime_status_array, android.R.layout.simple_spinner_item); + statusAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + statusSpinner.setAdapter(statusAdapter); + + ArrayAdapter typeAdapter = ArrayAdapter.createFromResource( + requireContext(), R.array.anime_type_array, android.R.layout.simple_spinner_item); + typeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + typeSpinner.setAdapter(typeAdapter); + } + + private void setupRecycler() { + searchAdapter = new AnimeSearchAdapter(new ArrayList<>(), this::onAnimeSelected); + searchResults.setAdapter(searchAdapter); + } + + private void setupSearch() { + searchEditText.setOnEditorActionListener((TextView v, int actionId, KeyEvent event) -> { + if (actionId == EditorInfo.IME_ACTION_SEARCH || + (event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) { + String query = searchEditText.getText().toString().trim(); + if (!query.isEmpty()) { + api.searchAnimeAniList(query).enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + searchAdapter.setAnimeList(response.body()); + } else { + Toast.makeText(getContext(), "Sin resultados", Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + Toast.makeText(getContext(), "Error de red", Toast.LENGTH_SHORT).show(); + } + }); + } + return true; + } + return false; + }); + } + + private void onAnimeSelected(AniListAnime selected) { + String status = statusSpinner.getSelectedItem().toString(); + String type = typeSpinner.getSelectedItem().toString(); + + int score = 0; + int progress = 0; + try { + score = Integer.parseInt(scoreEditText.getText().toString()); + progress = Integer.parseInt(progressEditText.getText().toString()); + } catch (NumberFormatException ignored) {} + + AnimeEntity anime = new AnimeEntity(); + anime.setUserId(userId); + anime.setTitle(selected.getTitle()); + anime.setImageUrl(selected.getImageUrl()); + anime.setStatus(status); + anime.setType(type); + anime.setScore(score); + anime.setProgress(progress); + + api.insertAnime(anime).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + Toast.makeText(getContext(), "Anime añadido", Toast.LENGTH_SHORT).show(); + requireActivity().getSupportFragmentManager().popBackStack(); + } else { + Toast.makeText(getContext(), "Error al guardar anime", Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(getContext(), "Fallo en la conexión", Toast.LENGTH_SHORT).show(); + } + }); + } +} diff --git a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/addmanga/AddMangaFragment.java b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/addmanga/AddMangaFragment.java new file mode 100644 index 0000000..1ea0dc2 --- /dev/null +++ b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/addmanga/AddMangaFragment.java @@ -0,0 +1,152 @@ +package com.santiparra.yomitrack.ui.fragments.addmanga; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.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.adapters.manga_adapter.MangaSearchAdapter; + +import java.util.ArrayList; +import java.util.List; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class AddMangaFragment extends Fragment { + + private EditText searchEditText, scoreEditText, progressEditText; + private Spinner statusSpinner, typeSpinner; + private RecyclerView searchResults; + private MangaSearchAdapter searchAdapter; + private ApiService api; + private int userId; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_add_anime, container, false); + + searchEditText = view.findViewById(R.id.editTextSearch); + scoreEditText = view.findViewById(R.id.editTextScore); + progressEditText = view.findViewById(R.id.editTextProgress); + statusSpinner = view.findViewById(R.id.spinnerStatus); + typeSpinner = view.findViewById(R.id.spinnerType); + searchResults = view.findViewById(R.id.recyclerSearchResults); + searchResults.setLayoutManager(new LinearLayoutManager(getContext())); + + api = ApiClient.getClient().create(ApiService.class); + SharedPreferences prefs = requireContext().getSharedPreferences("app_prefs", Context.MODE_PRIVATE); + userId = prefs.getInt("current_user_id", -1); + + setupSpinners(); + setupRecycler(); + setupSearch(); + + return view; + } + + private void setupSpinners() { + ArrayAdapter statusAdapter = ArrayAdapter.createFromResource( + requireContext(), R.array.anime_status_array, android.R.layout.simple_spinner_item); + statusAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + statusSpinner.setAdapter(statusAdapter); + + ArrayAdapter typeAdapter = ArrayAdapter.createFromResource( + requireContext(), R.array.anime_type_array, android.R.layout.simple_spinner_item); + typeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + typeSpinner.setAdapter(typeAdapter); + } + + private void setupRecycler() { + searchAdapter = new MangaSearchAdapter(new ArrayList<>(), this::onMangaSelected); + searchResults.setAdapter(searchAdapter); + } + + private void setupSearch() { + searchEditText.setOnEditorActionListener((TextView v, int actionId, KeyEvent event) -> { + if (actionId == EditorInfo.IME_ACTION_SEARCH || + (event != null && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) { + String query = searchEditText.getText().toString().trim(); + if (!query.isEmpty()) { + api.searchMangaAniList(query).enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + searchAdapter.setMangaList(response.body()); + } else { + Toast.makeText(getContext(), "Sin resultados", Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + Toast.makeText(getContext(), "Error de red", Toast.LENGTH_SHORT).show(); + } + }); + } + return true; + } + return false; + }); + } + + private void onMangaSelected(AniListAnime selected) { + String status = statusSpinner.getSelectedItem().toString(); + String type = typeSpinner.getSelectedItem().toString(); + + int score = 0; + int progress = 0; + try { + score = Integer.parseInt(scoreEditText.getText().toString()); + progress = Integer.parseInt(progressEditText.getText().toString()); + } catch (NumberFormatException ignored) {} + + MangaEntity manga = new MangaEntity(); + manga.setUserId(userId); + manga.setTitle(selected.getTitle()); + manga.setImageUrl(selected.getImageUrl()); + manga.setStatus(status); + manga.setType(type); + manga.setScore(score); + manga.setProgress(progress); + + api.insertManga(manga).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + Toast.makeText(getContext(), "Manga añadido", Toast.LENGTH_SHORT).show(); + requireActivity().getSupportFragmentManager().popBackStack(); + } else { + Toast.makeText(getContext(), "Error al guardar manga", Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(getContext(), "Fallo en la conexión", Toast.LENGTH_SHORT).show(); + } + }); + } +} diff --git a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/editanime/EditAnimeFragment.java b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/editanime/EditAnimeFragment.java new file mode 100644 index 0000000..4986fab --- /dev/null +++ b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/editanime/EditAnimeFragment.java @@ -0,0 +1,162 @@ +// EditAnimeFragment.java optimizado con setupSpinners y safe saveChanges + +package com.santiparra.yomitrack.ui.fragments.editanime; + +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 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 retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class EditAnimeFragment extends Fragment { + + private EditText editTextTitle, editTextScore, editTextProgress; + private Spinner spinnerStatus, spinnerType; + private Button buttonSave; + 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); + + editTextTitle = view.findViewById(R.id.editTextAnimeTitle); + editTextScore = view.findViewById(R.id.editTextScore); + editTextProgress = view.findViewById(R.id.editTextProgress); + spinnerStatus = view.findViewById(R.id.spinnerStatus); + spinnerType = view.findViewById(R.id.spinnerType); + buttonSave = view.findViewById(R.id.buttonSaveAnime); + + 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(); + } + + setupSpinners(); + + buttonSave.setOnClickListener(v -> saveChanges()); + + return view; + } + + private void setupSpinners() { + ArrayAdapter statusAdapter = ArrayAdapter.createFromResource( + requireContext(), R.array.anime_status_array, android.R.layout.simple_spinner_item); + statusAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinnerStatus.setAdapter(statusAdapter); + + ArrayAdapter typeAdapter = ArrayAdapter.createFromResource( + requireContext(), R.array.anime_type_array, android.R.layout.simple_spinner_item); + typeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinnerType.setAdapter(typeAdapter); + + if (anime != null) { + String[] statusArray = getResources().getStringArray(R.array.anime_status_array); + String[] typeArray = getResources().getStringArray(R.array.anime_type_array); + + for (int i = 0; i < statusArray.length; i++) { + if (statusArray[i].equalsIgnoreCase(anime.getStatus())) { + spinnerStatus.setSelection(i); + break; + } + } + for (int i = 0; i < typeArray.length; i++) { + if (typeArray[i].equalsIgnoreCase(anime.getType())) { + spinnerType.setSelection(i); + break; + } + } + } + } + + private void fillFields() { + editTextTitle.setText(anime.getTitle()); + editTextScore.setText(String.valueOf(anime.getScore())); + editTextProgress.setText(String.valueOf(anime.getProgress())); + } + + private void saveChanges() { + if (anime == null) { + Toast.makeText(requireContext(), "Error: Anime no cargado", Toast.LENGTH_SHORT).show(); + return; + } + + String title = editTextTitle.getText().toString().trim(); + 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() != 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; + } + + anime.setTitle(title); + anime.setScore(score); + anime.setProgress(progress); + anime.setStatus(status); + anime.setType(type); + + api.updateAnime(anime.getId(),anime).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + Toast.makeText(requireContext(), "Anime actualizado", Toast.LENGTH_SHORT).show(); + requireActivity().getSupportFragmentManager().popBackStack(); + } else { + Toast.makeText(requireContext(), "Error al actualizar", Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(requireContext(), "Fallo en la conexión", Toast.LENGTH_SHORT).show(); + } + }); + } +} diff --git a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/editmanga/EditMangaFragment.java b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/editmanga/EditMangaFragment.java new file mode 100644 index 0000000..1cb431a --- /dev/null +++ b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/editmanga/EditMangaFragment.java @@ -0,0 +1,136 @@ +// EditMangaFragment.java conectado a fragment_edit_manga.xml y funcional + +package com.santiparra.yomitrack.ui.fragments.editmanga; + +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 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.MangaEntity; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class EditMangaFragment extends Fragment { + + private EditText editTextTitle, editTextScore, editTextProgress; + private Spinner spinnerStatus, spinnerType; + private Button buttonSave; + 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); + + editTextTitle = view.findViewById(R.id.editTextMangaTitle); + editTextScore = view.findViewById(R.id.editTextScore); + editTextProgress = view.findViewById(R.id.editTextProgress); + spinnerStatus = view.findViewById(R.id.spinnerStatus); + spinnerType = view.findViewById(R.id.spinnerType); + buttonSave = view.findViewById(R.id.buttonSaveManga); + + api = ApiClient.getClient().create(ApiService.class); + SharedPreferences prefs = requireContext().getSharedPreferences("app_prefs", Context.MODE_PRIVATE); + userId = prefs.getInt("current_user_id", -1); + + setupSpinners(); + + if (getArguments() != null && getArguments().containsKey("manga")) { + manga = (MangaEntity) getArguments().getSerializable("manga"); + fillFields(); + } + + buttonSave.setOnClickListener(v -> updateManga()); + + return view; + } + + private void setupSpinners() { + ArrayAdapter statusAdapter = ArrayAdapter.createFromResource( + requireContext(), R.array.anime_status_array, android.R.layout.simple_spinner_item); + statusAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinnerStatus.setAdapter(statusAdapter); + + ArrayAdapter typeAdapter = ArrayAdapter.createFromResource( + requireContext(), R.array.anime_type_array, android.R.layout.simple_spinner_item); + typeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinnerType.setAdapter(typeAdapter); + } + + 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); + break; + } + } + + 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; + } + } + } + + private void updateManga() { + String title = editTextTitle.getText().toString().trim(); + int score = Integer.parseInt(editTextScore.getText().toString()); + int progress = Integer.parseInt(editTextProgress.getText().toString()); + String status = spinnerStatus.getSelectedItem().toString(); + String type = spinnerType.getSelectedItem().toString(); + + manga.setTitle(title); + manga.setScore(score); + manga.setProgress(progress); + manga.setStatus(status); + manga.setType(type); + + api.updateManga(manga.getId(),manga).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + Toast.makeText(getContext(), "Manga actualizado", Toast.LENGTH_SHORT).show(); + requireActivity().getSupportFragmentManager().popBackStack(); + } else { + Toast.makeText(getContext(), "Error al actualizar", Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(getContext(), "Fallo de conexión", Toast.LENGTH_SHORT).show(); + } + }); + } +} diff --git a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/login/LoginActivity.java b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/login/LoginActivity.java new file mode 100644 index 0000000..dd6c82b --- /dev/null +++ b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/login/LoginActivity.java @@ -0,0 +1,17 @@ +package com.santiparra.yomitrack.ui.fragments.login; + +import android.os.Bundle; +import androidx.appcompat.app.AppCompatActivity; +import com.santiparra.yomitrack.R; + +/** + * Actividad contenedora del sistema de login y registro. + * Esta actividad aloja el NavHostFragment definido en el layout activity_login.xml + */ +public class LoginActivity extends AppCompatActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_login); // contiene el NavHostFragment + } +} diff --git a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/login/LoginFragment.java b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/login/LoginFragment.java new file mode 100644 index 0000000..dd3264f --- /dev/null +++ b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/login/LoginFragment.java @@ -0,0 +1,111 @@ +package com.santiparra.yomitrack.ui.fragments.login; + +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.fragment.app.Fragment; +import androidx.navigation.NavController; +import androidx.navigation.fragment.NavHostFragment; + +import com.santiparra.yomitrack.R; +import com.santiparra.yomitrack.api.ApiClient; +import com.santiparra.yomitrack.api.ApiService; +import com.santiparra.yomitrack.db.entities.UserEntity; +import com.santiparra.yomitrack.model.LoginResponse; +import com.santiparra.yomitrack.ui.MainActivity; + +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; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_login, 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); + + loginButton.setOnClickListener(v -> { + String username = usernameEditText.getText().toString().trim(); + String password = passwordEditText.getText().toString().trim(); + + if (username.isEmpty() || password.isEmpty()) { + Toast.makeText(getContext(), "Rellena todos los campos", Toast.LENGTH_SHORT).show(); + return; + } + + ApiService api = ApiClient.getClient().create(ApiService.class); + UserEntity user = new UserEntity(); + user.username = username; + user.password = password; + + Call call = api.loginUser(user); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + LoginResponse result = response.body(); + + if (result.success) { + UserEntity user = result.user; + Toast.makeText(getContext(), "Bienvenido " + user.username, Toast.LENGTH_SHORT).show(); + + saveSession(user.id); // ✅ Guardamos el ID del usuario + navigateToHome(); // ✅ Entramos a MainActivity + } else { + Toast.makeText(getContext(), result.message, Toast.LENGTH_SHORT).show(); + } + } else { + Toast.makeText(getContext(), "Error inesperado", Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(getContext(), "Error de red: " + t.getMessage(), Toast.LENGTH_SHORT).show(); + } + }); + }); + + 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 navigateToHome() { + startActivity(new Intent(getActivity(), MainActivity.class)); + requireActivity().finish(); + } +} diff --git a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/login/SplashActivity.java b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/login/SplashActivity.java new file mode 100644 index 0000000..a4317e8 --- /dev/null +++ b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/login/SplashActivity.java @@ -0,0 +1,34 @@ +package com.santiparra.yomitrack.ui.fragments.login; + +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; + +import androidx.appcompat.app.AppCompatActivity; + +import com.santiparra.yomitrack.ui.MainActivity; +import com.santiparra.yomitrack.ui.fragments.login.LoginActivity; + +/** + * Actividad inicial que decide si ir a LoginActivity o directamente a MainActivity. + */ +public class SplashActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + SharedPreferences prefs = getSharedPreferences("app_prefs", MODE_PRIVATE); + int userId = prefs.getInt("current_user_id", -999); + + if (userId != -999) { + // Ya hay una sesión guardada: entrar a la app + startActivity(new Intent(this, MainActivity.class)); + } else { + // No hay sesión: ir a login + startActivity(new Intent(this, LoginActivity.class)); + } + + finish(); // Cierra el Splash + } +} diff --git a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/register/RegisterFragment.java b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/register/RegisterFragment.java new file mode 100644 index 0000000..ba6223f --- /dev/null +++ b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/register/RegisterFragment.java @@ -0,0 +1,91 @@ +// 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 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.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; + + @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; + } + + private void attemptRegister() { + String username = usernameEditText.getText().toString().trim(); + String password = passwordEditText.getText().toString().trim(); + + if (username.isEmpty() || password.isEmpty()) { + safeToast("Todos los campos son obligatorios"); + return; + } + + HashMap request = new HashMap<>(); + request.put("username", username); + request.put("password", password); + + api.registerUser(request).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (isAdded() && response.isSuccessful() && response.body() != null && response.body().getUserId() > 0) { + int userId = response.body().getUserId(); + SharedPreferences prefs = requireContext().getSharedPreferences("app_prefs", Context.MODE_PRIVATE); + prefs.edit().putInt("current_user_id", userId).apply(); + safeToast("Registro exitoso"); + requireActivity().finish(); + } else { + safeToast("Error al registrar usuario"); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + safeToast("Fallo de conexión: " + t.getMessage()); + } + }); + } + + private void safeToast(String message) { + if (isAdded() && getContext() != null) { + Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show(); + } + } +} diff --git a/app/src/main/res/anim/item_animation_fade_scale.xml b/app/src/main/res/anim/item_animation_fade_scale.xml new file mode 100644 index 0000000..90b3206 --- /dev/null +++ b/app/src/main/res/anim/item_animation_fade_scale.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/dot_green.xml b/app/src/main/res/drawable/dot_green.xml new file mode 100644 index 0000000..3ac0e53 --- /dev/null +++ b/app/src/main/res/drawable/dot_green.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/ic_add.png b/app/src/main/res/drawable/ic_add.png new file mode 100644 index 0000000..a0bbac7 Binary files /dev/null and b/app/src/main/res/drawable/ic_add.png differ diff --git a/app/src/main/res/drawable/ic_view_compact.png b/app/src/main/res/drawable/ic_view_compact.png new file mode 100644 index 0000000..3bc6bcd Binary files /dev/null and b/app/src/main/res/drawable/ic_view_compact.png differ diff --git a/app/src/main/res/drawable/ic_view_large.png b/app/src/main/res/drawable/ic_view_large.png new file mode 100644 index 0000000..c2de485 Binary files /dev/null and b/app/src/main/res/drawable/ic_view_large.png differ diff --git a/app/src/main/res/drawable/ic_view_normal.png b/app/src/main/res/drawable/ic_view_normal.png new file mode 100644 index 0000000..32b3c5d Binary files /dev/null and b/app/src/main/res/drawable/ic_view_normal.png differ diff --git a/app/src/main/res/drawable/placeholder_image.xml b/app/src/main/res/drawable/placeholder_image.xml new file mode 100644 index 0000000..a8b409b --- /dev/null +++ b/app/src/main/res/drawable/placeholder_image.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rectangle_placeholder.xml b/app/src/main/res/drawable/rectangle_placeholder.xml new file mode 100644 index 0000000..a8b409b --- /dev/null +++ b/app/src/main/res/drawable/rectangle_placeholder.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rounded_image_background.xml b/app/src/main/res/drawable/rounded_image_background.xml new file mode 100644 index 0000000..4069a8e --- /dev/null +++ b/app/src/main/res/drawable/rounded_image_background.xml @@ -0,0 +1,5 @@ + + + + diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000..cec3ff6 --- /dev/null +++ b/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,9 @@ + + diff --git a/app/src/main/res/layout/fragment_add_anime.xml b/app/src/main/res/layout/fragment_add_anime.xml new file mode 100644 index 0000000..ae96161 --- /dev/null +++ b/app/src/main/res/layout/fragment_add_anime.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_add_manga.xml b/app/src/main/res/layout/fragment_add_manga.xml new file mode 100644 index 0000000..8bd5c7f --- /dev/null +++ b/app/src/main/res/layout/fragment_add_manga.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_edit_anime.xml b/app/src/main/res/layout/fragment_edit_anime.xml new file mode 100644 index 0000000..0dcb888 --- /dev/null +++ b/app/src/main/res/layout/fragment_edit_anime.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + +