Merge branch 'develop' into feature/attendance-excuse-whole-day

This commit is contained in:
Mikołaj Pich 2024-03-01 21:16:06 +01:00
commit 1591d494ab
13 changed files with 192 additions and 51 deletions

View File

@ -3,5 +3,10 @@ package io.github.wulkanowy.data.enums
enum class MessageFolder(val id: Int = 1) { enum class MessageFolder(val id: Int = 1) {
RECEIVED(1), RECEIVED(1),
SENT(2), SENT(2),
TRASHED(3) TRASHED(3),
;
companion object {
fun byId(id: Int) = entries.first { it.id == id }
}
} }

View File

@ -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.db.entities.Student
import io.github.wulkanowy.data.enums.MessageFolder import io.github.wulkanowy.data.enums.MessageFolder
import io.github.wulkanowy.data.enums.MessageFolder.RECEIVED 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.enums.MessageFolder.TRASHED
import io.github.wulkanowy.data.mappers.mapFromEntities import io.github.wulkanowy.data.mappers.mapFromEntities
import io.github.wulkanowy.data.mappers.mapToEntities 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.onResourceError
import io.github.wulkanowy.data.onResourceSuccess import io.github.wulkanowy.data.onResourceSuccess
import io.github.wulkanowy.data.pojos.MessageDraft import io.github.wulkanowy.data.pojos.MessageDraft
import io.github.wulkanowy.data.toFirstResult
import io.github.wulkanowy.data.waitForResult import io.github.wulkanowy.data.waitForResult
import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase import io.github.wulkanowy.domain.messages.GetMailboxByStudentUseCase
import io.github.wulkanowy.sdk.Sdk 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.init
import io.github.wulkanowy.utils.uniqueSubtract import io.github.wulkanowy.utils.uniqueSubtract
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@ -155,17 +156,30 @@ class MessageRepository @Inject constructor(
subject: String, subject: String,
content: String, content: String,
recipients: List<Recipient>, recipients: List<Recipient>,
mailboxId: String, mailbox: Mailbox,
) { ) {
sdk.init(student).sendMessage( sdk.init(student).sendMessage(
subject = subject, subject = subject,
content = content, content = content,
recipients = recipients.mapFromEntities(), 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() val firstMessage = messages.first()
sdk.init(student).deleteMessages( sdk.init(student).deleteMessages(
messages = messages.map { it.messageGlobalKey }, messages = messages.map { it.messageGlobalKey },
@ -184,18 +198,24 @@ class MessageRepository @Inject constructor(
} }
messagesDb.updateAll(deletedMessages) messagesDb.updateAll(deletedMessages)
} else messagesDb.deleteAll(messages) } else {
messagesDb.deleteAll(messages)
}
}
private suspend fun refreshFolders(
student: Student,
mailbox: Mailbox?,
folders: List<MessageFolder> = MessageFolder.entries
) {
folders.forEach {
getMessages( getMessages(
student = student, student = student,
mailbox = mailbox, mailbox = mailbox,
folder = TRASHED, folder = it,
forceRefresh = true, forceRefresh = true,
).first() ).toFirstResult()
} }
suspend fun deleteMessage(student: Student, mailbox: Mailbox?, message: Message) {
deleteMessages(student, mailbox, listOf(message))
} }
suspend fun getMailboxes(student: Student, forceRefresh: Boolean) = networkBoundResource( suspend fun getMailboxes(student: Student, forceRefresh: Boolean) = networkBoundResource(
@ -240,7 +260,7 @@ class MessageRepository @Inject constructor(
value?.let { json.encodeToString(it) } value?.let { json.encodeToString(it) }
) )
suspend fun isMuted(author: String): Boolean { private suspend fun isMuted(author: String): Boolean {
return mutedMessageSendersDao.checkMute(author) return mutedMessageSendersDao.checkMute(author)
} }

View File

@ -44,8 +44,12 @@ class MessagePreviewFragment :
private var menuForwardButton: MenuItem? = null private var menuForwardButton: MenuItem? = null
private var menuRestoreButton: MenuItem? = null
private var menuDeleteButton: MenuItem? = null private var menuDeleteButton: MenuItem? = null
private var menuDeleteForeverButton: MenuItem? = null
private var menuShareButton: MenuItem? = null private var menuShareButton: MenuItem? = null
private var menuPrintButton: MenuItem? = null private var menuPrintButton: MenuItem? = null
@ -64,6 +68,9 @@ class MessagePreviewFragment :
override val unmuteMessageSuccessString: String override val unmuteMessageSuccessString: String
get() = getString(R.string.message_unmute_success) get() = getString(R.string.message_unmute_success)
override val restoreMessageSuccessString: String
get() = getString(R.string.message_restore_success)
override val messageNoSubjectString: String override val messageNoSubjectString: String
get() = getString(R.string.message_no_subject) get() = getString(R.string.message_no_subject)
@ -111,7 +118,9 @@ class MessagePreviewFragment :
inflater.inflate(R.menu.action_menu_message_preview, menu) inflater.inflate(R.menu.action_menu_message_preview, menu)
menuReplyButton = menu.findItem(R.id.messagePreviewMenuReply) menuReplyButton = menu.findItem(R.id.messagePreviewMenuReply)
menuForwardButton = menu.findItem(R.id.messagePreviewMenuForward) menuForwardButton = menu.findItem(R.id.messagePreviewMenuForward)
menuRestoreButton = menu.findItem(R.id.messagePreviewMenuRestore)
menuDeleteButton = menu.findItem(R.id.messagePreviewMenuDelete) menuDeleteButton = menu.findItem(R.id.messagePreviewMenuDelete)
menuDeleteForeverButton = menu.findItem(R.id.messagePreviewMenuDeleteForever)
menuShareButton = menu.findItem(R.id.messagePreviewMenuShare) menuShareButton = menu.findItem(R.id.messagePreviewMenuShare)
menuPrintButton = menu.findItem(R.id.messagePreviewMenuPrint) menuPrintButton = menu.findItem(R.id.messagePreviewMenuPrint)
menuMuteButton = menu.findItem(R.id.messagePreviewMenuMute) menuMuteButton = menu.findItem(R.id.messagePreviewMenuMute)
@ -124,7 +133,9 @@ class MessagePreviewFragment :
return when (item.itemId) { return when (item.itemId) {
R.id.messagePreviewMenuReply -> presenter.onReply() R.id.messagePreviewMenuReply -> presenter.onReply()
R.id.messagePreviewMenuForward -> presenter.onForward() R.id.messagePreviewMenuForward -> presenter.onForward()
R.id.messagePreviewMenuRestore -> presenter.onMessageRestore()
R.id.messagePreviewMenuDelete -> presenter.onMessageDelete() R.id.messagePreviewMenuDelete -> presenter.onMessageDelete()
R.id.messagePreviewMenuDeleteForever -> presenter.onMessageDelete()
R.id.messagePreviewMenuShare -> presenter.onShare() R.id.messagePreviewMenuShare -> presenter.onShare()
R.id.messagePreviewMenuPrint -> presenter.onPrint() R.id.messagePreviewMenuPrint -> presenter.onPrint()
R.id.messagePreviewMenuMute -> presenter.onMute() R.id.messagePreviewMenuMute -> presenter.onMute()
@ -152,23 +163,17 @@ class MessagePreviewFragment :
binding.messagePreviewRecycler.visibility = if (show) VISIBLE else GONE binding.messagePreviewRecycler.visibility = if (show) VISIBLE else GONE
} }
override fun showOptions(show: Boolean, isReplayable: Boolean) { override fun showOptions(show: Boolean, isReplayable: Boolean, isRestorable: Boolean) {
menuReplyButton?.isVisible = isReplayable menuReplyButton?.isVisible = show && isReplayable
menuForwardButton?.isVisible = show menuForwardButton?.isVisible = show
menuDeleteButton?.isVisible = show menuRestoreButton?.isVisible = show && isRestorable
menuDeleteButton?.isVisible = show && !isRestorable
menuDeleteForeverButton?.isVisible = show && isRestorable
menuShareButton?.isVisible = show menuShareButton?.isVisible = show
menuPrintButton?.isVisible = show menuPrintButton?.isVisible = show
menuMuteButton?.isVisible = show && isReplayable 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) { override fun showErrorView(show: Boolean) {
binding.messagePreviewError.visibility = if (show) VISIBLE else GONE binding.messagePreviewError.visibility = if (show) VISIBLE else GONE
} }

View File

@ -14,9 +14,11 @@ import io.github.wulkanowy.ui.base.BasePresenter
import io.github.wulkanowy.ui.base.ErrorHandler import io.github.wulkanowy.ui.base.ErrorHandler
import io.github.wulkanowy.utils.AnalyticsHelper import io.github.wulkanowy.utils.AnalyticsHelper
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
class MessagePreviewPresenter @Inject constructor( class MessagePreviewPresenter @Inject constructor(
errorHandler: ErrorHandler, errorHandler: ErrorHandler,
@ -74,6 +76,7 @@ class MessagePreviewPresenter @Inject constructor(
} }
} }
} else { } else {
delay(1.seconds)
view?.run { view?.run {
showMessage(messageNotExists) showMessage(messageNotExists)
popView() popView()
@ -172,13 +175,51 @@ class MessagePreviewPresenter @Inject constructor(
return true 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() { private fun deleteMessage() {
messageWithAttachments?.message ?: return messageWithAttachments?.message ?: return
view?.run { view?.run {
showContent(false) showContent(false)
showProgress(true) showProgress(true)
showOptions(show = false, isReplayable = false) showOptions(
show = false,
isReplayable = false,
isRestorable = false,
)
showErrorView(false) showErrorView(false)
} }
@ -187,8 +228,7 @@ class MessagePreviewPresenter @Inject constructor(
presenterScope.launch { presenterScope.launch {
runCatching { runCatching {
val student = studentRepository.getCurrentStudent(decryptPass = true) val student = studentRepository.getCurrentStudent(decryptPass = true)
val mailbox = messageRepository.getMailboxByStudent(student) messageRepository.deleteMessage(student, messageWithAttachments?.message!!)
messageRepository.deleteMessage(student, mailbox, messageWithAttachments?.message!!)
}.onFailure { }.onFailure {
retryCallback = { onMessageDelete() } retryCallback = { onMessageDelete() }
errorHandler.dispatch(it) errorHandler.dispatch(it)
@ -213,6 +253,11 @@ class MessagePreviewPresenter @Inject constructor(
} }
} }
fun onMessageRestore(): Boolean {
restoreMessage()
return true
}
fun onMessageDelete(): Boolean { fun onMessageDelete(): Boolean {
deleteMessage() deleteMessage()
return true return true
@ -222,15 +267,9 @@ class MessagePreviewPresenter @Inject constructor(
view?.apply { view?.apply {
showOptions( showOptions(
show = messageWithAttachments?.message != null, 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()
}
}
} }
} }

View File

@ -13,6 +13,8 @@ interface MessagePreviewView : BaseView {
val unmuteMessageSuccessString: String val unmuteMessageSuccessString: String
val restoreMessageSuccessString: String
val messageNoSubjectString: String val messageNoSubjectString: String
val printHTML: String val printHTML: String
@ -35,11 +37,7 @@ interface MessagePreviewView : BaseView {
fun setErrorRetryCallback(callback: () -> Unit) fun setErrorRetryCallback(callback: () -> Unit)
fun showOptions(show: Boolean, isReplayable: Boolean) fun showOptions(show: Boolean, isReplayable: Boolean, isRestorable: Boolean)
fun setDeletedOptionsLabels()
fun setNotDeletedOptionsLabels()
fun openMessageReply(message: Message?) fun openMessageReply(message: Message?)

View File

@ -203,7 +203,7 @@ class SendMessagePresenter @Inject constructor(
subject = subject, subject = subject,
content = content, content = content,
recipients = recipients, recipients = recipients,
mailboxId = mailbox.globalKey, mailbox = mailbox,
) )
}.logResourceStatus("sending message").onEach { }.logResourceStatus("sending message").onEach {
when (it) { when (it) {

View File

@ -5,7 +5,9 @@ import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View 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 android.widget.CompoundButton
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
@ -64,10 +66,12 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
} }
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
if (presenter.folder == MessageFolder.TRASHED) { val isTrashFolder = presenter.folder == MessageFolder.TRASHED
val menuItem = menu.findItem(R.id.messageTabContextMenuDelete)
menuItem.setTitle(R.string.message_delete_forever) menu.findItem(R.id.messageTabContextMenuDelete).setVisible(!isTrashFolder)
} menu.findItem(R.id.messageTabContextMenuDeleteForever).setVisible(isTrashFolder)
menu.findItem(R.id.messageTabContextMenuRestore).setVisible(isTrashFolder)
return presenter.onPrepareActionMode() return presenter.onPrepareActionMode()
} }
@ -79,6 +83,8 @@ class MessageTabFragment : BaseFragment<FragmentMessageTabBinding>(R.layout.frag
override fun onActionItemClicked(mode: ActionMode, menu: MenuItem): Boolean { override fun onActionItemClicked(mode: ActionMode, menu: MenuItem): Boolean {
when (menu.itemId) { when (menu.itemId) {
R.id.messageTabContextMenuDelete -> presenter.onActionModeSelectDelete() R.id.messageTabContextMenuDelete -> presenter.onActionModeSelectDelete()
R.id.messageTabContextMenuRestore -> presenter.onActionModeSelectRestore()
R.id.messageTabContextMenuDeleteForever -> presenter.onActionModeSelectDelete()
R.id.messageTabContextMenuSelectAll -> presenter.onActionModeSelectCheckAll() R.id.messageTabContextMenuSelectAll -> presenter.onActionModeSelectCheckAll()
} }
return true return true

View File

@ -121,8 +121,27 @@ class MessageTabPresenter @Inject constructor(
return true 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() { fun onActionModeSelectDelete() {
Timber.i("Delete ${messagesToDelete.size} messages)") Timber.i("Delete ${messagesToDelete.size} messages")
val messageList = messagesToDelete.toList() val messageList = messagesToDelete.toList()
presenterScope.launch { presenterScope.launch {
@ -134,7 +153,7 @@ class MessageTabPresenter @Inject constructor(
runCatching { runCatching {
val student = studentRepository.getCurrentStudent(true) val student = studentRepository.getCurrentStudent(true)
messageRepository.deleteMessages(student, selectedMailbox, messageList) messageRepository.deleteMessages(student, messageList)
} }
.onFailure(errorHandler::dispatch) .onFailure(errorHandler::dispatch)
.onSuccess { view?.showMessage(R.string.message_messages_deleted) } .onSuccess { view?.showMessage(R.string.message_messages_deleted) }

View 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="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>

View 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>

View File

@ -29,6 +29,13 @@
android:title="@string/message_forward" android:title="@string/message_forward"
app:iconTint="@color/material_on_surface_emphasis_medium" app:iconTint="@color/material_on_surface_emphasis_medium"
app:showAsAction="ifRoom" /> 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 <item
android:id="@+id/messagePreviewMenuDelete" android:id="@+id/messagePreviewMenuDelete"
android:icon="@drawable/ic_menu_message_delete" android:icon="@drawable/ic_menu_message_delete"
@ -36,6 +43,13 @@
android:title="@string/message_move_to_trash" android:title="@string/message_move_to_trash"
app:iconTint="@color/material_on_surface_emphasis_medium" app:iconTint="@color/material_on_surface_emphasis_medium"
app:showAsAction="ifRoom" /> 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 <item
android:id="@+id/messagePreviewMenuMute" android:id="@+id/messagePreviewMenuMute"
android:icon="@drawable/ic_settings_notifications" android:icon="@drawable/ic_settings_notifications"

View File

@ -1,6 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> 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 <item
android:id="@+id/messageTabContextMenuDelete" android:id="@+id/messageTabContextMenuDelete"
android:icon="@drawable/ic_menu_message_delete" android:icon="@drawable/ic_menu_message_delete"
@ -8,6 +15,13 @@
android:title="@string/message_move_to_trash" android:title="@string/message_move_to_trash"
app:iconTint="@color/material_on_surface_emphasis_medium" app:iconTint="@color/material_on_surface_emphasis_medium"
app:showAsAction="always" /> 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 <item
android:id="@+id/messageTabContextMenuSelectAll" android:id="@+id/messageTabContextMenuSelectAll"
android:icon="@drawable/ic_message_select_all" android:icon="@drawable/ic_message_select_all"

View File

@ -327,8 +327,10 @@
<string name="message_forward">Forward</string> <string name="message_forward">Forward</string>
<string name="message_select_all">Select all</string> <string name="message_select_all">Select all</string>
<string name="message_unselect_all">Unselect 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_move_to_trash">Move to trash</string>
<string name="message_delete_forever">Delete permanently</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_delete_success">Message deleted successfully</string>
<string name="message_mailbox_type_student">student</string> <string name="message_mailbox_type_student">student</string>
<string name="message_mailbox_type_parent">parent</string> <string name="message_mailbox_type_parent">parent</string>
@ -366,6 +368,7 @@
<item quantity="other">%1$d selected</item> <item quantity="other">%1$d selected</item>
</plurals> </plurals>
<string name="message_messages_deleted">Messages deleted</string> <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_mailbox_chooser_title">Choose mailbox</string>
<string name="message_incognito_mode_on">Incognito mode is on</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> <string name="message_incognito_description">Thanks to incognito mode sender is not notified when you read the message</string>