Merge branch 'main' into feat-play-compliance
This commit is contained in:
@@ -54,8 +54,8 @@ class MailboxScreenTest {
|
||||
smtp = ServerConfig("smtp.example.org", 465, MailSecurity.SSL_TLS),
|
||||
)
|
||||
|
||||
private fun message(uid: String, subject: String, bodyFetched: Boolean = false) = Message(
|
||||
id = "imap:a:INBOX:$uid",
|
||||
private fun message(uid: String, subject: String, bodyFetched: Boolean = false, folder: String = "INBOX") = Message(
|
||||
id = "imap:a:$folder:$uid",
|
||||
accountId = "imap:a",
|
||||
sender = "Sender $uid",
|
||||
senderEmail = "s$uid@example.org",
|
||||
@@ -66,12 +66,12 @@ class MailboxScreenTest {
|
||||
timestampMillis = 1_000L,
|
||||
isRead = true,
|
||||
isStarred = false,
|
||||
folder = "INBOX",
|
||||
folder = folder,
|
||||
inInbox = true,
|
||||
bodyFetched = bodyFetched,
|
||||
)
|
||||
|
||||
private fun setContent(repo: FakeMailRepository) {
|
||||
private fun setContent(repo: FakeMailRepository): MailboxViewModel {
|
||||
val viewModel = MailboxViewModel(
|
||||
repo,
|
||||
FakeAccountRepository(accounts = listOf(account)),
|
||||
@@ -92,6 +92,7 @@ class MailboxScreenTest {
|
||||
)
|
||||
}
|
||||
}
|
||||
return viewModel
|
||||
}
|
||||
|
||||
private fun waitForText(text: String) = composeTestRule.waitUntil(5_000) {
|
||||
@@ -132,11 +133,58 @@ class MailboxScreenTest {
|
||||
composeTestRule.onNodeWithText("Second").performClick()
|
||||
composeTestRule.onNodeWithContentDescription(string(R.string.action_more)).performClick()
|
||||
|
||||
composeTestRule.onNodeWithText(string(R.string.action_archive)).assertIsDisplayed()
|
||||
composeTestRule.onNodeWithText(string(R.string.action_select_all)).assertIsDisplayed()
|
||||
composeTestRule.onNodeWithText(string(R.string.action_reply)).assertDoesNotExist()
|
||||
composeTestRule.onNodeWithText(string(R.string.action_forward)).assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun archiveIcon_isDirect_andArchivesTheSelection() {
|
||||
val repo = FakeMailRepository(messages = listOf(message("1", "First"), message("2", "Second")))
|
||||
setContent(repo)
|
||||
waitForText("First")
|
||||
|
||||
composeTestRule.onNodeWithText("First").performTouchInput { longClick() }
|
||||
composeTestRule.onNodeWithText("Second").performClick()
|
||||
// A direct icon button — no trip through the overflow menu.
|
||||
composeTestRule.onNodeWithContentDescription(string(R.string.action_archive)).performClick()
|
||||
|
||||
composeTestRule.waitUntil(5_000) { repo.archivedIds.isNotEmpty() }
|
||||
assertEquals(setOf("imap:a:INBOX:1", "imap:a:INBOX:2"), repo.archivedIds.first().toSet())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun spamIcon_isDirect_andConfirmsBeforeReporting() {
|
||||
val repo = FakeMailRepository(messages = listOf(message("1", "First")))
|
||||
setContent(repo)
|
||||
waitForText("First")
|
||||
|
||||
composeTestRule.onNodeWithText("First").performTouchInput { longClick() }
|
||||
composeTestRule.onNodeWithContentDescription(string(R.string.action_spam)).performClick()
|
||||
composeTestRule.onNodeWithText(string(R.string.confirm_spam_title)).assertIsDisplayed()
|
||||
composeTestRule.onNodeWithText(string(R.string.action_move)).performClick()
|
||||
|
||||
composeTestRule.waitUntil(5_000) { repo.spammedIds.isNotEmpty() }
|
||||
assertEquals(listOf("imap:a:INBOX:1"), repo.spammedIds.first())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun archiveIcon_hides_whileViewingTheArchiveFolder() {
|
||||
val repo = FakeMailRepository(
|
||||
messages = listOf(message("1", "Old news", folder = "Archive")),
|
||||
folders = listOf(Folder("imap:a", "Archive", "Archive", FolderRole.ARCHIVE, selectable = true)),
|
||||
)
|
||||
val viewModel = setContent(repo)
|
||||
viewModel.selectFolder("imap:a", "Archive")
|
||||
waitForText("Old news")
|
||||
|
||||
composeTestRule.onNodeWithText("Old news").performTouchInput { longClick() }
|
||||
|
||||
composeTestRule.onNodeWithContentDescription(string(R.string.action_archive)).assertDoesNotExist()
|
||||
composeTestRule.onNodeWithContentDescription(string(R.string.action_spam)).assertIsDisplayed()
|
||||
composeTestRule.onNodeWithContentDescription(string(R.string.action_delete)).assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun delete_confirmsMoveToTrash_thenTrashesViaRepository() {
|
||||
val repo = FakeMailRepository(messages = listOf(message("1", "First")))
|
||||
|
||||
@@ -31,11 +31,13 @@ import androidx.compose.material.icons.automirrored.filled.Send
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Done
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.Email
|
||||
import androidx.compose.material.icons.filled.Menu
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material.icons.filled.Warning
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DrawerValue
|
||||
@@ -530,9 +532,12 @@ private fun SelectedAvatar() {
|
||||
}
|
||||
|
||||
/**
|
||||
* The contextual action bar shown while messages are selected. Archive/Delete are the common actions;
|
||||
* the overflow holds the rest. Reply/Reply All/Forward appear only for a single selected message, and
|
||||
* Archive/Spam are hidden while already viewing that role's folder.
|
||||
* The contextual action bar shown while messages are selected. The common actions — Archive, Spam,
|
||||
* Delete — are direct icon buttons (matching the reader's icons-not-menus app bar); Archive/Spam are
|
||||
* hidden while already viewing that role's folder. The overflow keeps only the long tail: Move (no
|
||||
* usable glyph in material-icons-core) and Select all, plus Reply/Reply All/Forward for a single
|
||||
* selected message. At most four 48dp actions plus the close button fit a 320dp-wide bar; the
|
||||
* count title just truncates earlier on such screens.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -558,6 +563,18 @@ private fun SelectionTopBar(
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
if (folderRole != FolderRole.ARCHIVE) {
|
||||
IconButton(onClick = onArchive) {
|
||||
// material-icons-core ships no archive glyph; the checkmark leans on the
|
||||
// "done with it = archive it" mail idiom (Google Inbox's sweep).
|
||||
Icon(Icons.Filled.Done, contentDescription = stringResource(R.string.action_archive))
|
||||
}
|
||||
}
|
||||
if (folderRole != FolderRole.SPAM) {
|
||||
IconButton(onClick = onSpam) {
|
||||
Icon(Icons.Filled.Warning, contentDescription = stringResource(R.string.action_spam))
|
||||
}
|
||||
}
|
||||
IconButton(onClick = onDelete) {
|
||||
Icon(Icons.Filled.Delete, contentDescription = stringResource(R.string.action_delete))
|
||||
}
|
||||
@@ -566,24 +583,6 @@ private fun SelectionTopBar(
|
||||
Icon(Icons.Filled.MoreVert, contentDescription = stringResource(R.string.action_more))
|
||||
}
|
||||
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
if (folderRole != FolderRole.ARCHIVE) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.action_archive)) },
|
||||
onClick = {
|
||||
expanded = false
|
||||
onArchive()
|
||||
},
|
||||
)
|
||||
}
|
||||
if (folderRole != FolderRole.SPAM) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.action_spam)) },
|
||||
onClick = {
|
||||
expanded = false
|
||||
onSpam()
|
||||
},
|
||||
)
|
||||
}
|
||||
if (canMove) {
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringResource(R.string.action_move)) },
|
||||
|
||||
Reference in New Issue
Block a user