diff --git a/app/src/main/java/com/example/tampopo_client/resources/FriendRequestsResource.java b/app/src/main/java/com/example/tampopo_client/resources/FriendRequestsResource.java index b82d1ae..bc52a64 100644 --- a/app/src/main/java/com/example/tampopo_client/resources/FriendRequestsResource.java +++ b/app/src/main/java/com/example/tampopo_client/resources/FriendRequestsResource.java @@ -33,6 +33,6 @@ @DELETE("friend-requests/{friend-request-id}") Call deleteFriendRequest( @Path("friend-request-id") String friendRequestId, - @Query("token") String token + @Query("receiver-token") String token ); } \ No newline at end of file diff --git a/app/src/main/java/com/example/tampopo_client/viewmodels/FriendSentRequestViewModel.java b/app/src/main/java/com/example/tampopo_client/viewmodels/FriendSentRequestViewModel.java index 3cc0a89..9e8d0f0 100644 --- a/app/src/main/java/com/example/tampopo_client/viewmodels/FriendSentRequestViewModel.java +++ b/app/src/main/java/com/example/tampopo_client/viewmodels/FriendSentRequestViewModel.java @@ -50,17 +50,34 @@ return operationResult; } + // 直近の読み込み条件をキャッシュ(削除後の自動再読込に使用) + private String cachedToken; + private String cachedUserId; + // サーバーから送信済みフレンドリクエスト一覧を取得してキャッシュに保存 - public void loadSentRequests(String token) { + public void loadSentRequests(String token, String myUserId) { + this.cachedToken = token; + this.cachedUserId = myUserId; + Call> call = friendRequestsResource.getFriendRequests(token); -// フレンド一覧をサーバーからもらう時にエラーが起きている call.enqueue(new Callback>() { @Override public void onResponse(Call> call, Response> response) { if (response.isSuccessful()) { - sentRequests.setValue(response.body()); + List all = response.body(); + if (all == null) all = java.util.Collections.emptyList(); + + // 自分が送信したものだけにフィルタ + java.util.List sentOnly = new java.util.ArrayList<>(); + for (FriendRequest fr : all) { + if (fr.getSenderId() != null && fr.getSenderId().equals(myUserId)) { + sentOnly.add(fr); + } + } + + sentRequests.setValue(sentOnly); operationResult.setValue("Success"); - System.out.println("Success SetValue: " + response.body()); + System.out.println("Success SetValue (sentOnly): " + sentOnly); } else { operationResult.setValue("Error: " + response.code()); System.out.println("Response error: " + response.code()); @@ -86,8 +103,10 @@ //Retrofitでサーバーにリクエストを送った後、その結果が帰ってくる。成功 operationResult.setValue("Friend request sent successfully."); - // 成功時に送信済みリストを更新 - loadSentRequests(token); + // 成功時に送信済みリストを更新(キャッシュがあれば活用) + if (cachedToken != null && cachedUserId != null) { + loadSentRequests(cachedToken, cachedUserId); + } System.out.println("Friend request sent: " + response.body()); } else { @@ -103,6 +122,31 @@ } }); } + + // 送信済みフレンドリクエストの取り消し + public void deleteFriendRequest(String friendRequestId, String token) { + Call call = friendRequestsResource.deleteFriendRequest(friendRequestId, token); + + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + operationResult.setValue("Friend request deleted successfully."); + // 再読込 + if (cachedToken != null && cachedUserId != null) { + loadSentRequests(cachedToken, cachedUserId); + } + } else { + operationResult.setValue("Error deleting request: " + response.code()); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + operationResult.setValue("Network error: " + t.getMessage()); + } + }); + } } diff --git a/app/src/main/java/com/example/tampopo_client/views/FriendActivity.java b/app/src/main/java/com/example/tampopo_client/views/FriendActivity.java index f556cc1..a5e05bf 100644 --- a/app/src/main/java/com/example/tampopo_client/views/FriendActivity.java +++ b/app/src/main/java/com/example/tampopo_client/views/FriendActivity.java @@ -47,6 +47,8 @@ if (position == 0) { tab.setText("フレンド"); } else if (position == 1) { + tab.setText("申請中"); + } else if (position == 2) { tab.setText("保留中"); } }).attach(); diff --git a/app/src/main/java/com/example/tampopo_client/views/FriendSentFragment.java b/app/src/main/java/com/example/tampopo_client/views/FriendSentFragment.java new file mode 100644 index 0000000..1bfac72 --- /dev/null +++ b/app/src/main/java/com/example/tampopo_client/views/FriendSentFragment.java @@ -0,0 +1,102 @@ +package com.example.tampopo_client.views; + +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.example.tampopo_client.R; +import com.example.tampopo_client.Tampopo; +import com.example.tampopo_client.models.FriendRequest; +import com.example.tampopo_client.viewmodels.FriendSentRequestViewModel; + +import java.util.ArrayList; +import java.util.List; + +public class FriendSentFragment extends Fragment { + + private static final String ARG_COLUMN_COUNT = "column-count"; + private int mColumnCount = 1; + + private MyFriendSentRequestRecyclerViewAdapter adapter; + private FriendSentRequestViewModel friendSentRequestViewModel; + private Tampopo tampopo; + + public FriendSentFragment() {} + + public static FriendSentFragment newInstance(int columnCount) { + FriendSentFragment fragment = new FriendSentFragment(); + Bundle args = new Bundle(); + args.putInt(ARG_COLUMN_COUNT, columnCount); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (getArguments() != null) { + mColumnCount = getArguments().getInt(ARG_COLUMN_COUNT); + } + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_friend_sent_list, container, false); + + friendSentRequestViewModel = new ViewModelProvider(this).get(FriendSentRequestViewModel.class); + + tampopo = (Tampopo) requireActivity().getApplication(); + String myUserId = tampopo.getUserId(); + String token = tampopo.getToken(); + + if (view instanceof RecyclerView) { + Context context = view.getContext(); + RecyclerView recyclerView = (RecyclerView) view; + if (mColumnCount <= 1) { + recyclerView.setLayoutManager(new LinearLayoutManager(context)); + } else { + recyclerView.setLayoutManager(new GridLayoutManager(context, mColumnCount)); + } + adapter = new MyFriendSentRequestRecyclerViewAdapter(new ArrayList<>(), friendSentRequestViewModel, myUserId, token); + recyclerView.setAdapter(adapter); + } + + return view; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + // 初回ロード + friendSentRequestViewModel.loadSentRequests(tampopo.getToken(), tampopo.getUserId()); + + friendSentRequestViewModel.getSentRequestsLiveData().observe(getViewLifecycleOwner(), new Observer>() { + @Override + public void onChanged(List friendRequests) { + if (adapter != null) { + adapter.setItems(friendRequests); + adapter.notifyDataSetChanged(); + } + } + }); + } + + @Override + public void onResume() { + super.onResume(); + // 相手が許可してペア成立した場合、サーバ側で申請が消えるため再読込でUIからも消える + if (tampopo != null) { + friendSentRequestViewModel.loadSentRequests(tampopo.getToken(), tampopo.getUserId()); + } + } +} diff --git a/app/src/main/java/com/example/tampopo_client/views/MyFragmentAdapter.java b/app/src/main/java/com/example/tampopo_client/views/MyFragmentAdapter.java index bdde5e5..9e24999 100644 --- a/app/src/main/java/com/example/tampopo_client/views/MyFragmentAdapter.java +++ b/app/src/main/java/com/example/tampopo_client/views/MyFragmentAdapter.java @@ -15,17 +15,20 @@ @Override public Fragment createFragment(int position) { if (position == 0) { - // フラグメント①:申請を受けた + // フラグメント①:フレンド return new FriendListFragment(); + } else if (position == 1) { + // フラグメント②:申請中(自分が送った申請) + return new FriendSentFragment(); } else { - // フラグメント②:友達リスト + // フラグメント③:保留中(自分が受け取った申請) return new FriendReceivedFragment(); } } @Override public int getItemCount() { - return 2; + return 3; } } diff --git a/app/src/main/java/com/example/tampopo_client/views/MyFriendSentRequestRecyclerViewAdapter.java b/app/src/main/java/com/example/tampopo_client/views/MyFriendSentRequestRecyclerViewAdapter.java new file mode 100644 index 0000000..c8ca719 --- /dev/null +++ b/app/src/main/java/com/example/tampopo_client/views/MyFriendSentRequestRecyclerViewAdapter.java @@ -0,0 +1,148 @@ +package com.example.tampopo_client.views; + +import android.app.AlertDialog; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.example.tampopo_client.R; +import com.example.tampopo_client.models.FriendRequest; +import com.example.tampopo_client.resources.UserResource; +import com.example.tampopo_client.viewmodels.FriendSentRequestViewModel; + +import java.util.ArrayList; +import java.util.List; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.jackson.JacksonConverterFactory; +import retrofit2.converter.scalars.ScalarsConverterFactory; + +public class MyFriendSentRequestRecyclerViewAdapter extends RecyclerView.Adapter { + + private final List items; + private final FriendSentRequestViewModel viewModel; + private final String myUserId; + private final String token; + + private final UserResource userResource; + + public MyFriendSentRequestRecyclerViewAdapter(List items, + FriendSentRequestViewModel viewModel, + String myUserId, + String token) { + this.items = new ArrayList<>(items); + this.viewModel = viewModel; + this.myUserId = myUserId; + this.token = token; + + Retrofit retrofit = new Retrofit.Builder() + .baseUrl("http://nitta-lab-www.is.konan-u.ac.jp/tampopo/") + .addConverterFactory(ScalarsConverterFactory.create()) + .addConverterFactory(JacksonConverterFactory.create()) + .build(); + this.userResource = retrofit.create(UserResource.class); + } + + public void setItems(List newItems) { + this.items.clear(); + if (newItems != null) this.items.addAll(newItems); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fragment_friend_sent, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + FriendRequest item = items.get(position); + holder.bind(item); + } + + @Override + public int getItemCount() { + return items.size(); + } + + class ViewHolder extends RecyclerView.ViewHolder { + ImageView iconView; + TextView nameView; + TextView userIdView; + Button pendingButton; + + ViewHolder(@NonNull View itemView) { + super(itemView); + iconView = itemView.findViewById(R.id.sent_icon); + nameView = itemView.findViewById(R.id.sent_name); + userIdView = itemView.findViewById(R.id.sent_user_id); + pendingButton = itemView.findViewById(R.id.sent_pending_button); + } + + void bind(FriendRequest request) { + // 相手のユーザーID(自分がsenderなので相手はreceiver) + String otherUserId = request.getReceiverId(); + userIdView.setText(otherUserId != null ? otherUserId : ""); + nameView.setText(""); + iconView.setImageResource(R.drawable.friend_icon); + + // アイコン + if (otherUserId != null) { + userResource.getIcon(otherUserId).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + Glide.with(iconView.getContext()).load(response.body()).placeholder(R.drawable.friend_icon).into(iconView); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + // ignore + } + }); + + // ニックネーム + userResource.getName(otherUserId).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + nameView.setText(response.body()); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + // ignore + } + }); + } + + pendingButton.setText("申請中"); + pendingButton.setOnClickListener(v -> { + new AlertDialog.Builder(v.getContext()) + .setTitle("申請の取り消し") + .setMessage("このフレンド申請を取り消しますか?") + .setNegativeButton("キャンセル", (dialog, which) -> dialog.dismiss()) + .setPositiveButton("削除", (dialog, which) -> { + // 申請削除 + if (request.getId() != null) { + viewModel.deleteFriendRequest(String.valueOf(request.getId()), token); + } + }) + .show(); + }); + } + } +} diff --git a/app/src/main/res/layout/fragment_friend_sent.xml b/app/src/main/res/layout/fragment_friend_sent.xml new file mode 100644 index 0000000..2309e9d --- /dev/null +++ b/app/src/main/res/layout/fragment_friend_sent.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + +