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-16 22:29:36 +02:00
parent 1f526aa91e
commit a040e9c755
46 changed files with 1924 additions and 0 deletions

BIN
app/src/main/java.zip Normal file

Binary file not shown.

View File

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

View File

@ -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<RegisterResponse> registerUser(@Body Map<String, String> request);
@POST("users/login")
Call<LoginResponse> loginUser(@Body UserEntity user);
// ---------------- Anime ----------------
@POST("anime/add")
Call<String> insertAnime(@Body AnimeEntity anime);
@GET("anime/list/{userId}")
Call<List<AnimeEntity>> getAnimeByUser(@Path("userId") int userId);
@PUT("anime/{id}")
Call<String> updateAnime(@Path("id") int animeId, @Body AnimeEntity anime);
@DELETE("anime/delete/{id}")
Call<String> deleteAnime(@Path("id") int id);
// ---------------- Manga ----------------
@POST("manga/add")
Call<String> insertManga(@Body MangaEntity manga);
@GET("manga/list/{userId}")
Call<List<MangaEntity>> getMangaByUser(@Path("userId") int userId);
@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);
@GET("anilist/search/manga")
Call<List<AniListAnime>> searchMangaAniList(@Query("query") String query);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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<ItemModel> items;
public BrowseSection(String title, List<ItemModel> items) {
this.title = title;
this.items = items;
}
public String getTitle() {
return title;
}
public List<ItemModel> getItems() {
return items;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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<AnimeSearchAdapter.SearchViewHolder> {
private List<AniListAnime> animeList;
private final OnAnimeClickListener clickListener;
public interface OnAnimeClickListener {
void onClick(AniListAnime anime);
}
public AnimeSearchAdapter(List<AniListAnime> animeList, OnAnimeClickListener clickListener) {
this.animeList = animeList;
this.clickListener = clickListener;
}
public void setAnimeList(List<AniListAnime> 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);
}
}
}

View File

@ -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<BrowseGridAdapter.ViewHolder> {
private final List<ItemModel> items;
public BrowseGridAdapter(List<ItemModel> items) {
this.items = items;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_browse_card, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
ItemModel item = items.get(position);
holder.title.setText(item.getTitle());
Glide.with(holder.itemView.getContext())
.load(item.getImageUrl())
.placeholder(R.drawable.placeholder_image)
.into(holder.cover);
// 👉 Animación
Animation animation = AnimationUtils.loadAnimation(holder.itemView.getContext(), R.anim.item_animation_fade_scale);
holder.itemView.startAnimation(animation);
}
@Override
public int getItemCount() {
return items.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
ImageView cover;
TextView title;
ViewHolder(View itemView) {
super(itemView);
cover = itemView.findViewById(R.id.imageViewCover);
title = itemView.findViewById(R.id.textViewTitle);
}
}
}

View File

@ -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<BrowseSectionAdapter.BrowseViewHolder> {
private final List<BrowseSection> sectionList;
public BrowseSectionAdapter(List<BrowseSection> sectionList) {
this.sectionList = sectionList;
}
@NonNull
@Override
public BrowseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_section, parent, false);
return new BrowseViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull BrowseViewHolder holder, int position) {
BrowseSection section = sectionList.get(position);
holder.sectionTitle.setText(section.getTitle());
HomeAdapter adapter = new HomeAdapter(section.getItems(), section.getTitle());
holder.recyclerView.setLayoutManager(new LinearLayoutManager(holder.itemView.getContext(), LinearLayoutManager.HORIZONTAL, false));
holder.recyclerView.setAdapter(adapter);
}
@Override
public int getItemCount() {
return sectionList.size();
}
static class BrowseViewHolder extends RecyclerView.ViewHolder {
TextView sectionTitle;
RecyclerView recyclerView;
public BrowseViewHolder(@NonNull View itemView) {
super(itemView);
sectionTitle = itemView.findViewById(R.id.sectionTitle);
recyclerView = itemView.findViewById(R.id.sectionRecyclerView);
}
}
}

View File

@ -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<MangaSearchAdapter.SearchViewHolder> {
private List<AniListAnime> mangaList;
private final OnMangaClickListener clickListener;
public interface OnMangaClickListener {
void onClick(AniListAnime manga);
}
public MangaSearchAdapter(List<AniListAnime> mangaList, OnMangaClickListener clickListener) {
this.mangaList = mangaList;
this.clickListener = clickListener;
}
public void setMangaList(List<AniListAnime> 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);
}
}
}

View File

@ -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<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);
statusSpinner.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);
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<List<AniListAnime>>() {
@Override
public void onResponse(Call<List<AniListAnime>> call, Response<List<AniListAnime>> 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<List<AniListAnime>> 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<String>() {
@Override
public void onResponse(Call<String> call, Response<String> 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<String> call, Throwable t) {
Toast.makeText(getContext(), "Fallo en la conexión", Toast.LENGTH_SHORT).show();
}
});
}
}

View File

@ -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<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);
statusSpinner.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);
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<List<AniListAnime>>() {
@Override
public void onResponse(Call<List<AniListAnime>> call, Response<List<AniListAnime>> 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<List<AniListAnime>> 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<String>() {
@Override
public void onResponse(Call<String> call, Response<String> 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<String> call, Throwable t) {
Toast.makeText(getContext(), "Fallo en la conexión", Toast.LENGTH_SHORT).show();
}
});
}
}

View File

@ -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<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;
}
}
}
}
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<String>() {
@Override
public void onResponse(Call<String> call, Response<String> 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<String> call, Throwable t) {
Toast.makeText(requireContext(), "Fallo en la conexión", Toast.LENGTH_SHORT).show();
}
});
}
}

View File

@ -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<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);
}
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<String>() {
@Override
public void onResponse(Call<String> call, Response<String> 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<String> call, Throwable t) {
Toast.makeText(getContext(), "Fallo de conexión", Toast.LENGTH_SHORT).show();
}
});
}
}

View File

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

View File

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

View File

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

View File

@ -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<String, String> request = new HashMap<>();
request.put("username", username);
request.put("password", password);
api.registerUser(request).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();
} else {
safeToast("Error al registrar usuario");
}
}
@Override
public void onFailure(Call<RegisterResponse> 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();
}
}
}

View File

@ -0,0 +1,18 @@
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:interpolator/decelerate_quad"
android:fillAfter="true">
<alpha
android:fromAlpha="0"
android:toAlpha="1"
android:duration="300"/>
<scale
android:fromXScale="0.9"
android:fromYScale="0.9"
android:toXScale="1.0"
android:toYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
android:duration="300"/>
</set>

View File

@ -0,0 +1,4 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#4CAF50" /> <!-- verde -->
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
</selector>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
</selector>

View File

@ -0,0 +1,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="8dp" />
<solid android:color="#CCCCCC" />
</shape>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:navGraph="@navigation/nav_graph"
app:defaultNavHost="true" />

View File

@ -0,0 +1,54 @@
<?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"
android:orientation="vertical"
android:paddingTop="100dp"
android:paddingBottom="80dp"
android:paddingHorizontal="16dp">
<EditText
android:id="@+id/editTextSearch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Buscar anime..."
android:inputType="text"
android:imeOptions="actionSearch" />
<EditText
android:id="@+id/editTextScore"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Puntuación (0-100)"
android:inputType="number"
android:layout_marginTop="12dp" />
<EditText
android:id="@+id/editTextProgress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Progreso (episodios vistos)"
android:inputType="number"
android:layout_marginTop="12dp" />
<Spinner
android:id="@+id/spinnerStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp" />
<Spinner
android:id="@+id/spinnerType"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerSearchResults"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:paddingTop="12dp"
android:clipToPadding="false"
android:scrollbars="vertical" />
</LinearLayout>

View File

@ -0,0 +1,53 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="100dp"
android:paddingBottom="80dp"
android:paddingHorizontal="16dp">
<EditText
android:id="@+id/editTextSearch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Buscar manga..."
android:inputType="text"
android:imeOptions="actionSearch" />
<EditText
android:id="@+id/editTextScore"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Puntuación (0-100)"
android:inputType="number"
android:layout_marginTop="12dp" />
<EditText
android:id="@+id/editTextProgress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Progreso (capítulos leídos)"
android:inputType="number"
android:layout_marginTop="12dp" />
<Spinner
android:id="@+id/spinnerStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp" />
<Spinner
android:id="@+id/spinnerType"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerSearchResults"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:paddingTop="12dp"
android:clipToPadding="false"
android:scrollbars="vertical" />
</LinearLayout>

View File

@ -0,0 +1,63 @@
<?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:paddingTop="100dp"
android:paddingBottom="80dp"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:orientation="vertical">
<EditText
android:id="@+id/editTextAnimeTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Título del anime"
android:inputType="text"
android:padding="12dp"
android:background="@drawable/edit_text_background" />
<EditText
android:id="@+id/editTextScore"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Puntuación (0-100)"
android:inputType="number"
android:layout_marginTop="12dp" />
<EditText
android:id="@+id/editTextProgress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Progreso (episodios vistos)"
android:inputType="number"
android:layout_marginTop="12dp" />
<Spinner
android:id="@+id/spinnerStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:padding="8dp" />
<Spinner
android:id="@+id/spinnerType"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:padding="8dp" />
<Button
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:textColor="@android:color/white" />
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,62 @@
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="100dp"
android:paddingBottom="80dp"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:orientation="vertical">
<EditText
android:id="@+id/editTextMangaTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Título del manga"
android:inputType="text"
android:padding="12dp"
android:background="@drawable/edit_text_background" />
<EditText
android:id="@+id/editTextScore"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Puntuación (0-100)"
android:inputType="number"
android:layout_marginTop="12dp" />
<EditText
android:id="@+id/editTextProgress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Progreso (capítulos leídos)"
android:inputType="number"
android:layout_marginTop="12dp" />
<Spinner
android:id="@+id/spinnerStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:padding="8dp" />
<Spinner
android:id="@+id/spinnerType"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:padding="8dp" />
<Button
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:textColor="@android:color/white" />
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,38 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp">
<EditText
android:id="@+id/editTextUsername"
android:hint="Usuario"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/editTextPassword"
android:hint="Contraseña"
android:inputType="textPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/buttonLogin"
android:text="Iniciar Sesión"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/buttonGuest"
android:text="Entrar como Invitado"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/buttonGoRegister"
android:text="Registrarse"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@ -0,0 +1,25 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp">
<EditText
android:id="@+id/editTextUsernameRegister"
android:hint="Nuevo usuario"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<EditText
android:id="@+id/editTextPasswordRegister"
android:hint="Contraseña"
android:inputType="textPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/buttonRegister"
android:text="Registrarse"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@ -0,0 +1,23 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/imageCover"
android:layout_width="60dp"
android:layout_height="60dp"
android:scaleType="centerCrop"
android:layout_marginEnd="12dp"
android:contentDescription="Imagen" />
<TextView
android:id="@+id/textTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Título"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>

View File

@ -0,0 +1,27 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_margin="8dp"
android:gravity="center_horizontal">
<ImageView
android:id="@+id/imageViewCover"
android:layout_width="120dp"
android:layout_height="170dp"
android:scaleType="centerCrop"
android:clipToOutline="true"
android:background="@drawable/sample_cover" />
<TextView
android:id="@+id/textViewTitle"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:text="Anime Title"
android:textSize="12sp"
android:gravity="center"
android:textColor="@color/textPrimary"
android:maxLines="2"
android:ellipsize="end"
android:layout_marginTop="4dp" />
</LinearLayout>

View File

@ -0,0 +1,7 @@
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/spinnerItemText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/textPrimary"
android:padding="8dp"
android:textSize="16sp" />

View File

@ -0,0 +1,8 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_view_type"
android:icon="@drawable/ic_view_normal"
android:title="Cambiar vista"
app:showAsAction="always" />
</menu>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/loginFragment">
<fragment
android:id="@+id/loginFragment"
android:name="com.santiparra.yomitrack.ui.fragments.login.LoginFragment"
android:label="Login" >
<action
android:id="@+id/action_login_to_register"
app:destination="@id/registerFragment" />
</fragment>
<fragment
android:id="@+id/registerFragment"
android:name="com.santiparra.yomitrack.ui.fragments.register.RegisterFragment"
android:label="Register" />
</navigation>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">10.0.2.2</domain>
</domain-config>
</network-security-config>