From d8d1de2f3525e7d0a499daf31d3bf425d40abbb1 Mon Sep 17 00:00:00 2001 From: Sowjanya Kota <101803542+sowjanyakch@users.noreply.github.com> Date: Fri, 19 Jan 2024 18:12:31 +0100 Subject: [PATCH 01/22] Checking Edit Capability Signed-off-by:Sowjanya Kota <101803542+sowjanyakch@users.noreply.github.com> --- .../java/com/nextcloud/talk/api/NcApi.java | 316 +++++------------- .../com/nextcloud/talk/chat/ChatActivity.kt | 3 + .../talk/ui/dialog/MessageActionsDialog.kt | 10 + .../res/layout/dialog_message_actions.xml | 34 ++ app/src/main/res/values/strings.xml | 2 + 5 files changed, 128 insertions(+), 237 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApi.java b/app/src/main/java/com/nextcloud/talk/api/NcApi.java index 674443ee6..f99d16557 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java +++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java @@ -24,6 +24,7 @@ package com.nextcloud.talk.api; import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall; +import com.nextcloud.talk.models.json.chat.ChatOCSSingleMessage; import com.nextcloud.talk.models.json.chat.ChatOverall; import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage; import com.nextcloud.talk.models.json.chat.ChatShareOverall; @@ -96,19 +97,14 @@ public interface NcApi { */ @GET - Observable getContactsWithSearchParam(@Header("Authorization") String authorization, - @Url String url, - @Nullable @Query("shareTypes[]") List listOfShareTypes, - @QueryMap Map options); + Observable getContactsWithSearchParam(@Header("Authorization") String authorization, @Url String url, @Nullable @Query("shareTypes[]") List listOfShareTypes, @QueryMap Map options); /* Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /room */ @GET - Observable getRooms(@Header("Authorization") String authorization, - @Url String url, - @Nullable @Query("includeStatus") Boolean includeStatus); + Observable getRooms(@Header("Authorization") String authorization, @Url String url, @Nullable @Query("includeStatus") Boolean includeStatus); /* Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /room/roomToken @@ -125,9 +121,7 @@ public interface NcApi { */ @POST - Observable createRoom(@Header("Authorization") String authorization, - @Url String url, - @QueryMap Map options); + Observable createRoom(@Header("Authorization") String authorization, @Url String url, @QueryMap Map options); /* QueryMap items are as follows: @@ -138,16 +132,12 @@ public interface NcApi { @FormUrlEncoded @PUT - Observable renameRoom(@Header("Authorization") String authorization, - @Url String url, - @Field("roomName") String roomName); + Observable renameRoom(@Header("Authorization") String authorization, @Url String url, @Field("roomName") String roomName); @FormUrlEncoded @PUT - Observable setConversationDescription(@Header("Authorization") String authorization, - @Url String url, - @Field("description") String description); + Observable setConversationDescription(@Header("Authorization") String authorization, @Url String url, @Field("description") String description); /* QueryMap items are as follows: @@ -156,48 +146,32 @@ public interface NcApi { Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /room/roomToken/participants */ @POST - Observable addParticipant(@Header("Authorization") String authorization, - @Url String url, - @QueryMap Map options); + Observable addParticipant(@Header("Authorization") String authorization, @Url String url, @QueryMap Map options); @POST - Observable resendParticipantInvitations(@Header("Authorization") String authorization, - @Url String url); + Observable resendParticipantInvitations(@Header("Authorization") String authorization, @Url String url); // also used for removing a guest from a conversation @Deprecated @DELETE - Observable removeParticipantFromConversation(@Header("Authorization") String authorization, - @Url String url, - @Query("participant") String participantId); + Observable removeParticipantFromConversation(@Header("Authorization") String authorization, @Url String url, @Query("participant") String participantId); @DELETE - Observable removeAttendeeFromConversation(@Header("Authorization") String authorization, - @Url String url, - @Query("attendeeId") Long attendeeId); + Observable removeAttendeeFromConversation(@Header("Authorization") String authorization, @Url String url, @Query("attendeeId") Long attendeeId); @Deprecated @POST - Observable promoteUserToModerator(@Header("Authorization") String authorization, - @Url String url, - @Query("participant") String participantId); + Observable promoteUserToModerator(@Header("Authorization") String authorization, @Url String url, @Query("participant") String participantId); @Deprecated @DELETE - Observable demoteModeratorToUser(@Header("Authorization") String authorization, - @Url String url, - @Query("participant") String participantId); + Observable demoteModeratorToUser(@Header("Authorization") String authorization, @Url String url, @Query("participant") String participantId); @POST - Observable promoteAttendeeToModerator(@Header("Authorization") String authorization, - @Url String url, - @Query("attendeeId") Long attendeeId); + Observable promoteAttendeeToModerator(@Header("Authorization") String authorization, @Url String url, @Query("attendeeId") Long attendeeId); @DELETE - Observable demoteAttendeeFromModerator(@Header("Authorization") String authorization, - @Url String url, - @Query("attendeeId") Long attendeeId); + Observable demoteAttendeeFromModerator(@Header("Authorization") String authorization, @Url String url, @Query("attendeeId") Long attendeeId); /* Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /room/roomToken/participants/self @@ -228,15 +202,11 @@ public interface NcApi { Observable getPeersForCall(@Header("Authorization") String authorization, @Url String url); @GET - Observable getPeersForCall(@Header("Authorization") String authorization, - @Url String url, - @QueryMap Map fields); + Observable getPeersForCall(@Header("Authorization") String authorization, @Url String url, @QueryMap Map fields); @FormUrlEncoded @POST - Observable joinRoom(@Nullable @Header("Authorization") String authorization, - @Url String url, - @Nullable @Field("password") String password); + Observable joinRoom(@Nullable @Header("Authorization") String authorization, @Url String url, @Nullable @Field("password") String password); @DELETE Observable leaveRoom(@Nullable @Header("Authorization") String authorization, @Url String url); @@ -247,11 +217,7 @@ public interface NcApi { @FormUrlEncoded @POST - Observable joinCall(@Nullable @Header("Authorization") String authorization, - @Url String url, - @Field("flags") Integer inCall, - @Field("silent") Boolean callWithoutNotification, - @Nullable @Field("recordingConsent") Boolean recordingConsent); + Observable joinCall(@Nullable @Header("Authorization") String authorization, @Url String url, @Field("flags") Integer inCall, @Field("silent") Boolean callWithoutNotification, @Nullable @Field("recordingConsent") Boolean recordingConsent); /* Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /call/callToken @@ -260,8 +226,7 @@ public interface NcApi { Observable leaveCall(@Nullable @Header("Authorization") String authorization, @Url String url); @GET - Observable getSignalingSettings(@Nullable @Header("Authorization") String authorization, - @Url String url); + Observable getSignalingSettings(@Nullable @Header("Authorization") String authorization, @Url String url); /* QueryMap items are as follows: @@ -271,16 +236,13 @@ public interface NcApi { */ @FormUrlEncoded @POST - Observable sendSignalingMessages(@Nullable @Header("Authorization") String authorization, - @Url String url, - @Field("messages") String messages); + Observable sendSignalingMessages(@Nullable @Header("Authorization") String authorization, @Url String url, @Field("messages") String messages); /* Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /signaling */ @GET - Observable pullSignalingMessages(@Nullable @Header("Authorization") String authorization, - @Url String url); + Observable pullSignalingMessages(@Nullable @Header("Authorization") String authorization, @Url String url); /* QueryMap items are as follows: @@ -298,11 +260,7 @@ public interface NcApi { @FormUrlEncoded @PUT - Observable setUserData(@Header("Authorization") String authorization, - @Url String url, - @Field("key") String key, - @Field("value") String value); - + Observable setUserData(@Header("Authorization") String authorization, @Url String url, @Field("key") String key, @Field("value") String value); /* Server URL is: baseUrl + /status.php @@ -322,21 +280,14 @@ public interface NcApi { */ @POST - Observable registerDeviceForNotificationsWithNextcloud( - @Header("Authorization") String authorization, - @Url String url, - @QueryMap Map options); + Observable registerDeviceForNotificationsWithNextcloud(@Header("Authorization") String authorization, @Url String url, @QueryMap Map options); @DELETE - Observable unregisterDeviceForNotificationsWithNextcloud( - @Header("Authorization") String authorization, - @Url String url); + Observable unregisterDeviceForNotificationsWithNextcloud(@Header("Authorization") String authorization, @Url String url); @FormUrlEncoded @POST - Observable registerDeviceForNotificationsWithPushProxy(@Url String url, - @FieldMap Map fields); - + Observable registerDeviceForNotificationsWithPushProxy(@Url String url, @FieldMap Map fields); /* QueryMap items are as follows: @@ -345,20 +296,15 @@ public interface NcApi { - "userPublicKey": "{{userPublicKey}}" */ @DELETE - Observable unregisterDeviceForNotificationsWithProxy(@Url String url, - @QueryMap Map fields); + Observable unregisterDeviceForNotificationsWithProxy(@Url String url, @QueryMap Map fields); @FormUrlEncoded @PUT - Observable setPassword(@Header("Authorization") String authorization, - @Url String url, - @Field("password") String password); + Observable setPassword(@Header("Authorization") String authorization, @Url String url, @Field("password") String password); @FormUrlEncoded @PUT - Observable> setPassword2(@Header("Authorization") String authorization, - @Url String url, - @Field("password") String password); + Observable> setPassword2(@Header("Authorization") String authorization, @Url String url, @Field("password") String password); @GET Observable getCapabilities(@Header("Authorization") String authorization, @Url String url); @@ -374,9 +320,7 @@ public interface NcApi { - "lastKnownMessageId", int, use one from X-Chat-Last-Given */ @GET - Observable> pullChatMessages(@Header("Authorization") String authorization, - @Url String url, - @QueryMap Map fields); + Observable> pullChatMessages(@Header("Authorization") String authorization, @Url String url, @QueryMap Map fields); /* Fieldmap items are as follows: @@ -386,107 +330,68 @@ public interface NcApi { @FormUrlEncoded @POST - Observable sendChatMessage(@Header("Authorization") String authorization, - @Url String url, - @Field("message") CharSequence message, - @Field("actorDisplayName") String actorDisplayName, - @Field("replyTo") Integer replyTo, - @Field("silent") Boolean sendWithoutNotification); + Observable sendChatMessage(@Header("Authorization") String authorization, @Url String url, @Field("message") CharSequence message, @Field("actorDisplayName") String actorDisplayName, @Field("replyTo") Integer replyTo, @Field("silent") Boolean sendWithoutNotification); + + @PUT + Observable editChatMessage(@Header("Authorization") String authorization, @Url String url, @Field("message") String message); @GET - Observable> getSharedItems( - @Header("Authorization") String authorization, - @Url String url, - @Query("objectType") String objectType, - @Nullable @Query("lastKnownMessageId") Integer lastKnownMessageId, - @Nullable @Query("limit") Integer limit); + Observable> getSharedItems(@Header("Authorization") String authorization, @Url String url, @Query("objectType") String objectType, @Nullable @Query("lastKnownMessageId") Integer lastKnownMessageId, @Nullable @Query("limit") Integer limit); @GET - Observable> getSharedItemsOverview(@Header("Authorization") String authorization, - @Url String url, - @Nullable @Query("limit") Integer limit); + Observable> getSharedItemsOverview(@Header("Authorization") String authorization, @Url String url, @Nullable @Query("limit") Integer limit); @GET - Observable getMentionAutocompleteSuggestions(@Header("Authorization") String authorization, - @Url String url, - @Query("search") String query, - @Nullable @Query("limit") Integer limit, - @QueryMap Map fields); + Observable getMentionAutocompleteSuggestions(@Header("Authorization") String authorization, @Url String url, @Query("search") String query, @Nullable @Query("limit") Integer limit, @QueryMap Map fields); // Url is: /api/{apiVersion}/room/{token}/pin @POST - Observable addConversationToFavorites(@Header("Authorization") String authorization, - @Url String url); + Observable addConversationToFavorites(@Header("Authorization") String authorization, @Url String url); // Url is: /api/{apiVersion}/room/{token}/favorites @DELETE - Observable removeConversationFromFavorites(@Header("Authorization") String authorization, - @Url String url); + Observable removeConversationFromFavorites(@Header("Authorization") String authorization, @Url String url); @GET - Observable getNcNotification(@Header("Authorization") String authorization, - @Url String url); + Observable getNcNotification(@Header("Authorization") String authorization, @Url String url); @FormUrlEncoded @POST - Observable setNotificationLevel(@Header("Authorization") String authorization, - @Url String url, - @Field("level") int level); + Observable setNotificationLevel(@Header("Authorization") String authorization, @Url String url, @Field("level") int level); @FormUrlEncoded @PUT - Observable setReadOnlyState(@Header("Authorization") String authorization, - @Url String url, - @Field("state") int state); + Observable setReadOnlyState(@Header("Authorization") String authorization, @Url String url, @Field("state") int state); @FormUrlEncoded @POST - Observable createRemoteShare(@Nullable @Header("Authorization") String authorization, - @Url String url, - @Field("path") String remotePath, - @Field("shareWith") String roomToken, - @Field("shareType") String shareType, - @Field("talkMetaData") String talkMetaData); + Observable createRemoteShare(@Nullable @Header("Authorization") String authorization, @Url String url, @Field("path") String remotePath, @Field("shareWith") String roomToken, @Field("shareType") String shareType, @Field("talkMetaData") String talkMetaData); @FormUrlEncoded @PUT - Observable setLobbyForConversation(@Header("Authorization") String authorization, - @Url String url, - @Field("state") Integer state, - @Field("timer") Long timer); + Observable setLobbyForConversation(@Header("Authorization") String authorization, @Url String url, @Field("state") Integer state, @Field("timer") Long timer); @POST - Observable setReadStatusPrivacy(@Header("Authorization") String authorization, - @Url String url, - @Body RequestBody body); + Observable setReadStatusPrivacy(@Header("Authorization") String authorization, @Url String url, @Body RequestBody body); @POST - Observable setTypingStatusPrivacy(@Header("Authorization") String authorization, - @Url String url, - @Body RequestBody body); + Observable setTypingStatusPrivacy(@Header("Authorization") String authorization, @Url String url, @Body RequestBody body); @POST - Observable searchContactsByPhoneNumber(@Header("Authorization") String authorization, - @Url String url, - @Body RequestBody search); + Observable searchContactsByPhoneNumber(@Header("Authorization") String authorization, @Url String url, @Body RequestBody search); @PUT - Observable> uploadFile(@Header("Authorization") String authorization, - @Url String url, - @Body RequestBody body); + Observable> uploadFile(@Header("Authorization") String authorization, @Url String url, @Body RequestBody body); @HEAD - Observable> checkIfFileExists(@Header("Authorization") String authorization, - @Url String url); + Observable> checkIfFileExists(@Header("Authorization") String authorization, @Url String url); @GET - Call downloadFile(@Header("Authorization") String authorization, - @Url String url); + Call downloadFile(@Header("Authorization") String authorization, @Url String url); @DELETE - Observable deleteChatMessage(@Header("Authorization") String authorization, - @Url String url); + Observable deleteChatMessage(@Header("Authorization") String authorization, @Url String url); @DELETE Observable deleteAvatar(@Header("Authorization") String authorization, @Url String url); @@ -497,40 +402,28 @@ public interface NcApi { @Multipart @POST - Observable uploadAvatar(@Header("Authorization") String authorization, - @Url String url, - @Part MultipartBody.Part attachment); + Observable uploadAvatar(@Header("Authorization") String authorization, @Url String url, @Part MultipartBody.Part attachment); @Multipart @POST - Observable uploadConversationAvatar(@Header("Authorization") String authorization, - @Url String url, - @Part MultipartBody.Part attachment); + Observable uploadConversationAvatar(@Header("Authorization") String authorization, @Url String url, @Part MultipartBody.Part attachment); @GET - Observable getEditableUserProfileFields(@Header("Authorization") String authorization, - @Url String url); + Observable getEditableUserProfileFields(@Header("Authorization") String authorization, @Url String url); @GET - Call downloadResizedImage(@Header("Authorization") String authorization, - @Url String url); + Call downloadResizedImage(@Header("Authorization") String authorization, @Url String url); @FormUrlEncoded @POST - Observable sendLocation(@Header("Authorization") String authorization, - @Url String url, - @Field("objectType") String objectType, - @Field("objectId") String objectId, - @Field("metaData") String metaData); + Observable sendLocation(@Header("Authorization") String authorization, @Url String url, @Field("objectType") String objectType, @Field("objectId") String objectId, @Field("metaData") String metaData); @DELETE Observable clearChatHistory(@Header("Authorization") String authorization, @Url String url); @FormUrlEncoded @POST - Observable notificationCalls(@Header("Authorization") String authorization, - @Url String url, - @Field("level") Integer level); + Observable notificationCalls(@Header("Authorization") String authorization, @Url String url, @Field("level") Integer level); @GET Observable hoverCard(@Header("Authorization") String authorization, @Url String url); @@ -538,9 +431,7 @@ public interface NcApi { // Url is: /api/{apiVersion}/chat/{token}/read @FormUrlEncoded @POST - Observable setChatReadMarker(@Header("Authorization") String authorization, - @Url String url, - @Field("lastReadMessage") int lastReadMessage); + Observable setChatReadMarker(@Header("Authorization") String authorization, @Url String url, @Field("lastReadMessage") int lastReadMessage); // Url is: /api/{apiVersion}/chat/{token}/read @DELETE @@ -568,100 +459,62 @@ public interface NcApi { @FormUrlEncoded @PUT - Observable setPredefinedStatusMessage(@Header("Authorization") String authorization, - @Url String url, - @Field("messageId") String selectedPredefinedMessageId, - @Field("clearAt") Long clearAt); + Observable setPredefinedStatusMessage(@Header("Authorization") String authorization, @Url String url, @Field("messageId") String selectedPredefinedMessageId, @Field("clearAt") Long clearAt); @FormUrlEncoded @PUT - Observable setCustomStatusMessage(@Header("Authorization") String authorization, - @Url String url, - @Field("statusIcon") String statusIcon, - @Field("message") String message, - @Field("clearAt") Long clearAt); + Observable setCustomStatusMessage(@Header("Authorization") String authorization, @Url String url, @Field("statusIcon") String statusIcon, @Field("message") String message, @Field("clearAt") Long clearAt); @FormUrlEncoded @PUT - Observable setStatusType(@Header("Authorization") String authorization, - @Url String url, - @Field("statusType") String statusType); + Observable setStatusType(@Header("Authorization") String authorization, @Url String url, @Field("statusType") String statusType); @POST - Observable sendReaction(@Header("Authorization") String authorization, - @Url String url, - @Query("reaction") String reaction); + Observable sendReaction(@Header("Authorization") String authorization, @Url String url, @Query("reaction") String reaction); @DELETE - Observable deleteReaction(@Header("Authorization") String authorization, - @Url String url, - @Query("reaction") String reaction); + Observable deleteReaction(@Header("Authorization") String authorization, @Url String url, @Query("reaction") String reaction); @GET - Observable getReactions(@Header("Authorization") String authorization, - @Url String url, - @Query("reaction") String reaction); + Observable getReactions(@Header("Authorization") String authorization, @Url String url, @Query("reaction") String reaction); @GET - Observable performUnifiedSearch(@Header("Authorization") String authorization, - @Url String url, - @Query("term") String term, - @Query("from") String fromUrl, - @Query("limit") Integer limit, - @Query("cursor") Integer cursor); + Observable performUnifiedSearch(@Header("Authorization") String authorization, @Url String url, @Query("term") String term, @Query("from") String fromUrl, @Query("limit") Integer limit, @Query("cursor") Integer cursor); @GET - Observable getPoll(@Header("Authorization") String authorization, - @Url String url); + Observable getPoll(@Header("Authorization") String authorization, @Url String url); @FormUrlEncoded @POST - Observable createPoll(@Header("Authorization") String authorization, - @Url String url, - @Query("question") String question, - @Field("options[]") List options, - @Query("resultMode") Integer resultMode, - @Query("maxVotes") Integer maxVotes); + Observable createPoll(@Header("Authorization") String authorization, @Url String url, @Query("question") String question, @Field("options[]") List options, @Query("resultMode") Integer resultMode, @Query("maxVotes") Integer maxVotes); @FormUrlEncoded @POST - Observable votePoll(@Header("Authorization") String authorization, - @Url String url, - @Field("optionIds[]") List optionIds); + Observable votePoll(@Header("Authorization") String authorization, @Url String url, @Field("optionIds[]") List optionIds); @DELETE - Observable closePoll(@Header("Authorization") String authorization, - @Url String url); + Observable closePoll(@Header("Authorization") String authorization, @Url String url); @FormUrlEncoded @POST - Observable setMessageExpiration(@Header("Authorization") String authorization, - @Url String url, - @Field("seconds") Integer seconds); + Observable setMessageExpiration(@Header("Authorization") String authorization, @Url String url, @Field("seconds") Integer seconds); @GET - Observable getOpenGraph(@Header("Authorization") String authorization, - @Url String url, - @Query("reference") String urlToFindPreviewFor); + Observable getOpenGraph(@Header("Authorization") String authorization, @Url String url, @Query("reference") String urlToFindPreviewFor); @FormUrlEncoded @POST - Observable startRecording(@Header("Authorization") String authorization, - @Url String url, - @Field("status") Integer status); + Observable startRecording(@Header("Authorization") String authorization, @Url String url, @Field("status") Integer status); @DELETE - Observable stopRecording(@Header("Authorization") String authorization, - @Url String url); + Observable stopRecording(@Header("Authorization") String authorization, @Url String url); @POST - Observable requestAssistance(@Header("Authorization") String authorization, - @Url String url); + Observable requestAssistance(@Header("Authorization") String authorization, @Url String url); @DELETE - Observable withdrawRequestAssistance(@Header("Authorization") String authorization, - @Url String url); + Observable withdrawRequestAssistance(@Header("Authorization") String authorization, @Url String url); @POST Observable sendCommonPostRequest(@Header("Authorization") String authorization, @Url String url); @@ -671,33 +524,22 @@ public interface NcApi { @POST - Observable translateMessage(@Header("Authorization") String authorization, - @Url String url, - @Query("text") String text, - @Query("toLanguage") String toLanguage, - @Nullable @Query("fromLanguage") String fromLanguage); + Observable translateMessage(@Header("Authorization") String authorization, @Url String url, @Query("text") String text, @Query("toLanguage") String toLanguage, @Nullable @Query("fromLanguage") String fromLanguage); @GET - Observable getLanguages(@Header("Authorization") String authorization, - @Url String url); + Observable getLanguages(@Header("Authorization") String authorization, @Url String url); @GET - Observable getReminder(@Header("Authorization") String authorization, - @Url String url); + Observable getReminder(@Header("Authorization") String authorization, @Url String url); @DELETE - Observable deleteReminder(@Header("Authorization") String authorization, - @Url String url); + Observable deleteReminder(@Header("Authorization") String authorization, @Url String url); @FormUrlEncoded @POST - Observable setReminder(@Header("Authorization") String authorization, - @Url String url, - @Field("timestamp") int timestamp); + Observable setReminder(@Header("Authorization") String authorization, @Url String url, @Field("timestamp") int timestamp); @FormUrlEncoded @PUT - Observable setRecordingConsent(@Header("Authorization") String authorization, - @Url String url, - @Field("recordingConsent") int recordingConsent); + Observable setRecordingConsent(@Header("Authorization") String authorization, @Url String url, @Field("recordingConsent") int recordingConsent); } diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 3ace986ef..1e6e3d594 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -4534,6 +4534,9 @@ class ChatActivity : startActivity(shareIntent) } + fun editMessage(message: ChatMessage) { + } + companion object { private val TAG = ChatActivity::class.simpleName private const val CONTENT_TYPE_CALL_STARTED: Byte = 1 diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt index 7414a36ae..af44b342e 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt @@ -108,6 +108,7 @@ class MessageActionsDialog( hasUserActorId(message) && currentConversation?.type != ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL ) + initMenuEditMessage(CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "edit-messages")) initMenuDeleteMessage(showMessageDeletionButton) initMenuForwardMessage( ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() && @@ -293,6 +294,15 @@ class MessageActionsDialog( dialogMessageActionsBinding.menuDeleteMessage.visibility = getVisibility(visible) } + private fun initMenuEditMessage(visible: Boolean) { + dialogMessageActionsBinding.menuEditMessage.setOnClickListener { + chatActivity.editMessage(message) + dismiss() + } + + dialogMessageActionsBinding.menuEditMessage.visibility = getVisibility(visible) + } + private fun initMenuReplyPrivately(visible: Boolean) { if (visible) { dialogMessageActionsBinding.menuReplyPrivately.setOnClickListener { diff --git a/app/src/main/res/layout/dialog_message_actions.xml b/app/src/main/res/layout/dialog_message_actions.xml index 7b201b247..04e0815c3 100644 --- a/app/src/main/res/layout/dialog_message_actions.xml +++ b/app/src/main/res/layout/dialog_message_actions.xml @@ -160,6 +160,40 @@ + + + + + + + + + Caption Retrieval failed Languages could not be retrieved + Edit Message Icon + Edit message From 337f07abfe224128237821078eaefc1b61578cc5 Mon Sep 17 00:00:00 2001 From: sowjanyakch Date: Wed, 24 Jan 2024 14:00:27 +0100 Subject: [PATCH 02/22] Basic Edit feature Signed-off-by: Sowjanya Kota <101803542+sowjanyakch@users.noreply.github.com> --- .../java/com/nextcloud/talk/api/NcApi.java | 1 + .../com/nextcloud/talk/chat/ChatActivity.kt | 189 ++++++++++++++++-- .../com/nextcloud/talk/ui/MessageInput.kt | 2 + app/src/main/res/drawable/ic_check_24.xml | 5 + app/src/main/res/drawable/ic_edit_24.xml | 5 + .../main/res/layout/view_message_input.xml | 14 ++ 6 files changed, 195 insertions(+), 21 deletions(-) create mode 100644 app/src/main/res/drawable/ic_check_24.xml create mode 100644 app/src/main/res/drawable/ic_edit_24.xml diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApi.java b/app/src/main/java/com/nextcloud/talk/api/NcApi.java index f99d16557..98af5ede2 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java +++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java @@ -332,6 +332,7 @@ public interface NcApi { @POST Observable sendChatMessage(@Header("Authorization") String authorization, @Url String url, @Field("message") CharSequence message, @Field("actorDisplayName") String actorDisplayName, @Field("replyTo") Integer replyTo, @Field("silent") Boolean sendWithoutNotification); + @FormUrlEncoded @PUT Observable editChatMessage(@Header("Authorization") String authorization, @Url String url, @Field("message") String message); diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 1e6e3d594..58d1b68f4 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -66,6 +66,7 @@ import android.view.Menu import android.view.MenuItem import android.view.MotionEvent import android.view.View +import android.view.View.GONE import android.view.View.OnTouchListener import android.view.animation.AccelerateDecelerateInterpolator import android.view.animation.AccelerateInterpolator @@ -166,6 +167,7 @@ import com.nextcloud.talk.models.domain.ObjectType import com.nextcloud.talk.models.domain.ReactionAddedModel import com.nextcloud.talk.models.domain.ReactionDeletedModel import com.nextcloud.talk.models.json.chat.ChatMessage +import com.nextcloud.talk.models.json.chat.ChatOCSSingleMessage import com.nextcloud.talk.models.json.chat.ChatOverall import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage import com.nextcloud.talk.models.json.chat.ReadStatus @@ -240,6 +242,7 @@ import io.reactivex.Observer import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers +import io.reactivex.subjects.BehaviorSubject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -299,6 +302,8 @@ class ChatActivity : lateinit var chatViewModel: ChatViewModel + val editableBehaviorSubject = BehaviorSubject.createDefault(false) + override val view: View get() = binding.root @@ -348,6 +353,9 @@ class ChatActivity : private var recorder: MediaRecorder? = null + private lateinit var originalMessage:ChatMessage + + private enum class MediaRecorderState { INITIAL, INITIALIZED, @@ -756,12 +764,18 @@ class ChatActivity : val filters = arrayOfNulls(1) val lengthFilter = CapabilitiesUtilNew.getMessageMaxLength(conversationUser) + if(!editableBehaviorSubject.value!!){ + binding.messageInputView.editMessageButton.visibility = View.GONE + binding.messageInputView.messageSendButton.visibility = View.VISIBLE + } + filters[0] = InputFilter.LengthFilter(lengthFilter) binding.messageInputView.inputEditText?.filters = filters - binding.messageInputView.inputEditText?.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { // unused atm + } @Suppress("Detekt.TooGenericExceptionCaught") @@ -778,6 +792,111 @@ class ChatActivity : } val editable = binding.messageInputView.inputEditText?.editableText + + if (editable != null && binding.messageInputView.inputEditText != null) { + val mentionSpans = editable.getSpans( + 0, + binding.messageInputView.inputEditText!!.length(), + Spans.MentionChipSpan::class.java + ) + var mentionSpan: Spans.MentionChipSpan + for (i in mentionSpans.indices) { + mentionSpan = mentionSpans[i] + if (start >= editable.getSpanStart(mentionSpan) && + start < editable.getSpanEnd(mentionSpan) + ) { + if (editable.subSequence( + editable.getSpanStart(mentionSpan), + editable.getSpanEnd(mentionSpan) + ).toString().trim { it <= ' ' } != mentionSpan.label + ) { + editable.removeSpan(mentionSpan) + } + } + } + } + + } + + override fun afterTextChanged(s: Editable) { + // unused atm + } + }) + + // Image keyboard support + // See: https://developer.android.com/guide/topics/text/image-keyboard + (binding.messageInputView.inputEditText as ImageEmojiEditText).onCommitContentListener = { + uploadFile(it.toString(), false) + } + + initVoiceRecordButton() + + if (sharedText.isNotEmpty()) { + binding.messageInputView.inputEditText?.setText(sharedText) + } + + binding.messageInputView.setAttachmentsListener { + AttachmentDialog(this, this).show() + } + + binding.messageInputView.button?.setOnClickListener { + submitMessage(false) + } + + if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "silent-send")) { + binding.messageInputView.button?.setOnLongClickListener { + showSendButtonMenu() + true + } + } + + binding.messageInputView.button?.contentDescription = + resources?.getString(R.string.nc_description_send_message_button) + } + + + private fun editMessageInputView(message:ChatMessage) { + editableBehaviorSubject.onNext(true) + + val filters = arrayOfNulls(1) + val lengthFilter = CapabilitiesUtilNew.getMessageMaxLength(conversationUser) + var editText = "" + + filters[0] = InputFilter.LengthFilter(lengthFilter) + binding.messageInputView.inputEditText?.filters = filters + + val editableText = Editable.Factory.getInstance().newEditable(message.message) + binding.messageInputView.inputEditText.text = editableText + if(editableBehaviorSubject.value!!){ + binding.messageInputView.editMessageButton.visibility = View.VISIBLE + binding.messageInputView.messageSendButton.visibility = View.GONE + binding.messageInputView.button.visibility = GONE + } + + binding.messageInputView.inputEditText?.addTextChangedListener(object : TextWatcher { + + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { + // unused atm + + } + + @Suppress("Detekt.TooGenericExceptionCaught") + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + + updateOwnTypingStatus(s) + + if (s.length >= lengthFilter) { + binding.messageInputView.inputEditText?.error = String.format( + Objects.requireNonNull(resources).getString(R.string.nc_limit_hit), + lengthFilter.toString() + ) + } else { + binding.messageInputView.inputEditText?.error = null + } + + val editable = binding.messageInputView.inputEditText?.editableText + editText = editable.toString() + if (editable != null && binding.messageInputView.inputEditText != null) { val mentionSpans = editable.getSpans( 0, @@ -804,35 +923,57 @@ class ChatActivity : override fun afterTextChanged(s: Editable) { // unused atm + binding.messageInputView.messageSendButton.visibility = GONE } }) - // Image keyboard support - // See: https://developer.android.com/guide/topics/text/image-keyboard - (binding.messageInputView.inputEditText as ImageEmojiEditText).onCommitContentListener = { - uploadFile(it.toString(), false) - } - - initVoiceRecordButton() - - if (sharedText.isNotEmpty()) { - binding.messageInputView.inputEditText?.setText(sharedText) - } binding.messageInputView.setAttachmentsListener { AttachmentDialog(this, this).show() } - binding.messageInputView.button?.setOnClickListener { submitMessage(false) } - - if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "silent-send")) { - binding.messageInputView.button?.setOnLongClickListener { - showSendButtonMenu() - true - } + binding.messageInputView.editMessageButton.setOnClickListener { + editMessageAPI(message, editedMessage = editText) } - binding.messageInputView.button?.contentDescription = - resources?.getString(R.string.nc_description_send_message_button) + } + + private fun editMessageAPI(message:ChatMessage, editedMessage:String){ + var apiVersion = 1 + // FIXME Fix API checking with guests? + if (conversationUser != null) { + apiVersion = ApiUtils.getChatApiVersion(conversationUser, intArrayOf(1)) + } + + ncApi.editChatMessage( + credentials, + ApiUtils.getUrlForChatMessage( + apiVersion, + conversationUser?.baseUrl, + roomToken, + message?.id + ),editedMessage + )?.subscribeOn(Schedulers.io()) + ?.observeOn(AndroidSchedulers.mainThread()) + ?.subscribe(object : Observer { + override fun onSubscribe(d: Disposable) { + // unused atm + } + + override fun onNext(t: ChatOCSSingleMessage) { + //unused atm + } + + override fun onError(e: Throwable) { + + } + + override fun onComplete() { + binding.messageInputView.editMessageButton.visibility = GONE + binding.messageInputView.messageSendButton.visibility = View.VISIBLE + editableBehaviorSubject.onNext(false) + binding.messageInputView.inputEditText.setText("") + } + }) } private fun themeMessageInputView() { @@ -4534,7 +4675,13 @@ class ChatActivity : startActivity(shareIntent) } + + + fun editMessage(message: ChatMessage) { + + editMessageInputView(message) + } companion object { diff --git a/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt b/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt index 5da7243cc..f42a657d5 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt @@ -43,6 +43,7 @@ class MessageInput : MessageInput { lateinit var sendVoiceRecording: ImageView lateinit var micInputCloud: MicInputCloud lateinit var playPauseBtn: MaterialButton + lateinit var editMessageButton:ImageButton lateinit var seekBar: SeekBar constructor(context: Context?) : super(context) { @@ -69,6 +70,7 @@ class MessageInput : MessageInput { micInputCloud = findViewById(R.id.micInputCloud) playPauseBtn = findViewById(R.id.playPauseBtn) seekBar = findViewById(R.id.seekbar) + editMessageButton = findViewById(R.id.editMessageButton) } var messageInput: EmojiEditText diff --git a/app/src/main/res/drawable/ic_check_24.xml b/app/src/main/res/drawable/ic_check_24.xml new file mode 100644 index 000000000..2501e9fd9 --- /dev/null +++ b/app/src/main/res/drawable/ic_check_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_edit_24.xml b/app/src/main/res/drawable/ic_edit_24.xml new file mode 100644 index 000000000..1c9bd3e6b --- /dev/null +++ b/app/src/main/res/drawable/ic_edit_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/view_message_input.xml b/app/src/main/res/layout/view_message_input.xml index 319724c8e..3ff89f30a 100644 --- a/app/src/main/res/layout/view_message_input.xml +++ b/app/src/main/res/layout/view_message_input.xml @@ -229,6 +229,8 @@ android:scaleType="centerInside" android:contentDescription="@string/nc_description_send_message_button" /> + + + + + Date: Thu, 25 Jan 2024 17:47:21 +0100 Subject: [PATCH 03/22] Edit inputText view refactoring Signed-off-by: Sowjanya Kota <101803542+sowjanyakch@users.noreply.github.com> Signed-off-by: sowjanyakch --- .../com/nextcloud/talk/chat/ChatActivity.kt | 198 ++++++------------ .../com/nextcloud/talk/ui/MessageInput.kt | 2 + app/src/main/res/drawable/ic_clear_24.xml | 5 + .../main/res/layout/view_message_input.xml | 20 +- app/src/main/res/values/strings.xml | 2 + 5 files changed, 95 insertions(+), 132 deletions(-) create mode 100644 app/src/main/res/drawable/ic_clear_24.xml diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 58d1b68f4..3f5bd5366 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -303,6 +303,8 @@ class ChatActivity : lateinit var chatViewModel: ChatViewModel val editableBehaviorSubject = BehaviorSubject.createDefault(false) + val editedTextBehaviorSubject = BehaviorSubject.createDefault("") + private lateinit var editMessage: ChatMessage override val view: View get() = binding.root @@ -353,9 +355,6 @@ class ChatActivity : private var recorder: MediaRecorder? = null - private lateinit var originalMessage:ChatMessage - - private enum class MediaRecorderState { INITIAL, INITIALIZED, @@ -753,7 +752,6 @@ class ChatActivity : }) initMessageInputView() - loadAvatarForStatusBar() setActionBarTitle() @@ -764,18 +762,20 @@ class ChatActivity : val filters = arrayOfNulls(1) val lengthFilter = CapabilitiesUtilNew.getMessageMaxLength(conversationUser) - if(!editableBehaviorSubject.value!!){ - binding.messageInputView.editMessageButton.visibility = View.GONE - binding.messageInputView.messageSendButton.visibility = View.VISIBLE + if (editableBehaviorSubject.value!!) { + val editableText = Editable.Factory.getInstance().newEditable(editMessage.message) + binding.messageInputView.inputEditText.text = editableText + binding.messageInputView.inputEditText.setSelection(editableText.length) + } filters[0] = InputFilter.LengthFilter(lengthFilter) binding.messageInputView.inputEditText?.filters = filters + binding.messageInputView.inputEditText?.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { // unused atm - } @Suppress("Detekt.TooGenericExceptionCaught") @@ -792,6 +792,7 @@ class ChatActivity : } val editable = binding.messageInputView.inputEditText?.editableText + editedTextBehaviorSubject.onNext(editable.toString().trim()) if (editable != null && binding.messageInputView.inputEditText != null) { val mentionSpans = editable.getSpans( @@ -815,7 +816,6 @@ class ChatActivity : } } } - } override fun afterTextChanged(s: Editable) { @@ -825,119 +825,53 @@ class ChatActivity : // Image keyboard support // See: https://developer.android.com/guide/topics/text/image-keyboard - (binding.messageInputView.inputEditText as ImageEmojiEditText).onCommitContentListener = { - uploadFile(it.toString(), false) - } - initVoiceRecordButton() - - if (sharedText.isNotEmpty()) { - binding.messageInputView.inputEditText?.setText(sharedText) - } - - binding.messageInputView.setAttachmentsListener { - AttachmentDialog(this, this).show() - } - - binding.messageInputView.button?.setOnClickListener { - submitMessage(false) - } - - if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "silent-send")) { - binding.messageInputView.button?.setOnLongClickListener { - showSendButtonMenu() - true - } - } - - binding.messageInputView.button?.contentDescription = - resources?.getString(R.string.nc_description_send_message_button) - } - - - private fun editMessageInputView(message:ChatMessage) { - editableBehaviorSubject.onNext(true) - - val filters = arrayOfNulls(1) - val lengthFilter = CapabilitiesUtilNew.getMessageMaxLength(conversationUser) - var editText = "" - - filters[0] = InputFilter.LengthFilter(lengthFilter) - binding.messageInputView.inputEditText?.filters = filters - - val editableText = Editable.Factory.getInstance().newEditable(message.message) - binding.messageInputView.inputEditText.text = editableText - if(editableBehaviorSubject.value!!){ - binding.messageInputView.editMessageButton.visibility = View.VISIBLE + (binding.messageInputView.inputEditText as ImageEmojiEditText).onCommitContentListener = { + uploadFile(it.toString(), false) + } + initVoiceRecordButton() + if (editableBehaviorSubject.value!!) { binding.messageInputView.messageSendButton.visibility = View.GONE - binding.messageInputView.button.visibility = GONE + binding.messageInputView.recordAudioButton.visibility = View.GONE + binding.messageInputView.editMessageButton.visibility = View.VISIBLE + binding.messageInputView.clearEditMessage.visibility = View.VISIBLE } - binding.messageInputView.inputEditText?.addTextChangedListener(object : TextWatcher { - - override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { - // unused atm - - } - - @Suppress("Detekt.TooGenericExceptionCaught") - override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { - - updateOwnTypingStatus(s) - - if (s.length >= lengthFilter) { - binding.messageInputView.inputEditText?.error = String.format( - Objects.requireNonNull(resources).getString(R.string.nc_limit_hit), - lengthFilter.toString() - ) - } else { - binding.messageInputView.inputEditText?.error = null - } - - val editable = binding.messageInputView.inputEditText?.editableText - editText = editable.toString() - - if (editable != null && binding.messageInputView.inputEditText != null) { - val mentionSpans = editable.getSpans( - 0, - binding.messageInputView.inputEditText!!.length(), - Spans.MentionChipSpan::class.java - ) - var mentionSpan: Spans.MentionChipSpan - for (i in mentionSpans.indices) { - mentionSpan = mentionSpans[i] - if (start >= editable.getSpanStart(mentionSpan) && - start < editable.getSpanEnd(mentionSpan) - ) { - if (editable.subSequence( - editable.getSpanStart(mentionSpan), - editable.getSpanEnd(mentionSpan) - ).toString().trim { it <= ' ' } != mentionSpan.label - ) { - editable.removeSpan(mentionSpan) - } - } - } - } - } - - override fun afterTextChanged(s: Editable) { - // unused atm - binding.messageInputView.messageSendButton.visibility = GONE - } - }) + if (sharedText.isNotEmpty()) { + binding.messageInputView.inputEditText?.setText(sharedText) + } binding.messageInputView.setAttachmentsListener { AttachmentDialog(this, this).show() } - binding.messageInputView.editMessageButton.setOnClickListener { - editMessageAPI(message, editedMessage = editText) + binding.messageInputView.button?.setOnClickListener { + submitMessage(false) } + binding.messageInputView.editMessageButton.setOnClickListener { + if(editMessage.message == editedTextBehaviorSubject.value!!){ + clearEditUI() + return@setOnClickListener + } + editMessageAPI(editMessage, editedMessageText = editedTextBehaviorSubject.value!!) + } + binding.messageInputView.clearEditMessage.setOnClickListener { + clearEditUI() + } + + if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "silent-send")) { + binding.messageInputView.button?.setOnLongClickListener { + showSendButtonMenu() + true + } + } + + binding.messageInputView.button?.contentDescription = + resources?.getString(R.string.nc_description_send_message_button) } - private fun editMessageAPI(message:ChatMessage, editedMessage:String){ + private fun editMessageAPI(message: ChatMessage, editedMessageText: String) { var apiVersion = 1 // FIXME Fix API checking with guests? if (conversationUser != null) { @@ -951,7 +885,7 @@ class ChatActivity : conversationUser?.baseUrl, roomToken, message?.id - ),editedMessage + ), editedMessageText )?.subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) ?.subscribe(object : Observer { @@ -964,16 +898,20 @@ class ChatActivity : } override fun onError(e: Throwable) { - } override fun onComplete() { - binding.messageInputView.editMessageButton.visibility = GONE - binding.messageInputView.messageSendButton.visibility = View.VISIBLE - editableBehaviorSubject.onNext(false) - binding.messageInputView.inputEditText.setText("") + clearEditUI() } }) + // remove last item from list + } + + private fun clearEditUI() { + binding.messageInputView.editMessageButton.visibility = GONE + binding.messageInputView.clearEditMessage.visibility = View.GONE + editableBehaviorSubject.onNext(false) + binding.messageInputView.inputEditText.setText("") } private fun themeMessageInputView() { @@ -1202,10 +1140,12 @@ class ChatActivity : @SuppressLint("ClickableViewAccessibility") private fun initVoiceRecordButton() { if (!isVoiceRecordingLocked) { - if (binding.messageInputView.messageInput.text!!.isNotEmpty()) { - showMicrophoneButton(false) - } else { - showMicrophoneButton(true) + if (!editableBehaviorSubject.value!!) { + if (binding.messageInputView.messageInput.text!!.isNotEmpty()) { + showMicrophoneButton(false) + } else { + showMicrophoneButton(true) + } } } else if (mediaRecorderState == MediaRecorderState.RECORDING) { binding.messageInputView.playPauseBtn.visibility = View.GONE @@ -1219,10 +1159,12 @@ class ChatActivity : isVoicePreviewPlaying = false binding.messageInputView.messageInput.doAfterTextChanged { - if (binding.messageInputView.messageInput.text?.isEmpty() == true) { - showMicrophoneButton(true) - } else { - showMicrophoneButton(false) + if (!editableBehaviorSubject.value!!) { + if (binding.messageInputView.messageInput.text?.isEmpty() == true) { + showMicrophoneButton(true) + } else { + showMicrophoneButton(false) + } } } @@ -4381,6 +4323,7 @@ class ChatActivity : } private fun showMicrophoneButton(show: Boolean) { + if (show && CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "voice-message-sharing")) { Log.d(TAG, "Microphone shown") binding.messageInputView.messageSendButton.visibility = View.GONE @@ -4675,13 +4618,10 @@ class ChatActivity : startActivity(shareIntent) } - - - fun editMessage(message: ChatMessage) { - - editMessageInputView(message) - + editableBehaviorSubject.onNext(true) + editMessage = message + initMessageInputView() } companion object { diff --git a/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt b/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt index f42a657d5..c66a04934 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt @@ -44,6 +44,7 @@ class MessageInput : MessageInput { lateinit var micInputCloud: MicInputCloud lateinit var playPauseBtn: MaterialButton lateinit var editMessageButton:ImageButton + lateinit var clearEditMessage:ImageButton lateinit var seekBar: SeekBar constructor(context: Context?) : super(context) { @@ -71,6 +72,7 @@ class MessageInput : MessageInput { playPauseBtn = findViewById(R.id.playPauseBtn) seekBar = findViewById(R.id.seekbar) editMessageButton = findViewById(R.id.editMessageButton) + clearEditMessage = findViewById(R.id.clearEditButton) } var messageInput: EmojiEditText diff --git a/app/src/main/res/drawable/ic_clear_24.xml b/app/src/main/res/drawable/ic_clear_24.xml new file mode 100644 index 000000000..70db409b3 --- /dev/null +++ b/app/src/main/res/drawable/ic_clear_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/view_message_input.xml b/app/src/main/res/layout/view_message_input.xml index 3ff89f30a..90573caeb 100644 --- a/app/src/main/res/layout/view_message_input.xml +++ b/app/src/main/res/layout/view_message_input.xml @@ -230,7 +230,6 @@ android:contentDescription="@string/nc_description_send_message_button" /> - + android:visibility = "gone" + tools:visibility = "visible" + android:contentDescription="@string/nc_send_edit_message" /> + + Languages could not be retrieved Edit Message Icon Edit message + Send Edit Message + Clear Edit Message From 2b24c69cbf12c748d63fb5891e76b1a66f70b8d1 Mon Sep 17 00:00:00 2001 From: sowjanyakch Date: Tue, 30 Jan 2024 09:45:15 +0100 Subject: [PATCH 04/22] System Messages and Error handling Signed-off-by: Sowjanya Kota <101803542+sowjanyakch@users.noreply.github.com> Signed-off-by: sowjanyakch --- .../com/nextcloud/talk/chat/ChatActivity.kt | 35 +++++++++++++++++-- .../talk/models/json/chat/ChatMessage.kt | 1 + .../EnumSystemMessageTypeConverter.kt | 2 ++ app/src/main/res/values/strings.xml | 2 ++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 3f5bd5366..bcb3e8bd9 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -84,6 +84,7 @@ import android.widget.RelativeLayout.BELOW import android.widget.RelativeLayout.LayoutParams import android.widget.SeekBar import android.widget.TextView +import android.widget.Toast import androidx.activity.OnBackPressedCallback import androidx.appcompat.view.ContextThemeWrapper import androidx.core.content.ContextCompat @@ -893,8 +894,25 @@ class ChatActivity : // unused atm } - override fun onNext(t: ChatOCSSingleMessage) { + override fun onNext(message: ChatOCSSingleMessage) { //unused atm + when(message.meta!!.statusCode){ + HTTP_BAD_REQUEST -> { + Toast.makeText(context, + getString(R.string.edit_error_24_hours_old_message),Toast.LENGTH_SHORT) + .show() + } + HTTP_FORBIDDEN -> { + Toast.makeText(context, + getString(R.string.conversation_is_read_only), + Toast.LENGTH_SHORT).show() + } + HTTP_NOT_FOUND -> { + Toast.makeText(context, + "Conversation Cannot be Found", + Toast.LENGTH_SHORT).show() + } + } } override fun onError(e: Throwable) { @@ -904,7 +922,7 @@ class ChatActivity : clearEditUI() } }) - // remove last item from list + } private fun clearEditUI() { @@ -912,6 +930,7 @@ class ChatActivity : binding.messageInputView.clearEditMessage.visibility = View.GONE editableBehaviorSubject.onNext(false) binding.messageInputView.inputEditText.setText("") + } private fun themeMessageInputView() { @@ -3820,6 +3839,10 @@ class ChatActivity : } else if (isPollVotedMessage(currentMessage)) { // delete poll system messages chatMessageIterator.remove() + }else if(isEditMessage(currentMessage)){ + if (!chatMessageMap.containsKey(currentMessage.value.parentMessage!!.id)){ + chatMessageIterator.remove() + } } } return chatMessageMap.values.toList() @@ -3860,6 +3883,11 @@ class ChatActivity : currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION_REVOKED } + private fun isEditMessage(currentMessage:MutableMap.MutableEntry):Boolean{ + return currentMessage.value.parentMessage != null && currentMessage.value.systemMessageType == ChatMessage + .SystemMessageType.MESSAGE_EDITED + } + private fun isPollVotedMessage(currentMessage: MutableMap.MutableEntry): Boolean { return currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.POLL_VOTED } @@ -4672,6 +4700,9 @@ class ChatActivity : private const val STATUS_SIZE_IN_DP = 9f private const val HTTP_CODE_NOT_MODIFIED = 304 private const val HTTP_CODE_PRECONDITION_FAILED = 412 + private const val HTTP_BAD_REQUEST = 400 + private const val HTTP_FORBIDDEN = 403 + private const val HTTP_NOT_FOUND = 404 private const val QUOTED_MESSAGE_IMAGE_MAX_HEIGHT = 96f private const val MENTION_AUTO_COMPLETE_ELEVATION = 6f private const val MESSAGE_PULL_LIMIT = 100 diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt index c9d70038a..3b6db4fd7 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt @@ -507,6 +507,7 @@ data class ChatMessage( GUEST_MODERATOR_PROMOTED, GUEST_MODERATOR_DEMOTED, MESSAGE_DELETED, + MESSAGE_EDITED, FILE_SHARED, OBJECT_SHARED, MATTERBRIDGE_CONFIG_ADDED, diff --git a/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumSystemMessageTypeConverter.kt b/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumSystemMessageTypeConverter.kt index 9fe1a36b3..807e7a914 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumSystemMessageTypeConverter.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumSystemMessageTypeConverter.kt @@ -127,6 +127,7 @@ class EnumSystemMessageTypeConverter : StringBasedTypeConverter GUEST_MODERATOR_PROMOTED "guest_moderator_demoted" -> GUEST_MODERATOR_DEMOTED "message_deleted" -> MESSAGE_DELETED + "message_edited" -> ChatMessage.SystemMessageType.MESSAGE_EDITED "file_shared" -> FILE_SHARED "object_shared" -> OBJECT_SHARED "matterbridge_config_added" -> MATTERBRIDGE_CONFIG_ADDED @@ -193,6 +194,7 @@ class EnumSystemMessageTypeConverter : StringBasedTypeConverter "guest_moderator_promoted" GUEST_MODERATOR_DEMOTED -> "guest_moderator_demoted" MESSAGE_DELETED -> "message_deleted" + ChatMessage.SystemMessageType.MESSAGE_EDITED -> "message_edited" FILE_SHARED -> "file_shared" OBJECT_SHARED -> "object_shared" MATTERBRIDGE_CONFIG_ADDED -> "matterbridge_config_added" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6bb941f8d..b87215ad3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -790,4 +790,6 @@ How to translate with transifex: Edit message Send Edit Message Clear Edit Message + Cannot Edit Messages older than 24 hours + Conversation is read Only From cc8719668d2c33301aecb5513d9de7f842dad0d4 Mon Sep 17 00:00:00 2001 From: sowjanyakch Date: Wed, 31 Jan 2024 09:22:13 +0100 Subject: [PATCH 05/22] layout - edit message view Signed-off-by: Sowjanya Kota <101803542+sowjanyakch@users.noreply.github.com> Signed-off-by: sowjanyakch --- app/src/main/res/layout/activity_chat.xml | 7 ++ app/src/main/res/layout/edit_message_view.xml | 64 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 app/src/main/res/layout/edit_message_view.xml diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml index f2b9361ca..d79085ec8 100644 --- a/app/src/main/res/layout/activity_chat.xml +++ b/app/src/main/res/layout/activity_chat.xml @@ -133,6 +133,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 253c3fe02476c42f1fa94964fff0e6f1d4a0b05c Mon Sep 17 00:00:00 2001 From: sowjanyakch Date: Thu, 1 Feb 2024 13:08:18 +0100 Subject: [PATCH 06/22] Update Edit Message Signed-off-by: Sowjanya Kota --- .../java/com/nextcloud/talk/api/NcApi.java | 3 +- .../com/nextcloud/talk/chat/ChatActivity.kt | 54 +++++++------------ .../com/nextcloud/talk/ui/MessageInput.kt | 2 - app/src/main/res/layout/activity_chat.xml | 37 +++++++------ app/src/main/res/layout/edit_message_view.xml | 12 ++--- .../main/res/layout/view_message_input.xml | 16 +----- app/src/main/res/values/strings.xml | 1 + 7 files changed, 44 insertions(+), 81 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApi.java b/app/src/main/java/com/nextcloud/talk/api/NcApi.java index 98af5ede2..a10b5add3 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java +++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java @@ -334,7 +334,8 @@ public interface NcApi { @FormUrlEncoded @PUT - Observable editChatMessage(@Header("Authorization") String authorization, @Url String url, @Field("message") String message); + Observable editChatMessage(@Header("Authorization") String authorization, @Url String url, @Field( + "message") String message); @GET Observable> getSharedItems(@Header("Authorization") String authorization, @Url String url, @Query("objectType") String objectType, @Nullable @Query("lastKnownMessageId") Integer lastKnownMessageId, @Nullable @Query("limit") Integer limit); diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index bcb3e8bd9..a0aa39709 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -84,7 +84,6 @@ import android.widget.RelativeLayout.BELOW import android.widget.RelativeLayout.LayoutParams import android.widget.SeekBar import android.widget.TextView -import android.widget.Toast import androidx.activity.OnBackPressedCallback import androidx.appcompat.view.ContextThemeWrapper import androidx.core.content.ContextCompat @@ -168,7 +167,6 @@ import com.nextcloud.talk.models.domain.ObjectType import com.nextcloud.talk.models.domain.ReactionAddedModel import com.nextcloud.talk.models.domain.ReactionDeletedModel import com.nextcloud.talk.models.json.chat.ChatMessage -import com.nextcloud.talk.models.json.chat.ChatOCSSingleMessage import com.nextcloud.talk.models.json.chat.ChatOverall import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage import com.nextcloud.talk.models.json.chat.ReadStatus @@ -762,12 +760,13 @@ class ChatActivity : private fun initMessageInputView() { val filters = arrayOfNulls(1) val lengthFilter = CapabilitiesUtilNew.getMessageMaxLength(conversationUser) + binding.editView.editMessageView.visibility = GONE if (editableBehaviorSubject.value!!) { val editableText = Editable.Factory.getInstance().newEditable(editMessage.message) binding.messageInputView.inputEditText.text = editableText binding.messageInputView.inputEditText.setSelection(editableText.length) - + binding.editView.editMessage.setText(editMessage.message) } filters[0] = InputFilter.LengthFilter(lengthFilter) @@ -835,7 +834,7 @@ class ChatActivity : binding.messageInputView.messageSendButton.visibility = View.GONE binding.messageInputView.recordAudioButton.visibility = View.GONE binding.messageInputView.editMessageButton.visibility = View.VISIBLE - binding.messageInputView.clearEditMessage.visibility = View.VISIBLE + binding.editView.editMessageView.visibility = View.VISIBLE } if (sharedText.isNotEmpty()) { @@ -851,13 +850,13 @@ class ChatActivity : } binding.messageInputView.editMessageButton.setOnClickListener { - if(editMessage.message == editedTextBehaviorSubject.value!!){ + if (editMessage.message == editedTextBehaviorSubject.value!!) { clearEditUI() return@setOnClickListener } editMessageAPI(editMessage, editedMessageText = editedTextBehaviorSubject.value!!) } - binding.messageInputView.clearEditMessage.setOnClickListener { + binding.editView.clearEdit.setOnClickListener { clearEditUI() } @@ -889,48 +888,33 @@ class ChatActivity : ), editedMessageText )?.subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) - ?.subscribe(object : Observer { + ?.subscribe(object : Observer { override fun onSubscribe(d: Disposable) { // unused atm } - override fun onNext(message: ChatOCSSingleMessage) { - //unused atm - when(message.meta!!.statusCode){ - HTTP_BAD_REQUEST -> { - Toast.makeText(context, - getString(R.string.edit_error_24_hours_old_message),Toast.LENGTH_SHORT) - .show() - } - HTTP_FORBIDDEN -> { - Toast.makeText(context, - getString(R.string.conversation_is_read_only), - Toast.LENGTH_SHORT).show() - } - HTTP_NOT_FOUND -> { - Toast.makeText(context, - "Conversation Cannot be Found", - Toast.LENGTH_SHORT).show() - } - } + override fun onNext(messageEdited: ChatOverallSingleMessage) { + message.message = messageEdited.ocs?.data?.parentMessage?.text + adapter?.update(message) + adapter?.notifyDataSetChanged() + clearEditUI() } override fun onError(e: Throwable) { + } override fun onComplete() { - clearEditUI() + } }) - } private fun clearEditUI() { binding.messageInputView.editMessageButton.visibility = GONE - binding.messageInputView.clearEditMessage.visibility = View.GONE editableBehaviorSubject.onNext(false) binding.messageInputView.inputEditText.setText("") - + binding.editView.editMessageView.visibility = GONE } private fun themeMessageInputView() { @@ -1004,7 +988,7 @@ class ChatActivity : ) adapter?.setLoadMoreListener(this) - adapter?.setDateHeadersFormatter { format(it) } + adapter?.setDateHeadersFormatter {format(it)} adapter?.setOnMessageViewLongClickListener { view, message -> onMessageViewLongClick(view, message) } adapter?.registerViewClickListener( R.id.playPauseBtn @@ -3839,10 +3823,8 @@ class ChatActivity : } else if (isPollVotedMessage(currentMessage)) { // delete poll system messages chatMessageIterator.remove() - }else if(isEditMessage(currentMessage)){ - if (!chatMessageMap.containsKey(currentMessage.value.parentMessage!!.id)){ - chatMessageIterator.remove() - } + } else if (isEditMessage(currentMessage)) { + chatMessageIterator.remove() } } return chatMessageMap.values.toList() @@ -3883,7 +3865,7 @@ class ChatActivity : currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION_REVOKED } - private fun isEditMessage(currentMessage:MutableMap.MutableEntry):Boolean{ + private fun isEditMessage(currentMessage: MutableMap.MutableEntry): Boolean { return currentMessage.value.parentMessage != null && currentMessage.value.systemMessageType == ChatMessage .SystemMessageType.MESSAGE_EDITED } diff --git a/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt b/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt index c66a04934..f42a657d5 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt @@ -44,7 +44,6 @@ class MessageInput : MessageInput { lateinit var micInputCloud: MicInputCloud lateinit var playPauseBtn: MaterialButton lateinit var editMessageButton:ImageButton - lateinit var clearEditMessage:ImageButton lateinit var seekBar: SeekBar constructor(context: Context?) : super(context) { @@ -72,7 +71,6 @@ class MessageInput : MessageInput { playPauseBtn = findViewById(R.id.playPauseBtn) seekBar = findViewById(R.id.seekbar) editMessageButton = findViewById(R.id.editMessageButton) - clearEditMessage = findViewById(R.id.clearEditButton) } var messageInput: EmojiEditText diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml index d79085ec8..1ed31932f 100644 --- a/app/src/main/res/layout/activity_chat.xml +++ b/app/src/main/res/layout/activity_chat.xml @@ -140,8 +140,8 @@ android:id="@+id/messagesListView" android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingBottom="20dp" android:clipToPadding="false" + android:paddingBottom="20dp" android:visibility="gone" app:dateHeaderTextSize="13sp" app:incomingBubblePaddingBottom="@dimen/message_bubble_corners_vertical_padding" @@ -170,11 +170,10 @@ app:outcomingTextSize="@dimen/chat_text_size" app:outcomingTimeTextSize="12sp" app:textAutoLink="all" - tools:visibility="visible"/> + tools:visibility="visible" /> @@ -227,9 +227,9 @@ android:id="@+id/typing_indicator_wrapper" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="vertical" android:layout_alignParentBottom="true" - android:layout_marginBottom="-19dp"> + android:layout_marginBottom="-19dp" + android:orientation="vertical"> - + tools:ignore="Overdraw" + tools:text="Marcel is typing"> @@ -259,12 +258,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> - - + + - - - - + android:text = "@string/nc_edit_message_text"> @@ -61,4 +58,3 @@ - \ No newline at end of file diff --git a/app/src/main/res/layout/view_message_input.xml b/app/src/main/res/layout/view_message_input.xml index 90573caeb..59dd049ae 100644 --- a/app/src/main/res/layout/view_message_input.xml +++ b/app/src/main/res/layout/view_message_input.xml @@ -71,7 +71,7 @@ android:layout_height="wrap_content" android:layout_below="@+id/quotedChatMessageView" android:layout_centerHorizontal="true" - android:layout_toStartOf="@id/messageSendButton" + android:layout_marginEnd = "48dp" android:layout_toEndOf="@id/smileyButton" android:imeOptions="actionDone" android:inputType="textAutoCorrect|textMultiLine|textCapSentences" @@ -253,20 +253,6 @@ tools:visibility = "visible" android:contentDescription="@string/nc_send_edit_message" /> - - Clear Edit Message Cannot Edit Messages older than 24 hours Conversation is read Only + Edit Message Text From 6db42115baea3cf8f2d7d9693b74c28e6e0bf86c Mon Sep 17 00:00:00 2001 From: sowjanyakch Date: Thu, 1 Feb 2024 22:34:20 +0100 Subject: [PATCH 07/22] add (edited) tag in incoming and outgoing messages layout Signed-off-by:Sowjanya Kota --- .../messages/IncomingTextMessageViewHolder.kt | 8 ++++++++ .../messages/OutcomingTextMessageViewHolder.kt | 7 +++++++ .../layout/item_custom_incoming_text_message.xml | 15 ++++++++++++++- .../layout/item_custom_outcoming_text_message.xml | 15 +++++++++++++++ app/src/main/res/values/strings.xml | 1 + 5 files changed, 45 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt index 17333d452..07d2412e7 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt @@ -114,6 +114,14 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) : binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) binding.messageText.text = processedMessageText + if (message.parentMessage?.id != null && message.systemMessageType == ChatMessage.SystemMessageType + .MESSAGE_EDITED + ) { + binding.messageType.visibility = View.VISIBLE + } else { + binding.messageType.visibility = View.GONE + } + binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp) // parent message handling diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt index 680af44a6..9142ee679 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt @@ -100,6 +100,13 @@ class OutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessageViewH setBubbleOnChatMessage(message) binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) + if (message.parentMessage?.id != null && message.systemMessageType == ChatMessage.SystemMessageType + .MESSAGE_EDITED + ) { + binding.messageType.visibility = View.VISIBLE + } else { + binding.messageType.visibility = View.GONE + } binding.messageTime.layoutParams = layoutParams viewThemeUtils.platform.colorTextView(binding.messageText, ColorRole.ON_SURFACE_VARIANT) binding.messageText.text = processedMessageText diff --git a/app/src/main/res/layout/item_custom_incoming_text_message.xml b/app/src/main/res/layout/item_custom_incoming_text_message.xml index fe6ac95ed..b5dba9ec4 100644 --- a/app/src/main/res/layout/item_custom_incoming_text_message.xml +++ b/app/src/main/res/layout/item_custom_incoming_text_message.xml @@ -78,6 +78,19 @@ app:layout_wrapBefore="true" tools:text="Talk to you later!" /> + + + + - + \ No newline at end of file diff --git a/app/src/main/res/layout/item_custom_outcoming_text_message.xml b/app/src/main/res/layout/item_custom_outcoming_text_message.xml index 9298f5035..3147b62d8 100644 --- a/app/src/main/res/layout/item_custom_outcoming_text_message.xml +++ b/app/src/main/res/layout/item_custom_outcoming_text_message.xml @@ -57,6 +57,20 @@ android:textIsSelectable="false" tools:text="Talk to you later!" /> + + + + + + Cannot Edit Messages older than 24 hours Conversation is read Only Edit Message Text + (edited) From 96a244408e93c159145c55d3d4a1f2feb839d345 Mon Sep 17 00:00:00 2001 From: Sowjanya Kota <101803542+sowjanyakch@users.noreply.github.com> Date: Fri, 19 Jan 2024 18:12:31 +0100 Subject: [PATCH 08/22] Checking Edit Capability --- .../java/com/nextcloud/talk/api/NcApi.java | 316 +++++------------- .../com/nextcloud/talk/chat/ChatActivity.kt | 3 + .../talk/ui/dialog/MessageActionsDialog.kt | 10 + .../res/layout/dialog_message_actions.xml | 34 ++ app/src/main/res/values/strings.xml | 2 + 5 files changed, 128 insertions(+), 237 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApi.java b/app/src/main/java/com/nextcloud/talk/api/NcApi.java index 674443ee6..f99d16557 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java +++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java @@ -24,6 +24,7 @@ package com.nextcloud.talk.api; import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall; +import com.nextcloud.talk.models.json.chat.ChatOCSSingleMessage; import com.nextcloud.talk.models.json.chat.ChatOverall; import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage; import com.nextcloud.talk.models.json.chat.ChatShareOverall; @@ -96,19 +97,14 @@ public interface NcApi { */ @GET - Observable getContactsWithSearchParam(@Header("Authorization") String authorization, - @Url String url, - @Nullable @Query("shareTypes[]") List listOfShareTypes, - @QueryMap Map options); + Observable getContactsWithSearchParam(@Header("Authorization") String authorization, @Url String url, @Nullable @Query("shareTypes[]") List listOfShareTypes, @QueryMap Map options); /* Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /room */ @GET - Observable getRooms(@Header("Authorization") String authorization, - @Url String url, - @Nullable @Query("includeStatus") Boolean includeStatus); + Observable getRooms(@Header("Authorization") String authorization, @Url String url, @Nullable @Query("includeStatus") Boolean includeStatus); /* Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /room/roomToken @@ -125,9 +121,7 @@ public interface NcApi { */ @POST - Observable createRoom(@Header("Authorization") String authorization, - @Url String url, - @QueryMap Map options); + Observable createRoom(@Header("Authorization") String authorization, @Url String url, @QueryMap Map options); /* QueryMap items are as follows: @@ -138,16 +132,12 @@ public interface NcApi { @FormUrlEncoded @PUT - Observable renameRoom(@Header("Authorization") String authorization, - @Url String url, - @Field("roomName") String roomName); + Observable renameRoom(@Header("Authorization") String authorization, @Url String url, @Field("roomName") String roomName); @FormUrlEncoded @PUT - Observable setConversationDescription(@Header("Authorization") String authorization, - @Url String url, - @Field("description") String description); + Observable setConversationDescription(@Header("Authorization") String authorization, @Url String url, @Field("description") String description); /* QueryMap items are as follows: @@ -156,48 +146,32 @@ public interface NcApi { Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /room/roomToken/participants */ @POST - Observable addParticipant(@Header("Authorization") String authorization, - @Url String url, - @QueryMap Map options); + Observable addParticipant(@Header("Authorization") String authorization, @Url String url, @QueryMap Map options); @POST - Observable resendParticipantInvitations(@Header("Authorization") String authorization, - @Url String url); + Observable resendParticipantInvitations(@Header("Authorization") String authorization, @Url String url); // also used for removing a guest from a conversation @Deprecated @DELETE - Observable removeParticipantFromConversation(@Header("Authorization") String authorization, - @Url String url, - @Query("participant") String participantId); + Observable removeParticipantFromConversation(@Header("Authorization") String authorization, @Url String url, @Query("participant") String participantId); @DELETE - Observable removeAttendeeFromConversation(@Header("Authorization") String authorization, - @Url String url, - @Query("attendeeId") Long attendeeId); + Observable removeAttendeeFromConversation(@Header("Authorization") String authorization, @Url String url, @Query("attendeeId") Long attendeeId); @Deprecated @POST - Observable promoteUserToModerator(@Header("Authorization") String authorization, - @Url String url, - @Query("participant") String participantId); + Observable promoteUserToModerator(@Header("Authorization") String authorization, @Url String url, @Query("participant") String participantId); @Deprecated @DELETE - Observable demoteModeratorToUser(@Header("Authorization") String authorization, - @Url String url, - @Query("participant") String participantId); + Observable demoteModeratorToUser(@Header("Authorization") String authorization, @Url String url, @Query("participant") String participantId); @POST - Observable promoteAttendeeToModerator(@Header("Authorization") String authorization, - @Url String url, - @Query("attendeeId") Long attendeeId); + Observable promoteAttendeeToModerator(@Header("Authorization") String authorization, @Url String url, @Query("attendeeId") Long attendeeId); @DELETE - Observable demoteAttendeeFromModerator(@Header("Authorization") String authorization, - @Url String url, - @Query("attendeeId") Long attendeeId); + Observable demoteAttendeeFromModerator(@Header("Authorization") String authorization, @Url String url, @Query("attendeeId") Long attendeeId); /* Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /room/roomToken/participants/self @@ -228,15 +202,11 @@ public interface NcApi { Observable getPeersForCall(@Header("Authorization") String authorization, @Url String url); @GET - Observable getPeersForCall(@Header("Authorization") String authorization, - @Url String url, - @QueryMap Map fields); + Observable getPeersForCall(@Header("Authorization") String authorization, @Url String url, @QueryMap Map fields); @FormUrlEncoded @POST - Observable joinRoom(@Nullable @Header("Authorization") String authorization, - @Url String url, - @Nullable @Field("password") String password); + Observable joinRoom(@Nullable @Header("Authorization") String authorization, @Url String url, @Nullable @Field("password") String password); @DELETE Observable leaveRoom(@Nullable @Header("Authorization") String authorization, @Url String url); @@ -247,11 +217,7 @@ public interface NcApi { @FormUrlEncoded @POST - Observable joinCall(@Nullable @Header("Authorization") String authorization, - @Url String url, - @Field("flags") Integer inCall, - @Field("silent") Boolean callWithoutNotification, - @Nullable @Field("recordingConsent") Boolean recordingConsent); + Observable joinCall(@Nullable @Header("Authorization") String authorization, @Url String url, @Field("flags") Integer inCall, @Field("silent") Boolean callWithoutNotification, @Nullable @Field("recordingConsent") Boolean recordingConsent); /* Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /call/callToken @@ -260,8 +226,7 @@ public interface NcApi { Observable leaveCall(@Nullable @Header("Authorization") String authorization, @Url String url); @GET - Observable getSignalingSettings(@Nullable @Header("Authorization") String authorization, - @Url String url); + Observable getSignalingSettings(@Nullable @Header("Authorization") String authorization, @Url String url); /* QueryMap items are as follows: @@ -271,16 +236,13 @@ public interface NcApi { */ @FormUrlEncoded @POST - Observable sendSignalingMessages(@Nullable @Header("Authorization") String authorization, - @Url String url, - @Field("messages") String messages); + Observable sendSignalingMessages(@Nullable @Header("Authorization") String authorization, @Url String url, @Field("messages") String messages); /* Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /signaling */ @GET - Observable pullSignalingMessages(@Nullable @Header("Authorization") String authorization, - @Url String url); + Observable pullSignalingMessages(@Nullable @Header("Authorization") String authorization, @Url String url); /* QueryMap items are as follows: @@ -298,11 +260,7 @@ public interface NcApi { @FormUrlEncoded @PUT - Observable setUserData(@Header("Authorization") String authorization, - @Url String url, - @Field("key") String key, - @Field("value") String value); - + Observable setUserData(@Header("Authorization") String authorization, @Url String url, @Field("key") String key, @Field("value") String value); /* Server URL is: baseUrl + /status.php @@ -322,21 +280,14 @@ public interface NcApi { */ @POST - Observable registerDeviceForNotificationsWithNextcloud( - @Header("Authorization") String authorization, - @Url String url, - @QueryMap Map options); + Observable registerDeviceForNotificationsWithNextcloud(@Header("Authorization") String authorization, @Url String url, @QueryMap Map options); @DELETE - Observable unregisterDeviceForNotificationsWithNextcloud( - @Header("Authorization") String authorization, - @Url String url); + Observable unregisterDeviceForNotificationsWithNextcloud(@Header("Authorization") String authorization, @Url String url); @FormUrlEncoded @POST - Observable registerDeviceForNotificationsWithPushProxy(@Url String url, - @FieldMap Map fields); - + Observable registerDeviceForNotificationsWithPushProxy(@Url String url, @FieldMap Map fields); /* QueryMap items are as follows: @@ -345,20 +296,15 @@ public interface NcApi { - "userPublicKey": "{{userPublicKey}}" */ @DELETE - Observable unregisterDeviceForNotificationsWithProxy(@Url String url, - @QueryMap Map fields); + Observable unregisterDeviceForNotificationsWithProxy(@Url String url, @QueryMap Map fields); @FormUrlEncoded @PUT - Observable setPassword(@Header("Authorization") String authorization, - @Url String url, - @Field("password") String password); + Observable setPassword(@Header("Authorization") String authorization, @Url String url, @Field("password") String password); @FormUrlEncoded @PUT - Observable> setPassword2(@Header("Authorization") String authorization, - @Url String url, - @Field("password") String password); + Observable> setPassword2(@Header("Authorization") String authorization, @Url String url, @Field("password") String password); @GET Observable getCapabilities(@Header("Authorization") String authorization, @Url String url); @@ -374,9 +320,7 @@ public interface NcApi { - "lastKnownMessageId", int, use one from X-Chat-Last-Given */ @GET - Observable> pullChatMessages(@Header("Authorization") String authorization, - @Url String url, - @QueryMap Map fields); + Observable> pullChatMessages(@Header("Authorization") String authorization, @Url String url, @QueryMap Map fields); /* Fieldmap items are as follows: @@ -386,107 +330,68 @@ public interface NcApi { @FormUrlEncoded @POST - Observable sendChatMessage(@Header("Authorization") String authorization, - @Url String url, - @Field("message") CharSequence message, - @Field("actorDisplayName") String actorDisplayName, - @Field("replyTo") Integer replyTo, - @Field("silent") Boolean sendWithoutNotification); + Observable sendChatMessage(@Header("Authorization") String authorization, @Url String url, @Field("message") CharSequence message, @Field("actorDisplayName") String actorDisplayName, @Field("replyTo") Integer replyTo, @Field("silent") Boolean sendWithoutNotification); + + @PUT + Observable editChatMessage(@Header("Authorization") String authorization, @Url String url, @Field("message") String message); @GET - Observable> getSharedItems( - @Header("Authorization") String authorization, - @Url String url, - @Query("objectType") String objectType, - @Nullable @Query("lastKnownMessageId") Integer lastKnownMessageId, - @Nullable @Query("limit") Integer limit); + Observable> getSharedItems(@Header("Authorization") String authorization, @Url String url, @Query("objectType") String objectType, @Nullable @Query("lastKnownMessageId") Integer lastKnownMessageId, @Nullable @Query("limit") Integer limit); @GET - Observable> getSharedItemsOverview(@Header("Authorization") String authorization, - @Url String url, - @Nullable @Query("limit") Integer limit); + Observable> getSharedItemsOverview(@Header("Authorization") String authorization, @Url String url, @Nullable @Query("limit") Integer limit); @GET - Observable getMentionAutocompleteSuggestions(@Header("Authorization") String authorization, - @Url String url, - @Query("search") String query, - @Nullable @Query("limit") Integer limit, - @QueryMap Map fields); + Observable getMentionAutocompleteSuggestions(@Header("Authorization") String authorization, @Url String url, @Query("search") String query, @Nullable @Query("limit") Integer limit, @QueryMap Map fields); // Url is: /api/{apiVersion}/room/{token}/pin @POST - Observable addConversationToFavorites(@Header("Authorization") String authorization, - @Url String url); + Observable addConversationToFavorites(@Header("Authorization") String authorization, @Url String url); // Url is: /api/{apiVersion}/room/{token}/favorites @DELETE - Observable removeConversationFromFavorites(@Header("Authorization") String authorization, - @Url String url); + Observable removeConversationFromFavorites(@Header("Authorization") String authorization, @Url String url); @GET - Observable getNcNotification(@Header("Authorization") String authorization, - @Url String url); + Observable getNcNotification(@Header("Authorization") String authorization, @Url String url); @FormUrlEncoded @POST - Observable setNotificationLevel(@Header("Authorization") String authorization, - @Url String url, - @Field("level") int level); + Observable setNotificationLevel(@Header("Authorization") String authorization, @Url String url, @Field("level") int level); @FormUrlEncoded @PUT - Observable setReadOnlyState(@Header("Authorization") String authorization, - @Url String url, - @Field("state") int state); + Observable setReadOnlyState(@Header("Authorization") String authorization, @Url String url, @Field("state") int state); @FormUrlEncoded @POST - Observable createRemoteShare(@Nullable @Header("Authorization") String authorization, - @Url String url, - @Field("path") String remotePath, - @Field("shareWith") String roomToken, - @Field("shareType") String shareType, - @Field("talkMetaData") String talkMetaData); + Observable createRemoteShare(@Nullable @Header("Authorization") String authorization, @Url String url, @Field("path") String remotePath, @Field("shareWith") String roomToken, @Field("shareType") String shareType, @Field("talkMetaData") String talkMetaData); @FormUrlEncoded @PUT - Observable setLobbyForConversation(@Header("Authorization") String authorization, - @Url String url, - @Field("state") Integer state, - @Field("timer") Long timer); + Observable setLobbyForConversation(@Header("Authorization") String authorization, @Url String url, @Field("state") Integer state, @Field("timer") Long timer); @POST - Observable setReadStatusPrivacy(@Header("Authorization") String authorization, - @Url String url, - @Body RequestBody body); + Observable setReadStatusPrivacy(@Header("Authorization") String authorization, @Url String url, @Body RequestBody body); @POST - Observable setTypingStatusPrivacy(@Header("Authorization") String authorization, - @Url String url, - @Body RequestBody body); + Observable setTypingStatusPrivacy(@Header("Authorization") String authorization, @Url String url, @Body RequestBody body); @POST - Observable searchContactsByPhoneNumber(@Header("Authorization") String authorization, - @Url String url, - @Body RequestBody search); + Observable searchContactsByPhoneNumber(@Header("Authorization") String authorization, @Url String url, @Body RequestBody search); @PUT - Observable> uploadFile(@Header("Authorization") String authorization, - @Url String url, - @Body RequestBody body); + Observable> uploadFile(@Header("Authorization") String authorization, @Url String url, @Body RequestBody body); @HEAD - Observable> checkIfFileExists(@Header("Authorization") String authorization, - @Url String url); + Observable> checkIfFileExists(@Header("Authorization") String authorization, @Url String url); @GET - Call downloadFile(@Header("Authorization") String authorization, - @Url String url); + Call downloadFile(@Header("Authorization") String authorization, @Url String url); @DELETE - Observable deleteChatMessage(@Header("Authorization") String authorization, - @Url String url); + Observable deleteChatMessage(@Header("Authorization") String authorization, @Url String url); @DELETE Observable deleteAvatar(@Header("Authorization") String authorization, @Url String url); @@ -497,40 +402,28 @@ public interface NcApi { @Multipart @POST - Observable uploadAvatar(@Header("Authorization") String authorization, - @Url String url, - @Part MultipartBody.Part attachment); + Observable uploadAvatar(@Header("Authorization") String authorization, @Url String url, @Part MultipartBody.Part attachment); @Multipart @POST - Observable uploadConversationAvatar(@Header("Authorization") String authorization, - @Url String url, - @Part MultipartBody.Part attachment); + Observable uploadConversationAvatar(@Header("Authorization") String authorization, @Url String url, @Part MultipartBody.Part attachment); @GET - Observable getEditableUserProfileFields(@Header("Authorization") String authorization, - @Url String url); + Observable getEditableUserProfileFields(@Header("Authorization") String authorization, @Url String url); @GET - Call downloadResizedImage(@Header("Authorization") String authorization, - @Url String url); + Call downloadResizedImage(@Header("Authorization") String authorization, @Url String url); @FormUrlEncoded @POST - Observable sendLocation(@Header("Authorization") String authorization, - @Url String url, - @Field("objectType") String objectType, - @Field("objectId") String objectId, - @Field("metaData") String metaData); + Observable sendLocation(@Header("Authorization") String authorization, @Url String url, @Field("objectType") String objectType, @Field("objectId") String objectId, @Field("metaData") String metaData); @DELETE Observable clearChatHistory(@Header("Authorization") String authorization, @Url String url); @FormUrlEncoded @POST - Observable notificationCalls(@Header("Authorization") String authorization, - @Url String url, - @Field("level") Integer level); + Observable notificationCalls(@Header("Authorization") String authorization, @Url String url, @Field("level") Integer level); @GET Observable hoverCard(@Header("Authorization") String authorization, @Url String url); @@ -538,9 +431,7 @@ public interface NcApi { // Url is: /api/{apiVersion}/chat/{token}/read @FormUrlEncoded @POST - Observable setChatReadMarker(@Header("Authorization") String authorization, - @Url String url, - @Field("lastReadMessage") int lastReadMessage); + Observable setChatReadMarker(@Header("Authorization") String authorization, @Url String url, @Field("lastReadMessage") int lastReadMessage); // Url is: /api/{apiVersion}/chat/{token}/read @DELETE @@ -568,100 +459,62 @@ public interface NcApi { @FormUrlEncoded @PUT - Observable setPredefinedStatusMessage(@Header("Authorization") String authorization, - @Url String url, - @Field("messageId") String selectedPredefinedMessageId, - @Field("clearAt") Long clearAt); + Observable setPredefinedStatusMessage(@Header("Authorization") String authorization, @Url String url, @Field("messageId") String selectedPredefinedMessageId, @Field("clearAt") Long clearAt); @FormUrlEncoded @PUT - Observable setCustomStatusMessage(@Header("Authorization") String authorization, - @Url String url, - @Field("statusIcon") String statusIcon, - @Field("message") String message, - @Field("clearAt") Long clearAt); + Observable setCustomStatusMessage(@Header("Authorization") String authorization, @Url String url, @Field("statusIcon") String statusIcon, @Field("message") String message, @Field("clearAt") Long clearAt); @FormUrlEncoded @PUT - Observable setStatusType(@Header("Authorization") String authorization, - @Url String url, - @Field("statusType") String statusType); + Observable setStatusType(@Header("Authorization") String authorization, @Url String url, @Field("statusType") String statusType); @POST - Observable sendReaction(@Header("Authorization") String authorization, - @Url String url, - @Query("reaction") String reaction); + Observable sendReaction(@Header("Authorization") String authorization, @Url String url, @Query("reaction") String reaction); @DELETE - Observable deleteReaction(@Header("Authorization") String authorization, - @Url String url, - @Query("reaction") String reaction); + Observable deleteReaction(@Header("Authorization") String authorization, @Url String url, @Query("reaction") String reaction); @GET - Observable getReactions(@Header("Authorization") String authorization, - @Url String url, - @Query("reaction") String reaction); + Observable getReactions(@Header("Authorization") String authorization, @Url String url, @Query("reaction") String reaction); @GET - Observable performUnifiedSearch(@Header("Authorization") String authorization, - @Url String url, - @Query("term") String term, - @Query("from") String fromUrl, - @Query("limit") Integer limit, - @Query("cursor") Integer cursor); + Observable performUnifiedSearch(@Header("Authorization") String authorization, @Url String url, @Query("term") String term, @Query("from") String fromUrl, @Query("limit") Integer limit, @Query("cursor") Integer cursor); @GET - Observable getPoll(@Header("Authorization") String authorization, - @Url String url); + Observable getPoll(@Header("Authorization") String authorization, @Url String url); @FormUrlEncoded @POST - Observable createPoll(@Header("Authorization") String authorization, - @Url String url, - @Query("question") String question, - @Field("options[]") List options, - @Query("resultMode") Integer resultMode, - @Query("maxVotes") Integer maxVotes); + Observable createPoll(@Header("Authorization") String authorization, @Url String url, @Query("question") String question, @Field("options[]") List options, @Query("resultMode") Integer resultMode, @Query("maxVotes") Integer maxVotes); @FormUrlEncoded @POST - Observable votePoll(@Header("Authorization") String authorization, - @Url String url, - @Field("optionIds[]") List optionIds); + Observable votePoll(@Header("Authorization") String authorization, @Url String url, @Field("optionIds[]") List optionIds); @DELETE - Observable closePoll(@Header("Authorization") String authorization, - @Url String url); + Observable closePoll(@Header("Authorization") String authorization, @Url String url); @FormUrlEncoded @POST - Observable setMessageExpiration(@Header("Authorization") String authorization, - @Url String url, - @Field("seconds") Integer seconds); + Observable setMessageExpiration(@Header("Authorization") String authorization, @Url String url, @Field("seconds") Integer seconds); @GET - Observable getOpenGraph(@Header("Authorization") String authorization, - @Url String url, - @Query("reference") String urlToFindPreviewFor); + Observable getOpenGraph(@Header("Authorization") String authorization, @Url String url, @Query("reference") String urlToFindPreviewFor); @FormUrlEncoded @POST - Observable startRecording(@Header("Authorization") String authorization, - @Url String url, - @Field("status") Integer status); + Observable startRecording(@Header("Authorization") String authorization, @Url String url, @Field("status") Integer status); @DELETE - Observable stopRecording(@Header("Authorization") String authorization, - @Url String url); + Observable stopRecording(@Header("Authorization") String authorization, @Url String url); @POST - Observable requestAssistance(@Header("Authorization") String authorization, - @Url String url); + Observable requestAssistance(@Header("Authorization") String authorization, @Url String url); @DELETE - Observable withdrawRequestAssistance(@Header("Authorization") String authorization, - @Url String url); + Observable withdrawRequestAssistance(@Header("Authorization") String authorization, @Url String url); @POST Observable sendCommonPostRequest(@Header("Authorization") String authorization, @Url String url); @@ -671,33 +524,22 @@ public interface NcApi { @POST - Observable translateMessage(@Header("Authorization") String authorization, - @Url String url, - @Query("text") String text, - @Query("toLanguage") String toLanguage, - @Nullable @Query("fromLanguage") String fromLanguage); + Observable translateMessage(@Header("Authorization") String authorization, @Url String url, @Query("text") String text, @Query("toLanguage") String toLanguage, @Nullable @Query("fromLanguage") String fromLanguage); @GET - Observable getLanguages(@Header("Authorization") String authorization, - @Url String url); + Observable getLanguages(@Header("Authorization") String authorization, @Url String url); @GET - Observable getReminder(@Header("Authorization") String authorization, - @Url String url); + Observable getReminder(@Header("Authorization") String authorization, @Url String url); @DELETE - Observable deleteReminder(@Header("Authorization") String authorization, - @Url String url); + Observable deleteReminder(@Header("Authorization") String authorization, @Url String url); @FormUrlEncoded @POST - Observable setReminder(@Header("Authorization") String authorization, - @Url String url, - @Field("timestamp") int timestamp); + Observable setReminder(@Header("Authorization") String authorization, @Url String url, @Field("timestamp") int timestamp); @FormUrlEncoded @PUT - Observable setRecordingConsent(@Header("Authorization") String authorization, - @Url String url, - @Field("recordingConsent") int recordingConsent); + Observable setRecordingConsent(@Header("Authorization") String authorization, @Url String url, @Field("recordingConsent") int recordingConsent); } diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 3ace986ef..1e6e3d594 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -4534,6 +4534,9 @@ class ChatActivity : startActivity(shareIntent) } + fun editMessage(message: ChatMessage) { + } + companion object { private val TAG = ChatActivity::class.simpleName private const val CONTENT_TYPE_CALL_STARTED: Byte = 1 diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt index 7414a36ae..af44b342e 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt @@ -108,6 +108,7 @@ class MessageActionsDialog( hasUserActorId(message) && currentConversation?.type != ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL ) + initMenuEditMessage(CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "edit-messages")) initMenuDeleteMessage(showMessageDeletionButton) initMenuForwardMessage( ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() && @@ -293,6 +294,15 @@ class MessageActionsDialog( dialogMessageActionsBinding.menuDeleteMessage.visibility = getVisibility(visible) } + private fun initMenuEditMessage(visible: Boolean) { + dialogMessageActionsBinding.menuEditMessage.setOnClickListener { + chatActivity.editMessage(message) + dismiss() + } + + dialogMessageActionsBinding.menuEditMessage.visibility = getVisibility(visible) + } + private fun initMenuReplyPrivately(visible: Boolean) { if (visible) { dialogMessageActionsBinding.menuReplyPrivately.setOnClickListener { diff --git a/app/src/main/res/layout/dialog_message_actions.xml b/app/src/main/res/layout/dialog_message_actions.xml index 7b201b247..04e0815c3 100644 --- a/app/src/main/res/layout/dialog_message_actions.xml +++ b/app/src/main/res/layout/dialog_message_actions.xml @@ -160,6 +160,40 @@ + + + + + + + + + Caption Retrieval failed Languages could not be retrieved + Edit Message Icon + Edit message From 5ecfd3cd3b6fe172068a740e51fe7108b4d51934 Mon Sep 17 00:00:00 2001 From: sowjanyakch Date: Wed, 24 Jan 2024 14:00:27 +0100 Subject: [PATCH 09/22] Basic Edit feature --- .../java/com/nextcloud/talk/api/NcApi.java | 1 + .../com/nextcloud/talk/chat/ChatActivity.kt | 189 ++++++++++++++++-- .../com/nextcloud/talk/ui/MessageInput.kt | 2 + app/src/main/res/drawable/ic_check_24.xml | 5 + app/src/main/res/drawable/ic_edit_24.xml | 5 + .../main/res/layout/view_message_input.xml | 14 ++ 6 files changed, 195 insertions(+), 21 deletions(-) create mode 100644 app/src/main/res/drawable/ic_check_24.xml create mode 100644 app/src/main/res/drawable/ic_edit_24.xml diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApi.java b/app/src/main/java/com/nextcloud/talk/api/NcApi.java index f99d16557..98af5ede2 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java +++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java @@ -332,6 +332,7 @@ public interface NcApi { @POST Observable sendChatMessage(@Header("Authorization") String authorization, @Url String url, @Field("message") CharSequence message, @Field("actorDisplayName") String actorDisplayName, @Field("replyTo") Integer replyTo, @Field("silent") Boolean sendWithoutNotification); + @FormUrlEncoded @PUT Observable editChatMessage(@Header("Authorization") String authorization, @Url String url, @Field("message") String message); diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 1e6e3d594..58d1b68f4 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -66,6 +66,7 @@ import android.view.Menu import android.view.MenuItem import android.view.MotionEvent import android.view.View +import android.view.View.GONE import android.view.View.OnTouchListener import android.view.animation.AccelerateDecelerateInterpolator import android.view.animation.AccelerateInterpolator @@ -166,6 +167,7 @@ import com.nextcloud.talk.models.domain.ObjectType import com.nextcloud.talk.models.domain.ReactionAddedModel import com.nextcloud.talk.models.domain.ReactionDeletedModel import com.nextcloud.talk.models.json.chat.ChatMessage +import com.nextcloud.talk.models.json.chat.ChatOCSSingleMessage import com.nextcloud.talk.models.json.chat.ChatOverall import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage import com.nextcloud.talk.models.json.chat.ReadStatus @@ -240,6 +242,7 @@ import io.reactivex.Observer import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers +import io.reactivex.subjects.BehaviorSubject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -299,6 +302,8 @@ class ChatActivity : lateinit var chatViewModel: ChatViewModel + val editableBehaviorSubject = BehaviorSubject.createDefault(false) + override val view: View get() = binding.root @@ -348,6 +353,9 @@ class ChatActivity : private var recorder: MediaRecorder? = null + private lateinit var originalMessage:ChatMessage + + private enum class MediaRecorderState { INITIAL, INITIALIZED, @@ -756,12 +764,18 @@ class ChatActivity : val filters = arrayOfNulls(1) val lengthFilter = CapabilitiesUtilNew.getMessageMaxLength(conversationUser) + if(!editableBehaviorSubject.value!!){ + binding.messageInputView.editMessageButton.visibility = View.GONE + binding.messageInputView.messageSendButton.visibility = View.VISIBLE + } + filters[0] = InputFilter.LengthFilter(lengthFilter) binding.messageInputView.inputEditText?.filters = filters - binding.messageInputView.inputEditText?.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { // unused atm + } @Suppress("Detekt.TooGenericExceptionCaught") @@ -778,6 +792,111 @@ class ChatActivity : } val editable = binding.messageInputView.inputEditText?.editableText + + if (editable != null && binding.messageInputView.inputEditText != null) { + val mentionSpans = editable.getSpans( + 0, + binding.messageInputView.inputEditText!!.length(), + Spans.MentionChipSpan::class.java + ) + var mentionSpan: Spans.MentionChipSpan + for (i in mentionSpans.indices) { + mentionSpan = mentionSpans[i] + if (start >= editable.getSpanStart(mentionSpan) && + start < editable.getSpanEnd(mentionSpan) + ) { + if (editable.subSequence( + editable.getSpanStart(mentionSpan), + editable.getSpanEnd(mentionSpan) + ).toString().trim { it <= ' ' } != mentionSpan.label + ) { + editable.removeSpan(mentionSpan) + } + } + } + } + + } + + override fun afterTextChanged(s: Editable) { + // unused atm + } + }) + + // Image keyboard support + // See: https://developer.android.com/guide/topics/text/image-keyboard + (binding.messageInputView.inputEditText as ImageEmojiEditText).onCommitContentListener = { + uploadFile(it.toString(), false) + } + + initVoiceRecordButton() + + if (sharedText.isNotEmpty()) { + binding.messageInputView.inputEditText?.setText(sharedText) + } + + binding.messageInputView.setAttachmentsListener { + AttachmentDialog(this, this).show() + } + + binding.messageInputView.button?.setOnClickListener { + submitMessage(false) + } + + if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "silent-send")) { + binding.messageInputView.button?.setOnLongClickListener { + showSendButtonMenu() + true + } + } + + binding.messageInputView.button?.contentDescription = + resources?.getString(R.string.nc_description_send_message_button) + } + + + private fun editMessageInputView(message:ChatMessage) { + editableBehaviorSubject.onNext(true) + + val filters = arrayOfNulls(1) + val lengthFilter = CapabilitiesUtilNew.getMessageMaxLength(conversationUser) + var editText = "" + + filters[0] = InputFilter.LengthFilter(lengthFilter) + binding.messageInputView.inputEditText?.filters = filters + + val editableText = Editable.Factory.getInstance().newEditable(message.message) + binding.messageInputView.inputEditText.text = editableText + if(editableBehaviorSubject.value!!){ + binding.messageInputView.editMessageButton.visibility = View.VISIBLE + binding.messageInputView.messageSendButton.visibility = View.GONE + binding.messageInputView.button.visibility = GONE + } + + binding.messageInputView.inputEditText?.addTextChangedListener(object : TextWatcher { + + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { + // unused atm + + } + + @Suppress("Detekt.TooGenericExceptionCaught") + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + + updateOwnTypingStatus(s) + + if (s.length >= lengthFilter) { + binding.messageInputView.inputEditText?.error = String.format( + Objects.requireNonNull(resources).getString(R.string.nc_limit_hit), + lengthFilter.toString() + ) + } else { + binding.messageInputView.inputEditText?.error = null + } + + val editable = binding.messageInputView.inputEditText?.editableText + editText = editable.toString() + if (editable != null && binding.messageInputView.inputEditText != null) { val mentionSpans = editable.getSpans( 0, @@ -804,35 +923,57 @@ class ChatActivity : override fun afterTextChanged(s: Editable) { // unused atm + binding.messageInputView.messageSendButton.visibility = GONE } }) - // Image keyboard support - // See: https://developer.android.com/guide/topics/text/image-keyboard - (binding.messageInputView.inputEditText as ImageEmojiEditText).onCommitContentListener = { - uploadFile(it.toString(), false) - } - - initVoiceRecordButton() - - if (sharedText.isNotEmpty()) { - binding.messageInputView.inputEditText?.setText(sharedText) - } binding.messageInputView.setAttachmentsListener { AttachmentDialog(this, this).show() } - binding.messageInputView.button?.setOnClickListener { submitMessage(false) } - - if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "silent-send")) { - binding.messageInputView.button?.setOnLongClickListener { - showSendButtonMenu() - true - } + binding.messageInputView.editMessageButton.setOnClickListener { + editMessageAPI(message, editedMessage = editText) } - binding.messageInputView.button?.contentDescription = - resources?.getString(R.string.nc_description_send_message_button) + } + + private fun editMessageAPI(message:ChatMessage, editedMessage:String){ + var apiVersion = 1 + // FIXME Fix API checking with guests? + if (conversationUser != null) { + apiVersion = ApiUtils.getChatApiVersion(conversationUser, intArrayOf(1)) + } + + ncApi.editChatMessage( + credentials, + ApiUtils.getUrlForChatMessage( + apiVersion, + conversationUser?.baseUrl, + roomToken, + message?.id + ),editedMessage + )?.subscribeOn(Schedulers.io()) + ?.observeOn(AndroidSchedulers.mainThread()) + ?.subscribe(object : Observer { + override fun onSubscribe(d: Disposable) { + // unused atm + } + + override fun onNext(t: ChatOCSSingleMessage) { + //unused atm + } + + override fun onError(e: Throwable) { + + } + + override fun onComplete() { + binding.messageInputView.editMessageButton.visibility = GONE + binding.messageInputView.messageSendButton.visibility = View.VISIBLE + editableBehaviorSubject.onNext(false) + binding.messageInputView.inputEditText.setText("") + } + }) } private fun themeMessageInputView() { @@ -4534,7 +4675,13 @@ class ChatActivity : startActivity(shareIntent) } + + + fun editMessage(message: ChatMessage) { + + editMessageInputView(message) + } companion object { diff --git a/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt b/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt index 5da7243cc..f42a657d5 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt @@ -43,6 +43,7 @@ class MessageInput : MessageInput { lateinit var sendVoiceRecording: ImageView lateinit var micInputCloud: MicInputCloud lateinit var playPauseBtn: MaterialButton + lateinit var editMessageButton:ImageButton lateinit var seekBar: SeekBar constructor(context: Context?) : super(context) { @@ -69,6 +70,7 @@ class MessageInput : MessageInput { micInputCloud = findViewById(R.id.micInputCloud) playPauseBtn = findViewById(R.id.playPauseBtn) seekBar = findViewById(R.id.seekbar) + editMessageButton = findViewById(R.id.editMessageButton) } var messageInput: EmojiEditText diff --git a/app/src/main/res/drawable/ic_check_24.xml b/app/src/main/res/drawable/ic_check_24.xml new file mode 100644 index 000000000..2501e9fd9 --- /dev/null +++ b/app/src/main/res/drawable/ic_check_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_edit_24.xml b/app/src/main/res/drawable/ic_edit_24.xml new file mode 100644 index 000000000..1c9bd3e6b --- /dev/null +++ b/app/src/main/res/drawable/ic_edit_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/view_message_input.xml b/app/src/main/res/layout/view_message_input.xml index 319724c8e..3ff89f30a 100644 --- a/app/src/main/res/layout/view_message_input.xml +++ b/app/src/main/res/layout/view_message_input.xml @@ -229,6 +229,8 @@ android:scaleType="centerInside" android:contentDescription="@string/nc_description_send_message_button" /> + + + + + Date: Thu, 25 Jan 2024 17:47:21 +0100 Subject: [PATCH 10/22] Edit inputText view refactoring --- .../com/nextcloud/talk/chat/ChatActivity.kt | 198 ++++++------------ .../com/nextcloud/talk/ui/MessageInput.kt | 2 + app/src/main/res/drawable/ic_clear_24.xml | 5 + .../main/res/layout/view_message_input.xml | 20 +- app/src/main/res/values/strings.xml | 2 + 5 files changed, 95 insertions(+), 132 deletions(-) create mode 100644 app/src/main/res/drawable/ic_clear_24.xml diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 58d1b68f4..3f5bd5366 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -303,6 +303,8 @@ class ChatActivity : lateinit var chatViewModel: ChatViewModel val editableBehaviorSubject = BehaviorSubject.createDefault(false) + val editedTextBehaviorSubject = BehaviorSubject.createDefault("") + private lateinit var editMessage: ChatMessage override val view: View get() = binding.root @@ -353,9 +355,6 @@ class ChatActivity : private var recorder: MediaRecorder? = null - private lateinit var originalMessage:ChatMessage - - private enum class MediaRecorderState { INITIAL, INITIALIZED, @@ -753,7 +752,6 @@ class ChatActivity : }) initMessageInputView() - loadAvatarForStatusBar() setActionBarTitle() @@ -764,18 +762,20 @@ class ChatActivity : val filters = arrayOfNulls(1) val lengthFilter = CapabilitiesUtilNew.getMessageMaxLength(conversationUser) - if(!editableBehaviorSubject.value!!){ - binding.messageInputView.editMessageButton.visibility = View.GONE - binding.messageInputView.messageSendButton.visibility = View.VISIBLE + if (editableBehaviorSubject.value!!) { + val editableText = Editable.Factory.getInstance().newEditable(editMessage.message) + binding.messageInputView.inputEditText.text = editableText + binding.messageInputView.inputEditText.setSelection(editableText.length) + } filters[0] = InputFilter.LengthFilter(lengthFilter) binding.messageInputView.inputEditText?.filters = filters + binding.messageInputView.inputEditText?.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { // unused atm - } @Suppress("Detekt.TooGenericExceptionCaught") @@ -792,6 +792,7 @@ class ChatActivity : } val editable = binding.messageInputView.inputEditText?.editableText + editedTextBehaviorSubject.onNext(editable.toString().trim()) if (editable != null && binding.messageInputView.inputEditText != null) { val mentionSpans = editable.getSpans( @@ -815,7 +816,6 @@ class ChatActivity : } } } - } override fun afterTextChanged(s: Editable) { @@ -825,119 +825,53 @@ class ChatActivity : // Image keyboard support // See: https://developer.android.com/guide/topics/text/image-keyboard - (binding.messageInputView.inputEditText as ImageEmojiEditText).onCommitContentListener = { - uploadFile(it.toString(), false) - } - initVoiceRecordButton() - - if (sharedText.isNotEmpty()) { - binding.messageInputView.inputEditText?.setText(sharedText) - } - - binding.messageInputView.setAttachmentsListener { - AttachmentDialog(this, this).show() - } - - binding.messageInputView.button?.setOnClickListener { - submitMessage(false) - } - - if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "silent-send")) { - binding.messageInputView.button?.setOnLongClickListener { - showSendButtonMenu() - true - } - } - - binding.messageInputView.button?.contentDescription = - resources?.getString(R.string.nc_description_send_message_button) - } - - - private fun editMessageInputView(message:ChatMessage) { - editableBehaviorSubject.onNext(true) - - val filters = arrayOfNulls(1) - val lengthFilter = CapabilitiesUtilNew.getMessageMaxLength(conversationUser) - var editText = "" - - filters[0] = InputFilter.LengthFilter(lengthFilter) - binding.messageInputView.inputEditText?.filters = filters - - val editableText = Editable.Factory.getInstance().newEditable(message.message) - binding.messageInputView.inputEditText.text = editableText - if(editableBehaviorSubject.value!!){ - binding.messageInputView.editMessageButton.visibility = View.VISIBLE + (binding.messageInputView.inputEditText as ImageEmojiEditText).onCommitContentListener = { + uploadFile(it.toString(), false) + } + initVoiceRecordButton() + if (editableBehaviorSubject.value!!) { binding.messageInputView.messageSendButton.visibility = View.GONE - binding.messageInputView.button.visibility = GONE + binding.messageInputView.recordAudioButton.visibility = View.GONE + binding.messageInputView.editMessageButton.visibility = View.VISIBLE + binding.messageInputView.clearEditMessage.visibility = View.VISIBLE } - binding.messageInputView.inputEditText?.addTextChangedListener(object : TextWatcher { - - override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { - // unused atm - - } - - @Suppress("Detekt.TooGenericExceptionCaught") - override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { - - updateOwnTypingStatus(s) - - if (s.length >= lengthFilter) { - binding.messageInputView.inputEditText?.error = String.format( - Objects.requireNonNull(resources).getString(R.string.nc_limit_hit), - lengthFilter.toString() - ) - } else { - binding.messageInputView.inputEditText?.error = null - } - - val editable = binding.messageInputView.inputEditText?.editableText - editText = editable.toString() - - if (editable != null && binding.messageInputView.inputEditText != null) { - val mentionSpans = editable.getSpans( - 0, - binding.messageInputView.inputEditText!!.length(), - Spans.MentionChipSpan::class.java - ) - var mentionSpan: Spans.MentionChipSpan - for (i in mentionSpans.indices) { - mentionSpan = mentionSpans[i] - if (start >= editable.getSpanStart(mentionSpan) && - start < editable.getSpanEnd(mentionSpan) - ) { - if (editable.subSequence( - editable.getSpanStart(mentionSpan), - editable.getSpanEnd(mentionSpan) - ).toString().trim { it <= ' ' } != mentionSpan.label - ) { - editable.removeSpan(mentionSpan) - } - } - } - } - } - - override fun afterTextChanged(s: Editable) { - // unused atm - binding.messageInputView.messageSendButton.visibility = GONE - } - }) + if (sharedText.isNotEmpty()) { + binding.messageInputView.inputEditText?.setText(sharedText) + } binding.messageInputView.setAttachmentsListener { AttachmentDialog(this, this).show() } - binding.messageInputView.editMessageButton.setOnClickListener { - editMessageAPI(message, editedMessage = editText) + binding.messageInputView.button?.setOnClickListener { + submitMessage(false) } + binding.messageInputView.editMessageButton.setOnClickListener { + if(editMessage.message == editedTextBehaviorSubject.value!!){ + clearEditUI() + return@setOnClickListener + } + editMessageAPI(editMessage, editedMessageText = editedTextBehaviorSubject.value!!) + } + binding.messageInputView.clearEditMessage.setOnClickListener { + clearEditUI() + } + + if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "silent-send")) { + binding.messageInputView.button?.setOnLongClickListener { + showSendButtonMenu() + true + } + } + + binding.messageInputView.button?.contentDescription = + resources?.getString(R.string.nc_description_send_message_button) } - private fun editMessageAPI(message:ChatMessage, editedMessage:String){ + private fun editMessageAPI(message: ChatMessage, editedMessageText: String) { var apiVersion = 1 // FIXME Fix API checking with guests? if (conversationUser != null) { @@ -951,7 +885,7 @@ class ChatActivity : conversationUser?.baseUrl, roomToken, message?.id - ),editedMessage + ), editedMessageText )?.subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) ?.subscribe(object : Observer { @@ -964,16 +898,20 @@ class ChatActivity : } override fun onError(e: Throwable) { - } override fun onComplete() { - binding.messageInputView.editMessageButton.visibility = GONE - binding.messageInputView.messageSendButton.visibility = View.VISIBLE - editableBehaviorSubject.onNext(false) - binding.messageInputView.inputEditText.setText("") + clearEditUI() } }) + // remove last item from list + } + + private fun clearEditUI() { + binding.messageInputView.editMessageButton.visibility = GONE + binding.messageInputView.clearEditMessage.visibility = View.GONE + editableBehaviorSubject.onNext(false) + binding.messageInputView.inputEditText.setText("") } private fun themeMessageInputView() { @@ -1202,10 +1140,12 @@ class ChatActivity : @SuppressLint("ClickableViewAccessibility") private fun initVoiceRecordButton() { if (!isVoiceRecordingLocked) { - if (binding.messageInputView.messageInput.text!!.isNotEmpty()) { - showMicrophoneButton(false) - } else { - showMicrophoneButton(true) + if (!editableBehaviorSubject.value!!) { + if (binding.messageInputView.messageInput.text!!.isNotEmpty()) { + showMicrophoneButton(false) + } else { + showMicrophoneButton(true) + } } } else if (mediaRecorderState == MediaRecorderState.RECORDING) { binding.messageInputView.playPauseBtn.visibility = View.GONE @@ -1219,10 +1159,12 @@ class ChatActivity : isVoicePreviewPlaying = false binding.messageInputView.messageInput.doAfterTextChanged { - if (binding.messageInputView.messageInput.text?.isEmpty() == true) { - showMicrophoneButton(true) - } else { - showMicrophoneButton(false) + if (!editableBehaviorSubject.value!!) { + if (binding.messageInputView.messageInput.text?.isEmpty() == true) { + showMicrophoneButton(true) + } else { + showMicrophoneButton(false) + } } } @@ -4381,6 +4323,7 @@ class ChatActivity : } private fun showMicrophoneButton(show: Boolean) { + if (show && CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "voice-message-sharing")) { Log.d(TAG, "Microphone shown") binding.messageInputView.messageSendButton.visibility = View.GONE @@ -4675,13 +4618,10 @@ class ChatActivity : startActivity(shareIntent) } - - - fun editMessage(message: ChatMessage) { - - editMessageInputView(message) - + editableBehaviorSubject.onNext(true) + editMessage = message + initMessageInputView() } companion object { diff --git a/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt b/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt index f42a657d5..c66a04934 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt @@ -44,6 +44,7 @@ class MessageInput : MessageInput { lateinit var micInputCloud: MicInputCloud lateinit var playPauseBtn: MaterialButton lateinit var editMessageButton:ImageButton + lateinit var clearEditMessage:ImageButton lateinit var seekBar: SeekBar constructor(context: Context?) : super(context) { @@ -71,6 +72,7 @@ class MessageInput : MessageInput { playPauseBtn = findViewById(R.id.playPauseBtn) seekBar = findViewById(R.id.seekbar) editMessageButton = findViewById(R.id.editMessageButton) + clearEditMessage = findViewById(R.id.clearEditButton) } var messageInput: EmojiEditText diff --git a/app/src/main/res/drawable/ic_clear_24.xml b/app/src/main/res/drawable/ic_clear_24.xml new file mode 100644 index 000000000..70db409b3 --- /dev/null +++ b/app/src/main/res/drawable/ic_clear_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/view_message_input.xml b/app/src/main/res/layout/view_message_input.xml index 3ff89f30a..90573caeb 100644 --- a/app/src/main/res/layout/view_message_input.xml +++ b/app/src/main/res/layout/view_message_input.xml @@ -230,7 +230,6 @@ android:contentDescription="@string/nc_description_send_message_button" /> - + android:visibility = "gone" + tools:visibility = "visible" + android:contentDescription="@string/nc_send_edit_message" /> + + Languages could not be retrieved Edit Message Icon Edit message + Send Edit Message + Clear Edit Message From 80c843227e809bd19217112fad7c52eb545c981c Mon Sep 17 00:00:00 2001 From: sowjanyakch Date: Tue, 30 Jan 2024 09:45:15 +0100 Subject: [PATCH 11/22] System Messages and Error handling --- .../com/nextcloud/talk/chat/ChatActivity.kt | 35 +++++++++++++++++-- .../talk/models/json/chat/ChatMessage.kt | 1 + .../EnumSystemMessageTypeConverter.kt | 2 ++ app/src/main/res/values/strings.xml | 2 ++ 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 3f5bd5366..bcb3e8bd9 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -84,6 +84,7 @@ import android.widget.RelativeLayout.BELOW import android.widget.RelativeLayout.LayoutParams import android.widget.SeekBar import android.widget.TextView +import android.widget.Toast import androidx.activity.OnBackPressedCallback import androidx.appcompat.view.ContextThemeWrapper import androidx.core.content.ContextCompat @@ -893,8 +894,25 @@ class ChatActivity : // unused atm } - override fun onNext(t: ChatOCSSingleMessage) { + override fun onNext(message: ChatOCSSingleMessage) { //unused atm + when(message.meta!!.statusCode){ + HTTP_BAD_REQUEST -> { + Toast.makeText(context, + getString(R.string.edit_error_24_hours_old_message),Toast.LENGTH_SHORT) + .show() + } + HTTP_FORBIDDEN -> { + Toast.makeText(context, + getString(R.string.conversation_is_read_only), + Toast.LENGTH_SHORT).show() + } + HTTP_NOT_FOUND -> { + Toast.makeText(context, + "Conversation Cannot be Found", + Toast.LENGTH_SHORT).show() + } + } } override fun onError(e: Throwable) { @@ -904,7 +922,7 @@ class ChatActivity : clearEditUI() } }) - // remove last item from list + } private fun clearEditUI() { @@ -912,6 +930,7 @@ class ChatActivity : binding.messageInputView.clearEditMessage.visibility = View.GONE editableBehaviorSubject.onNext(false) binding.messageInputView.inputEditText.setText("") + } private fun themeMessageInputView() { @@ -3820,6 +3839,10 @@ class ChatActivity : } else if (isPollVotedMessage(currentMessage)) { // delete poll system messages chatMessageIterator.remove() + }else if(isEditMessage(currentMessage)){ + if (!chatMessageMap.containsKey(currentMessage.value.parentMessage!!.id)){ + chatMessageIterator.remove() + } } } return chatMessageMap.values.toList() @@ -3860,6 +3883,11 @@ class ChatActivity : currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION_REVOKED } + private fun isEditMessage(currentMessage:MutableMap.MutableEntry):Boolean{ + return currentMessage.value.parentMessage != null && currentMessage.value.systemMessageType == ChatMessage + .SystemMessageType.MESSAGE_EDITED + } + private fun isPollVotedMessage(currentMessage: MutableMap.MutableEntry): Boolean { return currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.POLL_VOTED } @@ -4672,6 +4700,9 @@ class ChatActivity : private const val STATUS_SIZE_IN_DP = 9f private const val HTTP_CODE_NOT_MODIFIED = 304 private const val HTTP_CODE_PRECONDITION_FAILED = 412 + private const val HTTP_BAD_REQUEST = 400 + private const val HTTP_FORBIDDEN = 403 + private const val HTTP_NOT_FOUND = 404 private const val QUOTED_MESSAGE_IMAGE_MAX_HEIGHT = 96f private const val MENTION_AUTO_COMPLETE_ELEVATION = 6f private const val MESSAGE_PULL_LIMIT = 100 diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt index c9d70038a..3b6db4fd7 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt @@ -507,6 +507,7 @@ data class ChatMessage( GUEST_MODERATOR_PROMOTED, GUEST_MODERATOR_DEMOTED, MESSAGE_DELETED, + MESSAGE_EDITED, FILE_SHARED, OBJECT_SHARED, MATTERBRIDGE_CONFIG_ADDED, diff --git a/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumSystemMessageTypeConverter.kt b/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumSystemMessageTypeConverter.kt index 9fe1a36b3..807e7a914 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumSystemMessageTypeConverter.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumSystemMessageTypeConverter.kt @@ -127,6 +127,7 @@ class EnumSystemMessageTypeConverter : StringBasedTypeConverter GUEST_MODERATOR_PROMOTED "guest_moderator_demoted" -> GUEST_MODERATOR_DEMOTED "message_deleted" -> MESSAGE_DELETED + "message_edited" -> ChatMessage.SystemMessageType.MESSAGE_EDITED "file_shared" -> FILE_SHARED "object_shared" -> OBJECT_SHARED "matterbridge_config_added" -> MATTERBRIDGE_CONFIG_ADDED @@ -193,6 +194,7 @@ class EnumSystemMessageTypeConverter : StringBasedTypeConverter "guest_moderator_promoted" GUEST_MODERATOR_DEMOTED -> "guest_moderator_demoted" MESSAGE_DELETED -> "message_deleted" + ChatMessage.SystemMessageType.MESSAGE_EDITED -> "message_edited" FILE_SHARED -> "file_shared" OBJECT_SHARED -> "object_shared" MATTERBRIDGE_CONFIG_ADDED -> "matterbridge_config_added" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6bb941f8d..b87215ad3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -790,4 +790,6 @@ How to translate with transifex: Edit message Send Edit Message Clear Edit Message + Cannot Edit Messages older than 24 hours + Conversation is read Only From 7d4abf19a1666f50a4b8f37c8b2139e7cc642131 Mon Sep 17 00:00:00 2001 From: sowjanyakch Date: Wed, 31 Jan 2024 09:22:13 +0100 Subject: [PATCH 12/22] layout - edit message view --- app/src/main/res/layout/activity_chat.xml | 7 ++ app/src/main/res/layout/edit_message_view.xml | 64 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 app/src/main/res/layout/edit_message_view.xml diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml index f2b9361ca..d79085ec8 100644 --- a/app/src/main/res/layout/activity_chat.xml +++ b/app/src/main/res/layout/activity_chat.xml @@ -133,6 +133,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From eb27b7039fd4da977c7980ddee44eab173684073 Mon Sep 17 00:00:00 2001 From: sowjanyakch Date: Thu, 1 Feb 2024 13:08:18 +0100 Subject: [PATCH 13/22] Update Edit Message --- .../java/com/nextcloud/talk/api/NcApi.java | 3 +- .../com/nextcloud/talk/chat/ChatActivity.kt | 54 +++++++------------ .../com/nextcloud/talk/ui/MessageInput.kt | 2 - app/src/main/res/layout/activity_chat.xml | 37 +++++++------ app/src/main/res/layout/edit_message_view.xml | 12 ++--- .../main/res/layout/view_message_input.xml | 16 +----- app/src/main/res/values/strings.xml | 1 + 7 files changed, 44 insertions(+), 81 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApi.java b/app/src/main/java/com/nextcloud/talk/api/NcApi.java index 98af5ede2..a10b5add3 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java +++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java @@ -334,7 +334,8 @@ public interface NcApi { @FormUrlEncoded @PUT - Observable editChatMessage(@Header("Authorization") String authorization, @Url String url, @Field("message") String message); + Observable editChatMessage(@Header("Authorization") String authorization, @Url String url, @Field( + "message") String message); @GET Observable> getSharedItems(@Header("Authorization") String authorization, @Url String url, @Query("objectType") String objectType, @Nullable @Query("lastKnownMessageId") Integer lastKnownMessageId, @Nullable @Query("limit") Integer limit); diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index bcb3e8bd9..a0aa39709 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -84,7 +84,6 @@ import android.widget.RelativeLayout.BELOW import android.widget.RelativeLayout.LayoutParams import android.widget.SeekBar import android.widget.TextView -import android.widget.Toast import androidx.activity.OnBackPressedCallback import androidx.appcompat.view.ContextThemeWrapper import androidx.core.content.ContextCompat @@ -168,7 +167,6 @@ import com.nextcloud.talk.models.domain.ObjectType import com.nextcloud.talk.models.domain.ReactionAddedModel import com.nextcloud.talk.models.domain.ReactionDeletedModel import com.nextcloud.talk.models.json.chat.ChatMessage -import com.nextcloud.talk.models.json.chat.ChatOCSSingleMessage import com.nextcloud.talk.models.json.chat.ChatOverall import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage import com.nextcloud.talk.models.json.chat.ReadStatus @@ -762,12 +760,13 @@ class ChatActivity : private fun initMessageInputView() { val filters = arrayOfNulls(1) val lengthFilter = CapabilitiesUtilNew.getMessageMaxLength(conversationUser) + binding.editView.editMessageView.visibility = GONE if (editableBehaviorSubject.value!!) { val editableText = Editable.Factory.getInstance().newEditable(editMessage.message) binding.messageInputView.inputEditText.text = editableText binding.messageInputView.inputEditText.setSelection(editableText.length) - + binding.editView.editMessage.setText(editMessage.message) } filters[0] = InputFilter.LengthFilter(lengthFilter) @@ -835,7 +834,7 @@ class ChatActivity : binding.messageInputView.messageSendButton.visibility = View.GONE binding.messageInputView.recordAudioButton.visibility = View.GONE binding.messageInputView.editMessageButton.visibility = View.VISIBLE - binding.messageInputView.clearEditMessage.visibility = View.VISIBLE + binding.editView.editMessageView.visibility = View.VISIBLE } if (sharedText.isNotEmpty()) { @@ -851,13 +850,13 @@ class ChatActivity : } binding.messageInputView.editMessageButton.setOnClickListener { - if(editMessage.message == editedTextBehaviorSubject.value!!){ + if (editMessage.message == editedTextBehaviorSubject.value!!) { clearEditUI() return@setOnClickListener } editMessageAPI(editMessage, editedMessageText = editedTextBehaviorSubject.value!!) } - binding.messageInputView.clearEditMessage.setOnClickListener { + binding.editView.clearEdit.setOnClickListener { clearEditUI() } @@ -889,48 +888,33 @@ class ChatActivity : ), editedMessageText )?.subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) - ?.subscribe(object : Observer { + ?.subscribe(object : Observer { override fun onSubscribe(d: Disposable) { // unused atm } - override fun onNext(message: ChatOCSSingleMessage) { - //unused atm - when(message.meta!!.statusCode){ - HTTP_BAD_REQUEST -> { - Toast.makeText(context, - getString(R.string.edit_error_24_hours_old_message),Toast.LENGTH_SHORT) - .show() - } - HTTP_FORBIDDEN -> { - Toast.makeText(context, - getString(R.string.conversation_is_read_only), - Toast.LENGTH_SHORT).show() - } - HTTP_NOT_FOUND -> { - Toast.makeText(context, - "Conversation Cannot be Found", - Toast.LENGTH_SHORT).show() - } - } + override fun onNext(messageEdited: ChatOverallSingleMessage) { + message.message = messageEdited.ocs?.data?.parentMessage?.text + adapter?.update(message) + adapter?.notifyDataSetChanged() + clearEditUI() } override fun onError(e: Throwable) { + } override fun onComplete() { - clearEditUI() + } }) - } private fun clearEditUI() { binding.messageInputView.editMessageButton.visibility = GONE - binding.messageInputView.clearEditMessage.visibility = View.GONE editableBehaviorSubject.onNext(false) binding.messageInputView.inputEditText.setText("") - + binding.editView.editMessageView.visibility = GONE } private fun themeMessageInputView() { @@ -1004,7 +988,7 @@ class ChatActivity : ) adapter?.setLoadMoreListener(this) - adapter?.setDateHeadersFormatter { format(it) } + adapter?.setDateHeadersFormatter {format(it)} adapter?.setOnMessageViewLongClickListener { view, message -> onMessageViewLongClick(view, message) } adapter?.registerViewClickListener( R.id.playPauseBtn @@ -3839,10 +3823,8 @@ class ChatActivity : } else if (isPollVotedMessage(currentMessage)) { // delete poll system messages chatMessageIterator.remove() - }else if(isEditMessage(currentMessage)){ - if (!chatMessageMap.containsKey(currentMessage.value.parentMessage!!.id)){ - chatMessageIterator.remove() - } + } else if (isEditMessage(currentMessage)) { + chatMessageIterator.remove() } } return chatMessageMap.values.toList() @@ -3883,7 +3865,7 @@ class ChatActivity : currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION_REVOKED } - private fun isEditMessage(currentMessage:MutableMap.MutableEntry):Boolean{ + private fun isEditMessage(currentMessage: MutableMap.MutableEntry): Boolean { return currentMessage.value.parentMessage != null && currentMessage.value.systemMessageType == ChatMessage .SystemMessageType.MESSAGE_EDITED } diff --git a/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt b/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt index c66a04934..f42a657d5 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt @@ -44,7 +44,6 @@ class MessageInput : MessageInput { lateinit var micInputCloud: MicInputCloud lateinit var playPauseBtn: MaterialButton lateinit var editMessageButton:ImageButton - lateinit var clearEditMessage:ImageButton lateinit var seekBar: SeekBar constructor(context: Context?) : super(context) { @@ -72,7 +71,6 @@ class MessageInput : MessageInput { playPauseBtn = findViewById(R.id.playPauseBtn) seekBar = findViewById(R.id.seekbar) editMessageButton = findViewById(R.id.editMessageButton) - clearEditMessage = findViewById(R.id.clearEditButton) } var messageInput: EmojiEditText diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml index d79085ec8..1ed31932f 100644 --- a/app/src/main/res/layout/activity_chat.xml +++ b/app/src/main/res/layout/activity_chat.xml @@ -140,8 +140,8 @@ android:id="@+id/messagesListView" android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingBottom="20dp" android:clipToPadding="false" + android:paddingBottom="20dp" android:visibility="gone" app:dateHeaderTextSize="13sp" app:incomingBubblePaddingBottom="@dimen/message_bubble_corners_vertical_padding" @@ -170,11 +170,10 @@ app:outcomingTextSize="@dimen/chat_text_size" app:outcomingTimeTextSize="12sp" app:textAutoLink="all" - tools:visibility="visible"/> + tools:visibility="visible" /> @@ -227,9 +227,9 @@ android:id="@+id/typing_indicator_wrapper" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="vertical" android:layout_alignParentBottom="true" - android:layout_marginBottom="-19dp"> + android:layout_marginBottom="-19dp" + android:orientation="vertical"> - + tools:ignore="Overdraw" + tools:text="Marcel is typing"> @@ -259,12 +258,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> - - + + - - - - + android:text = "@string/nc_edit_message_text"> @@ -61,4 +58,3 @@ - \ No newline at end of file diff --git a/app/src/main/res/layout/view_message_input.xml b/app/src/main/res/layout/view_message_input.xml index 90573caeb..59dd049ae 100644 --- a/app/src/main/res/layout/view_message_input.xml +++ b/app/src/main/res/layout/view_message_input.xml @@ -71,7 +71,7 @@ android:layout_height="wrap_content" android:layout_below="@+id/quotedChatMessageView" android:layout_centerHorizontal="true" - android:layout_toStartOf="@id/messageSendButton" + android:layout_marginEnd = "48dp" android:layout_toEndOf="@id/smileyButton" android:imeOptions="actionDone" android:inputType="textAutoCorrect|textMultiLine|textCapSentences" @@ -253,20 +253,6 @@ tools:visibility = "visible" android:contentDescription="@string/nc_send_edit_message" /> - - Clear Edit Message Cannot Edit Messages older than 24 hours Conversation is read Only + Edit Message Text From 426c8823c53a6df8787aea8f8b193e607167c1ab Mon Sep 17 00:00:00 2001 From: sowjanyakch Date: Thu, 1 Feb 2024 22:34:20 +0100 Subject: [PATCH 14/22] add (edited) tag in incoming and outgoing messages layout --- .../messages/IncomingTextMessageViewHolder.kt | 8 ++++++++ .../messages/OutcomingTextMessageViewHolder.kt | 7 +++++++ .../layout/item_custom_incoming_text_message.xml | 15 ++++++++++++++- .../layout/item_custom_outcoming_text_message.xml | 15 +++++++++++++++ app/src/main/res/values/strings.xml | 1 + 5 files changed, 45 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt index 17333d452..07d2412e7 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt @@ -114,6 +114,14 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) : binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) binding.messageText.text = processedMessageText + if (message.parentMessage?.id != null && message.systemMessageType == ChatMessage.SystemMessageType + .MESSAGE_EDITED + ) { + binding.messageType.visibility = View.VISIBLE + } else { + binding.messageType.visibility = View.GONE + } + binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp) // parent message handling diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt index 680af44a6..9142ee679 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt @@ -100,6 +100,13 @@ class OutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessageViewH setBubbleOnChatMessage(message) binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) + if (message.parentMessage?.id != null && message.systemMessageType == ChatMessage.SystemMessageType + .MESSAGE_EDITED + ) { + binding.messageType.visibility = View.VISIBLE + } else { + binding.messageType.visibility = View.GONE + } binding.messageTime.layoutParams = layoutParams viewThemeUtils.platform.colorTextView(binding.messageText, ColorRole.ON_SURFACE_VARIANT) binding.messageText.text = processedMessageText diff --git a/app/src/main/res/layout/item_custom_incoming_text_message.xml b/app/src/main/res/layout/item_custom_incoming_text_message.xml index fe6ac95ed..b5dba9ec4 100644 --- a/app/src/main/res/layout/item_custom_incoming_text_message.xml +++ b/app/src/main/res/layout/item_custom_incoming_text_message.xml @@ -78,6 +78,19 @@ app:layout_wrapBefore="true" tools:text="Talk to you later!" /> + + + + - + \ No newline at end of file diff --git a/app/src/main/res/layout/item_custom_outcoming_text_message.xml b/app/src/main/res/layout/item_custom_outcoming_text_message.xml index 9298f5035..3147b62d8 100644 --- a/app/src/main/res/layout/item_custom_outcoming_text_message.xml +++ b/app/src/main/res/layout/item_custom_outcoming_text_message.xml @@ -57,6 +57,20 @@ android:textIsSelectable="false" tools:text="Talk to you later!" /> + + + + + + Cannot Edit Messages older than 24 hours Conversation is read Only Edit Message Text + (edited) From d27c7a6212e68b85818f62ffd2d4998268c20cba Mon Sep 17 00:00:00 2001 From: sowjanyakch Date: Mon, 5 Feb 2024 22:43:00 +0100 Subject: [PATCH 15/22] UI improvements --- .../messages/IncomingTextMessageViewHolder.kt | 9 - .../OutcomingTextMessageViewHolder.kt | 7 - .../java/com/nextcloud/talk/api/NcApi.java | 320 +++++++++++++----- .../com/nextcloud/talk/chat/ChatActivity.kt | 34 +- .../com/nextcloud/talk/ui/MessageInput.kt | 2 +- .../talk/ui/dialog/MessageActionsDialog.kt | 2 +- app/src/main/res/drawable/ic_check_24.xml | 27 +- app/src/main/res/values/strings.xml | 2 + 8 files changed, 296 insertions(+), 107 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt index 07d2412e7..4f1c494de 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt @@ -79,7 +79,6 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) : sharedApplication!!.componentApplication.inject(this) setAvatarAndAuthorOnMessageItem(message) - colorizeMessageBubble(message) itemView.isSelected = false @@ -114,14 +113,6 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) : binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) binding.messageText.text = processedMessageText - if (message.parentMessage?.id != null && message.systemMessageType == ChatMessage.SystemMessageType - .MESSAGE_EDITED - ) { - binding.messageType.visibility = View.VISIBLE - } else { - binding.messageType.visibility = View.GONE - } - binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp) // parent message handling diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt index 9142ee679..680af44a6 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt @@ -100,13 +100,6 @@ class OutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessageViewH setBubbleOnChatMessage(message) binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) - if (message.parentMessage?.id != null && message.systemMessageType == ChatMessage.SystemMessageType - .MESSAGE_EDITED - ) { - binding.messageType.visibility = View.VISIBLE - } else { - binding.messageType.visibility = View.GONE - } binding.messageTime.layoutParams = layoutParams viewThemeUtils.platform.colorTextView(binding.messageText, ColorRole.ON_SURFACE_VARIANT) binding.messageText.text = processedMessageText diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApi.java b/app/src/main/java/com/nextcloud/talk/api/NcApi.java index a10b5add3..493067de2 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java +++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java @@ -24,7 +24,6 @@ package com.nextcloud.talk.api; import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall; -import com.nextcloud.talk.models.json.chat.ChatOCSSingleMessage; import com.nextcloud.talk.models.json.chat.ChatOverall; import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage; import com.nextcloud.talk.models.json.chat.ChatShareOverall; @@ -97,14 +96,19 @@ public interface NcApi { */ @GET - Observable getContactsWithSearchParam(@Header("Authorization") String authorization, @Url String url, @Nullable @Query("shareTypes[]") List listOfShareTypes, @QueryMap Map options); + Observable getContactsWithSearchParam(@Header("Authorization") String authorization, + @Url String url, + @Nullable @Query("shareTypes[]") List listOfShareTypes, + @QueryMap Map options); /* Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /room */ @GET - Observable getRooms(@Header("Authorization") String authorization, @Url String url, @Nullable @Query("includeStatus") Boolean includeStatus); + Observable getRooms(@Header("Authorization") String authorization, + @Url String url, + @Nullable @Query("includeStatus") Boolean includeStatus); /* Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /room/roomToken @@ -121,7 +125,9 @@ public interface NcApi { */ @POST - Observable createRoom(@Header("Authorization") String authorization, @Url String url, @QueryMap Map options); + Observable createRoom(@Header("Authorization") String authorization, + @Url String url, + @QueryMap Map options); /* QueryMap items are as follows: @@ -132,12 +138,16 @@ public interface NcApi { @FormUrlEncoded @PUT - Observable renameRoom(@Header("Authorization") String authorization, @Url String url, @Field("roomName") String roomName); + Observable renameRoom(@Header("Authorization") String authorization, + @Url String url, + @Field("roomName") String roomName); @FormUrlEncoded @PUT - Observable setConversationDescription(@Header("Authorization") String authorization, @Url String url, @Field("description") String description); + Observable setConversationDescription(@Header("Authorization") String authorization, + @Url String url, + @Field("description") String description); /* QueryMap items are as follows: @@ -146,32 +156,48 @@ public interface NcApi { Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /room/roomToken/participants */ @POST - Observable addParticipant(@Header("Authorization") String authorization, @Url String url, @QueryMap Map options); + Observable addParticipant(@Header("Authorization") String authorization, + @Url String url, + @QueryMap Map options); @POST - Observable resendParticipantInvitations(@Header("Authorization") String authorization, @Url String url); + Observable resendParticipantInvitations(@Header("Authorization") String authorization, + @Url String url); // also used for removing a guest from a conversation @Deprecated @DELETE - Observable removeParticipantFromConversation(@Header("Authorization") String authorization, @Url String url, @Query("participant") String participantId); + Observable removeParticipantFromConversation(@Header("Authorization") String authorization, + @Url String url, + @Query("participant") String participantId); @DELETE - Observable removeAttendeeFromConversation(@Header("Authorization") String authorization, @Url String url, @Query("attendeeId") Long attendeeId); + Observable removeAttendeeFromConversation(@Header("Authorization") String authorization, + @Url String url, + @Query("attendeeId") Long attendeeId); @Deprecated @POST - Observable promoteUserToModerator(@Header("Authorization") String authorization, @Url String url, @Query("participant") String participantId); + Observable promoteUserToModerator(@Header("Authorization") String authorization, + @Url String url, + @Query("participant") String participantId); @Deprecated @DELETE - Observable demoteModeratorToUser(@Header("Authorization") String authorization, @Url String url, @Query("participant") String participantId); + Observable demoteModeratorToUser(@Header("Authorization") String authorization, + @Url String url, + @Query("participant") String participantId); @POST - Observable promoteAttendeeToModerator(@Header("Authorization") String authorization, @Url String url, @Query("attendeeId") Long attendeeId); + Observable promoteAttendeeToModerator(@Header("Authorization") String authorization, + @Url String url, + @Query("attendeeId") Long attendeeId); @DELETE - Observable demoteAttendeeFromModerator(@Header("Authorization") String authorization, @Url String url, @Query("attendeeId") Long attendeeId); + Observable demoteAttendeeFromModerator(@Header("Authorization") String authorization, + @Url String url, + @Query("attendeeId") Long attendeeId); /* Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /room/roomToken/participants/self @@ -202,11 +228,15 @@ public interface NcApi { Observable getPeersForCall(@Header("Authorization") String authorization, @Url String url); @GET - Observable getPeersForCall(@Header("Authorization") String authorization, @Url String url, @QueryMap Map fields); + Observable getPeersForCall(@Header("Authorization") String authorization, + @Url String url, + @QueryMap Map fields); @FormUrlEncoded @POST - Observable joinRoom(@Nullable @Header("Authorization") String authorization, @Url String url, @Nullable @Field("password") String password); + Observable joinRoom(@Nullable @Header("Authorization") String authorization, + @Url String url, + @Nullable @Field("password") String password); @DELETE Observable leaveRoom(@Nullable @Header("Authorization") String authorization, @Url String url); @@ -217,7 +247,11 @@ public interface NcApi { @FormUrlEncoded @POST - Observable joinCall(@Nullable @Header("Authorization") String authorization, @Url String url, @Field("flags") Integer inCall, @Field("silent") Boolean callWithoutNotification, @Nullable @Field("recordingConsent") Boolean recordingConsent); + Observable joinCall(@Nullable @Header("Authorization") String authorization, + @Url String url, + @Field("flags") Integer inCall, + @Field("silent") Boolean callWithoutNotification, + @Nullable @Field("recordingConsent") Boolean recordingConsent); /* Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /call/callToken @@ -226,7 +260,8 @@ public interface NcApi { Observable leaveCall(@Nullable @Header("Authorization") String authorization, @Url String url); @GET - Observable getSignalingSettings(@Nullable @Header("Authorization") String authorization, @Url String url); + Observable getSignalingSettings(@Nullable @Header("Authorization") String authorization, + @Url String url); /* QueryMap items are as follows: @@ -236,13 +271,16 @@ public interface NcApi { */ @FormUrlEncoded @POST - Observable sendSignalingMessages(@Nullable @Header("Authorization") String authorization, @Url String url, @Field("messages") String messages); + Observable sendSignalingMessages(@Nullable @Header("Authorization") String authorization, + @Url String url, + @Field("messages") String messages); /* Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /signaling */ @GET - Observable pullSignalingMessages(@Nullable @Header("Authorization") String authorization, @Url String url); + Observable pullSignalingMessages(@Nullable @Header("Authorization") String authorization, + @Url String url); /* QueryMap items are as follows: @@ -260,7 +298,11 @@ public interface NcApi { @FormUrlEncoded @PUT - Observable setUserData(@Header("Authorization") String authorization, @Url String url, @Field("key") String key, @Field("value") String value); + Observable setUserData(@Header("Authorization") String authorization, + @Url String url, + @Field("key") String key, + @Field("value") String value); + /* Server URL is: baseUrl + /status.php @@ -280,14 +322,21 @@ public interface NcApi { */ @POST - Observable registerDeviceForNotificationsWithNextcloud(@Header("Authorization") String authorization, @Url String url, @QueryMap Map options); + Observable registerDeviceForNotificationsWithNextcloud( + @Header("Authorization") String authorization, + @Url String url, + @QueryMap Map options); @DELETE - Observable unregisterDeviceForNotificationsWithNextcloud(@Header("Authorization") String authorization, @Url String url); + Observable unregisterDeviceForNotificationsWithNextcloud( + @Header("Authorization") String authorization, + @Url String url); @FormUrlEncoded @POST - Observable registerDeviceForNotificationsWithPushProxy(@Url String url, @FieldMap Map fields); + Observable registerDeviceForNotificationsWithPushProxy(@Url String url, + @FieldMap Map fields); + /* QueryMap items are as follows: @@ -296,15 +345,20 @@ public interface NcApi { - "userPublicKey": "{{userPublicKey}}" */ @DELETE - Observable unregisterDeviceForNotificationsWithProxy(@Url String url, @QueryMap Map fields); + Observable unregisterDeviceForNotificationsWithProxy(@Url String url, + @QueryMap Map fields); @FormUrlEncoded @PUT - Observable setPassword(@Header("Authorization") String authorization, @Url String url, @Field("password") String password); + Observable setPassword(@Header("Authorization") String authorization, + @Url String url, + @Field("password") String password); @FormUrlEncoded @PUT - Observable> setPassword2(@Header("Authorization") String authorization, @Url String url, @Field("password") String password); + Observable> setPassword2(@Header("Authorization") String authorization, + @Url String url, + @Field("password") String password); @GET Observable getCapabilities(@Header("Authorization") String authorization, @Url String url); @@ -320,7 +374,9 @@ public interface NcApi { - "lastKnownMessageId", int, use one from X-Chat-Last-Given */ @GET - Observable> pullChatMessages(@Header("Authorization") String authorization, @Url String url, @QueryMap Map fields); + Observable> pullChatMessages(@Header("Authorization") String authorization, + @Url String url, + @QueryMap Map fields); /* Fieldmap items are as follows: @@ -330,70 +386,113 @@ public interface NcApi { @FormUrlEncoded @POST - Observable sendChatMessage(@Header("Authorization") String authorization, @Url String url, @Field("message") CharSequence message, @Field("actorDisplayName") String actorDisplayName, @Field("replyTo") Integer replyTo, @Field("silent") Boolean sendWithoutNotification); + Observable sendChatMessage(@Header("Authorization") String authorization, + @Url String url, + @Field("message") CharSequence message, + @Field("actorDisplayName") String actorDisplayName, + @Field("replyTo") Integer replyTo, + @Field("silent") Boolean sendWithoutNotification); @FormUrlEncoded @PUT - Observable editChatMessage(@Header("Authorization") String authorization, @Url String url, @Field( - "message") String message); + Observable editChatMessage(@Header("Authorization") String authorization, + @Url String url, + @Field("message") String message); @GET - Observable> getSharedItems(@Header("Authorization") String authorization, @Url String url, @Query("objectType") String objectType, @Nullable @Query("lastKnownMessageId") Integer lastKnownMessageId, @Nullable @Query("limit") Integer limit); + Observable> getSharedItems( + @Header("Authorization") String authorization, + @Url String url, + @Query("objectType") String objectType, + @Nullable @Query("lastKnownMessageId") Integer lastKnownMessageId, + @Nullable @Query("limit") Integer limit); @GET - Observable> getSharedItemsOverview(@Header("Authorization") String authorization, @Url String url, @Nullable @Query("limit") Integer limit); + Observable> getSharedItemsOverview(@Header("Authorization") String authorization, + @Url String url, + @Nullable @Query("limit") Integer limit); @GET - Observable getMentionAutocompleteSuggestions(@Header("Authorization") String authorization, @Url String url, @Query("search") String query, @Nullable @Query("limit") Integer limit, @QueryMap Map fields); + Observable getMentionAutocompleteSuggestions(@Header("Authorization") String authorization, + @Url String url, + @Query("search") String query, + @Nullable @Query("limit") Integer limit, + @QueryMap Map fields); // Url is: /api/{apiVersion}/room/{token}/pin @POST - Observable addConversationToFavorites(@Header("Authorization") String authorization, @Url String url); + Observable addConversationToFavorites(@Header("Authorization") String authorization, + @Url String url); // Url is: /api/{apiVersion}/room/{token}/favorites @DELETE - Observable removeConversationFromFavorites(@Header("Authorization") String authorization, @Url String url); + Observable removeConversationFromFavorites(@Header("Authorization") String authorization, + @Url String url); @GET - Observable getNcNotification(@Header("Authorization") String authorization, @Url String url); + Observable getNcNotification(@Header("Authorization") String authorization, + @Url String url); @FormUrlEncoded @POST - Observable setNotificationLevel(@Header("Authorization") String authorization, @Url String url, @Field("level") int level); + Observable setNotificationLevel(@Header("Authorization") String authorization, + @Url String url, + @Field("level") int level); @FormUrlEncoded @PUT - Observable setReadOnlyState(@Header("Authorization") String authorization, @Url String url, @Field("state") int state); + Observable setReadOnlyState(@Header("Authorization") String authorization, + @Url String url, + @Field("state") int state); @FormUrlEncoded @POST - Observable createRemoteShare(@Nullable @Header("Authorization") String authorization, @Url String url, @Field("path") String remotePath, @Field("shareWith") String roomToken, @Field("shareType") String shareType, @Field("talkMetaData") String talkMetaData); + Observable createRemoteShare(@Nullable @Header("Authorization") String authorization, + @Url String url, + @Field("path") String remotePath, + @Field("shareWith") String roomToken, + @Field("shareType") String shareType, + @Field("talkMetaData") String talkMetaData); @FormUrlEncoded @PUT - Observable setLobbyForConversation(@Header("Authorization") String authorization, @Url String url, @Field("state") Integer state, @Field("timer") Long timer); + Observable setLobbyForConversation(@Header("Authorization") String authorization, + @Url String url, + @Field("state") Integer state, + @Field("timer") Long timer); @POST - Observable setReadStatusPrivacy(@Header("Authorization") String authorization, @Url String url, @Body RequestBody body); + Observable setReadStatusPrivacy(@Header("Authorization") String authorization, + @Url String url, + @Body RequestBody body); @POST - Observable setTypingStatusPrivacy(@Header("Authorization") String authorization, @Url String url, @Body RequestBody body); + Observable setTypingStatusPrivacy(@Header("Authorization") String authorization, + @Url String url, + @Body RequestBody body); @POST - Observable searchContactsByPhoneNumber(@Header("Authorization") String authorization, @Url String url, @Body RequestBody search); + Observable searchContactsByPhoneNumber(@Header("Authorization") String authorization, + @Url String url, + @Body RequestBody search); @PUT - Observable> uploadFile(@Header("Authorization") String authorization, @Url String url, @Body RequestBody body); + Observable> uploadFile(@Header("Authorization") String authorization, + @Url String url, + @Body RequestBody body); @HEAD - Observable> checkIfFileExists(@Header("Authorization") String authorization, @Url String url); + Observable> checkIfFileExists(@Header("Authorization") String authorization, + @Url String url); @GET - Call downloadFile(@Header("Authorization") String authorization, @Url String url); + Call downloadFile(@Header("Authorization") String authorization, + @Url String url); @DELETE - Observable deleteChatMessage(@Header("Authorization") String authorization, @Url String url); + Observable deleteChatMessage(@Header("Authorization") String authorization, + @Url String url); @DELETE Observable deleteAvatar(@Header("Authorization") String authorization, @Url String url); @@ -404,28 +503,40 @@ public interface NcApi { @Multipart @POST - Observable uploadAvatar(@Header("Authorization") String authorization, @Url String url, @Part MultipartBody.Part attachment); + Observable uploadAvatar(@Header("Authorization") String authorization, + @Url String url, + @Part MultipartBody.Part attachment); @Multipart @POST - Observable uploadConversationAvatar(@Header("Authorization") String authorization, @Url String url, @Part MultipartBody.Part attachment); + Observable uploadConversationAvatar(@Header("Authorization") String authorization, + @Url String url, + @Part MultipartBody.Part attachment); @GET - Observable getEditableUserProfileFields(@Header("Authorization") String authorization, @Url String url); + Observable getEditableUserProfileFields(@Header("Authorization") String authorization, + @Url String url); @GET - Call downloadResizedImage(@Header("Authorization") String authorization, @Url String url); + Call downloadResizedImage(@Header("Authorization") String authorization, + @Url String url); @FormUrlEncoded @POST - Observable sendLocation(@Header("Authorization") String authorization, @Url String url, @Field("objectType") String objectType, @Field("objectId") String objectId, @Field("metaData") String metaData); + Observable sendLocation(@Header("Authorization") String authorization, + @Url String url, + @Field("objectType") String objectType, + @Field("objectId") String objectId, + @Field("metaData") String metaData); @DELETE Observable clearChatHistory(@Header("Authorization") String authorization, @Url String url); @FormUrlEncoded @POST - Observable notificationCalls(@Header("Authorization") String authorization, @Url String url, @Field("level") Integer level); + Observable notificationCalls(@Header("Authorization") String authorization, + @Url String url, + @Field("level") Integer level); @GET Observable hoverCard(@Header("Authorization") String authorization, @Url String url); @@ -433,7 +544,9 @@ public interface NcApi { // Url is: /api/{apiVersion}/chat/{token}/read @FormUrlEncoded @POST - Observable setChatReadMarker(@Header("Authorization") String authorization, @Url String url, @Field("lastReadMessage") int lastReadMessage); + Observable setChatReadMarker(@Header("Authorization") String authorization, + @Url String url, + @Field("lastReadMessage") int lastReadMessage); // Url is: /api/{apiVersion}/chat/{token}/read @DELETE @@ -461,62 +574,100 @@ public interface NcApi { @FormUrlEncoded @PUT - Observable setPredefinedStatusMessage(@Header("Authorization") String authorization, @Url String url, @Field("messageId") String selectedPredefinedMessageId, @Field("clearAt") Long clearAt); + Observable setPredefinedStatusMessage(@Header("Authorization") String authorization, + @Url String url, + @Field("messageId") String selectedPredefinedMessageId, + @Field("clearAt") Long clearAt); @FormUrlEncoded @PUT - Observable setCustomStatusMessage(@Header("Authorization") String authorization, @Url String url, @Field("statusIcon") String statusIcon, @Field("message") String message, @Field("clearAt") Long clearAt); + Observable setCustomStatusMessage(@Header("Authorization") String authorization, + @Url String url, + @Field("statusIcon") String statusIcon, + @Field("message") String message, + @Field("clearAt") Long clearAt); @FormUrlEncoded @PUT - Observable setStatusType(@Header("Authorization") String authorization, @Url String url, @Field("statusType") String statusType); + Observable setStatusType(@Header("Authorization") String authorization, + @Url String url, + @Field("statusType") String statusType); @POST - Observable sendReaction(@Header("Authorization") String authorization, @Url String url, @Query("reaction") String reaction); + Observable sendReaction(@Header("Authorization") String authorization, + @Url String url, + @Query("reaction") String reaction); @DELETE - Observable deleteReaction(@Header("Authorization") String authorization, @Url String url, @Query("reaction") String reaction); + Observable deleteReaction(@Header("Authorization") String authorization, + @Url String url, + @Query("reaction") String reaction); @GET - Observable getReactions(@Header("Authorization") String authorization, @Url String url, @Query("reaction") String reaction); + Observable getReactions(@Header("Authorization") String authorization, + @Url String url, + @Query("reaction") String reaction); @GET - Observable performUnifiedSearch(@Header("Authorization") String authorization, @Url String url, @Query("term") String term, @Query("from") String fromUrl, @Query("limit") Integer limit, @Query("cursor") Integer cursor); + Observable performUnifiedSearch(@Header("Authorization") String authorization, + @Url String url, + @Query("term") String term, + @Query("from") String fromUrl, + @Query("limit") Integer limit, + @Query("cursor") Integer cursor); @GET - Observable getPoll(@Header("Authorization") String authorization, @Url String url); + Observable getPoll(@Header("Authorization") String authorization, + @Url String url); @FormUrlEncoded @POST - Observable createPoll(@Header("Authorization") String authorization, @Url String url, @Query("question") String question, @Field("options[]") List options, @Query("resultMode") Integer resultMode, @Query("maxVotes") Integer maxVotes); + Observable createPoll(@Header("Authorization") String authorization, + @Url String url, + @Query("question") String question, + @Field("options[]") List options, + @Query("resultMode") Integer resultMode, + @Query("maxVotes") Integer maxVotes); @FormUrlEncoded @POST - Observable votePoll(@Header("Authorization") String authorization, @Url String url, @Field("optionIds[]") List optionIds); + Observable votePoll(@Header("Authorization") String authorization, + @Url String url, + @Field("optionIds[]") List optionIds); @DELETE - Observable closePoll(@Header("Authorization") String authorization, @Url String url); + Observable closePoll(@Header("Authorization") String authorization, + @Url String url); @FormUrlEncoded @POST - Observable setMessageExpiration(@Header("Authorization") String authorization, @Url String url, @Field("seconds") Integer seconds); + Observable setMessageExpiration(@Header("Authorization") String authorization, + @Url String url, + @Field("seconds") Integer seconds); @GET - Observable getOpenGraph(@Header("Authorization") String authorization, @Url String url, @Query("reference") String urlToFindPreviewFor); + Observable getOpenGraph(@Header("Authorization") String authorization, + @Url String url, + @Query("reference") String urlToFindPreviewFor); @FormUrlEncoded @POST - Observable startRecording(@Header("Authorization") String authorization, @Url String url, @Field("status") Integer status); + Observable startRecording(@Header("Authorization") String authorization, + @Url String url, + @Field("status") Integer status); @DELETE - Observable stopRecording(@Header("Authorization") String authorization, @Url String url); + Observable stopRecording(@Header("Authorization") String authorization, + @Url String url); @POST - Observable requestAssistance(@Header("Authorization") String authorization, @Url String url); + Observable requestAssistance(@Header("Authorization") String authorization, + @Url String url); @DELETE - Observable withdrawRequestAssistance(@Header("Authorization") String authorization, @Url String url); + Observable withdrawRequestAssistance(@Header("Authorization") String authorization, + @Url String url); @POST Observable sendCommonPostRequest(@Header("Authorization") String authorization, @Url String url); @@ -526,22 +677,33 @@ public interface NcApi { @POST - Observable translateMessage(@Header("Authorization") String authorization, @Url String url, @Query("text") String text, @Query("toLanguage") String toLanguage, @Nullable @Query("fromLanguage") String fromLanguage); + Observable translateMessage(@Header("Authorization") String authorization, + @Url String url, + @Query("text") String text, + @Query("toLanguage") String toLanguage, + @Nullable @Query("fromLanguage") String fromLanguage); @GET - Observable getLanguages(@Header("Authorization") String authorization, @Url String url); + Observable getLanguages(@Header("Authorization") String authorization, + @Url String url); @GET - Observable getReminder(@Header("Authorization") String authorization, @Url String url); + Observable getReminder(@Header("Authorization") String authorization, + @Url String url); @DELETE - Observable deleteReminder(@Header("Authorization") String authorization, @Url String url); + Observable deleteReminder(@Header("Authorization") String authorization, + @Url String url); @FormUrlEncoded @POST - Observable setReminder(@Header("Authorization") String authorization, @Url String url, @Field("timestamp") int timestamp); + Observable setReminder(@Header("Authorization") String authorization, + @Url String url, + @Field("timestamp") int timestamp); @FormUrlEncoded @PUT - Observable setRecordingConsent(@Header("Authorization") String authorization, @Url String url, @Field("recordingConsent") int recordingConsent); -} + Observable setRecordingConsent(@Header("Authorization") String authorization, + @Url String url, + @Field("recordingConsent") int recordingConsent); +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index a0aa39709..aaa3c5c56 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -835,6 +835,7 @@ class ChatActivity : binding.messageInputView.recordAudioButton.visibility = View.GONE binding.messageInputView.editMessageButton.visibility = View.VISIBLE binding.editView.editMessageView.visibility = View.VISIBLE + binding.messageInputView.attachmentButton.visibility = View.GONE } if (sharedText.isNotEmpty()) { @@ -885,7 +886,8 @@ class ChatActivity : conversationUser?.baseUrl, roomToken, message?.id - ), editedMessageText + ), + editedMessageText )?.subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) ?.subscribe(object : Observer { @@ -894,6 +896,29 @@ class ChatActivity : } override fun onNext(messageEdited: ChatOverallSingleMessage) { + when (messageEdited.ocs?.meta?.statusCode) { + HTTP_BAD_REQUEST -> { + Snackbar.make( + binding.root, + getString(R.string.edit_error_24_hours_old_message), + Snackbar.LENGTH_LONG + ).show() + } + HTTP_FORBIDDEN -> { + Snackbar.make( + binding.root, + getString(R.string.conversation_is_read_only), + Snackbar.LENGTH_LONG + ).show() + } + HTTP_NOT_FOUND -> { + Snackbar.make( + binding.root, + "Conversation not found", + Snackbar.LENGTH_LONG + ).show() + } + } message.message = messageEdited.ocs?.data?.parentMessage?.text adapter?.update(message) adapter?.notifyDataSetChanged() @@ -901,11 +926,9 @@ class ChatActivity : } override fun onError(e: Throwable) { - } override fun onComplete() { - } }) } @@ -915,6 +938,7 @@ class ChatActivity : editableBehaviorSubject.onNext(false) binding.messageInputView.inputEditText.setText("") binding.editView.editMessageView.visibility = GONE + binding.messageInputView.attachmentButton.visibility = View.VISIBLE } private fun themeMessageInputView() { @@ -988,7 +1012,7 @@ class ChatActivity : ) adapter?.setLoadMoreListener(this) - adapter?.setDateHeadersFormatter {format(it)} + adapter?.setDateHeadersFormatter { format(it) } adapter?.setOnMessageViewLongClickListener { view, message -> onMessageViewLongClick(view, message) } adapter?.registerViewClickListener( R.id.playPauseBtn @@ -1084,7 +1108,6 @@ class ChatActivity : R.layout.item_system_message, this ) - messageHolders.registerContentType( CONTENT_TYPE_UNREAD_NOTICE_MESSAGE, UnreadNoticeMessageViewHolder::class.java, @@ -4333,7 +4356,6 @@ class ChatActivity : } private fun showMicrophoneButton(show: Boolean) { - if (show && CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "voice-message-sharing")) { Log.d(TAG, "Microphone shown") binding.messageInputView.messageSendButton.visibility = View.GONE diff --git a/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt b/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt index f42a657d5..18def2f05 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/MessageInput.kt @@ -43,7 +43,7 @@ class MessageInput : MessageInput { lateinit var sendVoiceRecording: ImageView lateinit var micInputCloud: MicInputCloud lateinit var playPauseBtn: MaterialButton - lateinit var editMessageButton:ImageButton + lateinit var editMessageButton: ImageButton lateinit var seekBar: SeekBar constructor(context: Context?) : super(context) { diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt index af44b342e..ae5b54af2 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt @@ -108,7 +108,7 @@ class MessageActionsDialog( hasUserActorId(message) && currentConversation?.type != ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL ) - initMenuEditMessage(CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "edit-messages")) + initMenuEditMessage(CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "edit-messages") && !message.isDeleted) initMenuDeleteMessage(showMessageDeletionButton) initMenuForwardMessage( ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() && diff --git a/app/src/main/res/drawable/ic_check_24.xml b/app/src/main/res/drawable/ic_check_24.xml index 2501e9fd9..fef2c90e2 100644 --- a/app/src/main/res/drawable/ic_check_24.xml +++ b/app/src/main/res/drawable/ic_check_24.xml @@ -1,5 +1,24 @@ - - + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 66980875e..1a27d9d5d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -794,4 +794,6 @@ How to translate with transifex: Conversation is read Only Edit Message Text (edited) + Conversation not found + From 683f924556567ffbe3f9cd9c96829d14a87aa35d Mon Sep 17 00:00:00 2001 From: sowjanyakch Date: Fri, 9 Feb 2024 10:03:57 +0100 Subject: [PATCH 16/22] Add editor name and message timestamp to MessageActionsDialog Signed-off-by: sowjanyakch --- .../messages/IncomingTextMessageViewHolder.kt | 14 +-- .../OutcomingTextMessageViewHolder.kt | 15 ++-- .../com/nextcloud/talk/chat/ChatActivity.kt | 90 ++++++++++++++++++- .../talk/models/json/chat/ChatMessage.kt | 12 +++ .../talk/ui/dialog/MessageActionsDialog.kt | 24 ++++- .../res/layout/dialog_message_actions.xml | 35 ++++++++ app/src/main/res/values/strings.xml | 3 +- 7 files changed, 175 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt index ae9d35fcb..961ddeb88 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt @@ -113,16 +113,14 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) : binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) binding.messageText.text = processedMessageText - if (message.parentMessage?.id != null && message.systemMessageType == ChatMessage.SystemMessageType - .MESSAGE_EDITED - ) { + if (message.lastEditTimestamp != 0L && !message.isDeleted) { binding.messageType.visibility = View.VISIBLE + binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp) } else { binding.messageType.visibility = View.GONE + binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp) } - binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp) - // parent message handling if (!message.isDeleted && message.parentMessage != null) { processParentMessage(message) @@ -244,6 +242,12 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) : } } + fun updateMessage(message: ChatMessage) { + binding.messageText.text = message.message + binding.messageType.visibility = View.VISIBLE + binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp) + } + fun assignCommonMessageInterface(commonMessageInterface: CommonMessageInterface) { this.commonMessageInterface = commonMessageInterface } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt index 9142ee679..7d8f2947f 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt @@ -100,18 +100,17 @@ class OutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessageViewH setBubbleOnChatMessage(message) binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) - if (message.parentMessage?.id != null && message.systemMessageType == ChatMessage.SystemMessageType - .MESSAGE_EDITED - ) { - binding.messageType.visibility = View.VISIBLE - } else { - binding.messageType.visibility = View.GONE - } binding.messageTime.layoutParams = layoutParams viewThemeUtils.platform.colorTextView(binding.messageText, ColorRole.ON_SURFACE_VARIANT) binding.messageText.text = processedMessageText - binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp) + if (message.lastEditTimestamp != 0L && !message.isDeleted) { + binding.messageType.visibility = View.VISIBLE + binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp) + } else { + binding.messageType.visibility = View.GONE + binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp) + } // parent message handling if (!message.isDeleted && message.parentMessage != null) { diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index aaa3c5c56..499821afa 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -922,6 +922,13 @@ class ChatActivity : message.message = messageEdited.ocs?.data?.parentMessage?.text adapter?.update(message) adapter?.notifyDataSetChanged() + + // val inflater = LayoutInflater.from(context) + // val outgoingTextViewLayout = inflater.inflate(R.layout.item_custom_incoming_text_message, null) + // val messageType = outgoingTextViewLayout.findViewById(R.id.messageType) + // val messageTime = outgoingTextViewLayout.findViewById(R.id.messageTime) + // messageType.visibility = View.VISIBLE + // messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp) clearEditUI() } @@ -2791,8 +2798,9 @@ class ChatActivity : } } - private fun uploadFile(fileUri: String, isVoiceMessage: Boolean, caption: String = "") { + private fun uploadFile(fileUri: String, isVoiceMessage: Boolean, caption: String = "", token: String = "") { var metaData = "" + var room = "" if (!participantPermissions.hasChatPermission()) { Log.w(TAG, "uploading file(s) is forbidden because of missing attendee permissions") @@ -2807,11 +2815,13 @@ class ChatActivity : metaData = "{\"caption\":\"$caption\"}" } + if (token == "") room = roomToken else room = token + try { require(fileUri.isNotEmpty()) UploadAndShareFilesWorker.upload( fileUri, - roomToken, + room, currentConversation?.displayName!!, metaData ) @@ -4287,6 +4297,82 @@ class ChatActivity : } } + fun shareToNotes(message: ChatMessage, roomToken: String) { + val apiVersion = ApiUtils.getChatApiVersion(conversationUser, intArrayOf(1)) + val type = message.getCalculateMessageType() + var shareUri: Uri? = null + var data: HashMap? + var metaData: String = "" + var objectId: String = "" + if (message.hasFileAttachment()) { + val filename = message.selectedIndividualHashMap!!["name"] + path = applicationContext.cacheDir.absolutePath + "/" + filename + shareUri = FileProvider.getUriForFile( + this, + BuildConfig.APPLICATION_ID, + File(path) + ) + + this.grantUriPermission( + applicationContext.packageName, + shareUri, + Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION + ) + } else if (message.hasGeoLocation()) { + data = message.messageParameters?.get("object") + objectId = data?.get("id")!! + val name = data.get("name")!! + val lat = data.get("latitude")!! + val lon = data.get("longitude")!! + metaData = + "{\"type\":\"geo-location\",\"id\":\"geo:$lat,$lon\",\"latitude\":\"$lat\"," + + "\"longitude\":\"$lon\",\"name\":\"$name\"}" + } + + when (type) { + ChatMessage.MessageType.VOICE_MESSAGE -> { + uploadFile(shareUri.toString(), true, token = roomToken) + Snackbar.make(binding.root, R.string.nc_message_sent, Snackbar.LENGTH_SHORT).show() + } + ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE -> { + val caption = if (message.message != "{file}") message.message else "" + if (null != shareUri) { + try { + context.contentResolver.openInputStream(shareUri)?.close() + uploadFile(shareUri.toString(), false, caption!!, roomToken) + Snackbar.make(binding.root, R.string.nc_message_sent, Snackbar.LENGTH_SHORT).show() + } catch (e: java.lang.Exception) { + Log.w(TAG, "File corresponding to the uri does not exist " + shareUri.toString()) + downloadFileToCache(message, false) { + uploadFile(shareUri.toString(), false, caption!!, roomToken) + Snackbar.make(binding.root, R.string.nc_message_sent, Snackbar.LENGTH_SHORT).show() + } + } + } + } + ChatMessage.MessageType.SINGLE_NC_GEOLOCATION_MESSAGE -> { + chatViewModel.shareLocationToNotes( + credentials!!, + ApiUtils.getUrlToSendLocation(apiVersion, conversationUser!!.baseUrl, roomToken), + "geo-location", + objectId, + metaData + ) + Snackbar.make(binding.root, R.string.nc_message_sent, Snackbar.LENGTH_SHORT).show() + } + ChatMessage.MessageType.REGULAR_TEXT_MESSAGE -> { + chatViewModel.shareToNotes( + credentials!!, + ApiUtils.getUrlForChat(apiVersion, conversationUser!!.baseUrl, roomToken), + message.message!!, + conversationUser!!.displayName!! + ) + Snackbar.make(binding.root, R.string.nc_message_sent, Snackbar.LENGTH_SHORT).show() + } + else -> {} + } + } + fun openInFilesApp(message: ChatMessage) { val keyID = message.selectedIndividualHashMap!![PreviewMessageViewHolder.KEY_ID] val link = message.selectedIndividualHashMap!!["link"] diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt index 3b6db4fd7..467549314 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt @@ -121,6 +121,18 @@ data class ChatMessage( @JsonField(name = ["markdown"]) var renderMarkdown: Boolean? = null, + @JsonField(name = ["lastEditActorDisplayName"]) + var lastEditActorDisplayName: String? = null, + + @JsonField(name = ["lastEditActorId"]) + var lastEditActorId: String? = null, + + @JsonField(name = ["lastEditActorType"]) + var lastEditActorType: String? = null, + + @JsonField(name = ["lastEditTimestamp"]) + var lastEditTimestamp: Long = 0, + var isDownloadingVoiceMessage: Boolean = false, var resetVoiceMessage: Boolean = false, diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt index 5f9847047..4431551ff 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt @@ -51,6 +51,7 @@ import com.nextcloud.talk.repositories.reactions.ReactionsRepository import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ConversationUtils +import com.nextcloud.talk.utils.DateUtils import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew import com.vanniktech.emoji.EmojiPopup import com.vanniktech.emoji.EmojiTextView @@ -78,6 +79,9 @@ class MessageActionsDialog( @Inject lateinit var reactionsRepository: ReactionsRepository + @Inject + lateinit var dateUtils: DateUtils + private lateinit var dialogMessageActionsBinding: DialogMessageActionsBinding private lateinit var popup: EmojiPopup @@ -88,6 +92,11 @@ class MessageActionsDialog( private val messageHasRegularText = ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message .getCalculateMessageType() && !message.isDeleted + private val isMessageEditable = CapabilitiesUtilNew.hasSpreedFeatureCapability( + user, + "edit-messages" + ) && !message.isDeleted + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) NextcloudTalkApplication.sharedApplication?.componentApplication?.inject(this) @@ -129,6 +138,7 @@ class MessageActionsDialog( ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() && CapabilitiesUtilNew.isTranslationsSupported(user) ) + initMenuEditorDetails(message.lastEditTimestamp != 0L && isMessageEditable) initMenuReplyToMessage(message.replyable && hasChatPermission) initMenuReplyPrivately( message.replyable && @@ -136,7 +146,7 @@ class MessageActionsDialog( hasUserActorId(message) && currentConversation?.type != ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL ) - initMenuEditMessage(CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "edit-messages")) + initMenuEditMessage(isMessageEditable) initMenuDeleteMessage(showMessageDeletionButton) initMenuForwardMessage( ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() && @@ -318,13 +328,13 @@ class MessageActionsDialog( dismiss() } } - dialogMessageActionsBinding.menuDeleteMessage.visibility = getVisibility(visible) } private fun initMenuEditMessage(visible: Boolean) { dialogMessageActionsBinding.menuEditMessage.setOnClickListener { chatActivity.editMessage(message) + Log.d("EDIT MESSAGE", "$message") dismiss() } @@ -353,6 +363,16 @@ class MessageActionsDialog( dialogMessageActionsBinding.menuReplyToMessage.visibility = getVisibility(visible) } + private fun initMenuEditorDetails(showEditorDetails: Boolean) { + if (showEditorDetails) { + val editedTime = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp) + val editorName = "Edited by " + message.lastEditActorDisplayName + dialogMessageActionsBinding.editorName.setText(editorName) + dialogMessageActionsBinding.editedTime.setText(editedTime) + } + dialogMessageActionsBinding.menuMessageEditedInfo.visibility = getVisibility(showEditorDetails) + } + private fun initMenuItemCopy(visible: Boolean) { if (visible) { dialogMessageActionsBinding.menuCopyMessage.setOnClickListener { diff --git a/app/src/main/res/layout/dialog_message_actions.xml b/app/src/main/res/layout/dialog_message_actions.xml index db7d0fe2c..575a64129 100644 --- a/app/src/main/res/layout/dialog_message_actions.xml +++ b/app/src/main/res/layout/dialog_message_actions.xml @@ -127,6 +127,41 @@ android:layout_height="wrap_content" android:orientation="vertical"> + + + + + + + + Retrieval failed Languages could not be retrieved Edit Message Icon - Edit message + Edit Send Edit Message Clear Edit Message Cannot Edit Messages older than 24 hours @@ -796,4 +796,5 @@ How to translate with transifex: (edited) Conversation not found Add to Notes + Edited by admin From a67be70eaa7054b2aa9b5b4fa06ac4cc40ca2355 Mon Sep 17 00:00:00 2001 From: sowjanyakch Date: Fri, 9 Feb 2024 10:03:57 +0100 Subject: [PATCH 17/22] Add editor name and message timestamp to MessageActionsDialog Signed-off-by: sowjanyakch --- .../messages/IncomingTextMessageViewHolder.kt | 14 +-- .../OutcomingTextMessageViewHolder.kt | 15 ++-- .../com/nextcloud/talk/chat/ChatActivity.kt | 90 ++++++++++++++++++- .../talk/models/json/chat/ChatMessage.kt | 12 +++ .../talk/ui/dialog/MessageActionsDialog.kt | 24 ++++- .../res/layout/dialog_message_actions.xml | 35 ++++++++ app/src/main/res/values/strings.xml | 3 +- 7 files changed, 175 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt index ae9d35fcb..961ddeb88 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt @@ -113,16 +113,14 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) : binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) binding.messageText.text = processedMessageText - if (message.parentMessage?.id != null && message.systemMessageType == ChatMessage.SystemMessageType - .MESSAGE_EDITED - ) { + if (message.lastEditTimestamp != 0L && !message.isDeleted) { binding.messageType.visibility = View.VISIBLE + binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp) } else { binding.messageType.visibility = View.GONE + binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp) } - binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp) - // parent message handling if (!message.isDeleted && message.parentMessage != null) { processParentMessage(message) @@ -244,6 +242,12 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) : } } + fun updateMessage(message: ChatMessage) { + binding.messageText.text = message.message + binding.messageType.visibility = View.VISIBLE + binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp) + } + fun assignCommonMessageInterface(commonMessageInterface: CommonMessageInterface) { this.commonMessageInterface = commonMessageInterface } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt index 9142ee679..7d8f2947f 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt @@ -100,18 +100,17 @@ class OutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessageViewH setBubbleOnChatMessage(message) binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) - if (message.parentMessage?.id != null && message.systemMessageType == ChatMessage.SystemMessageType - .MESSAGE_EDITED - ) { - binding.messageType.visibility = View.VISIBLE - } else { - binding.messageType.visibility = View.GONE - } binding.messageTime.layoutParams = layoutParams viewThemeUtils.platform.colorTextView(binding.messageText, ColorRole.ON_SURFACE_VARIANT) binding.messageText.text = processedMessageText - binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp) + if (message.lastEditTimestamp != 0L && !message.isDeleted) { + binding.messageType.visibility = View.VISIBLE + binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp) + } else { + binding.messageType.visibility = View.GONE + binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp) + } // parent message handling if (!message.isDeleted && message.parentMessage != null) { diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index aaa3c5c56..499821afa 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -922,6 +922,13 @@ class ChatActivity : message.message = messageEdited.ocs?.data?.parentMessage?.text adapter?.update(message) adapter?.notifyDataSetChanged() + + // val inflater = LayoutInflater.from(context) + // val outgoingTextViewLayout = inflater.inflate(R.layout.item_custom_incoming_text_message, null) + // val messageType = outgoingTextViewLayout.findViewById(R.id.messageType) + // val messageTime = outgoingTextViewLayout.findViewById(R.id.messageTime) + // messageType.visibility = View.VISIBLE + // messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp) clearEditUI() } @@ -2791,8 +2798,9 @@ class ChatActivity : } } - private fun uploadFile(fileUri: String, isVoiceMessage: Boolean, caption: String = "") { + private fun uploadFile(fileUri: String, isVoiceMessage: Boolean, caption: String = "", token: String = "") { var metaData = "" + var room = "" if (!participantPermissions.hasChatPermission()) { Log.w(TAG, "uploading file(s) is forbidden because of missing attendee permissions") @@ -2807,11 +2815,13 @@ class ChatActivity : metaData = "{\"caption\":\"$caption\"}" } + if (token == "") room = roomToken else room = token + try { require(fileUri.isNotEmpty()) UploadAndShareFilesWorker.upload( fileUri, - roomToken, + room, currentConversation?.displayName!!, metaData ) @@ -4287,6 +4297,82 @@ class ChatActivity : } } + fun shareToNotes(message: ChatMessage, roomToken: String) { + val apiVersion = ApiUtils.getChatApiVersion(conversationUser, intArrayOf(1)) + val type = message.getCalculateMessageType() + var shareUri: Uri? = null + var data: HashMap? + var metaData: String = "" + var objectId: String = "" + if (message.hasFileAttachment()) { + val filename = message.selectedIndividualHashMap!!["name"] + path = applicationContext.cacheDir.absolutePath + "/" + filename + shareUri = FileProvider.getUriForFile( + this, + BuildConfig.APPLICATION_ID, + File(path) + ) + + this.grantUriPermission( + applicationContext.packageName, + shareUri, + Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION + ) + } else if (message.hasGeoLocation()) { + data = message.messageParameters?.get("object") + objectId = data?.get("id")!! + val name = data.get("name")!! + val lat = data.get("latitude")!! + val lon = data.get("longitude")!! + metaData = + "{\"type\":\"geo-location\",\"id\":\"geo:$lat,$lon\",\"latitude\":\"$lat\"," + + "\"longitude\":\"$lon\",\"name\":\"$name\"}" + } + + when (type) { + ChatMessage.MessageType.VOICE_MESSAGE -> { + uploadFile(shareUri.toString(), true, token = roomToken) + Snackbar.make(binding.root, R.string.nc_message_sent, Snackbar.LENGTH_SHORT).show() + } + ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE -> { + val caption = if (message.message != "{file}") message.message else "" + if (null != shareUri) { + try { + context.contentResolver.openInputStream(shareUri)?.close() + uploadFile(shareUri.toString(), false, caption!!, roomToken) + Snackbar.make(binding.root, R.string.nc_message_sent, Snackbar.LENGTH_SHORT).show() + } catch (e: java.lang.Exception) { + Log.w(TAG, "File corresponding to the uri does not exist " + shareUri.toString()) + downloadFileToCache(message, false) { + uploadFile(shareUri.toString(), false, caption!!, roomToken) + Snackbar.make(binding.root, R.string.nc_message_sent, Snackbar.LENGTH_SHORT).show() + } + } + } + } + ChatMessage.MessageType.SINGLE_NC_GEOLOCATION_MESSAGE -> { + chatViewModel.shareLocationToNotes( + credentials!!, + ApiUtils.getUrlToSendLocation(apiVersion, conversationUser!!.baseUrl, roomToken), + "geo-location", + objectId, + metaData + ) + Snackbar.make(binding.root, R.string.nc_message_sent, Snackbar.LENGTH_SHORT).show() + } + ChatMessage.MessageType.REGULAR_TEXT_MESSAGE -> { + chatViewModel.shareToNotes( + credentials!!, + ApiUtils.getUrlForChat(apiVersion, conversationUser!!.baseUrl, roomToken), + message.message!!, + conversationUser!!.displayName!! + ) + Snackbar.make(binding.root, R.string.nc_message_sent, Snackbar.LENGTH_SHORT).show() + } + else -> {} + } + } + fun openInFilesApp(message: ChatMessage) { val keyID = message.selectedIndividualHashMap!![PreviewMessageViewHolder.KEY_ID] val link = message.selectedIndividualHashMap!!["link"] diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt index 3b6db4fd7..467549314 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt @@ -121,6 +121,18 @@ data class ChatMessage( @JsonField(name = ["markdown"]) var renderMarkdown: Boolean? = null, + @JsonField(name = ["lastEditActorDisplayName"]) + var lastEditActorDisplayName: String? = null, + + @JsonField(name = ["lastEditActorId"]) + var lastEditActorId: String? = null, + + @JsonField(name = ["lastEditActorType"]) + var lastEditActorType: String? = null, + + @JsonField(name = ["lastEditTimestamp"]) + var lastEditTimestamp: Long = 0, + var isDownloadingVoiceMessage: Boolean = false, var resetVoiceMessage: Boolean = false, diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt index 5f9847047..4431551ff 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt @@ -51,6 +51,7 @@ import com.nextcloud.talk.repositories.reactions.ReactionsRepository import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ConversationUtils +import com.nextcloud.talk.utils.DateUtils import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew import com.vanniktech.emoji.EmojiPopup import com.vanniktech.emoji.EmojiTextView @@ -78,6 +79,9 @@ class MessageActionsDialog( @Inject lateinit var reactionsRepository: ReactionsRepository + @Inject + lateinit var dateUtils: DateUtils + private lateinit var dialogMessageActionsBinding: DialogMessageActionsBinding private lateinit var popup: EmojiPopup @@ -88,6 +92,11 @@ class MessageActionsDialog( private val messageHasRegularText = ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message .getCalculateMessageType() && !message.isDeleted + private val isMessageEditable = CapabilitiesUtilNew.hasSpreedFeatureCapability( + user, + "edit-messages" + ) && !message.isDeleted + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) NextcloudTalkApplication.sharedApplication?.componentApplication?.inject(this) @@ -129,6 +138,7 @@ class MessageActionsDialog( ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() && CapabilitiesUtilNew.isTranslationsSupported(user) ) + initMenuEditorDetails(message.lastEditTimestamp != 0L && isMessageEditable) initMenuReplyToMessage(message.replyable && hasChatPermission) initMenuReplyPrivately( message.replyable && @@ -136,7 +146,7 @@ class MessageActionsDialog( hasUserActorId(message) && currentConversation?.type != ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL ) - initMenuEditMessage(CapabilitiesUtilNew.hasSpreedFeatureCapability(user, "edit-messages")) + initMenuEditMessage(isMessageEditable) initMenuDeleteMessage(showMessageDeletionButton) initMenuForwardMessage( ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() && @@ -318,13 +328,13 @@ class MessageActionsDialog( dismiss() } } - dialogMessageActionsBinding.menuDeleteMessage.visibility = getVisibility(visible) } private fun initMenuEditMessage(visible: Boolean) { dialogMessageActionsBinding.menuEditMessage.setOnClickListener { chatActivity.editMessage(message) + Log.d("EDIT MESSAGE", "$message") dismiss() } @@ -353,6 +363,16 @@ class MessageActionsDialog( dialogMessageActionsBinding.menuReplyToMessage.visibility = getVisibility(visible) } + private fun initMenuEditorDetails(showEditorDetails: Boolean) { + if (showEditorDetails) { + val editedTime = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp) + val editorName = "Edited by " + message.lastEditActorDisplayName + dialogMessageActionsBinding.editorName.setText(editorName) + dialogMessageActionsBinding.editedTime.setText(editedTime) + } + dialogMessageActionsBinding.menuMessageEditedInfo.visibility = getVisibility(showEditorDetails) + } + private fun initMenuItemCopy(visible: Boolean) { if (visible) { dialogMessageActionsBinding.menuCopyMessage.setOnClickListener { diff --git a/app/src/main/res/layout/dialog_message_actions.xml b/app/src/main/res/layout/dialog_message_actions.xml index db7d0fe2c..575a64129 100644 --- a/app/src/main/res/layout/dialog_message_actions.xml +++ b/app/src/main/res/layout/dialog_message_actions.xml @@ -127,6 +127,41 @@ android:layout_height="wrap_content" android:orientation="vertical"> + + + + + + + + Retrieval failed Languages could not be retrieved Edit Message Icon - Edit message + Edit Send Edit Message Clear Edit Message Cannot Edit Messages older than 24 hours @@ -796,4 +796,5 @@ How to translate with transifex: (edited) Conversation not found Add to Notes + Edited by admin From 4e4cb26862c56fabde22e4a10ca054152d6fa171 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Fri, 9 Feb 2024 11:14:00 +0100 Subject: [PATCH 18/22] fix to update adapter for "edited messages" by system message This will update the message when an edit was made on other devices. So the system message will trigger that you are informed about a change. But instead to show the system message, you use it's information to immediately update the adapter. Signed-off-by: Marcel Hibbe --- .../java/com/nextcloud/talk/chat/ChatActivity.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 499821afa..bfac6f729 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -3857,6 +3857,10 @@ class ChatActivity : // delete poll system messages chatMessageIterator.remove() } else if (isEditMessage(currentMessage)) { + if (!chatMessageMap.containsKey(currentMessage.value.parentMessage!!.id)) { + setMessageAsEdited(currentMessage.value.parentMessage) + } + chatMessageIterator.remove() } } @@ -4464,6 +4468,17 @@ class ChatActivity : adapter?.update(messageTemp) } + private fun setMessageAsEdited(message: IMessage?) { + val messageTemp = message as ChatMessage + messageTemp.lastEditTimestamp = message.timestamp + + messageTemp.isOneToOneConversation = + currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL + messageTemp.activeUser = conversationUser + + adapter?.update(messageTemp) + } + private fun updateAdapterForReaction(message: IMessage?) { val messageTemp = message as ChatMessage From 6708aeebad2f1f201f7038d504f59ba2b361ba67 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Fri, 9 Feb 2024 11:16:21 +0100 Subject: [PATCH 19/22] fix to show message as edited after editing on own device Signed-off-by: Marcel Hibbe --- app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index bfac6f729..dfdd86104 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -920,6 +920,7 @@ class ChatActivity : } } message.message = messageEdited.ocs?.data?.parentMessage?.text + message.lastEditTimestamp = System.currentTimeMillis() adapter?.update(message) adapter?.notifyDataSetChanged() From 8d4c0fb57c54967676fb1233520448b2856391b7 Mon Sep 17 00:00:00 2001 From: sowjanyakch Date: Tue, 13 Feb 2024 11:47:39 +0100 Subject: [PATCH 20/22] Minor UI improvements Signed-off-by: sowjanyakch --- .../messages/IncomingTextMessageViewHolder.kt | 6 -- .../com/nextcloud/talk/chat/ChatActivity.kt | 73 ++++++++++--------- .../talk/ui/dialog/MessageActionsDialog.kt | 17 ++++- .../res/layout/dialog_message_actions.xml | 71 +++++++++--------- app/src/main/res/layout/edit_message_view.xml | 6 +- .../item_custom_incoming_text_message.xml | 28 +++---- .../item_custom_outcoming_text_message.xml | 27 +++---- app/src/main/res/values/strings.xml | 1 + 8 files changed, 121 insertions(+), 108 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt index 961ddeb88..43f682de6 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt @@ -242,12 +242,6 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) : } } - fun updateMessage(message: ChatMessage) { - binding.messageText.text = message.message - binding.messageType.visibility = View.VISIBLE - binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp) - } - fun assignCommonMessageInterface(commonMessageInterface: CommonMessageInterface) { this.commonMessageInterface = commonMessageInterface } diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index dfdd86104..a83d41756 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -66,7 +66,6 @@ import android.view.Menu import android.view.MenuItem import android.view.MotionEvent import android.view.View -import android.view.View.GONE import android.view.View.OnTouchListener import android.view.animation.AccelerateDecelerateInterpolator import android.view.animation.AccelerateInterpolator @@ -301,8 +300,6 @@ class ChatActivity : lateinit var chatViewModel: ChatViewModel - val editableBehaviorSubject = BehaviorSubject.createDefault(false) - val editedTextBehaviorSubject = BehaviorSubject.createDefault("") private lateinit var editMessage: ChatMessage override val view: View @@ -363,6 +360,8 @@ class ChatActivity : RELEASED, ERROR } + private val editableBehaviorSubject = BehaviorSubject.createDefault(false) + private val editedTextBehaviorSubject = BehaviorSubject.createDefault("") private var mediaRecorderState: MediaRecorderState = MediaRecorderState.INITIAL @@ -532,6 +531,8 @@ class ChatActivity : context.getSharedPreferences(localClassName, MODE_PRIVATE).apply { val text = getString(roomToken, "") val cursor = getInt(roomToken + CURSOR_KEY, 0) + // val editFlag = getBoolean(EDIT_FLAG, false) + // editableBehaviorSubject.onNext(editFlag) binding.messageInputView.messageInput.setText(text) binding.messageInputView.messageInput.setSelection(cursor) } @@ -558,6 +559,7 @@ class ChatActivity : context.getSharedPreferences(localClassName, MODE_PRIVATE).edit().apply { putString(roomToken, text) putInt(roomToken + CURSOR_KEY, cursor) + // putBoolean(EDIT_FLAG, editableBehaviorSubject.value!!) apply() } } @@ -760,13 +762,14 @@ class ChatActivity : private fun initMessageInputView() { val filters = arrayOfNulls(1) val lengthFilter = CapabilitiesUtilNew.getMessageMaxLength(conversationUser) - binding.editView.editMessageView.visibility = GONE if (editableBehaviorSubject.value!!) { val editableText = Editable.Factory.getInstance().newEditable(editMessage.message) binding.messageInputView.inputEditText.text = editableText binding.messageInputView.inputEditText.setSelection(editableText.length) binding.editView.editMessage.setText(editMessage.message) + } else { + binding.editView.editMessageView.visibility = View.GONE } filters[0] = InputFilter.LengthFilter(lengthFilter) @@ -831,11 +834,7 @@ class ChatActivity : } initVoiceRecordButton() if (editableBehaviorSubject.value!!) { - binding.messageInputView.messageSendButton.visibility = View.GONE - binding.messageInputView.recordAudioButton.visibility = View.GONE - binding.messageInputView.editMessageButton.visibility = View.VISIBLE - binding.editView.editMessageView.visibility = View.VISIBLE - binding.messageInputView.attachmentButton.visibility = View.GONE + setEditUI() } if (sharedText.isNotEmpty()) { @@ -920,16 +919,9 @@ class ChatActivity : } } message.message = messageEdited.ocs?.data?.parentMessage?.text - message.lastEditTimestamp = System.currentTimeMillis() + message.lastEditTimestamp = messageEdited.ocs?.data?.lastEditTimestamp!! adapter?.update(message) adapter?.notifyDataSetChanged() - - // val inflater = LayoutInflater.from(context) - // val outgoingTextViewLayout = inflater.inflate(R.layout.item_custom_incoming_text_message, null) - // val messageType = outgoingTextViewLayout.findViewById(R.id.messageType) - // val messageTime = outgoingTextViewLayout.findViewById(R.id.messageTime) - // messageType.visibility = View.VISIBLE - // messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp) clearEditUI() } @@ -941,11 +933,19 @@ class ChatActivity : }) } + private fun setEditUI() { + binding.messageInputView.messageSendButton.visibility = View.GONE + binding.messageInputView.recordAudioButton.visibility = View.GONE + binding.messageInputView.editMessageButton.visibility = View.VISIBLE + binding.editView.editMessageView.visibility = View.VISIBLE + binding.messageInputView.attachmentButton.visibility = View.GONE + } + private fun clearEditUI() { - binding.messageInputView.editMessageButton.visibility = GONE + binding.messageInputView.editMessageButton.visibility = View.GONE editableBehaviorSubject.onNext(false) binding.messageInputView.inputEditText.setText("") - binding.editView.editMessageView.visibility = GONE + binding.editView.editMessageView.visibility = View.GONE binding.messageInputView.attachmentButton.visibility = View.VISIBLE } @@ -987,6 +987,9 @@ class ChatActivity : binding.messageInputView.findViewById(R.id.micInputCloud)?.let { viewThemeUtils.talk.themeMicInputCloud(it) } + binding.messageInputView.findViewById(R.id.editMessageButton)?.let { + viewThemeUtils.platform.colorImageView(it, ColorRole.PRIMARY) + } } private fun setupActionBar() { @@ -4471,7 +4474,7 @@ class ChatActivity : private fun setMessageAsEdited(message: IMessage?) { val messageTemp = message as ChatMessage - messageTemp.lastEditTimestamp = message.timestamp + messageTemp.lastEditTimestamp = message.lastEditTimestamp messageTemp.isOneToOneConversation = currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL @@ -4527,6 +4530,19 @@ class ChatActivity : } private fun isShowMessageDeletionButton(message: ChatMessage): Boolean { + val isUserAllowedByPrivileges = userAllowedByPrivilages(message) + + return when { + !isUserAllowedByPrivileges -> false + message.systemMessageType != ChatMessage.SystemMessageType.DUMMY -> false + message.isDeleted -> false + !CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "delete-messages") -> false + !participantPermissions.hasChatPermission() -> false + else -> true + } + } + + fun userAllowedByPrivilages(message: ChatMessage): Boolean { if (conversationUser == null) return false val isUserAllowedByPrivileges = if (message.actorId == conversationUser!!.userId) { @@ -4534,20 +4550,7 @@ class ChatActivity : } else { ConversationUtils.canModerate(currentConversation!!, conversationUser!!) } - - val isOlderThanSixHours = message - .createdAt - .before(Date(System.currentTimeMillis() - AGE_THRESHOLD_FOR_DELETE_MESSAGE)) - - return when { - !isUserAllowedByPrivileges -> false - isOlderThanSixHours -> false - message.systemMessageType != ChatMessage.SystemMessageType.DUMMY -> false - message.isDeleted -> false - !CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "delete-messages") -> false - !participantPermissions.hasChatPermission() -> false - else -> true - } + return isUserAllowedByPrivileges } override fun hasContentFor(message: ChatMessage, type: Byte): Boolean { @@ -4771,7 +4774,6 @@ class ChatActivity : private const val GET_ROOM_INFO_DELAY_NORMAL: Long = 30000 private const val GET_ROOM_INFO_DELAY_LOBBY: Long = 5000 private const val HTTP_CODE_OK: Int = 200 - private const val AGE_THRESHOLD_FOR_DELETE_MESSAGE: Int = 21600000 // (6 hours in millis = 6 * 3600 * 1000) private const val REQUEST_CODE_CHOOSE_FILE: Int = 555 private const val REQUEST_CODE_SELECT_CONTACT: Int = 666 private const val REQUEST_CODE_MESSAGE_SEARCH: Int = 777 @@ -4835,5 +4837,6 @@ class ChatActivity : private const val MILISEC_15: Long = 15 private const val LINEBREAK = "\n" private const val CURSOR_KEY = "_cursor" + private const val EDIT_FLAG = "_editFlag" } } diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt index 4431551ff..a1a04970d 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt @@ -61,6 +61,7 @@ import io.reactivex.Observer import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers +import java.util.Date import javax.inject.Inject @AutoInjector(NextcloudTalkApplication::class) @@ -92,10 +93,19 @@ class MessageActionsDialog( private val messageHasRegularText = ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message .getCalculateMessageType() && !message.isDeleted + private val isOlderThanTwentyFourHours = message.createdAt.before( + Date( + System.currentTimeMillis() - + AGE_THRESHOLD_FOR_EDIT_MESSAGE + ) + ) + + private val isUserAllowedToEdit = chatActivity.userAllowedByPrivilages(message) + private val isMessageEditable = CapabilitiesUtilNew.hasSpreedFeatureCapability( user, "edit-messages" - ) && !message.isDeleted + ) && messageHasRegularText && !isOlderThanTwentyFourHours && isUserAllowedToEdit override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -138,7 +148,7 @@ class MessageActionsDialog( ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() && CapabilitiesUtilNew.isTranslationsSupported(user) ) - initMenuEditorDetails(message.lastEditTimestamp != 0L && isMessageEditable) + initMenuEditorDetails(message.lastEditTimestamp != 0L && !message.isDeleted) initMenuReplyToMessage(message.replyable && hasChatPermission) initMenuReplyPrivately( message.replyable && @@ -366,7 +376,7 @@ class MessageActionsDialog( private fun initMenuEditorDetails(showEditorDetails: Boolean) { if (showEditorDetails) { val editedTime = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp) - val editorName = "Edited by " + message.lastEditActorDisplayName + val editorName = context.getString(R.string.nc_edited_by) + message.lastEditActorDisplayName dialogMessageActionsBinding.editorName.setText(editorName) dialogMessageActionsBinding.editedTime.setText(editedTime) } @@ -515,5 +525,6 @@ class MessageActionsDialog( private const val ACTOR_LENGTH = 6 private const val NO_PREVIOUS_MESSAGE_ID: Int = -1 private const val DELAY: Long = 200 + private const val AGE_THRESHOLD_FOR_EDIT_MESSAGE: Long = 86400000 } } diff --git a/app/src/main/res/layout/dialog_message_actions.xml b/app/src/main/res/layout/dialog_message_actions.xml index 575a64129..2dc892e4c 100644 --- a/app/src/main/res/layout/dialog_message_actions.xml +++ b/app/src/main/res/layout/dialog_message_actions.xml @@ -146,6 +146,8 @@ tools:text="@string/nc_edited_by_admin" android:textAlignment="viewStart" android:textColor="@color/high_emphasis_text" + android:maxLines="1" + android:ellipsize="end" android:textSize="@dimen/bottom_sheet_text_size" /> - - - - - - - - - + + + + + + + + + + + app:tint="@color/grey_600"> @@ -30,7 +30,7 @@ android:id = "@+id/editMessageTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textColor = "@color/colorPrimaryDark" + android:textColor = "@color/grey_600" android:text = "@string/nc_edit_message_text"> @@ -50,7 +50,7 @@ android:id = "@+id/clearEdit" android:layout_width="48dp" android:layout_height="48dp" - android:padding = "8dp" + android:padding = "12dp" android:src = "@drawable/ic_clear_24" android:gravity = "top|end" app:tint="@color/colorPrimaryDark"> diff --git a/app/src/main/res/layout/item_custom_incoming_text_message.xml b/app/src/main/res/layout/item_custom_incoming_text_message.xml index b5dba9ec4..57313c387 100644 --- a/app/src/main/res/layout/item_custom_incoming_text_message.xml +++ b/app/src/main/res/layout/item_custom_incoming_text_message.xml @@ -78,19 +78,6 @@ app:layout_wrapBefore="true" tools:text="Talk to you later!" /> - - - - + + + + + diff --git a/app/src/main/res/layout/item_custom_outcoming_text_message.xml b/app/src/main/res/layout/item_custom_outcoming_text_message.xml index 3147b62d8..9383c9dc3 100644 --- a/app/src/main/res/layout/item_custom_outcoming_text_message.xml +++ b/app/src/main/res/layout/item_custom_outcoming_text_message.xml @@ -58,19 +58,6 @@ tools:text="Talk to you later!" /> - - - - + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bbe6b6f89..64596d2ef 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -797,4 +797,5 @@ How to translate with transifex: Conversation not found Add to Notes Edited by admin + "Edited by " From 861b565c0177b489585d8564eff99c1bb71fa10c Mon Sep 17 00:00:00 2001 From: sowjanyakch Date: Wed, 14 Feb 2024 16:03:48 +0100 Subject: [PATCH 21/22] Added date to editor details and changes related to design. Signed-off-by: Sowjanya Kota --- .../messages/IncomingTextMessageViewHolder.kt | 4 +-- .../OutcomingTextMessageViewHolder.kt | 4 +-- .../com/nextcloud/talk/chat/ChatActivity.kt | 17 +++++------ .../talk/ui/dialog/MessageActionsDialog.kt | 16 ++++++----- app/src/main/res/layout/activity_chat.xml | 4 ++- .../res/layout/dialog_message_actions.xml | 12 ++++---- app/src/main/res/layout/edit_message_view.xml | 5 ++-- .../item_custom_incoming_text_message.xml | 5 +++- .../item_custom_outcoming_text_message.xml | 28 +++++++++---------- 9 files changed, 49 insertions(+), 46 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt index 43f682de6..4533c8816 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt @@ -114,10 +114,10 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) : binding.messageText.text = processedMessageText if (message.lastEditTimestamp != 0L && !message.isDeleted) { - binding.messageType.visibility = View.VISIBLE + binding.messageEditIndicator.visibility = View.VISIBLE binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp) } else { - binding.messageType.visibility = View.GONE + binding.messageEditIndicator.visibility = View.GONE binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp) } diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt index 7d8f2947f..232b9e433 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt @@ -105,10 +105,10 @@ class OutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessageViewH binding.messageText.text = processedMessageText if (message.lastEditTimestamp != 0L && !message.isDeleted) { - binding.messageType.visibility = View.VISIBLE + binding.messageEditIndicator.visibility = View.VISIBLE binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp) } else { - binding.messageType.visibility = View.GONE + binding.messageEditIndicator.visibility = View.GONE binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp) } diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index a83d41756..e05e849df 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -531,8 +531,6 @@ class ChatActivity : context.getSharedPreferences(localClassName, MODE_PRIVATE).apply { val text = getString(roomToken, "") val cursor = getInt(roomToken + CURSOR_KEY, 0) - // val editFlag = getBoolean(EDIT_FLAG, false) - // editableBehaviorSubject.onNext(editFlag) binding.messageInputView.messageInput.setText(text) binding.messageInputView.messageInput.setSelection(cursor) } @@ -559,7 +557,6 @@ class ChatActivity : context.getSharedPreferences(localClassName, MODE_PRIVATE).edit().apply { putString(roomToken, text) putInt(roomToken + CURSOR_KEY, cursor) - // putBoolean(EDIT_FLAG, editableBehaviorSubject.value!!) apply() } } @@ -763,13 +760,13 @@ class ChatActivity : val filters = arrayOfNulls(1) val lengthFilter = CapabilitiesUtilNew.getMessageMaxLength(conversationUser) + binding.editView.editMessageView.visibility = View.GONE + if (editableBehaviorSubject.value!!) { val editableText = Editable.Factory.getInstance().newEditable(editMessage.message) binding.messageInputView.inputEditText.text = editableText binding.messageInputView.inputEditText.setSelection(editableText.length) binding.editView.editMessage.setText(editMessage.message) - } else { - binding.editView.editMessageView.visibility = View.GONE } filters[0] = InputFilter.LengthFilter(lengthFilter) @@ -918,14 +915,12 @@ class ChatActivity : ).show() } } - message.message = messageEdited.ocs?.data?.parentMessage?.text - message.lastEditTimestamp = messageEdited.ocs?.data?.lastEditTimestamp!! - adapter?.update(message) - adapter?.notifyDataSetChanged() clearEditUI() } override fun onError(e: Throwable) { + Log.e(TAG, "failed to edit message", e) + Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show() } override fun onComplete() { @@ -990,6 +985,9 @@ class ChatActivity : binding.messageInputView.findViewById(R.id.editMessageButton)?.let { viewThemeUtils.platform.colorImageView(it, ColorRole.PRIMARY) } + binding.editView.clearEdit.let { + viewThemeUtils.platform.colorImageView(it, ColorRole.PRIMARY) + } } private fun setupActionBar() { @@ -4837,6 +4835,5 @@ class ChatActivity : private const val MILISEC_15: Long = 15 private const val LINEBREAK = "\n" private const val CURSOR_KEY = "_cursor" - private const val EDIT_FLAG = "_editFlag" } } diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt index a1a04970d..ea6a9e936 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt @@ -51,6 +51,7 @@ import com.nextcloud.talk.repositories.reactions.ReactionsRepository import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ConversationUtils +import com.nextcloud.talk.utils.DateConstants import com.nextcloud.talk.utils.DateUtils import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew import com.vanniktech.emoji.EmojiPopup @@ -93,12 +94,9 @@ class MessageActionsDialog( private val messageHasRegularText = ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message .getCalculateMessageType() && !message.isDeleted - private val isOlderThanTwentyFourHours = message.createdAt.before( - Date( - System.currentTimeMillis() - - AGE_THRESHOLD_FOR_EDIT_MESSAGE - ) - ) + private val isOlderThanTwentyFourHours = message + .createdAt + .before(Date(System.currentTimeMillis() - AGE_THRESHOLD_FOR_EDIT_MESSAGE)) private val isUserAllowedToEdit = chatActivity.userAllowedByPrivilages(message) @@ -375,7 +373,11 @@ class MessageActionsDialog( private fun initMenuEditorDetails(showEditorDetails: Boolean) { if (showEditorDetails) { - val editedTime = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp) + val editedTime = dateUtils.getLocalDateTimeStringFromTimestamp( + message.lastEditTimestamp * + DateConstants.SECOND_DIVIDER + ) + val editorName = context.getString(R.string.nc_edited_by) + message.lastEditActorDisplayName dialogMessageActionsBinding.editorName.setText(editorName) dialogMessageActionsBinding.editedTime.setText(editedTime) diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml index 1ed31932f..b54e3de07 100644 --- a/app/src/main/res/layout/activity_chat.xml +++ b/app/src/main/res/layout/activity_chat.xml @@ -263,7 +263,9 @@ android:id="@+id/editView" layout="@layout/edit_message_view" android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:layout_marginEnd="6dp" > + + android:textSize="15sp" + android:textColor = "@color/grey_600"/> + android:textSize="15sp" + android:textColor ="@color/grey_600"/> diff --git a/app/src/main/res/layout/edit_message_view.xml b/app/src/main/res/layout/edit_message_view.xml index d314b4888..7e55f1e8d 100644 --- a/app/src/main/res/layout/edit_message_view.xml +++ b/app/src/main/res/layout/edit_message_view.xml @@ -7,7 +7,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" - android:id = "@+id/edit_message_view" + android:id = "@+id/editMessageView" android:orientation = "horizontal"> + android:gravity = "top|end"> diff --git a/app/src/main/res/layout/item_custom_incoming_text_message.xml b/app/src/main/res/layout/item_custom_incoming_text_message.xml index 57313c387..5a861f62c 100644 --- a/app/src/main/res/layout/item_custom_incoming_text_message.xml +++ b/app/src/main/res/layout/item_custom_incoming_text_message.xml @@ -95,11 +95,14 @@ diff --git a/app/src/main/res/layout/item_custom_outcoming_text_message.xml b/app/src/main/res/layout/item_custom_outcoming_text_message.xml index 9383c9dc3..74457e9be 100644 --- a/app/src/main/res/layout/item_custom_outcoming_text_message.xml +++ b/app/src/main/res/layout/item_custom_outcoming_text_message.xml @@ -73,6 +73,20 @@ app:layout_wrapBefore="false" tools:text="10:35" /> + + + - - - - - - From 4e9f62e400f5acbf5f3a3d7c286dda9b2bda0985 Mon Sep 17 00:00:00 2001 From: sowjanyakch Date: Wed, 14 Feb 2024 17:30:33 +0100 Subject: [PATCH 22/22] Added content description for ImageView. Signed-off-by: Sowjanya Kota --- app/src/main/res/layout/edit_message_view.xml | 9 +++++---- app/src/main/res/values/strings.xml | 2 ++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/layout/edit_message_view.xml b/app/src/main/res/layout/edit_message_view.xml index 7e55f1e8d..297783813 100644 --- a/app/src/main/res/layout/edit_message_view.xml +++ b/app/src/main/res/layout/edit_message_view.xml @@ -11,28 +11,29 @@ android:orientation = "horizontal"> + - - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 64596d2ef..9de2c754b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -798,4 +798,6 @@ How to translate with transifex: Add to Notes Edited by admin "Edited by " + clear Edit Button + Edit Icon