mirror of
https://github.com/nextcloud/talk-android
synced 2025-06-26 23:20:19 +01:00
Merge pull request #3608 from nextcloud/feature/edit_messages
Feature/edit messages
This commit is contained in:
commit
e5f25bda67
@ -79,7 +79,6 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
|
||||
setAvatarAndAuthorOnMessageItem(message)
|
||||
|
||||
colorizeMessageBubble(message)
|
||||
|
||||
itemView.isSelected = false
|
||||
@ -114,7 +113,13 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
|
||||
binding.messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
|
||||
binding.messageText.text = processedMessageText
|
||||
|
||||
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
|
||||
if (message.lastEditTimestamp != 0L && !message.isDeleted) {
|
||||
binding.messageEditIndicator.visibility = View.VISIBLE
|
||||
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp)
|
||||
} else {
|
||||
binding.messageEditIndicator.visibility = View.GONE
|
||||
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
|
||||
}
|
||||
|
||||
// parent message handling
|
||||
if (!message.isDeleted && message.parentMessage != null) {
|
||||
|
@ -104,7 +104,13 @@ class OutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessageViewH
|
||||
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.messageEditIndicator.visibility = View.VISIBLE
|
||||
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp)
|
||||
} else {
|
||||
binding.messageEditIndicator.visibility = View.GONE
|
||||
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
|
||||
}
|
||||
|
||||
// parent message handling
|
||||
if (!message.isDeleted && message.parentMessage != null) {
|
||||
|
@ -357,8 +357,8 @@ public interface NcApi {
|
||||
@FormUrlEncoded
|
||||
@PUT
|
||||
Observable<Response<GenericOverall>> setPassword2(@Header("Authorization") String authorization,
|
||||
@Url String url,
|
||||
@Field("password") String password);
|
||||
@Url String url,
|
||||
@Field("password") String password);
|
||||
|
||||
@GET
|
||||
Observable<CapabilitiesOverall> getCapabilities(@Header("Authorization") String authorization, @Url String url);
|
||||
@ -393,6 +393,12 @@ public interface NcApi {
|
||||
@Field("replyTo") Integer replyTo,
|
||||
@Field("silent") Boolean sendWithoutNotification);
|
||||
|
||||
@FormUrlEncoded
|
||||
@PUT
|
||||
Observable<ChatOverallSingleMessage> editChatMessage(@Header("Authorization") String authorization,
|
||||
@Url String url,
|
||||
@Field("message") String message);
|
||||
|
||||
@GET
|
||||
Observable<Response<ChatShareOverall>> getSharedItems(
|
||||
@Header("Authorization") String authorization,
|
||||
@ -463,8 +469,8 @@ public interface NcApi {
|
||||
|
||||
@POST
|
||||
Observable<GenericOverall> setTypingStatusPrivacy(@Header("Authorization") String authorization,
|
||||
@Url String url,
|
||||
@Body RequestBody body);
|
||||
@Url String url,
|
||||
@Body RequestBody body);
|
||||
|
||||
@POST
|
||||
Observable<ContactsByNumberOverall> searchContactsByPhoneNumber(@Header("Authorization") String authorization,
|
||||
@ -504,8 +510,8 @@ public interface NcApi {
|
||||
@Multipart
|
||||
@POST
|
||||
Observable<RoomOverall> uploadConversationAvatar(@Header("Authorization") String authorization,
|
||||
@Url String url,
|
||||
@Part MultipartBody.Part attachment);
|
||||
@Url String url,
|
||||
@Part MultipartBody.Part attachment);
|
||||
|
||||
@GET
|
||||
Observable<UserProfileFieldsOverall> getEditableUserProfileFields(@Header("Authorization") String authorization,
|
||||
@ -698,6 +704,6 @@ public interface NcApi {
|
||||
@FormUrlEncoded
|
||||
@PUT
|
||||
Observable<GenericOverall> setRecordingConsent(@Header("Authorization") String authorization,
|
||||
@Url String url,
|
||||
@Field("recordingConsent") int recordingConsent);
|
||||
}
|
||||
@Url String url,
|
||||
@Field("recordingConsent") int recordingConsent);
|
||||
}
|
@ -244,6 +244,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
|
||||
@ -303,6 +304,8 @@ class ChatActivity :
|
||||
|
||||
lateinit var chatViewModel: ChatViewModel
|
||||
|
||||
private lateinit var editMessage: ChatMessage
|
||||
|
||||
override val view: View
|
||||
get() = binding.root
|
||||
|
||||
@ -361,6 +364,8 @@ class ChatActivity :
|
||||
RELEASED,
|
||||
ERROR
|
||||
}
|
||||
private val editableBehaviorSubject = BehaviorSubject.createDefault(false)
|
||||
private val editedTextBehaviorSubject = BehaviorSubject.createDefault("")
|
||||
|
||||
private var mediaRecorderState: MediaRecorderState = MediaRecorderState.INITIAL
|
||||
|
||||
@ -763,7 +768,6 @@ class ChatActivity :
|
||||
})
|
||||
|
||||
initMessageInputView()
|
||||
|
||||
loadAvatarForStatusBar()
|
||||
setActionBarTitle()
|
||||
|
||||
@ -774,10 +778,20 @@ class ChatActivity :
|
||||
val filters = arrayOfNulls<InputFilter>(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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@ -796,6 +810,8 @@ class ChatActivity :
|
||||
}
|
||||
|
||||
val editable = binding.messageInputView.inputEditText?.editableText
|
||||
editedTextBehaviorSubject.onNext(editable.toString().trim())
|
||||
|
||||
if (editable != null && binding.messageInputView.inputEditText != null) {
|
||||
val mentionSpans = editable.getSpans(
|
||||
0,
|
||||
@ -827,20 +843,37 @@ 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 (editableBehaviorSubject.value!!) {
|
||||
setEditUI()
|
||||
}
|
||||
|
||||
if (sharedText.isNotEmpty()) {
|
||||
binding.messageInputView.inputEditText?.setText(sharedText)
|
||||
}
|
||||
|
||||
binding.messageInputView.setAttachmentsListener {
|
||||
AttachmentDialog(this, this).show()
|
||||
}
|
||||
|
||||
binding.messageInputView.button?.setOnClickListener { submitMessage(false) }
|
||||
binding.messageInputView.button?.setOnClickListener {
|
||||
submitMessage(false)
|
||||
}
|
||||
|
||||
binding.messageInputView.editMessageButton.setOnClickListener {
|
||||
if (editMessage.message == editedTextBehaviorSubject.value!!) {
|
||||
clearEditUI()
|
||||
return@setOnClickListener
|
||||
}
|
||||
editMessageAPI(editMessage, editedMessageText = editedTextBehaviorSubject.value!!)
|
||||
}
|
||||
binding.editView.clearEdit.setOnClickListener {
|
||||
clearEditUI()
|
||||
}
|
||||
|
||||
if (CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "silent-send")) {
|
||||
binding.messageInputView.button?.setOnLongClickListener {
|
||||
@ -853,6 +886,82 @@ class ChatActivity :
|
||||
resources?.getString(R.string.nc_description_send_message_button)
|
||||
}
|
||||
|
||||
private fun editMessageAPI(message: ChatMessage, editedMessageText: 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
|
||||
),
|
||||
editedMessageText
|
||||
)?.subscribeOn(Schedulers.io())
|
||||
?.observeOn(AndroidSchedulers.mainThread())
|
||||
?.subscribe(object : Observer<ChatOverallSingleMessage> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
// unused atm
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
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() {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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 = View.GONE
|
||||
editableBehaviorSubject.onNext(false)
|
||||
binding.messageInputView.inputEditText.setText("")
|
||||
binding.editView.editMessageView.visibility = View.GONE
|
||||
binding.messageInputView.attachmentButton.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
private fun themeMessageInputView() {
|
||||
binding.messageInputView.button?.let { viewThemeUtils.platform.colorImageView(it, ColorRole.PRIMARY) }
|
||||
|
||||
@ -891,6 +1000,12 @@ class ChatActivity :
|
||||
binding.messageInputView.findViewById<MicInputCloud>(R.id.micInputCloud)?.let {
|
||||
viewThemeUtils.talk.themeMicInputCloud(it)
|
||||
}
|
||||
binding.messageInputView.findViewById<ImageView>(R.id.editMessageButton)?.let {
|
||||
viewThemeUtils.platform.colorImageView(it, ColorRole.PRIMARY)
|
||||
}
|
||||
binding.editView.clearEdit.let {
|
||||
viewThemeUtils.platform.colorImageView(it, ColorRole.PRIMARY)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupActionBar() {
|
||||
@ -1020,7 +1135,6 @@ class ChatActivity :
|
||||
R.layout.item_system_message,
|
||||
this
|
||||
)
|
||||
|
||||
messageHolders.registerContentType(
|
||||
CONTENT_TYPE_UNREAD_NOTICE_MESSAGE,
|
||||
UnreadNoticeMessageViewHolder::class.java,
|
||||
@ -1079,10 +1193,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
|
||||
@ -1096,10 +1212,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3851,6 +3969,12 @@ class ChatActivity :
|
||||
chatMessageIterator.remove()
|
||||
} else if (isPollVotedMessage(currentMessage)) {
|
||||
// delete poll system messages
|
||||
chatMessageIterator.remove()
|
||||
} else if (isEditMessage(currentMessage)) {
|
||||
if (!chatMessageMap.containsKey(currentMessage.value.parentMessage!!.id)) {
|
||||
setMessageAsEdited(currentMessage.value.parentMessage)
|
||||
}
|
||||
|
||||
chatMessageIterator.remove()
|
||||
}
|
||||
}
|
||||
@ -3892,6 +4016,11 @@ class ChatActivity :
|
||||
currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION_REVOKED
|
||||
}
|
||||
|
||||
private fun isEditMessage(currentMessage: MutableMap.MutableEntry<String, ChatMessage>): Boolean {
|
||||
return currentMessage.value.parentMessage != null && currentMessage.value.systemMessageType == ChatMessage
|
||||
.SystemMessageType.MESSAGE_EDITED
|
||||
}
|
||||
|
||||
private fun isPollVotedMessage(currentMessage: MutableMap.MutableEntry<String, ChatMessage>): Boolean {
|
||||
return currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.POLL_VOTED
|
||||
}
|
||||
@ -4453,6 +4582,17 @@ class ChatActivity :
|
||||
adapter?.update(messageTemp)
|
||||
}
|
||||
|
||||
private fun setMessageAsEdited(message: IMessage?) {
|
||||
val messageTemp = message as ChatMessage
|
||||
messageTemp.lastEditTimestamp = message.lastEditTimestamp
|
||||
|
||||
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
|
||||
|
||||
@ -4500,6 +4640,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) {
|
||||
@ -4507,20 +4660,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 {
|
||||
@ -4725,6 +4865,12 @@ class ChatActivity :
|
||||
startActivity(shareIntent)
|
||||
}
|
||||
|
||||
fun editMessage(message: ChatMessage) {
|
||||
editableBehaviorSubject.onNext(true)
|
||||
editMessage = message
|
||||
initMessageInputView()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = ChatActivity::class.simpleName
|
||||
private const val CONTENT_TYPE_CALL_STARTED: Byte = 1
|
||||
@ -4738,7 +4884,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
|
||||
@ -4773,6 +4918,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
|
||||
|
@ -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,
|
||||
@ -507,6 +519,7 @@ data class ChatMessage(
|
||||
GUEST_MODERATOR_PROMOTED,
|
||||
GUEST_MODERATOR_DEMOTED,
|
||||
MESSAGE_DELETED,
|
||||
MESSAGE_EDITED,
|
||||
FILE_SHARED,
|
||||
OBJECT_SHARED,
|
||||
MATTERBRIDGE_CONFIG_ADDED,
|
||||
|
@ -127,6 +127,7 @@ class EnumSystemMessageTypeConverter : StringBasedTypeConverter<ChatMessage.Syst
|
||||
"guest_moderator_promoted" -> 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<ChatMessage.Syst
|
||||
GUEST_MODERATOR_PROMOTED -> "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"
|
||||
|
@ -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
|
||||
|
@ -51,6 +51,8 @@ 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
|
||||
import com.vanniktech.emoji.EmojiTextView
|
||||
@ -60,6 +62,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)
|
||||
@ -78,6 +81,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 +94,17 @@ 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"
|
||||
) && messageHasRegularText && !isOlderThanTwentyFourHours && isUserAllowedToEdit
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
NextcloudTalkApplication.sharedApplication?.componentApplication?.inject(this)
|
||||
@ -129,6 +146,7 @@ class MessageActionsDialog(
|
||||
ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() &&
|
||||
CapabilitiesUtilNew.isTranslationsSupported(user)
|
||||
)
|
||||
initMenuEditorDetails(message.lastEditTimestamp != 0L && !message.isDeleted)
|
||||
initMenuReplyToMessage(message.replyable && hasChatPermission)
|
||||
initMenuReplyPrivately(
|
||||
message.replyable &&
|
||||
@ -136,6 +154,7 @@ class MessageActionsDialog(
|
||||
hasUserActorId(message) &&
|
||||
currentConversation?.type != ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
|
||||
)
|
||||
initMenuEditMessage(isMessageEditable)
|
||||
initMenuDeleteMessage(showMessageDeletionButton)
|
||||
initMenuForwardMessage(
|
||||
ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() &&
|
||||
@ -317,10 +336,19 @@ 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()
|
||||
}
|
||||
|
||||
dialogMessageActionsBinding.menuEditMessage.visibility = getVisibility(visible)
|
||||
}
|
||||
|
||||
private fun initMenuReplyPrivately(visible: Boolean) {
|
||||
if (visible) {
|
||||
dialogMessageActionsBinding.menuReplyPrivately.setOnClickListener {
|
||||
@ -343,6 +371,20 @@ class MessageActionsDialog(
|
||||
dialogMessageActionsBinding.menuReplyToMessage.visibility = getVisibility(visible)
|
||||
}
|
||||
|
||||
private fun initMenuEditorDetails(showEditorDetails: Boolean) {
|
||||
if (showEditorDetails) {
|
||||
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)
|
||||
}
|
||||
dialogMessageActionsBinding.menuMessageEditedInfo.visibility = getVisibility(showEditorDetails)
|
||||
}
|
||||
|
||||
private fun initMenuItemCopy(visible: Boolean) {
|
||||
if (visible) {
|
||||
dialogMessageActionsBinding.menuCopyMessage.setOnClickListener {
|
||||
@ -485,5 +527,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
|
||||
}
|
||||
}
|
||||
|
24
app/src/main/res/drawable/ic_check_24.xml
Normal file
24
app/src/main/res/drawable/ic_check_24.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<!--
|
||||
@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"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path android:fillColor="@color/fontAppbar" android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_clear_24.xml
Normal file
5
app/src/main/res/drawable/ic_clear_24.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/ic_edit_24.xml
Normal file
5
app/src/main/res/drawable/ic_edit_24.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
|
||||
</vector>
|
@ -133,14 +133,15 @@
|
||||
<include layout="@layout/item_custom_incoming_text_message_shimmer" />
|
||||
|
||||
<include layout="@layout/item_custom_incoming_text_message_shimmer" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.stfalcon.chatkit.messages.MessagesList
|
||||
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"
|
||||
@ -169,11 +170,10 @@
|
||||
app:outcomingTextSize="@dimen/chat_text_size"
|
||||
app:outcomingTimeTextSize="12sp"
|
||||
app:textAutoLink="all"
|
||||
tools:visibility="visible"/>
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.nextcloud.ui.popupbubble.PopupBubble
|
||||
android:id="@+id/popupBubbleView"
|
||||
android:theme="@style/Button.Primary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@id/typing_indicator_wrapper"
|
||||
@ -182,11 +182,12 @@
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="64dp"
|
||||
android:layout_marginBottom="26dp"
|
||||
android:minHeight="@dimen/min_size_clickable_area"
|
||||
android:layout_toStartOf="@+id/scrollDownButton"
|
||||
android:text="@string/nc_new_messages"
|
||||
app:background="@color/colorPrimary"
|
||||
android:ellipsize="middle"
|
||||
android:minHeight="@dimen/min_size_clickable_area"
|
||||
android:text="@string/nc_new_messages"
|
||||
android:theme="@style/Button.Primary"
|
||||
app:background="@color/colorPrimary"
|
||||
app:cornerRadius="@dimen/button_corner_radius"
|
||||
app:icon="@drawable/ic_baseline_arrow_downward_24px" />
|
||||
|
||||
@ -226,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">
|
||||
|
||||
<View
|
||||
android:id="@+id/separator_1"
|
||||
@ -240,15 +241,14 @@
|
||||
android:id="@+id/typing_indicator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
android:layout_marginStart="@dimen/side_margin"
|
||||
android:layout_marginEnd="@dimen/side_margin"
|
||||
android:background="@color/bg_default"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textColor="@color/low_emphasis_text"
|
||||
tools:text="Marcel is typing"
|
||||
tools:ignore="Overdraw">
|
||||
</TextView>
|
||||
tools:ignore="Overdraw"
|
||||
tools:text="Marcel is typing"></TextView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@ -259,6 +259,14 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include
|
||||
android:id="@+id/editView"
|
||||
layout="@layout/edit_message_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="6dp" >
|
||||
</include>
|
||||
|
||||
<com.nextcloud.talk.ui.MessageInput
|
||||
android:id="@+id/messageInputView"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -127,6 +127,43 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/menu_message_edited_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/bottom_sheet_item_height"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical"
|
||||
tools:ignore="UseCompoundDrawables">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/editor_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="@dimen/standard_padding"
|
||||
tools:text="@string/nc_edited_by_admin"
|
||||
android:textAlignment="viewStart"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:textSize="15sp"
|
||||
android:textColor = "@color/grey_600"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/edited_time"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="@dimen/standard_padding"
|
||||
tools:text="12:30 AM"
|
||||
android:textAlignment="viewStart"
|
||||
android:textSize="15sp"
|
||||
android:textColor ="@color/grey_600"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/menu_reply_to_message"
|
||||
android:layout_width="match_parent"
|
||||
@ -457,6 +494,41 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/menu_edit_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/bottom_sheet_item_height"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
tools:ignore="UseCompoundDrawables">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/menu_icon_edit_message"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/edit_message_icon_description"
|
||||
android:paddingStart="@dimen/standard_padding"
|
||||
android:paddingEnd="@dimen/zero"
|
||||
android:src="@drawable/ic_edit_24"
|
||||
app:tint="@color/high_emphasis_menu_icon" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/menu_text_edit_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:paddingStart="@dimen/standard_double_padding"
|
||||
android:paddingEnd="@dimen/standard_padding"
|
||||
android:text="@string/nc_edit_message"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="@color/high_emphasis_text"
|
||||
android:textSize="@dimen/bottom_sheet_text_size" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/menu_delete_message"
|
||||
android:layout_width="match_parent"
|
||||
|
60
app/src/main/res/layout/edit_message_view.xml
Normal file
60
app/src/main/res/layout/edit_message_view.xml
Normal file
@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id = "@+id/editMessageView"
|
||||
android:orientation = "horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id = "@+id/editIcon"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:padding = "12dp"
|
||||
android:src = "@drawable/ic_edit_24"
|
||||
android:layout_gravity = "start|top"
|
||||
android:contentDescription= "@string/nc_edit_icon"
|
||||
app:tint="@color/grey_600">
|
||||
|
||||
</ImageView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight = "1"
|
||||
android:orientation = "vertical">
|
||||
|
||||
<TextView
|
||||
android:id = "@+id/editMessageTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor = "@color/grey_600"
|
||||
android:text = "@string/nc_edit_message_text">
|
||||
</TextView>
|
||||
|
||||
<TextView
|
||||
android:id = "@+id/editMessage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines = "1"
|
||||
tools:text = "Edit message very very very very very very very very very very long">
|
||||
</TextView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id = "@+id/clearEdit"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:padding = "12dp"
|
||||
android:src = "@drawable/ic_clear_24"
|
||||
android:contentDescription="@string/nc_clear_edit_button"
|
||||
android:gravity = "top|end">
|
||||
</ImageView>
|
||||
|
||||
</LinearLayout>
|
@ -85,17 +85,33 @@
|
||||
android:layout_below="@id/messageText"
|
||||
android:layout_marginStart="8dp"
|
||||
android:alpha="0.6"
|
||||
android:gravity="end"
|
||||
android:textColor="@color/no_emphasis_text"
|
||||
android:textIsSelectable="false"
|
||||
android:gravity = "end"
|
||||
app:layout_alignSelf="center"
|
||||
app:layout_flexGrow="1"
|
||||
app:layout_wrapBefore="false"
|
||||
tools:text="12:38" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageEditIndicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/messageText"
|
||||
android:layout_marginStart="8dp"
|
||||
android:alpha="0.6"
|
||||
android:textColor="@color/no_emphasis_text"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_alignSelf="center"
|
||||
android:text = "@string/hint_edited_message"
|
||||
android:textSize="12sp">
|
||||
|
||||
</TextView>
|
||||
|
||||
<include
|
||||
android:id="@+id/reactions"
|
||||
layout="@layout/reactions_inside_message" />
|
||||
|
||||
</com.google.android.flexbox.FlexboxLayout>
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
@ -57,6 +57,7 @@
|
||||
android:textIsSelectable="false"
|
||||
tools:text="Talk to you later!" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@id/messageTime"
|
||||
android:layout_width="wrap_content"
|
||||
@ -72,6 +73,21 @@
|
||||
app:layout_wrapBefore="false"
|
||||
tools:text="10:35" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageEditIndicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/messageText"
|
||||
android:layout_marginStart="8dp"
|
||||
android:alpha="0.6"
|
||||
android:textColor="@color/no_emphasis_text"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_alignSelf="center"
|
||||
android:text = "@string/hint_edited_message"
|
||||
android:textSize="12sp">
|
||||
|
||||
</TextView>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/checkMark"
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -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"
|
||||
@ -229,6 +229,7 @@
|
||||
android:scaleType="centerInside"
|
||||
android:contentDescription="@string/nc_description_send_message_button" />
|
||||
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/recordAudioButton"
|
||||
android:layout_width="48dp"
|
||||
@ -239,6 +240,19 @@
|
||||
android:src="@drawable/ic_baseline_mic_24"
|
||||
android:contentDescription="@string/nc_description_record_voice" />
|
||||
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/editMessageButton"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_below="@id/quotedChatMessageView"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:background="@color/transparent"
|
||||
android:src="@drawable/ic_check_24"
|
||||
android:visibility = "gone"
|
||||
tools:visibility = "visible"
|
||||
android:contentDescription="@string/nc_send_edit_message" />
|
||||
|
||||
<Space
|
||||
android:id="@id/attachmentButtonSpace"
|
||||
android:layout_width="0dp"
|
||||
|
@ -786,5 +786,18 @@ How to translate with transifex:
|
||||
<string name="nc_caption">Caption</string>
|
||||
<string name="languages_error_title">Retrieval failed</string>
|
||||
<string name="languages_error_message">Languages could not be retrieved</string>
|
||||
<string name="edit_message_icon_description">Edit Message Icon</string>
|
||||
<string name="nc_edit_message">Edit</string>
|
||||
<string name="nc_send_edit_message">Send Edit Message</string>
|
||||
<string name="nc_clear_edit_message">Clear Edit Message</string>
|
||||
<string name="edit_error_24_hours_old_message">Cannot Edit Messages older than 24 hours</string>
|
||||
<string name="conversation_is_read_only">Conversation is read Only</string>
|
||||
<string name="nc_edit_message_text">Edit Message Text</string>
|
||||
<string name="hint_edited_message">(edited)</string>
|
||||
<string name="nc_conversation_not_found">Conversation not found</string>
|
||||
<string name="add_to_notes">Add to Notes</string>
|
||||
<string name="nc_edited_by_admin">Edited by admin</string>
|
||||
<string name="nc_edited_by">"Edited by "</string>
|
||||
<string name="nc_clear_edit_button">clear Edit Button</string>
|
||||
<string name="nc_edit_icon">Edit Icon</string>
|
||||
</resources>
|
||||
|
Loading…
Reference in New Issue
Block a user