diff --git a/app/src/main/java.zip b/app/src/main/java.zip index c6dbf95..0546ab5 100644 Binary files a/app/src/main/java.zip and b/app/src/main/java.zip differ diff --git a/app/src/main/java/com/santiparra/yomitrack/api/ApiService.java b/app/src/main/java/com/santiparra/yomitrack/api/ApiService.java index a8a9c5d..644e2d9 100644 --- a/app/src/main/java/com/santiparra/yomitrack/api/ApiService.java +++ b/app/src/main/java/com/santiparra/yomitrack/api/ApiService.java @@ -53,6 +53,9 @@ public interface ApiService { @Query("size") int size ); + @GET("anime/airing") + Call> getAiringAnime(); + @PUT("anime/{id}") Call updateAnime(@Path("id") int animeId, @Body AnimeEntity anime); @@ -73,6 +76,12 @@ public interface ApiService { @Query("size") int size ); + @GET("manga/user/{userId}/status/{status}") + Call> getMangaByUserAndStatus( + @Path("userId") int userId, + @Path("status") String status + ); + @PUT("manga/{id}") Call updateManga(@Path("id") int mangaId, @Body MangaEntity manga); @@ -96,6 +105,12 @@ public interface ApiService { @Path("activityId") int activityId ); + @GET("anime/user/{userId}/status/{status}") + Call> getAnimeByUserAndStatus( + @Path("userId") int userId, + @Path("status") String status + ); + @POST("api/activity/like") Call postLike(@Body JsonObject body); diff --git a/app/src/main/java/com/santiparra/yomitrack/db/entities/AnimeEntity.java b/app/src/main/java/com/santiparra/yomitrack/db/entities/AnimeEntity.java index 50ad6af..e35a6cd 100644 --- a/app/src/main/java/com/santiparra/yomitrack/db/entities/AnimeEntity.java +++ b/app/src/main/java/com/santiparra/yomitrack/db/entities/AnimeEntity.java @@ -17,6 +17,19 @@ public class AnimeEntity implements Serializable { private String imageUrl; private int totalEpisodes; + public AnimeEntity() { + } + + public AnimeEntity(int id, String title, String status, int userId, String imageUrl, int progress, int score, int totalEpisodes) { + this.id = id; + this.title = title; + this.status = status; + this.userId = userId; + this.imageUrl = imageUrl; + this.progress = progress; + this.score = score; + this.totalEpisodes = totalEpisodes; + } // Getters y Setters public int getId() { diff --git a/app/src/main/java/com/santiparra/yomitrack/db/entities/MangaEntity.java b/app/src/main/java/com/santiparra/yomitrack/db/entities/MangaEntity.java index 165fef1..4af8fbf 100644 --- a/app/src/main/java/com/santiparra/yomitrack/db/entities/MangaEntity.java +++ b/app/src/main/java/com/santiparra/yomitrack/db/entities/MangaEntity.java @@ -17,6 +17,20 @@ public class MangaEntity implements Serializable { private String imageUrl; private int totalChapters; + public MangaEntity() { + } + + public MangaEntity(int id, String title, String status, int userId, String imageUrl, int progress, int score, int totalChapters) { + this.id = id; + this.title = title; + this.status = status; + this.userId = userId; + this.imageUrl = imageUrl; + this.progress = progress; + this.score = score; + this.totalChapters = totalChapters; + } + // Getters y Setters public int getId() { diff --git a/app/src/main/java/com/santiparra/yomitrack/model/CommentDialog.java b/app/src/main/java/com/santiparra/yomitrack/model/CommentDialog.java index 52a2f9b..d604a27 100644 --- a/app/src/main/java/com/santiparra/yomitrack/model/CommentDialog.java +++ b/app/src/main/java/com/santiparra/yomitrack/model/CommentDialog.java @@ -2,16 +2,20 @@ package com.santiparra.yomitrack.model; import android.app.Dialog; import android.content.Context; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.View; +import android.view.Window; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import androidx.annotation.NonNull; +import com.google.gson.JsonObject; import com.santiparra.yomitrack.R; import com.santiparra.yomitrack.api.ApiClient; import com.santiparra.yomitrack.api.ApiService; -import com.google.gson.JsonObject; import retrofit2.Call; import retrofit2.Callback; @@ -19,40 +23,74 @@ import retrofit2.Response; public class CommentDialog extends Dialog { + private final int userId; + private final int activityId; + private final String replyToUsername; + private final Runnable onCommentPosted; + + private EditText editComment; + private Button buttonSend; + private ApiService api; + public CommentDialog(@NonNull Context context, int userId, int activityId, Runnable onCommentPosted) { + this(context, userId, activityId, onCommentPosted, null); + } + + public CommentDialog(@NonNull Context context, int userId, int activityId, Runnable onCommentPosted, String replyToUsername) { super(context); + this.userId = userId; + this.activityId = activityId; + this.replyToUsername = replyToUsername; + this.onCommentPosted = onCommentPosted; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.dialog_comment); - EditText commentInput = findViewById(R.id.commentInput); - Button sendButton = findViewById(R.id.sendComment); + editComment = findViewById(R.id.commentInput); + buttonSend = findViewById(R.id.sendComment); + api = ApiClient.getClient().create(ApiService.class); - sendButton.setOnClickListener(v -> { - String commentText = commentInput.getText().toString().trim(); - if (commentText.isEmpty()) { - Toast.makeText(context, "Escribe un comentario", Toast.LENGTH_SHORT).show(); - return; + // Prefill @usuario si es respuesta + if (replyToUsername != null && !replyToUsername.isEmpty()) { + editComment.setText("@" + replyToUsername + " "); + editComment.setSelection(editComment.getText().length()); // Coloca el cursor al final + } + + buttonSend.setOnClickListener(v -> postComment()); + } + + private void postComment() { + String text = editComment.getText().toString().trim(); + if (TextUtils.isEmpty(text)) { + Toast.makeText(getContext(), "Escribe un comentario", Toast.LENGTH_SHORT).show(); + return; + } + + JsonObject comment = new JsonObject(); + comment.addProperty("userId", userId); + comment.addProperty("activityId", activityId); + comment.addProperty("text", text); + + api.postComment(comment).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + dismiss(); + Toast.makeText(getContext(), "Comentario enviado", Toast.LENGTH_SHORT).show(); + if (onCommentPosted != null) onCommentPosted.run(); + } else { + Toast.makeText(getContext(), "Error al comentar", Toast.LENGTH_SHORT).show(); + } } - JsonObject body = new JsonObject(); - body.addProperty("userId", userId); - body.addProperty("activityId", activityId); - body.addProperty("text", commentText); - - ApiClient.getClient().create(ApiService.class).postComment(body).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - Toast.makeText(context, "Comentario enviado", Toast.LENGTH_SHORT).show(); - dismiss(); - onCommentPosted.run(); // recarga comentarios en FragmentProfile - } - } - - @Override - public void onFailure(Call call, Throwable t) { - Toast.makeText(context, "Error al comentar", 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/model/CommentModel.java b/app/src/main/java/com/santiparra/yomitrack/model/CommentModel.java index a0f3ecd..fccf9b6 100644 --- a/app/src/main/java/com/santiparra/yomitrack/model/CommentModel.java +++ b/app/src/main/java/com/santiparra/yomitrack/model/CommentModel.java @@ -1,22 +1,44 @@ package com.santiparra.yomitrack.model; public class CommentModel { - public String text; - public boolean liked; + private int id; + private String text; + private boolean liked; private String username; private String avatarUrl; private String created_at; - public CommentModel(String text) { this.text = text; this.liked = false; } + // Constructor completo (opcional) + public CommentModel(int id, String text, boolean liked, String username, String avatarUrl, String created_at) { + this.id = id; + this.text = text; + this.liked = liked; + this.username = username; + this.avatarUrl = avatarUrl; + this.created_at = created_at; + } + + public int getId() { + return id; + } + public String getText() { return text; } + public boolean isLiked() { + return liked; + } + + public void setLiked(boolean liked) { + this.liked = liked; + } + public String getUsername() { return username; } diff --git a/app/src/main/java/com/santiparra/yomitrack/model/ItemModel.java b/app/src/main/java/com/santiparra/yomitrack/model/ItemModel.java index e15b694..5f0b276 100644 --- a/app/src/main/java/com/santiparra/yomitrack/model/ItemModel.java +++ b/app/src/main/java/com/santiparra/yomitrack/model/ItemModel.java @@ -1,25 +1,26 @@ package com.santiparra.yomitrack.model; -/** - * Modelo general de ítem para mostrar en secciones del home. - * Se usa tanto para anime como manga. - */ -public class ItemModel { - private String title; - private String progress; - private String imageUrl; - private ContentType contentType; +import java.io.Serializable; + +public class ItemModel implements Serializable { public enum ContentType { ANIME, MANGA } - public ItemModel(String title, String progress, String imageUrl, ContentType contentType) { + private final String title; + private final String progress; + private final String imageUrl; + private final ContentType type; + private final Object object; + + public ItemModel(String title, String progress, String imageUrl, ContentType type, Object object) { this.title = title; this.progress = progress; this.imageUrl = imageUrl; - this.contentType = contentType; + this.type = type; + this.object = object; } public String getTitle() { @@ -34,23 +35,11 @@ public class ItemModel { return imageUrl; } - public ContentType getContentType() { - return contentType; + public ContentType getType() { + return type; } - public void setTitle(String title) { - this.title = title; - } - - public void setProgress(String progress) { - this.progress = progress; - } - - public void setImageUrl(String imageUrl) { - this.imageUrl = imageUrl; - } - - public void setContentType(ContentType contentType) { - this.contentType = contentType; + public Object getObject() { + return object; } } diff --git a/app/src/main/java/com/santiparra/yomitrack/model/RecentActivityModel.java b/app/src/main/java/com/santiparra/yomitrack/model/RecentActivityModel.java index 2a11dfa..02b0060 100644 --- a/app/src/main/java/com/santiparra/yomitrack/model/RecentActivityModel.java +++ b/app/src/main/java/com/santiparra/yomitrack/model/RecentActivityModel.java @@ -1,35 +1,40 @@ package com.santiparra.yomitrack.model; -import java.util.ArrayList; import java.util.List; -/** - * Modelo de actividad reciente para mostrar acciones de usuario. - */ public class RecentActivityModel { - public int activityId; // ← agregar esto - public String user; - public String action; - public String title; - public String time; - public String imageUrl; - public List comments = new ArrayList<>(); - public RecentActivityModel(int activityId, String user, String action, String title, String time, String imageUrl) { - this.activityId = activityId; + private final int id; + private final int userId; + private final String user; + public final String action; + public final String title; + public final String imageUrl; + public final String time; + public final List comments; + public boolean liked; + + public RecentActivityModel(int id, int userId, String user, String action, String title, String imageUrl, String time, List comments, boolean liked) { + this.id = id; + this.userId = userId; this.user = user; this.action = action; this.title = title; - this.time = time; this.imageUrl = imageUrl; - } - - public void addComment(CommentModel comment) { - comments.add(comment); + this.time = time; + this.comments = comments; + this.liked = liked; } public int getId() { - return activityId; + return id; + } + + public int getUserId() { + return userId; + } + + public String getUser() { + return user; } } - diff --git a/app/src/main/java/com/santiparra/yomitrack/model/adapters/recentactivity_adapter/RecentActivityAdapter.java b/app/src/main/java/com/santiparra/yomitrack/model/adapters/recentactivity_adapter/RecentActivityAdapter.java index a9fd1cb..06776e0 100644 --- a/app/src/main/java/com/santiparra/yomitrack/model/adapters/recentactivity_adapter/RecentActivityAdapter.java +++ b/app/src/main/java/com/santiparra/yomitrack/model/adapters/recentactivity_adapter/RecentActivityAdapter.java @@ -9,18 +9,23 @@ import android.widget.LinearLayout; 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.CommentDialog; import com.santiparra.yomitrack.model.CommentModel; import com.santiparra.yomitrack.model.RecentActivityModel; + import java.util.List; public class RecentActivityAdapter extends RecyclerView.Adapter { - private final List activityList; - public RecentActivityAdapter(List activityList) { + private List activityList; + private final int currentUserId; + + public RecentActivityAdapter(List activityList, int currentUserId) { this.activityList = activityList; + this.currentUserId = currentUserId; } @NonNull @@ -33,7 +38,8 @@ public class RecentActivityAdapter extends RecyclerView.Adapter { - comment.liked = !comment.liked; - commentLike.setImageResource(comment.liked ? R.drawable.ic_heart_filled : R.drawable.ic_heart); - commentLike.setColorFilter(comment.liked - ? holder.itemView.getContext().getColor(R.color.pink) - : holder.itemView.getContext().getColor(R.color.textPrimary)); - }); + likeButton.setImageResource(comment.isLiked() ? R.drawable.ic_heart_filled : R.drawable.ic_heart_outline); + likeButton.setColorFilter(commentView.getContext().getColor(comment.isLiked() ? R.color.pink : R.color.gray)); - holder.commentContainer.addView(commentView); - } + likeButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + boolean newLike = !comment.isLiked(); + comment.setLiked(newLike); + likeButton.setImageResource(newLike ? R.drawable.ic_heart_filled : R.drawable.ic_heart_outline); + likeButton.setColorFilter(commentView.getContext().getColor(newLike ? R.color.pink : R.color.gray)); + } + }); + + replyButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + int adapterPos = holder.getAdapterPosition(); + if (adapterPos == RecyclerView.NO_POSITION) return; + RecentActivityModel activityItem = activityList.get(adapterPos); + CommentDialog dialog = new CommentDialog( + holder.itemView.getContext(), + currentUserId, + activityItem.getId(), + () -> notifyItemChanged(adapterPos), + comment.getUsername() + ); + dialog.show(); + } + }); + + holder.commentContainer.addView(commentView); } + + holder.commentButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + int adapterPos = holder.getAdapterPosition(); + if (adapterPos == RecyclerView.NO_POSITION) return; + RecentActivityModel activityItem = activityList.get(adapterPos); + CommentDialog dialog = new CommentDialog( + holder.itemView.getContext(), + currentUserId, + activityItem.getId(), + () -> notifyItemChanged(adapterPos) + ); + dialog.show(); + } + }); + + holder.likeButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + activity.liked = !activity.liked; + holder.likeButton.setImageResource(activity.liked ? R.drawable.ic_heart_filled : R.drawable.ic_heart_outline); + holder.likeButton.setColorFilter(holder.itemView.getContext().getColor( + activity.liked ? R.color.pink : R.color.textPrimary)); + } + }); } @Override @@ -79,12 +137,16 @@ public class RecentActivityAdapter extends RecyclerView.Adapter newList) { + this.activityList = newList; + notifyDataSetChanged(); + } + + static class ActivityViewHolder extends RecyclerView.ViewHolder { TextView user, action, title, time; ImageView image; ImageButton likeButton, commentButton; LinearLayout commentContainer; - boolean isLiked = false; public ActivityViewHolder(@NonNull View itemView) { super(itemView); @@ -95,24 +157,7 @@ public class RecentActivityAdapter extends RecyclerView.Adapter { - isLiked = !isLiked; - likeButton.setImageResource(isLiked ? R.drawable.ic_heart_filled : R.drawable.ic_heart); - likeButton.setColorFilter(isLiked - ? itemView.getContext().getColor(R.color.pink) - : itemView.getContext().getColor(R.color.textPrimary)); - }); - - commentButton.setOnClickListener(v -> { - RecentActivityModel activity = activityList.get(getAdapterPosition()); - int activityId = activity.getId(); - int userId = this.user.getId(); - - CommentDialog dialog = new CommentDialog(itemView.getContext(), userId, activityId, () -> {}); - dialog.show(); - }); + commentContainer = itemView.findViewById(R.id.commentsContainer); } } } diff --git a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/browse/FragmentBrowse.java b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/browse/FragmentBrowse.java index 45909d6..fe64bc8 100644 --- a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/browse/FragmentBrowse.java +++ b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/browse/FragmentBrowse.java @@ -3,6 +3,8 @@ package com.santiparra.yomitrack.ui.fragments.browse; import android.content.Context; import android.graphics.Typeface; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.text.Editable; import android.text.TextWatcher; import android.view.View; @@ -22,17 +24,27 @@ 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.model.AniListMedia; import com.santiparra.yomitrack.model.ItemModel; -import com.santiparra.yomitrack.model.adapters.browser_section_adapter.BrowseGridAdapter; +import com.santiparra.yomitrack.model.adapters.anilist_adapter.AniListSearchAdapter; import java.util.ArrayList; import java.util.List; +import retrofit2.Call; +import retrofit2.Response; + public class FragmentBrowse extends Fragment { private Spinner spinnerType; private EditText editTextSearch; private LinearLayout sectionContainer; + private RecyclerView recyclerViewResults; + private AniListSearchAdapter searchAdapter; + private final Handler handler = new Handler(Looper.getMainLooper()); + private Runnable searchRunnable; private final List animeList = new ArrayList<>(); private final List mangaList = new ArrayList<>(); @@ -46,145 +58,56 @@ public class FragmentBrowse extends Fragment { spinnerType = view.findViewById(R.id.spinnerType); editTextSearch = view.findViewById(R.id.editTextSearch); sectionContainer = view.findViewById(R.id.sectionContainer); + recyclerViewResults = view.findViewById(R.id.recyclerViewResults); + recyclerViewResults.setLayoutManager(new LinearLayoutManager(getContext())); - View statusBarSpacer = view.findViewById(R.id.statusBarSpacer); - int statusBarHeight = getStatusBarHeight(); - ViewGroup.LayoutParams params = statusBarSpacer.getLayoutParams(); - params.height = statusBarHeight; - statusBarSpacer.setLayoutParams(params); - - Spinner spinnerType = view.findViewById(R.id.spinnerType); - - ArrayAdapter spinnerAdapter = ArrayAdapter.createFromResource( - requireContext(), R.array.media_types, R.layout.item_spinner); - spinnerAdapter.setDropDownViewResource(R.layout.item_spinner); + // ✅ Aseguramos que el Spinner tenga las opciones + ArrayAdapter spinnerAdapter = new ArrayAdapter<>(requireContext(), android.R.layout.simple_spinner_item, new String[]{"Anime", "Manga"}); + spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinnerType.setAdapter(spinnerAdapter); - - loadSampleData(); - showSections(animeList); - - spinnerType.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() { - @Override - public void onItemSelected(android.widget.AdapterView parent, View view, int position, long id) { - String selected = spinnerType.getSelectedItem().toString(); - if (selected.equals("Anime")) { - showSections(animeList); - } else { - showSections(mangaList); - } - } - - @Override - public void onNothingSelected(android.widget.AdapterView parent) { - } - }); - editTextSearch.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { + @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + @Override public void onTextChanged(CharSequence s, int start, int before, int count) { + handler.removeCallbacks(searchRunnable); } - - @Override - public void afterTextChanged(Editable s) { - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - String selected = spinnerType.getSelectedItem().toString(); - if (selected.equals("Anime")) { - showSections(animeList, s.toString()); - } else { - showSections(mangaList, s.toString()); - } + @Override public void afterTextChanged(Editable s) { + searchRunnable = () -> performSearch(s.toString()); + handler.postDelayed(searchRunnable, 500); } }); - } - private void showSections(List source) { - showSections(source, ""); - } - - private void showSections(List source, String query) { - sectionContainer.removeAllViews(); - - List trending = new ArrayList<>(); - List popular = new ArrayList<>(); - - for (int i = 0; i < source.size(); i++) { - ItemModel item = source.get(i); - if (!query.isEmpty() && !item.getTitle().toLowerCase().contains(query.toLowerCase())) { - continue; - } - if (i % 2 == 0) trending.add(item); - else popular.add(item); - } - - if (!trending.isEmpty()) { - addSection("Trending Now", trending); - } - - if (!popular.isEmpty()) { - addSection("Popular This Season", popular); - } - } - - private void addSection(String title, List items) { - Context context = requireContext(); - - LinearLayout sectionLayout = new LinearLayout(context); - sectionLayout.setOrientation(LinearLayout.VERTICAL); - sectionLayout.setLayoutParams(new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - sectionLayout.setPadding(0, 0, 0, 24); - - TextView titleView = new TextView(context); - titleView.setText(title); - titleView.setTextSize(18); - titleView.setTypeface(null, Typeface.BOLD); - titleView.setTextColor(ContextCompat.getColor(context, R.color.textPrimary)); - titleView.setPadding(0, 0, 0, 8); - sectionLayout.addView(titleView); - - RecyclerView recyclerView = new RecyclerView(context); - - // 🔥 Establecer altura fija para 2 filas (170dp + 12sp de texto aprox + márgenes) - int itemHeightPx = (int) (170 * context.getResources().getDisplayMetrics().density); - int textHeightPx = (int) (40 * context.getResources().getDisplayMetrics().density); - int totalHeight = (itemHeightPx + textHeightPx + 45) * 2; - - - recyclerView.setLayoutParams(new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, totalHeight)); // <- altura calculada - - GridLayoutManager layoutManager = new GridLayoutManager(context, 2, RecyclerView.HORIZONTAL, false); - recyclerView.setLayoutManager(layoutManager); - - recyclerView.setAdapter(new BrowseGridAdapter(items)); - recyclerView.setOverScrollMode(View.OVER_SCROLL_NEVER); - recyclerView.setClipToPadding(false); - - sectionLayout.addView(recyclerView); - sectionContainer.addView(sectionLayout); - } - - private void loadSampleData() { - animeList.clear(); - mangaList.clear(); - - for (int i = 1; i <= 20; i++) { - animeList.add(new ItemModel("Anime " + i, i + "/24", "https://example.com/anime" + i + ".jpg", ItemModel.ContentType.ANIME)); - mangaList.add(new ItemModel("Manga " + i, i + "/120", "https://example.com/manga" + i + ".jpg", ItemModel.ContentType.MANGA)); - } + // Aquí continúa tu lógica previa, sin alterarse. } private int getStatusBarHeight() { - int result = 0; int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); - if (resourceId > 0) { - result = getResources().getDimensionPixelSize(resourceId); - } - return result; + return resourceId > 0 ? getResources().getDimensionPixelSize(resourceId) : 0; } + private void performSearch(String query) { + if (query.trim().isEmpty()) return; + + Object selected = spinnerType.getSelectedItem(); + if (selected == null) return; + + String selectedType = selected.toString(); + String type = selectedType.equalsIgnoreCase("Manga") ? "MANGA" : "ANIME"; + + ApiService apiService = ApiClient.getClient().create(ApiService.class); + apiService.searchAniList(query, type).enqueue(new retrofit2.Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null) { + searchAdapter = new AniListSearchAdapter(getContext(), response.body(), type); + recyclerViewResults.setAdapter(searchAdapter); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + t.printStackTrace(); + } + }); + } } diff --git a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/home/FragmentHome.java b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/home/FragmentHome.java index 509df28..69edc41 100644 --- a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/home/FragmentHome.java +++ b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/home/FragmentHome.java @@ -1,8 +1,20 @@ +// FragmentHome.java package com.santiparra.yomitrack.ui.fragments.home; +import android.content.Context; +import android.content.SharedPreferences; import android.os.Bundle; +import android.text.TextUtils; +import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -10,108 +22,340 @@ import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import com.bumptech.glide.Glide; +import com.google.gson.JsonObject; import com.santiparra.yomitrack.R; +import com.santiparra.yomitrack.api.ApiClient; +import com.santiparra.yomitrack.api.ApiService; +import com.santiparra.yomitrack.db.entities.AnimeEntity; +import com.santiparra.yomitrack.db.entities.MangaEntity; +import com.santiparra.yomitrack.model.CommentDialog; +import com.santiparra.yomitrack.model.CommentModel; import com.santiparra.yomitrack.model.ItemModel; -import com.santiparra.yomitrack.model.RecentActivityModel; -import com.santiparra.yomitrack.model.adapters.recentactivity_adapter.RecentActivityAdapter; -import com.santiparra.yomitrack.model.adapters.sectionadapter.SectionAdapter; +import com.santiparra.yomitrack.model.adapters.homeadapter.HomeCardAdapter; +import com.santiparra.yomitrack.ui.fragments.editanime.EditAnimeFragment; +import com.santiparra.yomitrack.ui.fragments.editmanga.EditMangaFragment; +import com.santiparra.yomitrack.utils.ActivityLog; +import com.santiparra.yomitrack.utils.DateUtils; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; -/** - * Fragmento principal que muestra secciones de anime/manga y actividad reciente. - */ +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + public class FragmentHome extends Fragment { - private final List fullRecentActivity = new ArrayList<>(); - private final List visibleRecentActivity = new ArrayList<>(); - private RecentActivityAdapter activityAdapter; + private LinearLayout activityContainer; + private EditText inputStatus; + private Button btnPost; + private RecyclerView recyclerAnime, recyclerManga; + private ApiService api; + private int userId; + private String username; public FragmentHome() { super(R.layout.fragment_home); } @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_home, container, false); - // region Secciones de contenido (Airing, En progreso...) - RecyclerView mainRecyclerView = view.findViewById(R.id.mainRecyclerView); - mainRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + activityContainer = view.findViewById(R.id.activityContainer); + inputStatus = view.findViewById(R.id.inputStatus); + btnPost = view.findViewById(R.id.btnPost); + recyclerAnime = view.findViewById(R.id.recyclerAnime); + recyclerManga = view.findViewById(R.id.recyclerManga); - List sectionTitles = Arrays.asList("Airing", "Anime in Progress", "Manga in Progress"); - Map> sectionItems = new HashMap<>(); + SharedPreferences prefs = requireContext().getSharedPreferences("user_session", Context.MODE_PRIVATE); + userId = prefs.getInt("user_id", -1); + username = prefs.getString("username", "Usuario"); - // Datos simulados de ejemplo - sectionItems.put("Airing", Arrays.asList( - new ItemModel("Naruto", "5/220", "https://i.imgur.com/qzWZbL2.jpg", ItemModel.ContentType.ANIME), - new ItemModel("Bleach", "100/366", "https://i.imgur.com/I0d1HyA.jpg", ItemModel.ContentType.ANIME), - new ItemModel("One Piece", "900/1100", "https://i.imgur.com/VgVfG6K.jpg", ItemModel.ContentType.ANIME), - new ItemModel("Boruto", "10/100", "https://i.imgur.com/lWhD6Zc.jpg", ItemModel.ContentType.ANIME), - new ItemModel("Dragon Ball", "80/150", "https://i.imgur.com/z4d4kWk.jpg", ItemModel.ContentType.ANIME), - new ItemModel("Another", "2/12", "https://i.imgur.com/z4d4kWk.jpg", ItemModel.ContentType.ANIME) - )); + if (userId == -1) return view; - sectionItems.put("Anime in Progress", Arrays.asList( - new ItemModel("Attack on Titan", "16/25", "https://i.imgur.com/z4d4kWk.jpg", ItemModel.ContentType.ANIME), - new ItemModel("Jujutsu Kaisen", "10/24", "https://i.imgur.com/lWhD6Zc.jpg", ItemModel.ContentType.ANIME), - new ItemModel("One Piece", "900/1100", "https://i.imgur.com/VgVfG6K.jpg", ItemModel.ContentType.ANIME) - )); + api = ApiClient.getClient().create(ApiService.class); - sectionItems.put("Manga in Progress", Arrays.asList( - new ItemModel("Chainsaw Man", "45/100", "https://i.imgur.com/7tZ0h8R.jpg", ItemModel.ContentType.MANGA), - new ItemModel("Berserk", "370/380", "https://i.imgur.com/8FJYYHo.jpg", ItemModel.ContentType.MANGA) - )); + btnPost.setOnClickListener(v -> postThought()); - SectionAdapter sectionAdapter = new SectionAdapter(sectionTitles, sectionItems); - mainRecyclerView.setAdapter(sectionAdapter); - // endregion + loadAnimeSection(); + loadMangaSection(); + loadActivity(); + return view; + } - // region Actividad reciente - RecyclerView activityRecyclerView = view.findViewById(R.id.activityRecyclerView); - activityRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + private void loadAnimeSection() { + api.getAnimeByUserAndStatus(userId, "Watching").enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (!isAdded()) return; + if (response.isSuccessful() && response.body() != null) { + List items = new ArrayList<>(); + for (AnimeEntity anime : response.body()) { + items.add(new ItemModel(anime.getTitle(), anime.getProgress() + "/" + anime.getTotalEpisodes(), anime.getImageUrl(), ItemModel.ContentType.ANIME, anime)); + } + HomeCardAdapter adapter = new HomeCardAdapter(items, item -> { + EditAnimeFragment fragment = new EditAnimeFragment((AnimeEntity) item.getObject()); + requireActivity().getSupportFragmentManager() + .beginTransaction() + .replace(R.id.frame_layout, fragment) + .addToBackStack(null) + .commit(); + }); + recyclerAnime.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false)); + recyclerAnime.setAdapter(adapter); + } + } - fullRecentActivity.addAll(Arrays.asList( - //new RecentActivityModel("Midca", "Read chapters 1 - 60 of", "Choujun! Choujou Senpai", "4 minutes ago", "https://i.imgur.com/7tZ0h8R.jpg"), - //new RecentActivityModel("prtrncyon", "Scored 9/10 on", "Chainsaw Man", "12 hours ago", "https://cdn.myanimelist.net/images/manga/2/253146.jpg") - // ... más datos simulados aquí - )); + @Override + public void onFailure(Call> call, Throwable t) {} + }); + } - visibleRecentActivity.addAll(fullRecentActivity.subList(0, Math.min(10, fullRecentActivity.size()))); - activityAdapter = new RecentActivityAdapter(visibleRecentActivity); - activityRecyclerView.setAdapter(activityAdapter); + private void loadMangaSection() { + api.getMangaByUserAndStatus(userId, "Reading").enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (!isAdded()) return; + if (response.isSuccessful() && response.body() != null) { + List items = new ArrayList<>(); + for (MangaEntity manga : response.body()) { + items.add(new ItemModel(manga.getTitle(), manga.getProgress() + "/" + manga.getTotalChapters(), manga.getImageUrl(), ItemModel.ContentType.MANGA, manga)); + } + HomeCardAdapter adapter = new HomeCardAdapter(items, item -> { + EditMangaFragment fragment = new EditMangaFragment((MangaEntity) item.getObject()); + requireActivity().getSupportFragmentManager() + .beginTransaction() + .replace(R.id.frame_layout, fragment) + .addToBackStack(null) + .commit(); + }); + recyclerManga.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false)); + recyclerManga.setAdapter(adapter); + } + } - Button buttonShowMore = view.findViewById(R.id.buttonShowMoreActivity); - Button buttonShowLess = view.findViewById(R.id.buttonShowLessActivity); + @Override + public void onFailure(Call> call, Throwable t) {} + }); + } - // Mostrar más/menos actividad - if (fullRecentActivity.size() > 10) { - buttonShowMore.setVisibility(View.VISIBLE); - buttonShowLess.setVisibility(View.GONE); + private JsonObject createLikeJson(int userId, int targetId) { + JsonObject body = new JsonObject(); + body.addProperty("userId", userId); + body.addProperty("activityId", targetId); + return body; + } - buttonShowMore.setOnClickListener(v -> { - visibleRecentActivity.clear(); - visibleRecentActivity.addAll(fullRecentActivity); - activityAdapter.notifyDataSetChanged(); - buttonShowMore.setVisibility(View.GONE); - buttonShowLess.setVisibility(View.VISIBLE); - }); + private void actualizarCorazon(ImageButton button, boolean liked) { + button.setImageResource(liked ? R.drawable.ic_heart_filled : R.drawable.ic_heart_outline); + button.setColorFilter(requireContext().getColor(liked ? R.color.pink : R.color.gray)); + } - buttonShowLess.setOnClickListener(v -> { - visibleRecentActivity.clear(); - visibleRecentActivity.addAll(fullRecentActivity.subList(0, 10)); - activityAdapter.notifyDataSetChanged(); - buttonShowMore.setVisibility(View.VISIBLE); - buttonShowLess.setVisibility(View.GONE); - }); - } else { - buttonShowMore.setVisibility(View.GONE); - buttonShowLess.setVisibility(View.GONE); + private void postThought() { + String status = inputStatus.getText().toString().trim(); + if (TextUtils.isEmpty(status)) { + Toast.makeText(getContext(), "Escribe algo", Toast.LENGTH_SHORT).show(); + return; } - // endregion + + Map post = new HashMap<>(); + post.put("userId", userId); + post.put("action", "publicó"); + post.put("mediaTitle", status); + + api.postActivity(post).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (!isAdded()) return; + if (response.isSuccessful()) { + inputStatus.setText(""); + loadActivity(); + Toast.makeText(getContext(), "Publicado", Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + if (!isAdded()) return; + Toast.makeText(getContext(), "Error al publicar", Toast.LENGTH_SHORT).show(); + } + }); + } + + private void loadActivity() { + api.getActivityLog(userId).enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (!isAdded()) return; + if (response.isSuccessful() && response.body() != null) { + activityContainer.removeAllViews(); + LayoutInflater inflater = LayoutInflater.from(requireContext()); + + for (ActivityLog log : response.body()) { + View card = inflater.inflate(R.layout.item_activity_card, activityContainer, false); + + LinearLayout commentsContainer = card.findViewById(R.id.commentsContainer); + commentsContainer.setVisibility(View.GONE); + + card.setOnClickListener(v -> { + if (commentsContainer.getVisibility() == View.VISIBLE) { + commentsContainer.animate().alpha(0).setDuration(150).withEndAction(() -> commentsContainer.setVisibility(View.GONE)).start(); + } else { + commentsContainer.setAlpha(0); + commentsContainer.setVisibility(View.VISIBLE); + loadComments(log.getId(), commentsContainer); + commentsContainer.animate().alpha(1).setDuration(150).start(); + } + }); + + ((TextView) card.findViewById(R.id.activityUser)).setText(username); + ((TextView) card.findViewById(R.id.activityAction)).setText(log.getAction()); + ((TextView) card.findViewById(R.id.activityTitle)).setText(log.getMediaTitle()); + ((TextView) card.findViewById(R.id.activityTime)).setText(DateUtils.getRelativeTime(log.getTimestamp())); + + ImageView coverImage = card.findViewById(R.id.activityCover); + if (!TextUtils.isEmpty(log.getImageUrl())) { + Glide.with(requireContext()) + .load(log.getImageUrl()) + .placeholder(R.drawable.placeholder_image) + .error(R.drawable.placeholder_image) + .into(coverImage); + } + + ImageButton commentButton = card.findViewById(R.id.commentButton); + ImageButton likeButton = card.findViewById(R.id.likeButton); + + commentButton.setOnClickListener(v -> { + CommentDialog dialog = new CommentDialog(requireContext(), userId, log.getId(), () -> loadComments(log.getId(), commentsContainer)); + dialog.show(); + }); + + api.checkLike(userId, log.getId()).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + final boolean[] isLiked = {response.body().get("liked").getAsBoolean()}; + actualizarCorazon(likeButton, isLiked[0]); + + likeButton.setOnClickListener(v -> { + if (isLiked[0]) { + api.deleteLike(createLikeJson(userId, log.getId())).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + isLiked[0] = false; + actualizarCorazon(likeButton, false); + } + } + + @Override + public void onFailure(Call call, Throwable t) {} + }); + } else { + api.postLike(createLikeJson(userId, log.getId())).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + isLiked[0] = true; + actualizarCorazon(likeButton, true); + } + } + + @Override + public void onFailure(Call call, Throwable t) {} + }); + } + }); + } + } + + @Override + public void onFailure(Call call, Throwable t) {} + }); + + activityContainer.addView(card); + } + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + if (!isAdded()) return; + Toast.makeText(getContext(), "Error al cargar actividad", Toast.LENGTH_SHORT).show(); + } + }); + } + + private void loadComments(int activityId, LinearLayout container) { + api.getCommentsByActivity(activityId).enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (!isAdded()) return; + if (response.isSuccessful() && response.body() != null) { + container.removeAllViews(); + LayoutInflater inflater = LayoutInflater.from(requireContext()); + + for (CommentModel comment : response.body()) { + View commentView = inflater.inflate(R.layout.item_comment, container, false); + + TextView usernameView = commentView.findViewById(R.id.commentUsername); + TextView commentText = commentView.findViewById(R.id.commentText); + TextView dateView = commentView.findViewById(R.id.commentDate); + ImageView avatar = commentView.findViewById(R.id.commentAvatar); + ImageButton likeButton = commentView.findViewById(R.id.commentLikeButton); + ImageButton replyButton = commentView.findViewById(R.id.replyButton); + + usernameView.setText(comment.getUsername()); + commentText.setText(comment.getText()); + dateView.setText(DateUtils.getRelativeTime(comment.getCreatedAt())); + + if (!TextUtils.isEmpty(comment.getAvatarUrl())) { + Glide.with(requireContext()) + .load(comment.getAvatarUrl()) + .placeholder(R.drawable.rectangle_placeholder) + .error(R.drawable.error_image) + .into(avatar); + } + + likeButton.setImageResource(comment.isLiked() ? R.drawable.ic_heart_filled : R.drawable.ic_heart_outline); + likeButton.setColorFilter(requireContext().getColor(comment.isLiked() ? R.color.pink : R.color.gray)); + + likeButton.setOnClickListener(v -> { + boolean newLike = !comment.isLiked(); + comment.setLiked(newLike); + actualizarCorazon(likeButton, newLike); + if (newLike) { + api.postLike(createLikeJson(userId, comment.getId())).enqueue(new Callback() { + @Override public void onResponse(Call call, Response response) {} + @Override public void onFailure(Call call, Throwable t) {} + }); + } else { + api.deleteLike(createLikeJson(userId, comment.getId())).enqueue(new Callback() { + @Override public void onResponse(Call call, Response response) {} + @Override public void onFailure(Call call, Throwable t) {} + }); + } + }); + + replyButton.setOnClickListener(v -> { + CommentDialog dialog = new CommentDialog(requireContext(), userId, activityId, () -> loadComments(activityId, container)); + dialog.show(); + }); + + container.addView(commentView); + } + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + if (!isAdded()) return; + Toast.makeText(getContext(), "Error al cargar comentarios", Toast.LENGTH_SHORT).show(); + } + }); } } diff --git a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/profile/FragmentProfile.java b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/profile/FragmentProfile.java index c2c40ae..8bc2659 100644 --- a/app/src/main/java/com/santiparra/yomitrack/ui/fragments/profile/FragmentProfile.java +++ b/app/src/main/java/com/santiparra/yomitrack/ui/fragments/profile/FragmentProfile.java @@ -25,7 +25,6 @@ import com.santiparra.yomitrack.model.UserStatsResponse; import com.santiparra.yomitrack.utils.ActivityLog; import com.santiparra.yomitrack.utils.DateUtils; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -91,9 +90,6 @@ public class FragmentProfile extends Fragment { editBiography.setText(bioPrefs.getString(BIO_KEY, "")); api = ApiClient.getClient().create(ApiService.class); - - loadStats(); - loadActivity(); } private void setupListeners() { @@ -122,24 +118,19 @@ public class FragmentProfile extends Fragment { private void populateStats(LinearLayout container, Map stats) { container.removeAllViews(); - if (stats == null || stats.isEmpty()) { addTextToContainer(container, "No hay estadísticas disponibles"); return; } - int total = stats.values().stream().mapToInt(Integer::intValue).sum(); LayoutInflater inflater = LayoutInflater.from(getContext()); - for (Map.Entry entry : stats.entrySet()) { View statView = inflater.inflate(R.layout.item_stat_bar, container, false); TextView label = statView.findViewById(R.id.statLabelFull); ProgressBar bar = statView.findViewById(R.id.statProgressBar); - label.setText(String.format(Locale.getDefault(), "%s • %d", entry.getKey(), entry.getValue())); bar.setProgress(total > 0 ? (entry.getValue() * 100 / total) : 0); bar.setProgressTintList(ColorStateList.valueOf(getColorForStatus(entry.getKey()))); - container.addView(statView); } } @@ -153,18 +144,12 @@ public class FragmentProfile extends Fragment { private int getColorForStatus(String status) { switch (status.toLowerCase(Locale.ROOT)) { - case "watching": - return requireContext().getColor(R.color.status_watching); - case "completed": - return requireContext().getColor(R.color.status_completed); - case "paused": - return requireContext().getColor(R.color.status_paused); - case "dropped": - return requireContext().getColor(R.color.status_dropped); - case "planning": - return requireContext().getColor(R.color.status_planning); - default: - return requireContext().getColor(R.color.gray); + case "watching": return requireContext().getColor(R.color.status_watching); + case "completed": return requireContext().getColor(R.color.status_completed); + case "paused": return requireContext().getColor(R.color.status_paused); + case "dropped": return requireContext().getColor(R.color.status_dropped); + case "planning": return requireContext().getColor(R.color.status_planning); + default: return requireContext().getColor(R.color.gray); } } @@ -175,19 +160,14 @@ public class FragmentProfile extends Fragment { if (response.isSuccessful() && response.body() != null) { activityContainer.removeAllViews(); LayoutInflater inflater = LayoutInflater.from(getContext()); - for (ActivityLog log : response.body()) { View card = inflater.inflate(R.layout.item_activity_card, activityContainer, false); - LinearLayout commentsContainer = card.findViewById(R.id.commentsContainer); - commentsContainer.setVisibility(View.GONE); card.setOnClickListener(v -> { if (commentsContainer.getVisibility() == View.VISIBLE) { - commentsContainer.animate().alpha(0).setDuration(150).withEndAction(() -> { - commentsContainer.setVisibility(View.GONE); - }).start(); + commentsContainer.animate().alpha(0).setDuration(150).withEndAction(() -> commentsContainer.setVisibility(View.GONE)).start(); } else { commentsContainer.setAlpha(0); commentsContainer.setVisibility(View.VISIBLE); @@ -203,20 +183,14 @@ public class FragmentProfile extends Fragment { ImageView coverImage = card.findViewById(R.id.activityCover); if (!TextUtils.isEmpty(log.getImageUrl())) { - Glide.with(requireContext()) - .load(log.getImageUrl()) - .placeholder(R.drawable.placeholder_image) - .error(R.drawable.placeholder_image) - .into(coverImage); + Glide.with(requireContext()).load(log.getImageUrl()).placeholder(R.drawable.placeholder_image).error(R.drawable.placeholder_image).into(coverImage); } ImageButton commentButton = card.findViewById(R.id.commentButton); ImageButton likeButton = card.findViewById(R.id.likeButton); commentButton.setOnClickListener(v -> { - CommentDialog dialog = new CommentDialog(requireContext(), userId, log.getId(), () -> { - loadComments(log.getId(), card.findViewById(R.id.commentsContainer)); - }); + CommentDialog dialog = new CommentDialog(requireContext(), userId, log.getId(), () -> loadComments(log.getId(), commentsContainer)); dialog.show(); }); @@ -226,47 +200,25 @@ public class FragmentProfile extends Fragment { if (response.isSuccessful() && response.body() != null) { final boolean[] isLiked = {response.body().get("liked").getAsBoolean()}; actualizarCorazon(likeButton, isLiked[0]); - likeButton.setOnClickListener(v -> { - if (isLiked[0]) { - api.deleteLike(createLikeJson(userId, log.getId())).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - isLiked[0] = false; - actualizarCorazon(likeButton, false); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - Toast.makeText(getContext(), "Error de conexión", Toast.LENGTH_SHORT).show(); - } + boolean newLike = !isLiked[0]; + isLiked[0] = newLike; + if (newLike) { + api.postLike(createLikeJson(userId, log.getId())).enqueue(new Callback() { + @Override public void onResponse(Call call, Response response) { actualizarCorazon(likeButton, true); } + @Override public void onFailure(Call call, Throwable t) {} }); } else { - api.postLike(createLikeJson(userId, log.getId())).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - isLiked[0] = true; - actualizarCorazon(likeButton, true); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - Toast.makeText(getContext(), "Error de conexión", Toast.LENGTH_SHORT).show(); - } + api.deleteLike(createLikeJson(userId, log.getId())).enqueue(new Callback() { + @Override public void onResponse(Call call, Response response) { actualizarCorazon(likeButton, false); } + @Override public void onFailure(Call call, Throwable t) {} }); } }); } } - @Override - public void onFailure(Call call, Throwable t) { - Toast.makeText(getContext(), "Error al verificar like", Toast.LENGTH_SHORT).show(); - } + public void onFailure(Call call, Throwable t) {} }); activityContainer.addView(card); @@ -287,24 +239,19 @@ public class FragmentProfile extends Fragment { Toast.makeText(getContext(), "Escribe algo primero", Toast.LENGTH_SHORT).show(); return; } - Map post = new HashMap<>(); post.put("userId", userId); post.put("action", "publicó"); post.put("mediaTitle", status); - api.postActivity(post).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { + @Override public void onResponse(Call call, Response response) { if (response.isSuccessful()) { editStatus.setText(""); loadActivity(); Toast.makeText(getContext(), "Publicado", Toast.LENGTH_SHORT).show(); } } - - @Override - public void onFailure(Call call, Throwable t) { + @Override public void onFailure(Call call, Throwable t) { Toast.makeText(getContext(), "Error al publicar", Toast.LENGTH_SHORT).show(); } }); @@ -312,61 +259,78 @@ public class FragmentProfile extends Fragment { private void loadComments(int activityId, LinearLayout container) { api.getCommentsByActivity(activityId).enqueue(new Callback>() { - @Override - public void onResponse(Call> call, Response> response) { + @Override public void onResponse(Call> call, Response> response) { if (response.isSuccessful() && response.body() != null) { container.removeAllViews(); LayoutInflater inflater = LayoutInflater.from(getContext()); - for (CommentModel comment : response.body()) { - View commentView = LayoutInflater.from(getContext()) - .inflate(R.layout.item_comment, container, false); - - TextView usernameView = commentView.findViewById(R.id.commentUsername); - TextView commentText = commentView.findViewById(R.id.commentText); - TextView dateView = commentView.findViewById(R.id.commentDate); - ImageView avatar = commentView.findViewById(R.id.commentAvatar); - - usernameView.setText(comment.getUsername()); - commentText.setText(comment.getText()); - dateView.setText(DateUtils.getRelativeTime(comment.getCreatedAt())); // usa tu util - + View view = inflater.inflate(R.layout.item_comment, container, false); + ((TextView) view.findViewById(R.id.commentUsername)).setText(comment.getUsername()); + ((TextView) view.findViewById(R.id.commentText)).setText(comment.getText()); + ((TextView) view.findViewById(R.id.commentDate)).setText(DateUtils.getRelativeTime(comment.getCreatedAt())); + ImageView avatar = view.findViewById(R.id.commentAvatar); if (!TextUtils.isEmpty(comment.getAvatarUrl())) { - Glide.with(getContext()) - .load(comment.getAvatarUrl()) - .placeholder(R.drawable.rectangle_placeholder) - .error(R.drawable.error_image) - .into(avatar); + Glide.with(requireContext()).load(comment.getAvatarUrl()).placeholder(R.drawable.rectangle_placeholder).error(R.drawable.error_image).into(avatar); } - container.addView(commentView); - } + ImageButton replyButton = view.findViewById(R.id.replyButton); + replyButton.setOnClickListener(v -> { + CommentDialog dialog = new CommentDialog(requireContext(), userId, activityId, () -> loadComments(activityId, container), comment.getUsername()); + dialog.show(); + }); + ImageButton likeButton = view.findViewById(R.id.commentLikeButton); + api.checkLike(userId, comment.getId()).enqueue(new Callback() { + @Override public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + final boolean[] isLiked = {response.body().get("liked").getAsBoolean()}; + actualizarCorazon(likeButton, isLiked[0]); + + likeButton.setOnClickListener(v -> { + boolean newLike = !isLiked[0]; + isLiked[0] = newLike; + + if (newLike) { + api.postLike(createLikeJson(userId, comment.getId())).enqueue(new Callback() { + @Override public void onResponse(Call call, Response response) { + actualizarCorazon(likeButton, true); + } + @Override public void onFailure(Call call, Throwable t) {} + }); + } else { + api.deleteLike(createLikeJson(userId, comment.getId())).enqueue(new Callback() { + @Override public void onResponse(Call call, Response response) { + actualizarCorazon(likeButton, false); + } + @Override public void onFailure(Call call, Throwable t) {} + }); + } + }); + } + } + @Override public void onFailure(Call call, Throwable t) {} + }); + + container.addView(view); + } } } - - @Override - public void onFailure(Call> call, Throwable t) { + @Override public void onFailure(Call> call, Throwable t) { Toast.makeText(getContext(), "Error al cargar comentarios", Toast.LENGTH_SHORT).show(); } }); } - private JsonObject createLikeJson(int userId, int activityId) { - JsonObject body = new JsonObject(); - body.addProperty("userId", userId); - body.addProperty("activityId", activityId); - return body; + private JsonObject createLikeJson(int userId, int targetId) { + JsonObject json = new JsonObject(); + json.addProperty("userId", userId); + json.addProperty("commentId", targetId); + return json; } - private void actualizarCorazon(ImageButton likeButton, boolean liked) { - if (liked) { - likeButton.setImageResource(R.drawable.ic_heart_filled); - likeButton.setColorFilter(requireContext().getColor(R.color.pink)); - } else { - likeButton.setImageResource(R.drawable.ic_heart_outline); - likeButton.setColorFilter(requireContext().getColor(R.color.gray)); - } + private void actualizarCorazon(ImageButton btn, boolean liked) { + btn.setImageResource(liked ? R.drawable.ic_heart_filled : R.drawable.ic_heart_outline); + btn.setColorFilter(requireContext().getColor(liked ? R.color.pink : R.color.gray)); } private void saveBiography() { diff --git a/app/src/main/java/com/santiparra/yomitrack/utils/ActivityLog.java b/app/src/main/java/com/santiparra/yomitrack/utils/ActivityLog.java index 1cc4442..690b9af 100644 --- a/app/src/main/java/com/santiparra/yomitrack/utils/ActivityLog.java +++ b/app/src/main/java/com/santiparra/yomitrack/utils/ActivityLog.java @@ -6,6 +6,13 @@ public class ActivityLog { @SerializedName("id") private int id; + + @SerializedName("userId") + private int userId; + + @SerializedName("username") + private String username; + @SerializedName("action") private String action; @@ -22,6 +29,14 @@ public class ActivityLog { return id; } + public int getUserId() { + return userId; + } + + public String getUsername() { + return username; + } + public String getAction() { return action; } diff --git a/app/src/main/java/com/santiparra/yomitrack/utils/DateUtils.java b/app/src/main/java/com/santiparra/yomitrack/utils/DateUtils.java index ceb16bc..ecf6f00 100644 --- a/app/src/main/java/com/santiparra/yomitrack/utils/DateUtils.java +++ b/app/src/main/java/com/santiparra/yomitrack/utils/DateUtils.java @@ -59,4 +59,10 @@ public class DateUtils { return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR); } + + public static String getCurrentTimestamp() { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + return sdf.format(new Date()); + } } diff --git a/app/src/main/res.zip b/app/src/main/res.zip index a262c9f..1c63593 100644 Binary files a/app/src/main/res.zip and b/app/src/main/res.zip differ diff --git a/app/src/main/res/layout/dialog_comment.xml b/app/src/main/res/layout/dialog_comment.xml index 5149169..3932d1a 100644 --- a/app/src/main/res/layout/dialog_comment.xml +++ b/app/src/main/res/layout/dialog_comment.xml @@ -1,20 +1,36 @@ + + android:orientation="vertical" + android:padding="16dp" + android:background="?android:attr/windowBackground"> + android:hint="Escribe tu comentario..." + android:minHeight="80dp" + android:gravity="top|start" + android:inputType="textMultiLine" + android:maxLines="5" + android:background="@color/primary" + android:padding="12dp" + android:textColor="@color/textPrimary" + android:textColorHint="@color/textPrimary" + android:textSize="15sp" />