forked from github/wulkanowy-mirror
Add message from trash restoring (#2438)
This commit is contained in:
parent
c04752ed39
commit
ea28fc783c
@ -3,5 +3,10 @@ package io.github.wulkanowy.data.enums
|
||||
enum class MessageFolder(val id: Int = 1) {
|
||||
RECEIVED(1),
|
||||
SENT(2),
|
||||
TRASHED(3)
|
||||
TRASHED(3),
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun byId(id: Int) = entries.first { it.id == id }
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import io.github.wulkanowy.data.db.entities.Recipient
|
||||
import io.github.wulkanowy.data.db.entities.Student
|
||||
import io.github.wulkanowy.data.enums.MessageFolder
|
||||
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED
|
||||
import io.github.wulkanowy.data.enums.MessageFolder.SENT
|
||||
import io.github.wulkanowy.data.enums.MessageFolder.TRASHED
|
||||
import io.github.wulkanowy.data.mappers.mapFromEntities
|
||||
import io.github.wulkanowy.data.mappers.mapToEntities
|
||||
@ -25,6 +26,7 @@ import io.github.wulkanowy.data.networkBoundResource
|
||||
import io.github.wulkanowy.data.onResourceError
|
||||
import io.github.wulkanowy.data.onResourceSuccess
|
||||
import io.github.wulkanowy.data.pojos.MessageDraft
|
||||
import io.github.wulkanowy.data.toFirstResult
|
||||
import io.github.wulkanowy.data.waitForResult
|
||||
import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase
|
||||
import io.github.wulkanowy.sdk.Sdk
|
||||
@ -34,7 +36,6 @@ import io.github.wulkanowy.utils.getRefreshKey
|
||||
import io.github.wulkanowy.utils.init
|
||||
import io.github.wulkanowy.utils.uniqueSubtract
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
@ -155,17 +156,30 @@ class MessageRepository @Inject constructor(
|
||||
subject: String,
|
||||
content: String,
|
||||
recipients: List<Recipient>,
|
||||
mailboxId: String,
|
||||
mailbox: Mailbox,
|
||||
) {
|
||||
sdk.init(student).sendMessage(
|
||||
subject = subject,
|
||||
content = content,
|
||||
recipients = recipients.mapFromEntities(),
|
||||
mailboxId = mailboxId,
|
||||
mailboxId = mailbox.globalKey,
|
||||
)
|
||||
refreshFolders(student, mailbox, listOf(SENT))
|
||||
}
|
||||
|
||||
suspend fun deleteMessages(student: Student, mailbox: Mailbox?, messages: List<Message>) {
|
||||
suspend fun restoreMessages(student: Student, mailbox: Mailbox?, messages: List<Message>) {
|
||||
sdk.init(student).restoreMessages(
|
||||
messages = messages.map { it.messageGlobalKey },
|
||||
)
|
||||
|
||||
refreshFolders(student, mailbox)
|
||||
}
|
||||
|
||||
suspend fun deleteMessage(student: Student, message: Message) {
|
||||
deleteMessages(student, listOf(message))
|
||||
}
|
||||
|
||||
suspend fun deleteMessages(student: Student, messages: List<Message>) {
|
||||
val firstMessage = messages.first()
|
||||
sdk.init(student).deleteMessages(
|
||||
messages = messages.map { it.messageGlobalKey },
|
||||
@ -184,18 +198,24 @@ class MessageRepository @Inject constructor(
|
||||
}
|
||||
|
||||
messagesDb.updateAll(deletedMessages)
|
||||
} else messagesDb.deleteAll(messages)
|
||||
|
||||
getMessages(
|
||||
student = student,
|
||||
mailbox = mailbox,
|
||||
folder = TRASHED,
|
||||
forceRefresh = true,
|
||||
).first()
|
||||
} else {
|
||||
messagesDb.deleteAll(messages)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteMessage(student: Student, mailbox: Mailbox?, message: Message) {
|
||||
deleteMessages(student, mailbox, listOf(message))
|
||||
private suspend fun refreshFolders(
|
||||
student: Student,
|
||||
mailbox: Mailbox?,
|
||||
folders: List<MessageFolder> = MessageFolder.entries
|
||||
) {
|
||||
folders.forEach {
|
||||
getMessages(
|
||||
student = student,
|
||||
mailbox = mailbox,
|
||||
folder = it,
|
||||
forceRefresh = true,
|
||||
).toFirstResult()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getMailboxes(student: Student, forceRefresh: Boolean) = networkBoundResource(
|
||||
@ -240,7 +260,7 @@ class MessageRepository @Inject constructor(
|
||||
value?.let { json.encodeToString(it) }
|
||||
)
|
||||
|
||||
suspend fun isMuted(author: String): Boolean {
|
||||
private suspend fun isMuted(author: String): Boolean {
|
||||
return mutedMessageSendersDao.checkMute(author)
|
||||
}
|
||||
|
||||
|
@ -44,8 +44,12 @@ class MessagePreviewFragment :
|
||||
|
||||
private var menuForwardButton: MenuItem? = null
|
||||
|
||||
private var menuRestoreButton: MenuItem? = null
|
||||
|
||||
private var menuDeleteButton: MenuItem? = null
|
||||
|
||||
private var menuDeleteForeverButton: MenuItem? = null
|
||||
|
||||
private var menuShareButton: MenuItem? = null
|
||||
|
||||
private var menuPrintButton: MenuItem? = null
|
||||
@ -64,6 +68,9 @@ class MessagePreviewFragment :
|
||||
override val unmuteMessageSuccessString: String
|
||||
get() = getString(R.string.message_unmute_success)
|
||||
|
||||
override val restoreMessageSuccessString: String
|
||||
get() = getString(R.string.message_restore_success)
|
||||
|
||||
override val messageNoSubjectString: String
|
||||
get() = getString(R.string.message_no_subject)
|
||||
|
||||
@ -111,7 +118,9 @@ class MessagePreviewFragment :
|
||||
inflater.inflate(R.menu.action_menu_message_preview, menu)
|
||||
menuReplyButton = menu.findItem(R.id.messagePreviewMenuReply)
|
||||
menuForwardButton = menu.findItem(R.id.messagePreviewMenuForward)
|
||||
menuRestoreButton = menu.findItem(R.id.messagePreviewMenuRestore)
|
||||
menuDeleteButton = menu.findItem(R.id.messagePreviewMenuDelete)
|
||||
menuDeleteForeverButton = menu.findItem(R.id.messagePreviewMenuDeleteForever)
|
||||
menuShareButton = menu.findItem(R.id.messagePreviewMenuShare)
|
||||
menuPrintButton = menu.findItem(R.id.messagePreviewMenuPrint)
|
||||
menuMuteButton = menu.findItem(R.id.messagePreviewMenuMute)
|
||||
@ -124,7 +133,9 @@ class MessagePreviewFragment :
|
||||
return when (item.itemId) {
|
||||
R.id.messagePreviewMenuReply -> presenter.onReply()
|
||||
R.id.messagePreviewMenuForward -> presenter.onForward()
|
||||
R.id.messagePreviewMenuRestore -> presenter.onMessageRestore()
|
||||
R.id.messagePreviewMenuDelete -> presenter.onMessageDelete()
|
||||
R.id.messagePreviewMenuDeleteForever -> presenter.onMessageDelete()
|
||||
R.id.messagePreviewMenuShare -> presenter.onShare()
|
||||
R.id.messagePreviewMenuPrint -> presenter.onPrint()
|
||||
R.id.messagePreviewMenuMute -> presenter.onMute()
|
||||
@ -152,23 +163,17 @@ class MessagePreviewFragment :
|
||||
binding.messagePreviewRecycler.visibility = if (show) VISIBLE else GONE
|
||||
}
|
||||
|
||||
override fun showOptions(show: Boolean, isReplayable: Boolean) {
|
||||
menuReplyButton?.isVisible = isReplayable
|
||||
override fun showOptions(show: Boolean, isReplayable: Boolean, isRestorable: Boolean) {
|
||||
menuReplyButton?.isVisible = show && isReplayable
|
||||
menuForwardButton?.isVisible = show
|
||||
menuDeleteButton?.isVisible = show
|
||||
menuRestoreButton?.isVisible = show && isRestorable
|
||||
menuDeleteButton?.isVisible = show && !isRestorable
|
||||
menuDeleteForeverButton?.isVisible = show && isRestorable
|
||||
menuShareButton?.isVisible = show
|
||||
menuPrintButton?.isVisible = show
|
||||
menuMuteButton?.isVisible = show && isReplayable
|
||||
}
|
||||
|
||||
override fun setDeletedOptionsLabels() {
|
||||
menuDeleteButton?.setTitle(R.string.message_delete_forever)
|
||||
}
|
||||
|
||||
override fun setNotDeletedOptionsLabels() {
|
||||
menuDeleteButton?.setTitle(R.string.message_move_to_trash)
|
||||
}
|
||||
|
||||
override fun showErrorView(show: Boolean) {
|
||||
binding.messagePreviewError.visibility = if (show) VISIBLE else GONE
|
||||
}
|
||||
|
@ -14,9 +14,11 @@ import io.github.wulkanowy.ui.base.BasePresenter
|
||||
import io.github.wulkanowy.ui.base.ErrorHandler
|
||||
import io.github.wulkanowy.utils.AnalyticsHelper
|
||||
import io.github.wulkanowy.utils.toFormattedString
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class MessagePreviewPresenter @Inject constructor(
|
||||
errorHandler: ErrorHandler,
|
||||
@ -74,6 +76,7 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
delay(1.seconds)
|
||||
view?.run {
|
||||
showMessage(messageNotExists)
|
||||
popView()
|
||||
@ -172,13 +175,51 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
return true
|
||||
}
|
||||
|
||||
private fun restoreMessage() {
|
||||
val message = messageWithAttachments?.message ?: return
|
||||
|
||||
view?.run {
|
||||
showContent(false)
|
||||
showProgress(true)
|
||||
showOptions(
|
||||
show = false,
|
||||
isReplayable = false,
|
||||
isRestorable = false,
|
||||
)
|
||||
showErrorView(false)
|
||||
}
|
||||
Timber.i("Restore message ${message.messageGlobalKey}")
|
||||
presenterScope.launch {
|
||||
runCatching {
|
||||
val student = studentRepository.getCurrentStudent(decryptPass = true)
|
||||
val mailbox = messageRepository.getMailboxByStudent(student)
|
||||
messageRepository.restoreMessages(student, mailbox, listOfNotNull(message))
|
||||
}
|
||||
.onFailure {
|
||||
retryCallback = { onMessageRestore() }
|
||||
errorHandler.dispatch(it)
|
||||
}
|
||||
.onSuccess {
|
||||
view?.run {
|
||||
showMessage(restoreMessageSuccessString)
|
||||
popView()
|
||||
}
|
||||
}
|
||||
view?.showProgress(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteMessage() {
|
||||
messageWithAttachments?.message ?: return
|
||||
|
||||
view?.run {
|
||||
showContent(false)
|
||||
showProgress(true)
|
||||
showOptions(show = false, isReplayable = false)
|
||||
showOptions(
|
||||
show = false,
|
||||
isReplayable = false,
|
||||
isRestorable = false,
|
||||
)
|
||||
showErrorView(false)
|
||||
}
|
||||
|
||||
@ -187,8 +228,7 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
presenterScope.launch {
|
||||
runCatching {
|
||||
val student = studentRepository.getCurrentStudent(decryptPass = true)
|
||||
val mailbox = messageRepository.getMailboxByStudent(student)
|
||||
messageRepository.deleteMessage(student, mailbox, messageWithAttachments?.message!!)
|
||||
messageRepository.deleteMessage(student, messageWithAttachments?.message!!)
|
||||
}.onFailure {
|
||||
retryCallback = { onMessageDelete() }
|
||||
errorHandler.dispatch(it)
|
||||
@ -213,6 +253,11 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun onMessageRestore(): Boolean {
|
||||
restoreMessage()
|
||||
return true
|
||||
}
|
||||
|
||||
fun onMessageDelete(): Boolean {
|
||||
deleteMessage()
|
||||
return true
|
||||
@ -222,15 +267,9 @@ class MessagePreviewPresenter @Inject constructor(
|
||||
view?.apply {
|
||||
showOptions(
|
||||
show = messageWithAttachments?.message != null,
|
||||
isReplayable = messageWithAttachments?.message?.folderId != MessageFolder.SENT.id,
|
||||
isReplayable = messageWithAttachments?.message?.folderId == MessageFolder.RECEIVED.id,
|
||||
isRestorable = messageWithAttachments?.message?.folderId == MessageFolder.TRASHED.id,
|
||||
)
|
||||
messageWithAttachments?.message?.let {
|
||||
when (it.folderId == MessageFolder.TRASHED.id) {
|
||||
true -> setDeletedOptionsLabels()
|
||||
false -> setNotDeletedOptionsLabels()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,8 @@ interface MessagePreviewView : BaseView {
|
||||
|
||||
val unmuteMessageSuccessString: String
|
||||
|
||||
val restoreMessageSuccessString: String
|
||||
|
||||
val messageNoSubjectString: String
|
||||
|
||||
val printHTML: String
|
||||
@ -35,11 +37,7 @@ interface MessagePreviewView : BaseView {
|
||||
|
||||
fun setErrorRetryCallback(callback: () -> Unit)
|
||||
|
||||
fun showOptions(show: Boolean, isReplayable: Boolean)
|
||||
|
||||
fun setDeletedOptionsLabels()
|
||||
|
||||
fun setNotDeletedOptionsLabels()
|
||||
fun showOptions(show: Boolean, isReplayable: Boolean, isRestorable: Boolean)
|
||||
|
||||
fun openMessageReply(message: Message?)
|
||||
|
||||
|
@ -203,7 +203,7 @@ class SendMessagePresenter @Inject constructor(
|
||||
subject = subject,
|
||||
content = content,
|
||||
recipients = recipients,
|
||||
mailboxId = mailbox.globalKey,
|
||||
mailbox = mailbox,
|
||||
)
|
||||
}.logResourceStatus("sending message").onEach {
|
||||
when (it) {
|
||||
|
@ -5,7 +5,9 @@ import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.View.*
|
||||
import android.view.View.GONE
|
||||
import android.view.View.INVISIBLE
|
||||
import android.view.View.VISIBLE
|
||||
import android.widget.CompoundButton
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.view.ActionMode
|
||||
@ -64,10 +66,12 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
if (presenter.folder == MessageFolder.TRASHED) {
|
||||
val menuItem = menu.findItem(R.id.messageTabContextMenuDelete)
|
||||
menuItem.setTitle(R.string.message_delete_forever)
|
||||
}
|
||||
val isTrashFolder = presenter.folder == MessageFolder.TRASHED
|
||||
|
||||
menu.findItem(R.id.messageTabContextMenuDelete).setVisible(!isTrashFolder)
|
||||
menu.findItem(R.id.messageTabContextMenuDeleteForever).setVisible(isTrashFolder)
|
||||
menu.findItem(R.id.messageTabContextMenuRestore).setVisible(isTrashFolder)
|
||||
|
||||
return presenter.onPrepareActionMode()
|
||||
}
|
||||
|
||||
@ -79,6 +83,8 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
|
||||
override fun onActionItemClicked(mode: ActionMode, menu: MenuItem): Boolean {
|
||||
when (menu.itemId) {
|
||||
R.id.messageTabContextMenuDelete -> presenter.onActionModeSelectDelete()
|
||||
R.id.messageTabContextMenuRestore -> presenter.onActionModeSelectRestore()
|
||||
R.id.messageTabContextMenuDeleteForever -> presenter.onActionModeSelectDelete()
|
||||
R.id.messageTabContextMenuSelectAll -> presenter.onActionModeSelectCheckAll()
|
||||
}
|
||||
return true
|
||||
|
@ -121,8 +121,27 @@ class MessageTabPresenter @Inject constructor(
|
||||
return true
|
||||
}
|
||||
|
||||
fun onActionModeSelectRestore() {
|
||||
Timber.i("Restore ${messagesToDelete.size} messages")
|
||||
val messageList = messagesToDelete.toList()
|
||||
|
||||
presenterScope.launch {
|
||||
view?.run {
|
||||
showProgress(true)
|
||||
showContent(false)
|
||||
showActionMode(false)
|
||||
}
|
||||
runCatching {
|
||||
val student = studentRepository.getCurrentStudent(true)
|
||||
messageRepository.restoreMessages(student, selectedMailbox, messageList)
|
||||
}
|
||||
.onFailure(errorHandler::dispatch)
|
||||
.onSuccess { view?.showMessage(R.string.message_messages_restored) }
|
||||
}
|
||||
}
|
||||
|
||||
fun onActionModeSelectDelete() {
|
||||
Timber.i("Delete ${messagesToDelete.size} messages)")
|
||||
Timber.i("Delete ${messagesToDelete.size} messages")
|
||||
val messageList = messagesToDelete.toList()
|
||||
|
||||
presenterScope.launch {
|
||||
@ -134,7 +153,7 @@ class MessageTabPresenter @Inject constructor(
|
||||
|
||||
runCatching {
|
||||
val student = studentRepository.getCurrentStudent(true)
|
||||
messageRepository.deleteMessages(student, selectedMailbox, messageList)
|
||||
messageRepository.deleteMessages(student, messageList)
|
||||
}
|
||||
.onFailure(errorHandler::dispatch)
|
||||
.onSuccess { view?.showMessage(R.string.message_messages_deleted) }
|
||||
|
@ -0,0 +1,9 @@
|
||||
<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="#fff"
|
||||
android:pathData="M14.12,10.47L12,12.59l-2.13,-2.12 -1.41,1.41L10.59,14l-2.12,2.12 1.41,1.41L12,15.41l2.12,2.12 1.41,-1.41L13.41,14l2.12,-2.12zM15.5,4l-1,-1h-5l-1,1H5v2h14V4zM6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM8,9h8v10H8V9z" />
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_menu_message_restore.xml
Normal file
9
app/src/main/res/drawable/ic_menu_message_restore.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<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="#fff"
|
||||
android:pathData="M15.5,4l-1,-1h-5l-1,1L5,4v2h14L19,4zM6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2L18,7L6,7v12zM8,14L8,9h8v10L8,19v-5zM10,18h4v-4h2l-4,-4 -4,4h2z" />
|
||||
</vector>
|
@ -29,6 +29,13 @@
|
||||
android:title="@string/message_forward"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/messagePreviewMenuRestore"
|
||||
android:icon="@drawable/ic_menu_message_restore"
|
||||
android:orderInCategory="1"
|
||||
android:title="@string/message_restore_from_trash"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/messagePreviewMenuDelete"
|
||||
android:icon="@drawable/ic_menu_message_delete"
|
||||
@ -36,6 +43,13 @@
|
||||
android:title="@string/message_move_to_trash"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/messagePreviewMenuDeleteForever"
|
||||
android:icon="@drawable/ic_menu_message_delete_forever"
|
||||
android:orderInCategory="1"
|
||||
android:title="@string/message_delete_forever"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="ifRoom" />
|
||||
<item
|
||||
android:id="@+id/messagePreviewMenuMute"
|
||||
android:icon="@drawable/ic_settings_notifications"
|
||||
|
@ -1,6 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/messageTabContextMenuRestore"
|
||||
android:icon="@drawable/ic_menu_message_restore"
|
||||
android:orderInCategory="1"
|
||||
android:title="@string/message_restore_from_trash"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="always" />
|
||||
<item
|
||||
android:id="@+id/messageTabContextMenuDelete"
|
||||
android:icon="@drawable/ic_menu_message_delete"
|
||||
@ -8,6 +15,13 @@
|
||||
android:title="@string/message_move_to_trash"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="always" />
|
||||
<item
|
||||
android:id="@+id/messageTabContextMenuDeleteForever"
|
||||
android:icon="@drawable/ic_menu_message_delete_forever"
|
||||
android:orderInCategory="1"
|
||||
android:title="@string/message_delete_forever"
|
||||
app:iconTint="@color/material_on_surface_emphasis_medium"
|
||||
app:showAsAction="always" />
|
||||
<item
|
||||
android:id="@+id/messageTabContextMenuSelectAll"
|
||||
android:icon="@drawable/ic_message_select_all"
|
||||
|
@ -325,8 +325,10 @@
|
||||
<string name="message_forward">Forward</string>
|
||||
<string name="message_select_all">Select all</string>
|
||||
<string name="message_unselect_all">Unselect all</string>
|
||||
<string name="message_restore_from_trash">Restore from trash</string>
|
||||
<string name="message_move_to_trash">Move to trash</string>
|
||||
<string name="message_delete_forever">Delete permanently</string>
|
||||
<string name="message_restore_success">Message restored successfully</string>
|
||||
<string name="message_delete_success">Message deleted successfully</string>
|
||||
<string name="message_mailbox_type_student">student</string>
|
||||
<string name="message_mailbox_type_parent">parent</string>
|
||||
@ -364,6 +366,7 @@
|
||||
<item quantity="other">%1$d selected</item>
|
||||
</plurals>
|
||||
<string name="message_messages_deleted">Messages deleted</string>
|
||||
<string name="message_messages_restored">Messages restored</string>
|
||||
<string name="message_mailbox_chooser_title">Choose mailbox</string>
|
||||
<string name="message_incognito_mode_on">Incognito mode is on</string>
|
||||
<string name="message_incognito_description">Thanks to incognito mode sender is not notified when you read the message</string>
|
||||
|
Loading…
Reference in New Issue
Block a user