Improve organization of glide packages.

Generic image processing classes were scattered alongside Signal-specific Glide code across multiple packages: `org.signal.glide`, `org.thoughtcrime.securesms.glide` and `org.thoughtcrime.securesms.mms`.

This change provides a clearer separation of concerns:
- `org.signal.glide` contains generic image loading components
- `org.thoughtcrime.securesms.glide` contains Signal-specific Glide integrations
- Feature-specific loaders are moved to their respective domain packages (e.g. `.badges`, `.contacts`)
This commit is contained in:
jeffrey-signal
2025-08-15 12:44:42 -04:00
committed by Jeffrey Starke
parent cc43add7af
commit 47508495ed
48 changed files with 220 additions and 179 deletions

View File

@@ -8,15 +8,14 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.RequestManager
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.badges.glide.BadgeSpriteTransformation
import org.thoughtcrime.securesms.badges.load.BadgeSpriteTransformation
import org.thoughtcrime.securesms.badges.load.GiftBadgeModel
import org.thoughtcrime.securesms.badges.models.Badge
import org.thoughtcrime.securesms.components.settings.app.subscription.BadgeImageSize
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
import org.thoughtcrime.securesms.glide.GiftBadgeModel
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.ScreenDensity
import org.thoughtcrime.securesms.util.ThemeUtil
import org.thoughtcrime.securesms.util.visible
class BadgeImageView @JvmOverloads constructor(
context: Context,

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.badges.load;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory;
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
import org.thoughtcrime.securesms.badges.models.Badge;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.glide.OkHttpStreamFetcher;
import java.io.InputStream;
import okhttp3.OkHttpClient;
/**
* 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> {
private final OkHttpClient client;
private BadgeLoader(OkHttpClient client) {
this.client = client;
}
@Override
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 badgeSpriteSheetRequest) {
return true;
}
public static Factory createFactory() {
return new Factory(AppDependencies.getSignalOkHttpClient());
}
public static class Factory implements ModelLoaderFactory<Badge, InputStream> {
private final OkHttpClient client;
private Factory(@NonNull OkHttpClient client) {
this.client = client;
}
@Override
public @NonNull ModelLoader<Badge, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
return new BadgeLoader(client);
}
@Override
public void teardown() {
}
}
}

View File

@@ -1,4 +1,9 @@
package org.thoughtcrime.securesms.badges.glide
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.badges.load
import android.graphics.Bitmap
import android.graphics.Canvas

View File

@@ -0,0 +1,103 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.badges.load
import com.bumptech.glide.Priority
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.Key
import com.bumptech.glide.load.Options
import com.bumptech.glide.load.data.DataFetcher
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.model.ModelLoader
import com.bumptech.glide.load.model.ModelLoaderFactory
import com.bumptech.glide.load.model.MultiModelLoaderFactory
import okhttp3.OkHttpClient
import org.signal.libsignal.zkgroup.receipts.ReceiptCredentialPresentation
import org.thoughtcrime.securesms.components.settings.app.subscription.getBadge
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.glide.OkHttpStreamFetcher
import java.io.InputStream
import java.security.MessageDigest
import java.util.Locale
/**
* Glide Model allowing the direct loading of a GiftBadge.
*
* This model will first resolve a GiftBadge into a Badge, and then it will delegate to the Badge loader.
*/
data class GiftBadgeModel(val giftBadge: GiftBadge) : Key {
class Loader(val client: OkHttpClient) : ModelLoader<GiftBadgeModel, InputStream> {
override fun buildLoadData(model: GiftBadgeModel, width: Int, height: Int, options: Options): ModelLoader.LoadData<InputStream>? {
return ModelLoader.LoadData(model, Fetcher(client, model))
}
override fun handles(model: GiftBadgeModel): Boolean = true
}
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
messageDigest.update(giftBadge.encode())
}
class Fetcher(
private val client: OkHttpClient,
private val giftBadge: GiftBadgeModel
) : DataFetcher<InputStream> {
private var okHttpStreamFetcher: OkHttpStreamFetcher? = null
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
try {
val receiptCredentialPresentation = ReceiptCredentialPresentation(giftBadge.giftBadge.redemptionToken.toByteArray())
val giftBadgeResponse = AppDependencies.donationsService.getDonationsConfiguration(Locale.getDefault())
if (giftBadgeResponse.result.isPresent) {
val badge = giftBadgeResponse.result.get().getBadge(receiptCredentialPresentation.receiptLevel.toInt())
okHttpStreamFetcher = OkHttpStreamFetcher(client, GlideUrl(badge.imageUrl.toString()))
okHttpStreamFetcher?.loadData(priority, callback)
} else if (giftBadgeResponse.applicationError.isPresent) {
callback.onLoadFailed(Exception(giftBadgeResponse.applicationError.get()))
} else if (giftBadgeResponse.executionError.isPresent) {
callback.onLoadFailed(Exception(giftBadgeResponse.executionError.get()))
} else {
callback.onLoadFailed(Exception("No result or error in service response."))
}
} catch (e: Exception) {
callback.onLoadFailed(e)
}
}
override fun cleanup() {
okHttpStreamFetcher?.cleanup()
}
override fun cancel() {
okHttpStreamFetcher?.cancel()
}
override fun getDataClass(): Class<InputStream> {
return InputStream::class.java
}
override fun getDataSource(): DataSource {
return DataSource.REMOTE
}
}
class Factory(private val client: OkHttpClient) : ModelLoaderFactory<GiftBadgeModel, InputStream> {
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<GiftBadgeModel, InputStream> {
return Loader(client)
}
override fun teardown() {}
}
companion object {
@JvmStatic
fun createFactory(): Factory {
return Factory(AppDependencies.signalOkHttpClient)
}
}
}

View File

@@ -13,7 +13,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
import kotlinx.parcelize.Parcelize
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.badges.glide.BadgeSpriteTransformation
import org.thoughtcrime.securesms.badges.load.BadgeSpriteTransformation
import org.thoughtcrime.securesms.components.settings.PreferenceModel
import org.thoughtcrime.securesms.util.ThemeUtil
import org.thoughtcrime.securesms.util.adapter.mapping.LayoutFactory