New way of faetching of contact thumbnail image added.

MAILAND-1875
This commit is contained in:
Tomasz Giszczak 2021-06-01 10:33:53 +02:00
parent cb243c87dc
commit bb6d4af6c4
19 changed files with 92 additions and 21 deletions

View File

@ -8,8 +8,8 @@
<inspection_tool class="FunctionName" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="namePattern" value="[a-zA-Z][A-Za-z\d]*" />
</inspection_tool>
<inspection_tool class="IllegalIdentifier" enabled="false" level="ERROR" enabled_by_default="false">
<scope name="Gradle files" level="None" enabled="false" />
<inspection_tool class="IllegalIdentifier" enabled="true" level="ERROR" enabled_by_default="true">
<scope name="Gradle files" level="None" enabled="true" />
</inspection_tool>
<inspection_tool class="IncompatibleAPI" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="ObjectPropertyName" enabled="true" level="WEAK WARNING" enabled_by_default="true">

View File

@ -341,7 +341,8 @@ dependencies {
`viewStateStore-paging`,
`remark`,
`okio`,
`store`
`store`,
`coil`
)
debugImplementation(

View File

@ -167,7 +167,7 @@ internal class ContactDaoTest {
saveAllContactsData(contactData)
saveAllContactsEmails(contactEmails)
}
fullContactDetails.forEach(this::insertFullContactDetails)
fullContactDetails.forEach(this::insertFullContactDetailsBlocking)
}
private fun assertDatabaseState(
@ -528,7 +528,7 @@ internal class ContactDaoTest {
)
)
val expected = fullContactDetails + inserted
database.insertFullContactDetails(inserted)
database.insertFullContactDetailsBlocking(inserted)
assertDatabaseState(expectedFullContactDetails = expected)
}

View File

@ -164,7 +164,7 @@ public class SendPreferencesFactory {
continue;
}
FullContactDetails contact = contactDetails.get(contactID).getContact();
contactDao.insertFullContactDetails(contact);
contactDao.insertFullContactDetailsBlocking(contact);
result.put(email, contact);
}
return result;

View File

@ -453,7 +453,7 @@ class EventHandler @AssistedInject constructor(
val contactName = contact.name
val contactData = ContactData(contactId, contactName!!)
contactDao.saveContactData(contactData)
contactDao.insertFullContactDetails(contact)
contactDao.insertFullContactDetailsBlocking(contact)
}
EventType.UPDATE -> {
@ -474,7 +474,7 @@ class EventHandler @AssistedInject constructor(
if (localFullContact != null) {
contactDao.deleteFullContactsDetails(localFullContact)
}
contactDao.insertFullContactDetails(fullContact)
contactDao.insertFullContactDetailsBlocking(fullContact)
}
EventType.DELETE -> {

View File

@ -219,7 +219,7 @@ open class ContactDetailsRepository @Inject constructor(
}
}
fun insertFullContactDetails(fullContactDetails: FullContactDetails) =
suspend fun insertFullContactDetails(fullContactDetails: FullContactDetails) =
contactDao.insertFullContactDetails(fullContactDetails)
}

View File

@ -21,16 +21,20 @@ package ch.protonmail.android.contacts.details.presentation
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Bundle
import android.provider.ContactsContract
import android.view.MenuItem
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView
import androidx.lifecycle.lifecycleScope
@ -43,15 +47,19 @@ import ch.protonmail.android.contacts.details.presentation.model.ContactDetailsV
import ch.protonmail.android.databinding.ActivityContactDetailsBinding
import ch.protonmail.android.utils.extensions.showToast
import ch.protonmail.android.views.ListItemThumbnail
import coil.request.ImageRequest
import coil.transform.CircleCropTransformation
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import me.proton.core.util.kotlin.EMPTY_STRING
import timber.log.Timber
@AndroidEntryPoint
class ContactDetailsActivity : AppCompatActivity() {
private lateinit var thumbnailImage: ImageView
private lateinit var detailsAdapter: ContactDetailsAdapter
private lateinit var thumbnail: ListItemThumbnail
private lateinit var contactName: TextView
@ -77,13 +85,14 @@ class ContactDetailsActivity : AppCompatActivity() {
detailsContainer = binding.scrollViewContactDetails
contactName = binding.textViewContactDetailsContactName
thumbnail = binding.thumbnailContactDetails
thumbnailImage = binding.imageViewContactDetailsThumbnail
detailsAdapter = ContactDetailsAdapter(
::onWriteToContact,
::onCallContact,
::onAddressClicked,
::onUrlClicked
)
)
binding.recyclerViewContactsDetails.apply {
layoutManager = LinearLayoutManager(context)
adapter = detailsAdapter
@ -174,6 +183,50 @@ class ContactDetailsActivity : AppCompatActivity() {
) {
contactName.text = title
thumbnail.bind(isSelectedActive = false, isMultiselectActive = false, initials = initials)
if (!photoBytes.isNullOrEmpty()) {
setThumbnailImage(photoBytes)
} else if (photoUrl != null) {
loadThumbnailImage(photoUrl)
}
}
private fun setThumbnailImage(photoBytes: List<Byte>) {
val byteArray = photoBytes.toByteArray()
val imageBitmap: Bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
val roundedImage = RoundedBitmapDrawableFactory.create(resources, imageBitmap).apply {
setAntiAlias(true)
cornerRadius = resources.getDimensionPixelSize(R.dimen.avatar_size) / 2f
}
thumbnailImage.apply {
setImageDrawable(roundedImage)
isVisible = true
}
thumbnail.isVisible = false
}
private fun loadThumbnailImage(photoUrl: String?) {
Timber.v("Loading photo url: $photoUrl")
val targetSize = resources.getDimensionPixelSize(R.dimen.avatar_size)
ImageRequest.Builder(this)
.data(photoUrl)
.size(targetSize, targetSize)
.transformations(CircleCropTransformation())
.target(
onSuccess = { drawable ->
Timber.d("Thumbnail loading finished")
thumbnailImage.apply {
setImageDrawable(drawable)
isVisible = true
}
thumbnail.isVisible = false
},
onError = {
Timber.i("Thumbnail loading error")
thumbnailImage.isVisible = false
thumbnail.isVisible = true
}
)
}
private fun setContactDataForActionButtons(item: ContactDetailsUiItem) {

View File

@ -59,7 +59,9 @@ class ContactDetailsViewModel @Inject constructor(
Timber.v("getContactDetails for $contactId")
viewModelScope.launch {
fetchContactDetails(contactId)
.catch { ContactDetailsViewState.Error(it) }
.catch {
mutableContactsResultFlow.value = ContactDetailsViewState.Error(it)
}
.collect { fetchResult ->
mutableContactsResultFlow.value = mapper.mapToContactViewData(fetchResult)
}

View File

@ -400,7 +400,10 @@ interface ContactDao {
//region Full contact details
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertFullContactDetails(fullContactDetails: FullContactDetails)
fun insertFullContactDetailsBlocking(fullContactDetails: FullContactDetails)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertFullContactDetails(fullContactDetails: FullContactDetails)
@Query("SELECT * FROM $TABLE_FULL_CONTACT_DETAILS WHERE $COLUMN_CONTACT_ID = :id")
fun findFullContactDetailsByIdBlocking(id: String): FullContactDetails?

View File

@ -71,7 +71,6 @@ class FullContactDetailsResponse : ResponseBody() {
@SerializedName(FIELD_CONTACT)
private lateinit var serverContact: ServerFullContactDetails
val contact: FullContactDetails by lazy {
val factory = FullContactDetailsFactory()
factory.createFullContactDetails(serverContact)

View File

@ -122,7 +122,7 @@ public class ResignContactJob extends ProtonMailEndlessJob {
AppUtil.postEventOnUi(new ResignContactEvent(mSendPreference, ContactEvent.ERROR, mDestination));
return;
}
contactDao.insertFullContactDetails(fullContactDetails);
contactDao.insertFullContactDetailsBlocking(fullContactDetails);
ContactEncryptedData encCard = getCardByType(fullContactDetails.getEncryptedData(), ContactEncryption.ENCRYPTED_AND_SIGNED);
CreateContactV2BodyItem body;
@ -145,7 +145,7 @@ public class ResignContactJob extends ProtonMailEndlessJob {
AppUtil.postEventOnUi(new ResignContactEvent(mSendPreference, ContactEvent.DUPLICATE_EMAIL, mDestination));
} else {
//TODO this insert is probably not needed as it is already saved some lines above
contactDao.insertFullContactDetails(fullContactDetails);
contactDao.insertFullContactDetailsBlocking(fullContactDetails);
AppUtil.postEventOnUi(new ResignContactEvent(mSendPreference, ContactEvent.SUCCESS, mDestination));
}
}

View File

@ -200,7 +200,7 @@ public class UpdateContactJob extends ProtonMailEndlessJob {
contact.addEncryptedData(contactEncryptedData);
contact.setName(contactName);
contact.setEmails(contactEmails);
mContactDao.insertFullContactDetails(contact);
mContactDao.insertFullContactDetailsBlocking(contact);
if (updateJoins) {
for (Map.Entry<ContactLabel, List<String>> entry : mapContactGroupContactEmails.entrySet()) {
updateJoins(entry.getKey().getID(), entry.getKey().getName(), entry.getValue());

View File

@ -82,7 +82,6 @@ class FetchVerificationKeys @Inject constructor(
emptyList()
}
)
} ?: emptyList()
}

View File

@ -35,7 +35,7 @@ import androidx.core.content.withStyledAttributes
import ch.protonmail.android.R
import timber.log.Timber
private const val TEXT_SIZE_PROPORTION = 2.5f
private const val TEXT_SIZE_PROPORTION = 3f
private const val DEFAULT_CIRCLE_SIZE = 100
/**

View File

@ -43,14 +43,24 @@ along with ProtonMail. If not, see https://www.gnu.org/licenses/.
android:layout_margin="@dimen/padding_l"
app:circleSize="@dimen/avatar_size" />
<ImageView
android:id="@+id/image_view_contact_details_thumbnail"
android:layout_width="@dimen/avatar_size"
android:layout_height="@dimen/avatar_size"
android:layout_gravity="center"
android:layout_margin="@dimen/padding_l"
android:contentDescription="@string/contact_details"
android:scaleType="fitXY"
android:visibility="gone" />
<TextView
android:id="@+id/text_view_contact_details_contact_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/Proton.Text.Headline"
android:layout_gravity="center|bottom"
android:layout_marginBottom="@dimen/padding_l"
android:gravity="center"
android:textAppearance="@style/Proton.Text.Headline"
tools:text="@tools:sample/full_names" />
</com.google.android.material.appbar.AppBarLayout>

View File

@ -71,7 +71,7 @@ class FetchVerificationKeysTest : CoroutinesTest {
every { contactId } returns testContactId
}
every { contactDao.findContactEmailByEmail(testEmail) } returns testContactEmail
every { contactDao.insertFullContactDetails(any()) } returns Unit
every { contactDao.insertFullContactDetailsBlocking(any()) } returns Unit
val fullContactDetailsResponse = mockk<FullContactDetailsResponse> {
every { contact } returns mockk {
every { contactId } returns "contactId"
@ -123,7 +123,7 @@ class FetchVerificationKeysTest : CoroutinesTest {
every { contactId } returns testContactId
}
every { contactDao.findContactEmailByEmail(testEmail) } returns testContactEmail
every { contactDao.insertFullContactDetails(any()) } returns Unit
every { contactDao.insertFullContactDetailsBlocking(any()) } returns Unit
val fullContactDetailsResponse = mockk<FullContactDetailsResponse> {
every { contact } returns mockk {
every { contactId } returns "contactId"

View File

@ -122,6 +122,8 @@ val DependencyHandler.`fasterxml-jackson-anno` get() = dependency("com.fast
val DependencyHandler.`fasterxml-jackson-databind` get() = dependency("com.fasterxml.jackson.core", module = "jackson-databind") version `jackson version`
val DependencyHandler.`remark` get() = dependency("com.overzealous", module = "remark") version `remark version`
val DependencyHandler.`store` get() = dependency("com.dropbox.mobile.store", module = "store4") version `store version`
val DependencyHandler.`coil` get() = dependency("io.coil-kt", module="coil") version `coil version`
val DependencyHandler.`coil-base` get() = dependency("io.coil-kt", module="coil-base") version `coil version`
// endregion
// endregion

View File

@ -134,3 +134,4 @@ const val `timber version` = "4.7.1" // Released:
const val `trustKit version` = "1.1.2" // Released: Jun 09, 2019
const val `remark version` = "1.1.0" // Released: Dec 08, 2016
const val `store version` = "4.0.0" // Released: Nov 30, 2020
const val `coil version` = "1.2.1" // Released: Apr 28, 2021

View File

@ -28,6 +28,7 @@ import java.io.InputStream
*
* @author Davide Farella
*/
@Deprecated("Replaced with coil library")
interface DownloadFile {
suspend operator fun invoke(url: String): InputStream