mirror of
https://github.com/nextcloud/talk-android
synced 2025-06-20 12:09:45 +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
|
@GET
|
||||||
Observable<HoverCardOverall> hoverCard(@Header("Authorization") String authorization, @Url String url);
|
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
|
var countGroupedMessages = 0
|
||||||
if (!isFromTheFuture) {
|
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) {
|
for (i in chatMessageList.indices) {
|
||||||
if (chatMessageList.size > i + 1) {
|
if (chatMessageList.size > i + 1) {
|
||||||
@ -2092,6 +2108,23 @@ class ChatController(args: Bundle) :
|
|||||||
val isThereANewNotice =
|
val isThereANewNotice =
|
||||||
shouldAddNewMessagesNotice || adapter?.getMessagePositionByIdInReverse("-1") != -1
|
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) {
|
for (i in chatMessageList.indices) {
|
||||||
chatMessage = chatMessageList[i]
|
chatMessage = chatMessageList[i]
|
||||||
|
|
||||||
@ -2319,6 +2352,40 @@ class ChatController(args: Bundle) :
|
|||||||
clipboardManager.setPrimaryClip(clipData)
|
clipboardManager.setPrimaryClip(clipData)
|
||||||
true
|
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 -> {
|
R.id.action_forward_message -> {
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
bundle.putBoolean(BundleKeys.KEY_FORWARD_MSG_FLAG, true)
|
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_delete_message).isVisible = isShowMessageDeletionButton(message)
|
||||||
menu.findItem(R.id.action_forward_message).isVisible =
|
menu.findItem(R.id.action_forward_message).isVisible =
|
||||||
ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getMessageType()
|
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 (menu.hasVisibleItems()) {
|
||||||
if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
setForceShowIcon(true)
|
setForceShowIcon(true)
|
||||||
}
|
}
|
||||||
show()
|
show()
|
||||||
@ -2723,5 +2792,6 @@ class ChatController(args: Bundle) :
|
|||||||
private const val SEMI_TRANSPARENT_INT: Int = 99
|
private const val SEMI_TRANSPARENT_INT: Int = 99
|
||||||
private const val VOICE_MESSAGE_SEEKBAR_BASE: Int = 1000
|
private const val VOICE_MESSAGE_SEEKBAR_BASE: Int = 1000
|
||||||
private const val SECOND: Long = 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)
|
@AutoInjector(NextcloudTalkApplication.class)
|
||||||
public class CallMenuController extends BaseController implements FlexibleAdapter.OnItemClickListener {
|
public class CallMenuController extends BaseController implements FlexibleAdapter.OnItemClickListener {
|
||||||
|
public static final int ALL_MESSAGES_READ = 0;
|
||||||
@BindView(R.id.recycler_view)
|
@BindView(R.id.recycler_view)
|
||||||
RecyclerView recyclerView;
|
RecyclerView recyclerView;
|
||||||
|
|
||||||
@ -170,6 +171,12 @@ public class CallMenuController extends BaseController implements FlexibleAdapte
|
|||||||
R.color.grey_600)));
|
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)) {
|
if (conversation.isNameEditable(currentUser)) {
|
||||||
menuItems.add(new MenuItem(getResources().getString(R.string.nc_rename),
|
menuItems.add(new MenuItem(getResources().getString(R.string.nc_rename),
|
||||||
2,
|
2,
|
||||||
@ -340,7 +347,6 @@ public class CallMenuController extends BaseController implements FlexibleAdapte
|
|||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Parcel
|
@Parcel
|
||||||
|
@ -33,6 +33,7 @@ import android.widget.Button;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.bluelinelabs.conductor.RouterTransaction;
|
import com.bluelinelabs.conductor.RouterTransaction;
|
||||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
|
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
|
||||||
@ -74,6 +75,7 @@ import io.reactivex.Observer;
|
|||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
import retrofit2.HttpException;
|
import retrofit2.HttpException;
|
||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
|
|
||||||
@ -278,7 +280,8 @@ public class OperationsMenuController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
credentials = ApiUtils.getCredentials(currentUser.getUsername(), currentUser.getToken());
|
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) {
|
switch (operationCode) {
|
||||||
case 2:
|
case 2:
|
||||||
@ -480,6 +483,17 @@ public class OperationsMenuController extends BaseController {
|
|||||||
});
|
});
|
||||||
|
|
||||||
break;
|
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 97:
|
||||||
case 98:
|
case 98:
|
||||||
if (operationCode == 97) {
|
if (operationCode == 97) {
|
||||||
|
@ -75,6 +75,10 @@ public abstract class CapabilitiesUtil {
|
|||||||
return !hasSpreedFeatureCapability(user, "chat-replies");
|
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) {
|
public static boolean hasSpreedFeatureCapability(@Nullable UserEntity user, String capabilityName) {
|
||||||
if (user != null && user.getCapabilities() != null) {
|
if (user != null && user.getCapabilities() != null) {
|
||||||
try {
|
try {
|
||||||
|
@ -62,6 +62,8 @@ public class ChatMessage implements MessageContentType, MessageContentType.Image
|
|||||||
public boolean isDeleted;
|
public boolean isDeleted;
|
||||||
@JsonField(name = "id")
|
@JsonField(name = "id")
|
||||||
public int jsonMessageId;
|
public int jsonMessageId;
|
||||||
|
@JsonIgnore
|
||||||
|
public int previousMessageId = -1;
|
||||||
@JsonField(name = "token")
|
@JsonField(name = "token")
|
||||||
public String token;
|
public String token;
|
||||||
// guests or users
|
// guests or users
|
||||||
|
@ -40,6 +40,8 @@ import androidx.annotation.Nullable;
|
|||||||
import okhttp3.Credentials;
|
import okhttp3.Credentials;
|
||||||
|
|
||||||
public class ApiUtils {
|
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 APIv3 = 3;
|
||||||
public static final int APIv4 = 4;
|
public static final int APIv4 = 4;
|
||||||
private static final String TAG = "ApiUtils";
|
private static final String TAG = "ApiUtils";
|
||||||
@ -59,7 +61,7 @@ public class ApiUtils {
|
|||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static String getUrlForRemovingParticipantFromConversation(String baseUrl, String roomToken, boolean isGuest) {
|
public static String getUrlForRemovingParticipantFromConversation(String baseUrl, String roomToken, boolean isGuest) {
|
||||||
String url = getUrlForParticipants(1, baseUrl, roomToken);
|
String url = getUrlForParticipants(APIv1, baseUrl, roomToken);
|
||||||
|
|
||||||
if (isGuest) {
|
if (isGuest) {
|
||||||
url += "/guests";
|
url += "/guests";
|
||||||
@ -121,7 +123,7 @@ public class ApiUtils {
|
|||||||
public static int getConversationApiVersion(UserEntity user, int[] versions) throws NoSupportedApiException {
|
public static int getConversationApiVersion(UserEntity user, int[] versions) throws NoSupportedApiException {
|
||||||
boolean hasApiV4 = false;
|
boolean hasApiV4 = false;
|
||||||
for (int version : versions) {
|
for (int version : versions) {
|
||||||
hasApiV4 |= version == 4;
|
hasApiV4 |= version == APIv4;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasApiV4) {
|
if (!hasApiV4) {
|
||||||
@ -135,11 +137,11 @@ public class ApiUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fallback for old API versions
|
// Fallback for old API versions
|
||||||
if ((version == 1 || version == 2)) {
|
if ((version == APIv1 || version == APIv2)) {
|
||||||
if (CapabilitiesUtil.hasSpreedFeatureCapability(user, "conversation-v2")) {
|
if (CapabilitiesUtil.hasSpreedFeatureCapability(user, "conversation-v2")) {
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
if (version == 1 &&
|
if (version == APIv1 &&
|
||||||
CapabilitiesUtil.hasSpreedFeatureCapability(user, "mention-flag") &&
|
CapabilitiesUtil.hasSpreedFeatureCapability(user, "mention-flag") &&
|
||||||
!CapabilitiesUtil.hasSpreedFeatureCapability(user, "conversation-v4")) {
|
!CapabilitiesUtil.hasSpreedFeatureCapability(user, "conversation-v4")) {
|
||||||
return version;
|
return version;
|
||||||
@ -155,13 +157,13 @@ public class ApiUtils {
|
|||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version == 2 &&
|
if (version == APIv2 &&
|
||||||
CapabilitiesUtil.hasSpreedFeatureCapability(user, "sip-support") &&
|
CapabilitiesUtil.hasSpreedFeatureCapability(user, "sip-support") &&
|
||||||
!CapabilitiesUtil.hasSpreedFeatureCapability(user, "signaling-v3")) {
|
!CapabilitiesUtil.hasSpreedFeatureCapability(user, "signaling-v3")) {
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version == 1 &&
|
if (version == APIv1 &&
|
||||||
!CapabilitiesUtil.hasSpreedFeatureCapability(user, "signaling-v3")) {
|
!CapabilitiesUtil.hasSpreedFeatureCapability(user, "signaling-v3")) {
|
||||||
// Has no capability, we just assume it is always there when there is no v3 or later
|
// Has no capability, we just assume it is always there when there is no v3 or later
|
||||||
return version;
|
return version;
|
||||||
@ -172,7 +174,7 @@ public class ApiUtils {
|
|||||||
|
|
||||||
public static int getChatApiVersion(UserEntity user, int[] versions) throws NoSupportedApiException {
|
public static int getChatApiVersion(UserEntity user, int[] versions) throws NoSupportedApiException {
|
||||||
for (int version : versions) {
|
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*
|
// Do not question that chat-v2 capability shows the availability of api/v1/ endpoint *see no evil*
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
@ -406,4 +408,8 @@ public class ApiUtils {
|
|||||||
|
|
||||||
public static String getUrlForHoverCard(String baseUrl, String userId) { return baseUrl + ocsApiVersion +
|
public static String getUrlForHoverCard(String baseUrl, String userId) { return baseUrl + ocsApiVersion +
|
||||||
"/hovercard/v1/" + userId; }
|
"/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"
|
android:title="@string/nc_copy_message"
|
||||||
app:showAsAction="always" />
|
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
|
<item
|
||||||
android:id="@+id/action_forward_message"
|
android:id="@+id/action_forward_message"
|
||||||
android:icon="@drawable/ic_share_action"
|
android:icon="@drawable/ic_share_action"
|
||||||
|
@ -187,6 +187,8 @@
|
|||||||
<string name="nc_new_conversation">New conversation</string>
|
<string name="nc_new_conversation">New conversation</string>
|
||||||
<string name="nc_join_via_link">Join with a link</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_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_add_to_favorites">Add to favorites</string>
|
||||||
<string name="nc_remove_from_favorites">Remove from favorites</string>
|
<string name="nc_remove_from_favorites">Remove from favorites</string>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user