More logic added to ContactDetailsViewModel and some refactor of related components.

MAILAND-1875
This commit is contained in:
Tomasz Giszczak 2021-05-26 15:27:18 +02:00
parent cb9dc91e8b
commit 11a89b21f4
15 changed files with 128 additions and 31 deletions

View File

@ -186,7 +186,7 @@ internal class ContactDaoTest {
//hack as encrypted data has equals not defined
val actualFullContactDetailsSet = expectedFullContactDetails
.map(FullContactDetails::contactId)
.map(database::findFullContactDetailsById)
.map(database::findFullContactDetailsByIdBlocking)
.map { it?.apply { encryptedData = mutableListOf() } }
.toSet()
@ -296,7 +296,7 @@ internal class ContactDaoTest {
fun findContactEmailsByContactId() {
val contactId = contactEmails[2].contactId!!
val expected = contactEmails.filter { it.contactId == contactId }
val actual = database.findContactEmailsByContactId(contactId)
val actual = database.findContactEmailsByContactIdBlocking(contactId)
Assert.assertEquals(expected, actual)
assertDatabaseState()
}
@ -535,7 +535,7 @@ internal class ContactDaoTest {
@Test
fun findFullContactDetailsById() {
val expected = fullContactDetails[1]
val actual = database.findFullContactDetailsById(expected.contactId)
val actual = database.findFullContactDetailsByIdBlocking(expected.contactId)
Assert.assertThat(
actual, `is`(FullContactsDetailsMatcher(expected))
)
@ -554,7 +554,7 @@ internal class ContactDaoTest {
val deleted = fullContactDetails[1]
val expected = fullContactDetails - deleted
database.deleteFullContactsDetails(deleted)
val found = database.findFullContactDetailsById(deleted.contactId)
val found = database.findFullContactDetailsByIdBlocking(deleted.contactId)
Assert.assertNull(found)
assertDatabaseState(expectedFullContactDetails = expected)
}

View File

@ -32,7 +32,7 @@ class ExtractFullContactDetailsTask(
override fun doInBackground(vararg voids: Void): FullContactDetails? {
return try {
contactDao.findFullContactDetailsById(contactId)
contactDao.findFullContactDetailsByIdBlocking(contactId)
} catch (tooBigException: SQLiteBlobTooBigException) {
Timber.i(tooBigException, "Data too big to be fetched")
null

View File

@ -466,7 +466,7 @@ class EventHandler @AssistedInject constructor(
}
val localFullContact = try {
contactDao.findFullContactDetailsById(contactId)
contactDao.findFullContactDetailsByIdBlocking(contactId)
} catch (tooBigException: SQLiteBlobTooBigException) {
Timber.i(tooBigException, "Data too big to be fetched")
null

View File

@ -20,15 +20,18 @@
package ch.protonmail.android.contacts.details
import android.os.Bundle
import android.view.MenuItem
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import ch.protonmail.android.R
import ch.protonmail.android.databinding.ActivityContactDetailsBinding
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class ContactDetailsActivity : AppCompatActivity() {
private val viewModel by viewModels<ContactDetailsViewModel>()
private val viewModel: ContactDetailsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -45,6 +48,14 @@ class ContactDetailsActivity : AppCompatActivity() {
viewModel.getContactDetails(contactId)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
override fun onBackPressed() {
super.onBackPressed()
finish()

View File

@ -30,6 +30,9 @@ import ch.protonmail.android.worker.RemoveMembersFromContactGroupWorker
import com.birbit.android.jobqueue.JobManager
import io.reactivex.Completable
import io.reactivex.Observable
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext
import me.proton.core.util.kotlin.DispatcherProvider
import me.proton.core.util.kotlin.toInt
@ -50,11 +53,16 @@ open class ContactDetailsRepository @Inject constructor(
.toObservable()
}
fun getContactEmails(id: String): Observable<List<ContactEmail>> {
@Deprecated("Use non rx version observeContactEmails")
fun getContactEmailsBlocking(id: String): Observable<List<ContactEmail>> {
return contactDao.findContactEmailsByContactIdObservable(id)
.toObservable()
}
suspend fun getContactEmails(contactId: String): List<ContactEmail> =
contactDao.findContactEmailsByContactId(contactId)
@Deprecated("Use non rx version observeContactGroups")
fun getContactGroups(): Observable<List<ContactLabel>> {
return Observable.concatArrayDelayError(
getContactGroupsFromDB(),
@ -63,6 +71,16 @@ open class ContactDetailsRepository @Inject constructor(
)
}
fun observeContactGroups(): Flow<List<ContactLabel>> = observeContactGroupsFromDb()
.onEach { fetchContactGroupsFromApi() }
private suspend fun fetchContactGroupsFromApi() {
api.fetchContactGroupsList().also {
contactDao.clearContactGroupsLabelsTable()
contactDao.saveContactGroupsList(it)
}
}
private fun getContactGroupsFromApi(): Observable<List<ContactLabel>> {
return api.fetchContactGroupsAsObservable().doOnNext {
contactDao.clearContactGroupsLabelsTableBlocking()
@ -84,6 +102,12 @@ open class ContactDetailsRepository @Inject constructor(
.toObservable()
}
private fun observeContactGroupsFromDb() = contactDao.observeContactGroups()
.map { labels ->
labels.map { label -> label.contactEmailsCount = contactDao.countContactEmailsByLabelId(label.ID) }
labels
}
fun editContactGroup(contactLabel: ContactLabel): Completable {
val contactLabelConverterFactory = ContactLabelFactory()
val labelBody = contactLabelConverterFactory.createServerObjectFromDBObject(contactLabel)
@ -107,7 +131,9 @@ open class ContactDetailsRepository @Inject constructor(
}
}
fun setMembersForContactGroup(contactGroupId: String, contactGroupName: String, membersList: List<String>): Completable {
fun setMembersForContactGroup(
contactGroupId: String, contactGroupName: String, membersList: List<String>
): Completable {
val labelContactsBody = LabelContactsBody(contactGroupId, membersList)
return api.labelContacts(labelContactsBody)
.doOnComplete {
@ -119,7 +145,9 @@ open class ContactDetailsRepository @Inject constructor(
}
.doOnError { throwable ->
if (throwable is IOException) {
jobManager.addJobInBackground(SetMembersForContactGroupJob(contactGroupId, contactGroupName, membersList))
jobManager.addJobInBackground(
SetMembersForContactGroupJob(contactGroupId, contactGroupName, membersList)
)
}
}
}
@ -163,7 +191,7 @@ open class ContactDetailsRepository @Inject constructor(
suspend fun updateAllContactEmails(contactId: String?, contactServerEmails: List<ContactEmail>) {
withContext(dispatcherProvider.Io) {
contactId?.let {
val localContactEmails = contactDao.findContactEmailsByContactId(it)
val localContactEmails = contactDao.findContactEmailsByContactIdBlocking(it)
contactDao.deleteAllContactsEmails(localContactEmails)
contactDao.saveAllContactsEmails(contactServerEmails)
}
@ -180,4 +208,7 @@ open class ContactDetailsRepository @Inject constructor(
return@withContext contactDao.saveContactData(contactData)
}
suspend fun getFullContactDetails(contactId: String): FullContactDetails? =
contactDao.findFullContactDetailsById(contactId)
}

View File

@ -19,18 +19,25 @@
package ch.protonmail.android.contacts.details
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.work.WorkManager
import ch.protonmail.android.usecase.fetch.FetchContactDetails
import ch.protonmail.android.worker.DeleteContactWorker
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import me.proton.core.user.domain.UserManager
import me.proton.core.util.kotlin.DispatcherProvider
import javax.inject.Inject
@HiltViewModel
class ContactDetailsViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val fetchContactDetails: FetchContactDetails
private val dispatchers: DispatcherProvider,
private val contactDetailsRepository: ContactDetailsRepository,
private val fetchContactDetails: FetchContactDetails,
private val userManager: UserManager,
private val workManager: WorkManager
) : ViewModel() {
fun getContactDetails(contactId: String) {
@ -38,4 +45,43 @@ class ContactDetailsViewModel @Inject constructor(
fetchContactDetails(contactId)
}
}
fun deleteContact(contactId: String) = DeleteContactWorker.Enqueuer(workManager).enqueue(listOf(contactId))
fun observeContactGroups() = contactDetailsRepository.observeContactGroups()
.flowOn(dispatchers.Io)
suspend fun observeContactEmails(contactId: String) = contactDetailsRepository.getContactEmails(contactId)
// private fun decryptAndFillVCard(contact: FullContactDetails?) {
// var hasDecryptionError = false
// val crypto: Crypto<*> = forUser(userManager, userManager.requireCurrentUserId())
// var encData: List<ContactEncryptedData>? = ArrayList()
// if (contact != null && contact.encryptedData != null) {
// encData = contact.encryptedData
// } else {
// hasDecryptionError = true
// }
// for (contactEncryptedData in encData!!) {
// if (contactEncryptedData.type == 0) {
// mVCardType0 = contactEncryptedData.data
// } else if (contactEncryptedData.type == 2) {
// mVCardType2 = contactEncryptedData.data
// mVCardType2Signature = contactEncryptedData.signature
// } else if (contactEncryptedData.type == 3) {
// try {
// val tct = CipherText(contactEncryptedData.data)
// val tdr = crypto.decrypt(tct)
// mVCardType3 = tdr.decryptedData
// } catch (e: Exception) {
// hasDecryptionError = true
// Logger.doLogException(e)
// }
// mVCardType3Signature = contactEncryptedData.signature
// }
// }
// fillVCard(hasDecryptionError)
// }
}

View File

@ -213,7 +213,7 @@ open class ContactDetailsViewModelOld @Inject constructor(
)
}
},
contactDetailsRepository.getContactEmails(contactId).subscribeOn(ThreadSchedulers.io())
contactDetailsRepository.getContactEmailsBlocking(contactId).subscribeOn(ThreadSchedulers.io())
.doOnError {
if (allContactEmails.isEmpty()) {
_setupError.postValue(

View File

@ -101,7 +101,10 @@ interface ContactDao {
fun findContactEmailByEmailLiveData(email: String): LiveData<ContactEmail>
@Query("SELECT * FROM $TABLE_CONTACT_EMAILS WHERE $COLUMN_CONTACT_EMAILS_CONTACT_ID = :contactId")
fun findContactEmailsByContactId(contactId: String): List<ContactEmail>
fun findContactEmailsByContactIdBlocking(contactId: String): List<ContactEmail>
@Query("SELECT * FROM $TABLE_CONTACT_EMAILS WHERE $COLUMN_CONTACT_EMAILS_CONTACT_ID = :contactId")
suspend fun findContactEmailsByContactId(contactId: String): List<ContactEmail>
@Query("SELECT * FROM $TABLE_CONTACT_EMAILS WHERE $COLUMN_CONTACT_EMAILS_CONTACT_ID = :contactId")
fun findContactEmailsByContactIdObservable(contactId: String): Flowable<List<ContactEmail>>
@ -319,6 +322,9 @@ interface ContactDao {
@Query("SELECT * FROM $TABLE_CONTACT_LABEL ORDER BY $COLUMN_LABEL_NAME")
fun findContactGroupsObservable(): Flowable<List<ContactLabel>>
@Query("SELECT * FROM $TABLE_CONTACT_LABEL ORDER BY $COLUMN_LABEL_NAME")
fun observeContactGroups(): Flow<List<ContactLabel>>
@Query(
"""
SELECT *
@ -397,7 +403,10 @@ interface ContactDao {
fun insertFullContactDetails(fullContactDetails: FullContactDetails)
@Query("SELECT * FROM $TABLE_FULL_CONTACT_DETAILS WHERE $COLUMN_CONTACT_ID = :id")
fun findFullContactDetailsById(id: String): FullContactDetails?
fun findFullContactDetailsByIdBlocking(id: String): FullContactDetails?
@Query("SELECT * FROM $TABLE_FULL_CONTACT_DETAILS WHERE $COLUMN_CONTACT_ID = :id")
suspend fun findFullContactDetailsById(id: String): FullContactDetails?
@Query("DELETE FROM $TABLE_FULL_CONTACT_DETAILS")
fun clearFullContactDetailsCache()

View File

@ -311,7 +311,7 @@ class ConvertLocalContactsJob(
val remoteContactId = response.contactId
val previousContactData = contactDao.findContactDataByDbId(contactDataDbId)
if (remoteContactId != "") {
val contactEmails = contactDao.findContactEmailsByContactId(
val contactEmails = contactDao.findContactEmailsByContactIdBlocking(
previousContactData!!.contactId!!
)
previousContactData.contactId = remoteContactId

View File

@ -68,7 +68,7 @@ public class ResignContactJob extends ProtonMailEndlessJob {
AppUtil.postEventOnUi(new ResignContactEvent(mSendPreference, ContactEvent.ERROR, mDestination));
return;
}
FullContactDetails fullContactDetails = contactDao.findFullContactDetailsById(contactId);
FullContactDetails fullContactDetails = contactDao.findFullContactDetailsByIdBlocking(contactId);
if (user == null || fullContactDetails == null) {
AppUtil.postEventOnUi(new ResignContactEvent(mSendPreference, ContactEvent.ERROR, mDestination));
@ -106,7 +106,7 @@ public class ResignContactJob extends ProtonMailEndlessJob {
return;
}
FullContactDetails fullContactDetails = contactDao.findFullContactDetailsById(
FullContactDetails fullContactDetails = contactDao.findFullContactDetailsByIdBlocking(
contactId);
UserCrypto crypto = Crypto.forUser(getUserManager(), getUserManager().requireCurrentUserId());
ContactEncryptedData signedCard = getCardByType(fullContactDetails.getEncryptedData(), ContactEncryption.SIGNED);

View File

@ -150,7 +150,7 @@ public class UpdateContactJob extends ProtonMailEndlessJob {
mContactDao.saveContactData(contactData);
}
List<ContactEmail> emails = mContactDao.findContactEmailsByContactId(mContactId);
List<ContactEmail> emails = mContactDao.findContactEmailsByContactIdBlocking(mContactId);
mContactDao.deleteAllContactsEmails(emails);
for (ContactEmail email : contactEmails) {
@ -174,7 +174,7 @@ public class UpdateContactJob extends ProtonMailEndlessJob {
}
FullContactDetails contact = null;
try {
contact = mContactDao.findFullContactDetailsById(mContactId);
contact = mContactDao.findFullContactDetailsByIdBlocking(mContactId);
} catch (SQLiteBlobTooBigException tooBigException) {
Timber.i(tooBigException,"Data too big to be fetched");
}

View File

@ -99,7 +99,7 @@ class DeleteContactWorker @AssistedInject constructor(
contactData?.let { contact ->
contactDatabase.runInTransaction {
contact.contactId?.let {
val contactEmails = contactDao.findContactEmailsByContactId(it)
val contactEmails = contactDao.findContactEmailsByContactIdBlocking(it)
contactDao.deleteAllContactsEmails(contactEmails)
}
contactDao.deleteContactData(contact)

View File

@ -98,11 +98,11 @@ class ContactDetailsRepositoryTest {
ContactEmail("ID3", "martin@proton.com", "Martin"),
ContactEmail("ID4", "kent@proton.com", "kent")
)
every { contactDao.findContactEmailsByContactId(contactId) } returns localContactEmails
every { contactDao.findContactEmailsByContactIdBlocking(contactId) } returns localContactEmails
repository.updateAllContactEmails(contactId, serverEmails)
verify { contactDao.findContactEmailsByContactId(contactId) }
verify { contactDao.findContactEmailsByContactIdBlocking(contactId) }
verify { contactDao.deleteAllContactsEmails(localContactEmails) }
coVerify { contactDao.saveAllContactsEmails(serverEmails) }
}

View File

@ -80,7 +80,7 @@ class FetchContactDetailsTest : ArchTest, CoroutinesTest {
val fullContactsResponse = mockk<FullContactDetailsResponse> {
every { contact } returns fullContactsFromNet
}
every { contactDao.findFullContactDetailsById(contactId) } returns fullContactsFromDb
every { contactDao.findFullContactDetailsByIdBlocking(contactId) } returns fullContactsFromDb
every { contactDao.insertFullContactDetails(any()) } returns Unit
coEvery { api.fetchContactDetails(contactId) } returns fullContactsResponse
val expectedDb = FetchContactDetailsResult.Data(
@ -113,7 +113,7 @@ class FetchContactDetailsTest : ArchTest, CoroutinesTest {
val fullContactsFromDb = mockk<FullContactDetails> {
every { encryptedData } returns mutableListOf(contactEncryptedData)
}
every { contactDao.findFullContactDetailsById(contactId) } returns fullContactsFromDb
every { contactDao.findFullContactDetailsByIdBlocking(contactId) } returns fullContactsFromDb
val ioException = IOException("Cannot load contacts")
coEvery { api.fetchContactDetails(contactId) } throws ioException
val expected = FetchContactDetailsResult.Data(
@ -152,7 +152,7 @@ class FetchContactDetailsTest : ArchTest, CoroutinesTest {
val fullContactsResponse = mockk<FullContactDetailsResponse> {
every { contact } returns fullContactsFromNet
}
every { contactDao.findFullContactDetailsById(contactId) } returns fullContactsFromDb
every { contactDao.findFullContactDetailsByIdBlocking(contactId) } returns fullContactsFromDb
every { contactDao.insertFullContactDetails(any()) } returns Unit
coEvery { api.fetchContactDetails(contactId) } returns fullContactsResponse
val expected = FetchContactDetailsResult.Data(
@ -173,7 +173,7 @@ class FetchContactDetailsTest : ArchTest, CoroutinesTest {
// given
val contactId = "testContactId"
val exception = Exception("An error!")
every { contactDao.findFullContactDetailsById(contactId) } returns null
every { contactDao.findFullContactDetailsByIdBlocking(contactId) } returns null
coEvery { api.fetchContactDetails(contactId) } throws exception
val expected = FetchContactDetailsResult.Error(exception)

View File

@ -104,7 +104,7 @@ class DeleteContactWorkerTest {
val expected = ListenableWorker.Result.success()
every { contactDao.findContactDataById(contactId) } returns contactData
every { contactDao.findContactEmailsByContactId(contactId) } returns listOf(contactEmail)
every { contactDao.findContactEmailsByContactIdBlocking(contactId) } returns listOf(contactEmail)
every { contactDao.deleteAllContactsEmails(any()) } returns mockk()
every { contactDao.deleteContactData(any()) } returns mockk()
every { parameters.inputData } returns
@ -135,7 +135,7 @@ class DeleteContactWorkerTest {
)
every { contactDao.findContactDataById(contactId) } returns contactData
every { contactDao.findContactEmailsByContactId(contactId) } returns listOf(contactEmail)
every { contactDao.findContactEmailsByContactIdBlocking(contactId) } returns listOf(contactEmail)
every { contactDao.deleteAllContactsEmails(any()) } returns mockk()
every { contactDao.deleteContactData(any()) } returns mockk()
every { parameters.inputData } returns