Add support for updated server badge image url formats.
This commit is contained in:
parent
6e00920c95
commit
8d0acb277c
32 changed files with 602 additions and 214 deletions
|
@ -9,6 +9,7 @@ apply from: 'translations.gradle'
|
|||
apply from: 'witness-verifications.gradle'
|
||||
apply plugin: 'org.jetbrains.kotlin.android'
|
||||
apply plugin: 'app.cash.exhaustive'
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
|
@ -177,6 +178,7 @@ android {
|
|||
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"unset\""
|
||||
buildConfigField "String", "BUILD_ENVIRONMENT_TYPE", "\"unset\""
|
||||
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"unset\""
|
||||
buildConfigField "String", "BADGE_STATIC_ROOT", "\"https://updates2.signal.org/static/badges/\""
|
||||
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
package org.thoughtcrime.securesms.badges
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.AttributeSet
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.Px
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.core.content.res.use
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.badges.Badges.insetWithOutline
|
||||
import org.thoughtcrime.securesms.badges.glide.BadgeSpriteTransformation
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
import org.thoughtcrime.securesms.util.visible
|
||||
|
||||
|
@ -25,16 +23,11 @@ class BadgeImageView @JvmOverloads constructor(
|
|||
attrs: AttributeSet? = null
|
||||
) : AppCompatImageView(context, attrs) {
|
||||
|
||||
@Px
|
||||
private var outlineWidth: Float = 0f
|
||||
|
||||
@ColorInt
|
||||
private var outlineColor: Int = Color.BLACK
|
||||
private var badgeSize: Int = 0
|
||||
|
||||
init {
|
||||
context.obtainStyledAttributes(attrs, R.styleable.BadgeImageView).use {
|
||||
outlineWidth = it.getDimension(R.styleable.BadgeImageView_badge_outline_width, 0f)
|
||||
outlineColor = it.getColor(R.styleable.BadgeImageView_badge_outline_color, Color.BLACK)
|
||||
badgeSize = it.getInt(R.styleable.BadgeImageView_badge_size, 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,21 +48,17 @@ class BadgeImageView @JvmOverloads constructor(
|
|||
return
|
||||
}
|
||||
|
||||
GlideApp
|
||||
.with(this)
|
||||
.load(badge)
|
||||
.into(this)
|
||||
}
|
||||
|
||||
override fun setImageDrawable(drawable: Drawable?) {
|
||||
if (drawable == null || outlineWidth == 0f) {
|
||||
super.setImageDrawable(drawable)
|
||||
if (badge != null) {
|
||||
GlideApp
|
||||
.with(this)
|
||||
.load(badge)
|
||||
.downsample(DownsampleStrategy.NONE)
|
||||
.transform(BadgeSpriteTransformation(BadgeSpriteTransformation.Size.fromInteger(badgeSize), badge.imageDensity, ThemeUtil.isDarkTheme(context)))
|
||||
.into(this)
|
||||
} else {
|
||||
super.setImageDrawable(
|
||||
drawable.insetWithOutline(
|
||||
outlineWidth, outlineColor
|
||||
)
|
||||
)
|
||||
GlideApp
|
||||
.with(this)
|
||||
.clear(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,46 +11,23 @@ import com.google.android.flexbox.AlignItems
|
|||
import com.google.android.flexbox.FlexDirection
|
||||
import com.google.android.flexbox.FlexboxLayoutManager
|
||||
import com.google.android.flexbox.JustifyContent
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.badges.models.Badge
|
||||
import org.thoughtcrime.securesms.badges.models.BadgeAnimator
|
||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||
import org.thoughtcrime.securesms.util.customizeOnDraw
|
||||
|
||||
object Badges {
|
||||
fun Drawable.insetWithOutline(
|
||||
@Px outlineWidth: Float,
|
||||
@ColorInt outlineColor: Int
|
||||
): Drawable {
|
||||
val clone = mutate().constantState?.newDrawable()?.mutate()
|
||||
clone?.colorFilter = SimpleColorFilter(outlineColor)
|
||||
|
||||
return customizeOnDraw { wrapped, canvas ->
|
||||
clone?.bounds = wrapped.bounds
|
||||
clone?.draw(canvas)
|
||||
|
||||
val scale = 1 - ((outlineWidth * 2) / canvas.width)
|
||||
|
||||
canvas.withScale(x = scale, y = scale, canvas.width / 2f, canvas.height / 2f) {
|
||||
wrapped.draw(canvas)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Drawable.selectable(
|
||||
@Px outlineWidth: Float,
|
||||
@ColorInt outlineColor: Int,
|
||||
@ColorInt gapColor: Int,
|
||||
animator: BadgeAnimator
|
||||
): Drawable {
|
||||
val outline = mutate().constantState?.newDrawable()?.mutate()
|
||||
outline?.colorFilter = SimpleColorFilter(outlineColor)
|
||||
|
||||
val gap = mutate().constantState?.newDrawable()?.mutate()
|
||||
gap?.colorFilter = SimpleColorFilter(gapColor)
|
||||
|
||||
return customizeOnDraw { wrapped, canvas ->
|
||||
outline?.bounds = wrapped.bounds
|
||||
gap?.bounds = wrapped.bounds
|
||||
|
||||
outline?.draw(canvas)
|
||||
|
||||
|
@ -58,11 +35,7 @@ object Badges {
|
|||
val interpolatedScale = scale + (1f - scale) * animator.getFraction()
|
||||
|
||||
canvas.withScale(x = interpolatedScale, y = interpolatedScale, wrapped.bounds.width() / 2f, wrapped.bounds.height() / 2f) {
|
||||
gap?.draw(canvas)
|
||||
|
||||
canvas.withScale(x = interpolatedScale, y = interpolatedScale, wrapped.bounds.width() / 2f, wrapped.bounds.height() / 2f) {
|
||||
wrapped.draw(canvas)
|
||||
}
|
||||
wrapped.draw(canvas)
|
||||
}
|
||||
|
||||
if (animator.shouldInvalidate()) {
|
||||
|
@ -71,12 +44,13 @@ object Badges {
|
|||
}
|
||||
}
|
||||
|
||||
fun DSLConfiguration.displayBadges(badges: List<Badge>, selectedBadge: Badge? = null) {
|
||||
fun DSLConfiguration.displayBadges(context: Context, badges: List<Badge>, selectedBadge: Badge? = null) {
|
||||
badges
|
||||
.map { Badge.Model(it, it == selectedBadge) }
|
||||
.forEach { customPref(it) }
|
||||
|
||||
val empties = (4 - (badges.size % 4)) % 4
|
||||
val perRow = context.resources.getInteger(R.integer.badge_columns)
|
||||
val empties = (perRow - (badges.size % perRow)) % perRow
|
||||
repeat(empties) {
|
||||
customPref(Badge.EmptyModel())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
package org.thoughtcrime.securesms.badges.glide
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Rect
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
|
||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.security.MessageDigest
|
||||
|
||||
/**
|
||||
* Cuts out the badge of the requested size from the sprite sheet.
|
||||
*/
|
||||
class BadgeSpriteTransformation(
|
||||
private val size: Size,
|
||||
private val density: String,
|
||||
private val isDarkTheme: Boolean
|
||||
) : BitmapTransformation() {
|
||||
|
||||
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
|
||||
messageDigest.update("BadgeSpriteTransformation(${size.code},$density,$isDarkTheme)".toByteArray(CHARSET))
|
||||
}
|
||||
|
||||
override fun transform(pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap {
|
||||
val outBitmap = pool.get(outWidth, outHeight, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(outBitmap)
|
||||
val inBounds = getInBounds(density, size, isDarkTheme)
|
||||
val outBounds = Rect(0, 0, outWidth, outHeight)
|
||||
|
||||
canvas.drawBitmap(toTransform, inBounds, outBounds, null)
|
||||
|
||||
return outBitmap
|
||||
}
|
||||
|
||||
enum class Size(val code: String) {
|
||||
SMALL("small"),
|
||||
MEDIUM("medium"),
|
||||
LARGE("large"),
|
||||
XLARGE("xlarge");
|
||||
|
||||
companion object {
|
||||
fun fromInteger(integer: Int): Size {
|
||||
return when (integer) {
|
||||
0 -> SMALL
|
||||
1 -> MEDIUM
|
||||
2 -> LARGE
|
||||
3 -> XLARGE
|
||||
else -> LARGE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PADDING = 1
|
||||
|
||||
@VisibleForTesting
|
||||
fun getInBounds(density: String, size: Size, isDarkTheme: Boolean): Rect {
|
||||
val scaleFactor: Int = when (density) {
|
||||
"ldpi" -> 75
|
||||
"mdpi" -> 100
|
||||
"hdpi" -> 150
|
||||
"xhdpi" -> 200
|
||||
"xxhdpi" -> 300
|
||||
"xxxhdpi" -> 400
|
||||
else -> throw IllegalArgumentException("Unexpected density $density")
|
||||
}
|
||||
|
||||
val smallLength = 8 * scaleFactor / 100
|
||||
val mediumLength = 12 * scaleFactor / 100
|
||||
val largeLength = 18 * scaleFactor / 100
|
||||
val xlargeLength = 80 * scaleFactor / 100
|
||||
|
||||
val sideLength: Int = when (size) {
|
||||
Size.SMALL -> smallLength
|
||||
Size.MEDIUM -> mediumLength
|
||||
Size.LARGE -> largeLength
|
||||
Size.XLARGE -> xlargeLength
|
||||
}
|
||||
|
||||
val lightOffset: Int = when (size) {
|
||||
Size.LARGE -> PADDING
|
||||
Size.MEDIUM -> (largeLength + PADDING * 2) * 2 + PADDING
|
||||
Size.SMALL -> (largeLength + PADDING * 2) * 2 + (mediumLength + PADDING * 2) * 2 + PADDING
|
||||
Size.XLARGE -> (largeLength + PADDING * 2) * 2 + (mediumLength + PADDING * 2) * 2 + (smallLength + PADDING * 2) * 2 + PADDING
|
||||
}
|
||||
|
||||
val darkOffset = if (isDarkTheme) {
|
||||
when (size) {
|
||||
Size.XLARGE -> 0
|
||||
else -> sideLength + PADDING * 2
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
||||
return Rect(
|
||||
lightOffset + darkOffset,
|
||||
PADDING,
|
||||
lightOffset + darkOffset + sideLength,
|
||||
sideLength + PADDING
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,22 +2,26 @@ package org.thoughtcrime.securesms.badges.models
|
|||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.bumptech.glide.load.Key
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
|
||||
import com.bumptech.glide.request.target.CustomViewTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import org.signal.core.util.DimensionUnit
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.badges.Badges.selectable
|
||||
import org.thoughtcrime.securesms.badges.glide.BadgeSpriteTransformation
|
||||
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.util.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.MappingViewHolder
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil
|
||||
import java.security.MessageDigest
|
||||
|
||||
typealias OnBadgeClicked = (Badge, Boolean) -> Unit
|
||||
|
@ -25,42 +29,22 @@ typealias OnBadgeClicked = (Badge, Boolean) -> Unit
|
|||
/**
|
||||
* A Badge that can be collected and displayed by a user.
|
||||
*/
|
||||
@Parcelize
|
||||
data class Badge(
|
||||
val id: String,
|
||||
val category: Category,
|
||||
val imageUrl: Uri,
|
||||
val name: String,
|
||||
val description: String,
|
||||
val imageUrl: Uri,
|
||||
val imageDensity: String,
|
||||
val expirationTimestamp: Long,
|
||||
val visible: Boolean
|
||||
val visible: Boolean,
|
||||
) : Parcelable, Key {
|
||||
|
||||
constructor(parcel: Parcel) : this(
|
||||
requireNotNull(parcel.readString()),
|
||||
Category.fromCode(requireNotNull(parcel.readString())),
|
||||
requireNotNull(parcel.readParcelable(Uri::class.java.classLoader)),
|
||||
requireNotNull(parcel.readString()),
|
||||
requireNotNull(parcel.readString()),
|
||||
parcel.readLong(),
|
||||
parcel.readByte() == 1.toByte()
|
||||
)
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeString(id)
|
||||
parcel.writeString(category.code)
|
||||
parcel.writeParcelable(imageUrl, flags)
|
||||
parcel.writeString(name)
|
||||
parcel.writeString(description)
|
||||
parcel.writeLong(expirationTimestamp)
|
||||
parcel.writeByte(if (visible) 1 else 0)
|
||||
}
|
||||
|
||||
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
|
||||
messageDigest.update(id.toByteArray(Key.CHARSET))
|
||||
messageDigest.update(imageUrl.toString().toByteArray(Key.CHARSET))
|
||||
messageDigest.update(imageDensity.toByteArray(Key.CHARSET))
|
||||
}
|
||||
|
||||
fun resolveDescription(shortName: String): String {
|
||||
|
@ -130,6 +114,9 @@ data class Badge(
|
|||
|
||||
GlideApp.with(badge)
|
||||
.load(model.badge)
|
||||
.downsample(DownsampleStrategy.NONE)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.transform(BadgeSpriteTransformation(BadgeSpriteTransformation.Size.XLARGE, model.badge.imageDensity, ThemeUtil.isDarkTheme(context)))
|
||||
.into(target)
|
||||
|
||||
if (model.isSelected) {
|
||||
|
@ -170,7 +157,6 @@ data class Badge(
|
|||
val drawable = resource.selectable(
|
||||
DimensionUnit.DP.toPixels(2.5f),
|
||||
ContextCompat.getColor(view.context, R.color.signal_inverse_primary),
|
||||
ContextCompat.getColor(view.context, R.color.signal_background_primary),
|
||||
animator
|
||||
)
|
||||
|
||||
|
@ -202,20 +188,34 @@ data class Badge(
|
|||
}
|
||||
}
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<Badge> {
|
||||
companion object {
|
||||
private val SELECTION_CHANGED = Any()
|
||||
|
||||
override fun createFromParcel(parcel: Parcel): Badge {
|
||||
return Badge(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<Badge?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
|
||||
fun register(mappingAdapter: MappingAdapter, onBadgeClicked: OnBadgeClicked) {
|
||||
mappingAdapter.registerFactory(Model::class.java, MappingAdapter.LayoutFactory({ ViewHolder(it, onBadgeClicked) }, R.layout.badge_preference_view))
|
||||
mappingAdapter.registerFactory(EmptyModel::class.java, MappingAdapter.LayoutFactory({ EmptyViewHolder(it) }, R.layout.badge_preference_view))
|
||||
}
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class ImageSet(
|
||||
val ldpi: String,
|
||||
val mdpi: String,
|
||||
val hdpi: String,
|
||||
val xhdpi: String,
|
||||
val xxhdpi: String,
|
||||
val xxxhdpi: String
|
||||
) : Parcelable {
|
||||
fun getByDensity(density: String): String {
|
||||
return when (density) {
|
||||
"ldpi" -> ldpi
|
||||
"mdpi" -> mdpi
|
||||
"hdpi" -> hdpi
|
||||
"xhdpi" -> xhdpi
|
||||
"xxhdpi" -> xxhdpi
|
||||
"xxxhdpi" -> xxxhdpi
|
||||
else -> xhdpi
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,10 @@
|
|||
package org.thoughtcrime.securesms.badges.models
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.bumptech.glide.request.target.CustomViewTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import org.signal.core.util.DimensionUnit
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.badges.Badges.insetWithOutline
|
||||
import org.thoughtcrime.securesms.badges.BadgeImageView
|
||||
import org.thoughtcrime.securesms.components.AvatarImageView
|
||||
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.util.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.MappingViewHolder
|
||||
|
@ -35,40 +28,12 @@ object FeaturedBadgePreview {
|
|||
class ViewHolder(itemView: View) : MappingViewHolder<Model>(itemView) {
|
||||
|
||||
private val avatar: AvatarImageView = itemView.findViewById(R.id.avatar)
|
||||
private val badge: ImageView = itemView.findViewById(R.id.badge)
|
||||
private val target: Target = Target(badge)
|
||||
private val badge: BadgeImageView = itemView.findViewById(R.id.badge)
|
||||
|
||||
override fun bind(model: Model) {
|
||||
avatar.setRecipient(Recipient.self())
|
||||
avatar.disableQuickContact()
|
||||
|
||||
if (model.badge != null) {
|
||||
GlideApp.with(badge)
|
||||
.load(model.badge)
|
||||
.into(target)
|
||||
} else {
|
||||
GlideApp.with(badge).clear(badge)
|
||||
badge.setImageDrawable(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Target(view: ImageView) : CustomViewTarget<ImageView, Drawable>(view) {
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
view.setImageDrawable(errorDrawable)
|
||||
}
|
||||
|
||||
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
||||
view.setImageDrawable(
|
||||
resource.insetWithOutline(
|
||||
DimensionUnit.DP.toPixels(2.5f),
|
||||
ContextCompat.getColor(view.context, R.color.signal_background_primary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onResourceCleared(placeholder: Drawable?) {
|
||||
view.setImageDrawable(placeholder)
|
||||
badge.setBadge(model.badge)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
package org.thoughtcrime.securesms.badges.models
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.badges.BadgeImageView
|
||||
import org.thoughtcrime.securesms.util.MappingAdapter
|
||||
import org.thoughtcrime.securesms.util.MappingModel
|
||||
import org.thoughtcrime.securesms.util.MappingViewHolder
|
||||
|
@ -35,14 +34,12 @@ data class LargeBadge(
|
|||
|
||||
class ViewHolder(itemView: View) : MappingViewHolder<Model>(itemView) {
|
||||
|
||||
private val badge: ImageView = itemView.findViewById(R.id.badge)
|
||||
private val badge: BadgeImageView = itemView.findViewById(R.id.badge)
|
||||
private val name: TextView = itemView.findViewById(R.id.name)
|
||||
private val description: TextView = itemView.findViewById(R.id.description)
|
||||
|
||||
override fun bind(model: Model) {
|
||||
GlideApp.with(badge)
|
||||
.load(model.largeBadge.badge)
|
||||
.into(badge)
|
||||
badge.setBadge(model.largeBadge.badge)
|
||||
|
||||
name.text = model.largeBadge.badge.name
|
||||
description.text = model.largeBadge.badge.resolveDescription(model.shortName)
|
||||
|
|
|
@ -79,7 +79,7 @@ class SelectFeaturedBadgeFragment : DSLSettingsFragment(
|
|||
private fun getConfiguration(state: SelectFeaturedBadgeState): DSLConfiguration {
|
||||
return configure {
|
||||
sectionHeaderPref(R.string.SelectFeaturedBadgeFragment__select_a_badge)
|
||||
displayBadges(state.allUnlockedBadges, state.selectedBadge)
|
||||
displayBadges(requireContext(), state.allUnlockedBadges, state.selectedBadge)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ class BadgesOverviewFragment : DSLSettingsFragment(
|
|||
return configure {
|
||||
sectionHeaderPref(R.string.BadgesOverviewFragment__my_badges)
|
||||
|
||||
displayBadges(state.allUnlockedBadges)
|
||||
displayBadges(requireContext(), state.allUnlockedBadges)
|
||||
|
||||
switchPref(
|
||||
title = DSLSettingsText.from(R.string.BadgesOverviewFragment__display_badges_on_profile),
|
||||
|
|
|
@ -489,7 +489,7 @@ class ConversationSettingsFragment : DSLSettingsFragment(
|
|||
|
||||
sectionHeaderPref(R.string.ManageProfileFragment_badges)
|
||||
|
||||
displayBadges(state.recipient.badges)
|
||||
displayBadges(requireContext(), state.recipient.badges)
|
||||
}
|
||||
|
||||
if (recipientSettingsState.selfHasGroups) {
|
||||
|
|
|
@ -1373,9 +1373,10 @@ public class RecipientDatabase extends Database {
|
|||
badges.add(new Badge(
|
||||
protoBadge.getId(),
|
||||
Badge.Category.Companion.fromCode(protoBadge.getCategory()),
|
||||
Uri.parse(protoBadge.getImageUrl()),
|
||||
protoBadge.getName(),
|
||||
protoBadge.getDescription(),
|
||||
Uri.parse(protoBadge.getImageUrl()),
|
||||
protoBadge.getImageDensity(),
|
||||
protoBadge.getExpiration(),
|
||||
protoBadge.getVisible()
|
||||
));
|
||||
|
@ -1691,7 +1692,8 @@ public class RecipientDatabase extends Database {
|
|||
.setExpiration(badge.getExpirationTimestamp())
|
||||
.setVisible(badge.getVisible())
|
||||
.setName(badge.getName())
|
||||
.setImageUrl(badge.getImageUrl().toString()));
|
||||
.setImageUrl(badge.getImageUrl().toString())
|
||||
.setImageDensity(badge.getImageDensity()));
|
||||
}
|
||||
|
||||
ContentValues values = new ContentValues(1);
|
||||
|
|
|
@ -29,7 +29,7 @@ import okhttp3.ConnectionSpec;
|
|||
import okhttp3.OkHttpClient;
|
||||
|
||||
/**
|
||||
* A simple model loader for fetching media over http/https using OkHttp.
|
||||
* A loader which will load a sprite sheet for a particular badge at the correct dpi for this device.
|
||||
*/
|
||||
public class BadgeLoader implements ModelLoader<Badge, InputStream> {
|
||||
|
||||
|
@ -40,12 +40,12 @@ public class BadgeLoader implements ModelLoader<Badge, InputStream> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public @Nullable LoadData<InputStream> buildLoadData(@NonNull Badge badge, int width, int height, @NonNull Options options) {
|
||||
return new LoadData<>(badge, new OkHttpStreamFetcher(client, new GlideUrl(badge.getImageUrl().toString())));
|
||||
public @Nullable LoadData<InputStream> buildLoadData(@NonNull Badge request, int width, int height, @NonNull Options options) {
|
||||
return new LoadData<>(request, new OkHttpStreamFetcher(client, new GlideUrl(request.getImageUrl().toString())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handles(@NonNull Badge badge) {
|
||||
public boolean handles(@NonNull Badge badgeSpriteSheetRequest) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import androidx.annotation.Nullable;
|
|||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.zkgroup.profiles.ProfileKey;
|
||||
import org.signal.zkgroup.profiles.ProfileKeyCredential;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.badges.models.Badge;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
|
@ -21,8 +22,10 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
|||
import org.thoughtcrime.securesms.profiles.ProfileName;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.ProfileUtil;
|
||||
import org.thoughtcrime.securesms.util.ScreenDensity;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
|
||||
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
|
||||
|
@ -177,12 +180,14 @@ public class RefreshOwnProfileJob extends BaseJob {
|
|||
}
|
||||
|
||||
private static Badge adaptFromServiceBadge(@NonNull SignalServiceProfile.Badge serviceBadge) {
|
||||
Pair<Uri, String> uriAndDensity = RetrieveProfileJob.getBestBadgeImageUriForDevice(serviceBadge);
|
||||
return new Badge(
|
||||
serviceBadge.getId(),
|
||||
Badge.Category.Companion.fromCode(serviceBadge.getCategory()),
|
||||
Uri.parse(serviceBadge.getImageUrl()),
|
||||
serviceBadge.getName(),
|
||||
serviceBadge.getDescription(),
|
||||
uriAndDensity.first(),
|
||||
uriAndDensity.second(),
|
||||
getTimestamp(serviceBadge.getExpiration()),
|
||||
serviceBadge.isVisible()
|
||||
);
|
||||
|
|
|
@ -16,6 +16,7 @@ import org.signal.core.util.concurrent.SignalExecutors;
|
|||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.zkgroup.profiles.ProfileKey;
|
||||
import org.signal.zkgroup.profiles.ProfileKeyCredential;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.badges.models.Badge;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
|
@ -34,9 +35,9 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
|
|||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||
import org.thoughtcrime.securesms.util.ProfileUtil;
|
||||
import org.thoughtcrime.securesms.util.ScreenDensity;
|
||||
import org.thoughtcrime.securesms.util.SetUtil;
|
||||
import org.thoughtcrime.securesms.util.Stopwatch;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
@ -357,17 +358,45 @@ public class RetrieveProfileJob extends BaseJob {
|
|||
}
|
||||
|
||||
private static Badge adaptFromServiceBadge(@NonNull SignalServiceProfile.Badge serviceBadge) {
|
||||
Pair<Uri, String> uriAndDensity = RetrieveProfileJob.getBestBadgeImageUriForDevice(serviceBadge);
|
||||
return new Badge(
|
||||
serviceBadge.getId(),
|
||||
Badge.Category.Companion.fromCode(serviceBadge.getCategory()),
|
||||
Uri.parse(serviceBadge.getImageUrl()),
|
||||
serviceBadge.getName(),
|
||||
serviceBadge.getDescription(),
|
||||
uriAndDensity.first(),
|
||||
uriAndDensity.second(),
|
||||
0L,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public static @NonNull Pair<Uri, String> getBestBadgeImageUriForDevice(@NonNull SignalServiceProfile.Badge serviceBadge) {
|
||||
String bestDensity = ScreenDensity.getBestDensityBucketForDevice();
|
||||
|
||||
switch (bestDensity) {
|
||||
case "ldpi":
|
||||
return new Pair<>(getBadgeImageUri(serviceBadge.getLdpiUri()), "ldpi");
|
||||
case "mdpi":
|
||||
return new Pair<>(getBadgeImageUri(serviceBadge.getMdpiUri()), "mdpi");
|
||||
case "hdpi":
|
||||
return new Pair<>(getBadgeImageUri(serviceBadge.getHdpiUri()), "hdpi");
|
||||
case "xxhdpi":
|
||||
return new Pair<>(getBadgeImageUri(serviceBadge.getXxhdpiUri()), "xxhdpi");
|
||||
case "xxxhdpi":
|
||||
return new Pair<>(getBadgeImageUri(serviceBadge.getXxxhdpiUri()), "xxxhdpi");
|
||||
default:
|
||||
return new Pair<>(getBadgeImageUri(serviceBadge.getXhdpiUri()), "xdpi");
|
||||
}
|
||||
}
|
||||
|
||||
private static @NonNull Uri getBadgeImageUri(@NonNull String densityPath) {
|
||||
return Uri.parse(BuildConfig.BADGE_STATIC_ROOT).buildUpon()
|
||||
.appendPath(densityPath)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void setProfileKeyCredential(@NonNull Recipient recipient,
|
||||
@NonNull ProfileKey recipientProfileKey,
|
||||
@NonNull ProfileKeyCredential credential)
|
||||
|
|
|
@ -5,6 +5,8 @@ import android.util.DisplayMetrics;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -51,6 +53,16 @@ public final class ScreenDensity {
|
|||
return new ScreenDensity(bucket, density);
|
||||
}
|
||||
|
||||
public static @NonNull String getBestDensityBucketForDevice() {
|
||||
ScreenDensity density = get(ApplicationDependencies.getApplication());
|
||||
|
||||
if (density.isKnownDensity()) {
|
||||
return density.bucket;
|
||||
} else {
|
||||
return "xhdpi";
|
||||
}
|
||||
}
|
||||
|
||||
public String getBucket() {
|
||||
return bucket;
|
||||
}
|
||||
|
|
|
@ -25,13 +25,14 @@ message ReactionList {
|
|||
|
||||
message BadgeList {
|
||||
message Badge {
|
||||
string id = 1;
|
||||
string category = 2;
|
||||
string name = 3;
|
||||
string description = 4;
|
||||
string imageUrl = 5;
|
||||
uint64 expiration = 6;
|
||||
bool visible = 7;
|
||||
string id = 1;
|
||||
string category = 2;
|
||||
string name = 3;
|
||||
string description = 4;
|
||||
string imageUrl = 5;
|
||||
uint64 expiration = 6;
|
||||
bool visible = 7;
|
||||
string imageDensity = 8;
|
||||
}
|
||||
|
||||
repeated Badge badges = 1;
|
||||
|
|
|
@ -21,14 +21,13 @@
|
|||
|
||||
<org.thoughtcrime.securesms.badges.BadgeImageView
|
||||
android:id="@+id/badge"
|
||||
android:layout_width="26dp"
|
||||
android:layout_height="26dp"
|
||||
android:layout_marginStart="39dp"
|
||||
android:layout_marginTop="39dp"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="40dp"
|
||||
android:layout_marginTop="40dp"
|
||||
android:contentDescription="@string/ImageView__badge"
|
||||
android:visibility="gone"
|
||||
app:badge_outline_color="@color/signal_background_primary"
|
||||
app:badge_outline_width="1dp"
|
||||
app:badge_size="medium"
|
||||
app:layout_constraintStart_toStartOf="@id/icon"
|
||||
app:layout_constraintTop_toTopOf="@id/icon"
|
||||
tools:visibility="visible" />
|
||||
|
|
|
@ -25,14 +25,13 @@
|
|||
|
||||
<org.thoughtcrime.securesms.badges.BadgeImageView
|
||||
android:id="@+id/contact_badge"
|
||||
android:layout_width="@dimen/badge_size_small"
|
||||
android:layout_height="@dimen/badge_size_small"
|
||||
android:layout_marginStart="23dp"
|
||||
android:layout_marginTop="23dp"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:contentDescription="@string/ImageView__badge"
|
||||
android:visibility="gone"
|
||||
app:badge_outline_color="@color/signal_background_primary"
|
||||
app:badge_outline_width="1dp"
|
||||
app:badge_size="small"
|
||||
app:layout_constraintStart_toStartOf="@id/contact_photo_image"
|
||||
app:layout_constraintTop_toTopOf="@id/contact_photo_image"
|
||||
tools:visibility="visible" />
|
||||
|
|
|
@ -18,14 +18,13 @@
|
|||
|
||||
<org.thoughtcrime.securesms.badges.BadgeImageView
|
||||
android:id="@+id/message_request_badge"
|
||||
android:layout_width="34dp"
|
||||
android:layout_height="34dp"
|
||||
android:layout_marginStart="46dp"
|
||||
android:layout_marginTop="47dp"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginStart="47dp"
|
||||
android:layout_marginTop="48dp"
|
||||
android:contentDescription="@string/ImageView__badge"
|
||||
android:visibility="gone"
|
||||
app:badge_outline_color="@color/signal_background_primary"
|
||||
app:badge_outline_width="1dp"
|
||||
app:badge_size="large"
|
||||
app:layout_constraintStart_toStartOf="@id/message_request_avatar"
|
||||
app:layout_constraintTop_toTopOf="@id/message_request_avatar"
|
||||
tools:visibility="visible" />
|
||||
|
|
|
@ -191,14 +191,13 @@
|
|||
|
||||
<org.thoughtcrime.securesms.badges.BadgeImageView
|
||||
android:id="@+id/conversation_list_item_badge"
|
||||
android:layout_width="26dp"
|
||||
android:layout_height="26dp"
|
||||
android:layout_marginStart="@dimen/conversation_list_badge_offset"
|
||||
android:layout_marginTop="@dimen/conversation_list_badge_offset"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="26dp"
|
||||
android:layout_marginTop="26dp"
|
||||
android:contentDescription="@string/ImageView__badge"
|
||||
android:visibility="gone"
|
||||
app:badge_outline_color="@color/signal_background_primary"
|
||||
app:badge_outline_width="1dp"
|
||||
app:badge_size="medium"
|
||||
app:layout_constraintStart_toStartOf="@id/conversation_list_item_avatar"
|
||||
app:layout_constraintTop_toTopOf="@id/conversation_list_item_avatar"
|
||||
tools:visibility="visible" />
|
||||
|
|
|
@ -23,14 +23,13 @@
|
|||
|
||||
<org.thoughtcrime.securesms.badges.BadgeImageView
|
||||
android:id="@+id/bio_preference_badge"
|
||||
android:layout_width="34dp"
|
||||
android:layout_height="34dp"
|
||||
android:layout_marginStart="46dp"
|
||||
android:layout_marginTop="47dp"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginStart="47dp"
|
||||
android:layout_marginTop="48dp"
|
||||
android:contentDescription="@string/ImageView__badge"
|
||||
android:visibility="gone"
|
||||
app:badge_outline_color="@color/signal_background_primary"
|
||||
app:badge_outline_width="1dp"
|
||||
app:badge_size="large"
|
||||
app:layout_constraintStart_toStartOf="@id/bio_preference_avatar"
|
||||
app:layout_constraintTop_toTopOf="@id/bio_preference_avatar"
|
||||
tools:visibility="visible" />
|
||||
|
|
|
@ -31,16 +31,15 @@
|
|||
|
||||
<org.thoughtcrime.securesms.badges.BadgeImageView
|
||||
android:id="@+id/badge"
|
||||
android:layout_width="@dimen/badge_size_small"
|
||||
android:layout_height="@dimen/badge_size_small"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_alignStart="@id/contact_photo_image"
|
||||
android:layout_alignTop="@id/contact_photo_image"
|
||||
android:layout_marginStart="21dp"
|
||||
android:layout_marginTop="21dp"
|
||||
android:layout_marginStart="22dp"
|
||||
android:layout_marginTop="22dp"
|
||||
android:contentDescription="@string/ImageView__badge"
|
||||
android:visibility="gone"
|
||||
app:badge_outline_color="@color/signal_background_primary"
|
||||
app:badge_outline_width="1dp"
|
||||
app:badge_size="small"
|
||||
tools:visibility="visible" />
|
||||
</RelativeLayout>
|
||||
|
||||
|
|
|
@ -22,11 +22,12 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
<org.thoughtcrime.securesms.badges.BadgeImageView
|
||||
android:id="@+id/badge"
|
||||
android:layout_width="33dp"
|
||||
android:layout_height="33dp"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:contentDescription="@string/BadgesOverviewFragment__featured_badge"
|
||||
app:badge_size="large"
|
||||
app:layout_constraintBottom_toBottomOf="@id/avatar"
|
||||
app:layout_constraintEnd_toEndOf="@id/avatar" />
|
||||
|
||||
|
|
|
@ -23,14 +23,13 @@
|
|||
|
||||
<org.thoughtcrime.securesms.badges.BadgeImageView
|
||||
android:id="@+id/rbs_badge"
|
||||
android:layout_width="34dp"
|
||||
android:layout_height="34dp"
|
||||
android:layout_marginStart="46dp"
|
||||
android:layout_marginTop="47dp"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_marginStart="47dp"
|
||||
android:layout_marginTop="48dp"
|
||||
android:contentDescription="@string/ImageView__badge"
|
||||
android:visibility="gone"
|
||||
app:badge_outline_color="@color/signal_background_primary"
|
||||
app:badge_outline_width="1dp"
|
||||
app:badge_size="large"
|
||||
app:layout_constraintStart_toStartOf="@id/rbs_recipient_avatar"
|
||||
app:layout_constraintTop_toTopOf="@id/rbs_recipient_avatar"
|
||||
tools:visibility="visible" />
|
||||
|
|
|
@ -3,14 +3,16 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
<org.thoughtcrime.securesms.badges.BadgeImageView
|
||||
android:id="@+id/badge"
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:contentDescription="@string/BadgesOverviewFragment__featured_badge"
|
||||
app:badge_size="xlarge"
|
||||
tools:src="@drawable/test_gradient" />
|
||||
|
||||
<TextView
|
||||
|
|
|
@ -33,4 +33,6 @@
|
|||
<dimen name="toolbar_avatar_margin">34dp</dimen>
|
||||
|
||||
<dimen name="verify_identity_vertical_margin">32dp</dimen>
|
||||
|
||||
<integer name="badge_columns">4</integer>
|
||||
</resources>
|
|
@ -2,4 +2,6 @@
|
|||
<resources>
|
||||
<dimen name="media_bubble_max_width">350dp</dimen>
|
||||
<dimen name="media_bubble_max_height">300dp</dimen>
|
||||
|
||||
<integer name="badge_columns">5</integer>
|
||||
</resources>
|
|
@ -328,7 +328,11 @@
|
|||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="BadgeImageView">
|
||||
<attr name="badge_outline_width" format="dimension" />
|
||||
<attr name="badge_outline_color" format="color" />
|
||||
<attr name="badge_size" format="enum">
|
||||
<enum name="small" value="0" />
|
||||
<enum name="medium" value="1" />
|
||||
<enum name="large" value="2" />
|
||||
<enum name="xlarge" value="3" />
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
|
|
|
@ -205,9 +205,8 @@
|
|||
<dimen name="toolbar_avatar_size">28dp</dimen>
|
||||
<dimen name="toolbar_avatar_margin">26dp</dimen>
|
||||
<dimen name="conversation_list_avatar_size">48dp</dimen>
|
||||
<dimen name="conversation_list_badge_offset">25dp</dimen>
|
||||
|
||||
<dimen name="verify_identity_vertical_margin">16dp</dimen>
|
||||
|
||||
<dimen name="badge_size_small">18dp</dimen>
|
||||
<integer name="badge_columns">3</integer>
|
||||
</resources>
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
package org.thoughtcrime.securesms.badges.glide
|
||||
|
||||
import android.app.Application
|
||||
import android.graphics.Rect
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@Suppress("ClassName")
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE, application = Application::class)
|
||||
class BadgeSpriteTransformationTest__mdpi {
|
||||
|
||||
@Test
|
||||
fun `Given request for large mdpi in light theme, when I getInBounds, then I expect 18x18@1,1`() {
|
||||
// GIVEN
|
||||
val density = "mdpi"
|
||||
val size = BadgeSpriteTransformation.Size.LARGE
|
||||
val isDarkTheme = false
|
||||
|
||||
// WHEN
|
||||
val inBounds = BadgeSpriteTransformation.getInBounds(density, size, isDarkTheme)
|
||||
|
||||
// THEN
|
||||
assertRectMatches(inBounds, 1, 1, 18, 18)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given request for large mdpi in dark theme, when I getInBounds, then I expect 18x18@21,1`() {
|
||||
// GIVEN
|
||||
val density = "mdpi"
|
||||
val size = BadgeSpriteTransformation.Size.LARGE
|
||||
val isDarkTheme = true
|
||||
|
||||
// WHEN
|
||||
val inBounds = BadgeSpriteTransformation.getInBounds(density, size, isDarkTheme)
|
||||
|
||||
// THEN
|
||||
assertRectMatches(inBounds, 21, 1, 18, 18)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given request for medium mdpi in light theme, when I getInBounds, then I expect 12x12@41,1`() {
|
||||
// GIVEN
|
||||
val density = "mdpi"
|
||||
val size = BadgeSpriteTransformation.Size.MEDIUM
|
||||
val isDarkTheme = false
|
||||
|
||||
// WHEN
|
||||
val inBounds = BadgeSpriteTransformation.getInBounds(density, size, isDarkTheme)
|
||||
|
||||
// THEN
|
||||
assertRectMatches(inBounds, 41, 1, 12, 12)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given request for medium mdpi in dark theme, when I getInBounds, then I expect 12x12@55,1`() {
|
||||
// GIVEN
|
||||
val density = "mdpi"
|
||||
val size = BadgeSpriteTransformation.Size.MEDIUM
|
||||
val isDarkTheme = true
|
||||
|
||||
// WHEN
|
||||
val inBounds = BadgeSpriteTransformation.getInBounds(density, size, isDarkTheme)
|
||||
|
||||
// THEN
|
||||
assertRectMatches(inBounds, 55, 1, 12, 12)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given request for small mdpi in light theme, when I getInBounds, then I expect 8x8@69,1`() {
|
||||
// GIVEN
|
||||
val density = "mdpi"
|
||||
val size = BadgeSpriteTransformation.Size.SMALL
|
||||
val isDarkTheme = false
|
||||
|
||||
// WHEN
|
||||
val inBounds = BadgeSpriteTransformation.getInBounds(density, size, isDarkTheme)
|
||||
|
||||
// THEN
|
||||
assertRectMatches(inBounds, 69, 1, 8, 8)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given request for small mdpi in dark theme, when I getInBounds, then I expect 8x8@79,1`() {
|
||||
// GIVEN
|
||||
val density = "mdpi"
|
||||
val size = BadgeSpriteTransformation.Size.SMALL
|
||||
val isDarkTheme = true
|
||||
|
||||
// WHEN
|
||||
val inBounds = BadgeSpriteTransformation.getInBounds(density, size, isDarkTheme)
|
||||
|
||||
// THEN
|
||||
assertRectMatches(inBounds, 79, 1, 8, 8)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given request for xlarge mdpi in light theme, when I getInBounds, then I expect 80x80@89,1`() {
|
||||
// GIVEN
|
||||
val density = "mdpi"
|
||||
val size = BadgeSpriteTransformation.Size.XLARGE
|
||||
val isDarkTheme = false
|
||||
|
||||
// WHEN
|
||||
val inBounds = BadgeSpriteTransformation.getInBounds(density, size, isDarkTheme)
|
||||
|
||||
// THEN
|
||||
assertRectMatches(inBounds, 89, 1, 80, 80)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given request for xlarge mdpi in dark theme, when I getInBounds, then I expect 80x80@89,1`() {
|
||||
// GIVEN
|
||||
val density = "mdpi"
|
||||
val size = BadgeSpriteTransformation.Size.XLARGE
|
||||
val isDarkTheme = true
|
||||
|
||||
// WHEN
|
||||
val inBounds = BadgeSpriteTransformation.getInBounds(density, size, isDarkTheme)
|
||||
|
||||
// THEN
|
||||
assertRectMatches(inBounds, 89, 1, 80, 80)
|
||||
}
|
||||
|
||||
private fun assertRectMatches(rect: Rect, x: Int, y: Int, width: Int, height: Int) {
|
||||
assertEquals("Rect has wrong x value", x, rect.left)
|
||||
assertEquals("Rect has wrong y value", rect.top, y)
|
||||
assertEquals("Rect has wrong width", width, rect.width())
|
||||
assertEquals("Rect has wrong height", height, rect.height())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
package org.thoughtcrime.securesms.badges.glide
|
||||
|
||||
import android.app.Application
|
||||
import android.graphics.Rect
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@Suppress("ClassName")
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(manifest = Config.NONE, application = Application::class)
|
||||
class BadgeSpriteTransformationTest__xxxhdpi {
|
||||
|
||||
@Test
|
||||
fun `Given request for large xxxhdpi in light theme, when I getInBounds, then I expect 72x72@1,1`() {
|
||||
// GIVEN
|
||||
val density = "xxxhdpi"
|
||||
val size = BadgeSpriteTransformation.Size.LARGE
|
||||
val isDarkTheme = false
|
||||
|
||||
// WHEN
|
||||
val inBounds = BadgeSpriteTransformation.getInBounds(density, size, isDarkTheme)
|
||||
|
||||
// THEN
|
||||
assertRectMatches(inBounds, 1, 1, 72, 72)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given request for large xxxhdpi in dark theme, when I getInBounds, then I expect 72x72@21,1`() {
|
||||
// GIVEN
|
||||
val density = "xxxhdpi"
|
||||
val size = BadgeSpriteTransformation.Size.LARGE
|
||||
val isDarkTheme = true
|
||||
|
||||
// WHEN
|
||||
val inBounds = BadgeSpriteTransformation.getInBounds(density, size, isDarkTheme)
|
||||
|
||||
// THEN
|
||||
assertRectMatches(inBounds, 75, 1, 72, 72)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given request for medium xxxhdpi in light theme, when I getInBounds, then I expect 48x48@149,1`() {
|
||||
// GIVEN
|
||||
val density = "xxxhdpi"
|
||||
val size = BadgeSpriteTransformation.Size.MEDIUM
|
||||
val isDarkTheme = false
|
||||
|
||||
// WHEN
|
||||
val inBounds = BadgeSpriteTransformation.getInBounds(density, size, isDarkTheme)
|
||||
|
||||
// THEN
|
||||
assertRectMatches(inBounds, 149, 1, 48, 48)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given request for medium xxxhdpi in dark theme, when I getInBounds, then I expect 48x48@199,1`() {
|
||||
// GIVEN
|
||||
val density = "xxxhdpi"
|
||||
val size = BadgeSpriteTransformation.Size.MEDIUM
|
||||
val isDarkTheme = true
|
||||
|
||||
// WHEN
|
||||
val inBounds = BadgeSpriteTransformation.getInBounds(density, size, isDarkTheme)
|
||||
|
||||
// THEN
|
||||
assertRectMatches(inBounds, 199, 1, 48, 48)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given request for small xxxhdpi in light theme, when I getInBounds, then I expect 32x32@249,1`() {
|
||||
// GIVEN
|
||||
val density = "xxxhdpi"
|
||||
val size = BadgeSpriteTransformation.Size.SMALL
|
||||
val isDarkTheme = false
|
||||
|
||||
// WHEN
|
||||
val inBounds = BadgeSpriteTransformation.getInBounds(density, size, isDarkTheme)
|
||||
|
||||
// THEN
|
||||
assertRectMatches(inBounds, 249, 1, 32, 32)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given request for small xxxhdpi in dark theme, when I getInBounds, then I expect 32x32@283,1`() {
|
||||
// GIVEN
|
||||
val density = "xxxhdpi"
|
||||
val size = BadgeSpriteTransformation.Size.SMALL
|
||||
val isDarkTheme = true
|
||||
|
||||
// WHEN
|
||||
val inBounds = BadgeSpriteTransformation.getInBounds(density, size, isDarkTheme)
|
||||
|
||||
// THEN
|
||||
assertRectMatches(inBounds, 283, 1, 32, 32)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given request for xlarge xxxhdpi in light theme, when I getInBounds, then I expect 320x320@317,1`() {
|
||||
// GIVEN
|
||||
val density = "xxxhdpi"
|
||||
val size = BadgeSpriteTransformation.Size.XLARGE
|
||||
val isDarkTheme = false
|
||||
|
||||
// WHEN
|
||||
val inBounds = BadgeSpriteTransformation.getInBounds(density, size, isDarkTheme)
|
||||
|
||||
// THEN
|
||||
assertRectMatches(inBounds, 317, 1, 320, 320)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Given request for xlarge xxxhdpi in dark theme, when I getInBounds, then I expect 320x320@317,1`() {
|
||||
// GIVEN
|
||||
val density = "xxxhdpi"
|
||||
val size = BadgeSpriteTransformation.Size.XLARGE
|
||||
val isDarkTheme = true
|
||||
|
||||
// WHEN
|
||||
val inBounds = BadgeSpriteTransformation.getInBounds(density, size, isDarkTheme)
|
||||
|
||||
// THEN
|
||||
assertRectMatches(inBounds, 317, 1, 320, 320)
|
||||
}
|
||||
|
||||
private fun assertRectMatches(rect: Rect, x: Int, y: Int, width: Int, height: Int) {
|
||||
assertEquals("Rect has wrong x value", x, rect.left)
|
||||
assertEquals("Rect has wrong y value", rect.top, y)
|
||||
assertEquals("Rect has wrong width", width, rect.width())
|
||||
assertEquals("Rect has wrong height", height, rect.height())
|
||||
}
|
||||
}
|
|
@ -128,15 +128,30 @@ public class SignalServiceProfile {
|
|||
@JsonProperty
|
||||
private String category;
|
||||
|
||||
@JsonProperty
|
||||
private String imageUrl;
|
||||
|
||||
@JsonProperty
|
||||
private String name;
|
||||
|
||||
@JsonProperty
|
||||
private String description;
|
||||
|
||||
@JsonProperty
|
||||
private String ldpi;
|
||||
|
||||
@JsonProperty
|
||||
private String mdpi;
|
||||
|
||||
@JsonProperty
|
||||
private String hdpi;
|
||||
|
||||
@JsonProperty
|
||||
private String xhdpi;
|
||||
|
||||
@JsonProperty
|
||||
private String xxhdpi;
|
||||
|
||||
@JsonProperty
|
||||
private String xxxhdpi;
|
||||
|
||||
@JsonProperty
|
||||
private BigDecimal expiration;
|
||||
|
||||
|
@ -159,12 +174,32 @@ public class SignalServiceProfile {
|
|||
return description;
|
||||
}
|
||||
|
||||
public BigDecimal getExpiration() {
|
||||
return expiration;
|
||||
public String getLdpiUri() {
|
||||
return ldpi;
|
||||
}
|
||||
|
||||
public String getImageUrl() {
|
||||
return imageUrl;
|
||||
public String getMdpiUri() {
|
||||
return mdpi;
|
||||
}
|
||||
|
||||
public String getHdpiUri() {
|
||||
return hdpi;
|
||||
}
|
||||
|
||||
public String getXhdpiUri() {
|
||||
return xhdpi;
|
||||
}
|
||||
|
||||
public String getXxhdpiUri() {
|
||||
return xxhdpi;
|
||||
}
|
||||
|
||||
public String getXxxhdpiUri() {
|
||||
return xxxhdpi;
|
||||
}
|
||||
|
||||
public BigDecimal getExpiration() {
|
||||
return expiration;
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
|
|
Loading…
Add table
Reference in a new issue