mirror of
https://github.com/jellyfin/jellyfin-androidtv.git
synced 2025-02-17 05:20:14 +00:00
Replace Glide with Coil
This commit is contained in:
parent
40f186f4a0
commit
387baf93b6
@ -1,7 +1,6 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
kotlin("android")
|
||||
alias(libs.plugins.kotlin.ksp)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
alias(libs.plugins.aboutlibraries)
|
||||
}
|
||||
@ -149,8 +148,7 @@ dependencies {
|
||||
|
||||
// Image utility
|
||||
implementation(libs.blurhash)
|
||||
implementation(libs.glide.core)
|
||||
ksp(libs.glide.ksp)
|
||||
implementation(libs.bundles.coil)
|
||||
|
||||
// Crash Reporting
|
||||
implementation(libs.bundles.acra)
|
||||
|
@ -9,7 +9,6 @@ import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.await
|
||||
import com.bumptech.glide.Glide
|
||||
import com.vanniktech.blurhash.BlurHash
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@ -70,13 +69,6 @@ class JellyfinApplication : Application() {
|
||||
super.onLowMemory()
|
||||
|
||||
BlurHash.clearCache()
|
||||
Glide.with(this).onLowMemory()
|
||||
}
|
||||
|
||||
override fun onTrimMemory(level: Int) {
|
||||
super.onTrimMemory(level)
|
||||
|
||||
Glide.with(this).onTrimMemory(level)
|
||||
}
|
||||
|
||||
override fun attachBaseContext(base: Context?) {
|
||||
|
@ -1,22 +0,0 @@
|
||||
package org.jellyfin.androidtv
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.bumptech.glide.GlideBuilder
|
||||
import com.bumptech.glide.annotation.GlideModule
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.module.AppGlideModule
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
|
||||
@GlideModule
|
||||
class JellyfinGlideModule : AppGlideModule() {
|
||||
override fun applyOptions(context: Context, builder: GlideBuilder): Unit = with(builder) {
|
||||
setDefaultRequestOptions(
|
||||
// Set default disk cache strategy
|
||||
RequestOptions().diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||
)
|
||||
|
||||
// Silence image load errors
|
||||
setLogLevel(Log.ERROR)
|
||||
}
|
||||
}
|
@ -3,12 +3,12 @@ package org.jellyfin.androidtv.data.service
|
||||
import android.content.Context
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import com.bumptech.glide.Glide
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
@ -20,9 +20,7 @@ import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.imageApi
|
||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||
import org.jellyfin.sdk.model.api.ImageType
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.ExecutionException
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
@ -32,6 +30,7 @@ class BackgroundService(
|
||||
private val jellyfin: Jellyfin,
|
||||
private val api: ApiClient,
|
||||
private val userPreferences: UserPreferences,
|
||||
private val imageLoader: ImageLoader,
|
||||
) {
|
||||
companion object {
|
||||
val SLIDESHOW_DURATION = 30.seconds
|
||||
@ -106,22 +105,11 @@ class BackgroundService(
|
||||
// Cancel current loading job
|
||||
loadBackgroundsJob?.cancel()
|
||||
loadBackgroundsJob = scope.launch(Dispatchers.IO) {
|
||||
_backgrounds = backdropUrls
|
||||
.map { url ->
|
||||
Glide.with(context).asBitmap().load(url).submit()
|
||||
}
|
||||
.map { future ->
|
||||
async {
|
||||
try {
|
||||
future.get().asImageBitmap()
|
||||
} catch (ex: ExecutionException) {
|
||||
Timber.e(ex, "There was an error fetching the background image.")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
.filterNotNull()
|
||||
_backgrounds = backdropUrls.mapNotNull { url ->
|
||||
imageLoader.execute(
|
||||
request = ImageRequest.Builder(context).data(url).build()
|
||||
).drawable?.toBitmap()?.asImageBitmap()
|
||||
}
|
||||
|
||||
// Go to first background
|
||||
_currentIndex = 0
|
||||
|
@ -1,6 +1,11 @@
|
||||
package org.jellyfin.androidtv.di
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import coil.ImageLoader
|
||||
import coil.decode.GifDecoder
|
||||
import coil.decode.ImageDecoderDecoder
|
||||
import coil.decode.SvgDecoder
|
||||
import org.jellyfin.androidtv.BuildConfig
|
||||
import org.jellyfin.androidtv.auth.repository.ServerRepository
|
||||
import org.jellyfin.androidtv.auth.repository.UserRepository
|
||||
@ -90,6 +95,17 @@ val appModule = module {
|
||||
)
|
||||
}
|
||||
|
||||
// Coil (images)
|
||||
single {
|
||||
ImageLoader.Builder(androidContext()).apply {
|
||||
components {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) add(ImageDecoderDecoder.Factory())
|
||||
else add(GifDecoder.Factory())
|
||||
add(SvgDecoder.Factory())
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
// Non API related
|
||||
single { DataRefreshService() }
|
||||
single { PlaybackControllerContainer() }
|
||||
@ -110,7 +126,7 @@ val appModule = module {
|
||||
viewModel { ScreensaverViewModel(get()) }
|
||||
viewModel { SearchViewModel(get()) }
|
||||
|
||||
single { BackgroundService(get(), get(), get(), get()) }
|
||||
single { BackgroundService(get(), get(), get(), get(), get()) }
|
||||
|
||||
single { MarkdownRenderer(get()) }
|
||||
|
||||
|
@ -8,7 +8,9 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.bumptech.glide.Glide
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
@ -27,7 +29,6 @@ import org.jellyfin.sdk.model.api.ImageType
|
||||
import org.jellyfin.sdk.model.constant.ItemSortBy
|
||||
import org.koin.androidx.compose.get
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.ExecutionException
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Composable
|
||||
@ -35,6 +36,7 @@ fun DreamHost() {
|
||||
val api = get<ApiClient>()
|
||||
val userPreferences = get<UserPreferences>()
|
||||
val mediaManager = get<MediaManager>()
|
||||
val imageLoader = get<ImageLoader>()
|
||||
val context = LocalContext.current
|
||||
|
||||
var libraryShowcase by remember { mutableStateOf<DreamContent.LibraryShowcase?>(null) }
|
||||
@ -44,7 +46,7 @@ fun DreamHost() {
|
||||
delay(2.seconds)
|
||||
|
||||
while (true) {
|
||||
libraryShowcase = getRandomLibraryShowcase(api, context)
|
||||
libraryShowcase = getRandomLibraryShowcase(api, imageLoader, context)
|
||||
delay(30.seconds)
|
||||
}
|
||||
}
|
||||
@ -62,7 +64,11 @@ fun DreamHost() {
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getRandomLibraryShowcase(api: ApiClient, context: Context): DreamContent.LibraryShowcase? {
|
||||
private suspend fun getRandomLibraryShowcase(
|
||||
api: ApiClient,
|
||||
imageLoader: ImageLoader,
|
||||
context: Context
|
||||
): DreamContent.LibraryShowcase? {
|
||||
try {
|
||||
val response by api.itemsApi.getItemsByUserId(
|
||||
includeItemTypes = listOf(BaseItemKind.MOVIE, BaseItemKind.SERIES),
|
||||
@ -89,12 +95,9 @@ private suspend fun getRandomLibraryShowcase(api: ApiClient, context: Context):
|
||||
)
|
||||
|
||||
val backdrop = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
Glide.with(context).asBitmap().load(backdropUrl).submit().get()
|
||||
} catch (err: ExecutionException) {
|
||||
Timber.e("Unable to retrieve image for item ${item.id}", err)
|
||||
null
|
||||
}
|
||||
imageLoader.execute(
|
||||
request = ImageRequest.Builder(context).data(backdropUrl).build()
|
||||
).drawable?.toBitmap()
|
||||
} ?: return null
|
||||
|
||||
return DreamContent.LibraryShowcase(item, backdrop)
|
||||
|
@ -7,18 +7,18 @@ import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.ParcelFileDescriptor
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import org.jellyfin.androidtv.BuildConfig
|
||||
import org.jellyfin.androidtv.R
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.io.IOException
|
||||
|
||||
class ImageProvider : ContentProvider() {
|
||||
private val imageLoader by inject<ImageLoader>()
|
||||
|
||||
override fun onCreate(): Boolean = true
|
||||
|
||||
override fun getType(uri: Uri) = null
|
||||
@ -33,30 +33,40 @@ class ImageProvider : ContentProvider() {
|
||||
val (read, write) = ParcelFileDescriptor.createPipe()
|
||||
val outputStream = ParcelFileDescriptor.AutoCloseOutputStream(write)
|
||||
|
||||
ProcessLifecycleOwner.get().lifecycleScope.launch(Dispatchers.IO) {
|
||||
Glide.with(context!!)
|
||||
.asBitmap()
|
||||
.error(R.drawable.placeholder_icon)
|
||||
.load(src)
|
||||
.into(object : CustomTarget<Bitmap>() {
|
||||
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
||||
@Suppress("DEPRECATION")
|
||||
val format = when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> Bitmap.CompressFormat.WEBP_LOSSY
|
||||
else -> Bitmap.CompressFormat.WEBP
|
||||
}
|
||||
resource.compress(format, 95, outputStream)
|
||||
outputStream.close()
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) = outputStream.close()
|
||||
})
|
||||
}
|
||||
imageLoader.enqueue(ImageRequest.Builder(context!!).apply {
|
||||
data(src)
|
||||
error(R.drawable.placeholder_icon)
|
||||
target(
|
||||
onSuccess = { drawable -> writeDrawable(drawable, outputStream) },
|
||||
onError = { drawable -> writeDrawable(requireNotNull(drawable), outputStream) }
|
||||
)
|
||||
}.build())
|
||||
|
||||
return read
|
||||
}
|
||||
|
||||
private fun writeDrawable(
|
||||
drawable: Drawable,
|
||||
outputStream: ParcelFileDescriptor.AutoCloseOutputStream
|
||||
) {
|
||||
@Suppress("DEPRECATION")
|
||||
val format = when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> Bitmap.CompressFormat.WEBP_LOSSY
|
||||
else -> Bitmap.CompressFormat.WEBP
|
||||
}
|
||||
|
||||
try {
|
||||
outputStream.use {
|
||||
drawable.toBitmap().compress(format, COMPRESSION_QUALITY, outputStream)
|
||||
}
|
||||
} catch (_: IOException) {
|
||||
// Ignore IOException as this is commonly thrown when the load request is cancelled
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val COMPRESSION_QUALITY = 95
|
||||
|
||||
/**
|
||||
* Get a [Uri] that uses the [ImageProvider] to load an image. The input should be a valid
|
||||
* Jellyfin image URL created using the SDK.
|
||||
|
@ -9,15 +9,16 @@ import androidx.core.graphics.drawable.toDrawable
|
||||
import androidx.core.view.doOnAttach
|
||||
import androidx.lifecycle.findViewTreeLifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
import com.bumptech.glide.load.model.LazyHeaders
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import coil.transform.CircleCropTransformation
|
||||
import com.vanniktech.blurhash.BlurHash
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.androidtv.R
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import kotlin.math.round
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
@ -30,9 +31,10 @@ class AsyncImageView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
) : AppCompatImageView(context, attrs, defStyleAttr) {
|
||||
) : AppCompatImageView(context, attrs, defStyleAttr), KoinComponent {
|
||||
private val lifeCycleOwner get() = findViewTreeLifecycleOwner()
|
||||
private val styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.AsyncImageView, defStyleAttr, 0)
|
||||
private val imageLoader by inject<ImageLoader>()
|
||||
|
||||
/**
|
||||
* The duration of the crossfade when changing switching the images of the url, blurhash and
|
||||
@ -73,21 +75,20 @@ class AsyncImageView @JvmOverloads constructor(
|
||||
|
||||
// Start loading image or placeholder
|
||||
if (url == null) {
|
||||
Glide.with(this@AsyncImageView).load(placeholder).apply {
|
||||
if (circleCrop) circleCrop()
|
||||
}.into(this@AsyncImageView)
|
||||
} else {
|
||||
val glideUrl = GlideUrl(url, LazyHeaders.Builder().apply {
|
||||
setHeader("Accept", ApiClient.HEADER_ACCEPT)
|
||||
imageLoader.enqueue(ImageRequest.Builder(context).apply {
|
||||
target(this@AsyncImageView)
|
||||
data(placeholder)
|
||||
if (circleCrop) transformations(CircleCropTransformation())
|
||||
}.build())
|
||||
|
||||
Glide.with(this@AsyncImageView).load(glideUrl).apply {
|
||||
} else {
|
||||
imageLoader.enqueue(ImageRequest.Builder(context).apply {
|
||||
crossfade(crossFadeDuration.inWholeMilliseconds.toInt())
|
||||
target(this@AsyncImageView)
|
||||
data(url)
|
||||
placeholder(placeholderOrBlurHash)
|
||||
if (circleCrop) transformations(CircleCropTransformation())
|
||||
error(placeholder)
|
||||
if (circleCrop) circleCrop()
|
||||
// FIXME: Glide is unable to scale the image when transitions are enabled
|
||||
//transition(DrawableTransitionOptions.withCrossFade(crossFadeDuration.inWholeMilliseconds.toInt()))
|
||||
}.into(this@AsyncImageView)
|
||||
}.build())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,15 @@
|
||||
package org.jellyfin.androidtv.ui.home
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageButton
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.CustomViewTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jellyfin.androidtv.R
|
||||
import org.jellyfin.androidtv.auth.repository.SessionRepository
|
||||
@ -59,8 +54,10 @@ class HomeFragment : Fragment() {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
userRepository.currentUser.collect { user ->
|
||||
if (user != null) {
|
||||
val image = ImageUtils.getPrimaryImageUrl(user)
|
||||
setUserImage(image)
|
||||
binding.switchUsersImage.load(
|
||||
url = ImageUtils.getPrimaryImageUrl(user),
|
||||
placeholder = ContextCompat.getDrawable(requireContext(), R.drawable.ic_user)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -73,36 +70,6 @@ class HomeFragment : Fragment() {
|
||||
_binding = null
|
||||
}
|
||||
|
||||
private fun setUserImage(image: String?) {
|
||||
Glide.with(this)
|
||||
.load(image)
|
||||
.placeholder(R.drawable.ic_switch_users)
|
||||
.centerInside()
|
||||
.circleCrop()
|
||||
.into(object : CustomViewTarget<ImageButton, Drawable>(binding.switchUsers) {
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
|
||||
binding.switchUsers.imageTintMode = PorterDuff.Mode.SRC_IN
|
||||
binding.switchUsers.setImageDrawable(errorDrawable)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
||||
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
|
||||
binding.switchUsers.imageTintMode = null
|
||||
binding.switchUsers.setImageDrawable(resource)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResourceCleared(placeholder: Drawable?) {
|
||||
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
|
||||
binding.switchUsers.imageTintMode = PorterDuff.Mode.SRC_IN
|
||||
binding.switchUsers.setImageDrawable(placeholder)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun switchUser() {
|
||||
sessionRepository.destroyCurrentSession()
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
@ -40,13 +41,21 @@
|
||||
android:layout_width="8dp"
|
||||
android:layout_height="0dp" />
|
||||
|
||||
<ImageButton
|
||||
<FrameLayout
|
||||
android:id="@+id/switch_users"
|
||||
style="@style/Button.Icon"
|
||||
android:layout_width="41dp"
|
||||
android:layout_height="41dp"
|
||||
android:contentDescription="@string/lbl_switch_user"
|
||||
android:src="@drawable/ic_switch_users" />
|
||||
android:contentDescription="@string/lbl_switch_user">
|
||||
|
||||
<org.jellyfin.androidtv.ui.AsyncImageView
|
||||
android:id="@+id/switch_users_image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="4dp"
|
||||
app:circleCrop="true"
|
||||
tools:src="@drawable/ic_user" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
</org.jellyfin.androidtv.ui.shared.ToolbarView>
|
||||
|
||||
|
@ -23,9 +23,9 @@ androidx-tvprovider = "1.1.0-alpha01"
|
||||
androidx-window = "1.1.0"
|
||||
androidx-work = "2.8.1"
|
||||
blurhash = "0.1.0"
|
||||
coil = "2.4.0"
|
||||
detekt = "1.23.0"
|
||||
exoplayer = "2.18.7"
|
||||
glide = "4.15.1"
|
||||
gson = "2.8.9"
|
||||
jellyfin-apiclient = "v0.7.10"
|
||||
jellyfin-exoplayer-ffmpegextension = "2.18.7+1"
|
||||
@ -35,7 +35,6 @@ koin = "3.4.2"
|
||||
koin-compose = "3.4.5"
|
||||
kotest = "5.6.2"
|
||||
kotlin = "1.8.22"
|
||||
kotlin-ksp = "1.8.22-1.0.11"
|
||||
kotlinx-coroutines = "1.7.2"
|
||||
kotlinx-serialization = "1.5.1"
|
||||
leakcanary = "2.12"
|
||||
@ -48,7 +47,6 @@ timber = "5.0.1"
|
||||
[plugins]
|
||||
aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutlibraries" }
|
||||
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
|
||||
kotlin-ksp = { id = "com.google.devtools.ksp", version.ref = "kotlin-ksp" }
|
||||
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||
|
||||
[libraries]
|
||||
@ -105,8 +103,9 @@ markwon-html = { module = "io.noties.markwon:html", version.ref = "markwon" }
|
||||
|
||||
# Image utility
|
||||
blurhash = { module = "com.vanniktech:blurhash", version.ref = "blurhash" }
|
||||
glide-core = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
|
||||
glide-ksp = { module = "com.github.bumptech.glide:ksp", version.ref = "glide" }
|
||||
coil-base = { module = "io.coil-kt:coil-base", version.ref = "coil" }
|
||||
coil-gif = { module = "io.coil-kt:coil-gif", version.ref = "coil" }
|
||||
coil-svg = { module = "io.coil-kt:coil-svg", version.ref = "coil" }
|
||||
|
||||
# Crash Reporting
|
||||
acra-core = { module = "ch.acra:acra-core", version.ref = "acra" }
|
||||
@ -146,6 +145,11 @@ androidx-lifecycle = [
|
||||
"androidx-lifecycle-service",
|
||||
"androidx-lifecycle-viewmodel",
|
||||
]
|
||||
coil = [
|
||||
"coil-base",
|
||||
"coil-gif",
|
||||
"coil-svg",
|
||||
]
|
||||
koin = [
|
||||
"koin-android-compat",
|
||||
"koin-android-core",
|
||||
|
Loading…
x
Reference in New Issue
Block a user