srctree

Roee Hershberg parent 5f5b5b9e f99cc6b8
Refactor avatar making with AvatarFactory

Support for displaying the user avatar will be implemented and easilyintegrated with this class.

inlinesplit
filename was Deleted added: 101, removed: 57, total 44
@@ -0,0 +1,83 @@
// SPDX-FileCopyrightText: 2019-2021 aTox contributors
//
// SPDX-License-Identifier: GPL-3.0-only
 
package ltd.evilcorp.atox.ui
 
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.Typeface
import android.net.Uri
import android.widget.ImageView
import kotlin.math.abs
import ltd.evilcorp.atox.R
import ltd.evilcorp.core.vo.Contact
 
sealed interface Size {
fun asPx(res: Resources): Px
}
 
data class Px(val px: Int) : Size {
override fun asPx(res: Resources) = this
}
 
data class Dp(val dp: Int) : Size {
override fun asPx(res: Resources): Px = Px(dpToPx(dp, res))
}
 
// Class for creating an avatar for contact and assigning it into an ImageView
internal class AvatarFactory(contact: Contact) {
 
companion object {
const val DEFAULT_AVATAR_SIZE_DP = 50
}
 
private val name: String = contact.name
private val publicKey: String = contact.publicKey
private val avatarUri: String = contact.avatarUri
 
private fun getInitials(): String {
val segments = name.split(" ")
if (segments.size == 1) return segments.first().take(1)
return segments.first().take(1) + segments[1][0]
}
 
/*
* Method will assign an avatar to an image view. If avatar image has been set to the contact
* then it will be set to the image view, otherwise a new avatar image will be created based
* on the initials of the name and the public key for the background color.
*/
fun assignInto(imageView: ImageView, size: Size = Dp(DEFAULT_AVATAR_SIZE_DP)) =
if (avatarUri.isNotEmpty()) {
imageView.setImageURI(Uri.parse(avatarUri))
} else {
val side = size.asPx(imageView.resources).px
val textScale = side.toFloat() / Dp(DEFAULT_AVATAR_SIZE_DP).asPx(imageView.resources).px
 
val bitmap = Bitmap.createBitmap(side, side, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
val rect = RectF(0f, 0f, bitmap.width.toFloat(), bitmap.height.toFloat())
val colors = imageView.resources.getIntArray(R.array.contactBackgrounds)
val backgroundPaint = Paint().apply { color = colors[abs(publicKey.hashCode()).rem(colors.size)] }
 
val textPaint = Paint().apply {
color = Color.WHITE
textSize = imageView.resources.getDimension(R.dimen.contact_avatar_placeholder_text) * textScale
textAlign = Paint.Align.CENTER
isAntiAlias = true
typeface = Typeface.create("sans-serif-light", Typeface.NORMAL)
}
 
val textBounds = Rect()
val initials = getInitials()
textPaint.getTextBounds(initials, 0, initials.length, textBounds)
canvas.drawRoundRect(rect, rect.bottom, rect.right, backgroundPaint)
canvas.drawText(initials, rect.centerX(), rect.centerY() - textBounds.exactCenterY(), textPaint)
imageView.setImageBitmap(bitmap)
}
}
 
atox/src/main/kotlin/ui/Util.kt added: 101, removed: 57, total 44
@@ -5,17 +5,8 @@
package ltd.evilcorp.atox.ui
 
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.Typeface
import android.net.Uri
import android.widget.ImageView
import android.util.TypedValue
import androidx.core.content.res.ResourcesCompat
import kotlin.math.abs
import ltd.evilcorp.atox.R
import ltd.evilcorp.core.vo.ConnectionStatus
import ltd.evilcorp.core.vo.Contact
@@ -34,36 +25,5 @@ internal fun colorByStatus(resources: Resources, contact: Contact): Int {
}
}
 
private fun getInitials(contact: Contact): String {
val segments = contact.name.split(" ")
if (segments.size == 1) return segments.first().take(1)
return segments.first().take(1) + segments[1][0]
}
 
private const val DEFAULT_AVATAR_SIZE_DP = 50
internal fun setAvatarFromContact(imageView: ImageView, contact: Contact, sizeDp: Int = DEFAULT_AVATAR_SIZE_DP) =
if (contact.avatarUri.isNotEmpty()) {
imageView.setImageURI(Uri.parse(contact.avatarUri))
} else {
val side = (sizeDp * imageView.resources.displayMetrics.density).toInt()
val bitmap = Bitmap.createBitmap(side, side, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
val rect = RectF(0f, 0f, bitmap.width.toFloat(), bitmap.height.toFloat())
val colors = imageView.resources.getIntArray(R.array.contactBackgrounds)
val backgroundPaint = Paint().apply { color = colors[abs(contact.publicKey.hashCode()).rem(colors.size)] }
 
val textScale = sizeDp.toFloat() / DEFAULT_AVATAR_SIZE_DP
val textPaint = Paint().apply {
color = Color.WHITE
textSize = imageView.resources.getDimension(R.dimen.contact_avatar_placeholder_text) * textScale
textAlign = Paint.Align.CENTER
isAntiAlias = true
typeface = Typeface.create("sans-serif-light", Typeface.NORMAL)
}
val initials = getInitials(contact)
val textBounds = Rect()
textPaint.getTextBounds(initials, 0, initials.length, textBounds)
canvas.drawRoundRect(rect, rect.bottom, rect.right, backgroundPaint)
canvas.drawText(initials, rect.centerX(), rect.centerY() - textBounds.exactCenterY(), textPaint)
imageView.setImageBitmap(bitmap)
}
internal fun dpToPx(dp: Int, res: Resources): Int =
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), res.displayMetrics).toInt()
 
atox/src/main/kotlin/ui/call/CallFragment.kt added: 101, removed: 57, total 44
@@ -19,9 +19,10 @@ import ltd.evilcorp.atox.R
import ltd.evilcorp.atox.databinding.FragmentCallBinding
import ltd.evilcorp.atox.hasPermission
import ltd.evilcorp.atox.requireStringArg
import ltd.evilcorp.atox.ui.AvatarFactory
import ltd.evilcorp.atox.ui.BaseFragment
import ltd.evilcorp.atox.ui.Dp
import ltd.evilcorp.atox.ui.chat.CONTACT_PUBLIC_KEY
import ltd.evilcorp.atox.ui.setAvatarFromContact
import ltd.evilcorp.atox.vmFactory
import ltd.evilcorp.domain.feature.CallState
import ltd.evilcorp.domain.tox.PublicKey
@@ -52,7 +53,7 @@ class CallFragment : BaseFragment<FragmentCallBinding>(FragmentCallBinding::infl
 
vm.setActiveContact(PublicKey(requireStringArg(CONTACT_PUBLIC_KEY)))
vm.contact.observe(viewLifecycleOwner) {
setAvatarFromContact(callBackground, it, CALL_BACKGROUND_SIZE_DP)
AvatarFactory(it).assignInto(callBackground, Dp(CALL_BACKGROUND_SIZE_DP))
}
 
endCall.setOnClickListener {
 
atox/src/main/kotlin/ui/chat/ChatFragment.kt added: 101, removed: 57, total 44
@@ -42,9 +42,9 @@ import ltd.evilcorp.atox.R
import ltd.evilcorp.atox.databinding.FragmentChatBinding
import ltd.evilcorp.atox.requireStringArg
import ltd.evilcorp.atox.truncated
import ltd.evilcorp.atox.ui.AvatarFactory
import ltd.evilcorp.atox.ui.BaseFragment
import ltd.evilcorp.atox.ui.colorByStatus
import ltd.evilcorp.atox.ui.setAvatarFromContact
import ltd.evilcorp.atox.vmFactory
import ltd.evilcorp.core.vo.ConnectionStatus
import ltd.evilcorp.core.vo.FileTransfer
@@ -189,7 +189,7 @@ class ChatFragment : BaseFragment<FragmentChatBinding>(FragmentChatBinding::infl
}.lowercase(Locale.getDefault())
 
profileLayout.statusIndicator.setColorFilter(colorByStatus(resources, it))
setAvatarFromContact(profileLayout.profileImage, it)
AvatarFactory(it).assignInto(profileLayout.profileImage)
 
if (it.draftMessage.isNotEmpty() && outgoingMessage.text.isEmpty()) {
outgoingMessage.setText(it.draftMessage)
 
atox/src/main/kotlin/ui/contact_profile/ContactProfileFragment.kt added: 101, removed: 57, total 44
@@ -13,10 +13,10 @@ import androidx.fragment.app.viewModels
import ltd.evilcorp.atox.R
import ltd.evilcorp.atox.databinding.FragmentContactProfileBinding
import ltd.evilcorp.atox.requireStringArg
import ltd.evilcorp.atox.ui.AvatarFactory
import ltd.evilcorp.atox.ui.BaseFragment
import ltd.evilcorp.atox.ui.chat.CONTACT_PUBLIC_KEY
import ltd.evilcorp.atox.ui.colorByStatus
import ltd.evilcorp.atox.ui.setAvatarFromContact
import ltd.evilcorp.atox.vmFactory
import ltd.evilcorp.domain.tox.PublicKey
 
@@ -41,7 +41,7 @@ class ContactProfileFragment : BaseFragment<FragmentContactProfileBinding>(Fragm
contact.name = contact.name.ifEmpty { getString(R.string.contact_default_name) }
 
headerMainText.text = contact.name
setAvatarFromContact(profileLayout.profileImage, contact)
AvatarFactory(contact).assignInto(profileLayout.profileImage)
profileLayout.statusIndicator.setColorFilter(colorByStatus(resources, contact))
 
contactPublicKey.text = contact.publicKey
 
atox/src/main/kotlin/ui/contactlist/ContactAdapter.kt added: 101, removed: 57, total 44
@@ -16,8 +16,8 @@ import java.text.DateFormat
import ltd.evilcorp.atox.R
import ltd.evilcorp.atox.databinding.ContactListViewItemBinding
import ltd.evilcorp.atox.databinding.FriendRequestItemBinding
import ltd.evilcorp.atox.ui.AvatarFactory
import ltd.evilcorp.atox.ui.colorByStatus
import ltd.evilcorp.atox.ui.setAvatarFromContact
import ltd.evilcorp.core.vo.Contact
import ltd.evilcorp.core.vo.FriendRequest
 
@@ -112,7 +112,7 @@ class ContactAdapter(
}
}
vh.status.setColorFilter(colorByStatus(resources, this))
setAvatarFromContact(vh.image, this)
AvatarFactory(this).assignInto(vh.image)
vh.unreadIndicator.visibility = if (hasUnreadMessages) {
View.VISIBLE
} else {