From b7770c9fc65b28a56361e66f404e03c3c90f196b Mon Sep 17 00:00:00 2001 From: santi Date: Tue, 27 May 2025 00:03:51 +0200 Subject: [PATCH] Terminacion de la clase FragmentProfile que ya tiene toda la funcionalidad que a de tener y optimizacion de otras para que funcione y creacion de javas y xml! --- app/src/main/java.zip | Bin 59449 -> 63291 bytes .../santiparra/yomitrack/api/ApiService.java | 15 + .../yomitrack/db/entities/AnimeEntity.java | 13 + .../yomitrack/db/entities/MangaEntity.java | 14 + .../yomitrack/model/CommentDialog.java | 94 +++-- .../yomitrack/model/CommentModel.java | 28 +- .../santiparra/yomitrack/model/ItemModel.java | 43 +- .../yomitrack/model/RecentActivityModel.java | 45 +- .../RecentActivityAdapter.java | 133 ++++-- .../ui/fragments/browse/FragmentBrowse.java | 179 +++----- .../ui/fragments/home/FragmentHome.java | 390 ++++++++++++++---- .../ui/fragments/profile/FragmentProfile.java | 188 ++++----- .../yomitrack/utils/ActivityLog.java | 15 + .../santiparra/yomitrack/utils/DateUtils.java | 6 + app/src/main/res.zip | Bin 11131876 -> 11133755 bytes app/src/main/res/layout/dialog_comment.xml | 24 +- app/src/main/res/layout/fragment_browse.xml | 107 ++--- app/src/main/res/layout/fragment_home.xml | 168 ++++---- app/src/main/res/layout/item_comment.xml | 79 ++-- app/src/main/res/values/ids.xml | 4 + 20 files changed, 944 insertions(+), 601 deletions(-) diff --git a/app/src/main/java.zip b/app/src/main/java.zip index c6dbf95c8d6377c7a11aea4ae3b106b6b057ad1d..0546ab5078cf362908d33126729c182a36cda02c 100644 GIT binary patch delta 18788 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

$W=tD&7A@Y>1cUp&(( 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 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 a262c9fed8476303c5df3b90819f9773c89926f0..1c63593be109847cd8095127166315d71abfd544 100644 GIT binary patch delta 9099 zcmaL62Rzl^ANbF`W;WTg60*t2E|esD#3g%=lu;sX%4|!Vl%2STGEy!glr0(AtCW^V zk_hoT_ukL{Jj zO3LRstXxn{>M*r3{YUD(zQ`bz9b-*OO7+HclF~qAk(~&OyN^q#ms^O3uZw$d1USVF z`S2urWan%9TiI9Vs^d0{67#_=+7(4lYA*JMgwrQw@>r@yc#UZ%>#e@dNX|?cl=khv z3XYi_RF`)4|H-4j5M*G(yDzmgwXD>;;Mab)mX5dQaL@LZ1)ZxDo6}5-av#Ch^xetV zQ#oeM%|xuIFn%c>+}JQzyq!lUY=>wLzyEZ|a*#cpQQs!Dg$X@FO4G1e3>6jCGpbZ^ ztPQybQP>`GXiD>e6jrLz(o>TKJ-SoW^rW_%IJ1F(zK9E{8Vm;H6g&eB+2mpZV`2g$ zH3EacB4`k_2s#8kf&oFY*@j?3Fe6wHtOzy)JAwnjNlakm8o`iCC%Q3nX_AX4X0vdO z3Xye@(p(V~-LeeVNs8nv$MupdC8{fMA+{s95j+T91RsJQA%GA>2qAu9I+cAK~z_e98o9N%jqM@O=*?1pX4Z6N~8;vL`Wg_Afyp82w8+2LLQ-jP(&yp zlo5LoDhO4C8bTeh521n3L}(G|0<{;fl4?v``>l=8LFgj%5c?4a5c&uMgdyS};t;|J zaTsBYID#-i97UKS%!q5h%~{9E{a_ljP@{~M;gCh5;gBW53So_~AsP_Ll4_H71!eGzfsQcr|2sV3r3PsBB9k_3GEBF4$W!TyM8vasiM!~$6m9EkWz z5)v5)BM@g1k%%b7IYcxf27yPMN5mp7ATA=}5b=lvL?R*yk&L*6xQw`hxQe)jxQ@7i zNI~30q#|x1(h%tgfVhpwK-@vxMPwqf5ZQ#NhZLYPz zrJbNi&!eM4a)lthonTMu8*#CnU`rkeqH!m|3QN{ycN0!gbeuf|O$=FQ-bc`;NY`Ey zR%oz}$bTk{5Df6Tn7zKJAYabO6`yX~t`p;m>~-|*dW2e@^qaZY8rERgrQy-KoWH6YBK_#5@RasE~8C%F5mU^=`-Lhi`K)XXo6reWtR?&HTB4w>70K zFt(@w>EsPeFkR7R%kSx?Q6ES%(ql1;VPn%?v&;=Ku)t6FckI$TsPaNFOqN@tI#-wN z_A3KLVjj2rgPx5;bxP~athxJ-(+>?B1l_k_u(vgn%k{hw5l=vbRUzp1(&!=VY zwc6uW-}JT)|Ll#|x^euk?OK(t5=UhMjnwyPE`oUdfFU?R;HF4L5Ck>ezNKFoyl; z%n<1v)Rz^%(^h#u!W%Z1V(xfoygNQBdTQD5+Pd4ri%fU%6ITJozo%01(+87|7dY=z ze!srv8c44Xjfhm7c9G@QO?J6w>UQX9f|}jUZC#}f{bqT7F17J0&m$HWn2c{ovrX}b z-Vep{)zL>err`ScPmGoMjmfOCittYToCfQb46Iuh{RB;}Io@u%h0BuQGFtB4Ypt`@ zWmh&dDu1&68{SN9_;|~!A|cMcpp!nM34C^@ht)dbrDx4HV*{`9yWh3Y*praXFGuWO zshV4jHF0`Sskdr5P=3lK*l&52aFRKYohC}DcSos@a&P85Uz1g%->1`2Q{S8pMxUFt z^G#~tPvE&#;y2PZG5nmHA2WR7#b9{nSyx&a2Z=dhzkOmRg

s;n70_%dZON=>mTz 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 + 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" />