mirror of
https://github.com/nextcloud/talk-android
synced 2025-06-19 19:49:33 +01:00
Merge pull request #1752 from nextcloud/feature/1464/markAsRead
mark message as unread / conversation as read
This commit is contained in:
commit
5287fc2a96
@ -429,4 +429,11 @@ public interface NcApi {
|
||||
|
||||
@GET
|
||||
Observable<HoverCardOverall> hoverCard(@Header("Authorization") String authorization, @Url String url);
|
||||
|
||||
// Url is: /api/{apiVersion}/chat/{token}/read
|
||||
@FormUrlEncoded
|
||||
@POST
|
||||
Observable<GenericOverall> setChatReadMarker(@Header("Authorization") String authorization,
|
||||
@Url String url,
|
||||
@Field("lastReadMessage") int lastReadMessage);
|
||||
}
|
||||
|
@ -2049,6 +2049,22 @@ class ChatController(args: Bundle) :
|
||||
|
||||
var countGroupedMessages = 0
|
||||
if (!isFromTheFuture) {
|
||||
var previousMessageId = NO_PREVIOUS_MESSAGE_ID
|
||||
for (i in chatMessageList.indices.reversed()) {
|
||||
val chatMessage = chatMessageList[i]
|
||||
|
||||
if (previousMessageId > NO_PREVIOUS_MESSAGE_ID) {
|
||||
chatMessage.previousMessageId = previousMessageId
|
||||
} else if (adapter?.isEmpty != true) {
|
||||
if (adapter!!.items[0].item is ChatMessage) {
|
||||
chatMessage.previousMessageId = (adapter!!.items[0].item as ChatMessage).jsonMessageId
|
||||
} else if (adapter!!.items.size > 1 && adapter!!.items[1].item is ChatMessage) {
|
||||
chatMessage.previousMessageId = (adapter!!.items[1].item as ChatMessage).jsonMessageId
|
||||
}
|
||||
}
|
||||
|
||||
previousMessageId = chatMessage.jsonMessageId
|
||||
}
|
||||
|
||||
for (i in chatMessageList.indices) {
|
||||
if (chatMessageList.size > i + 1) {
|
||||
@ -2092,6 +2108,23 @@ class ChatController(args: Bundle) :
|
||||
val isThereANewNotice =
|
||||
shouldAddNewMessagesNotice || adapter?.getMessagePositionByIdInReverse("-1") != -1
|
||||
|
||||
var previousMessageId = NO_PREVIOUS_MESSAGE_ID
|
||||
for (i in chatMessageList.indices.reversed()) {
|
||||
val chatMessageItem = chatMessageList[i]
|
||||
|
||||
if (previousMessageId > NO_PREVIOUS_MESSAGE_ID) {
|
||||
chatMessageItem.previousMessageId = previousMessageId
|
||||
} else if (adapter?.isEmpty != true) {
|
||||
if (adapter!!.items[0].item is ChatMessage) {
|
||||
chatMessageItem.previousMessageId = (adapter!!.items[0].item as ChatMessage).jsonMessageId
|
||||
} else if (adapter!!.items.size > 1 && adapter!!.items[1].item is ChatMessage) {
|
||||
chatMessageItem.previousMessageId = (adapter!!.items[1].item as ChatMessage).jsonMessageId
|
||||
}
|
||||
}
|
||||
|
||||
previousMessageId = chatMessageItem.jsonMessageId
|
||||
}
|
||||
|
||||
for (i in chatMessageList.indices) {
|
||||
chatMessage = chatMessageList[i]
|
||||
|
||||
@ -2319,6 +2352,40 @@ class ChatController(args: Bundle) :
|
||||
clipboardManager.setPrimaryClip(clipData)
|
||||
true
|
||||
}
|
||||
R.id.action_mark_as_unread -> {
|
||||
val chatMessage = message as ChatMessage?
|
||||
if (chatMessage!!.previousMessageId > NO_PREVIOUS_MESSAGE_ID) {
|
||||
ncApi!!.setChatReadMarker(
|
||||
credentials,
|
||||
ApiUtils.getUrlForSetChatReadMarker(
|
||||
ApiUtils.getChatApiVersion(conversationUser, intArrayOf(ApiUtils.APIv1)),
|
||||
conversationUser?.baseUrl,
|
||||
roomToken
|
||||
),
|
||||
chatMessage.previousMessageId
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : Observer<GenericOverall> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
// unused atm
|
||||
}
|
||||
|
||||
override fun onNext(t: GenericOverall) {
|
||||
// unused atm
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
Log.e(TAG, e.message, e)
|
||||
}
|
||||
|
||||
override fun onComplete() {
|
||||
// unused atm
|
||||
}
|
||||
})
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.action_forward_message -> {
|
||||
val bundle = Bundle()
|
||||
bundle.putBoolean(BundleKeys.KEY_FORWARD_MSG_FLAG, true)
|
||||
@ -2471,8 +2538,10 @@ class ChatController(args: Bundle) :
|
||||
menu.findItem(R.id.action_delete_message).isVisible = isShowMessageDeletionButton(message)
|
||||
menu.findItem(R.id.action_forward_message).isVisible =
|
||||
ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getMessageType()
|
||||
menu.findItem(R.id.action_mark_as_unread).isVisible = message.previousMessageId > NO_PREVIOUS_MESSAGE_ID &&
|
||||
ChatMessage.MessageType.SYSTEM_MESSAGE != message.getMessageType()
|
||||
if (menu.hasVisibleItems()) {
|
||||
if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
setForceShowIcon(true)
|
||||
}
|
||||
show()
|
||||
@ -2723,5 +2792,6 @@ class ChatController(args: Bundle) :
|
||||
private const val SEMI_TRANSPARENT_INT: Int = 99
|
||||
private const val VOICE_MESSAGE_SEEKBAR_BASE: Int = 1000
|
||||
private const val SECOND: Long = 1000
|
||||
private const val NO_PREVIOUS_MESSAGE_ID: Int = -1
|
||||
}
|
||||
}
|
||||
|
@ -74,6 +74,7 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication.class)
|
||||
public class CallMenuController extends BaseController implements FlexibleAdapter.OnItemClickListener {
|
||||
public static final int ALL_MESSAGES_READ = 0;
|
||||
@BindView(R.id.recycler_view)
|
||||
RecyclerView recyclerView;
|
||||
|
||||
@ -170,6 +171,12 @@ public class CallMenuController extends BaseController implements FlexibleAdapte
|
||||
R.color.grey_600)));
|
||||
}
|
||||
|
||||
if(conversation.unreadMessages > ALL_MESSAGES_READ && CapabilitiesUtil.canSetChatReadMarker(currentUser)) {
|
||||
menuItems.add(new MenuItem(getResources().getString(R.string.nc_mark_as_read),
|
||||
96,
|
||||
ContextCompat.getDrawable(context, R.drawable.ic_eye)));
|
||||
}
|
||||
|
||||
if (conversation.isNameEditable(currentUser)) {
|
||||
menuItems.add(new MenuItem(getResources().getString(R.string.nc_rename),
|
||||
2,
|
||||
@ -340,7 +347,6 @@ public class CallMenuController extends BaseController implements FlexibleAdapte
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
@Parcel
|
||||
|
@ -33,6 +33,7 @@ import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.bluelinelabs.conductor.RouterTransaction;
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
|
||||
@ -74,6 +75,7 @@ import io.reactivex.Observer;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.HttpException;
|
||||
import retrofit2.Response;
|
||||
|
||||
@ -278,7 +280,8 @@ public class OperationsMenuController extends BaseController {
|
||||
}
|
||||
|
||||
credentials = ApiUtils.getCredentials(currentUser.getUsername(), currentUser.getToken());
|
||||
int apiVersion = ApiUtils.getConversationApiVersion(currentUser, new int[] {ApiUtils.APIv4, 1});
|
||||
int apiVersion = ApiUtils.getConversationApiVersion(currentUser, new int[] {ApiUtils.APIv4, ApiUtils.APIv1});
|
||||
int chatApiVersion = ApiUtils.getChatApiVersion(currentUser, new int[] {ApiUtils.APIv1});
|
||||
|
||||
switch (operationCode) {
|
||||
case 2:
|
||||
@ -480,6 +483,17 @@ public class OperationsMenuController extends BaseController {
|
||||
});
|
||||
|
||||
break;
|
||||
case 96:
|
||||
ncApi.setChatReadMarker(credentials,
|
||||
ApiUtils.getUrlForSetChatReadMarker(chatApiVersion,
|
||||
currentUser.getBaseUrl(),
|
||||
conversation.getToken()),
|
||||
conversation.lastMessage.jsonMessageId)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.retry(1)
|
||||
.subscribe(genericOperationsObserver);
|
||||
break;
|
||||
case 97:
|
||||
case 98:
|
||||
if (operationCode == 97) {
|
||||
|
@ -75,6 +75,10 @@ public abstract class CapabilitiesUtil {
|
||||
return !hasSpreedFeatureCapability(user, "chat-replies");
|
||||
}
|
||||
|
||||
public static boolean canSetChatReadMarker(@Nullable UserEntity user) {
|
||||
return hasSpreedFeatureCapability(user, "chat-read-marker");
|
||||
}
|
||||
|
||||
public static boolean hasSpreedFeatureCapability(@Nullable UserEntity user, String capabilityName) {
|
||||
if (user != null && user.getCapabilities() != null) {
|
||||
try {
|
||||
|
@ -62,6 +62,8 @@ public class ChatMessage implements MessageContentType, MessageContentType.Image
|
||||
public boolean isDeleted;
|
||||
@JsonField(name = "id")
|
||||
public int jsonMessageId;
|
||||
@JsonIgnore
|
||||
public int previousMessageId = -1;
|
||||
@JsonField(name = "token")
|
||||
public String token;
|
||||
// guests or users
|
||||
|
@ -40,6 +40,8 @@ import androidx.annotation.Nullable;
|
||||
import okhttp3.Credentials;
|
||||
|
||||
public class ApiUtils {
|
||||
public static final int APIv1 = 1;
|
||||
public static final int APIv2 = 2;
|
||||
public static final int APIv3 = 3;
|
||||
public static final int APIv4 = 4;
|
||||
private static final String TAG = "ApiUtils";
|
||||
@ -59,7 +61,7 @@ public class ApiUtils {
|
||||
*/
|
||||
@Deprecated
|
||||
public static String getUrlForRemovingParticipantFromConversation(String baseUrl, String roomToken, boolean isGuest) {
|
||||
String url = getUrlForParticipants(1, baseUrl, roomToken);
|
||||
String url = getUrlForParticipants(APIv1, baseUrl, roomToken);
|
||||
|
||||
if (isGuest) {
|
||||
url += "/guests";
|
||||
@ -121,7 +123,7 @@ public class ApiUtils {
|
||||
public static int getConversationApiVersion(UserEntity user, int[] versions) throws NoSupportedApiException {
|
||||
boolean hasApiV4 = false;
|
||||
for (int version : versions) {
|
||||
hasApiV4 |= version == 4;
|
||||
hasApiV4 |= version == APIv4;
|
||||
}
|
||||
|
||||
if (!hasApiV4) {
|
||||
@ -135,11 +137,11 @@ public class ApiUtils {
|
||||
}
|
||||
|
||||
// Fallback for old API versions
|
||||
if ((version == 1 || version == 2)) {
|
||||
if ((version == APIv1 || version == APIv2)) {
|
||||
if (CapabilitiesUtil.hasSpreedFeatureCapability(user, "conversation-v2")) {
|
||||
return version;
|
||||
}
|
||||
if (version == 1 &&
|
||||
if (version == APIv1 &&
|
||||
CapabilitiesUtil.hasSpreedFeatureCapability(user, "mention-flag") &&
|
||||
!CapabilitiesUtil.hasSpreedFeatureCapability(user, "conversation-v4")) {
|
||||
return version;
|
||||
@ -155,13 +157,13 @@ public class ApiUtils {
|
||||
return version;
|
||||
}
|
||||
|
||||
if (version == 2 &&
|
||||
if (version == APIv2 &&
|
||||
CapabilitiesUtil.hasSpreedFeatureCapability(user, "sip-support") &&
|
||||
!CapabilitiesUtil.hasSpreedFeatureCapability(user, "signaling-v3")) {
|
||||
return version;
|
||||
}
|
||||
|
||||
if (version == 1 &&
|
||||
if (version == APIv1 &&
|
||||
!CapabilitiesUtil.hasSpreedFeatureCapability(user, "signaling-v3")) {
|
||||
// Has no capability, we just assume it is always there when there is no v3 or later
|
||||
return version;
|
||||
@ -172,7 +174,7 @@ public class ApiUtils {
|
||||
|
||||
public static int getChatApiVersion(UserEntity user, int[] versions) throws NoSupportedApiException {
|
||||
for (int version : versions) {
|
||||
if (version == 1 && CapabilitiesUtil.hasSpreedFeatureCapability(user, "chat-v2")) {
|
||||
if (version == APIv1 && CapabilitiesUtil.hasSpreedFeatureCapability(user, "chat-v2")) {
|
||||
// Do not question that chat-v2 capability shows the availability of api/v1/ endpoint *see no evil*
|
||||
return version;
|
||||
}
|
||||
@ -406,4 +408,8 @@ public class ApiUtils {
|
||||
|
||||
public static String getUrlForHoverCard(String baseUrl, String userId) { return baseUrl + ocsApiVersion +
|
||||
"/hovercard/v1/" + userId; }
|
||||
|
||||
public static String getUrlForSetChatReadMarker(int version, String baseUrl, String roomToken) {
|
||||
return getUrlForChat(version, baseUrl, roomToken) + "/read";
|
||||
}
|
||||
}
|
||||
|
25
app/src/main/res/drawable/ic_eye.xml
Normal file
25
app/src/main/res/drawable/ic_eye.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<!--
|
||||
@author Google LLC
|
||||
Copyright (C) 2021 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#757575"
|
||||
android:pathData="M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9M12,17A5,5 0 0,1 7,12A5,5 0 0,1 12,7A5,5 0 0,1 17,12A5,5 0 0,1 12,17M12,4.5C7,4.5 2.73,7.61 1,12C2.73,16.39 7,19.5 12,19.5C17,19.5 21.27,16.39 23,12C21.27,7.61 17,4.5 12,4.5Z" />
|
||||
</vector>
|
26
app/src/main/res/drawable/ic_eye_off.xml
Normal file
26
app/src/main/res/drawable/ic_eye_off.xml
Normal file
@ -0,0 +1,26 @@
|
||||
<!--
|
||||
@author Google LLC
|
||||
Copyright (C) 2021 Google LLC
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="@color/medium_emphasis_text"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M11.83,9L15,12.16C15,12.11 15,12.05 15,12A3,3 0 0,0 12,9C11.94,9 11.89,9 11.83,9M7.53,9.8L9.08,11.35C9.03,11.56 9,11.77 9,12A3,3 0 0,0 12,15C12.22,15 12.44,14.97 12.65,14.92L14.2,16.47C13.53,16.8 12.79,17 12,17A5,5 0 0,1 7,12C7,11.21 7.2,10.47 7.53,9.8M2,4.27L4.28,6.55L4.73,7C3.08,8.3 1.78,10 1,12C2.73,16.39 7,19.5 12,19.5C13.55,19.5 15.03,19.2 16.38,18.66L16.81,19.08L19.73,22L21,20.73L3.27,3M12,7A5,5 0 0,1 17,12C17,12.64 16.87,13.26 16.64,13.82L19.57,16.75C21.07,15.5 22.27,13.86 23,12C21.27,7.61 17,4.5 12,4.5C10.6,4.5 9.26,4.75 8,5.2L10.17,7.35C10.74,7.13 11.35,7 12,7Z" />
|
||||
</vector>
|
@ -8,6 +8,12 @@
|
||||
android:title="@string/nc_copy_message"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_mark_as_unread"
|
||||
android:icon="@drawable/ic_eye_off"
|
||||
android:title="@string/nc_mark_as_unread"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_forward_message"
|
||||
android:icon="@drawable/ic_share_action"
|
||||
|
@ -187,6 +187,8 @@
|
||||
<string name="nc_new_conversation">New conversation</string>
|
||||
<string name="nc_join_via_link">Join with a link</string>
|
||||
<string name="nc_join_via_web">Join via web</string>
|
||||
<string name="nc_mark_as_read">Mark as read</string>
|
||||
<string name="nc_mark_as_unread">Mark as unread</string>
|
||||
<string name="nc_add_to_favorites">Add to favorites</string>
|
||||
<string name="nc_remove_from_favorites">Remove from favorites</string>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user