Moves Coil's image loader choice to a subscription on account.
This commit is contained in:
@@ -163,10 +163,11 @@ class Amethyst : Application() {
|
||||
|
||||
fun contentResolverFn(): ContentResolver = contentResolver
|
||||
|
||||
fun setImageLoader(shouldUseTor: Boolean?) =
|
||||
ImageLoaderSetup.setup(this, diskCache, memoryCache) {
|
||||
shouldUseTor?.let { okHttpClients.getHttpClient(it) } ?: okHttpClients.getHttpClient(false)
|
||||
fun setImageLoader(shouldUseTor: (String) -> Boolean?) {
|
||||
ImageLoaderSetup.setup(this, diskCache, memoryCache) { url ->
|
||||
shouldUseTor(url)?.let { okHttpClients.getHttpClient(it) } ?: okHttpClients.getHttpClient(false)
|
||||
}
|
||||
}
|
||||
|
||||
fun encryptedStorage(npub: String? = null): EncryptedSharedPreferences = EncryptedStorage.preferences(instance, npub)
|
||||
|
||||
|
||||
@@ -61,8 +61,6 @@ class ServiceManager(
|
||||
|
||||
val myAccount = account
|
||||
|
||||
Amethyst.instance.setImageLoader(myAccount?.shouldUseTorForImageDownload())
|
||||
|
||||
if (myAccount != null) {
|
||||
val relaySet = myAccount.connectToRelaysWithProxy.value
|
||||
client.reconnect(relaySet)
|
||||
|
||||
@@ -618,6 +618,7 @@ class Account(
|
||||
listName,
|
||||
location = Amethyst.instance.locationManager.geohashStateFlow,
|
||||
)
|
||||
|
||||
else -> {
|
||||
val note = LocalCache.checkGetOrCreateAddressableNote(listName)
|
||||
if (note != null) {
|
||||
@@ -1126,7 +1127,7 @@ class Account(
|
||||
|
||||
class EmojiMedia(
|
||||
val code: String,
|
||||
val url: MediaUrlImage,
|
||||
val link: MediaUrlImage,
|
||||
)
|
||||
|
||||
fun getEmojiPackSelection(): EmojiPackSelectionEvent? = getEmojiPackSelectionNote().event as? EmojiPackSelectionEvent
|
||||
@@ -1173,7 +1174,7 @@ class Account(
|
||||
null
|
||||
}
|
||||
}.flatten()
|
||||
.distinctBy { it.url }
|
||||
.distinctBy { it.link }
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val myEmojis by lazy {
|
||||
@@ -3646,11 +3647,33 @@ class Account(
|
||||
|
||||
fun markDonatedInThisVersion() = settings.markDonatedInThisVersion(BuildConfig.VERSION_NAME)
|
||||
|
||||
fun shouldUseTorForImageDownload() =
|
||||
when (settings.torSettings.torType.value) {
|
||||
TorType.OFF -> false
|
||||
TorType.INTERNAL -> settings.torSettings.imagesViaTor.value
|
||||
TorType.EXTERNAL -> settings.torSettings.imagesViaTor.value
|
||||
fun shouldUseTorForImageDownload(url: String) =
|
||||
shouldUseTorFor(
|
||||
url,
|
||||
settings.torSettings.torType.value,
|
||||
settings.torSettings.imagesViaTor.value,
|
||||
)
|
||||
|
||||
fun shouldUseTorFor(
|
||||
url: String,
|
||||
torType: TorType,
|
||||
imagesViaTor: Boolean,
|
||||
) = when (torType) {
|
||||
TorType.OFF -> false
|
||||
TorType.INTERNAL -> shouldUseTor(url, imagesViaTor)
|
||||
TorType.EXTERNAL -> shouldUseTor(url, imagesViaTor)
|
||||
}
|
||||
|
||||
private fun shouldUseTor(
|
||||
normalizedUrl: String,
|
||||
final: Boolean,
|
||||
): Boolean =
|
||||
if (isLocalHost(normalizedUrl)) {
|
||||
false
|
||||
} else if (isOnionUrl(normalizedUrl)) {
|
||||
true
|
||||
} else {
|
||||
final
|
||||
}
|
||||
|
||||
fun shouldUseTorForVideoDownload() =
|
||||
|
||||
+87
-10
@@ -24,12 +24,19 @@ import android.app.Application
|
||||
import android.os.Build
|
||||
import coil3.ImageLoader
|
||||
import coil3.SingletonImageLoader
|
||||
import coil3.Uri
|
||||
import coil3.annotation.DelicateCoilApi
|
||||
import coil3.annotation.ExperimentalCoilApi
|
||||
import coil3.disk.DiskCache
|
||||
import coil3.fetch.Fetcher
|
||||
import coil3.gif.AnimatedImageDecoder
|
||||
import coil3.gif.GifDecoder
|
||||
import coil3.memory.MemoryCache
|
||||
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
|
||||
import coil3.network.CacheStrategy
|
||||
import coil3.network.ConnectivityChecker
|
||||
import coil3.network.NetworkFetcher
|
||||
import coil3.network.okhttp.asNetworkClient
|
||||
import coil3.request.Options
|
||||
import coil3.size.Precision
|
||||
import coil3.svg.SvgDecoder
|
||||
import coil3.util.DebugLogger
|
||||
@@ -38,12 +45,22 @@ import okhttp3.Call
|
||||
|
||||
class ImageLoaderSetup {
|
||||
companion object {
|
||||
val gifFactory =
|
||||
if (Build.VERSION.SDK_INT >= 28) {
|
||||
AnimatedImageDecoder.Factory()
|
||||
} else {
|
||||
GifDecoder.Factory()
|
||||
}
|
||||
val svgFactory = SvgDecoder.Factory()
|
||||
|
||||
val debugLogger = if (isDebug) DebugLogger() else null
|
||||
|
||||
@OptIn(DelicateCoilApi::class)
|
||||
fun setup(
|
||||
app: Application,
|
||||
diskCache: DiskCache,
|
||||
memoryCache: MemoryCache,
|
||||
callFactory: () -> Call.Factory,
|
||||
callFactory: (url: String) -> Call.Factory,
|
||||
) {
|
||||
SingletonImageLoader.setUnsafe(
|
||||
ImageLoader
|
||||
@@ -51,21 +68,81 @@ class ImageLoaderSetup {
|
||||
.diskCache { diskCache }
|
||||
.memoryCache { memoryCache }
|
||||
.precision(Precision.INEXACT)
|
||||
.logger(if (isDebug) DebugLogger() else null)
|
||||
.logger(debugLogger)
|
||||
.components {
|
||||
if (Build.VERSION.SDK_INT >= 28) {
|
||||
add(AnimatedImageDecoder.Factory())
|
||||
} else {
|
||||
add(GifDecoder.Factory())
|
||||
}
|
||||
add(SvgDecoder.Factory())
|
||||
add(gifFactory)
|
||||
add(svgFactory)
|
||||
add(Base64Fetcher.Factory)
|
||||
add(BlurHashFetcher.Factory)
|
||||
add(Base64Fetcher.BKeyer)
|
||||
add(BlurHashFetcher.BKeyer)
|
||||
add(OkHttpNetworkFetcherFactory(callFactory))
|
||||
add(OkHttpFactory(callFactory))
|
||||
}.build(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copied from Coil to allow networkClient to be a function of the url.
|
||||
* So that Tor and non Tor clients can be used.
|
||||
*/
|
||||
@OptIn(ExperimentalCoilApi::class)
|
||||
class OkHttpFactory(
|
||||
val networkClient: (url: String) -> Call.Factory,
|
||||
) : Fetcher.Factory<Uri> {
|
||||
private val cacheStrategyLazy = lazy { CacheStrategy.DEFAULT }
|
||||
private val connectivityCheckerLazy = singleParameterLazy(::ConnectivityChecker)
|
||||
|
||||
override fun create(
|
||||
data: Uri,
|
||||
options: Options,
|
||||
imageLoader: ImageLoader,
|
||||
): Fetcher? {
|
||||
if (!isApplicable(data)) return null
|
||||
|
||||
val url = data.toString()
|
||||
|
||||
return NetworkFetcher(
|
||||
url = url,
|
||||
options = options,
|
||||
networkClient = lazy { networkClient(url).asNetworkClient() },
|
||||
diskCache = lazy { imageLoader.diskCache },
|
||||
cacheStrategy = cacheStrategyLazy,
|
||||
connectivityChecker = connectivityCheckerLazy.get(options.context),
|
||||
)
|
||||
}
|
||||
|
||||
private fun isApplicable(data: Uri): Boolean = data.scheme == "http" || data.scheme == "https"
|
||||
}
|
||||
|
||||
internal fun <P, T> singleParameterLazy(initializer: (P) -> T) = SingleParameterLazy(initializer)
|
||||
|
||||
internal class SingleParameterLazy<P, T>(
|
||||
initializer: (P) -> T,
|
||||
) : Any() {
|
||||
private var initializer: ((P) -> T)? = initializer
|
||||
private var value: Any? = UNINITIALIZED
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun get(parameter: P): T {
|
||||
val value1 = value
|
||||
if (value1 !== UNINITIALIZED) {
|
||||
return value1 as T
|
||||
}
|
||||
|
||||
return synchronized(this) {
|
||||
val value2 = value
|
||||
if (value2 !== UNINITIALIZED) {
|
||||
value2 as T
|
||||
} else {
|
||||
val newValue = initializer!!(parameter)
|
||||
value = newValue
|
||||
initializer = null
|
||||
newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object UNINITIALIZED
|
||||
|
||||
@@ -879,7 +879,7 @@ open class NewPostViewModel :
|
||||
): List<EmojiUrlTag> {
|
||||
if (myEmojiSet == null) return emptyList()
|
||||
return CustomEmoji.findAllEmojiCodes(message).mapNotNull { possibleEmoji ->
|
||||
myEmojiSet.firstOrNull { it.code == possibleEmoji }?.let { EmojiUrlTag(it.code, it.url.url) }
|
||||
myEmojiSet.firstOrNull { it.code == possibleEmoji }?.let { EmojiUrlTag(it.code, it.link.url) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1078,12 +1078,12 @@ open class NewPostViewModel :
|
||||
}
|
||||
|
||||
open fun autocompleteWithEmojiUrl(item: Account.EmojiMedia) {
|
||||
val wordToInsert = item.url.url + " "
|
||||
val wordToInsert = item.link.url + " "
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
iMetaAttachments.downloadAndPrepare(
|
||||
item.url.url,
|
||||
{ Amethyst.instance.okHttpClients.getHttpClient(accountViewModel?.account?.shouldUseTorForImageDownload() ?: false) },
|
||||
item.link.url,
|
||||
{ Amethyst.instance.okHttpClients.getHttpClient(accountViewModel?.account?.shouldUseTorForImageDownload(item.link.url) ?: false) },
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ fun ShowEmojiSuggestionList(
|
||||
horizontalArrangement = spacedBy(Size10dp),
|
||||
) {
|
||||
Box(Size40Modifier) {
|
||||
UrlImageView(it.url, accountViewModel)
|
||||
UrlImageView(it.link, accountViewModel)
|
||||
}
|
||||
Text(it.code, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f))
|
||||
Box(Size40Modifier, contentAlignment = Alignment.Center) {
|
||||
|
||||
+1
-1
@@ -1522,7 +1522,7 @@ class AccountViewModel(
|
||||
|
||||
fun okHttpClientForNip96(url: String): OkHttpClient = Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForNIP96(url))
|
||||
|
||||
fun okHttpClientForImage(url: String): OkHttpClient = Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForImageDownload())
|
||||
fun okHttpClientForImage(url: String): OkHttpClient = Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForImageDownload(url))
|
||||
|
||||
fun okHttpClientForVideo(url: String): OkHttpClient = Amethyst.instance.okHttpClients.getHttpClient(account.shouldUseTorForVideoDownload(url))
|
||||
|
||||
|
||||
+12
-1
@@ -84,9 +84,13 @@ fun LoggedInPage(
|
||||
// Adds this account to the authentication procedures for relays.
|
||||
RelayAuthSubscription(accountViewModel)
|
||||
|
||||
// Loads account information from Relays.
|
||||
// Sets up Coil's Image Loader
|
||||
ObserveImageLoadingTor(accountViewModel)
|
||||
|
||||
// Loads account information + DMs and Notifications from Relays.
|
||||
AccountFilterAssemblerSubscription(accountViewModel)
|
||||
|
||||
// Pre-loads each of the main screens.
|
||||
HomeFilterAssemblerSubscription(accountViewModel)
|
||||
ChatroomListFilterAssemblerSubscription(accountViewModel)
|
||||
VideoFilterAssemblerSubscription(accountViewModel)
|
||||
@@ -124,6 +128,13 @@ fun ObserveAntiSpamFilterSettings(accountViewModel: AccountViewModel) {
|
||||
Amethyst.instance.cache.antiSpam.active = isSpamActive
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ObserveImageLoadingTor(accountViewModel: AccountViewModel) {
|
||||
LaunchedEffect(Unit) {
|
||||
Amethyst.instance.setImageLoader(accountViewModel.account::shouldUseTorForImageDownload)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ManageRelayServices(
|
||||
accountViewModel: AccountViewModel,
|
||||
|
||||
+4
-4
@@ -502,7 +502,7 @@ class ChatNewMessageViewModel :
|
||||
): List<EmojiUrlTag> {
|
||||
if (myEmojiSet == null) return emptyList()
|
||||
return CustomEmoji.findAllEmojiCodes(message).mapNotNull { possibleEmoji ->
|
||||
myEmojiSet.firstOrNull { it.code == possibleEmoji }?.let { EmojiUrlTag(it.code, it.url.url) }
|
||||
myEmojiSet.firstOrNull { it.code == possibleEmoji }?.let { EmojiUrlTag(it.code, it.link.url) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -647,12 +647,12 @@ class ChatNewMessageViewModel :
|
||||
}
|
||||
|
||||
fun autocompleteWithEmojiUrl(item: Account.EmojiMedia) {
|
||||
val wordToInsert = item.url.url + " "
|
||||
val wordToInsert = item.link.url + " "
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
iMetaAttachments.downloadAndPrepare(
|
||||
item.url.url,
|
||||
{ Amethyst.instance.okHttpClients.getHttpClient(accountViewModel?.account?.shouldUseTorForImageDownload() ?: false) },
|
||||
item.link.url,
|
||||
{ Amethyst.instance.okHttpClients.getHttpClient(accountViewModel?.account?.shouldUseTorForImageDownload(item.link.url) ?: false) },
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -464,7 +464,7 @@ open class ChannelNewMessageViewModel :
|
||||
): List<EmojiUrlTag> {
|
||||
if (myEmojiSet == null) return emptyList()
|
||||
return CustomEmoji.findAllEmojiCodes(message).mapNotNull { possibleEmoji ->
|
||||
myEmojiSet.firstOrNull { it.code == possibleEmoji }?.let { EmojiUrlTag(it.code, it.url.url) }
|
||||
myEmojiSet.firstOrNull { it.code == possibleEmoji }?.let { EmojiUrlTag(it.code, it.link.url) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -561,12 +561,12 @@ open class ChannelNewMessageViewModel :
|
||||
}
|
||||
|
||||
open fun autocompleteWithEmojiUrl(item: Account.EmojiMedia) {
|
||||
val wordToInsert = item.url.url + " "
|
||||
val wordToInsert = item.link.url + " "
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
iMetaAttachments.downloadAndPrepare(
|
||||
item.url.url,
|
||||
{ Amethyst.instance.okHttpClients.getHttpClient(accountViewModel?.account?.shouldUseTorForImageDownload() ?: false) },
|
||||
item.link.url,
|
||||
{ Amethyst.instance.okHttpClients.getHttpClient(accountViewModel?.account?.shouldUseTorForImageDownload(item.link.url) ?: false) },
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -374,7 +374,7 @@ open class NewProductViewModel :
|
||||
): List<EmojiUrlTag> {
|
||||
if (myEmojiSet == null) return emptyList()
|
||||
return CustomEmoji.findAllEmojiCodes(message).mapNotNull { possibleEmoji ->
|
||||
myEmojiSet.firstOrNull { it.code == possibleEmoji }?.let { EmojiUrlTag(it.code, it.url.url) }
|
||||
myEmojiSet.firstOrNull { it.code == possibleEmoji }?.let { EmojiUrlTag(it.code, it.link.url) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,12 +555,12 @@ open class NewProductViewModel :
|
||||
}
|
||||
|
||||
open fun autocompleteWithEmojiUrl(item: Account.EmojiMedia) {
|
||||
val wordToInsert = item.url.url + " "
|
||||
val wordToInsert = item.link.url + " "
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
iMetaDescription.downloadAndPrepare(
|
||||
item.url.url,
|
||||
{ Amethyst.instance.okHttpClients.getHttpClient(accountViewModel?.account?.shouldUseTorForImageDownload() ?: false) },
|
||||
item.link.url,
|
||||
{ Amethyst.instance.okHttpClients.getHttpClient(accountViewModel?.account?.shouldUseTorForImageDownload(item.link.url) ?: false) },
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user