mirror of
https://github.com/jellyfin/jellyfin-android.git
synced 2026-10-26 16:45:26 +00:00
Migrate downloads UI to Compose (#1948)
This commit is contained in:
1 parent
9fa2a96d31
commit
146db28af5
13 files changed
+244
-204
No files matched your search
@@ -1,14 +0,0 @@
|
||||
package org.jellyfin.mobile.downloads
|
||||
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import org.jellyfin.mobile.data.entity.DownloadEntity
|
||||
|
||||
class DownloadDiffCallback : DiffUtil.ItemCallback<DownloadEntity>() {
|
||||
override fun areItemsTheSame(oldItem: DownloadEntity, newItem: DownloadEntity): Boolean {
|
||||
return oldItem.itemId == newItem.itemId
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: DownloadEntity, newItem: DownloadEntity): Boolean {
|
||||
return oldItem == newItem
|
||||
}
|
||||
}
|
||||
@@ -48,16 +48,14 @@ object DownloadServiceUtil : KoinComponent {
|
||||
@Synchronized
|
||||
private fun ensureDownloadManagerInitialized(context: Context) {
|
||||
if (downloadManager == null) {
|
||||
downloadManager =
|
||||
DownloadManager(
|
||||
context,
|
||||
databaseProvider,
|
||||
downloadCache,
|
||||
dataSourceFactory,
|
||||
Executors.newFixedThreadPool(DOWNLOAD_THREADS),
|
||||
)
|
||||
downloadTracker =
|
||||
DownloadTracker(downloadManager!!)
|
||||
downloadManager = DownloadManager(
|
||||
context,
|
||||
databaseProvider,
|
||||
downloadCache,
|
||||
dataSourceFactory,
|
||||
Executors.newFixedThreadPool(DOWNLOAD_THREADS),
|
||||
)
|
||||
downloadTracker = DownloadTracker(downloadManager!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,9 @@ class DownloadTracker(downloadManager: DownloadManager) {
|
||||
|
||||
private val listeners: CopyOnWriteArraySet<Listener> = CopyOnWriteArraySet()
|
||||
private val downloads: HashMap<Uri, Download> = HashMap()
|
||||
private val downloadIndex: DownloadIndex
|
||||
private val downloadIndex: DownloadIndex = downloadManager.downloadIndex
|
||||
|
||||
init {
|
||||
downloadIndex = downloadManager.downloadIndex
|
||||
downloadManager.addListener(DownloadManagerListener())
|
||||
loadDownloads()
|
||||
}
|
||||
|
||||
@@ -151,9 +151,10 @@ class DownloadUtils(
|
||||
BaseItemKind.EPISODE,
|
||||
BaseItemKind.MOVIE,
|
||||
BaseItemKind.VIDEO,
|
||||
BaseItemKind.MUSIC_VIDEO,
|
||||
)
|
||||
|
||||
private suspend fun downloadFiles(jellyfinMediaSource: JellyfinMediaSource) {
|
||||
private fun downloadFiles(jellyfinMediaSource: JellyfinMediaSource) {
|
||||
val jellyfinDownloadTracker = JellyfinDownloadTracker(jellyfinMediaSource)
|
||||
downloadTracker.addListener(jellyfinDownloadTracker)
|
||||
downloadMediaFile(jellyfinMediaSource)
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
package org.jellyfin.mobile.downloads
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil3.load
|
||||
import coil3.request.error
|
||||
import coil3.request.fallback
|
||||
import org.jellyfin.mobile.R
|
||||
import org.jellyfin.mobile.data.entity.DownloadEntity
|
||||
import org.jellyfin.mobile.databinding.DownloadItemBinding
|
||||
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 java.util.UUID
|
||||
|
||||
class DownloadsAdapter(
|
||||
private val apiClient: ApiClient,
|
||||
private val onItemClick: (DownloadEntity) -> Unit,
|
||||
private val onItemHold: (DownloadEntity) -> Unit,
|
||||
) : ListAdapter<DownloadEntity, DownloadsAdapter.DownloadViewHolder>(
|
||||
DownloadDiffCallback(),
|
||||
) {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadViewHolder {
|
||||
val binding = DownloadItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return DownloadViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: DownloadViewHolder, position: Int) {
|
||||
holder.bind(getItem(position))
|
||||
}
|
||||
|
||||
inner class DownloadViewHolder(private val binding: DownloadItemBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(downloadEntity: DownloadEntity) {
|
||||
val context = itemView.context
|
||||
|
||||
val mediaItem: BaseItemDto? = downloadEntity.mediaSource.item
|
||||
binding.textViewName.text = downloadEntity.mediaSource.getName(context)
|
||||
binding.textViewDescription.text = when {
|
||||
mediaItem?.seriesName != null -> context.getString(
|
||||
R.string.tv_show_desc,
|
||||
mediaItem.seriesName,
|
||||
mediaItem.parentIndexNumber,
|
||||
mediaItem.indexNumber,
|
||||
)
|
||||
mediaItem?.productionYear != null -> mediaItem.productionYear.toString()
|
||||
else -> downloadEntity.mediaSource.id
|
||||
}
|
||||
binding.textViewFileSize.text = downloadEntity.fileSize
|
||||
|
||||
itemView.setOnClickListener {
|
||||
onItemClick(downloadEntity)
|
||||
}
|
||||
itemView.setOnLongClickListener {
|
||||
onItemHold(downloadEntity)
|
||||
true
|
||||
}
|
||||
|
||||
val thumbnailUrl = getThumbnailUrl(context, downloadEntity.mediaSource.itemId)
|
||||
binding.imageViewThumbnail.load(thumbnailUrl) {
|
||||
fallback(R.drawable.ic_local_movies_white_64)
|
||||
error(R.drawable.ic_local_movies_white_64)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getThumbnailUrl(context: Context, mediaSourceItemId: UUID): String? {
|
||||
val size = context.resources.getDimensionPixelSize(R.dimen.movie_thumbnail_list_size)
|
||||
|
||||
return apiClient.imageApi.getItemImageUrl(
|
||||
itemId = mediaSourceItemId,
|
||||
imageType = ImageType.PRIMARY,
|
||||
maxWidth = size,
|
||||
maxHeight = size,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,76 +1,16 @@
|
||||
package org.jellyfin.mobile.downloads
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jellyfin.mobile.R
|
||||
import org.jellyfin.mobile.data.entity.DownloadEntity
|
||||
import org.jellyfin.mobile.databinding.FragmentDownloadsBinding
|
||||
import org.jellyfin.mobile.events.ActivityEvent
|
||||
import org.jellyfin.mobile.events.ActivityEventHandler
|
||||
import org.jellyfin.mobile.player.interaction.PlayOptions
|
||||
import org.jellyfin.mobile.utils.applyWindowInsetsAsMargins
|
||||
import org.jellyfin.mobile.utils.extensions.requireMainActivity
|
||||
import org.jellyfin.mobile.utils.withThemedContext
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import androidx.compose.runtime.Composable
|
||||
import org.jellyfin.mobile.ui.ComposeFragment
|
||||
import org.jellyfin.mobile.ui.screens.downloads.DownloadsScreen
|
||||
|
||||
class DownloadsFragment : Fragment(), KoinComponent {
|
||||
private val viewModel: DownloadsViewModel by inject()
|
||||
private val activityEventHandler: ActivityEventHandler by inject()
|
||||
private val apiClient: ApiClient by inject()
|
||||
private lateinit var adapter: DownloadsAdapter
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
val localInflater = inflater.withThemedContext(requireContext(), R.style.AppTheme_Settings)
|
||||
val binding = FragmentDownloadsBinding.inflate(localInflater, container, false)
|
||||
binding.root.applyWindowInsetsAsMargins()
|
||||
binding.toolbar.setTitle(R.string.downloads)
|
||||
|
||||
requireMainActivity().apply {
|
||||
setSupportActionBar(binding.toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
adapter = DownloadsAdapter(
|
||||
apiClient,
|
||||
onItemClick = { download -> onDownloadItemClick(download) },
|
||||
onItemHold = { download -> onDownloadItemHold(download) },
|
||||
class DownloadsFragment : ComposeFragment() {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
DownloadsScreen(
|
||||
onBackPressed = {
|
||||
isAdded && !parentFragmentManager.isStateSaved && parentFragmentManager.popBackStackImmediate()
|
||||
},
|
||||
)
|
||||
binding.recyclerView.adapter = adapter
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
viewModel.downloads.collect { downloads ->
|
||||
adapter.submitList(downloads)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
private fun onDownloadItemClick(download: DownloadEntity) {
|
||||
val playOptions = PlayOptions(
|
||||
ids = listOf(download.mediaSource.itemId),
|
||||
mediaSourceId = download.mediaSource.id,
|
||||
startIndex = 0,
|
||||
startPosition = null,
|
||||
audioStreamIndex = 1,
|
||||
subtitleStreamIndex = -1,
|
||||
playFromDownloads = true,
|
||||
)
|
||||
activityEventHandler.emit(ActivityEvent.LaunchNativePlayer(playOptions))
|
||||
}
|
||||
|
||||
private fun onDownloadItemHold(download: DownloadEntity) {
|
||||
activityEventHandler.emit(ActivityEvent.RemoveDownload(download.mediaSource))
|
||||
}
|
||||
}
|
||||
@@ -3,35 +3,42 @@ package org.jellyfin.mobile.downloads
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import org.jellyfin.mobile.data.dao.DownloadDao
|
||||
import org.jellyfin.mobile.data.entity.DownloadEntity
|
||||
import org.jellyfin.mobile.events.ActivityEvent
|
||||
import org.jellyfin.mobile.events.ActivityEventHandler
|
||||
import org.jellyfin.mobile.player.interaction.PlayOptions
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class DownloadsViewModel : ViewModel(), KoinComponent {
|
||||
|
||||
private val downloadDao: DownloadDao by inject()
|
||||
private val activityEventHandler: ActivityEventHandler by inject()
|
||||
|
||||
// This is a mutable state flow that will be used internally in the viewmodel, empty list is given as initial value.
|
||||
private val _downloads = MutableStateFlow(emptyList<DownloadEntity>())
|
||||
val downloads: StateFlow<List<DownloadEntity>> = downloadDao
|
||||
.getAllDownloads()
|
||||
.flowOn(Dispatchers.IO)
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
|
||||
|
||||
// Immutable state flow that you expose to your UI
|
||||
val downloads = _downloads.asStateFlow()
|
||||
|
||||
init {
|
||||
getAllDownloads()
|
||||
fun playDownload(download: DownloadEntity) {
|
||||
val playOptions = PlayOptions(
|
||||
ids = listOf(download.mediaSource.itemId),
|
||||
mediaSourceId = download.mediaSource.id,
|
||||
startIndex = 0,
|
||||
startPosition = null,
|
||||
audioStreamIndex = 1,
|
||||
subtitleStreamIndex = -1,
|
||||
playFromDownloads = true,
|
||||
)
|
||||
activityEventHandler.emit(ActivityEvent.LaunchNativePlayer(playOptions))
|
||||
}
|
||||
|
||||
private fun getAllDownloads() {
|
||||
viewModelScope.launch { // this: CoroutineScope
|
||||
downloadDao.getAllDownloads().flowOn(Dispatchers.IO).collect { downloads: List<DownloadEntity> ->
|
||||
_downloads.update { downloads }
|
||||
}
|
||||
}
|
||||
fun removeDownload(download: DownloadEntity, force: Boolean = false) {
|
||||
activityEventHandler.emit(ActivityEvent.RemoveDownload(download.mediaSource, force))
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class JellyfinDownloadService : DownloadService(
|
||||
return downloadManager
|
||||
}
|
||||
|
||||
override fun getScheduler(): Scheduler? {
|
||||
override fun getScheduler(): Scheduler {
|
||||
return PlatformScheduler(this, jobId)
|
||||
}
|
||||
|
||||
|
||||
@@ -164,14 +164,16 @@ sealed class JellyfinMediaSource(
|
||||
val name = if (
|
||||
it.type in arrayOf(BaseItemKind.PROGRAM, BaseItemKind.RECORDING) &&
|
||||
(it.isSeries == true || !it.episodeTitle.isNullOrEmpty())
|
||||
) { it.episodeTitle } else { it.name }
|
||||
) {
|
||||
it.episodeTitle
|
||||
} else {
|
||||
it.name
|
||||
}
|
||||
|
||||
val specialEpisode = context.getString(R.string.special_episode)
|
||||
val extraInfo = when {
|
||||
it.type == BaseItemKind.TV_CHANNEL && !it.channelNumber.isNullOrEmpty() -> it.channelNumber
|
||||
it.type == BaseItemKind.EPISODE && it.parentIndexNumber == 0 -> specialEpisode
|
||||
it.type in arrayOf(BaseItemKind.EPISODE, BaseItemKind.RECORDING) &&
|
||||
it.indexNumber != null && it.parentIndexNumber != null ->
|
||||
val extraInfo = when (it.type) {
|
||||
BaseItemKind.TV_CHANNEL if !it.channelNumber.isNullOrEmpty() -> it.channelNumber
|
||||
BaseItemKind.EPISODE if it.parentIndexNumber == 0 -> context.getString(R.string.special_episode)
|
||||
in arrayOf(BaseItemKind.EPISODE, BaseItemKind.RECORDING) if it.indexNumber != null && it.parentIndexNumber != null ->
|
||||
"S${it.parentIndexNumber}:E${it.indexNumber}${it.indexNumberEnd?.let { n -> "-$n" } ?: ""}"
|
||||
else -> ""
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.jellyfin.mobile.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.fragment.app.Fragment
|
||||
import org.jellyfin.mobile.R
|
||||
import org.jellyfin.mobile.ui.utils.AppTheme
|
||||
|
||||
abstract class ComposeFragment : Fragment(R.layout.fragment_compose) {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val composeView: ComposeView = view.findViewById(R.id.compose_view)
|
||||
composeView.setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
|
||||
composeView.setContent {
|
||||
AppTheme {
|
||||
Surface(color = MaterialTheme.colors.background) {
|
||||
Content()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
abstract fun Content()
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package org.jellyfin.mobile.ui.screens.downloads
|
||||
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.ListItem
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalResources
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import coil3.compose.AsyncImage
|
||||
import org.jellyfin.mobile.R
|
||||
import org.jellyfin.mobile.data.entity.DownloadEntity
|
||||
import org.jellyfin.mobile.downloads.DownloadsViewModel
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.imageApi
|
||||
import org.jellyfin.sdk.model.api.ImageType
|
||||
import org.koin.compose.koinInject
|
||||
|
||||
@Composable
|
||||
fun DownloadsList(
|
||||
viewModel: DownloadsViewModel = viewModel(),
|
||||
contentPadding: PaddingValues = PaddingValues.Zero,
|
||||
) {
|
||||
val downloads by viewModel.downloads.collectAsState()
|
||||
LazyColumn(
|
||||
contentPadding = contentPadding,
|
||||
) {
|
||||
items(
|
||||
downloads,
|
||||
key = DownloadEntity::itemId,
|
||||
) { download ->
|
||||
DownloadItem(
|
||||
download,
|
||||
modifier = Modifier.combinedClickable(
|
||||
onClick = { viewModel.playDownload(download) },
|
||||
onLongClick = { viewModel.removeDownload(download) },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun DownloadItem(
|
||||
download: DownloadEntity,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val apiClient: ApiClient = koinInject()
|
||||
|
||||
ListItem(
|
||||
modifier = modifier,
|
||||
text = {
|
||||
val name = remember(download.mediaSource.itemId) {
|
||||
download.mediaSource.getName(context)
|
||||
}
|
||||
Text(
|
||||
text = name,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
},
|
||||
icon = {
|
||||
val maxSize = LocalResources.current.getDimensionPixelSize(R.dimen.movie_thumbnail_list_size)
|
||||
val url = remember(download.mediaSource.itemId) {
|
||||
apiClient.imageApi.getItemImageUrl(
|
||||
itemId = download.mediaSource.itemId,
|
||||
imageType = ImageType.PRIMARY,
|
||||
maxWidth = maxSize,
|
||||
maxHeight = maxSize,
|
||||
)
|
||||
}
|
||||
|
||||
AsyncImage(
|
||||
model = url,
|
||||
placeholder = painterResource(R.drawable.ic_local_movies_white_64),
|
||||
fallback = painterResource(R.drawable.ic_local_movies_white_64),
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
secondaryText = {
|
||||
Text(
|
||||
text = download.fileSize,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
},
|
||||
singleLineSecondaryText = true,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.jellyfin.mobile.ui.screens.downloads
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import org.jellyfin.mobile.R
|
||||
import org.jellyfin.mobile.downloads.DownloadsViewModel
|
||||
|
||||
@Composable
|
||||
fun DownloadsScreen(
|
||||
viewModel: DownloadsViewModel = viewModel(),
|
||||
onBackPressed: () -> Unit = {},
|
||||
) {
|
||||
Scaffold(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.systemBarsPadding(),
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(text = stringResource(R.string.downloads))
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = onBackPressed,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Outlined.ArrowBack,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
backgroundColor = Color.Transparent,
|
||||
)
|
||||
},
|
||||
content = { innerPadding ->
|
||||
DownloadsList(
|
||||
viewModel = viewModel,
|
||||
contentPadding = innerPadding,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -114,6 +114,7 @@ compose-material-icons-extended = { group = "androidx.compose.material", name =
|
||||
jellyfin-sdk = { group = "org.jellyfin.sdk", name = "jellyfin-core", version.ref = "jellyfin-sdk" }
|
||||
coil = { group = "io.coil-kt.coil3", name = "coil", version.ref = "coil" }
|
||||
coil-network = { group = "io.coil-kt.coil3", name = "coil-network-okhttp", version.ref = "coil" }
|
||||
coil-compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coil" }
|
||||
|
||||
# Media
|
||||
androidx-media = { group = "androidx.media", name = "media", version.ref = "androidx-media" }
|
||||
@@ -154,7 +155,7 @@ detekt-formatting = { group = "io.gitlab.arturbosch.detekt", name = "detekt-form
|
||||
[bundles]
|
||||
coroutines = ["coroutines-core", "coroutines-android"]
|
||||
koin = ["koin", "koin-compose"]
|
||||
coil = ["coil", "coil-network"]
|
||||
coil = ["coil", "coil-network", "coil-compose"]
|
||||
androidx-lifecycle = [
|
||||
"androidx-lifecycle-viewmodel",
|
||||
"androidx-lifecycle-livedata",
|
||||
|
||||
Reference in new issue
Block a user