diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index b268ef3..55829cb 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -4,6 +4,14 @@ 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 3d15caf..3cc9160 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 @@ -17,8 +17,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.SortedSet; import java.util.TreeSet; +import java.util.stream.Collectors; import retrofit2.Call; import retrofit2.Callback; @@ -26,19 +28,14 @@ import retrofit2.Retrofit; import retrofit2.converter.jackson.JacksonConverterFactory; -/** - * アクティビティを扱うためのViewModel - * - * @author Shohei Yamagiwa - * @implNote Repositoryは作成せずに、すべてViewModelで処理する - */ public class ActivityViewModel extends RealTimeViewModel { private final ActivitiesResource activitiesResource; private final UserResource userResource; - private final MutableLiveData myLatestActivityLiveData; // 自分の最新のアクティビティ - private final Map>> friendToActivitiesLiveData; // <フレンドのユーザーID, フレンドのアクティビティのリスト> - private final MutableLiveData> friendUserIdsLiveData; // フレンドのユーザーIDのリスト + private final MutableLiveData myLatestActivityLiveData; + private final Map>> friendToActivitiesLiveData; + private final MutableLiveData> friendUserIdsLiveData; + private final MutableLiveData> allFriendsLatestActivitiesLiveData; private final String myUserId; private final String myToken; @@ -47,17 +44,10 @@ 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); @@ -65,6 +55,7 @@ friendToActivitiesLiveData = new HashMap<>(); friendUserIdsLiveData = new MutableLiveData<>(List.of()); myLatestActivityLiveData = new MutableLiveData<>(null); + allFriendsLatestActivitiesLiveData = new MutableLiveData<>(new ArrayList<>()); userActivityStatusChangeListeners = new ArrayList<>(); } @@ -76,28 +67,9 @@ 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); } @@ -107,13 +79,20 @@ } }); - // 自分のフレンドの最新のアクティビティを取得して更新する - if (friendUserIdsLiveData.isInitialized() && friendUserIdsLiveData.getValue() != null) { + if (friendUserIdsLiveData.getValue() != null) { for (String userId : friendUserIdsLiveData.getValue()) { pullLatestActivity(userId, activitiesResource, new ActivityFetchCallback() { @Override public void onSuccess(Activity activity) { - updateFriendToActivitiesLiveData(activity, userId); + List currentActivities = allFriendsLatestActivitiesLiveData.getValue(); + if (currentActivities == null) { + currentActivities = new ArrayList<>(); + } + // 既存のリストから同じユーザーIDのアクティビティを削除 + currentActivities.removeIf(a -> a.getUserId().equals(activity.getUserId())); + // 新しいアクティビティを追加 + currentActivities.add(activity); + allFriendsLatestActivitiesLiveData.postValue(currentActivities); } @Override @@ -124,47 +103,19 @@ } } - if (friendUserIdsLiveData.isInitialized()) { - // 最新のフレンドのユーザーIDを取得して更新する - // TODO: 適切なコールバックを使う必要あり - pullLatestFriendUserIds(myUserId, myToken); - } + pullLatestFriendUserIds(myUserId, myToken); }; } - /** - * ユーザ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; - + MutableLiveData> userActivitiesLiveData = friendToActivitiesLiveData.computeIfAbsent(userId, k -> new MutableLiveData<>(new ArrayList<>())); List userActivities = userActivitiesLiveData.getValue(); - if (userActivities == null || userActivities.isEmpty()) { - userActivitiesLiveData.postValue(List.of(latestActivity)); - } else { - if (userActivities.equals(List.of(latestActivity))) { - return; - } - + if (userActivities == null || userActivities.isEmpty() || !userActivities.get(0).equals(latestActivity)) { 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; @@ -176,16 +127,16 @@ public void onResponse(@NonNull Call call, @NonNull Response response) { if (response.isSuccessful()) { String createdActivityId = response.body(); + if (createdActivityId == null) return; Call getActivityCall = activitiesResource.getActivity(userId, createdActivityId); getActivityCall.enqueue(new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) { Activity createdActivity = response.body(); - if (createdActivity == null) { - return; + if (createdActivity != null) { + myLatestActivityLiveData.postValue(createdActivity); } - myLatestActivityLiveData.postValue(createdActivity); } @Override @@ -205,83 +156,40 @@ }); } - /** - * 最新のユーザーのアクティビティを取得・更新する - * - * @param userId 取得対象のユーザーのID - * @param resource アクティビティのリソース - * @param 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) { - if (response.isSuccessful()) { - Collection activities = response.body().values(); // アクティビティが存在しない場合は空のリスト - if (activities == null || activities.isEmpty()) { - return; + if (response.isSuccessful() && response.body() != null) { + Collection activities = response.body().values(); + if (!activities.isEmpty()) { + callback.onSuccess(activities.iterator().next()); } - - Activity latestActivity = activities.iterator().next(); - callback.onSuccess(latestActivity); } } @Override 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 - * @param token 自分のユーザー認証用トークン - */ private void pullLatestFriendUserIds(String userId, String token) { Call> fetchFriendUserIdsCall = userResource.getFriends(userId, token); fetchFriendUserIdsCall.enqueue(new Callback>() { @Override public void onResponse(@NonNull Call> call, @NonNull Response> response) { - if (response.isSuccessful()) { - - // MEMO: 入れ込んだだけ - // フレンドのIDをアクティビティ更新順に並べ替える - List friendUserIds = friendUserIdsLiveData.getValue(); - if (friendUserIds == null || !friendUserIds.equals(response.body())) { + if (response.isSuccessful() && response.body() != null) { + if (!Objects.equals(friendUserIdsLiveData.getValue(), 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())); } } } @@ -297,23 +205,18 @@ 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); + return friendToActivitiesLiveData.computeIfAbsent(userId, k -> new MutableLiveData<>()); } public MutableLiveData> getFriendUserIdsLiveData() { return friendUserIdsLiveData; } + public MutableLiveData> getAllFriendsLatestActivitiesLiveData() { + return allFriendsLatestActivitiesLiveData; + } + public String getMyUserId() { return myUserId; } 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 f6b4111..3a854e6 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 @@ -44,10 +44,7 @@ import com.google.android.material.imageview.ShapeableImageView; import com.example.tampopo_client.viewmodels.ActivityViewModelFactory; import com.example.tampopo_client.viewmodels.ChatViewModel; -import com.example.tampopo_client.viewmodels.ChatViewModelFactory; import com.example.tampopo_client.viewmodels.NotificationListener; -import com.example.tampopo_client.viewmodels.UserViewModel; -import com.google.android.material.imageview.ShapeableImageView; import java.util.ArrayList; import java.util.HashMap; @@ -133,52 +130,13 @@ ChatViewModelFactory factory1 = new ChatViewModelFactory(tampopo.getUserId(), tampopo.getToken(), tampopo.getChatroomId()); chatViewModel = new ViewModelProvider(this, factory1).get(ChatViewModel.class); - MutableLiveData> friendsLiveData = activityViewModel.getFriendUserIdsLiveData(); - - friendsLiveData.observe(this, new Observer>() { + activityViewModel.getAllFriendsLatestActivitiesLiveData().observe(this, new Observer>() { @Override - public void onChanged(List friends) { - // フレンドの追加と削除 - updateActivityView(friends); + public void onChanged(List activities) { + updateActivityView(activities); } }); - - for (String friendId : userViews.keySet()) { - 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); - } - //フレンドの位置決め - // 最新更新フレンドをリストに追加(最大6人保持) - synchronized (recentUpdatedFriends) { - // すでに存在する場合は削除して再追加(重複防止) - recentUpdatedFriends.remove(updateFriendId); - // 先頭に追加(最近更新した人ほど前) - recentUpdatedFriends.add(0, updateFriendId); - - // 6人を超えたら古いものを削除 - if (recentUpdatedFriends.size() > 6) { - recentUpdatedFriends.remove(recentUpdatedFriends.size() - 1); - } - } - - } - }); - } - //メイン画面から設定画面への遷移 ImageButton settingButton = (ImageButton) findViewById(R.id.setting); settingButton.setOnClickListener(new View.OnClickListener() { @@ -301,60 +259,32 @@ } } - private void updateActivityView(List friends) { - ///natty ユーザごとにコメントの更新をする - LinearLayout messageList = findViewById(R.id.messageList); + private void updateActivityView(List activities) { ConstraintLayout layout = findViewById(R.id.main); i = 0; - for (String friendId : friends) { - -// String friendId = entry.getUserId(); -// Activity activity = entry; + for (Activity activity : activities) { + String friendId = activity.getUserId(); FriendIconView userView = userViews.get(friendId); - MutableLiveData> activitiesLiveData = activityViewModel.getActivitiesLiveDataFromUserId(friendId); - if (userView == null) { - // 新しいユーザなので、アイコン+コメントを作成 - FriendIconView container = new FriendIconView(this); - container.setPadding(16, 16, 16, 16); - container.setId(View.generateViewId()); + userView = new FriendIconView(this, friendId, "", chatViewModel); + userView.setId(View.generateViewId()); ConstraintLayout.LayoutParams params = new ConstraintLayout.LayoutParams( ConstraintLayout.LayoutParams.WRAP_CONTENT, ConstraintLayout.LayoutParams.WRAP_CONTENT ); - container.setLayoutParams(params); + userView.setLayoutParams(params); - layout.addView(container); - -// // ユーザのアイコン(固定) -// ShapeableImageView iconView = new ShapeableImageView(this); -// iconView.setLayoutParams(new LinearLayout.LayoutParams(100, 100)); -// iconView.setScaleType(ImageView.ScaleType.CENTER_CROP); -// iconView.setStrokeColor(ContextCompat.getColorStateList(this, R.color.red)); -// iconView.setStrokeWidth(2f); -// iconView.setShapeAppearanceModel( -// iconView.getShapeAppearanceModel().toBuilder() -// .setAllCornerSizes(50) // 丸く -// .build() -// ); -// -// // ユーザIDに応じてアイコンリソースを決定(仮にハードコード or マッピング) -// iconView.setImageResource(getUserIconResource(friendId)); // ←ここがポイント - - // Mapに登録、画面に追加 - userViews.put(friendId, container); - userView = container; - //messageList.addView(container); - - // TODO: CHANGE - activitiesLiveData.observeForever(container.getActivitiesObserver()); + layout.addView(userView); + userViews.put(friendId, userView); } + userView.setComment(activity.getText()); + ConstraintSet set = new ConstraintSet(); set.clone(layout); int marginTopInPx = (int) TypedValue.applyDimension( @@ -373,7 +303,7 @@ set.applyTo(layout); - if(i<6){ + if(i < 6){ i++; } }