kopia lustrzana https://github.com/ryukoposting/Signal-Android
Add support for updated server badge image url formats.
rodzic
6e00920c95
commit
8d0acb277c
|
@ -9,6 +9,7 @@ apply from: 'translations.gradle'
|
||||||
apply from: 'witness-verifications.gradle'
|
apply from: 'witness-verifications.gradle'
|
||||||
apply plugin: 'org.jetbrains.kotlin.android'
|
apply plugin: 'org.jetbrains.kotlin.android'
|
||||||
apply plugin: 'app.cash.exhaustive'
|
apply plugin: 'app.cash.exhaustive'
|
||||||
|
apply plugin: 'kotlin-parcelize'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
maven {
|
maven {
|
||||||
|
@ -177,6 +178,7 @@ android {
|
||||||
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"unset\""
|
buildConfigField "String", "BUILD_DISTRIBUTION_TYPE", "\"unset\""
|
||||||
buildConfigField "String", "BUILD_ENVIRONMENT_TYPE", "\"unset\""
|
buildConfigField "String", "BUILD_ENVIRONMENT_TYPE", "\"unset\""
|
||||||
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"unset\""
|
buildConfigField "String", "BUILD_VARIANT_TYPE", "\"unset\""
|
||||||
|
buildConfigField "String", "BADGE_STATIC_ROOT", "\"https://updates2.signal.org/static/badges/\""
|
||||||
|
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||||
|
|
|
@ -1,20 +1,18 @@
|
||||||
package org.thoughtcrime.securesms.badges
|
package org.thoughtcrime.securesms.badges
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import androidx.annotation.ColorInt
|
|
||||||
import androidx.annotation.Px
|
|
||||||
import androidx.appcompat.widget.AppCompatImageView
|
import androidx.appcompat.widget.AppCompatImageView
|
||||||
import androidx.core.content.res.use
|
import androidx.core.content.res.use
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import org.thoughtcrime.securesms.R
|
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.badges.models.Badge
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp
|
import org.thoughtcrime.securesms.mms.GlideApp
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
import org.thoughtcrime.securesms.util.ThemeUtil
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil
|
import org.thoughtcrime.securesms.util.ViewUtil
|
||||||
import org.thoughtcrime.securesms.util.visible
|
import org.thoughtcrime.securesms.util.visible
|
||||||
|
|
||||||
|
@ -25,16 +23,11 @@ class BadgeImageView @JvmOverloads constructor(
|
||||||
attrs: AttributeSet? = null
|
attrs: AttributeSet? = null
|
||||||
) : AppCompatImageView(context, attrs) {
|
) : AppCompatImageView(context, attrs) {
|
||||||
|
|
||||||
@Px
|
private var badgeSize: Int = 0
|
||||||
private var outlineWidth: Float = 0f
|
|
||||||
|
|
||||||
@ColorInt
|
|
||||||
private var outlineColor: Int = Color.BLACK
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
context.obtainStyledAttributes(attrs, R.styleable.BadgeImageView).use {
|
context.obtainStyledAttributes(attrs, R.styleable.BadgeImageView).use {
|
||||||
outlineWidth = it.getDimension(R.styleable.BadgeImageView_badge_outline_width, 0f)
|
badgeSize = it.getInt(R.styleable.BadgeImageView_badge_size, 0)
|
||||||
outlineColor = it.getColor(R.styleable.BadgeImageView_badge_outline_color, Color.BLACK)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,21 +48,17 @@ class BadgeImageView @JvmOverloads constructor(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
GlideApp
|
if (badge != null) {
|
||||||
.with(this)
|
GlideApp
|
||||||
.load(badge)
|
.with(this)
|
||||||
.into(this)
|
.load(badge)
|
||||||
}
|
.downsample(DownsampleStrategy.NONE)
|
||||||
|
.transform(BadgeSpriteTransformation(BadgeSpriteTransformation.Size.fromInteger(badgeSize), badge.imageDensity, ThemeUtil.isDarkTheme(context)))
|
||||||
override fun setImageDrawable(drawable: Drawable?) {
|
.into(this)
|
||||||
if (drawable == null || outlineWidth == 0f) {
|
|
||||||
super.setImageDrawable(drawable)
|
|
||||||
} else {
|
} else {
|
||||||
super.setImageDrawable(
|
GlideApp
|
||||||
drawable.insetWithOutline(
|
.with(this)
|
||||||
outlineWidth, outlineColor
|
.clear(this)
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,46 +11,23 @@ import com.google.android.flexbox.AlignItems
|
||||||
import com.google.android.flexbox.FlexDirection
|
import com.google.android.flexbox.FlexDirection
|
||||||
import com.google.android.flexbox.FlexboxLayoutManager
|
import com.google.android.flexbox.FlexboxLayoutManager
|
||||||
import com.google.android.flexbox.JustifyContent
|
import com.google.android.flexbox.JustifyContent
|
||||||
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.badges.models.Badge
|
import org.thoughtcrime.securesms.badges.models.Badge
|
||||||
import org.thoughtcrime.securesms.badges.models.BadgeAnimator
|
import org.thoughtcrime.securesms.badges.models.BadgeAnimator
|
||||||
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
import org.thoughtcrime.securesms.components.settings.DSLConfiguration
|
||||||
import org.thoughtcrime.securesms.util.customizeOnDraw
|
import org.thoughtcrime.securesms.util.customizeOnDraw
|
||||||
|
|
||||||
object Badges {
|
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(
|
fun Drawable.selectable(
|
||||||
@Px outlineWidth: Float,
|
@Px outlineWidth: Float,
|
||||||
@ColorInt outlineColor: Int,
|
@ColorInt outlineColor: Int,
|
||||||
@ColorInt gapColor: Int,
|
|
||||||
animator: BadgeAnimator
|
animator: BadgeAnimator
|
||||||
): Drawable {
|
): Drawable {
|
||||||
val outline = mutate().constantState?.newDrawable()?.mutate()
|
val outline = mutate().constantState?.newDrawable()?.mutate()
|
||||||
outline?.colorFilter = SimpleColorFilter(outlineColor)
|
outline?.colorFilter = SimpleColorFilter(outlineColor)
|
||||||
|
|
||||||
val gap = mutate().constantState?.newDrawable()?.mutate()
|
|
||||||
gap?.colorFilter = SimpleColorFilter(gapColor)
|
|
||||||
|
|
||||||
return customizeOnDraw { wrapped, canvas ->
|
return customizeOnDraw { wrapped, canvas ->
|
||||||
outline?.bounds = wrapped.bounds
|
outline?.bounds = wrapped.bounds
|
||||||
gap?.bounds = wrapped.bounds
|
|
||||||
|
|
||||||
outline?.draw(canvas)
|
outline?.draw(canvas)
|
||||||
|
|
||||||
|
@ -58,11 +35,7 @@ object Badges {
|
||||||
val interpolatedScale = scale + (1f - scale) * animator.getFraction()
|
val interpolatedScale = scale + (1f - scale) * animator.getFraction()
|
||||||
|
|
||||||
canvas.withScale(x = interpolatedScale, y = interpolatedScale, wrapped.bounds.width() / 2f, wrapped.bounds.height() / 2f) {
|
canvas.withScale(x = interpolatedScale, y = interpolatedScale, wrapped.bounds.width() / 2f, wrapped.bounds.height() / 2f) {
|
||||||
gap?.draw(canvas)
|
wrapped.draw(canvas)
|
||||||
|
|
||||||
canvas.withScale(x = interpolatedScale, y = interpolatedScale, wrapped.bounds.width() / 2f, wrapped.bounds.height() / 2f) {
|
|
||||||
wrapped.draw(canvas)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (animator.shouldInvalidate()) {
|
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
|
badges
|
||||||
.map { Badge.Model(it, it == selectedBadge) }
|
.map { Badge.Model(it, it == selectedBadge) }
|
||||||
.forEach { customPref(it) }
|
.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) {
|
repeat(empties) {
|
||||||
customPref(Badge.EmptyModel())
|
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.graphics.drawable.Drawable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Parcel
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.bumptech.glide.load.Key
|
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.target.CustomViewTarget
|
||||||
import com.bumptech.glide.request.transition.Transition
|
import com.bumptech.glide.request.transition.Transition
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
import org.signal.core.util.DimensionUnit
|
import org.signal.core.util.DimensionUnit
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.badges.Badges.selectable
|
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.components.settings.PreferenceModel
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp
|
import org.thoughtcrime.securesms.mms.GlideApp
|
||||||
import org.thoughtcrime.securesms.util.MappingAdapter
|
import org.thoughtcrime.securesms.util.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.MappingViewHolder
|
import org.thoughtcrime.securesms.util.MappingViewHolder
|
||||||
|
import org.thoughtcrime.securesms.util.ThemeUtil
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
typealias OnBadgeClicked = (Badge, Boolean) -> Unit
|
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.
|
* A Badge that can be collected and displayed by a user.
|
||||||
*/
|
*/
|
||||||
|
@Parcelize
|
||||||
data class Badge(
|
data class Badge(
|
||||||
val id: String,
|
val id: String,
|
||||||
val category: Category,
|
val category: Category,
|
||||||
val imageUrl: Uri,
|
|
||||||
val name: String,
|
val name: String,
|
||||||
val description: String,
|
val description: String,
|
||||||
|
val imageUrl: Uri,
|
||||||
|
val imageDensity: String,
|
||||||
val expirationTimestamp: Long,
|
val expirationTimestamp: Long,
|
||||||
val visible: Boolean
|
val visible: Boolean,
|
||||||
) : Parcelable, Key {
|
) : 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) {
|
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
|
||||||
messageDigest.update(id.toByteArray(Key.CHARSET))
|
messageDigest.update(id.toByteArray(Key.CHARSET))
|
||||||
|
messageDigest.update(imageUrl.toString().toByteArray(Key.CHARSET))
|
||||||
|
messageDigest.update(imageDensity.toByteArray(Key.CHARSET))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resolveDescription(shortName: String): String {
|
fun resolveDescription(shortName: String): String {
|
||||||
|
@ -130,6 +114,9 @@ data class Badge(
|
||||||
|
|
||||||
GlideApp.with(badge)
|
GlideApp.with(badge)
|
||||||
.load(model.badge)
|
.load(model.badge)
|
||||||
|
.downsample(DownsampleStrategy.NONE)
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
.transform(BadgeSpriteTransformation(BadgeSpriteTransformation.Size.XLARGE, model.badge.imageDensity, ThemeUtil.isDarkTheme(context)))
|
||||||
.into(target)
|
.into(target)
|
||||||
|
|
||||||
if (model.isSelected) {
|
if (model.isSelected) {
|
||||||
|
@ -170,7 +157,6 @@ data class Badge(
|
||||||
val drawable = resource.selectable(
|
val drawable = resource.selectable(
|
||||||
DimensionUnit.DP.toPixels(2.5f),
|
DimensionUnit.DP.toPixels(2.5f),
|
||||||
ContextCompat.getColor(view.context, R.color.signal_inverse_primary),
|
ContextCompat.getColor(view.context, R.color.signal_inverse_primary),
|
||||||
ContextCompat.getColor(view.context, R.color.signal_background_primary),
|
|
||||||
animator
|
animator
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -202,20 +188,34 @@ data class Badge(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object CREATOR : Parcelable.Creator<Badge> {
|
companion object {
|
||||||
private val SELECTION_CHANGED = Any()
|
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) {
|
fun register(mappingAdapter: MappingAdapter, onBadgeClicked: OnBadgeClicked) {
|
||||||
mappingAdapter.registerFactory(Model::class.java, MappingAdapter.LayoutFactory({ ViewHolder(it, onBadgeClicked) }, R.layout.badge_preference_view))
|
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))
|
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
|
package org.thoughtcrime.securesms.badges.models
|
||||||
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.view.View
|
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.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.AvatarImageView
|
||||||
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
import org.thoughtcrime.securesms.components.settings.PreferenceModel
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp
|
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.util.MappingAdapter
|
import org.thoughtcrime.securesms.util.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.MappingViewHolder
|
import org.thoughtcrime.securesms.util.MappingViewHolder
|
||||||
|
@ -35,40 +28,12 @@ object FeaturedBadgePreview {
|
||||||
class ViewHolder(itemView: View) : MappingViewHolder<Model>(itemView) {
|
class ViewHolder(itemView: View) : MappingViewHolder<Model>(itemView) {
|
||||||
|
|
||||||
private val avatar: AvatarImageView = itemView.findViewById(R.id.avatar)
|
private val avatar: AvatarImageView = itemView.findViewById(R.id.avatar)
|
||||||
private val badge: ImageView = itemView.findViewById(R.id.badge)
|
private val badge: BadgeImageView = itemView.findViewById(R.id.badge)
|
||||||
private val target: Target = Target(badge)
|
|
||||||
|
|
||||||
override fun bind(model: Model) {
|
override fun bind(model: Model) {
|
||||||
avatar.setRecipient(Recipient.self())
|
avatar.setRecipient(Recipient.self())
|
||||||
avatar.disableQuickContact()
|
avatar.disableQuickContact()
|
||||||
|
badge.setBadge(model.badge)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
package org.thoughtcrime.securesms.badges.models
|
package org.thoughtcrime.securesms.badges.models
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import org.thoughtcrime.securesms.R
|
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.MappingAdapter
|
||||||
import org.thoughtcrime.securesms.util.MappingModel
|
import org.thoughtcrime.securesms.util.MappingModel
|
||||||
import org.thoughtcrime.securesms.util.MappingViewHolder
|
import org.thoughtcrime.securesms.util.MappingViewHolder
|
||||||
|
@ -35,14 +34,12 @@ data class LargeBadge(
|
||||||
|
|
||||||
class ViewHolder(itemView: View) : MappingViewHolder<Model>(itemView) {
|
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 name: TextView = itemView.findViewById(R.id.name)
|
||||||
private val description: TextView = itemView.findViewById(R.id.description)
|
private val description: TextView = itemView.findViewById(R.id.description)
|
||||||
|
|
||||||
override fun bind(model: Model) {
|
override fun bind(model: Model) {
|
||||||
GlideApp.with(badge)
|
badge.setBadge(model.largeBadge.badge)
|
||||||
.load(model.largeBadge.badge)
|
|
||||||
.into(badge)
|
|
||||||
|
|
||||||
name.text = model.largeBadge.badge.name
|
name.text = model.largeBadge.badge.name
|
||||||
description.text = model.largeBadge.badge.resolveDescription(model.shortName)
|
description.text = model.largeBadge.badge.resolveDescription(model.shortName)
|
||||||
|
|
|
@ -79,7 +79,7 @@ class SelectFeaturedBadgeFragment : DSLSettingsFragment(
|
||||||
private fun getConfiguration(state: SelectFeaturedBadgeState): DSLConfiguration {
|
private fun getConfiguration(state: SelectFeaturedBadgeState): DSLConfiguration {
|
||||||
return configure {
|
return configure {
|
||||||
sectionHeaderPref(R.string.SelectFeaturedBadgeFragment__select_a_badge)
|
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 {
|
return configure {
|
||||||
sectionHeaderPref(R.string.BadgesOverviewFragment__my_badges)
|
sectionHeaderPref(R.string.BadgesOverviewFragment__my_badges)
|
||||||
|
|
||||||
displayBadges(state.allUnlockedBadges)
|
displayBadges(requireContext(), state.allUnlockedBadges)
|
||||||
|
|
||||||
switchPref(
|
switchPref(
|
||||||
title = DSLSettingsText.from(R.string.BadgesOverviewFragment__display_badges_on_profile),
|
title = DSLSettingsText.from(R.string.BadgesOverviewFragment__display_badges_on_profile),
|
||||||
|
|
|
@ -489,7 +489,7 @@ class ConversationSettingsFragment : DSLSettingsFragment(
|
||||||
|
|
||||||
sectionHeaderPref(R.string.ManageProfileFragment_badges)
|
sectionHeaderPref(R.string.ManageProfileFragment_badges)
|
||||||
|
|
||||||
displayBadges(state.recipient.badges)
|
displayBadges(requireContext(), state.recipient.badges)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recipientSettingsState.selfHasGroups) {
|
if (recipientSettingsState.selfHasGroups) {
|
||||||
|
|
|
@ -1373,9 +1373,10 @@ public class RecipientDatabase extends Database {
|
||||||
badges.add(new Badge(
|
badges.add(new Badge(
|
||||||
protoBadge.getId(),
|
protoBadge.getId(),
|
||||||
Badge.Category.Companion.fromCode(protoBadge.getCategory()),
|
Badge.Category.Companion.fromCode(protoBadge.getCategory()),
|
||||||
Uri.parse(protoBadge.getImageUrl()),
|
|
||||||
protoBadge.getName(),
|
protoBadge.getName(),
|
||||||
protoBadge.getDescription(),
|
protoBadge.getDescription(),
|
||||||
|
Uri.parse(protoBadge.getImageUrl()),
|
||||||
|
protoBadge.getImageDensity(),
|
||||||
protoBadge.getExpiration(),
|
protoBadge.getExpiration(),
|
||||||
protoBadge.getVisible()
|
protoBadge.getVisible()
|
||||||
));
|
));
|
||||||
|
@ -1691,7 +1692,8 @@ public class RecipientDatabase extends Database {
|
||||||
.setExpiration(badge.getExpirationTimestamp())
|
.setExpiration(badge.getExpirationTimestamp())
|
||||||
.setVisible(badge.getVisible())
|
.setVisible(badge.getVisible())
|
||||||
.setName(badge.getName())
|
.setName(badge.getName())
|
||||||
.setImageUrl(badge.getImageUrl().toString()));
|
.setImageUrl(badge.getImageUrl().toString())
|
||||||
|
.setImageDensity(badge.getImageDensity()));
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentValues values = new ContentValues(1);
|
ContentValues values = new ContentValues(1);
|
||||||
|
|
|
@ -29,7 +29,7 @@ import okhttp3.ConnectionSpec;
|
||||||
import okhttp3.OkHttpClient;
|
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> {
|
public class BadgeLoader implements ModelLoader<Badge, InputStream> {
|
||||||
|
|
||||||
|
@ -40,12 +40,12 @@ public class BadgeLoader implements ModelLoader<Badge, InputStream> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable LoadData<InputStream> buildLoadData(@NonNull Badge badge, int width, int height, @NonNull Options options) {
|
public @Nullable LoadData<InputStream> buildLoadData(@NonNull Badge request, int width, int height, @NonNull Options options) {
|
||||||
return new LoadData<>(badge, new OkHttpStreamFetcher(client, new GlideUrl(badge.getImageUrl().toString())));
|
return new LoadData<>(request, new OkHttpStreamFetcher(client, new GlideUrl(request.getImageUrl().toString())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handles(@NonNull Badge badge) {
|
public boolean handles(@NonNull Badge badgeSpriteSheetRequest) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import androidx.annotation.Nullable;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.signal.zkgroup.profiles.ProfileKey;
|
import org.signal.zkgroup.profiles.ProfileKey;
|
||||||
import org.signal.zkgroup.profiles.ProfileKeyCredential;
|
import org.signal.zkgroup.profiles.ProfileKeyCredential;
|
||||||
|
import org.thoughtcrime.securesms.BuildConfig;
|
||||||
import org.thoughtcrime.securesms.badges.models.Badge;
|
import org.thoughtcrime.securesms.badges.models.Badge;
|
||||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
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.profiles.ProfileName;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.util.ProfileUtil;
|
import org.thoughtcrime.securesms.util.ProfileUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.ScreenDensity;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
import org.whispersystems.libsignal.util.Pair;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
|
import org.whispersystems.signalservice.api.crypto.InvalidCiphertextException;
|
||||||
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
|
import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
|
||||||
|
@ -177,12 +180,14 @@ public class RefreshOwnProfileJob extends BaseJob {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Badge adaptFromServiceBadge(@NonNull SignalServiceProfile.Badge serviceBadge) {
|
private static Badge adaptFromServiceBadge(@NonNull SignalServiceProfile.Badge serviceBadge) {
|
||||||
|
Pair<Uri, String> uriAndDensity = RetrieveProfileJob.getBestBadgeImageUriForDevice(serviceBadge);
|
||||||
return new Badge(
|
return new Badge(
|
||||||
serviceBadge.getId(),
|
serviceBadge.getId(),
|
||||||
Badge.Category.Companion.fromCode(serviceBadge.getCategory()),
|
Badge.Category.Companion.fromCode(serviceBadge.getCategory()),
|
||||||
Uri.parse(serviceBadge.getImageUrl()),
|
|
||||||
serviceBadge.getName(),
|
serviceBadge.getName(),
|
||||||
serviceBadge.getDescription(),
|
serviceBadge.getDescription(),
|
||||||
|
uriAndDensity.first(),
|
||||||
|
uriAndDensity.second(),
|
||||||
getTimestamp(serviceBadge.getExpiration()),
|
getTimestamp(serviceBadge.getExpiration()),
|
||||||
serviceBadge.isVisible()
|
serviceBadge.isVisible()
|
||||||
);
|
);
|
||||||
|
|
|
@ -16,6 +16,7 @@ import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.signal.zkgroup.profiles.ProfileKey;
|
import org.signal.zkgroup.profiles.ProfileKey;
|
||||||
import org.signal.zkgroup.profiles.ProfileKeyCredential;
|
import org.signal.zkgroup.profiles.ProfileKeyCredential;
|
||||||
|
import org.thoughtcrime.securesms.BuildConfig;
|
||||||
import org.thoughtcrime.securesms.badges.models.Badge;
|
import org.thoughtcrime.securesms.badges.models.Badge;
|
||||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
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.recipients.RecipientUtil;
|
||||||
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
import org.thoughtcrime.securesms.transport.RetryLaterException;
|
||||||
import org.thoughtcrime.securesms.util.Base64;
|
import org.thoughtcrime.securesms.util.Base64;
|
||||||
import org.thoughtcrime.securesms.util.DateUtils;
|
|
||||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||||
import org.thoughtcrime.securesms.util.ProfileUtil;
|
import org.thoughtcrime.securesms.util.ProfileUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.ScreenDensity;
|
||||||
import org.thoughtcrime.securesms.util.SetUtil;
|
import org.thoughtcrime.securesms.util.SetUtil;
|
||||||
import org.thoughtcrime.securesms.util.Stopwatch;
|
import org.thoughtcrime.securesms.util.Stopwatch;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
@ -357,17 +358,45 @@ public class RetrieveProfileJob extends BaseJob {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Badge adaptFromServiceBadge(@NonNull SignalServiceProfile.Badge serviceBadge) {
|
private static Badge adaptFromServiceBadge(@NonNull SignalServiceProfile.Badge serviceBadge) {
|
||||||
|
Pair<Uri, String> uriAndDensity = RetrieveProfileJob.getBestBadgeImageUriForDevice(serviceBadge);
|
||||||
return new Badge(
|
return new Badge(
|
||||||
serviceBadge.getId(),
|
serviceBadge.getId(),
|
||||||
Badge.Category.Companion.fromCode(serviceBadge.getCategory()),
|
Badge.Category.Companion.fromCode(serviceBadge.getCategory()),
|
||||||
Uri.parse(serviceBadge.getImageUrl()),
|
|
||||||
serviceBadge.getName(),
|
serviceBadge.getName(),
|
||||||
serviceBadge.getDescription(),
|
serviceBadge.getDescription(),
|
||||||
|
uriAndDensity.first(),
|
||||||
|
uriAndDensity.second(),
|
||||||
0L,
|
0L,
|
||||||
true
|
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,
|
private void setProfileKeyCredential(@NonNull Recipient recipient,
|
||||||
@NonNull ProfileKey recipientProfileKey,
|
@NonNull ProfileKey recipientProfileKey,
|
||||||
@NonNull ProfileKeyCredential credential)
|
@NonNull ProfileKeyCredential credential)
|
||||||
|
|
|
@ -5,6 +5,8 @@ import android.util.DisplayMetrics;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -51,6 +53,16 @@ public final class ScreenDensity {
|
||||||
return new ScreenDensity(bucket, density);
|
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() {
|
public String getBucket() {
|
||||||
return bucket;
|
return bucket;
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,13 +25,14 @@ message ReactionList {
|
||||||
|
|
||||||
message BadgeList {
|
message BadgeList {
|
||||||
message Badge {
|
message Badge {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
string category = 2;
|
string category = 2;
|
||||||
string name = 3;
|
string name = 3;
|
||||||
string description = 4;
|
string description = 4;
|
||||||
string imageUrl = 5;
|
string imageUrl = 5;
|
||||||
uint64 expiration = 6;
|
uint64 expiration = 6;
|
||||||
bool visible = 7;
|
bool visible = 7;
|
||||||
|
string imageDensity = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
repeated Badge badges = 1;
|
repeated Badge badges = 1;
|
||||||
|
|
|
@ -21,14 +21,13 @@
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.badges.BadgeImageView
|
<org.thoughtcrime.securesms.badges.BadgeImageView
|
||||||
android:id="@+id/badge"
|
android:id="@+id/badge"
|
||||||
android:layout_width="26dp"
|
android:layout_width="24dp"
|
||||||
android:layout_height="26dp"
|
android:layout_height="24dp"
|
||||||
android:layout_marginStart="39dp"
|
android:layout_marginStart="40dp"
|
||||||
android:layout_marginTop="39dp"
|
android:layout_marginTop="40dp"
|
||||||
android:contentDescription="@string/ImageView__badge"
|
android:contentDescription="@string/ImageView__badge"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:badge_outline_color="@color/signal_background_primary"
|
app:badge_size="medium"
|
||||||
app:badge_outline_width="1dp"
|
|
||||||
app:layout_constraintStart_toStartOf="@id/icon"
|
app:layout_constraintStart_toStartOf="@id/icon"
|
||||||
app:layout_constraintTop_toTopOf="@id/icon"
|
app:layout_constraintTop_toTopOf="@id/icon"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
|
@ -25,14 +25,13 @@
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.badges.BadgeImageView
|
<org.thoughtcrime.securesms.badges.BadgeImageView
|
||||||
android:id="@+id/contact_badge"
|
android:id="@+id/contact_badge"
|
||||||
android:layout_width="@dimen/badge_size_small"
|
android:layout_width="16dp"
|
||||||
android:layout_height="@dimen/badge_size_small"
|
android:layout_height="16dp"
|
||||||
android:layout_marginStart="23dp"
|
android:layout_marginStart="24dp"
|
||||||
android:layout_marginTop="23dp"
|
android:layout_marginTop="24dp"
|
||||||
android:contentDescription="@string/ImageView__badge"
|
android:contentDescription="@string/ImageView__badge"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:badge_outline_color="@color/signal_background_primary"
|
app:badge_size="small"
|
||||||
app:badge_outline_width="1dp"
|
|
||||||
app:layout_constraintStart_toStartOf="@id/contact_photo_image"
|
app:layout_constraintStart_toStartOf="@id/contact_photo_image"
|
||||||
app:layout_constraintTop_toTopOf="@id/contact_photo_image"
|
app:layout_constraintTop_toTopOf="@id/contact_photo_image"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
|
@ -18,14 +18,13 @@
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.badges.BadgeImageView
|
<org.thoughtcrime.securesms.badges.BadgeImageView
|
||||||
android:id="@+id/message_request_badge"
|
android:id="@+id/message_request_badge"
|
||||||
android:layout_width="34dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="34dp"
|
android:layout_height="32dp"
|
||||||
android:layout_marginStart="46dp"
|
android:layout_marginStart="47dp"
|
||||||
android:layout_marginTop="47dp"
|
android:layout_marginTop="48dp"
|
||||||
android:contentDescription="@string/ImageView__badge"
|
android:contentDescription="@string/ImageView__badge"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:badge_outline_color="@color/signal_background_primary"
|
app:badge_size="large"
|
||||||
app:badge_outline_width="1dp"
|
|
||||||
app:layout_constraintStart_toStartOf="@id/message_request_avatar"
|
app:layout_constraintStart_toStartOf="@id/message_request_avatar"
|
||||||
app:layout_constraintTop_toTopOf="@id/message_request_avatar"
|
app:layout_constraintTop_toTopOf="@id/message_request_avatar"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
|
@ -191,14 +191,13 @@
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.badges.BadgeImageView
|
<org.thoughtcrime.securesms.badges.BadgeImageView
|
||||||
android:id="@+id/conversation_list_item_badge"
|
android:id="@+id/conversation_list_item_badge"
|
||||||
android:layout_width="26dp"
|
android:layout_width="24dp"
|
||||||
android:layout_height="26dp"
|
android:layout_height="24dp"
|
||||||
android:layout_marginStart="@dimen/conversation_list_badge_offset"
|
android:layout_marginStart="26dp"
|
||||||
android:layout_marginTop="@dimen/conversation_list_badge_offset"
|
android:layout_marginTop="26dp"
|
||||||
android:contentDescription="@string/ImageView__badge"
|
android:contentDescription="@string/ImageView__badge"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:badge_outline_color="@color/signal_background_primary"
|
app:badge_size="medium"
|
||||||
app:badge_outline_width="1dp"
|
|
||||||
app:layout_constraintStart_toStartOf="@id/conversation_list_item_avatar"
|
app:layout_constraintStart_toStartOf="@id/conversation_list_item_avatar"
|
||||||
app:layout_constraintTop_toTopOf="@id/conversation_list_item_avatar"
|
app:layout_constraintTop_toTopOf="@id/conversation_list_item_avatar"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
|
@ -23,14 +23,13 @@
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.badges.BadgeImageView
|
<org.thoughtcrime.securesms.badges.BadgeImageView
|
||||||
android:id="@+id/bio_preference_badge"
|
android:id="@+id/bio_preference_badge"
|
||||||
android:layout_width="34dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="34dp"
|
android:layout_height="32dp"
|
||||||
android:layout_marginStart="46dp"
|
android:layout_marginStart="47dp"
|
||||||
android:layout_marginTop="47dp"
|
android:layout_marginTop="48dp"
|
||||||
android:contentDescription="@string/ImageView__badge"
|
android:contentDescription="@string/ImageView__badge"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:badge_outline_color="@color/signal_background_primary"
|
app:badge_size="large"
|
||||||
app:badge_outline_width="1dp"
|
|
||||||
app:layout_constraintStart_toStartOf="@id/bio_preference_avatar"
|
app:layout_constraintStart_toStartOf="@id/bio_preference_avatar"
|
||||||
app:layout_constraintTop_toTopOf="@id/bio_preference_avatar"
|
app:layout_constraintTop_toTopOf="@id/bio_preference_avatar"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
|
@ -31,16 +31,15 @@
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.badges.BadgeImageView
|
<org.thoughtcrime.securesms.badges.BadgeImageView
|
||||||
android:id="@+id/badge"
|
android:id="@+id/badge"
|
||||||
android:layout_width="@dimen/badge_size_small"
|
android:layout_width="16dp"
|
||||||
android:layout_height="@dimen/badge_size_small"
|
android:layout_height="16dp"
|
||||||
android:layout_alignStart="@id/contact_photo_image"
|
android:layout_alignStart="@id/contact_photo_image"
|
||||||
android:layout_alignTop="@id/contact_photo_image"
|
android:layout_alignTop="@id/contact_photo_image"
|
||||||
android:layout_marginStart="21dp"
|
android:layout_marginStart="22dp"
|
||||||
android:layout_marginTop="21dp"
|
android:layout_marginTop="22dp"
|
||||||
android:contentDescription="@string/ImageView__badge"
|
android:contentDescription="@string/ImageView__badge"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:badge_outline_color="@color/signal_background_primary"
|
app:badge_size="small"
|
||||||
app:badge_outline_width="1dp"
|
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
||||||
|
|
|
@ -22,11 +22,12 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<ImageView
|
<org.thoughtcrime.securesms.badges.BadgeImageView
|
||||||
android:id="@+id/badge"
|
android:id="@+id/badge"
|
||||||
android:layout_width="33dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="33dp"
|
android:layout_height="32dp"
|
||||||
android:contentDescription="@string/BadgesOverviewFragment__featured_badge"
|
android:contentDescription="@string/BadgesOverviewFragment__featured_badge"
|
||||||
|
app:badge_size="large"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/avatar"
|
app:layout_constraintBottom_toBottomOf="@id/avatar"
|
||||||
app:layout_constraintEnd_toEndOf="@id/avatar" />
|
app:layout_constraintEnd_toEndOf="@id/avatar" />
|
||||||
|
|
||||||
|
|
|
@ -23,14 +23,13 @@
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.badges.BadgeImageView
|
<org.thoughtcrime.securesms.badges.BadgeImageView
|
||||||
android:id="@+id/rbs_badge"
|
android:id="@+id/rbs_badge"
|
||||||
android:layout_width="34dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="34dp"
|
android:layout_height="32dp"
|
||||||
android:layout_marginStart="46dp"
|
android:layout_marginStart="47dp"
|
||||||
android:layout_marginTop="47dp"
|
android:layout_marginTop="48dp"
|
||||||
android:contentDescription="@string/ImageView__badge"
|
android:contentDescription="@string/ImageView__badge"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:badge_outline_color="@color/signal_background_primary"
|
app:badge_size="large"
|
||||||
app:badge_outline_width="1dp"
|
|
||||||
app:layout_constraintStart_toStartOf="@id/rbs_recipient_avatar"
|
app:layout_constraintStart_toStartOf="@id/rbs_recipient_avatar"
|
||||||
app:layout_constraintTop_toTopOf="@id/rbs_recipient_avatar"
|
app:layout_constraintTop_toTopOf="@id/rbs_recipient_avatar"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
|
@ -3,14 +3,16 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<ImageView
|
<org.thoughtcrime.securesms.badges.BadgeImageView
|
||||||
android:id="@+id/badge"
|
android:id="@+id/badge"
|
||||||
android:layout_width="200dp"
|
android:layout_width="200dp"
|
||||||
android:layout_height="200dp"
|
android:layout_height="200dp"
|
||||||
android:layout_gravity="center_horizontal"
|
android:layout_gravity="center_horizontal"
|
||||||
android:contentDescription="@string/BadgesOverviewFragment__featured_badge"
|
android:contentDescription="@string/BadgesOverviewFragment__featured_badge"
|
||||||
|
app:badge_size="xlarge"
|
||||||
tools:src="@drawable/test_gradient" />
|
tools:src="@drawable/test_gradient" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
|
|
@ -33,4 +33,6 @@
|
||||||
<dimen name="toolbar_avatar_margin">34dp</dimen>
|
<dimen name="toolbar_avatar_margin">34dp</dimen>
|
||||||
|
|
||||||
<dimen name="verify_identity_vertical_margin">32dp</dimen>
|
<dimen name="verify_identity_vertical_margin">32dp</dimen>
|
||||||
|
|
||||||
|
<integer name="badge_columns">4</integer>
|
||||||
</resources>
|
</resources>
|
|
@ -2,4 +2,6 @@
|
||||||
<resources>
|
<resources>
|
||||||
<dimen name="media_bubble_max_width">350dp</dimen>
|
<dimen name="media_bubble_max_width">350dp</dimen>
|
||||||
<dimen name="media_bubble_max_height">300dp</dimen>
|
<dimen name="media_bubble_max_height">300dp</dimen>
|
||||||
|
|
||||||
|
<integer name="badge_columns">5</integer>
|
||||||
</resources>
|
</resources>
|
|
@ -328,7 +328,11 @@
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
||||||
<declare-styleable name="BadgeImageView">
|
<declare-styleable name="BadgeImageView">
|
||||||
<attr name="badge_outline_width" format="dimension" />
|
<attr name="badge_size" format="enum">
|
||||||
<attr name="badge_outline_color" format="color" />
|
<enum name="small" value="0" />
|
||||||
|
<enum name="medium" value="1" />
|
||||||
|
<enum name="large" value="2" />
|
||||||
|
<enum name="xlarge" value="3" />
|
||||||
|
</attr>
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -205,9 +205,8 @@
|
||||||
<dimen name="toolbar_avatar_size">28dp</dimen>
|
<dimen name="toolbar_avatar_size">28dp</dimen>
|
||||||
<dimen name="toolbar_avatar_margin">26dp</dimen>
|
<dimen name="toolbar_avatar_margin">26dp</dimen>
|
||||||
<dimen name="conversation_list_avatar_size">48dp</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="verify_identity_vertical_margin">16dp</dimen>
|
||||||
|
|
||||||
<dimen name="badge_size_small">18dp</dimen>
|
<integer name="badge_columns">3</integer>
|
||||||
</resources>
|
</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
|
@JsonProperty
|
||||||
private String category;
|
private String category;
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
private String imageUrl;
|
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@JsonProperty
|
@JsonProperty
|
||||||
private String description;
|
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
|
@JsonProperty
|
||||||
private BigDecimal expiration;
|
private BigDecimal expiration;
|
||||||
|
|
||||||
|
@ -159,12 +174,32 @@ public class SignalServiceProfile {
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal getExpiration() {
|
public String getLdpiUri() {
|
||||||
return expiration;
|
return ldpi;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getImageUrl() {
|
public String getMdpiUri() {
|
||||||
return imageUrl;
|
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() {
|
public boolean isVisible() {
|
||||||
|
|
Ładowanie…
Reference in New Issue