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!

This commit is contained in:
santi 2025-05-27 00:03:51 +02:00
parent c7053368a2
commit b7770c9fc6
20 changed files with 944 additions and 601 deletions

Binary file not shown.

View File

@ -53,6 +53,9 @@ public interface ApiService {
@Query("size") int size
);
@GET("anime/airing")
Call<List<AnimeEntity>> getAiringAnime();
@PUT("anime/{id}")
Call<ApiResponse> 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<List<MangaEntity>> getMangaByUserAndStatus(
@Path("userId") int userId,
@Path("status") String status
);
@PUT("manga/{id}")
Call<ApiResponse> 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<List<AnimeEntity>> getAnimeByUserAndStatus(
@Path("userId") int userId,
@Path("status") String status
);
@POST("api/activity/like")
Call<JsonObject> postLike(@Body JsonObject body);

View File

@ -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() {

View File

@ -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() {

View File

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

View File

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

View File

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

View File

@ -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<CommentModel> 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<CommentModel> comments;
public boolean liked;
public RecentActivityModel(int id, int userId, String user, String action, String title, String imageUrl, String time, List<CommentModel> 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;
}
}

View File

@ -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<RecentActivityAdapter.ActivityViewHolder> {
private final List<RecentActivityModel> activityList;
public RecentActivityAdapter(List<RecentActivityModel> activityList) {
private List<RecentActivityModel> activityList;
private final int currentUserId;
public RecentActivityAdapter(List<RecentActivityModel> activityList, int currentUserId) {
this.activityList = activityList;
this.currentUserId = currentUserId;
}
@NonNull
@ -33,7 +38,8 @@ public class RecentActivityAdapter extends RecyclerView.Adapter<RecentActivityAd
@Override
public void onBindViewHolder(@NonNull ActivityViewHolder holder, int position) {
RecentActivityModel activity = activityList.get(position);
holder.user.setText(activity.user);
holder.user.setText(activity.getUser());
holder.action.setText(activity.action);
holder.title.setText(activity.title);
holder.time.setText(activity.time);
@ -44,34 +50,86 @@ public class RecentActivityAdapter extends RecyclerView.Adapter<RecentActivityAd
.into(holder.image);
holder.commentContainer.removeAllViews();
LayoutInflater inflater = LayoutInflater.from(holder.itemView.getContext());
if (holder.commentContainer != null) {
holder.commentContainer.removeAllViews();
for (CommentModel comment : activity.comments) {
View commentView = inflater.inflate(R.layout.item_comment, holder.commentContainer, false);
for (CommentModel comment : activity.comments) {
View commentView = LayoutInflater.from(holder.itemView.getContext())
.inflate(R.layout.item_comment, holder.commentContainer, 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);
TextView commentText = commentView.findViewById(R.id.commentText);
ImageButton commentLike = commentView.findViewById(R.id.commentLikeButton);
usernameView.setText(comment.getUsername());
commentText.setText(comment.getText());
dateView.setText(comment.getCreatedAt());
commentText.setText(comment.text);
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));
Glide.with(commentView.getContext())
.load(comment.getAvatarUrl())
.placeholder(R.drawable.rectangle_placeholder)
.error(R.drawable.error_image)
.into(avatar);
commentLike.setOnClickListener(v -> {
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<RecentActivityAd
return activityList.size();
}
class ActivityViewHolder extends RecyclerView.ViewHolder {
public void updateData(List<RecentActivityModel> 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<RecentActivityAd
image = itemView.findViewById(R.id.activityCover);
likeButton = itemView.findViewById(R.id.likeButton);
commentButton = itemView.findViewById(R.id.commentButton);
commentContainer = itemView.findViewById(R.id.commentsContainer); // <- protección aplicada
likeButton.setOnClickListener(v -> {
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);
}
}
}

View File

@ -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<ItemModel> animeList = new ArrayList<>();
private final List<ItemModel> 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<CharSequence> 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<String> 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<ItemModel> source) {
showSections(source, "");
}
private void showSections(List<ItemModel> source, String query) {
sectionContainer.removeAllViews();
List<ItemModel> trending = new ArrayList<>();
List<ItemModel> 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<ItemModel> 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<List<AniListMedia>>() {
@Override
public void onResponse(Call<List<AniListMedia>> call, Response<List<AniListMedia>> response) {
if (response.isSuccessful() && response.body() != null) {
searchAdapter = new AniListSearchAdapter(getContext(), response.body(), type);
recyclerViewResults.setAdapter(searchAdapter);
}
}
@Override
public void onFailure(Call<List<AniListMedia>> call, Throwable t) {
t.printStackTrace();
}
});
}
}

View File

@ -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<RecentActivityModel> fullRecentActivity = new ArrayList<>();
private final List<RecentActivityModel> 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<String> sectionTitles = Arrays.asList("Airing", "Anime in Progress", "Manga in Progress");
Map<String, List<ItemModel>> 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<List<AnimeEntity>>() {
@Override
public void onResponse(Call<List<AnimeEntity>> call, Response<List<AnimeEntity>> response) {
if (!isAdded()) return;
if (response.isSuccessful() && response.body() != null) {
List<ItemModel> 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<List<AnimeEntity>> 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<List<MangaEntity>>() {
@Override
public void onResponse(Call<List<MangaEntity>> call, Response<List<MangaEntity>> response) {
if (!isAdded()) return;
if (response.isSuccessful() && response.body() != null) {
List<ItemModel> 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<List<MangaEntity>> 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<String, Object> post = new HashMap<>();
post.put("userId", userId);
post.put("action", "publicó");
post.put("mediaTitle", status);
api.postActivity(post).enqueue(new Callback<JsonObject>() {
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
if (!isAdded()) return;
if (response.isSuccessful()) {
inputStatus.setText("");
loadActivity();
Toast.makeText(getContext(), "Publicado", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<JsonObject> 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<List<ActivityLog>>() {
@Override
public void onResponse(Call<List<ActivityLog>> call, Response<List<ActivityLog>> 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<JsonObject>() {
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> 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<JsonObject>() {
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
if (response.isSuccessful()) {
isLiked[0] = false;
actualizarCorazon(likeButton, false);
}
}
@Override
public void onFailure(Call<JsonObject> call, Throwable t) {}
});
} else {
api.postLike(createLikeJson(userId, log.getId())).enqueue(new Callback<JsonObject>() {
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
if (response.isSuccessful()) {
isLiked[0] = true;
actualizarCorazon(likeButton, true);
}
}
@Override
public void onFailure(Call<JsonObject> call, Throwable t) {}
});
}
});
}
}
@Override
public void onFailure(Call<JsonObject> call, Throwable t) {}
});
activityContainer.addView(card);
}
}
}
@Override
public void onFailure(Call<List<ActivityLog>> 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<List<CommentModel>>() {
@Override
public void onResponse(Call<List<CommentModel>> call, Response<List<CommentModel>> 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<JsonObject>() {
@Override public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {}
@Override public void onFailure(Call<JsonObject> call, Throwable t) {}
});
} else {
api.deleteLike(createLikeJson(userId, comment.getId())).enqueue(new Callback<JsonObject>() {
@Override public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {}
@Override public void onFailure(Call<JsonObject> 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<List<CommentModel>> call, Throwable t) {
if (!isAdded()) return;
Toast.makeText(getContext(), "Error al cargar comentarios", Toast.LENGTH_SHORT).show();
}
});
}
}

View File

@ -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<String, Integer> 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<String, Integer> entry : stats.entrySet()) {
View statView = inflater.inflate(R.layout.item_stat_bar, container, false);
TextView label = statView.findViewById(R.id.statLabelFull);
ProgressBar bar = statView.findViewById(R.id.statProgressBar);
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<JsonObject>() {
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
if (response.isSuccessful()) {
isLiked[0] = false;
actualizarCorazon(likeButton, false);
}
}
@Override
public void onFailure(Call<JsonObject> 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<JsonObject>() {
@Override public void onResponse(Call<JsonObject> call, Response<JsonObject> response) { actualizarCorazon(likeButton, true); }
@Override public void onFailure(Call<JsonObject> call, Throwable t) {}
});
} else {
api.postLike(createLikeJson(userId, log.getId())).enqueue(new Callback<JsonObject>() {
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
if (response.isSuccessful()) {
isLiked[0] = true;
actualizarCorazon(likeButton, true);
}
}
@Override
public void onFailure(Call<JsonObject> call, Throwable t) {
Toast.makeText(getContext(), "Error de conexión", Toast.LENGTH_SHORT).show();
}
api.deleteLike(createLikeJson(userId, log.getId())).enqueue(new Callback<JsonObject>() {
@Override public void onResponse(Call<JsonObject> call, Response<JsonObject> response) { actualizarCorazon(likeButton, false); }
@Override public void onFailure(Call<JsonObject> call, Throwable t) {}
});
}
});
}
}
@Override
public void onFailure(Call<JsonObject> call, Throwable t) {
Toast.makeText(getContext(), "Error al verificar like", Toast.LENGTH_SHORT).show();
}
public void onFailure(Call<JsonObject> 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<String, Object> post = new HashMap<>();
post.put("userId", userId);
post.put("action", "publicó");
post.put("mediaTitle", status);
api.postActivity(post).enqueue(new Callback<JsonObject>() {
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
@Override public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
if (response.isSuccessful()) {
editStatus.setText("");
loadActivity();
Toast.makeText(getContext(), "Publicado", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onFailure(Call<JsonObject> call, Throwable t) {
@Override public void onFailure(Call<JsonObject> 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<List<CommentModel>>() {
@Override
public void onResponse(Call<List<CommentModel>> call, Response<List<CommentModel>> response) {
@Override public void onResponse(Call<List<CommentModel>> call, Response<List<CommentModel>> 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<JsonObject>() {
@Override public void onResponse(Call<JsonObject> call, Response<JsonObject> 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<JsonObject>() {
@Override public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
actualizarCorazon(likeButton, true);
}
@Override public void onFailure(Call<JsonObject> call, Throwable t) {}
});
} else {
api.deleteLike(createLikeJson(userId, comment.getId())).enqueue(new Callback<JsonObject>() {
@Override public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
actualizarCorazon(likeButton, false);
}
@Override public void onFailure(Call<JsonObject> call, Throwable t) {}
});
}
});
}
}
@Override public void onFailure(Call<JsonObject> call, Throwable t) {}
});
container.addView(view);
}
}
}
@Override
public void onFailure(Call<List<CommentModel>> call, Throwable t) {
@Override public void onFailure(Call<List<CommentModel>> 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() {

View File

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

View File

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

Binary file not shown.

View File

@ -1,20 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:padding="16dp"
android:id="@+id/commentDialogLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:padding="16dp"
android:background="?android:attr/windowBackground">
<EditText
android:id="@+id/commentInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Escribe tu comentario" />
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" />
<Button
android:id="@+id/sendComment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Enviar"
android:textAllCaps="false"
android:layout_gravity="end"
android:layout_marginTop="8dp"/>
android:layout_marginTop="12dp"
android:backgroundTint="@color/primary"
android:textColor="@android:color/white" />
</LinearLayout>

View File

@ -1,69 +1,72 @@
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/scrollView"
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:background="@color/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:fitsSystemWindows="true"
android:background="@color/background"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="16dp">
android:padding="16dp">
<!-- Título y spinner -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="horizontal"
android:layout_marginTop="75dp"
android:gravity="center_vertical">
<!-- Espaciador reducido -->
<View
android:id="@+id/statusBarSpacer"
android:layout_width="match_parent"
android:layout_height="12dp" />
<TextView
android:text="Browse"
android:textStyle="bold"
android:textSize="24sp"
android:textColor="@android:color/white"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"/>
<!-- Encabezado -->
<LinearLayout
android:layout_width="match_parent"
<Spinner
android:id="@+id/spinnerType"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
android:backgroundTint="@android:color/white"
android:popupBackground="@color/primary"
android:theme="@style/ThemeOverlay.AppCompat.Dark"/>
</LinearLayout>
<TextView
android:id="@+id/textViewBrowse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Browse"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="@color/textPrimary"
android:layout_marginEnd="8dp" />
<!-- Caja de búsqueda -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_search_box"
android:orientation="horizontal"
android:layout_marginTop="16dp"
android:padding="12dp">
<Spinner
android:id="@+id/spinnerType"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="@color/primary" />
</LinearLayout>
<ImageView
android:src="@android:drawable/ic_menu_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:tint="@android:color/darker_gray" />
<!-- Buscador -->
<EditText
android:id="@+id/editTextSearch"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="12dp"
android:hint="Search"
android:drawableStart="@android:drawable/ic_menu_search"
android:background="@drawable/edittext_background"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:textColor="@color/textPrimary"
android:textColorHint="@color/textPrimary" />
<!-- Contenedor de secciones -->
<LinearLayout
android:id="@+id/sectionContainer"
android:hint="Buscar..."
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="16dp" />
android:paddingStart="8dp"
android:textColor="@android:color/white"
android:textColorHint="@android:color/white"
android:background="@android:color/transparent"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<!-- Resultados -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewResults"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="12dp"
android:layout_weight="1"
android:clipToPadding="false"
android:paddingBottom="16dp"/>
</LinearLayout>

View File

@ -1,90 +1,102 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/background"
tools:context=".ui.fragments.home.FragmentHome">
android:fillViewport="true">
<androidx.core.widget.NestedScrollView
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:fillViewport="true">
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="80dp"
android:paddingBottom="100dp">
<LinearLayout
<!-- Sección Anime en progreso -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Watching"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@color/textPrimary" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerAnime"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingBottom="16dp"
android:clipToPadding="false"
android:nestedScrollingEnabled="false"
android:orientation="horizontal" />
<!-- Sección Manga en progreso -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Reading"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@color/textPrimary" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerManga"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingBottom="16dp"
android:clipToPadding="false"
android:nestedScrollingEnabled="false"
android:orientation="horizontal" />
<!-- EditText para estado -->
<EditText
android:id="@+id/inputStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
android:background="@drawable/edit_text_background"
android:hint="Write a status..."
android:minHeight="60dp"
android:padding="16dp"
android:textColor="@color/textPrimary"
android:textColorHint="@color/textPrimary"
android:textSize="16sp" />
<!-- Botón publicar -->
<Button
android:id="@+id/btnPost"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Publicar"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp" />
<!-- Actividad reciente -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:text="Recent Activity"
android:textColor="@color/textPrimary"
android:textSize="18sp"
android:textStyle="bold" />
<LinearLayout
android:id="@+id/activityContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="80dp"
android:paddingBottom="100dp">
android:paddingHorizontal="16dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/mainRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:contentDescription="@string/home_section_list"
android:nestedScrollingEnabled="false"
android:overScrollMode="never"
android:paddingHorizontal="16dp"
android:paddingTop="8dp" />
<EditText
android:id="@+id/editStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
android:background="@drawable/edit_text_background"
android:hint="Write a status..."
android:minHeight="60dp"
android:padding="16dp"
android:textColor="@color/textPrimary"
android:textColorHint="@color/textPrimary"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
android:text="Recent Activity"
android:textColor="@color/textPrimary"
android:textSize="18sp"
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/activityRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="32dp"
android:nestedScrollingEnabled="false"
android:paddingHorizontal="16dp" />
<Button
android:id="@+id/buttonShowMoreActivity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginEnd="16dp"
android:layout_marginBottom="32dp"
android:text="Mostrar más"
android:visibility="gone" />
<Button
android:id="@+id/buttonShowLessActivity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="32dp"
android:text="Mostrar menos"
android:visibility="gone" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</FrameLayout>
</LinearLayout>
</ScrollView>

View File

@ -4,66 +4,81 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:padding="8dp"
android:gravity="top"
android:background="@color/primary">
<!-- Avatar del usuario -->
<ImageView
android:id="@+id/commentAvatar"
android:layout_width="32dp"
android:layout_height="32dp"
android:scaleType="centerCrop"
android:layout_width="36dp"
android:layout_height="36dp"
android:src="@drawable/ic_profile"
android:scaleType="centerCrop"
android:layout_marginEnd="8dp"
android:background="@drawable/circle_mask" />
android:background="@drawable/circle_mask"
android:clipToOutline="true" />
<!-- Contenido del comentario -->
<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
android:layout_weight="1"
android:orientation="vertical">
<!-- Nombre del usuario -->
<TextView
android:id="@+id/commentUsername"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Username"
android:text="Usuario"
android:textStyle="bold"
android:textSize="13sp" />
android:textColor="@color/textPrimary"
android:textSize="14sp" />
<!-- Texto del comentario -->
<TextView
android:id="@+id/commentText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Comentario aquí"
android:text="Comentario de prueba"
android:textColor="@color/textPrimary"
android:textSize="14sp"
android:lineSpacingExtra="2dp"
android:maxLines="5"
android:ellipsize="end"
android:paddingEnd="8dp" />
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp" />
<!-- Fecha -->
<TextView
android:id="@+id/commentDate"
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="hace 5 min"
android:textSize="11sp"
android:textColor="@color/gray" />
</LinearLayout>
android:orientation="horizontal"
android:gravity="center_vertical">
<!-- Botón de like -->
<ImageButton
android:id="@+id/commentLikeButton"
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/ic_heart"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/like_comment"
app:tint="@color/gray" />
<TextView
android:id="@+id/commentDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="hace 5 min"
android:textSize="12sp"
android:textColor="@color/gray" />
<ImageButton
android:id="@+id/commentLikeButton"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="12dp"
android:src="@drawable/ic_heart"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/like_comment"
app:tint="@color/gray" />
<!-- Botón opcional para responder -->
<ImageButton
android:id="@+id/replyButton"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="8dp"
android:src="@drawable/ic_reply"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="Responder comentario"
app:tint="@color/gray" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@ -56,4 +56,8 @@
<item name="commentButton" type="id" />
<item name="likeButton" type="id" />
<item name="activityCover" type="id" />
<item name="itemTitle" type="id" />
<item name="btnAdd" type="id" />
<item name="itemImage" type="id" />
<item name="commentDialogLayout" type="id" />
</resources>