MailboxViewModel returns conversations as mailboxItems

when conversation view is enabled. Right now, the conversations returned
are Fake (from FakeConversationsRepository) as the repository is still
being implemented.
Moreover, some of the fields of a MailboxUiItem are not being correctly
mapped from conversations objects yet.

MAILAND-1644
This commit is contained in:
Marino Meneghel 2021-04-09 16:52:03 +02:00
parent 5a4ee61909
commit 4007127026
10 changed files with 283 additions and 13 deletions

View File

@ -48,9 +48,9 @@ import ch.protonmail.android.api.segments.event.FetchUpdatesJob;
import ch.protonmail.android.core.ProtonMailApplication;
import ch.protonmail.android.events.NoResultsEvent;
import ch.protonmail.android.jobs.SearchMessagesJob;
import ch.protonmail.android.mailbox.presentation.MailboxUiItem;
import ch.protonmail.android.mailbox.presentation.MailboxViewModel;
import ch.protonmail.android.mailbox.presentation.MailboxViewModelFactory;
import ch.protonmail.android.mailbox.presentation.model.MailboxUiItem;
import ch.protonmail.android.utils.AppUtil;
import dagger.hilt.android.AndroidEntryPoint;

View File

@ -27,7 +27,7 @@ import ch.protonmail.android.data.local.model.Label
import ch.protonmail.android.data.local.model.Message
import ch.protonmail.android.data.local.model.PendingSend
import ch.protonmail.android.data.local.model.PendingUpload
import ch.protonmail.android.mailbox.presentation.MailboxUiItem
import ch.protonmail.android.mailbox.presentation.model.MailboxUiItem
import ch.protonmail.android.utils.ui.selection.SelectionModeEnum
import kotlinx.android.synthetic.main.layout_sender_initial.view.*
import kotlinx.android.synthetic.main.list_item_mailbox.view.*
@ -179,7 +179,7 @@ class MessagesRecyclerViewAdapter(
}
this.view.setOnClickListener {
if (selectedMessageIds.isNotEmpty()) {
if (selectedMailboxItemsIds.isNotEmpty()) {
val messageId = it.tag as String
selectOrDeselectMessage(messageId, position)
} else {
@ -187,7 +187,7 @@ class MessagesRecyclerViewAdapter(
}
}
this.view.setOnLongClickListener {
if (selectedMessageIds.isEmpty()) {
if (selectedMailboxItemsIds.isEmpty()) {
val messageId = it.tag as String
return@setOnLongClickListener selectOrDeselectMessage(messageId, position)
}

View File

@ -19,7 +19,7 @@
package ch.protonmail.android.api.models;
import ch.protonmail.android.data.local.model.Message;
import ch.protonmail.android.mailbox.presentation.MailboxUiItem;
import ch.protonmail.android.mailbox.presentation.model.MailboxUiItem;
public class SimpleMessage {

View File

@ -33,6 +33,8 @@ import ch.protonmail.android.contacts.groups.edit.chooser.AddressChooserViewMode
import ch.protonmail.android.core.ProtonMailApplication
import ch.protonmail.android.core.UserManager
import ch.protonmail.android.data.ContactsRepository
import ch.protonmail.android.mailbox.domain.GetConversations
import ch.protonmail.android.mailbox.presentation.ConversationModeEnabled
import ch.protonmail.android.mailbox.presentation.MailboxViewModel
import ch.protonmail.android.settings.pin.viewmodel.PinFragmentViewModelFactory
import ch.protonmail.android.usecase.VerifyConnection
@ -98,7 +100,9 @@ internal class ViewModelModule {
contactsRepository: ContactsRepository,
verifyConnection: VerifyConnection,
networkConfigurator: NetworkConfigurator,
messageServiceScheduler: MessagesService.Scheduler
messageServiceScheduler: MessagesService.Scheduler,
conversationModeEnabled: ConversationModeEnabled,
getConversations: GetConversations
) = MailboxViewModel(
messageDetailsRepository,
userManager,
@ -108,6 +112,8 @@ internal class ViewModelModule {
contactsRepository,
verifyConnection,
networkConfigurator,
messageServiceScheduler
messageServiceScheduler,
conversationModeEnabled,
getConversations
)
}

View File

@ -0,0 +1,86 @@
/*
* Copyright (c) 2020 Proton Technologies AG
*
* This file is part of ProtonMail.
*
* ProtonMail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonMail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.mailbox.data
import ch.protonmail.android.mailbox.domain.Conversation
import ch.protonmail.android.mailbox.domain.ConversationsRepository
import ch.protonmail.android.mailbox.domain.model.Correspondent
import ch.protonmail.android.mailbox.domain.model.GetConversationsParameters
import ch.protonmail.android.mailbox.domain.model.MessageEntity
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import me.proton.core.domain.arch.DataResult
import me.proton.core.domain.arch.ResponseSource
import javax.inject.Inject
class FakeConversationsRepository @Inject constructor() : ConversationsRepository {
override fun getConversations(params: GetConversationsParameters): Flow<DataResult<List<Conversation>>> {
val sender = Correspondent("senderName", "sender@protonmail.com")
val recipients = listOf(
Correspondent("recipient", "recipient@protonmail.com"),
Correspondent("recipient1", "recipient1@pm.ch")
)
val correspondent = Correspondent("conversation sender", "bomber@pm.me")
return flowOf(
DataResult.Success(
ResponseSource.Local,
listOf(
Conversation(
"conversationId",
"A Fake conversation for you",
listOf(correspondent),
recipients,
4,
1,
2,
0,
"senderAddressId",
listOf(),
listOf(
MessageEntity(
"messageId",
"conversationId",
"subject",
true,
sender,
recipients,
123421L,
0,
0,
false,
false,
true,
emptyList(),
emptyList(),
""
)
)
)
)
)
)
}
override suspend fun getConversation(conversationId: String, messageId: String): Conversation? {
TODO("Not yet implemented")
}
}

View File

@ -138,6 +138,7 @@ import ch.protonmail.android.jobs.PostTrashJobV2
import ch.protonmail.android.jobs.PostUnreadJob
import ch.protonmail.android.jobs.PostUnstarJob
import ch.protonmail.android.mailbox.presentation.MailboxViewModel.MaxLabelsReached
import ch.protonmail.android.mailbox.presentation.model.MailboxUiItem
import ch.protonmail.android.prefs.SecureSharedPreferences
import ch.protonmail.android.servers.notification.EXTRA_MAILBOX_LOCATION
import ch.protonmail.android.settings.pin.EXTRA_TOTAL_COUNT_EVENT

View File

@ -20,6 +20,8 @@ package ch.protonmail.android.mailbox.presentation
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.map
import androidx.lifecycle.switchMap
import androidx.lifecycle.viewModelScope
import ch.protonmail.android.activities.messageDetails.repository.MessageDetailsRepository
@ -37,6 +39,10 @@ import ch.protonmail.android.domain.entity.Id
import ch.protonmail.android.jobs.ApplyLabelJob
import ch.protonmail.android.jobs.FetchByLocationJob
import ch.protonmail.android.jobs.RemoveLabelJob
import ch.protonmail.android.mailbox.domain.GetConversations
import ch.protonmail.android.mailbox.domain.GetConversationsResult
import ch.protonmail.android.mailbox.presentation.model.MailboxUiItem
import ch.protonmail.android.mailbox.presentation.model.MessageData
import ch.protonmail.android.usecase.VerifyConnection
import ch.protonmail.android.usecase.delete.DeleteMessage
import ch.protonmail.android.utils.Event
@ -67,7 +73,9 @@ class MailboxViewModel @Inject constructor(
private val contactsRepository: ContactsRepository,
verifyConnection: VerifyConnection,
networkConfigurator: NetworkConfigurator,
private val messageServiceScheduler: MessagesService.Scheduler
private val messageServiceScheduler: MessagesService.Scheduler,
private val conversationModeEnabled: ConversationModeEnabled,
private val getConversations: GetConversations
) : ConnectivityBaseViewModel(verifyConnection, networkConfigurator) {
var pendingSendsLiveData = messageDetailsRepository.findAllPendingSendsAsync()
@ -199,6 +207,28 @@ class MailboxViewModel @Inject constructor(
uuid: String,
refreshMessages: Boolean,
earliestTime: Long? = null
): LiveData<List<MailboxUiItem>> {
if (conversationModeEnabled(location)) {
return getConversationsAsMailboxItems(location)
}
return getMessagesAsMailboxItems(
earliestTime,
location,
labelId,
includeLabels,
uuid,
refreshMessages
)
}
private fun getMessagesAsMailboxItems(
earliestTime: Long?,
location: Constants.MessageLocationType,
labelId: String?,
includeLabels: Boolean,
uuid: String,
refreshMessages: Boolean
): LiveData<List<MailboxUiItem>> {
// When earliest time is valid the request is about paginated messages (page > 1)
if (earliestTime != null) {
@ -235,6 +265,33 @@ class MailboxViewModel @Inject constructor(
}
}
private fun getConversationsAsMailboxItems(
location: Constants.MessageLocationType
): LiveData<List<MailboxUiItem>> {
return getConversations(location).asLiveData().map { result ->
if (result is GetConversationsResult.Success) {
return@map result.conversations.map { conversation ->
MailboxUiItem(
conversation.id,
conversation.senders.first().name,
conversation.subject,
0L,
conversation.attachmentsCount > 0,
false,
conversation.unreadCount == 0,
conversation.expirationTime,
conversation.messagesCount,
null,
false,
conversation.labelIds,
conversation.receivers.joinToString { it.name }
)
}
}
return@map listOf()
}
}
fun messagesToMailboxItems(messages: List<Message>): LiveData<List<MailboxUiItem>> {
val mailboxItems = MutableLiveData<List<MailboxUiItem>>()
viewModelScope.launch(dispatchers.Io) {

View File

@ -17,7 +17,7 @@
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.mailbox.presentation
package ch.protonmail.android.mailbox.presentation.model
data class MailboxUiItem(
val itemId: String,

View File

@ -30,7 +30,7 @@ import androidx.recyclerview.widget.RecyclerView
import ch.protonmail.android.R
import ch.protonmail.android.core.Constants
import ch.protonmail.android.data.local.model.Label
import ch.protonmail.android.mailbox.presentation.MailboxUiItem
import ch.protonmail.android.mailbox.presentation.model.MailboxUiItem
import ch.protonmail.android.utils.DateUtil
import ch.protonmail.android.utils.UiUtil
import kotlinx.android.synthetic.main.list_item_mailbox.view.*

View File

@ -34,9 +34,15 @@ import ch.protonmail.android.data.local.model.MessageSender
import ch.protonmail.android.di.JobEntryPoint
import ch.protonmail.android.domain.entity.Id
import ch.protonmail.android.jobs.FetchByLocationJob
import ch.protonmail.android.mailbox.presentation.MailboxUiItem
import ch.protonmail.android.mailbox.domain.Conversation
import ch.protonmail.android.mailbox.domain.GetConversations
import ch.protonmail.android.mailbox.domain.GetConversationsResult
import ch.protonmail.android.mailbox.domain.model.Correspondent
import ch.protonmail.android.mailbox.domain.model.MessageEntity
import ch.protonmail.android.mailbox.presentation.ConversationModeEnabled
import ch.protonmail.android.mailbox.presentation.MailboxViewModel
import ch.protonmail.android.mailbox.presentation.MessageData
import ch.protonmail.android.mailbox.presentation.model.MailboxUiItem
import ch.protonmail.android.mailbox.presentation.model.MessageData
import ch.protonmail.android.testAndroid.lifecycle.testObserver
import ch.protonmail.android.usecase.VerifyConnection
import ch.protonmail.android.usecase.delete.DeleteMessage
@ -44,9 +50,11 @@ import ch.protonmail.android.utils.MessageUtils
import ch.protonmail.android.utils.MessageUtils.toContactsAndGroupsString
import com.birbit.android.jobqueue.JobManager
import dagger.hilt.EntryPoints
import io.mockk.Called
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.coVerifySequence
import io.mockk.every
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.mockk
@ -56,6 +64,7 @@ import io.mockk.unmockkStatic
import io.mockk.verify
import io.mockk.verifySequence
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runBlockingTest
import me.proton.core.test.kotlin.CoroutinesTest
import org.junit.Rule
import kotlin.test.BeforeTest
@ -92,6 +101,12 @@ class MailboxViewModelTest : CoroutinesTest {
@RelaxedMockK
private lateinit var messageServiceScheduler: MessagesService.Scheduler
@RelaxedMockK
private lateinit var conversationModeEnabled: ConversationModeEnabled
@RelaxedMockK
private lateinit var getConversations: GetConversations
private lateinit var viewModel: MailboxViewModel
@BeforeTest
@ -106,7 +121,9 @@ class MailboxViewModelTest : CoroutinesTest {
contactsRepository,
verifyConnection,
networkConfigurator,
messageServiceScheduler
messageServiceScheduler,
conversationModeEnabled,
getConversations
)
}
@ -503,6 +520,109 @@ class MailboxViewModelTest : CoroutinesTest {
assertEquals(expected, actual.observedValues.first())
}
@Test
fun getMailboxItemsCallsGetConversationsUseCaseWhenConversationModeIsEnabled() = runBlockingTest {
val location = Constants.MessageLocationType.INBOX
val labelId = "labelId923842"
val includeLabels = true
val uuid = "9238423bbe2h3423489wssdf"
val refreshMessages = false
every { conversationModeEnabled(location) } returns true
viewModel.getMailboxItems(
location,
labelId,
includeLabels,
uuid,
refreshMessages,
123L
)
coVerifySequence { getConversations(location) }
verify { messageServiceScheduler wasNot Called }
}
@Test
fun getMailboxItemsReturnsMailboxItemsMappedFromConversationsWhenGetConversationsUseCaseSucceeds() =
runBlockingTest {
val location = Constants.MessageLocationType.INBOX
val labelId = "labelId923842"
val includeLabels = true
val uuid = "9238423bbe2h3423489wssdf"
val refreshMessages = false
val sender = Correspondent("senderName", "sender@protonmail.com")
val recipients = listOf(
Correspondent("recipient", "recipient@protonmail.com"),
Correspondent("recipient1", "recipient1@pm.ch")
)
val conversation = buildFakeConversation(sender, recipients)
val successResult = GetConversationsResult.Success(listOf(conversation))
every { conversationModeEnabled(location) } returns true
coEvery { getConversations(location) } returns flowOf(successResult)
val actual = viewModel.getMailboxItems(
location,
labelId,
includeLabels,
uuid,
refreshMessages,
).testObserver()
val expected = listOf(
MailboxUiItem(
"conversationId",
"senderName",
"subject",
0,
hasAttachments = true,
isStarred = false,
isRead = false,
expirationTime = 0,
messagesCount = 4,
messageData = null,
isDeleted = false,
labelIds = emptyList(),
recipients = "recipient, recipient1"
)
)
assertEquals(expected, actual.observedValues.first())
}
private fun buildFakeConversation(
sender: Correspondent,
recipients: List<Correspondent>
) = Conversation(
"conversationId",
"subject",
listOf(sender),
recipients,
4,
1,
2,
0,
"senderAddressId",
listOf(),
listOf(
MessageEntity(
"messageId",
"conversationId",
"subject",
true,
sender,
recipients,
123421L,
0,
0,
false,
false,
true,
emptyList(),
emptyList(),
""
)
)
)
private fun fakeMailboxUiData(
itemId: String,
senderName: String,