diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 09ad5d2..40840fd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -61,6 +61,9 @@ + diff --git a/app/src/main/java/com/example/tampopo_client/Tampopo.java b/app/src/main/java/com/example/tampopo_client/Tampopo.java index 7f183cd..c4d1267 100644 --- a/app/src/main/java/com/example/tampopo_client/Tampopo.java +++ b/app/src/main/java/com/example/tampopo_client/Tampopo.java @@ -4,6 +4,10 @@ import android.content.SharedPreferences; public class Tampopo extends Application { + //いらないので下記2つをコメントアウトしました + //public String getUserId; + + //public String getToken; private String token; private String userId; private String password; @@ -52,7 +56,6 @@ } - public String getNickname() { return nickname; } public void setNickname(String nickname) { diff --git a/app/src/main/java/com/example/tampopo_client/models/Activity.java b/app/src/main/java/com/example/tampopo_client/models/Activity.java index 7c3d626..6dcf984 100644 --- a/app/src/main/java/com/example/tampopo_client/models/Activity.java +++ b/app/src/main/java/com/example/tampopo_client/models/Activity.java @@ -13,6 +13,9 @@ this.updateTime = updateTime; } + public Activity() { + } + public String getUserId() { return userId; } diff --git a/app/src/main/java/com/example/tampopo_client/models/Chatroom.java b/app/src/main/java/com/example/tampopo_client/models/Chatroom.java index 5a08e92..74622ba 100644 --- a/app/src/main/java/com/example/tampopo_client/models/Chatroom.java +++ b/app/src/main/java/com/example/tampopo_client/models/Chatroom.java @@ -2,21 +2,48 @@ public class Chatroom { private String chatroomId; // チャットルームのID + private String partnerUserId; // 相手ユーザーID + private String user1Id; // 1人目のユーザーID + private String user2Id; // 2人目のユーザーID public Chatroom() {} - - public Chatroom(String chatroomId) { + public Chatroom(String chatroomId, String user1Id, String user2Id) { this.chatroomId = chatroomId; + this.user1Id = user1Id; + this.user2Id = user2Id; + } + + + public Chatroom(String chatroomId,String partnerUserId) { + this.chatroomId = chatroomId; + this.partnerUserId = partnerUserId; } public String getChatroomId() { return chatroomId; } - public String foundChatroomId(){return chatroomId;} - public void setChatroomId(String chatroomId) { this.chatroomId = chatroomId; } + public String getUser1Id() { + return user1Id; + } + + + + public String getUser2Id() { + return user2Id; + } + + + public String getPartnerUserId() { + return partnerUserId; + } + + public void setPartnerUserId(String partnerUserId) { + this.partnerUserId = partnerUserId; + } + } diff --git a/app/src/main/java/com/example/tampopo_client/models/FriendPair.java b/app/src/main/java/com/example/tampopo_client/models/FriendPair.java index 4f9c35f..3ba71cd 100644 --- a/app/src/main/java/com/example/tampopo_client/models/FriendPair.java +++ b/app/src/main/java/com/example/tampopo_client/models/FriendPair.java @@ -10,6 +10,7 @@ this.user1Id = user1Id; } + public Integer getId() { return id; } diff --git a/app/src/main/java/com/example/tampopo_client/resources/ActivitiesResource.java b/app/src/main/java/com/example/tampopo_client/resources/ActivitiesResource.java index 7df8292..cef31d5 100644 --- a/app/src/main/java/com/example/tampopo_client/resources/ActivitiesResource.java +++ b/app/src/main/java/com/example/tampopo_client/resources/ActivitiesResource.java @@ -2,6 +2,7 @@ import com.example.tampopo_client.models.Activity; +import java.util.HashMap; import java.util.List; import retrofit2.Call; @@ -15,7 +16,7 @@ public interface ActivitiesResource { @GET("users/{user_id}/activities") - Call> getActivities(@Path("user_id") String userId, @Query("filter") String filter); + Call> getActivities(@Path("user_id") String userId, @Query("filter") String filter); @POST("users/{user_id}/activities") @FormUrlEncoded @@ -31,8 +32,8 @@ Call getText(@Path("user_id") String userId, @Path("activity_id") String activityId); @GET("users/{user_id}/activities/{activity_id}/updated-time") - Call getUpdatedTime(@Path("user_id") String userId, @Path("activity_id") String activityId); + Call getUpdatedTime(@Path("user_id") String userId, @Path("activity_id") String activityId); @GET("users/{user_id}/activities/last-updated-time") - Call getLastUpdatedTime(@Path("user_id") String userId, @Path("activity_id") String activityId); + Call getLastUpdatedTime(@Path("user_id") String userId, @Path("activity_id") String activityId); } diff --git a/app/src/main/java/com/example/tampopo_client/resources/ChatroomResource.java b/app/src/main/java/com/example/tampopo_client/resources/ChatroomResource.java index 2574364..4384636 100644 --- a/app/src/main/java/com/example/tampopo_client/resources/ChatroomResource.java +++ b/app/src/main/java/com/example/tampopo_client/resources/ChatroomResource.java @@ -27,6 +27,7 @@ @GET("foundChatroom") Call getMyChatroom( @Query("EnterUserId") String userId + ); diff --git a/app/src/main/java/com/example/tampopo_client/resources/FriendsResource.java b/app/src/main/java/com/example/tampopo_client/resources/FriendsResource.java index d323d6c..7c91307 100644 --- a/app/src/main/java/com/example/tampopo_client/resources/FriendsResource.java +++ b/app/src/main/java/com/example/tampopo_client/resources/FriendsResource.java @@ -2,6 +2,8 @@ import com.example.tampopo_client.models.FriendPair; +import java.util.List; + import retrofit2.Call; import retrofit2.http.DELETE; import retrofit2.http.Field; @@ -20,6 +22,13 @@ @Field("user1-id") String user1Id ); +// @GET("friends/{pair_id}/") +// Call getFriend( +// @Query("token") String token, +// @Path("pair_id") String pairId +// ); + + // @GET("friends/{pair_id}/") Call getFriend( @Query("token") String token, @@ -31,4 +40,11 @@ @Query("token") String token, @Path("pair_id") String pair_id ); + + //新しくサーバーで定義したので追加しました + @GET("friends/users/{user-id}") + Call> getFriends( + @Path("user-id") String userId, + @Query("token") String token + ); } diff --git a/app/src/main/java/com/example/tampopo_client/viewmodels/ActivitiesFetchCallback.java b/app/src/main/java/com/example/tampopo_client/viewmodels/ActivitiesFetchCallback.java new file mode 100644 index 0000000..ad6ca5f --- /dev/null +++ b/app/src/main/java/com/example/tampopo_client/viewmodels/ActivitiesFetchCallback.java @@ -0,0 +1,11 @@ +package com.example.tampopo_client.viewmodels; + +import com.example.tampopo_client.models.Activity; + +import java.util.List; + +public interface ActivitiesFetchCallback { + void onSuccess(List activities); + + void onFailure(Throwable throwable); +} diff --git a/app/src/main/java/com/example/tampopo_client/viewmodels/ActivityViewModel.java b/app/src/main/java/com/example/tampopo_client/viewmodels/ActivityViewModel.java index 2c455f2..3d15caf 100644 --- a/app/src/main/java/com/example/tampopo_client/viewmodels/ActivityViewModel.java +++ b/app/src/main/java/com/example/tampopo_client/viewmodels/ActivityViewModel.java @@ -9,8 +9,16 @@ import com.example.tampopo_client.resources.ActivitiesResource; import com.example.tampopo_client.resources.UserResource; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; import retrofit2.Call; import retrofit2.Callback; @@ -28,25 +36,37 @@ private final ActivitiesResource activitiesResource; private final UserResource userResource; - private final Map>> friendToActivitiesLiveData; // key=userId - private final MutableLiveData> friendUserIdsLiveData; // フレンドのユーザーIDのリスト private final MutableLiveData myLatestActivityLiveData; // 自分の最新のアクティビティ + private final Map>> friendToActivitiesLiveData; // <フレンドのユーザーID, フレンドのアクティビティのリスト> + private final MutableLiveData> friendUserIdsLiveData; // フレンドのユーザーIDのリスト - private final String userId; - private final String token; + private final String myUserId; + private final String myToken; - public ActivityViewModel(String userId, String token) { - this.userId = userId; - this.token = token; + private final List userActivityStatusChangeListeners; + + private final List sortedFriendUserIds = new ArrayList<>(); + + /** + * ActivityのViewModelを作成する。 + * + * @param myUserId 自身のユーザーID + * @param myToken 自身の認証用トークン + */ + public ActivityViewModel(String myUserId, String myToken) { + this.myUserId = myUserId; + this.myToken = myToken; // Retrofitの初期化 final Retrofit retrofit = new Retrofit.Builder().baseUrl("http://nitta-lab-www.is.konan-u.ac.jp/tampopo/").addConverterFactory(JacksonConverterFactory.create()).build(); activitiesResource = retrofit.create(ActivitiesResource.class); userResource = retrofit.create(UserResource.class); - friendToActivitiesLiveData = Map.of(); + friendToActivitiesLiveData = new HashMap<>(); friendUserIdsLiveData = new MutableLiveData<>(List.of()); - myLatestActivityLiveData = new MutableLiveData<>(); + myLatestActivityLiveData = new MutableLiveData<>(null); + + userActivityStatusChangeListeners = new ArrayList<>(); } @Override @@ -56,23 +76,95 @@ return; } - // 最新のアクティビティを取得して更新する + // 自分の最新のアクティビティを取得する + pullLatestActivity(myUserId, activitiesResource, new ActivityFetchCallback() { + @Override + public void onSuccess(Activity activity) { + Activity prevActivity = myLatestActivityLiveData.getValue(); + + // アクティビティが更新されていない場合 + LocalDateTime now = LocalDateTime.now(); + LocalDateTime lastUpdatedTime = getDateTimeFromString(activity.getUpdateTime()); + + if (now.isAfter(lastUpdatedTime.plusSeconds(5))) { + for (UserActivityStatusChangeListener observer : userActivityStatusChangeListeners) { + // アクティビティのステータス変更をリスナーに通知する + if (activity.equals(prevActivity)) { + observer.onUserStatusChanged(myUserId, UserActivityStatusChangeListener.Status.INACTIVE); + } else { + observer.onUserStatusChanged(myUserId, UserActivityStatusChangeListener.Status.ACTIVE); + } + } + } + + // 自分の新しいアクティビティをLiveDataに反映する + myLatestActivityLiveData.postValue(activity); + } + + @Override + public void onFailure(Throwable throwable) { + Log.e(ActivityViewModel.class.getSimpleName(), "An error has occurred while fetching my latest activity."); + } + }); + + // 自分のフレンドの最新のアクティビティを取得して更新する if (friendUserIdsLiveData.isInitialized() && friendUserIdsLiveData.getValue() != null) { for (String userId : friendUserIdsLiveData.getValue()) { - pullLatestActivity(userId, activitiesResource, friendToActivitiesLiveData); + pullLatestActivity(userId, activitiesResource, new ActivityFetchCallback() { + @Override + public void onSuccess(Activity activity) { + updateFriendToActivitiesLiveData(activity, userId); + } + + @Override + public void onFailure(Throwable throwable) { + Log.e(ActivityViewModel.class.getSimpleName(), "An error has occurred while fetching friend's latest activity.", throwable); + } + }); } } - // 最新のフレンドのユーザーIDを取得して更新する if (friendUserIdsLiveData.isInitialized()) { - pullLatestFriendUserIds(userId, token); + // 最新のフレンドのユーザーIDを取得して更新する + // TODO: 適切なコールバックを使う必要あり + pullLatestFriendUserIds(myUserId, myToken); } - - // Logging - Log.d(ActivityViewModel.class.getSimpleName(), "Polling data from the server."); }; } + /** + * ユーザIDからそのユーザーのアクティビティのリストのライブデータを取得し、最新のアクティビティに更新する + * + * @param latestActivity 最新のアクティビティ + * @param userId 更新対象のユーザーID + */ + private void updateFriendToActivitiesLiveData(Activity latestActivity, String userId) { + if (friendToActivitiesLiveData.get(userId) == null) { + friendToActivitiesLiveData.put(userId, new MutableLiveData<>(List.of())); + } + MutableLiveData> userActivitiesLiveData = friendToActivitiesLiveData.get(userId); + assert userActivitiesLiveData != null; + + List userActivities = userActivitiesLiveData.getValue(); + + if (userActivities == null || userActivities.isEmpty()) { + userActivitiesLiveData.postValue(List.of(latestActivity)); + } else { + if (userActivities.equals(List.of(latestActivity))) { + return; + } + + userActivitiesLiveData.postValue(List.of(latestActivity)); + } + } + + /** + * 自身のアクティビティを新しく作成する + * + * @param userId 自身のユーザーID + * @param token 自身の認証用トークン + * @param newActivity 新しいアクティビティのテキスト + */ public void createActivity(String userId, String token, String newActivity) { if (!myLatestActivityLiveData.isInitialized()) { return; @@ -85,7 +177,6 @@ if (response.isSuccessful()) { String createdActivityId = response.body(); - // TODO: 仮作成なので改善したい Call getActivityCall = activitiesResource.getActivity(userId, createdActivityId); getActivityCall.enqueue(new Callback() { @Override @@ -117,54 +208,47 @@ /** * 最新のユーザーのアクティビティを取得・更新する * - * @param userId 取得対象のユーザーのID - * @param resource アクティビティのリソース - * @param friendToActivitiesLiveData フレンドのユーザーIDからアクティビティへの写像のライブデータ + * @param userId 取得対象のユーザーのID + * @param resource アクティビティのリソース + * @param callback アクティビティが取得された後に呼び出されるコールバック */ - private void pullLatestActivity(String userId, ActivitiesResource resource, Map>> friendToActivitiesLiveData) { - Call> fetchActivityCall = resource.getActivities(userId, "LATEST"); - fetchActivityCall.enqueue(new Callback>() { + private void pullLatestActivity(String userId, ActivitiesResource resource, ActivityFetchCallback callback) { + Call> fetchActivityCall = resource.getActivities(userId, "LATEST"); + fetchActivityCall.enqueue(new Callback>() { @Override - public void onResponse(@NonNull Call> call, @NonNull Response> response) { + public void onResponse(@NonNull Call> call, @NonNull Response> response) { if (response.isSuccessful()) { - List fetchedActivities = response.body(); // アクティビティが存在しない場合は空のリスト - if (fetchedActivities == null) { + Collection activities = response.body().values(); // アクティビティが存在しない場合は空のリスト + if (activities == null || activities.isEmpty()) { return; } - if (friendToActivitiesLiveData.get(userId) == null) { - friendToActivitiesLiveData.put(userId, new MutableLiveData<>(List.of())); - } - MutableLiveData> userActivitiesLiveData = friendToActivitiesLiveData.get(userId); - assert userActivitiesLiveData != null; - - List userActivities = userActivitiesLiveData.getValue(); - if (userActivities == null) { - return; - } - if (fetchedActivities.isEmpty()) { - return; - } - - if (userActivities.isEmpty()) { - userActivitiesLiveData.postValue(fetchedActivities); - } else { - if (userActivities.equals(fetchedActivities)) { - return; - } - userActivitiesLiveData.postValue(fetchedActivities); - } + Activity latestActivity = activities.iterator().next(); + callback.onSuccess(latestActivity); } } @Override - public void onFailure(@NonNull Call> call, @NonNull Throwable t) { + public void onFailure(@NonNull Call> call, @NonNull Throwable t) { Log.e(ActivityViewModel.class.getSimpleName(), "An error has occurred while fetching the latest activity.", t); + + callback.onFailure(t); } }); } /** + * String型の日時をLocalDateTime型に変換する + * + * @param dateTime 変換対象の日時(文字列) + * @return 変換後の日時(LocalDateTime) + */ + private static LocalDateTime getDateTimeFromString(String dateTime) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm"); + return LocalDateTime.parse(dateTime, formatter); + } + + /** * 最新の自分のフレンドのユーザーIDをすべて取得・更新する。 * * @param userId 自分のユーザーID @@ -176,7 +260,29 @@ @Override public void onResponse(@NonNull Call> call, @NonNull Response> response) { if (response.isSuccessful()) { - friendUserIdsLiveData.postValue(response.body()); + + // MEMO: 入れ込んだだけ + // フレンドのIDをアクティビティ更新順に並べ替える + List friendUserIds = friendUserIdsLiveData.getValue(); + if (friendUserIds == null || !friendUserIds.equals(response.body())) { + friendUserIdsLiveData.postValue(response.body()); + SortedSet friends = new TreeSet<>(new Friend.UpdateTimeComparator()); + + assert friendUserIds != null; + friendUserIds.forEach(userId -> { + List activities = getActivitiesLiveDataFromUserId(userId).getValue(); + if (activities == null || activities.isEmpty()) { + return; + } + + Activity latestActivity = activities.get(0); + friends.add(new Friend(userId, latestActivity.getUpdateTime())); + }); + + // 並び替えたフレンドのユーザーIDを順番に格納して更新する + sortedFriendUserIds.clear(); + friends.forEach(friend -> sortedFriendUserIds.add(friend.getUserId())); + } } } @@ -191,7 +297,16 @@ return myLatestActivityLiveData; } + /** + * ユーザIDからそのユーザーのアクティビティのリストのライブデータを取得する + * + * @param userId 取得対象のユーザーID + * @return 取得対象のユーザーのアクティビティのリストのライブデータ + */ public MutableLiveData> getActivitiesLiveDataFromUserId(String userId) { + if (!friendToActivitiesLiveData.containsKey(userId)) { + friendToActivitiesLiveData.put(userId, new MutableLiveData<>()); + } return friendToActivitiesLiveData.get(userId); } @@ -199,11 +314,54 @@ return friendUserIdsLiveData; } - public String getUserId() { - return userId; + public String getMyUserId() { + return myUserId; } - public String getToken() { - return token; + public String getMyToken() { + return myToken; + } + + public List getSortedFriendUserIds() { + return sortedFriendUserIds; + } + + public void addActivityStatusChangeObserver(UserActivityStatusChangeListener observer) { + userActivityStatusChangeListeners.add(observer); + } + + public void clearActivityStatusChangeObservers() { + userActivityStatusChangeListeners.clear(); + } + + private static class Friend { + private final String userId; + private final LocalDateTime latestUpdateTime; + + public Friend(String userId, String latestUpdateTime) { + this.userId = userId; + this.latestUpdateTime = getDateTimeFromString(latestUpdateTime); + } + + public String getUserId() { + return userId; + } + + public LocalDateTime getLatestUpdateTime() { + return latestUpdateTime; + } + + public static class UpdateTimeComparator implements Comparator { + @Override + public int compare(Friend o1, Friend o2) { + return o1.getLatestUpdateTime().compareTo(o2.getLatestUpdateTime()); + } + } + } + + private interface ActivityFetchCallback { + void onSuccess(Activity activity); + + void onFailure(Throwable throwable); } } diff --git a/app/src/main/java/com/example/tampopo_client/viewmodels/ChatViewModel.java b/app/src/main/java/com/example/tampopo_client/viewmodels/ChatViewModel.java index 0c5a721..1cf6c18 100644 --- a/app/src/main/java/com/example/tampopo_client/viewmodels/ChatViewModel.java +++ b/app/src/main/java/com/example/tampopo_client/viewmodels/ChatViewModel.java @@ -3,46 +3,264 @@ import android.os.Handler; import android.os.Looper; import android.util.Log; - +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; +import com.example.tampopo_client.models.ChatMessage; +import com.example.tampopo_client.models.Chatroom; +import com.example.tampopo_client.resources.ChatroomResource; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.jackson.JacksonConverterFactory; public class ChatViewModel extends RealTimeViewModel { + + private final Retrofit retrofit; + private final ChatroomResource chatroomResource; + + // --- LiveData --- + private final MutableLiveData chatroomIdLiveData = new MutableLiveData<>(); + private final MutableLiveData> chatMessages = new MutableLiveData<>(new ArrayList<>()); + private final MutableLiveData latestMessage = new MutableLiveData<>(); + private final MutableLiveData chatroomClosed = new MutableLiveData<>(); + private final HashMap> chatFriendToFriendLiveData = new HashMap<>(); + + // 通話中(リアルタイム監視用) + private final MutableLiveData chatFriendToMeLiveData = new MutableLiveData<>(); + private final MutableLiveData chatToFriendLiveData = new MutableLiveData<>(); + + // ===== 以下は追加部分(Handlerループ) ===== + private final Handler handler = new Handler(Looper.getMainLooper()); + private boolean isChecking = false; // 二重起動防止フラグ + + + // =============================== + // 通知・リアルタイム関連 + // =============================== private final List notificationListeners = new ArrayList<>(); + private boolean notificationSent = false; // 一度だけ通知を送るためのフラグ - //private static final double NOTIFICATION_RECEIVE_PROBABILITY = 0.5; + private String userId; + private String token; + private String chatroomId; - //natty - private boolean notificationSent = false; // 一度だけ送信するためのフラグ + public ChatViewModel() { + this.retrofit = new Retrofit.Builder() + .baseUrl("http://nitta-lab-www.is.konan-u.ac.jp/tampopo/") + .addConverterFactory(JacksonConverterFactory.create()) + .build(); + + this.chatroomResource = retrofit.create(ChatroomResource.class); + } + + public ChatViewModel(String userId, String token,String chatroomId) { + this.userId = userId; + this.token = token; + this.chatroomId = chatroomId; -// @Override -// public Runnable onUpdate() { -// return () -> { -// // 1% の確率で onNotificationReceived() が呼び出される -// double borderValue = Math.floor(Math.random() * 100); -// double currentValue = NOTIFICATION_RECEIVE_PROBABILITY * 100; -// if (currentValue >= borderValue) { -// Log.d("ChatViewModel", "Received test notification."); -// notificationListeners.forEach(listener -> listener.onNotificationReceived()); -// } -// }; -// } - //natty + this.retrofit = new Retrofit.Builder() + .baseUrl("http://nitta-lab-www.is.konan-u.ac.jp/tampopo/") + .addConverterFactory(JacksonConverterFactory.create()) + .build(); + + this.chatroomResource = retrofit.create(ChatroomResource.class); + } + @Override public Runnable onUpdate() { return () -> { if (!notificationSent) { - // 10秒後に一回だけ通知を送る - notificationSent = true; // もう送らない + notificationSent = true; new Handler(Looper.getMainLooper()).postDelayed(() -> { Log.d("ChatViewModel", "Received test notification (after 10s)."); notificationListeners.forEach(NotificationListener::onNotificationReceived); - }, 10_000); // 10秒(10000ms) + }, 10_000); // 10秒後に通知 } + foundChatroom(userId); + loadLatestMessage(chatroomId, userId, token); }; } + + // getter + public MutableLiveData getChatroomIdLiveData() { return chatroomIdLiveData; } + public MutableLiveData> getChatMessages() { return chatMessages; } + public MutableLiveData getLatestMessageLiveData() { return latestMessage; } + public MutableLiveData getChatroomClosed() { return chatroomClosed; } + public MutableLiveData getChatFriendToMeLiveData() { return chatFriendToMeLiveData; } + public MutableLiveData getChatToFriendLiveData() { return chatToFriendLiveData; } + + // =============================== + // 1. チャットルームに入る(かける側) + // =============================== + public void enterChatroom(String myId, String partnerId, String token) { + Call call = chatroomResource.enterChatroom(myId, partnerId, token); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + chatroomIdLiveData.setValue(response.body().getChatroomId()); + Log.d("ChatVM", "enterChatroom success → chatroomId: " + response.body().getChatroomId()); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Log.e("ChatVM", "enterChatroom error: " + t.getMessage()); + } + }); + } + + // 1.5 自分がchatroomに入っているのか確認する + public void foundChatroom(String userId) { + Call call = chatroomResource.getMyChatroom(userId); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + Chatroom chatroom = response.body(); + + chatroomIdLiveData.setValue(chatroom.getChatroomId()); + chatFriendToMeLiveData.setValue(chatroom.getPartnerUserId()); + } else { + chatroomIdLiveData.setValue(null); + chatroomClosed.setValue(true); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Log.e("ChatroomVM", "checkCurrentChatroom error: " + t.getMessage()); + } + }); + } + + // =============================== + // 1.55 現在通話中のペアを確認 + // =============================== + public void fetchActiveChatPair(String userId, String token) { + Call call = chatroomResource.getMyChatroom(userId); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + Chatroom activeRoom = response.body(); + chatroomIdLiveData.setValue(activeRoom.getChatroomId()); + + String user1 = activeRoom.getUser1Id(); + String user2 = activeRoom.getUser2Id(); + + // ログインユーザーが user1 の場合 + if (userId.equals(user1)) { + chatFriendToMeLiveData.setValue(user2); // もう一方のユーザーを LiveData にセット + // HashMap にもセット + chatFriendToFriendLiveData.put(userId, new MutableLiveData<>(user2)); + } + // ログインユーザーが user2 の場合 + else if (userId.equals(user2)) { + chatToFriendLiveData.setValue(user1); // もう一方のユーザーを LiveData にセット + chatFriendToFriendLiveData.put(userId, new MutableLiveData<>(user1)); + } + + Log.d("ChatVM", "Active pair found: " + user1 + " ↔ " + user2); + } else { + chatroomIdLiveData.setValue(null); + chatroomClosed.setValue(true); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Log.e("ChatVM", "fetchActiveChatPair error: " + t.getMessage()); + } + }); + } + + + // =============================== + // 2. メッセージ送信 + // =============================== + public void sendMessage(String chatroomId, String senderId, String message, String token) { + Call call = chatroomResource.sendMessage(token, chatroomId, senderId, message); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + ChatMessage newMessage = response.body(); + latestMessage.setValue(newMessage); + } + } + + + @Override + public void onFailure(Call call, Throwable t) { + Log.e("ChatVM", "sendMessage error: " + t.getMessage()); + } + }); + } + + // =============================== + // 3. 最新メッセージ取得 + // =============================== + public void loadLatestMessage(String chatroomId, String userId, String token) { + Call> call = chatroomResource.getMessages(token, chatroomId, userId); + call.enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + if (response.isSuccessful() && response.body() != null && !response.body().isEmpty()) { + latestMessage.setValue(response.body().get(response.body().size() - 1)); + } + } + + @Override + public void onFailure(Call> call, Throwable t) { + Log.e("ChatVM", "loadLatestMessage error: " + t.getMessage()); + } + }); + } + + // =============================== + // 4. チャットルーム削除 + // =============================== + public void destroyChatroom(String chatroomId, String userId, String token) { + Call call = chatroomResource.destroyChatroom(token, chatroomId, userId); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + chatroomClosed.setValue(true); + if (!response.isSuccessful()) { + Log.e("ChatVM", "destroyChatroom failed: " + response.code()); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + chatroomClosed.setValue(true); + Log.e("ChatVM", "destroyChatroom error: " + t.getMessage()); + } + }); + } + + // ===== ここから Handlerループ部分をそのまま追記 ===== + /** + * 10秒ごとにサーバー確認を行う + */ + private void startServerCheck() { + handler.postDelayed(new Runnable() { + @Override + public void run() { + Log.d("ChatViewModel", "Checking server / Received test notification."); + notificationListeners.forEach(NotificationListener::onNotificationReceived); + handler.postDelayed(this, 10_000); + } + }, 10_000); + } + public void addNotificationListener(NotificationListener listener) { notificationListeners.add(listener); } @@ -50,4 +268,12 @@ public void clearNotificationListener() { notificationListeners.clear(); } + + /** + * 定期確認を止めたい場合に使う + */ + public void stopServerCheck() { + handler.removeCallbacksAndMessages(null); + isChecking = false; + } } diff --git a/app/src/main/java/com/example/tampopo_client/viewmodels/ChatViewModelFactory.java b/app/src/main/java/com/example/tampopo_client/viewmodels/ChatViewModelFactory.java new file mode 100644 index 0000000..48d5ce0 --- /dev/null +++ b/app/src/main/java/com/example/tampopo_client/viewmodels/ChatViewModelFactory.java @@ -0,0 +1,34 @@ +package com.example.tampopo_client.viewmodels; + +import androidx.annotation.NonNull; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; + +import java.lang.reflect.InvocationTargetException; + +public class ChatViewModelFactory implements ViewModelProvider.Factory { + private final String userId; + private final String token; + private final String chatroomId; + + public ChatViewModelFactory(String userId, String token, String chatroomId) { + this.userId = userId; + this.token = token; + this.chatroomId = chatroomId; + } + + @NonNull + @Override + public T create(@NonNull Class modelClass) { + if (modelClass.isAssignableFrom(ChatViewModel.class)) { + try { + return modelClass.getConstructor(String.class, String.class).newInstance(userId, token); + } catch (InvocationTargetException | IllegalAccessException | InstantiationException | + NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + throw new IllegalStateException("作成するViewModelが異なります。"); + } +} + diff --git a/app/src/main/java/com/example/tampopo_client/viewmodels/ChatroomViewModel.java b/app/src/main/java/com/example/tampopo_client/viewmodels/ChatroomViewModel.java deleted file mode 100644 index 901b43c..0000000 --- a/app/src/main/java/com/example/tampopo_client/viewmodels/ChatroomViewModel.java +++ /dev/null @@ -1,180 +0,0 @@ -package com.example.tampopo_client.viewmodels; - -import android.util.Log; - -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.ViewModel; - -import com.example.tampopo_client.models.ChatMessage; -import com.example.tampopo_client.models.Chatroom; -import com.example.tampopo_client.resources.ChatroomResource; - -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; - -public class ChatroomViewModel extends ViewModel { - - private final Retrofit retrofit; - private final ChatroomResource chatroomResource; - - private final MutableLiveData chatroomId = new MutableLiveData<>(); - private final MutableLiveData> chatMessages = new MutableLiveData<>(new ArrayList<>()); - private final MutableLiveData latestMessage = new MutableLiveData<>(); - private final MutableLiveData chatroomClosed = new MutableLiveData<>(); - - public ChatroomViewModel() { - this.retrofit = new Retrofit.Builder() - .baseUrl("http://nitta-lab-www.is.konan-u.ac.jp/tampopo/") - .addConverterFactory(JacksonConverterFactory.create()) - .build(); - - this.chatroomResource = retrofit.create(ChatroomResource.class); - } - - // LiveData getter - public MutableLiveData getChatroomIdLiveData() { - return chatroomId; - } - - public MutableLiveData> getChatMessages() { - return chatMessages; - } - - public MutableLiveData getLatestMessageLiveData() { - return latestMessage; - } - - public MutableLiveData getChatroomClosed() { - return chatroomClosed; - } - - // 1. チャットルームに入る(入れる側) - public void enterChatroom(String myId, String partnerId, String token) { - //自分のIdと相手のidと自分tokenを送る - Call call = chatroomResource.enterChatroom(myId, partnerId, token); - call.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful() && response.body() != null) { - chatroomId.setValue(response.body().getChatroomId()); - } - }//サーバーからchatroomIdが送られてきてそれを保持する処理 - - @Override - public void onFailure(Call call, Throwable t) { - Log.e("ChatViewModel", "enterChatroom error: " + t.getMessage()); - } - }); - } - - //1.5自分がcahtroomに入っているのかを確認する - public void foundChatroom(String userId) { - Call call = chatroomResource.getMyChatroom(userId); - call.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful() && response.body() != null) { - chatroomId.setValue(response.body().getChatroomId()); - //これがあっているのか確認chatroomIdどんなうごきをするのか確認せよ - } else { - chatroomId.setValue(null); // どこにも入っていない場合 - chatroomClosed.setValue(true); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - Log.e("ChatroomVM", "checkCurrentChatroom error: " + t.getMessage()); - } - }); - } - - // 2. メッセージ送信(リアルタイム風)送る側 - //自分のchatはmychatmesaageに変更するかも - public void sendMessage(String chatroomId, String senderId, String content, String token) { - //このメソッドは 指定したチャットルーム (chatroomId) に、あるユーザー (senderId) が、入力したメッセージ (message) を、認証トークン (token) を使って送信する役割です。 - Call call = chatroomResource.sendMessage(token, chatroomId, senderId, content); - //Retrofit を使って 「サーバーのAPIにメッセージ送信リクエストを作る」 メソッド。 - call.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful() && response.body() != null) { - ChatMessage newMessage = response.body(); - // 成功したらそのまま新しいメッセージをUIに流す - -// // 既存のメッセージリストを更新 - //受け取る側のコードっぽいので不要だと思ってます理由はnewMessageでUIを表示するのでそれ以外いらない -// //画面に表示されているメッセージリスト (chatMessages) を取り出し、そこに 新しいメッセージを追加して更新しています。 -// List current = chatMessages.getValue(); -// if (current == null) current = new ArrayList<>(); - - - // operationResult は表示不要なので更新しない - } - // 失敗した場合も「エラーです」とは出さない(履歴を残さない仕様) - } - - @Override - public void onFailure(Call call, Throwable t) { - // ネットワークエラーでも通知はしない(UI上に履歴が残らない通話風チャットだから) - // 必要なら内部ログにだけ残す - Log.e("ChatViewModel", "sendMessage error: " + t.getMessage()); - } - }); - } - - - // 3. 最新メッセージ取得(エラー時はUIに表示しない)受け取る側 - public void loadLatestMessage(String chatroomId, String partnerId, String token) { - Call> call = chatroomResource.getMessages(token, chatroomId, partnerId); - call.enqueue(new Callback>() { - @Override - public void onResponse(Call> call, Response> response) { - if (response.isSuccessful() && response.body() != null && !response.body().isEmpty()) { - // 最新メッセージだけ反映 - latestMessage.setValue(response.body().get(response.body().size() - 1)); - } - // else の場合は何もしない(UIにエラーを出さない) - } - - @Override - public void onFailure(Call> call, Throwable t) { - // ネットワークエラー時もUIには表示しない - Log.e("ChatViewModel", "loadLatestMessage error: " + t.getMessage()); - } - }); - } - - - // 4. チャットルーム削除 削除を実行した人の処理。削除された側の処理は含まれてません - public void destroyChatroom(String chatroomId, String userId, String token) { - Call call = chatroomResource.destroyChatroom(token, chatroomId, userId); - call.enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - // 成功でも失敗でも、画面を閉じる処理をUIに通知 - // 例えば LiveData を使って Activity/Fragment に伝える - chatroomClosed.setValue(true); - - // ログには残す - if (!response.isSuccessful()) { - Log.e("ChatViewModel", "destroyChatroom failed: " + response.code()); - } - } - - @Override - public void onFailure(Call call, Throwable t) { - // ネットワークエラーでも UI は閉じる - chatroomClosed.setValue(true); - Log.e("ChatViewModel", "destroyChatroom error: " + t.getMessage()); - } - }); - } -} - diff --git a/app/src/main/java/com/example/tampopo_client/viewmodels/FriendReceivedRequestViewModel.java b/app/src/main/java/com/example/tampopo_client/viewmodels/FriendReceivedRequestViewModel.java index 0b01c76..2fe45a8 100644 --- a/app/src/main/java/com/example/tampopo_client/viewmodels/FriendReceivedRequestViewModel.java +++ b/app/src/main/java/com/example/tampopo_client/viewmodels/FriendReceivedRequestViewModel.java @@ -17,58 +17,80 @@ import retrofit2.converter.scalars.ScalarsConverterFactory; public class FriendReceivedRequestViewModel extends ViewModel { - //サーバー(API)と通信するためのツール + // サーバー(API)と通信するためのツール private final Retrofit retrofit; - //APIの窓口 + // API の窓口 private final FriendRequestsResource friendRequestsResource; - //自分が受け取った申請 + // 自分が受け取った申請 private final MutableLiveData> receivedRequests; - //通信結果の状態 + // 通信結果の状態 private final MutableLiveData operationResult; - public FriendReceivedRequestViewModel() { + // 直近で使用したユーザーIDをキャッシュしておく + private String cachedUserId; + private String cachedToken; + public FriendReceivedRequestViewModel() { this.retrofit = new Retrofit.Builder() .baseUrl("http://nitta-lab-www.is.konan-u.ac.jp/tampopo/") .addConverterFactory(ScalarsConverterFactory.create()) .addConverterFactory(JacksonConverterFactory.create()) .build(); + this.friendRequestsResource = retrofit.create(FriendRequestsResource.class); this.receivedRequests = new MutableLiveData<>(new ArrayList<>()); this.operationResult = new MutableLiveData<>(); } - //viewがobserve出来るように + // viewがobserve出来るように public MutableLiveData> getReceivedRequestsLiveData() { return receivedRequests; } - //サーバーから受け取ったFriendReceivedRequestのデータを格納してキャッシュしていくぞ - public void loadReceivedRequests(String token) { - //tokenを渡して、受信フレンド申請一覧を取得するHTTPリクエスト(Webのサーバーに対して何かをお願いするメッセージ」)を作る準備をしている + public MutableLiveData getOperationResultLiveData() { + return operationResult; + } + + // サーバーから受け取った FriendRequest のデータを読み込む + public void loadReceivedRequests(String token, String myUserId) { + // キャッシュ保存(削除後の再読み込み用) + this.cachedUserId = myUserId; + this.cachedToken = token; + Call> call = friendRequestsResource.getFriendRequests(token); - //call.enqueueでサーバーへ送信(何を?) call.enqueue(new Callback>() { @Override public void onResponse(Call> call, Response> response) { if (response.isSuccessful()) { - //通信が成功したらLiveDataへのキャッシュ - receivedRequests.setValue(response.body()); - operationResult.setValue("Success"); - System.out.println("Success SetValue" + response.body()); + List allRequests = response.body(); + if (allRequests == null) allRequests = new ArrayList<>(); + + // ✅ 自分が受け取ったものだけを抽出 + List receivedOnly = new ArrayList<>(); + for (FriendRequest req : allRequests) { + if (req.getReceiverId() != null && req.getReceiverId().equals(myUserId)) { + receivedOnly.add(req); + } + } + + receivedRequests.setValue(receivedOnly); + operationResult.setValue("Success (filtered " + receivedOnly.size() + " requests)"); + System.out.println("Success SetValue (filtered): " + receivedOnly); + } else { operationResult.setValue("Error: " + response.code()); - System.out.println("response error"); + System.out.println("Response error: " + response.code()); } } @Override public void onFailure(Call> call, Throwable t) { operationResult.setValue("Network error: " + t.getMessage()); - System.out.println("ネットワークエラー: " + t); + System.out.println("Network error: " + t); } }); } + // フレンドリクエスト削除メソッド public void deleteFriendRequest(String friendRequestId, String token) { Call call = friendRequestsResource.deleteFriendRequest(friendRequestId, token); @@ -78,8 +100,13 @@ public void onResponse(Call call, Response response) { if (response.isSuccessful()) { operationResult.setValue("Friend request deleted successfully."); - loadReceivedRequests(token); // 削除後、一覧を更新 System.out.println("Deleted friend request ID: " + friendRequestId); + + // ✅ キャッシュがある場合は再読み込み + if (cachedToken != null && cachedUserId != null) { + loadReceivedRequests(cachedToken, cachedUserId); + } + } else { operationResult.setValue("Error deleting request: " + response.code()); System.out.println("Error deleting request: " + response.code()); @@ -97,3 +124,4 @@ + diff --git a/app/src/main/java/com/example/tampopo_client/viewmodels/FriendViewModel.java b/app/src/main/java/com/example/tampopo_client/viewmodels/FriendViewModel.java index 91b8021..6e958c5 100644 --- a/app/src/main/java/com/example/tampopo_client/viewmodels/FriendViewModel.java +++ b/app/src/main/java/com/example/tampopo_client/viewmodels/FriendViewModel.java @@ -45,7 +45,8 @@ } public void loadFriends(String userId, String token){ - Call> call = usersResource.getFriends(userId, token); + //userResourceではなくfriendResourceに書き換え + Call> call = friendsResource.getFriends(userId, token); call.enqueue(new Callback>() { @Override diff --git a/app/src/main/java/com/example/tampopo_client/viewmodels/UserActivityStatusChangeListener.java b/app/src/main/java/com/example/tampopo_client/viewmodels/UserActivityStatusChangeListener.java new file mode 100644 index 0000000..30724cc --- /dev/null +++ b/app/src/main/java/com/example/tampopo_client/viewmodels/UserActivityStatusChangeListener.java @@ -0,0 +1,10 @@ +package com.example.tampopo_client.viewmodels; + +public interface UserActivityStatusChangeListener { + void onUserStatusChanged(String userId, Status activityStatus); + + enum Status { + INACTIVE, + ACTIVE + } +} diff --git a/app/src/main/java/com/example/tampopo_client/viewmodels/UserViewModel.java b/app/src/main/java/com/example/tampopo_client/viewmodels/UserViewModel.java index e7c7920..4bd21e4 100644 --- a/app/src/main/java/com/example/tampopo_client/viewmodels/UserViewModel.java +++ b/app/src/main/java/com/example/tampopo_client/viewmodels/UserViewModel.java @@ -7,6 +7,8 @@ import com.example.tampopo_client.models.User; import com.example.tampopo_client.resources.UserResource; +import java.io.IOException; + import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; @@ -43,8 +45,10 @@ private static UserResource userResource; private static final MutableLiveData user = new MutableLiveData<>(); private final MutableLiveData token = new MutableLiveData<>(); - private static final MutableLiveDataloading = new MutableLiveData<>(false); - private static final MutableLiveData error = new MutableLiveData<>(); + private final MutableLiveData icon = new MutableLiveData<>(); + private final MutableLiveDataloading = new MutableLiveData<>(false); + private final MutableLiveData error = new MutableLiveData<>(); + //コンストラクタ public UserViewModel(){ @@ -58,6 +62,7 @@ public LiveData getUser() { return user; } public LiveData getToken() { return token; } + public LiveData getIcon() { return icon; } public LiveDataisLoading() { return loading;} public static LiveData getError() { return error; } @@ -103,6 +108,75 @@ } }); } + //ニックネーム + public String getNickname(String id) { + Call call = userResource.getName(id); + try { + Response response = call.execute(); + + if (response.isSuccessful()) { + System.out.println(response.code()); + return response.body(); + } else { + System.out.println(response.code()); + return null; + } + } catch (IOException e) { + throw new RuntimeException(e); + } +// + } + + //アイコン + public String getIcon(String id) { + Call call = userResource.getIcon(id); + try { + Response response = call.execute(); + + if (response.isSuccessful()) { + System.out.println(response.code()); + return response.body(); + } else { + System.out.println(response.code()); + return null; + } + } catch (IOException e) { + throw new RuntimeException(e); + } +// call.enqueue(new Callback() { +// @Override +// public void onResponse(Call call, Response response) { +// if (response.isSuccessful()) { +// icon.setValue(response.body()); +// System.out.println(response.code()); +// } else { +// System.out.println(response.code()); +// } +// } +// @Override public void onFailure(Call call, Throwable t) { +// System.out.println("エラー: " + t.getMessage()); +// } +// }); + } + + public void updateIcon(String id, String newIcon, String token) { + Call call = userResource.updateIcon(id, newIcon, token); + call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + icon.setValue(response.body()); + System.out.println(response.code()); + + } else { + System.out.println(response.code()); + } + } + @Override public void onFailure(Call call, Throwable t) { + System.out.println("エラー: " + t.getMessage()); + } + }); + } //ニックネームの変更(市井) public void updateName(String userId, String newName, String tokenValue) { diff --git a/app/src/main/java/com/example/tampopo_client/views/ChatActivity.java b/app/src/main/java/com/example/tampopo_client/views/ChatActivity.java index c88c250..3e43680 100644 --- a/app/src/main/java/com/example/tampopo_client/views/ChatActivity.java +++ b/app/src/main/java/com/example/tampopo_client/views/ChatActivity.java @@ -1,10 +1,13 @@ package com.example.tampopo_client.views; +import android.content.Intent; import android.os.Bundle; +import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; +import android.widget.ImageButton; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; @@ -13,19 +16,22 @@ import androidx.lifecycle.ViewModelProvider; import com.example.tampopo_client.R; import com.example.tampopo_client.Tampopo; -import com.example.tampopo_client.viewmodels.ChatroomViewModel; +import com.example.tampopo_client.viewmodels.ChatViewModel; public class ChatActivity extends AppCompatActivity { - private ChatroomViewModel chatroomViewModel; + private ChatViewModel chatroomViewModel; //メンバー変数 private EditText senderMessage; private EditText receiverMessage; - private Button buttonSend; + private Button sendButton; + private Button backButton; Tampopo tampopo; + + @Override protected void onCreate(Bundle savedInstanceState) { tampopo = (Tampopo)getApplication(); @@ -34,11 +40,12 @@ EdgeToEdge.enable(this);//画面の端っこまで使う(Edge-to-Edge)表示 setContentView(R.layout.activity_chat);//表示する画面のレイアウトXMLファイル(activity_chat.xml)を指定 - chatroomViewModel = new ViewModelProvider(this).get(ChatroomViewModel.class); + chatroomViewModel = new ViewModelProvider(this).get(ChatViewModel.class); senderMessage = findViewById(R.id.sender_message); receiverMessage = findViewById(R.id.receiver_message); - buttonSend = findViewById(R.id.buttonSend); + sendButton = findViewById(R.id.send_Button); + ImageButton backButton = findViewById(R.id.back_Button); receiverMessage.setKeyListener(null);//受信メッセージ編集不可 @@ -56,32 +63,68 @@ // } // }); -// String receiverText = senderMessage.getText().toString();//senderMessageにユーザーが入力した文字を取り出して、String型の 変数receiverText に入れる -// if (!receiverText.isEmpty()) { -// receiverMessage.setText(ChatroomViewModel.latestMessage); -// } + Intent intent = getIntent(); + String friendId = intent.getStringExtra("friendId"); + String chatroomId = intent.getStringExtra("chatroomId"); - buttonSend.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - String senderText = senderMessage.getText().toString();//senderMessageにユーザーが入力した文字を取り出して、String型の 変数receiverText に入れる - if (!senderText.isEmpty()) { - String senderId = tampopo.getUserId(); - String token = tampopo.getToken(); - String chatroomId = tampopo.getChatroomId(); - chatroomViewModel.sendMessage(chatroomId, senderId, senderText, token); - senderMessage.setText(senderText);//送信欄に表示 - } - } - }); + Log.d("ChatDebug", "チャット相手ID: " + friendId); + Log.d("ChatDebug", "チャットルームID: " + chatroomId); - chatroomViewModel.getLatestMessageLiveData().observe(this, receiverText -> { - if (receiverText != null) { - receiverMessage.setText(receiverText.getContent()); + String myId = tampopo.getUserId(); + String token = tampopo.getToken(); + chatroomViewModel.enterChatroom(myId, friendId, token); + + if (friendId == null || friendId.isEmpty()) { + Log.e("ChatDebug","Error: friendIdがnullです"); + } + + chatroomViewModel.getChatroomIdLiveData().observe(this, id -> { + if (id != null) { + tampopo.setChatroomId(id); + System.out.println("チャットルーム作成完了! ID: " + id); + } else { + System.out.println("チャットルーム作成に失敗しました"); } }); + //メッセージ送信 + sendButton.setOnClickListener(new OnClickListener() { //sendButtonがクリックされたときの処理 + @Override + public void onClick(View view) { //ボタンがクリックされたときに実行される処理本体 + String senderText = senderMessage.getText().toString(); //senderMessageにユーザーが入力した文字を取り出して、String型の 変数receiverText に入れる + if (!senderText.isEmpty()) { //senderTextが空でなければ + String senderId = tampopo.getUserId(); + String token = tampopo.getToken(); + String chatroomId = tampopo.getChatroomId(); + chatroomViewModel.sendMessage(chatroomId, senderId, senderText, token); + senderMessage.setText(senderText);//自分のメッセージを送信欄に表示 + } + } + }); + + + //メッセージ受信 + chatroomViewModel.getLatestMessageLiveData().observe(this, chatMessage -> { + if (chatMessage != null) { + String myUserId = tampopo.getUserId(); // 自分のIDを取得 + String senderId = chatMessage.getSenderId(); //相手(メッセージ送信者)のIDを取得 + + if (!myUserId.equals(senderId)) { //自分のIDが相手のIDと異なっていれば,(相手からのメッセージと判断) + receiverMessage.setText(chatMessage.getContent());// 相手からのメッセージを受信欄に表示 + } + } + }); + + + //戻るボタン + backButton.setOnClickListener(new OnClickListener() { //backButtonがクリックされたときの処理 + public void onClick(View view) { //ボタンがクリックされたときに実行される処理本体 + Intent intent = new Intent(ChatActivity.this, MainActivity.class); //ChatActivity から MainActivity に画面遷移 + startActivity(intent); + } + }); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(Type.systemBars()); diff --git a/app/src/main/java/com/example/tampopo_client/views/FriendIconView.java b/app/src/main/java/com/example/tampopo_client/views/FriendIconView.java index 93b6087..696407e 100644 --- a/app/src/main/java/com/example/tampopo_client/views/FriendIconView.java +++ b/app/src/main/java/com/example/tampopo_client/views/FriendIconView.java @@ -1,73 +1,101 @@ package com.example.tampopo_client.views; -import static androidx.core.content.ContextCompat.startActivity; - +import android.app.Dialog; import android.content.Context; import android.content.Intent; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.drawable.Drawable; -import android.text.TextPaint; import android.util.AttributeSet; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; +import android.widget.Button; import android.widget.FrameLayout; -import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; +import android.widget.Toast; -import androidx.activity.EdgeToEdge; import androidx.lifecycle.Observer; import com.bumptech.glide.Glide; import com.example.tampopo_client.R; +import com.example.tampopo_client.Tampopo; import com.example.tampopo_client.models.Activity; -import com.example.tampopo_client.viewmodels.ActivityViewModel; +import com.example.tampopo_client.viewmodels.ChatViewModel; import com.google.android.material.imageview.ShapeableImageView; import java.util.List; -import java.util.Map; -/** - * TODO: document your custom view class. - */ -public class FriendIconView extends FrameLayout implements Observer>{ - private String mExampleString; // TODO: use a default from R.string... -// private int mExampleColor = Color.RED; // TODO: use a default from R.color... - private float mExampleDimension = 0; // TODO: use a default from R.dimen... - private Drawable mExampleDrawable; - - private TextPaint mTextPaint; - private float mTextWidth; - private float mTextHeight; +public class FriendIconView extends FrameLayout { private ShapeableImageView mFriendIcon; private ImageView mFriendCommentImage; private TextView mFriendComment; private TextView mFriendNickname; private ImageView mFriendChatNotification; private boolean chatNotification = false; - private Observer> activityObserver; - String friendActivity = "123"; + private String friendActivity; + private String friendUserId; + private ChatViewModel chatViewModel; + + // TODO: CHANGE + //ActivityLiveData + private final Observer> activitiesObserver = new Observer>() { + @Override + public void onChanged(List activityList) { + if (activityList != null && !activityList.isEmpty()) { + Activity act = activityList.get(0); + friendActivity = act.getText(); + setComment(act.getUserId()); + } + } + }; + + //userLiveData + private final Observer userObserver = new Observer() { + @Override + public void onChanged(String user) { + } + }; + + //chatLiveData + private final Observer chatObserver = new Observer() { + @Override + public void onChanged(String chat) { + } + }; + + public Observer> getActivitiesObserver() { + return activitiesObserver; + } + + public Observer getChatObserver() { + return chatObserver; + } + + public FriendIconView(Context context, String friendUserId, ChatViewModel chatViewModel) { + this(context); + this.friendUserId = friendUserId; + this.chatViewModel = chatViewModel; + init(null, 0); + } + public FriendIconView(Context context) { super(context); + this.friendUserId = null; init(null, 0); } public FriendIconView(Context context, AttributeSet attrs) { super(context, attrs); + this.friendUserId = null; init(attrs, 0); } public FriendIconView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + this.friendUserId = null; init(attrs, defStyle); } private void init(AttributeSet attrs, int defStyle) { - LayoutInflater.from(getContext()).inflate(R.layout.sample_friend_icon_view,this,true); + LayoutInflater.from(getContext()).inflate(R.layout.sample_friend_icon_view, this, true); mFriendCommentImage = findViewById(R.id.friend_comment_image); mFriendComment = findViewById(R.id.friend_comment); mFriendNickname = findViewById(R.id.friend_nickname); @@ -75,64 +103,64 @@ mFriendChatNotification = findViewById(R.id.chat_notification); //true(チャット通知が来た時)なら表示 - if(chatNotification){ + if (chatNotification) { mFriendChatNotification.setVisibility(View.VISIBLE); } //false(チャット通知が来ていない)なら非表示 - else{ + else { mFriendChatNotification.setVisibility(View.GONE); } //iconを押したらチャットを始めることができる(チャットのダイアログができたらFriendActivityを変更) mFriendIcon.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { - Context ctx = getContext(); - Intent intent = new Intent(ctx, ChatActivity. class); - ctx.startActivity(intent); + if (chatNotification) { + Context ctx = getContext(); + Intent intent = new Intent(ctx, ChatActivity.class); + ctx.startActivity(intent); + mFriendChatNotification.setVisibility(View.GONE); + } else { + // 通話をかける場合 + showCallRequestDialog(getContext(), FriendIconView.this.friendUserId); + } } }); -// if(attrs != null){ -// TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.FriendIconView, defStyle, 0); -// String nickname = a.getString(R.styleable.FriendIconView_nickname); -// if(nickname != null){ -// setNickname(nickname); -// } -// Drawable iconDrawable = a.getDrawable(R.styleable.FriendIconView_iconSrc); -// if(iconDrawable != null){ -// mFriendIcon.setImageDrawable(iconDrawable); -// } -// a.recycle(); -// } + } - // Load attributes -// final TypedArray a = getContext().obtainStyledAttributes( -// attrs, R.styleable.FriendIconView, defStyle, 0); -// -// mExampleString = a.getString( -// R.styleable.FriendIconView_exampleString); -// mExampleColor = a.getColor( -// R.styleable.FriendIconView_exampleColor, -// mExampleColor); -// // Use getDimensionPixelSize or getDimensionPixelOffset when dealing with -// // values that should fall on pixel boundaries. -// mExampleDimension = a.getDimension( -// R.styleable.FriendIconView_exampleDimension, -// mExampleDimension); -// -// if (a.hasValue(R.styleable.FriendIconView_exampleDrawable)) { -// mExampleDrawable = a.getDrawable( -// R.styleable.FriendIconView_exampleDrawable); -// mExampleDrawable.setCallback(this); -// } + public void showCallRequestDialog(Context context, String fromUserName) { + Dialog dialog = new Dialog(context); + dialog.setContentView(R.layout.dialog_chat_receved); + dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent); + dialog.getWindow().setDimAmount(0.5f); -// // Set up a default TextPaint object -// mTextPaint = new TextPaint(); -// mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG); -// mTextPaint.setTextAlign(Paint.Align.LEFT); -// -// // Update TextPaint and text measurements from attributes -// invalidateTextPaintAndMeasurements(); -// + // メッセージテキスト + TextView tvMessage = dialog.findViewById(R.id.tv_message); + tvMessage.setText(fromUserName + " さんと通話を開始しますか?"); + + // 開始ボタン + Button btnStart = dialog.findViewById(R.id.btn_start); + btnStart.setOnClickListener(v -> { + Toast.makeText(context, "通話を開始しました", Toast.LENGTH_SHORT).show(); + dialog.dismiss(); + + // 通話を開始する + Tampopo tampopo = (Tampopo) ((MainActivity) getContext()).getApplication(); +// String chatroomId = chatViewModel.enterChatroom(tampopo.getUserId(), friendUserId, tampopo.getToken()); + + // ChatActivityに画面遷移する + Context ctx = getContext(); + Intent intent = new Intent(ctx, ChatActivity.class); + intent.putExtra("friendId", friendUserId); +// intent.putExtra("chatroomId", chatroomId); + + ctx.startActivity(intent); + }); + + // キャンセルボタン + Button btnCancel = dialog.findViewById(R.id.btn_cancel); + btnCancel.setOnClickListener(v -> dialog.dismiss()); + + dialog.show(); } public void setNickname(String nickname) { @@ -147,11 +175,12 @@ } } - public ImageView getImageView(){ + public ImageView getImageView() { return mFriendIcon; } - public void setComment(String comment){ - if(mFriendComment != null){ + + public void setComment(String comment) { + if (mFriendComment != null) { int comment_length = comment.length(); if (comment_length > 20) { mFriendComment.setTextSize(7); @@ -166,156 +195,46 @@ // public void setActivityLiveDataObserver(String uid, ActivityViewModel viewModel){ // activityObserver = new Observer>() { - @Override - public void onChanged(List activityList) { - if(activityList != null && !activityList.isEmpty()){ - Activity act = activityList.get(0); - friendActivity = act.getText(); - setAccount(act.getUserId()); - } - } + + // TODO: CHANGE +// @Override +// public void onChanged(List activityList) { +// if (activityList != null && !activityList.isEmpty()) { +// Activity act = activityList.get(0); +// friendActivity = act.getText(); +// setAccount(act.getUserId()); +// } +// } // }; // viewModel.getActivitiesLiveDataFromUserId(uid).observeForever(activityObserver); // } - //uidを引数にカスタムビューにニックネーム,コメント,アイコンをセットする - public void setAccount(String uid){ - setNickname("haru"); - setComment((friendActivity)); - String imageUrl = "http://nitta-lab-www.is.konan-u.ac.jp/tampopo-data/icon" + uid + ".jpg"; - Glide.with(getContext()) - .load(imageUrl) - .into(getImageView()); + /** + * FriendIconViewのアイコン画像を更新する + * + * @param iconUrl アイコン画像のURL + */ + public void setIconUrl(String iconUrl) { + Glide.with(getContext()).load(iconUrl).into(getImageView()); } - public void setChatNotification(boolean chat){ + + //uidを引数にカスタムビューにニックネーム,コメント,アイコンをセットする +// public void setAccount(String uid, String iconUrl) { +// this.userId = uid; +// setNickname("nitta"); +// //String imageUrl = "http://nitta-lab-www.is.konan-u.ac.jp/tampopo-data/icon" + uid + ".jpg"; +// Glide.with(getContext()).load(iconUrl).into(getImageView()); +// } + + public void setChatNotification(boolean chat) { //チャットを終了するときのonclickで一緒にsetChatNotification(false)もする(アイコン周りの赤丸を消す) //true(チャット通知が来た時)なら表示 - if(chat){ + if (chat) { mFriendChatNotification.setVisibility(View.VISIBLE); } //false(チャット通知が来ていない)なら非表示 - else{ + else { mFriendChatNotification.setVisibility(View.GONE); } } -// private void invalidateTextPaintAndMeasurements() { -// mTextPaint.setTextSize(mExampleDimension); -// mTextPaint.setColor(mExampleColor); -// mTextWidth = mTextPaint.measureText(mExampleString); -// -// Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics(); -// mTextHeight = fontMetrics.bottom; -// } - -// @Override -// protected void onDraw(Canvas canvas) { -// super.onDraw(canvas); -// -// // TODO: consider storing these as member variables to reduce -// // allocations per draw cycle. -// int paddingLeft = getPaddingLeft(); -// int paddingTop = getPaddingTop(); -// int paddingRight = getPaddingRight(); -// int paddingBottom = getPaddingBottom(); -// -// int contentWidth = getWidth() - paddingLeft - paddingRight; -// int contentHeight = getHeight() - paddingTop - paddingBottom; -// -// // Draw the text. -// canvas.drawText(mExampleString, -// paddingLeft + (contentWidth - mTextWidth) / 2, -// paddingTop + (contentHeight + mTextHeight) / 2, -// mTextPaint); -// -// // Draw the example drawable on top of the text. -// if (mExampleDrawable != null) { -// mExampleDrawable.setBounds(paddingLeft, paddingTop, -// paddingLeft + contentWidth, paddingTop + contentHeight); -// mExampleDrawable.draw(canvas); -// } -// } - - /** - * Gets the example string attribute value. - * - * @return The example string attribute value. - */ - public String getExampleString() { - return mExampleString; - } - - /** - * Sets the view"s example string attribute value. In the example view, this string - * is the text to draw. - * - * @param exampleString The example string attribute value to use. - */ -// public void setExampleString(String exampleString) { -// mExampleString = exampleString; -// invalidateTextPaintAndMeasurements(); -// } - - /** - * Gets the example color attribute value. - * - * @return The example color attribute value. - */ -// public int getExampleColor() { -// return mExampleColor; -// } - - /** - * Sets the view"s example color attribute value. In the example view, this color - * is the font color. - * - * @param exampleColor The example color attribute value to use. - */ -// public void setExampleColor(int exampleColor) { -// mExampleColor = exampleColor; -// invalidateTextPaintAndMeasurements(); -// } - - /** - * Gets the example dimension attribute value. - * - * @return The example dimension attribute value. - */ - public float getExampleDimension() { - return mExampleDimension; - } - - /** - * Sets the view"s example dimension attribute value. In the example view, this dimension - * is the font size. - * - * @param exampleDimension The example dimension attribute value to use. - */ -// public void setExampleDimension(float exampleDimension) { -// mExampleDimension = exampleDimension; -// invalidateTextPaintAndMeasurements(); -// } - - /** - * Gets the example drawable attribute value. - * - * @return The example drawable attribute value. - */ - public Drawable getExampleDrawable() { - return mExampleDrawable; - } - - /** - * Sets the view"s example drawable attribute value. In the example view, this drawable is - * drawn above the text. - * - * @param exampleDrawable The example drawable attribute value to use. - */ - public void setExampleDrawable(Drawable exampleDrawable) { - mExampleDrawable = exampleDrawable; - } - -// @Override -// public void onChanged(List activityList) { -// activityList.get(0). -// } } diff --git a/app/src/main/java/com/example/tampopo_client/views/FriendListFragment.java b/app/src/main/java/com/example/tampopo_client/views/FriendListFragment.java index 722c7b7..17730c5 100644 --- a/app/src/main/java/com/example/tampopo_client/views/FriendListFragment.java +++ b/app/src/main/java/com/example/tampopo_client/views/FriendListFragment.java @@ -31,6 +31,8 @@ // TODO: Customize parameters private int mColumnCount = 1; + private MyFriendRecyclerViewAdapter adapter; + /** * Mandatory empty constructor for the fragment manager to instantiate the * fragment (e.g. upon screen orientation changes). @@ -39,7 +41,15 @@ } // TODO: Customize parameter initialization - @SuppressWarnings("unused") +// @SuppressWarnings("unused") +// public static FriendListFragment newInstance(int columnCount) { +// FriendListFragment fragment = new FriendListFragment(); +// Bundle args = new Bundle(); +// args.putInt(ARG_COLUMN_COUNT, columnCount); +// fragment.setArguments(args); +// return fragment; +// } + public static FriendListFragment newInstance(int columnCount) { FriendListFragment fragment = new FriendListFragment(); Bundle args = new Bundle(); @@ -57,48 +67,113 @@ } } +// @Override +// public View onCreateView(LayoutInflater inflater, ViewGroup container, +// Bundle savedInstanceState) { +// View view = inflater.inflate(R.layout.fragment_friend_list_list, container, false); +// +// RecyclerView recyclerView = view.findViewById(R.id.list); +// Context context = view.getContext(); +// +// // Add some sample items. +// //for (int i = 1; i <= 30; i++) { +// // FriendContent.addItem(new FriendContent.FriendItem(Integer.toString(i), "ユーザ" + i)); +// //} +// +// // Set the adapter +// 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)); +// } +// recyclerView.setAdapter(new MyFriendRecyclerViewAdapter(FriendContent.ITEMS)); +// } +// adapter = new MyFriendRecyclerViewAdapter(FriendContent.ITEMS); +// recyclerView.setAdapter(adapter); +// return view; +// } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_friend_list_list, container, false); - // Add some sample items. - //for (int i = 1; i <= 30; i++) { - // FriendContent.addItem(new FriendContent.FriendItem(Integer.toString(i), "ユーザ" + i)); - //} + RecyclerView recyclerView = view.findViewById(R.id.list); + Context context = view.getContext(); - // Set the adapter - 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)); - } - recyclerView.setAdapter(new MyFriendRecyclerViewAdapter(FriendContent.ITEMS)); + if (mColumnCount <= 1) { + recyclerView.setLayoutManager(new LinearLayoutManager(context)); + } else { + recyclerView.setLayoutManager(new GridLayoutManager(context, mColumnCount)); } + + // adapterをここで初期化 + adapter = new MyFriendRecyclerViewAdapter(FriendContent.ITEMS); + recyclerView.setAdapter(adapter); + return view; } + +// @Override +// public void onViewCreated(View view, Bundle savedInstanceState) { +// super.onViewCreated(view, savedInstanceState); +// FriendViewModel friendViewModel = new ViewModelProvider(this).get(FriendViewModel.class); +// +// String userId = ((Tampopo) ((FriendActivity) view.getContext()).getApplication()).getUserId; +// String token = ((Tampopo) ((FriendActivity) view.getContext()).getApplication()).getToken; +// friendViewModel.loadFriends(userId, token); +// +// friendViewModel.getFriendIdsLiveData().observe(getViewLifecycleOwner(), new Observer>() { +// +// @Override +// public void onChanged(List friendIds) { +// for (String i : friendIds) { +// FriendContent.addItem(new FriendContent.FriendItem(i, "")); +// } +// adapter.notifyDataSetChanged(); +// } +// }); +// } + @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); FriendViewModel friendViewModel = new ViewModelProvider(this).get(FriendViewModel.class); - String userId = ((Tampopo) ((FriendActivity) view.getContext()).getApplication()).getUserId(); - String token = ((Tampopo) ((FriendActivity) view.getContext()).getApplication()).getToken(); +//自分の情報を取得する + Tampopo app = (Tampopo) requireActivity().getApplication(); + String userId = app.getUserId(); + String token = app.getToken(); + +//サーバーからフレンド一覧を取得する friendViewModel.loadFriends(userId, token); - friendViewModel.getFriendIdsLiveData().observe(getViewLifecycleOwner(), new Observer>() { - - @Override - public void onChanged(List friendIds) { - for (String i : friendIds) { - FriendContent.addItem(new FriendContent.FriendItem(i, "")); +//一旦旧コードをコメントアウトしました。 +// friendViewModel.getFriendIdsLiveData().observe(getViewLifecycleOwner(), new Observer>() { +// @Override +// public void onChanged(List friendIds) { +// // 一度リセットして新しいリストを追加 +// FriendContent.clear(); +// for (String id : friendIds) { +// FriendContent.addItem(new FriendContent.FriendItem(id, "")); +// } +// // adapterがクラス変数なのでここで使える +// adapter.notifyDataSetChanged(); +// } +// }); + friendViewModel.getFriendIdsLiveData().observe(getViewLifecycleOwner(), friendIds -> { + FriendContent.clear(); + if (friendIds != null) { + for (String id : friendIds) { + FriendContent.addItem(new FriendContent.FriendItem(id, "")); } } + adapter.notifyDataSetChanged(); }); } } \ No newline at end of file diff --git a/app/src/main/java/com/example/tampopo_client/views/FriendReceivedFragment.java b/app/src/main/java/com/example/tampopo_client/views/FriendReceivedFragment.java index 20e07a1..ddfc0bd 100644 --- a/app/src/main/java/com/example/tampopo_client/views/FriendReceivedFragment.java +++ b/app/src/main/java/com/example/tampopo_client/views/FriendReceivedFragment.java @@ -108,7 +108,8 @@ // 受信した友達申請を管理する ViewModel を取得 FriendReceivedRequestViewModel friendReceivedRequestViewModel = new ViewModelProvider(this).get(FriendReceivedRequestViewModel.class); // サーバーから受信した友達リクエスト一覧をロード - friendReceivedRequestViewModel.loadReceivedRequests(tampopo.getToken()); + //tampopo.getUserIdを追加しました + friendReceivedRequestViewModel.loadReceivedRequests(tampopo.getToken(),tampopo.getUserId()); // LiveData を監視して、データが変わったら RecyclerView に反映 friendReceivedRequestViewModel.getReceivedRequestsLiveData().observe(getViewLifecycleOwner(), new Observer>() { diff --git a/app/src/main/java/com/example/tampopo_client/views/MainActivity.java b/app/src/main/java/com/example/tampopo_client/views/MainActivity.java index 29af578..f2fa84a 100644 --- a/app/src/main/java/com/example/tampopo_client/views/MainActivity.java +++ b/app/src/main/java/com/example/tampopo_client/views/MainActivity.java @@ -39,6 +39,8 @@ import com.example.tampopo_client.Tampopo; import com.example.tampopo_client.models.Activity; import com.example.tampopo_client.viewmodels.ActivityViewModel; +import com.example.tampopo_client.viewmodels.ChatViewModelFactory; +import com.example.tampopo_client.viewmodels.UserViewModel; import com.google.android.material.imageview.ShapeableImageView; import com.example.tampopo_client.viewmodels.ActivityViewModelFactory; import com.example.tampopo_client.viewmodels.ChatViewModel; @@ -62,9 +64,13 @@ private Map userViews = new HashMap<>(); ActivityViewModel activityViewModel; + UserViewModel userViewModel; Tampopo tampopo; + //追加しました! private ChatViewModel chatViewModel; + private final List recentUpdatedFriends = new ArrayList<>(); // 最新6人 + @Override @@ -92,6 +98,10 @@ } } + // TODO: CHANGE + userViewModel = new ViewModelProvider(this).get(UserViewModel.class); + + // handleIncomingIntent(getIntent()); //メイン画面からフレンド一覧画面への遷移 ImageButton friendButton = (ImageButton) findViewById(R.id.friend); @@ -109,9 +119,10 @@ ActivityViewModelFactory factory = new ActivityViewModelFactory(tampopo.getUserId(), tampopo.getToken()); // Factoryを使って、引数をコンストラクタにわたしつつViewModelを作成 activityViewModel = new ViewModelProvider(this, factory).get(ActivityViewModel.class); + //追加しました!!!!!!!!!!! // ChatViewModelを初期化する - chatViewModel = new ViewModelProvider(this).get(ChatViewModel.class); - + ChatViewModelFactory factory1 = new ChatViewModelFactory(tampopo.getUserId(), tampopo.getToken(), tampopo.getChatroomId()); + chatViewModel = new ViewModelProvider(this, factory1).get(ChatViewModel.class); // MutableLiveData>friendUserIdsLiveDate = activityViewModel.getFriendUserIdsLiveData(); // friendUserIdsLiveDate.observe(this, new Observer>() { @@ -125,6 +136,7 @@ // }); MutableLiveData> friendsLiveData = activityViewModel.getFriendUserIdsLiveData(); + friendsLiveData.observe(this, new Observer>() { @Override public void onChanged(List friends) { @@ -134,14 +146,59 @@ }); for (String friendId: userViews.keySet()) { - MutableLiveData> activitiesLiveData = activityViewModel.getActivitiesLiveDataFromUserId(friendId); - activitiesLiveData.observe(this, new Observer>() { + final String updateFriendId = friendId; + MutableLiveData> activitiesLiveData = activityViewModel.getActivitiesLiveDataFromUserId(updateFriendId); + //MutableLiveData> activitiesLiveData = activityViewModel.getActivitiesLiveDataFromUserId(updateFriendId); + + FriendIconView friendView = userViews.get(friendId); + if(friendView == null) continue; + //activitiesLiveData.observeForever(friendView); + activitiesLiveData.observeForever(new Observer>() { @Override public void onChanged(List activities) { // 更新したフレンドの再登場,更新してないフレンドの退場」 + //更新した人を見つけてFriendIconViewを呼び出して、 + FriendIconView userView = userViews.get(updateFriendId); + if (userView != null && activities != null && !activities.isEmpty()) { + Activity latest = activities.get(activities.size() - 1); + userView.setComment(latest.getText()); + } + //アイコンとニックネーム情報をとってくる + new Thread(() -> { + String nickname = userViewModel.getNickname(updateFriendId); + String iconUrl = userViewModel.getIcon(updateFriendId); + + runOnUiThread(() -> { + if (nickname != null && !nickname.isEmpty()) { + userView.setNickname(nickname); + } else { + userView.setNickname(updateFriendId); + } + +// if (iconUrl != null && !iconUrl.isEmpty()) { +// userView.setIconUrl(iconUrl); +// } else { +// userView.setIconUrl("http://nitta-lab-www.is.konan-u.ac.jp/tampopo/images/default_icon.png"); +// } + }); + }).start(); + //フレンドの位置決め + // 最新更新フレンドをリストに追加(最大6人保持) + synchronized (recentUpdatedFriends) { + // すでに存在する場合は削除して再追加(重複防止) + recentUpdatedFriends.remove(updateFriendId); + // 先頭に追加(最近更新した人ほど前) + recentUpdatedFriends.add(0, updateFriendId); + + // 6人を超えたら古いものを削除 + if (recentUpdatedFriends.size() > 6) { + recentUpdatedFriends.remove(recentUpdatedFriends.size() - 1); + } + } + } }); - activitiesLiveData.observe(this, userViews.get(friendId)); + //activitiesLiveData.observe(this, userViews.get(friendId)); } //メイン画面から通知一覧画面への遷移 @@ -307,6 +364,8 @@ // Activity activity = entry; FriendIconView userView = userViews.get(friendId); + MutableLiveData> activitiesLiveData = activityViewModel.getActivitiesLiveDataFromUserId(friendId); + if (userView == null) { // 新しいユーザなので、アイコン+コメントを作成 @@ -342,6 +401,9 @@ userViews.put(friendId, container); messageList.addView(container); + // TODO: CHANGE + activitiesLiveData.observeForever(container.getActivitiesObserver()); + // } else { // // 既に表示されている → コメントだけ更新 // TextView commentView = (TextView) ((LinearLayout) userView).getChildAt(1); @@ -510,13 +572,14 @@ dialog.show(); } - +//通知を受信したときのダイアログ これが動いてます + //friendName+から通話があります。ってでるから通知が来たときのフレンドを変数に置く必要がある @Override public void onNotificationReceived() { // 通知を受信したときにダイアログを表示 - runOnUiThread(() -> showChatNotification("user01")); + runOnUiThread(() -> showChatNotification("user02")); // アイコンを赤枠に - runOnUiThread(() -> highlightUserIcon("user01")); + //runOnUiThread(() -> highlightUserIcon("user01")); } // @Override // protected void onNewIntent(Intent intent) { 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 5786821..bdde5e5 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 @@ -28,3 +28,4 @@ return 2; } } + diff --git a/app/src/main/java/com/example/tampopo_client/views/MyFriendRecyclerViewAdapter.java b/app/src/main/java/com/example/tampopo_client/views/MyFriendRecyclerViewAdapter.java index 22ef86b..e2a231d 100644 --- a/app/src/main/java/com/example/tampopo_client/views/MyFriendRecyclerViewAdapter.java +++ b/app/src/main/java/com/example/tampopo_client/views/MyFriendRecyclerViewAdapter.java @@ -1,40 +1,105 @@ +//旧コードは念の為コメントアウトしています。 +//package com.example.tampopo_client.views; +// +//import android.view.LayoutInflater; +//import android.view.ViewGroup; +//import android.widget.TextView; +// +//import com.example.tampopo_client.databinding.FragmentFriendListBinding; +//import com.example.tampopo_client.views.placeholder.FriendContent; +// +//import java.util.List; +// +//import androidx.recyclerview.widget.RecyclerView; +// +///** +// * {@link RecyclerView.Adapter} that can display a {@link FriendContent.FriendItem}. +// * TODO: Replace the implementation with code for your data type. +// */ +//public class MyFriendRecyclerViewAdapter extends RecyclerView.Adapter { +// +// private final List mValues; +// +// public MyFriendRecyclerViewAdapter(List items) { +// mValues = items; +// } +// +// @Override +// public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { +// +// return new ViewHolder(FragmentFriendListBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); +// +// } +// +// @Override +// public void onBindViewHolder(final ViewHolder holder, int position) { +// holder.mItem = mValues.get(position); +// //holder.mIdView.setText(mValues.get(position).id); +// holder.mContentView.setText(mValues.get(position).name); +// } +// +// @Override +// public int getItemCount() { +// return mValues.size(); +// } +// +// public class ViewHolder extends RecyclerView.ViewHolder { +// //public final TextView mIdView; +// public final TextView mContentView; +// public FriendContent.FriendItem mItem; +// +// public ViewHolder(FragmentFriendListBinding binding) { +// super(binding.getRoot()); +// //mIdView = binding.itemNumber; +// mContentView = binding.content; +// } +// +// @Override +// public String toString() { +// return super.toString() + " '" + mContentView.getText() + "'"; +// } +// } +//} + package com.example.tampopo_client.views; import android.view.LayoutInflater; +import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.TextView; -import com.example.tampopo_client.databinding.FragmentFriendListBinding; -import com.example.tampopo_client.views.placeholder.FriendContent; - -import java.util.List; - import androidx.recyclerview.widget.RecyclerView; -/** - * {@link RecyclerView.Adapter} that can display a {@link FriendContent.FriendItem}. - * TODO: Replace the implementation with code for your data type. - */ +import com.example.tampopo_client.R; +import com.example.tampopo_client.views.placeholder.FriendContent.FriendItem; + +import java.util.List; + public class MyFriendRecyclerViewAdapter extends RecyclerView.Adapter { - private final List mValues; + private final List mValues; - public MyFriendRecyclerViewAdapter(List items) { + public MyFriendRecyclerViewAdapter(List items) { mValues = items; } + //各行のビューを作成する @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - - return new ViewHolder(FragmentFriendListBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); - + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.fragment_friend_list, parent, false); + return new ViewHolder(view); } + + //データをビューに紐付ける @Override public void onBindViewHolder(final ViewHolder holder, int position) { - holder.mItem = mValues.get(position); - //holder.mIdView.setText(mValues.get(position).id); - holder.mContentView.setText(mValues.get(position).name); + FriendItem item = mValues.get(position); + holder.userIdView.setText(item.id); + holder.contentView.setText(item.name); + holder.userIcon.setImageResource(R.drawable.friend); } @Override @@ -43,19 +108,15 @@ } public class ViewHolder extends RecyclerView.ViewHolder { - //public final TextView mIdView; - public final TextView mContentView; - public FriendContent.FriendItem mItem; + public final ImageView userIcon; + public final TextView contentView; + public final TextView userIdView; - public ViewHolder(FragmentFriendListBinding binding) { - super(binding.getRoot()); - //mIdView = binding.itemNumber; - mContentView = binding.content; - } - - @Override - public String toString() { - return super.toString() + " '" + mContentView.getText() + "'"; + public ViewHolder(View view) { + super(view); + userIcon = view.findViewById(R.id.usericon); + contentView = view.findViewById(R.id.content); + userIdView = view.findViewById(R.id.userId); } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/example/tampopo_client/views/TestFriendIconActivity.java b/app/src/main/java/com/example/tampopo_client/views/TestFriendIconActivity.java index b70cc11..06c2cc3 100644 --- a/app/src/main/java/com/example/tampopo_client/views/TestFriendIconActivity.java +++ b/app/src/main/java/com/example/tampopo_client/views/TestFriendIconActivity.java @@ -33,26 +33,28 @@ float density = getResources().getDisplayMetrics().density; //FriendIconView 1個目 + String uid = "haru"; FriendIconView view1 = new FriendIconView(this); + //view1.setAccount(uid); FrameLayout.LayoutParams params1 = new FrameLayout.LayoutParams( - (int) (160*density), // width in px - (int) (100*density) // height in px + (int) (250*density), // width in px + (int) (250*density) // height in px ); view1.setLayoutParams(params1); - view1.setX(100); // px単位 + view1.setX(130); // px単位 view1.setY(150); rootLayout.addView(view1); //FriendIconView 2個目 FriendIconView view2 = new FriendIconView(this); FrameLayout.LayoutParams params2 = new FrameLayout.LayoutParams( - (int) (160*density), - (int) (100*density) + (int) (250*density), + (int) (250*density) ); - view1.setLayoutParams(params2); - view1.setX(100); - view1.setY(150); - rootLayout.addView(view1); + view2.setLayoutParams(params2); + view2.setX(200); + view2.setY(600); + rootLayout.addView(view2); setContentView(rootLayout); // diff --git a/app/src/main/java/com/example/tampopo_client/views/placeholder/FriendContent.java b/app/src/main/java/com/example/tampopo_client/views/placeholder/FriendContent.java index 3c48f8e..f399d6f 100644 --- a/app/src/main/java/com/example/tampopo_client/views/placeholder/FriendContent.java +++ b/app/src/main/java/com/example/tampopo_client/views/placeholder/FriendContent.java @@ -40,6 +40,11 @@ ITEM_MAP.put(item.id, item); } + public static void clear() { + ITEMS.clear(); + ITEM_MAP.clear(); + } + //private static FriendItem createPlaceholderItem(int position) { // return new FriendItem(String.valueOf(position), "ユーザー名 " , makeDetails(position)); diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml index ec31135..9a43176 100644 --- a/app/src/main/res/layout/activity_chat.xml +++ b/app/src/main/res/layout/activity_chat.xml @@ -41,7 +41,7 @@ app:layout_constraintVertical_bias="0.55" />